from enum import Enum

from flask_admin.model import filters


class BookmarkField(Enum):
    ID = 0
    URL = 1
    TITLE = 2
    TAGS = 3
    DESCRIPTION = 4


def equal_func(query, value, index):
    return filter(lambda x: x[index] == value, query)


def not_equal_func(query, value, index):
    return filter(lambda x: x[index] != value, query)


def greater_func(query, value, index):
    return filter(lambda x: x[index] > value, query)


def smaller_func(query, value, index):
    return filter(lambda x: x[index] < value, query)


def in_list_func(query, value, index):
    return filter(lambda x: x[index] in value, query)


def not_in_list_func(query, value, index):
    return filter(lambda x: x[index] not in value, query)


def top_x_func(query, value, index):
    items = sorted(set(x[index] for x in query), reverse=True)
    top_x = items[:value]
    return filter(lambda x: x[index] in top_x, query)


def bottom_x_func(query, value, index):
    items = sorted(set(x[index] for x in query), reverse=False)
    top_x = items[:value]
    return filter(lambda x: x[index] in top_x, query)


class FilterType(Enum):

    EQUAL = {'func': equal_func, 'text':'equals'}
    NOT_EQUAL = {'func': not_equal_func, 'text':'not equal'}
    GREATER = {'func': greater_func, 'text':'greater than'}
    SMALLER = {'func': smaller_func, 'text':'smaller than'}
    IN_LIST = {'func': in_list_func, 'text':'in list'}
    NOT_IN_LIST = {'func': not_in_list_func, 'text':'not in list'}
    TOP_X = {'func': top_x_func, 'text': 'top x'}
    BOTTOM_X = {'func': bottom_x_func, 'text': 'bottom x'}


class BaseFilter(filters.BaseFilter):

    def operation(self):
        return getattr(self, 'operation_text')

    def apply(self, query, value):
        return getattr(self, 'apply_func')(query, value, getattr(self, 'index'))


class TagBaseFilter(BaseFilter):

    def __init__(
            self,
            name,
            operation_text=None,
            apply_func=None,
            filter_type=None,
            options=None,
            data_type=None):
        if operation_text in ('in list', 'not in list'):
            super().__init__(name, options, data_type='select2-tags')
        else:
            super().__init__(name, options, data_type)
        if name == 'name':
            self.index = 0
        elif name == 'usage_count':
            self.index = 1
        else:
            raise ValueError('name: {}'.format(name))
        self.filter_type = None
        if filter_type:
            self.apply_func = filter_type.value['func']
            self.operation_text = filter_type.value['text']
            self.filter_type = filter_type
        else:
            self.apply_func = apply_func
            self.operation_text = operation_text

    def clean(self, value):
        if (
                self.filter_type in (FilterType.IN_LIST, FilterType.NOT_IN_LIST) and
                self.name == 'usage_count'):
            value = [int(v.strip()) for v in value.split(',') if v.strip()]
        elif self.filter_type in (FilterType.IN_LIST, FilterType.NOT_IN_LIST):
            value = [v.strip() for v in value.split(',') if v.strip()]
        elif self.name == 'usage_count':
            value = int(value)
            if self.filter_type in (FilterType.TOP_X, FilterType.BOTTOM_X) and value < 1:
                raise ValueError
        if isinstance(value, str):
            return value.strip()
        return value


class BookmarkBukuFilter(BaseFilter):
    keys = {
        'all_keywords': 'match all',
        'deep': 'deep',
        'regex': 'regex'
    }

    def __init__(self, *args, **kwargs):
        self.params = {key: kwargs.pop(key, False) for key in self.keys}
        super().__init__('buku', *args, **kwargs)

    def operation(self):
        parts = ', '.join(v for k, v in self.keys.items() if self.params[k])
        return 'search' + (parts and ' ' + parts)

    def apply(self, query, value):
        return query


class BookmarkBaseFilter(BaseFilter):

    def __init__(
            self,
            name,
            operation_text=None,
            apply_func=None,
            filter_type=None,
            options=None,
            data_type=None):
        if operation_text in ('in list', 'not in list'):
            super().__init__(name, options, data_type='select2-tags')
        else:
            super().__init__(name, options, data_type)
        bm_fields_dict = {x.name.lower(): x.value for x in BookmarkField}
        if name in bm_fields_dict:
            self.index = bm_fields_dict[name]
        else:
            raise ValueError('name: {}'.format(name))
        self.filter_type = None
        if filter_type:
            self.apply_func = filter_type.value['func']
            self.operation_text = filter_type.value['text']
        else:
            self.apply_func = apply_func
            self.operation_text = operation_text

    def clean(self, value):
        if (
                self.filter_type in (FilterType.IN_LIST, FilterType.NOT_IN_LIST) and
                self.name == BookmarkField.ID.name.lower()):
            value = [int(v.strip()) for v in value.split(',') if v.strip()]
        elif self.filter_type in (FilterType.IN_LIST, FilterType.NOT_IN_LIST):
            value = [v.strip() for v in value.split(',') if v.strip()]
        elif self.name == BookmarkField.ID.name.lower():
            value = int(value)
            if self.filter_type in (FilterType.TOP_X, FilterType.BOTTOM_X) and value < 1:
                raise ValueError
        if isinstance(value, str):
            return value.strip()
        return value


class BookmarkTagNumberEqualFilter(BookmarkBaseFilter):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        def apply_func(query, value, index):
            for item in query:
                tags = [tag for tag in item[index].split(',') if tag]
                if len(tags) == value:
                    yield item

        self.apply_func = apply_func

    def clean(self, value):
        value = int(value)
        if value < 0:
            raise ValueError
        return value


class BookmarkTagNumberGreaterFilter(BookmarkTagNumberEqualFilter):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        def apply_func(query, value, index):
            for item in query:
                tags = [tag for tag in item[index].split(',') if tag]
                if len(tags) > value:
                    yield item

        self.apply_func = apply_func


class BookmarkTagNumberNotEqualFilter(BookmarkTagNumberEqualFilter):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        def apply_func(query, value, index):
            for item in query:
                tags = [tag for tag in item[index].split(',') if tag]
                if len(tags) != value:
                    yield item

        self. apply_func = apply_func


class BookmarkTagNumberSmallerFilter(BookmarkBaseFilter):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        def apply_func(query, value, index):
            for item in query:
                tags = [tag for tag in item[index].split(',') if tag]
                if len(tags) < value:
                    yield item

        self.apply_func = apply_func

    def clean(self, value):
        value = int(value)
        if value < 1:
            raise ValueError
        return value
