# coding: utf-8

"""
    PyLucid models
    ~~~~~~~~~~~~~~

    Last commit info:
    ~~~~~~~~~~~~~~~~~
    $LastChangedDate: $
    $Rev: $
    $Author: $

    :copyleft: 2009 by the PyLucid team, see AUTHORS for more details.
    :license: GNU GPL v3 or above, see LICENSE for more details.
"""

import sys
from xml.sax.saxutils import escape

from django.conf import settings
from django.contrib import messages
from django.contrib.auth.models import Group
from django.contrib.sites.managers import CurrentSiteManager
from django.contrib.sites.models import Site
from django.core.cache import cache
from django.core.exceptions import PermissionDenied
from django.core.exceptions import ValidationError
from django.db import models
from django.http import Http404
from django.template.defaultfilters import slugify
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _

# http://code.google.com/p/django-tools/
from django_tools.middlewares import ThreadLocal
from django_tools import model_utils

from pylucid_project.apps.pylucid.tree_model import BaseTreeModel, TreeGenerator
from pylucid_project.apps.pylucid.models.base_models import BaseModel, BaseModelManager, UpdateInfoBaseModel



TAG_INPUT_HELP_URL = \
"http://google.com/search?q=cache:django-tagging.googlecode.com/files/tagging-0.2-overview.html#tag-input"



class PageTreeManager(BaseModelManager):
    """
    Manager class for PageTree model

    inherited from models.Manager:
        get_or_create() method, witch expected a request object as the first argument.
    """
    def filter_accessible(self, queryset, user):
        """ filter all pages with can't accessible for the given user """

        if user.is_anonymous():
            # Anonymous user are in no user group
            return queryset.filter(permitViewGroup__isnull=True)

        if user.is_superuser:
            # Superuser can see everything ;)
            return queryset

        # filter pages for not superuser and not anonymous

        user_groups = user.groups.values_list('pk', flat=True)

        if not user_groups:
            # User is in no group
            return queryset.filter(permitViewGroup__isnull=True)

        # Filter out all view group
        return queryset.filter(
            models.Q(permitViewGroup__isnull=True) | models.Q(permitViewGroup__in=user_groups)
        )

    def all_accessible(self, user=None, filter_showlinks=False):
        """ returns all pages that the given user can access. """
        if user == None:
            user = ThreadLocal.get_current_user()

        queryset = self.model.on_site.order_by("position")
        queryset = self.filter_accessible(queryset, user)

        if filter_showlinks:
            # Filter PageTree.showlinks
            queryset = queryset.filter(showlinks=True)

        return queryset

    def get_tree(self, user=None, filter_showlinks=False, exclude_plugin_pages=False, exclude_extras=None):
        """ return a TreeGenerator instance with all accessable page tree instance """
        queryset = self.all_accessible(user, filter_showlinks)

        if exclude_plugin_pages:
            queryset = queryset.exclude(page_type=PageTree.PLUGIN_TYPE)
        if exclude_extras:
            queryset = queryset.exclude(**exclude_extras)

        items = queryset.values("id", "parent", "slug")
        tree = TreeGenerator(items, skip_no_parent=True)
        return tree

    def get_choices(self, user=None, exclude_extras=None):
        """ returns a choices list for e.g. a forms select widget. """
        tree = PageTree.objects.get_tree(user, exclude_plugin_pages=True, exclude_extras=exclude_extras)
        choices = [("", "---------")] + [
            (node.id, node.get_absolute_url()) for node in tree.iter_flat_list()
        ]
        return choices

    def get_root_page(self, user, filter_parent=True):
        """ returns the 'first' root page tree entry witch the user can access """
        queryset = self.all_accessible(user)

        if filter_parent:
            # All "root" pages
            queryset = queryset.filter(parent=None)
        else:
            # fallback if no "root" page is accessable
            queryset = queryset.order_by("parent", "position")

        try:
            return queryset[0]
        except IndexError, err:
            if self.model.on_site.count() == 0:
                raise PageTree.DoesNotExist("There exist no PageTree items! Have you install PyLucid?")
            elif filter_parent == True:
                # If all root pages not accessible for the current user
                # -> try to get the first accessable page
                return self.get_root_page(user, filter_parent=False)
            else:
                raise

    def get_pagemeta(self, request, pagetree, show_lang_errors=True):
        """
        retuns the PageMeta instance witch associated to the given >pagetree< instance.
        
        dissolving language in client favored languages
        if not exist:
            return system default language
            
        If show_lang_errors==True:
            create a page_msg if PageMeta doesn't exist in client favored language.
        """
        from pylucid_project.apps.pylucid.models import PageMeta  # against import loops.

        # client favored Language instance:
        lang_entry = request.PYLUCID.current_language

        if pagetree.page_type == pagetree.PLUGIN_TYPE:
            # Automatic create a not existing PageMeta on PluginPages
            pagemeta = PageMeta.objects.verbose_get_or_create(
                request, pagetree, lang_entry, show_lang_errors=show_lang_errors
            )
            return pagemeta

        queryset = PageMeta.objects.filter(pagetree=pagetree)
        pagemeta, tried_languages = self.get_by_prefered_language(request, queryset)

        if pagemeta is None:
            msg = ""
            if settings.DEBUG:
                msg += "This page %r doesn't exist in any languages???" % pagetree
            raise Http404(msg)

        if tried_languages and show_lang_errors:
            messages.error(request,
                _(
                    "PageMeta %(slug)s doesn't exist in client"
                    " favored language %(tried_languages)s,"
                    " use %(used_code)s entry."
                ) % {
                    "slug": pagetree.slug,
                    "tried_languages": ", ".join([l.description for l in tried_languages]),
                    "used_code": pagemeta.language.description,
                }
            )

        return pagemeta


    def get_page_from_url(self, request, url_path):
        """
        returns a tuple the page tree instance from the given url_path
        XXX: move it out from model?
        """
        if not request.user.is_superuser:
            user_groups = request.user.groups.all()

        path = url_path.strip("/").split("/")
        page = None
        for no, page_slug in enumerate(path):
            if slugify(page_slug) != page_slug.lower():
                raise PageTree.DoesNotExist("url part %r is not slugify" % page_slug)

            try:
                page = PageTree.on_site.get(parent=page, slug=page_slug)
            except PageTree.DoesNotExist:
                etype, evalue, etb = sys.exc_info()
                evalue = etype("Wrong url %r: %s" % (page_slug, evalue))
                raise etype, evalue, etb

            page_view_group = page.permitViewGroup

            # Check permissions
            if request.user.is_anonymous():
                # Anonymous user are in no user group
                if page_view_group != None:
                    msg = "Permission deny"
                    if settings.DEBUG:
                        msg += " (url part: %s)" % escape(page_slug)
                    raise PermissionDenied(msg)
            elif not request.user.is_superuser: # Superuser can see everything ;)
                if (page_view_group != None) and (page_view_group not in user_groups):
                    msg = "Permission deny"
                    if settings.DEBUG:
                        msg += " (not in view group %r, url part: %r)" % (page_view_group, escape(page_slug))
                    raise PermissionDenied(msg)

            if page.page_type == PageTree.PLUGIN_TYPE:
                # It's a plugin
                prefix_url = "/".join(path[:no + 1])
                rest_url = "/".join(path[no + 1:])
