from django.contrib.flatpages.models import FlatPage
from django.template import loader, RequestContext
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect, HttpResponse, Http404, HttpResponseNotFound
from django.conf import settings
from django.core.cache import cache
from django.conf import settings
from django.contrib.sites.models import Site
from django.contrib.flatpages import views as flatpageviews
from templatetags.restructuredtext import restructuredparts, restructuredtext
from django.views.generic.list_detail import object_list
from forms import RstPageForm
from models import FlatPageHistory
import datetime
import difflib
import re
from feeds import PageHistoryFeed, RecentChangesFeed, FeedDoesNotExist

single_nbsp = re.compile("(?<!&nbsp;)(?<!^)&nbsp;(?!&nbsp;)", re.MULTILINE)

DEFAULT_STATIC_TEMPLATE         = 'restructuredtext/static.html'
DEFAULT_PAGE_HISTORY_TEMPLATE   = 'restructuredtext/history.html'
HISTORY_DIFF_ALLOW_WRAP         = True
EDIT_LOCK_TIME                  = 10

def rststatic(request, rel_path, document_root, template_name=None):
    #print rel_path, document_root
    ## look for document_root/rel_path(.rst|.txt)?
    if document_root.endswith('/') and rel_path.startswith('/'):
        document_root = document_root[:-1]
    if rel_path.endswith('/'): ## and it should
        rel_path = rel_path[:-1]
    base = document_root + rel_path
    from os.path import isfile, getmtime
    filename = base + '.txt'
    ## if not found, raise 404
    if not isfile(filename):
        filename = base + '.rst'
        if not isfile(filename):
            raise Http404('File not found: ' + rel_path)
    ## if found, get change date, and look in cache
    content = title = ''
    try:
        mtime = getmtime(filename)
        # if in cache and newer than disk, render with cache
        content = cache.get(filename)
        if content is not None and content[0]>= mtime:
            title = content[1]
            content = content[2]
        else:
            # if not in cache or older, re-render and cache
            parts = restructuredparts(file(filename, "U").read())
            title = parts['title']
            content = parts["html_body"]
            cache.set(filename, (mtime, title, content), 60*60*24*10) ## 10 day
    except:
        # if error, and debug, show the error
        if settings.DEBUG:
            raise
        raise Http404('File not found.')

    ## return page.
    if template_name:
        t = loader.select_template((template_name, DEFAULT_STATIC_TEMPLATE))
    else:
        t = loader.get_template(DEFAULT_STATIC_TEMPLATE)
    c = RequestContext(request, {
        'title': title,
        'content': content,
        'last_modified': datetime.datetime.fromtimestamp(mtime)
    })
    response = HttpResponse(t.render(c))
    return response

def user_meets_minimum_login(user, login_type):
    if login_type == 'anonymous':
        ## deals with inactive accounts.
        return user.is_anonymous() or user.is_authenticated()
    elif login_type == 'authenticated':
        return user.is_authenticated()
    elif login_type == 'staff':
        return user.is_authenticated() and (user.is_staff or user.is_superuser)
    elif login_type == 'superuser':
        return user.is_authenticated() and user.is_superuser
    else:
        raise TypeError("Invalid argument: unknown login type.")
    return False

