import math
import re
from abc import ABCMeta, abstractmethod
import numpy as np
import pandas as pd
import json
import operator


class ValidatorBaseClass(metaclass=ABCMeta):

    @abstractmethod
    def validate(self, x):
        raise NotImplementedError()

    @abstractmethod
    def to_dict(self):
        return NotImplementedError()


class Range(ValidatorBaseClass):

    def __init__(self, minimum=None, maximum=None, left_inclusive=True, right_inclusive=False):

        self.min = minimum
        self.max = maximum
        self.li = left_inclusive
        self.ri = right_inclusive
        self.dct = {'min': self.min, 'max': self.max, 'li': self.li, 'ri': self.ri}
        for val in self.dct.keys():
            if isinstance(self.dct[val], str):
                if self.dct[val] != '':
                    self.__setattr__(val, json.loads(self.dct[val]))
                else:
                    self.__setattr__(val, None)
        self.min = self.min if pd.notna(self.min) else -math.inf
        self.max = self.max if pd.notna(self.max) else math.inf

    def validate(self, x):
        y = pd.to_numeric(x.copy())
        if self.li and not self.ri:
            return (self.min <= y) & (y < self.max)
        elif self.li and self.ri:
            return (self.min <= y) & (y <= self.max)
        elif not self.li and self.ri:
            return (self.min < y) & (y <= self.max)
        else:
            return (self.min < y) & (y < self.max)

    def to_dict(self):
        return {'rule_type': 'Range', 'validator_parameters': {'maximum': self.max, 'minimum': self.min,
                                                               'left_inclusive': self.li, 'right_inclusive': self.ri}}

    def __repr__(self):
        return json.dumps({'name': 'بازه عددی', 'desc': 'Lorem Ipsom', 'example': 'Lorem Ipson', 'code': 'Range',
                           'params': [
                               {'name': 'کمینه', 'code': 'minimum', 'default': '-∞', 'type': 'number', 'tag': 'input'},
                               {'name': 'بیشینه', 'code': 'maximum', 'default': '∞', 'type': 'number', 'tag': 'input'},
                               {'name': 'شامل کمینه', 'code': 'left_inclusive', 'default': True, 'type': 'bool',
                                'tag': 'select', 'options': [{'text': 'بلی', 'value': True},
                                                             {'text': 'خیر', 'value': False}]},
                               {'name': 'شامل بیشینه', 'code': 'right_inclusive', 'default': True, 'type': 'bool',
                                'tag': 'select', 'options': [{'text': 'بلی', 'value': True},
                                                             {'text': 'خیر', 'value': False}]}]})


class Logic(ValidatorBaseClass):
    def __init__(self, op_type='eq', value=None):
        self.op_type = op_type
        self.value = value

    def validate(self, x):
        if self.op_type == 'lt':
            self.value = int(self.value)
            return operator.lt(x, self.value)
        elif self.op_type == 'le':
            self.value = int(self.value)
            return operator.le(x, self.value)
        elif self.op_type == 'eq':
            return operator.eq(x, self.value)
        elif self.op_type == 'nq':
            return operator.nq(x, self.value)
        elif self.op_type == 'ge':
            self.value = int(self.value)
            return operator.ge(x, self.value)
        elif self.op_type == 'gt':
            self.value = int(self.value)
            return operator.gt(x, self.value)

    def to_dict(self):
        return {'rule_type': 'Logic', 'validator_parameters': {'op_type': self.op_type,
                                                               'value': self.value}}

    def __repr__(self):
        return json.dumps({'name': 'عملگرهای منطقی', 'desc': 'Lorem Ipsom', 'example': 'Lorem Ipson', 'code': 'Logic',
                           'params': [
                               {'name': 'نوع عملگر', 'code': 'op_type', 'default': 'eq', 'type': 'text', 'tag': 'select',
                                'options': [{'text': 'مساوی است با', 'value': 'eq'},
                                            {'text': 'نامساوی است با', 'value': 'neq'},
                                            {'text': 'کوچکتر است از', 'value': 'lt'},
                                            {'text': 'کوچکتر مساوی است با', 'value': 'le'},
                                            {'text': 'بزرگتر مساوی است با', 'value': 'ge'},
                                            {'text': 'بزرگتر است از', 'value': 'gt'}]},
                               {'name': 'عبارت مقایسه کننده', 'code': 'value', 'default': '', 'type': 'text',
                                'tag': 'input'}]})


