from django.conf import settings
from django.core import validators
from django.db import models
from django.db.models import fields as django_fields
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from django.utils import six
from django.utils.translation import ugettext_lazy as _

from dirtyfields import DirtyFieldsMixin

from ..text import slugify, downgrading_slugify, django_slugify

if six.PY3:
    from functools import reduce


DEFAULT_SLUG = getattr(settings, 'DEFAULT_SLUG', "item")

SLUG_HELP = _("Kurzfassung des Namens für die Adresszeile im Browser. Vorzugsweise englisch, keine Umlaute, nur Bindestrich als Sonderzeichen.")


slug_re = validators._lazy_re_compile(r'^[-a-z0-9]+\Z')
validate_downgraded_slug = validators.RegexValidator(
    slug_re,
    _("Enter a valid 'slug' consisting of lower-case letters, numbers or hyphens."),
    'invalid'
)


class AutoSlugField(django_fields.SlugField):
    """
    SlugField which optionally populates the value and/or makes sure that
    the value is unique. By default as stricter slugify function is used.

    populate_from: Field name
    unique_slug: Boolean, automatically make the field value unique
    """

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('max_length', 50)
        kwargs.setdefault('help_text', SLUG_HELP)
        if 'populate_from' in kwargs:
            self.populate_from = kwargs.pop('populate_from')
        self.unique_slug = kwargs.pop('unique_slug', False)
        super(AutoSlugField, self).__init__(*args, **kwargs)

    def slugify(self, value):
        return slugify(value)

    def pre_save(self, model_instance, add):
        value = getattr(model_instance, self.attname)
        if not value:
            if hasattr(self, 'populate_from'):
                if callable(self.populate_from):
                    value = self.populate_from(model_instance, self)
                else:
                    # Follow dotted path (e.g. "occupation.corporation.name")
                    value = reduce(lambda obj, attr: getattr(obj, attr),
                        self.populate_from.split("."), model_instance)
                    if callable(value):
                        value = value()
            if not value:
                value = DEFAULT_SLUG
        value = self.slugify(value)
        if self.unique_slug:
            # TODO Move import to top of file once AutoSlugField is removed from shared.utils.fields and we no longer have a circular import
            from ..fields import uniquify_field_value
            return uniquify_field_value(
                model_instance, self.name, value, max_length=self.max_length)
        else:
            return value


class DowngradingSlugField(AutoSlugField):
    """
    SlugField which allows only lowercase ASCII characters and the dash,
    automatically downgrading/replacing the entered content.
    """

    default_validators = [validate_downgraded_slug]

    def __init__(self, *args, **kwargs):
        kwargs['allow_unicode'] = False
        super(DowngradingSlugField, self).__init__(*args, **kwargs)

    def slugify(self, value):
        return downgrading_slugify(value)

    def to_python(self, value):
        # Downgrade immediately so that validators work
        value = super().to_python(value)
        return self.slugify(value)

    def formfield(self, **kwargs):
        # Remove the slug validator from the form field so that we can modify
        # the field value in the model
        field = super().formfield(**kwargs)
        if field.default_validators:
            try:
                field.validators.remove(field.default_validators[0])
            except ValueError:
                pass
        return field


class SlugTreeMixin(DirtyFieldsMixin, models.Model):
    """
    Expects a `slug` and a `has_url` field.
    """
    slug_path = models.CharField(_("URL path"), max_length=2000, editable=False)
    has_url = models.BooleanField(_("has webaddress"), default=True)

    FIELDS_TO_CHECK = ['slug']

    class Meta:
        abstract = True

    def _get_slug_path(self):
        if self.pk:
            ancestors = self.get_ancestors(include_self=False).filter(has_url=True).values_list('slug', flat=True)
            parts = list(ancestors)
        else:
            parts = []
        if self.slug:
            parts += [self.slug]
        return "/".join(parts)

    def _rebuild_descendants_slug_path(self):
        for p in self.get_descendants():
            p.slug_path = p._get_slug_path()
            p.save()


@receiver(pre_save)
def slug_tree_mixin_pre_save(sender, instance, **kwargs):
    if isinstance(instance, SlugTreeMixin):
        # FIXME: find a way to not always call this
        instance.slug = instance._meta.get_field('slug').pre_save(instance, False)
        instance.slug_path = instance._get_slug_path()


@receiver(post_save)
def slug_tree_mixin_post_save(sender, instance, **kwargs):
    if isinstance(instance, SlugTreeMixin):
        if kwargs.get('created'):
            # Always get a new database instance before saving again
            # or MPTTModel.save() will interpret the newly .save as
            # not allowed tree move action
            # FIXME Not clear if this is a proper solution -> get rid of the slug_path stuff altogether
            instance_copy = type(instance).objects.get(pk=instance.pk)
            instance_copy.slug_path = instance_copy._get_slug_path()
            if 'slug_path' in instance_copy.get_dirty_fields().keys():
                instance_copy.save()
        elif instance.get_dirty_fields().keys() & {'slug_path'}:
            instance._rebuild_descendants_slug_path()

@@ -1504,32 +1504,97 @@
 e_slug', False)%0A
+        if self.unique_slug:%0A            kwargs%5B'unique'%5D = True%0A
         super(Au

Enforce unique=True if unique_slug=True.