from cms.constants import PUBLISHER_STATE_DIRTY
from django.db import models

from cms.models import Page, Title


class BaseExtension(models.Model):
    public_extension = models.OneToOneField('self', null=True, editable=False, related_name='draft_extension')
    extended_object = None

    class Meta:
        abstract = True

    def get_page(self):  # pragma: no cover
        raise NotImplementedError('Function must be overwritten in subclasses and return the extended page object.')

    def copy_relations(self, oldinstance, language):
        """
        Copy relations like many to many or foreign key relations to the public version.
        Similar to the same named cms plugin function.

        :param oldinstance: the draft version of the extension
        """
        pass

    def copy(self, target, language):
        """
        This method copies this extension to an unrelated-target. If you intend
        to "publish" this extension to the publisher counterpart of target, then
        use copy_to_publish() instead.
        """
        clone = self.__class__.objects.get(pk=self.pk)  # get a copy of this instance
        clone.pk = None
        clone.public_extension = None
        clone.extended_object = target  # set the new public object
        clone.save(mark_page=False)

        # If the target we're copying already has a publisher counterpart, then
        # connect the dots.
        target_prime = getattr(target, 'publisher_public')
        if target_prime:
            related_name = self.__class__.__name__.lower()
            clone_prime = getattr(target_prime, related_name)
            if clone_prime:
                clone.public_extension = clone_prime
            else:
                clone.public_extension = None

        clone.copy_relations(self, language)
        clone.save(force_update=True, mark_page=False)
        return clone

    def copy_to_public(self, public_object, language):
        """
        This method is used to "publish" this extension as part of the a larger
        operation on the target. If you intend to copy this extension to an
        unrelated object, use copy() instead.
        """
        this = self.__class__.objects.get(pk=self.pk)  # get a copy of this instance
        public_extension = self.public_extension  # get the public version of this instance if any

        this.extended_object = public_object  # set the new public object

        if public_extension:
            this.pk = public_extension.pk  # overwrite current public extension
            this.public_extension = None  # remove public extension or it will point to itself and raise duplicate entry
        else:
            this.pk = None  # create new public extension
            this.save(mark_page=False)
            self.public_extension = this
            self.save(mark_page=False)

        this.copy_relations(self, language)
        this.save(force_update=True, mark_page=False)

        return this


class PageExtension(BaseExtension):
    extended_object = models.OneToOneField(Page, editable=False)

    class Meta:
        abstract = True

    def get_page(self):
        return self.extended_object

    def save(self, *args, **kwargs):
        if kwargs.pop('mark_page', True):
            self.get_page().title_set.update(publisher_state=PUBLISHER_STATE_DIRTY)  # mark page dirty
        return super(BaseExtension, self).save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        if kwargs.pop('mark_page', True):
            self.get_page().title_set.update(publisher_state=PUBLISHER_STATE_DIRTY)  # mark page dirty
        return super(BaseExtension, self).delete(*args, **kwargs)


class TitleExtension(BaseExtension):
    extended_object = models.OneToOneField(Title, editable=False)

    class Meta:
        abstract = True

    def get_page(self):
        return self.extended_object.page

    def save(self, *args, **kwargs):
        if kwargs.pop('mark_page', True):
            Title.objects.filter(pk=self.extended_object.pk).update(
                publisher_state=PUBLISHER_STATE_DIRTY) # mark title dirty
        return super(BaseExtension, self).save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        if kwargs.pop('mark_page', True):
            Title.objects.filter(pk=self.extended_object.pk).update(
                publisher_state=PUBLISHER_STATE_DIRTY) # mark title dirty
        return super(BaseExtension, self).delete(*args, **kwargs)