def rstpage(request, url=None, prefix=None, template=None,
            help_url=None, login_type='authenticated'):
    """
    ReST Flat page view.

    login_type must be one of 'anonymous', 'authenticated', 'staff', 'superuser'
    defaults to 'authenticated'. This is for editing, not viewing.
    """
    ## RED_FLAG: Add SiteFeature support for turning off the creation
    ##           of new pages, and editing of existing as seperate
    ##           features.

    ## RED_FLAG: sometimes when a site is getting hammered, django can get
    ##           a wierd exception in the muddleware resulting in no
    ##           user. When this happens the subsiquent middlewares are not
    ##           called. We need to test for that here and raise a Http503
    ##           when this happens.
    if not hasattr(request, 'user'):
        return HttpResponse('Your request could not be completed due to '
                            'excessive traffic.\n'
                            'Please try again later.\n', status=503)

    ## compute the url path for the flatpage
    if url is None:
        url = request.path
    if not url.startswith('/'):
        url = "/" + url
    if prefix is not None:
        if prefix.endswith('/'): prefix = prefix[:-1]
        if not prefix.startswith('/'): prefix = '/' + prefix
        url = prefix + url
    if help_url is None and 'RSTPAGES_HELP_URL' in settings:
        help_url = getattr(settings, 'RSTPAGES_HELP_URL')

    ## get the flatpage, or deal with the 404 handling
    f = None
    try:
        f = get_object_or_404(FlatPage, url__exact=url,
                              sites__id__exact=settings.SITE_ID)
    except Http404:
        if not user_meets_minimum_login(request.user, login_type): raise
        #if not allow_anon request.user.has_perm('flatpage_create'): raise
        if not request.POST:
            ## 404 page with 'Create' button.
            try:
                fp = FlatPage.objects.get(url__exact=url)
                if fp.flatpagehistory_set.count()==0:
                    ## regular flatpage for another site.
                    raise Http404("Page not found.")
                deleted = True
            except FlatPage.DoesNotExist:
                deleted = False
            templates = [flatpageviews.DEFAULT_TEMPLATE]
            if template is not None and template:
                templates.insert(0, template)
            t = loader.select_template(templates)
            f = {'title': '404 - Page not found',
                 'content': '<h1>404: Page not found.</h1>' }
            fph = { 'do_404': True, 'deleted': deleted }
            return HttpResponseNotFound(t.render(
                        RequestContext(request,{'flatpage': f, 'fph': fph})))
    else:
        # If registration is required for accessing this page, and the user
        # isn't logged in, redirect to the login page.
        if f.registration_required and not request.user.is_authenticated():
            from django.contrib.auth.views import redirect_to_login
            return redirect_to_login(request.path)

    title = ""
    content = ""

    ## initialize some vars to defaults
    form = None
    do_edit = False
    do_create = False
    do_view = False
    do_ver = False
    is_valid = False
    can_revert = False
    can_delete = False ## this is a stub for perminent delete.
    last_modified = datetime.datetime.now()

    lockid = request.session.session_key
    if not request.user.is_anonymous(): lockid = request.user.id
    lock_name = 'fph|' + url
    lock = cache.get(lock_name)
    is_locked = lock is not None
    have_lock = lock == lockid
    ver_lock = None
    is_collision = False

    ## Some permissions checks
    can_edit = user_meets_minimum_login(request.user, login_type)
    regular_flatpage = False

    ## Figure out what template to use.
    templates = [flatpageviews.DEFAULT_TEMPLATE]
    if template is not None and template:
        templates.insert(0, template)
    if f is not None and f.template_name:
        templates.insert(0, f.template_name)
    if request.POST:
        ## all form pages come from a POST
        if not can_edit:
            if have_lock: cache.delete(lock_name)
            return HttpResponseRedirect(request.path)
        post = request.POST
        title = ''
        content = ''
        binding = None
        if '__ver_lock' in post: ver_lock=int(post['__ver_lock'])

        if '__current' in post:
            return HttpResponseRedirect(request.path)
        elif '__cancel' in post:
            if have_lock: cache.delete(lock_name)
            return HttpResponseRedirect(request.path)
        elif '__create' in post:
            do_create = True
            title = 'New Page'
            content = ''
            if ver_lock is None:
                ver_lock = 0
            else:
                is_collision=True
            if not is_locked and not is_collision:
                cache.set(lock_name, lockid, 60*EDIT_LOCK_TIME)
                is_locked=True
                have_lock=True
        elif '__restore' in post:
            try:
                rpage = FlatPage.objects.get(url__exact=url)
                rpage.flatpagehistory_set.all()[0:1].get()
            except (FlatPage.DoesNotExist, FlatPageHistory.DoesNotExist):
                return HttpResponseRedirect(request.path)
            rpage.sites.add(Site.objects.get_current())
            rpage.save()
            return HttpResponseRedirect(request.path)
        elif '__preview' in post:
            if 'content' in post and post['content']:
                parts = restructuredparts(post['content'])
                title = parts['title']
                content = parts['html_body']
            do_create = f is None
            do_edit = not do_create
            if have_lock: cache.set(lock_name, lockid, 60*EDIT_LOCK_TIME)
            elif do_edit:
                try:
                    hist = f.flatpagehistory_set.all()[0]
                    if ver_lock is None: ver_lock = hist.id
                    else: is_collision = ver_lock != hist.id
                except (IndexError, FlatPageHistory.DoesNotExist):
                    if ver_lock is None: ver_lock = 0
                    else: is_collision = ver_lock != 0
            else:
                if ver_lock is None: ver_lock = 0
                else: is_collision = ver_lock != 0
            if not is_locked and not is_collision:
                cache.set(lock_name, lockid, 60*EDIT_LOCK_TIME)
                is_locked=True
                have_lock=True
        elif '__edit' in post and f:
            do_edit = True
            title = f.title
            content = f.content
            try:
                hist = f.flatpagehistory_set.all()[0]
                binding = {'content': hist.content}
                if request.user.is_anonymous():
                    binding['username'] = 'Anonymous'
                ver_lock = hist.id
            except (IndexError, FlatPageHistory.DoesNotExist):
                return HttpResponseRedirect(request.path)
            if not is_locked:
                cache.set(lock_name, lockid, 60*EDIT_LOCK_TIME)
                is_locked=True
                have_lock=True
            elif have_lock:
                cache.set(lock_name, lockid, 60*EDIT_LOCK_TIME)
        elif '__delete_from_site' in post:
            if f is not None and (not is_locked or have_lock):
                f.sites.remove(Site.objects.get_current())
                f.save()
            if have_lock: cache.delete(lock_name)
            return HttpResponseRedirect(request.path)
        elif '__revert' in post or '__delete_ver' in post:
            if (f is None or '__ver' not in post or (is_locked and not have_lock)):
                return HttpResponseRedirect(request.path)
            if '__delete_ver' in post and not can_delete:
                if have_lock: cache.delete(lock_name)
                return HttpResponseRedirect(request.path)
            try:
                hist = f.flatpagehistory_set.get(pk=post['__ver'])
            except FlatPageHistory.DoesNotExist:
                if have_lock: cache.delete(lock_name)
                return HttpResponseRedirect(request.path)
            if '__delete_ver' in post:
                hist.delete()
            else:
                hist.revert(request.user)
            if have_lock: cache.delete(lock_name)
            return HttpResponseRedirect(request.path)
        elif '__history' in post:
            ## do history at some point.
            if f is None:
                return HttpResponseRedirect(request.path)
            history = f.flatpagehistory_set.all().select_related()
            fph = { 'do_404': False, 'do_history': True, 'do_view': False,
                    'title': f.title, 'history': history }
            f.title = 'History - ' + f.title
            t = loader.select_template(templates)
            last_modified = history[0].changed
            fph['is_today'] = (datetime.datetime.now() - last_modified <
                               datetime.timedelta(1))
            return HttpResponse(t.render(
                RequestContext(request,{'flatpage': f, 'fph': fph,
                                        'last_modified': last_modified})))
        elif '__diff' in post and '__rev1' in post and '__rev2' in post:
            if f is None or not post['__rev1'] or not post['__rev2']:
                return HttpResponseRedirect(request.path)
            try:
                ver1 = f.flatpagehistory_set.get(pk=post['__rev1'])
                ver2 = f.flatpagehistory_set.get(pk=post['__rev2'])
            except FlatPageHistory.DoesNotExist, e:
                return HttpResponseRedirect(request.path)
            differ = difflib.HtmlDiff()
            fromdesc, junk = ver1.rendered_title_and_content
            fromdesc += ' (ver %d)' % ver1.id
            fromdesc = '<a href="%s?ver=%d">'%(request.path,
                                               ver1.id)+fromdesc+'</a>'
            todesc, junk = ver2.rendered_title_and_content
            todesc += ' (ver %d)' % ver2.id
            todesc = '<a href="%s?ver=%d">'%(request.path,ver2.id)+todesc+'</a>'
            fph = { 'do_404': False, 'do_history': False, 'do_view': False,
                    'do_diff': True, 'title': f.title,
                    'ver1': ver1, 'ver2': ver2 }
            f.title = 'Diff (ver %d - ver %d) - '%(ver1.id, ver2.id) + f.title
            content = differ.make_table(ver1.content.splitlines(True),
                                        ver2.content.splitlines(True),
                                        fromdesc, todesc)
            if HISTORY_DIFF_ALLOW_WRAP:
                content = content.replace('<td nowrap="nowrap">',
                                          '<td class="line">')
                content = single_nbsp.sub(' ', content)
            f.content = content
            t = loader.select_template(templates)
            return HttpResponse(t.render(
                RequestContext(request,{'flatpage': f, 'fph': fph})))
        elif '__save' not in post:
            ## something wierd
            return HttpResponseRedirect(request.path)
        do_view = '__preview' in post or '__save' in post
        form = RstPageForm(post if do_view else binding, request.user, f, url)
        is_valid = form.is_valid()
        if '__save' in post and is_valid and (not is_locked or have_lock):
            form.save()
            if have_lock: cache.delete(lock_name)
            return HttpResponseRedirect(request.path)
    elif f is not None:
        if request.GET:
            get = request.GET
            if 'ver' in get and can_edit:
                ver = None
                try:
                    ver = f.flatpagehistory_set.get(pk=get['ver'])
                    if not is_locked:
                        can_revert = ver.id != f.flatpagehistory_set.all()[0].id
                    else:
                        can_revert = False
                except:
                    return HttpResponseRedirect(request.path)

                title, content = ver.rendered_title_and_content
                title = title + " (ver %d)" % ver.id
                last_modified = ver.changed
                do_view = True
                can_edit = False
                do_ver = ver.id
            elif 'feed' in get:
                ## this should get a cache which is cleared on save
                if get['feed'] == 'rss2':
                    try:
                        history = f.flatpagehistory_set.all().select_related()
                        feedgen = PageHistoryFeed('rstpage_history', request,
                                        history, f, limit=15).get_feed(url)
                    except FeedDoesNotExist:
                        raise Http404("Invalid feed parameters.")

                    response = HttpResponse(mimetype=feedgen.mime_type)
                    feedgen.write(response, 'utf-8')
                    return response
                raise Http404("Feed not found.")
            else:
                return HttpResponseRedirect(request.path)
        else:
            title = f.title
            content = f.content
            do_view = True
            try:
                last_modified = f.flatpagehistory_set.all()[0].changed
            except IndexError:
                ## regular flatpage.
                regular_flatpage = True
    else:
        ## something went wrong with the 404 handling above
        raise Http404("Page not found.")

    ## build the context
    if f is None:
        f = {}
        f['title'] = title
        f['content'] = content
    else:
        ## yikes, lets hope we dont save ;-)
        f.title = title
        f.content = content

    fph = None if regular_flatpage else {
        'do_404': False,
        'do_history': False,
        'do_diff': False,
        'do_ver': do_ver,
        'do_view': do_view,
        'do_edit': do_edit,
        'do_create': do_create,
        'is_valid': is_valid,
        'is_locked': is_locked,
        'is_collision': is_collision,
        'have_lock': have_lock,
        'ver_lock': ver_lock,
        'lock_time': EDIT_LOCK_TIME,
        'can_edit': can_edit,
        'can_revert': can_revert,
        'help_url': help_url,
        'form': form}

    c = RequestContext(request, {
        'flatpage': f,
        'fph': fph,
        'last_modified': last_modified,
    })

    t = loader.select_template(templates)

    response = HttpResponse(t.render(c))
    return response


