from typing import Callable, Sequence, Type, Any, Iterable

from dedupe import predicates


class Variable(object):
    def __len__(self):
        return 1

    def __repr__(self):
        return self.name

    def __hash__(self):
        return hash(self.name)

    def __eq__(self, other):
        return self.name == other.name

    def __init__(self, definition):

        if definition.get('has missing', False):
            self.has_missing = True
            try:
                exists_pred = predicates.ExistsPredicate(definition['field'])
                self.predicates.append(exists_pred)
            except KeyError:
                pass
        else:
            self.has_missing = False


class DerivedType(Variable):
    type = "Derived"

    def __init__(self, definition):
        self.name = "(%s: %s)" % (str(definition['name']),
                                  str(definition['type']))
        super(DerivedType, self).__init__(definition)


class MissingDataType(Variable):
    type = "MissingData"

    def __init__(self, name):

        self.name = "(%s: Not Missing)" % name

        self.has_missing = False


class FieldType(Variable):
    _index_thresholds: Sequence[float] = []
    _index_predicates: Sequence[Type[predicates.IndexPredicate]] = []
    _predicate_functions: Sequence[Callable[[Any], Iterable[str]]] = ()
    _Predicate = predicates.SimplePredicate

    def __init__(self, definition):
        self.field = definition['field']

        if 'variable name' in definition:
            self.name = definition['variable name']
        else:
            self.name = "(%s: %s)" % (self.field, self.type)

        self.predicates = [self._Predicate(pred, self.field)
                           for pred in self._predicate_functions]

        self.predicates += indexPredicates(self._index_predicates,
                                           self._index_thresholds,
                                           self.field)

        super(FieldType, self).__init__(definition)


class CustomType(FieldType):
    type = "Custom"

    def __init__(self, definition):
        super(CustomType, self).__init__(definition)

        try:
            self.comparator = definition["comparator"]
        except KeyError:
            raise KeyError("For 'Custom' field types you must define "
                           "a 'comparator' function in the field "
                           "definition. ")

        if 'variable name' not in definition:
            self.name = "(%s: %s, %s)" % (self.field,
                                          self.type,
                                          self.comparator.__name__)


def allSubclasses(cls):
    for q in cls.__subclasses__():
        yield q.type, q
        for p in allSubclasses(q):
            yield p


def indexPredicates(predicates, thresholds, field):
    index_predicates = []
    for predicate in predicates:
        for threshold in thresholds:
            index_predicates.append(predicate(threshold, field))

    return index_predicates
