"""
These classes are light wrappers around Django's database API that provide
convenience functionality and permalink functions for the databrowse app.
"""
from __future__ import unicode_literals

from django.db import models
from django.utils import formats
from django.utils.text import capfirst
from django.utils.encoding import smart_text, force_str, iri_to_uri
from django.db.models.query import QuerySet
from django.utils.encoding import python_2_unicode_compatible

EMPTY_VALUE = '(None)'
DISPLAY_SIZE = 100

class EasyModel(object):
    def __init__(self, site, model):
        self.site = site
        self.model = model
        self.model_list = list(site.registry.keys())
        self.verbose_name = model._meta.verbose_name
        self.verbose_name_plural = model._meta.verbose_name_plural

    def __repr__(self):
        return force_str('<EasyModel for %s>' % self.model._meta.object_name)

    def model_databrowse(self):
        "Returns the ModelDatabrowse class for this model."
        return self.site.registry[self.model]

    def url(self):
        return '%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name)

    def objects(self, **kwargs):
        return self.get_query_set().filter(**kwargs)

    def get_query_set(self):
        easy_qs = self.model._default_manager.get_query_set()._clone(klass=EasyQuerySet)
        easy_qs._easymodel = self
        return easy_qs

    def object_by_pk(self, pk):
        return EasyInstance(self, self.model._default_manager.get(pk=pk))

    def sample_objects(self):
        for obj in self.model._default_manager.all()[:3]:
            yield EasyInstance(self, obj)

    def field(self, name):
        try:
            f = self.model._meta.get_field(name)
        except models.FieldDoesNotExist:
            return None
        return EasyField(self, f)

    def fields(self):
        return [EasyField(self, f) for f in (self.model._meta.fields + self.model._meta.many_to_many)]

class EasyField(object):
    def __init__(self, easy_model, field):
        self.model, self.field = easy_model, field

    def __repr__(self):
        return force_str('<EasyField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))

    def choices(self):
        for value, label in self.field.choices:
            yield EasyChoice(self.model, self, value, label)

    def url(self):
        if self.field.choices:
            return '%s%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name)
        elif self.field.rel:
            return '%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name)

class EasyChoice(object):
    def __init__(self, easy_model, field, value, label):
        self.model, self.field = easy_model, field
        self.value, self.label = value, label

    def __repr__(self):
        return force_str('<EasyChoice for %s.%s>' % (self.model.model._meta.object_name, self.field.name))

    def url(self):
        return '%s%s/%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.field.name, iri_to_uri(self.value))

@python_2_unicode_compatible
class EasyInstance(object):
    def __init__(self, easy_model, instance):
        self.model, self.instance = easy_model, instance

    def __repr__(self):
        return force_str('<EasyInstance for %s (%s)>' % (self.model.model._meta.object_name, self.instance._get_pk_val()))

    def __str__(self):
        val = smart_text(self.instance)
        if len(val) > DISPLAY_SIZE:
            return val[:DISPLAY_SIZE] + '...'
        return val

    def pk(self):
        return self.instance._get_pk_val()

    def url(self):
        return '%s%s/%s/objects/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, iri_to_uri(self.pk()))

    def fields(self):
        """
        Generator that yields EasyInstanceFields for each field in this
        EasyInstance's model.
        """
        for f in self.model.model._meta.fields + self.model.model._meta.many_to_many:
            yield EasyInstanceField(self.model, self, f)

    def related_objects(self):
        """
        Generator that yields dictionaries of all models that have this
        EasyInstance's model as a ForeignKey or ManyToManyField, along with
        lists of related objects.
        """
        for rel_object in self.model.model._meta.get_all_related_objects() + self.model.model._meta.get_all_related_many_to_many_objects():
            if rel_object.model not in self.model.model_list:
                continue # Skip models that aren't in the model_list
            em = EasyModel(self.model.site, rel_object.model)
            yield {
                'model': em,
                'related_field': rel_object.field.verbose_name,
                'object_list': [EasyInstance(em, i) for i in getattr(self.instance, rel_object.get_accessor_name()).all()],
            }

class EasyInstanceField(object):
    def __init__(self, easy_model, instance, field):
        self.model, self.field, self.instance = easy_model, field, instance
        self.raw_value = getattr(instance.instance, field.name)

    def __repr__(self):
        return force_str('<EasyInstanceField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))

    def values(self):
        """
        Returns a list of values for this field for this instance. It's a list
        so we can accomodate many-to-many fields.
        """
        # This import is deliberately inside the function because it causes
        # some settings to be imported, and we don't want to do that at the
        # module level.
        if self.field.rel:
            if isinstance(self.field.rel, models.ManyToOneRel):
                objs = getattr(self.instance.instance, self.field.name)
            elif isinstance(self.field.rel, models.ManyToManyRel): # ManyToManyRel
                return list(getattr(self.instance.instance, self.field.name).all())
        elif self.field.choices:
            objs = dict(self.field.choices).get(self.raw_value, EMPTY_VALUE)
        elif isinstance(self.field, models.DateField) or isinstance(self.field, models.TimeField):
            if self.raw_value:
                if isinstance(self.field, models.DateTimeField):
                    objs = capfirst(formats.date_format(self.raw_value, 'DATETIME_FORMAT'))
                elif isinstance(self.field, models.TimeField):
                    objs = capfirst(formats.time_format(self.raw_value, 'TIME_FORMAT'))
                else:
                    objs = capfirst(formats.date_format(self.raw_value, 'DATE_FORMAT'))
            else:
                objs = EMPTY_VALUE
        elif isinstance(self.field, models.BooleanField) or isinstance(self.field, models.NullBooleanField):
            objs = {True: 'Yes', False: 'No', None: 'Unknown'}[self.raw_value]
        else:
            objs = self.raw_value
        return [objs]

    def urls(self):
        "Returns a list of (value, URL) tuples."
        # First, check the urls() method for each plugin.
        plugin_urls = []
        for plugin_name, plugin in self.model.model_databrowse().plugins.items():
            urls = plugin.urls(plugin_name, self)
            if urls is not None:
                return zip(self.values(), urls)
        if self.field.rel:
            m = EasyModel(self.model.site, self.field.rel.to)
            if self.field.rel.to in self.model.model_list:
                lst = []
                for value in self.values():
                    if value is None:
                        continue
                    url = '%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val()))
                    lst.append((smart_text(value), url))
            else:
                lst = [(value, None) for value in self.values()]
        elif self.field.choices:
            lst = []
            for value in self.values():
                url = '%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value))
                lst.append((value, url))
        elif isinstance(self.field, models.URLField):
            val = list(self.values())[0]
            lst = [(val, iri_to_uri(val))]
        else:
            lst = [(list(self.values())[0], None)]
        return lst

class EasyQuerySet(QuerySet):
    """
    When creating (or cloning to) an `EasyQuerySet`, make sure to set the
    `_easymodel` variable to the related `EasyModel`.
    """
    def iterator(self, *args, **kwargs):
        for obj in super(EasyQuerySet, self).iterator(*args, **kwargs):
            yield EasyInstance(self._easymodel, obj)

    def _clone(self, *args, **kwargs):
        c = super(EasyQuerySet, self)._clone(*args, **kwargs)
        c._easymodel = self._easymodel
        return c
