"""ReStructuredText Pages Models
"""
from django.db import models
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.flatpages.models import FlatPage
from templatetags.restructuredtext import restructuredparts
from validators import IsValidReST
from django.core.cache import cache
from django.utils import encoding
from pycon.core.validators import *
import difflib

WARNING='<!-- DO NOT EDIT! this content is generated from restructuredtext -->\n'


class FlatPageHistorySiteManager(models.Manager):
    def get_query_set(self):
        all = super(FlatPageHistorySiteManager, self).get_query_set()
        return all.filter(page__sites__id__exact=settings.SITE_ID)

## This gets UGLY because django does not allow 'SELECT DISTINCT ON'
## queries, which do not work in sqlite3 anyway.
## as a result this (much) is slower than it could be.
SUB_QUERY = ('(SELECT xx.id FROM restructuredtext_flatpagehistory AS xx '
             ' WHERE xx.page_id = restructuredtext_flatpagehistory.page_id '
             ' ORDER BY xx.changed DESC LIMIT 1) = '
                          'restructuredtext_flatpagehistory.id')

class FlaggedCurrentFlatPageHistorySiteManager(FlatPageHistorySiteManager):
    def get_query_set(self):
        allsite = super(FlaggedCurrentFlatPageHistorySiteManager, self).get_query_set()
        return allsite.extra(select={'is_current': SUB_QUERY})
    
class CurrentFlatPageHistorySiteManager(FlatPageHistorySiteManager):
    def get_query_set(self):
        allsite = super(CurrentFlatPageHistorySiteManager, self).get_query_set()
        return allsite.extra(where=[SUB_QUERY])
    
    
class FlatPageHistory(models.Model):
    page     = models.ForeignKey(FlatPage)
    changed  = models.DateTimeField("changed on", auto_now_add=True)
    content  = models.TextField(validator_list=[
                        IsValidReST("ReStructuredTextErrors: ", True, True)])
    comment  = models.CharField(maxlength=100, blank=True)
    user     = models.ForeignKey(User, null=True, blank=True,
                validator_list=[RequiredIfOtherFieldNotGiven('username',
                    'Required field if user is not assigned'),
                                IllegalIfOtherFieldGiven('username',
                    'Only supply if a user is not assigned')])
    username = models.CharField(maxlength=50, blank=True,
                validator_list=[RequiredIfOtherFieldNotGiven('user',
                    'Required field if user is not assigned'),
                                IllegalIfOtherFieldGiven('user',
                    'Only supply if a user is not assigned')])
    
    ## no filtering, all obejcts
    allsites = models.Manager()
    
    ## filtered for olny the current site
    objects = FlatPageHistorySiteManager()
    
    ## only the 'latest' or 'current' page histories for the current site
    current = CurrentFlatPageHistorySiteManager()
    
    ## the histories for the current site with 'is_current' attribute denoting
    ## it is the history for the flat page
    recent = FlaggedCurrentFlatPageHistorySiteManager()
    
    class Meta:
        verbose_name = 'flat page history'
        verbose_name_plural = 'flat page histories'
        ordering = ('-changed', 'page')

    class Admin:
        list_filter = ('page',)
        list_display = ('page', 'changed', 'user', 'username', 'comment')
        search_fields = ('content',)

    @property
    def rendered_title_and_content(self):
        if not hasattr(self, '_rendered'):
            parts = restructuredparts(self.content)
            self._rendered = (parts['title'], WARNING + parts['html_body'])
        return self._rendered
    
    @property
    def changed_by(self):
        if not hasattr(self, '_changed_by'):
            self._changed_by = (self.username if self.username
                                    else unicode(self.user))
        return self._changed_by
    
    # This has issues as these histories are allowed to have dynamic
    # view bases and prefixes. That means only the view knows what the
    # real absolute url is for the syndication feeds.
    # to get around this problem we must set an override in the settings file.
    # that way both the flat pages and the histories will have working
    # 'view on site' links. 
    def get_absolute_url(self):
        return encoding.iri_to_uri(self.page.url) + '?ver=' + str(self.id)

    def save(self):
        if self.id is None:
            title, content = self.rendered_title_and_content
            self.page.title = title
            self.page.content = content
            self.page.save()
        return super(FlatPageHistory, self).save()
    
    def delete(self):
        try:
            ## so we get a does not exist error.
            latest = self.page.flapagehistory_set.all()[0:1].values('id').get()
            
            if latest['id'] == self.id:
                ## we are deleting the latest, revert one up, or delete
                ## the page if we are the last history.
                page = self.page
                if self.page.flatpagehistory_set.count() == 1:
                    ## delete us first
                    res = super(FlatPageHistory, self).delete()
                    page.delete()
                    return res
                prev = self.page.flatpagehistory_set[1:2].get()
                title, content = prev.rendered_title_and_content
                page.title=title
                page.content=content
                page.save()
            else:
                ## delete any diff_previous cache
                try:
                    id = self.page.flapagehistory_set.filter(
                        pk__gt=self.id).order_by(
                            'id')[0:1].values('id').get()['id']
                    cache.delete('flatpagehistory_diff_previous_' + str(id))
                except FlatPageHistory.DoesNotExist:
                    pass
        except FlatPageHistory.DoesNotExist:
            pass
        return super(FlatPageHistory, self).delete()
    
    def diff_previous(self):
        """diff with previous version.
        """
        cache_name = 'flatpagehistory_diff_previous_' + str(self.id)
        diff = cache.get(cache_name)
        if diff is not None:
            return diff
        try:
            prev = FlatPageHistory.objects.filter(
                page=self.page, pk__lt=self.id).order_by('-id')[0:1].get()
            prev = prev.content
            if not prev.endswith('\n'): prev+='\n'
        except FlatPageHistory.DoesNotExist:
            prev = ''
        current = self.content
        if not current.endswith('\n'): current += '\n'
        diff = ''.join(difflib.ndiff(prev.splitlines(True),
                                     current.splitlines(True)))
        
        cache.set(cache_name, diff, 60*60*24*10) ## 10 day cache
        return diff
        
    def revert(self, user):
        """revert to this specific version, adding a new history entry for
        the change, with the new user.
        """
        new_hist = type(self)()
        new_hist.page=self.page
        new_hist.content=self.content
        new_hist.comment= u"Reverted to version %d." % self.id
        if user.is_anonymous():
            new_hist.username = u'Anonymous'
        else:
            new_hist.username = u''
            new_hist.user = user
        new_hist.save()
        title, content = self.rendered_title_and_content
        self.page.title = title
        self.page.content = content
        self.page.save()
        
    def revert_and_delete(self):
        """revert back to this version and delete all versions after it.
        """
        title, content = self.rendered_title_and_content
        self.page.title = title
        self.page.content = content
        self.page.save()
        ## This does a chained drop so the delete() methods are not called
        FlatPageHistory.objects.filter(page=self.page,
                        changed__gte=self.changed).exclude(pk=self.id).delete()
