# coding=utf-8
from abc import ABCMeta, abstractmethod
from collections import Iterable
import re

from flyforms.common import jsonify_types, UNSET
from flyforms.compatibility import string_types, with_metaclass

__all__ = (
    "EntryValidator",
    "MaxValueValidator",
    "MaxLengthValidator",
    "MinLengthValidator",
    "MinValueValidator",
    "EmailValidator",
    "RegexValidator",
    "Ip4AddressValidator",
    "RequiredValidator",
    "TypedValidator",
    "ItemTypedValidator",
    "JsonItemTypedValidator",
    "ValidationError"
)


class ValidationError(Exception):
    """
    Raised when a validator fails to validate it's input.
    """
    pass


class Validator(with_metaclass(ABCMeta, object)):
    """
    The abstract root class for all Validators
    """

    message = ""

    @abstractmethod
    def validate(self, value):
        """
        Validate the given value

        :param value: given value to validate

        :raises ValidationError: if given value is not valid
        """
        pass

    def __call__(self, value):
        """
        The same with validate
        """
        self.validate(value)

    def is_valid(self, value):
        """
        Validate value and catch :py:class`.ValidationError`.

        :param value: given value to validate

        :return: True if :py:class`.ValidationError` was not raised, otherwise False
        :rtype: bool
        """
        try:
            self.validate(value)
        except ValidationError:
            return False
        else:
            return True


class SimpleValidator(with_metaclass(ABCMeta, Validator)):
    """
    The Validator's subclass with only one validation case.
    Given value should satisfies condition in *positive_case* method
    """

    validation_case = abstractmethod(lambda self, x: x is x)  # Given value should satisfies this condition

    # .. deprecated:: 0.3.0
    #    will be removed in 1.0.0
    positive_case = validation_case

    def validate(self, value):
        """
        Implementation of Validator's validate method
        """
        try:
            if not self.validation_case(value):   # if positive case wasn't satisfied then ValidationError will be raised
                raise ValidationError(self.message)
        except TypeError as err:
            raise ValidationError(str(err))


class RequiredValidator(SimpleValidator):
    """
    Validates is given value not :py:data:`.UNSET` object
    """

    message = "Field is required."

    validation_case = lambda self, value: value is not UNSET


class TypedValidator(SimpleValidator):
    """
    Validates is given value instance of defined :code:`value_types`
    """

    message = "Bad value type."

    def __init__(self, value_types):
        """
        :param value_types: list of possible value types
        """
        self.value_types = value_types

    validation_case = lambda self, value: isinstance(value, self.value_types) or value is UNSET


class EntryValidator(SimpleValidator):
    """
    Validates is given value in specified during initialization iterable object
    """

    message = "Value not in choices."

    def __init__(self, iterable):
        """
        :param iterable: the iterable object

        :raise: TypeError if given object is not iterable
        """
        if not isinstance(iterable, Iterable):
            raise TypeError("Entry validator instance need an iterable object")
        self.iterable = iterable

    validation_case = lambda self, value: value in self.iterable


class ItemTypedValidator(SimpleValidator):
    """
    Validates is all items from iterable instance of defined :code:`value_types`
    """

    message = "Item is not type matching"

    def __init__(self, item_types):
        self.item_types = item_types

    validation_case = lambda self, value: all(isinstance(item, self.item_types) for item in value)


class JsonItemTypedValidator(ItemTypedValidator):
    """
    Validates is all items from iterable instance of :py:data:`.jsonify_types`
    """
    def __init__(self):
        super(JsonItemTypedValidator, self).__init__(jsonify_types)


class MinValueValidator(SimpleValidator):
    """
    Validates is given value greater than specified during initialization value
    """

    message = "Given value is less than minimum valid value."

    def __init__(self, min_value, strong=True):
        """
        :param min_value: the minimum valid value

        :param strong: boolean flag should be comparison strict or not
        :type strong: bool
        """
        self.min_value = min_value
        self.strong = strong

    validation_case = lambda self, value: value > self.min_value if self.strong else value >= self.min_value


class MaxValueValidator(SimpleValidator):
    """
    Validates is given value less than specified during initialization value
    """

    message = "Given value is greater than maximum valid value."

    def __init__(self, max_value, strong=True):
        """
        :param max_value: the maximum valid value

        :param strong: boolean flag should be comparison strict or not
        :type strong: bool
        """
        self.max_value = max_value
        self.strong = strong

    validation_case = lambda self, value: value < self.max_value if self.strong else value <= self.max_value


class MinLengthValidator(SimpleValidator):
    """
    Validates the minimum object length
    """

    message = "Value is too short."

    def __init__(self, min_length, strong=True):
        """
        :param min_length: the minimum valid length

        :param strong: boolean flag should be comparison strict or not
        :type strong: bool
        """
        self.len = min_length
        self.strong = strong

    def validate(self, value):
        """
        Implementation of Validator's validate method
        """
        if not hasattr(value, '__len__'):  # check for __len__ attribute
            raise ValidationError("Validation failed because value of type '%s' has no len()" % type(value))
        super(MinLengthValidator, self).validate(
            len(value)
        )

    validation_case = lambda self, value_len: value_len > self.len if self.strong else value_len >= self.len


class MaxLengthValidator(MinLengthValidator):
    """
    Validates the maximum object length
    """
    message = "Value is too long."

    def __init__(self, max_length, strong=True):
        """
        :param max_length: the maximum valid length

        :param strong: boolean flag should be comparison strict or not
        :type strong: bool
        """
        super(MaxLengthValidator, self).__init__(max_length, strong)

    validation_case = lambda self, value_len: value_len < self.len if self.strong else value_len <= self.len


class RegexValidator(SimpleValidator):
    """
    Validates matching with regular expression
    """

    message = "Value does not match the regular expression."

    def __init__(self, regex, flags=0):
        """
        :param regex: the regular expression

        :param flags: flags passed to re.match function
        """
        self.r = re.compile(regex, flags)

    def validate(self, value):
        """
        The Validator's validate method implementation
        """
        if not isinstance(value, string_types):
            raise ValidationError("Validation failed because value is not string")
        super(RegexValidator, self).validate(value)

    validation_case = lambda self, value: self.r.match(value)


class EmailValidator(RegexValidator):
    """
    Validates an email address via simple regex.
    """

    message = "Bad e-mail address"

    def __init__(self):
        super(EmailValidator, self).__init__(
            regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
        )


class Ip4AddressValidator(RegexValidator):
    """
    Validates an IPv4 address via simple regex.
    """

    message = "Bad ip address"

    def __init__(self):
        super(Ip4AddressValidator, self).__init__(
            regex=r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"
        )
