#!/usr/bin/env python
# vim: set fileencoding=utf-8 :

from __future__ import division, print_function, absolute_import, unicode_literals
from .. import forms as enum_forms
from django import forms
from django.db import models
from django.utils.functional import curry
from logging import getLogger
import django


logger = getLogger(__name__)


class EnumField(models.CharField):

    __metaclass__ = models.SubfieldBase

    def __init__(self, enum, *args, **kwargs):
        logger.debug('call: __init__ enum=%s, kwargs=%s' % (enum, kwargs))
        self.enum = enum
        self.default_enum = kwargs.pop('default', None)
        kwargs['max_length'] = self.enum.get_max_length()
        kwargs['choices'] = self.enum.choices()
        if isinstance(self.default_enum, self.enum):
            kwargs['default'] = self.default_enum.key
        super(EnumField, self).__init__(*args, **kwargs)

    def check(self, **kwargs):
        logger.debug('call: check kwargs=%s' % kwargs)
        errors = super(EnumField, self).check(**kwargs)
        errors.extend(self._check_enum_attribute(**kwargs))
        errors.extend(self._check_default_attribute(**kwargs))
        return errors

    def _check_enum_attribute(self, **kwargs):
        logger.debug('call: _check_enum_attribute kwargs=%s' % kwargs)
        errors = super(EnumField, self).check(**kwargs)
        if self.enum is None:
            return [
                    checks.Error(
                            "EnumFields must define a 'enum' attribute.",
                            obj=self,
                            id='django-enum.fields.E001',
                            ),
                    ]
        else:
            return []

    def _check_default_attribute(self, **kwargs):
        logger.debug('call: _check_default_attribute kwargs=%s' % kwargs)
        if self.default_enum is not None:
            if not isinstance(self.default_enum, self.enum):
                return [
                        checks.Error(
                                "'default' must be a member of %s." % (self.enum.__name__),
                                obj=self,
                                id='django-enum.fields.E002',
                                ),
                        ]
        return []

    @staticmethod
    def _get_display(self, field):
        return getattr(self, field.attname).label

    def contribute_to_class(self, cls, name, virtual_only=False):
        super(EnumField, self).contribute_to_class(cls, name, virtual_only)
        setattr(cls, 'get_%s_display' % self.name, curry(self._get_display, field=self))
        setattr(self.enum, '__len__', lambda x: len(unicode(x)))

    def from_db_value(self, value, expression, connection, context):
        logger.debug('call: from_db_value value=%s%s' % (value, type(value)))
        return self.to_python(value)

    def to_python(self, value):
        "Returns an Enum object."
        logger.debug('call: to_python value=%s%s' % (value, type(value)))
        if isinstance(value, self.enum) or value is None:
            logger.debug('to_python returns %s%s' % (value, type(value)))
            return value
        ret = self.enum.get_by_key(value)
        logger.debug('to_python returns %s%s' % (ret, type(ret)))
        return ret

    def get_internal_type(self):
        logger.debug('call: get_internal_type')
        return 'CharField'

    def db_type(self, connection):
        logger.debug('db_type returns char(%s)' % self.max_length)
        return 'char(%s)' % self.max_length

    def get_prep_value(self, value):
        logger.debug('call: get_prep_value value=%s%s' % (value, type(value)))
        value = super(EnumField, self).get_prep_value(value)
        return self.to_python(value)

    def get_db_prep_lookup(self, lookup_type, value, connection, prepared=True):
        logger.debug('call: get_db_prep_lookup lookup_type=%s, value=%s%s' % (lookup_type, value, type(value)))
        if lookup_type == 'exact':
            return [self.get_db_prep_value(value)]
        elif lookup_type == 'in':
            return [self.get_db_prep_value(v) for v in value]
        else:
            raise TypeError('Lookup type %r not supported.' % lookup_type)

    def get_db_prep_value(self, value, **kwargs):
        logger.debug('call: get_db_prep_value value=%s%s' % (value, type(value)))
        if isinstance(value, self.enum):
            return value.key
        if isinstance(value, basestring):
            return value
        raise Exception('%s is not a valid value.', value)

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

    def formfield(self, **kwargs):
        logger.debug('call: formfield kwargs=%s' % kwargs)

        defaults = {
                'widget': forms.Select,
                'form_class': enum_forms.fields.EnumField,
                'enum': self.enum,
                }
        defaults.update(kwargs)
        return super(EnumField, self).formfield(**defaults)

    def deconstruct(self):
        name, path, args, kwargs = super(EnumField, self).deconstruct()
        if django.VERSION >= (1, 9):
            kwargs['enum'] = self.enum
        else:
            path = 'django.db.models.fields.CharField'
        return name, path, args, kwargs
