# -*- coding:utf-8 -*-
from django.core import checks
from django.db.models import (CharField, IntegerField, Lookup, SubfieldBase,
                              Transform)
from django.utils import six

from django_mysql.validators import SetMaxLengthValidator

__all__ = ['SetCharField']


class SetCharField(six.with_metaclass(SubfieldBase, CharField)):
    """
    A subclass of CharField for using MySQL's handy FIND_IN_SET function with.
    """
    def __init__(self, base_field, size=None, **kwargs):
        self.base_field = base_field
        self.size = size

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

        if self.size:
            self.validators.append(SetMaxLengthValidator(self.size))

    def check(self, **kwargs):
        errors = super(SetCharField, self).check(**kwargs)
        if not isinstance(self.base_field, (CharField, IntegerField)):
            errors.append(
                checks.Error(
                    'Base field for set must be a CharField or IntegerField.',
                    hint=None,
                    obj=self,
                    id='django_mysql.E002'
                )
            )
        else:
            # 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 set has errors:\n    %s' % messages,
                        hint=None,
                        obj=self,
                        id='django_mysql.E001'
                    )
                )

            if isinstance(self.base_field, CharField) and self.size:
                max_size = (
                    (self.size * (self.base_field.max_length)) +
                    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.E003'
                        )
                    )
        return errors

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

    def deconstruct(self):
        name, path, args, kwargs = super(SetCharField, self).deconstruct()
        path = 'django_mysql.fields.SetCharField'
        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):
            value = set(value.split(','))
        return value

    def get_prep_value(self, value):
        if isinstance(value, set):
            value = {six.u(i) for i in value}
            for i in value:
                assert ',' not in i
            value = ','.join(value)
        return value


@SetCharField.register_lookup
class FindInSet(Lookup):
    lookup_name = 'has'

    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
        # Yes we're putting rhs on the left hand side since that's the order
        # FIND_IN_SET expects
        return 'FIND_IN_SET(%s, %s)' % (rhs, lhs), params


@SetCharField.register_lookup
class SetLength(Transform):
    lookup_name = 'len'
    output_field = IntegerField()

    expr = (
        # No str.count equivalent in MySQL :(
        "("
        "CHAR_LENGTH(%s) -"
        "CHAR_LENGTH(REPLACE(%s, ',', '')) +"
        "IF(CHAR_LENGTH(%s), 1, 0)"
        ")"
    )

    def as_sql(self, compiler, connection):
        lhs, params = compiler.compile(self.lhs)
        return self.expr % (lhs, lhs, lhs), params