#                if not rest_url.endswith("/"):
#                    rest_url += "/"
                return (page, prefix_url, rest_url)

        return (page, None, None)

    def get_backlist(self, request, pagetree=None):
        """
        Generate a list of backlinks, usefull for generating a "You are here" breadcrumb navigation.
        TODO: filter showlinks and permit settings
        TODO: filter current site
        FIXME: Think this created many database requests
        """
        if pagetree == None:
            pagetree = request.PYLUCID.pagetree

        pagemeta = self.get_pagemeta(request, pagetree, show_lang_errors=False)
        url = pagemeta.get_absolute_url()
        page_name = pagemeta.get_name()
        page_title = pagemeta.get_title()

        backlist = [{"url": url, "name": page_name, "title": page_title}]

        parent = pagetree.parent
        if parent:
            # insert parent links
            backlist = self.get_backlist(request, parent) + backlist

        return backlist


class PageTree(BaseModel, BaseTreeModel, UpdateInfoBaseModel):
    """
    The CMS page tree

    inherited attributes from TreeBaseModel:
        parent
        position

    inherited attributes from UpdateInfoBaseModel:
        createtime     -> datetime of creation
        lastupdatetime -> datetime of the last change
        createby       -> ForeignKey to user who creaded this entry
        lastupdateby   -> ForeignKey to user who has edited this entry
    """
    PAGE_TYPE = 'C'
    PLUGIN_TYPE = 'P'

    TYPE_CHOICES = (
        (PAGE_TYPE, 'CMS-Page'),
        (PLUGIN_TYPE , 'PluginPage'),
    )
    TYPE_DICT = dict(TYPE_CHOICES)

    objects = PageTreeManager()

    slug = models.SlugField(unique=False, help_text="(for building URLs)")

    site = models.ForeignKey(Site, default=Site.objects.get_current)
    on_site = CurrentSiteManager()

    page_type = models.CharField(max_length=1, choices=TYPE_CHOICES)

    design = models.ForeignKey("pylucid.Design", help_text="Page Template, CSS/JS files")

    showlinks = models.BooleanField(default=True,
        help_text="Accessable for all users, but don't put a Link to this page into menu/sitemap etc."
    )
    permitViewGroup = models.ForeignKey(Group, related_name="%(class)s_permitViewGroup",
        help_text="Limit viewable to a group?",
        null=True, blank=True,
    )
    permitEditGroup = models.ForeignKey(Group, related_name="%(class)s_permitEditGroup",
        help_text="Usergroup how can edit this page.",
        null=True, blank=True,
    )

    def clean_fields(self, exclude):
        """
        We must call clean_slug() here, because it needs a queryset. 
        """
        # check if parent is the same entry: child <-> parent loop:
        super(PageTree, self).clean_fields(exclude)

        # Check if slug exist in the same sub tree:
        if "slug" not in exclude:
            queryset = PageTree.on_site.filter(slug=self.slug, parent=self.parent)

            # Exclude the current object from the query if we are editing an
            # instance (as opposed to creating a new one)
            if self.pk is not None:
                queryset = queryset.exclude(pk=self.pk)

            exists = queryset.count()
            if exists:
                if self.parent == None: # parent is the tree root
                    parent_url = "/"
                else:
                    parent_url = self.parent.get_absolute_url()

                msg = "Page '%s<strong>%s</strong>/' exists already." % (parent_url, self.slug)
                message_dict = {"slug": (mark_safe(msg),)}
                raise ValidationError(message_dict)

        # Check if parent page is a ContentPage, a plugin page can't have any sub pages!
        if "parent" not in exclude and self.parent is not None and self.parent.page_type != self.PAGE_TYPE:
            parent_url = self.parent.get_absolute_url()
            msg = _(
                "Can't use the <strong>plugin</strong> page '%s' as parent page!"
                " Please choose a <strong>content</strong> page."
            ) % parent_url
            message_dict = {"parent": (mark_safe(msg),)}
            raise ValidationError(message_dict)

    _url_cache = {}
    def get_absolute_url(self):
        """ absolute url *without* language code (without domain/host part) """
        if self.pk in self._url_cache:
            #print "PageTree url cache len: %s, pk: %s" % (len(self._url_cache), self.pk)
            return self._url_cache[self.pk]

        if self.parent:
            parent_shortcut = self.parent.get_absolute_url()
            url = parent_shortcut + self.slug + "/"
        else:
            url = "/" + self.slug + "/"

        self._url_cache[self.pk] = url
        return url

    def get_absolute_uri(self):
        """ absolute url with domain/host part (but without language code) """
        absolute_url = self.get_absolute_url()
        domain = self.site.domain
        return "http://" + domain + absolute_url

    def save(self, *args, **kwargs):
        """ reset PageMeta and PageTree url cache """
        from pagemeta import PageMeta # against import loops.

        self._url_cache.clear()
        PageMeta._url_cache.clear()
        cache.clear() # FIXME: This cleaned the complete cache for every site!
        return super(PageTree, self).save(*args, **kwargs)

    def get_site(self):
        """ used e.g. for self.get_absolute_uri() and the admin page """
        return self.site

    def __unicode__(self):
        return u"PageTree %r (id: %i, site: %s, type: %s)" % (
            self.slug, self.id, self.site.domain, self.TYPE_DICT.get(self.page_type)
        )

    class Meta:
        app_label = 'pylucid'
        verbose_name_plural = verbose_name = "PageTree"
        unique_together = (("site", "slug", "parent"),)

        # FIXME: It would be great if we can order by get_absolute_url()
#        ordering = ("site", "id", "position")
        ordering = ("-lastupdatetime",)


# Check Meta.unique_together manually
model_utils.auto_add_check_unique_together(PageTree)