class Length(ValidatorBaseClass):
    def __init__(self, min_len=None, max_len=None, left_inclusive=True, right_inclusive=False):
        self.min_len = min_len
        self.max_len = max_len
        self.li = left_inclusive
        self.ri = right_inclusive
        self.dct = {'max_len': self.max_len, 'min_len': self.min_len, 'li': self.li, 'ri': self.ri}
        for val in self.dct.keys():
            if isinstance(self.dct[val], str):
                if self.dct[val] != '':
                    self.__setattr__(val, json.loads(self.dct[val]))
                else:
                    self.__setattr__(val, None)
        self.min_len = self.min_len if pd.notna(self.min_len) else 0
        self.max_len = self.max_len if pd.notna(self.max_len) else math.inf

    def validate(self, x):
        if self.li and not self.ri:
            return (self.min_len <= x.str.len()) & (x.str.len() < self.max_len)
        elif self.li and self.ri:
            return (self.min_len <= x.str.len()) & (x.str.len() <= self.max_len)
        elif not self.li and self.ri:
            return (self.min_len < x.str.len()) & (x.str.len() <= self.max_len)
        else:
            return (self.min_len < x.str.len()) & (x.str.len() < self.max_len)

    def to_dict(self):
        return {'rule_type': 'Length', 'validator_parameters': {'max_len': self.max_len, 'min_len': self.min_len,
                                                                'left_inclusive': self.li, 'right_inclusive': self.ri}}

    def __repr__(self):
        return json.dumps({'name': 'طول بازه', 'desc': 'Lorem Ipsom', 'example': 'Lorem Ipson', 'code': 'Length',
                           'params': [
                               {'name': 'کمینه', 'code': 'min_len', 'default': 0, 'type': 'number', 'tag': 'input'},
                               {'name': 'بیشینه', 'code': 'max_len', 'default': '∞', 'type': 'number', 'tag': 'input'},
                               {'name': 'شامل کمینه', 'code': 'left_inclusive', 'default': True, 'type': 'bool',
                                'tag': 'select', 'options': [{'text': 'بلی', 'value': True},
                                                             {'text': 'خیر', 'value': False}]},
                               {'name': 'شامل بیشینه', 'code': 'right_inclusive', 'default': True, 'type': 'bool',
                                'tag': 'select', 'options': [{'text': 'بلی', 'value': True},
                                                             {'text': 'خیر', 'value': False}]}
                               ]})


class Category(ValidatorBaseClass):
    def __init__(self, categories=None):
        self.categories = categories or []

    def validate(self, x):
        y = x.copy().astype('str')
        return y.isin(self.categories)

    def to_dict(self):
        return {'rule_type': 'Category', 'validator_parameters': {'categories': self.categories}}

    def __repr__(self):
        return json.dumps({'name': 'گروه‌بندی', 'desc': 'Lorem Ipsom', 'example': 'Lorem Ipson', 'code': 'Category',
                           'params': [{'name': 'مقادیر', 'code': 'categories', 'default': [], 'type': 'list',
                                       'tag': 'tagbox'}]})


class Regex(ValidatorBaseClass):
    def __init__(self, regex=''):
        self.regex = re.compile(regex)

    def validate(self, x):
        return x.apply(lambda record: bool(re.search(self.regex, record)))

    def to_dict(self):
        return {'rule_type': 'Regex', 'validator_parameters': {'regex': self.regex}}

    def __repr__(self):
        return json.dumps({'name': 'Regex', 'desc': 'Lorem Ipsom', 'example': 'Lorem Ipson', 'code': 'Regex',
                           'params': [
                               {'name': 'متن', 'code': 'regex', 'default': '', 'type': 'text', 'tag': 'textarea'}]})


class Missing(ValidatorBaseClass):
    def __init__(self, missing=np.nan):
        self.missing = missing

    def validate(self, x):
        # TODO this wouldn't work for np.nan because np.nan!=np.nan
        return x == self.missing

    def to_dict(self):
        return {'rule_type': 'Missing', 'validator_parameters': {'missing': self.missing}}

    def __repr__(self):
        return json.dumps({'name': 'گمشده', 'desc': 'Lorem Ipsom', 'example': 'Lorem Ipson', 'code': 'Missing',
                           'params': [{'name': 'مقدار گمشده', 'code': 'missing', 'default': np.nan, 'type': 'text',
                                       'tag': 'input'}]})


class Unique:
    def __init__(self, keep=False):
        self.keep = keep

    def validate(self, s):
        return s.duplicated(keep=self.keep)
        # return lambda x: x.duplicated(keep=self.keep)

    def to_dict(self):
        return {'rule_type': 'Unique', 'validator_parameters': {'keep': self.keep}}

    def __repr__(self):
        return json.dumps({'name': 'یکتایی', 'desc': 'Lorem Ipsom', 'example': 'Lorem Ipson', 'code': 'Unique',
                           'params': []})


class Empty(ValidatorBaseClass):

    def validate(self, x):
        return pd.isna(x)

    def to_dict(self):
        return {'rule_type': 'Empty'}

    def __repr__(self):
        return json.dumps({'name': 'خالی بودن', 'desc': 'Lorem Ipsom', 'example': 'Lorem Ipson', 'code': 'Empty',
                           'params': []})


class Compare(ValidatorBaseClass):
    def __init__(self, operation='='):
        self.operation = operation

    def validate(self, x):
        if self.operation == '=':
            return x[x.columns[0]] == x[x.columns[1]]
        elif self.operation == '>':
            return x[x.columns[0]] > x[x.columns[1]]
        elif self.operation == '<':
            return x[x.columns[0]] < x[x.columns[1]]
        raise NotImplementedError

    def to_dict(self):
        return {'rule_type': 'Compare', 'validator_parameters': {'operation': self.operation}}

# class Bool:
#     def __init__(self, operator=None):
#         self.operator = operator or 'or'
#
#     def validate(self, x, y):
#         if self.operator == 'and':
#             return x & y
#         elif self.operator == 'or':
#             return x | y


# class Math:
#     def __init__(self, operator=None):
#         self.operator = operator
#
#     def validate(self, x, y):
#         if self.operator == 'sum':
#             return x + y
#         elif self.operator == 'subtract':
#             return x - y
#         elif self.operator == 'multiply':
#             return x * y
#         elif self.operator == 'divide':
#             return x / y
