# -*- coding:utf-8 -*-
from __future__ import absolute_import

from django.core import checks
from django.db.models import CharField, IntegerField, Lookup, TextField
from django.utils import six
from django.utils.translation import ugettext_lazy as _

from django_mysql.forms import SimpleListField
from django_mysql.models.lookups import SetContains, SetIContains
from django_mysql.models.transforms import SetLength
from django_mysql.validators import ListMaxLengthValidator


class ListFieldMixin(object):

    def __init__(self, base_field, size=None, **kwargs):
        self.base_field = base_field
        self.size = size

        super(ListFieldMixin, self).__init__(**kwargs)

        if self.size:
            self.validators.append(ListMaxLengthValidator(int(self.size)))

    def get_default(self):
        default = super(ListFieldMixin, self).get_default()
        if default == '':
            return []
        else:
            return default

    def check(self, **kwargs):
        errors = super(ListFieldMixin, self).check(**kwargs)
        if not isinstance(self.base_field, (CharField, IntegerField)):
            errors.append(
                checks.Error(
                    'Base field for list must be a CharField or IntegerField.',
                    hint=None,
                    obj=self,
                    id='django_mysql.E005'
                )
            )
            return errors

        # Remove the field name checks as they are not needed here.
        base_errors = self.base_field.check()
        if base_errors:
            messages = '\n    '.join(
                '%s (%s)' % (error.msg, error.id)
                for error in base_errors
            )
            errors.append(
                checks.Error(
                    'Base field for list has errors:\n    %s' % messages,
                    hint=None,
                    obj=self,
                    id='django_mysql.E004'
                )
            )
        return errors

    @property
    def description(self):
        return _('List of %(base_description)s') % {
            'base_description': self.base_field.description
        }

    def set_attributes_from_name(self, name):
        super(ListFieldMixin, self).set_attributes_from_name(name)
        self.base_field.set_attributes_from_name(name)

    def deconstruct(self):
        name, path, args, kwargs = super(ListFieldMixin, self).deconstruct()

        bad_paths = (
            'django_mysql.models.fields.lists.' + self.__class__.__name__,
            'django_mysql.models.fields.' + self.__class__.__name__
        )
        if path in bad_paths:
            path = 'django_mysql.models.' + self.__class__.__name__

        args.insert(0, self.base_field)
        kwargs['size'] = self.size
        return name, path, args, kwargs

    def to_python(self, value):
        if isinstance(value, six.string_types):
            if not len(value):
                value = []
            else:
                value = [self.base_field.to_python(v) for
                         v in value.split(',')]
        return value

    def from_db_value(self, value, expression, connection, context):
        # Similar to to_python, for Django 1.8+
        if isinstance(value, six.string_types):
            if not len(value):
                value = []
            else:
                value = [self.base_field.to_python(v) for
                         v in value.split(',')]
        return value

    def get_prep_value(self, value):
        if isinstance(value, list):
            value = [
                six.text_type(self.base_field.get_prep_value(v))
                for v in value
            ]
            for v in value:
                if ',' in v:
                    raise ValueError(
                        "List members in {klass} {name} cannot contain commas"
                        .format(klass=self.__class__.__name__,
                                name=self.name)
                    )
                elif not len(v):
                    raise ValueError(
                        "The empty string cannot be stored in {klass} {name}"
                        .format(klass=self.__class__.__name__,
                                name=self.name)
                    )
            return ','.join(value)
        return value

    def get_lookup(self, lookup_name):
        lookup = super(ListFieldMixin, self).get_lookup(lookup_name)
        if lookup:
            return lookup

        try:
            index = int(lookup_name)
        except ValueError:
            pass
        else:
            index += 1  # MySQL uses 1-indexing
            return IndexLookupFactory(index)

        return lookup

    def value_to_string(self, obj):
        vals = self._get_val_from_obj(obj)
        return self.get_prep_value(vals)

    def formfield(self, **kwargs):
        defaults = {
            'form_class': SimpleListField,
            'base_field': self.base_field.formfield(),
            'max_length': self.size,
        }
        defaults.update(kwargs)
        return super(ListFieldMixin, self).formfield(**defaults)

    def contribute_to_class(self, cls, name, **kwargs):
        super(ListFieldMixin, self).contribute_to_class(cls, name, **kwargs)
        self.base_field.model = cls


class ListCharField(ListFieldMixin, CharField):
    """
    A subclass of CharField for using MySQL's handy FIND_IN_SET function with.
    """
    def check(self, **kwargs):
        errors = super(ListCharField, self).check(**kwargs)

        # Unfortunately this check can't really be done for IntegerFields since
        # they have boundless length
        has_base_error = any(e.id == 'django_mysql.E004' for e in errors)
        if (
            not has_base_error and
            isinstance(self.base_field, CharField) and
            self.size
        ):
            max_size = (
                # The chars used
                (self.size * (self.base_field.max_length)) +
                # The commas
                self.size - 1
            )
            if max_size > self.max_length:
                errors.append(
                    checks.Error(
                        'Field can overrun - set contains CharFields of max '
                        'length %s, leading to a comma-combined max length of '
                        '%s, which is greater than the space reserved for the '
                        'set - %s' %
                        (self.base_field.max_length, max_size,
                            self.max_length),
                        hint=None,
                        obj=self,
                        id='django_mysql.E006'
                    )
                )
        return errors


class ListTextField(ListFieldMixin, TextField):
    pass


ListCharField.register_lookup(SetContains)
ListTextField.register_lookup(SetContains)

ListCharField.register_lookup(SetIContains)
ListTextField.register_lookup(SetIContains)

ListCharField.register_lookup(SetLength)
ListTextField.register_lookup(SetLength)


class IndexLookup(Lookup):

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

    def as_sql(self, qn, connection):
        lhs, lhs_params = self.process_lhs(qn, connection)
        rhs, rhs_params = self.process_rhs(qn, connection)
        params = lhs_params + rhs_params
        # Put rhs on the left since that's the order FIND_IN_SET uses
        return '(FIND_IN_SET(%s, %s) = %s)' % (rhs, lhs, self.index), params


class IndexLookupFactory(object):

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

    def __call__(self, *args, **kwargs):
        return IndexLookup(self.index, *args, **kwargs)