def rstrecent(request, url=None, base=None, prefix=None,
              login_type='authenticated', **object_list_args):
    if not user_meets_minimum_login(request.user, login_type):
        ### special case to allow RSS feeds to be fully public. (YUCK)
        ### Will need to special case each feed type as we get more
        if not (request.GET and 'feed' in request.GET and
                request.GET['feed'] == 'rss2'):
            from django.contrib.auth.views import redirect_to_login
            return redirect_to_login(request.path)
    if 'extra_context' not in object_list_args:
        object_list_args['extra_context'] = {}
    extra_context = object_list_args['extra_context']
    if 'fph' not in extra_context or 'do_history' not in extra_context['fph']:
        extra_context['fph'] = {'do_history': True }
    if url is None:
        url = request.path
    if not url.startswith('/'):
        url = "/" + url
    if prefix is not None:
        if prefix.endswith('/'): prefix = prefix[:-1]
        if not prefix.startswith('/'): prefix = '/' + prefix
        url = prefix + url
    if base is not None and len(base):
        if base.endswith('/'): base = base[:-1]
        if not base.startswith('/'): base = '/' + base
    else:
        base = ''
    extra_context['url_base'] = base
    extra_context['url_prefix'] = prefix

    if 'title' not in extra_context:
        extra_context['title'] = 'Recent Changes'

    changes = FlatPageHistory.recent.filter(page__url__startswith=url)

    if not request.user.is_authenticated():
        changes = changes.filter(page__registration_required=False)

    if 'feed' in request.GET:
        if request.GET['feed'] == 'rss2':
            try:
                feedgen = RecentChangesFeed('rstpage_recent', request,
                                            changes, limit=15).get_feed(url)
            except FeedDoesNotExist:
                raise Http404("Invalid feed parameters.")

            response = HttpResponse(mimetype=feedgen.mime_type)
            feedgen.write(response, 'utf-8')
            return response
        raise Http404("Feed not found.")
    extra_context['is_today'] = (changes.count() and
                                 (datetime.datetime.now() - changes[0].changed) <
                                     datetime.timedelta(1))
    return object_list(request, changes.select_related(), **object_list_args)

