# coding: utf-8


import attr
import six
from attr.validators import optional, instance_of
from django.core.exceptions import ValidationError


# для использования в list_of(SELF), так как в этот момент ещё нельзя сослаться на текущий класс
class _Self(object):
    def __repr__(self):
        return 'SELF'


SELF = _Self()


def list_of(type):
    return _ListOfValidator(type, list)


def tuple_of(type):
    return _ListOfValidator(type, tuple)


def dict_of(type):
    return _DictOfValidator(type)


@attr.s(repr=False)
class _ListOfValidator(object):
    type = attr.ib()
    iter_class = attr.ib()

    def __call__(self, instance, attr, value):
        """
        We use a callable class to be able to change the ``__repr__``.
        """
        if self.type == SELF:
            self.type = type(instance)
        elif hasattr(self.type, '__iter__') and SELF in self.type:
            self.type = tuple(type(instance) if type_ == SELF else type_ for type_ in self.type)
        if not isinstance(value, self.iter_class):
            raise TypeError('%(name)s must be a tuple of type %(type)r, got %(value)r instead' % {
                'name': attr.name,
                'type': self.type,
                'value': value
            })
        for index, item in enumerate(value):
            if not isinstance(item, self.type):
                raise TypeError('%(name)s\' item #%(index)d must be %(type)s (got %(value)s that is a %(actual)r).' % {
                    'name': attr.name,
                    'type': self.type,
                    'actual': item.__class__,
                    'value': item,
                    'index': index,
                })

    def __repr__(self):
        return '<list_of validator for type %s>' % six.text_type(self.type)


@attr.s(repr=False)
class _DictOfValidator(object):
    type = attr.ib()

    def __call__(self, instance, attr, value):
        if not isinstance(value, dict):
            raise TypeError('%(name)s must be a dict of type %(type)r, got %(value)r instead' % {
                'name': attr.name,
                'type': self.type,
                'value': value
            })
        if self.type == SELF:
            self.type = type(instance)
        for key, item in value.items():
            if not isinstance(item, self.type):
                raise TypeError(
                    '%(name)s\' value for key %(key)s must be %(type)s got %(value)s that is a %(actual)r).' % {
                        'name': attr.name,
                        'type': self.type,
                        'actual': item.__class__,
                        'value': item,
                        'key': key,
                    }
                )

    def __repr__(self):
        return '<dict_of validator for type %s>' % self.type


@attr.s(repr=False)
class _EitherValidator(object):
    choices = attr.ib()

    def __call__(self, instance, attr, value):
        if value not in self.choices:
            raise ValidationError('%(value)s is not one of the available choices')

    def __repr__(self):
        return '<either validator for choices %s>' % six.text_type(self.choices)


def either(*choices):
    return _EitherValidator(choices)


@attr.s(slots=True)
class Hashable(object):
    hash = attr.ib(cmp=False, hash=False, validator=optional(instance_of(str)))