def rstindex(request, url=None, base=None, prefix=None,
             login_type='authenticated', **object_list_args):
    if not user_meets_minimum_login(request.user, login_type):
        from django.contrib.auth.views import redirect_to_login
        return redirect_to_login(request.path)
    if 'extra_context' not in object_list_args:
        object_list_args['extra_context'] = {}
    extra_context = object_list_args['extra_context']
    if url is None:
        url = request.path
    if not url.startswith('/'):
        url = "/" + url
    if prefix is not None:
        if prefix.endswith('/'): prefix = prefix[:-1]
        if not prefix.startswith('/'): prefix = '/' + prefix
        url = prefix + url
    if base is not None and len(base):
        if base.endswith('/'): base = base[:-1]
        if not base.startswith('/'): base = '/' + base
    else:
        base = ''
    extra_context['url_base'] = base
    extra_context['url_prefix'] = prefix

    if 'title' not in extra_context:
        extra_context['title'] = 'Page Index'
    if 'template_name' not in object_list_args:
        object_list_args['template_name']="restructuredtext/flatpage_list.html"
    if 'allow_empty' not in object_list_args:
        object_list_args['allow_empty'] = True
    current = FlatPageHistory.current.filter(page__url__startswith=url)

    extra_context['startswith'] = ''
    extra_context['is_byurl'] = False
    extra_context['is_bytitle'] = True
    if request.GET:
        if 'startswith' in request.GET and request.GET['startswith']:
            extra_context['startswith'] = request.GET['startswith']
            current = current.filter(
                page__title__istartswith=request.GET['startswith'])
        if 'byurl' in request.GET:
            current = current.order_by('django_flatpage.url')
            extra_context['is_byurl'] = True
            extra_context['is_bytitle'] = False
        else:
            current = current.order_by('django_flatpage.title')
    else:
        current = current.order_by('django_flatpage.title')
    return object_list(request, current.select_related(), **object_list_args)
