try:
    set
except NameError:
    from sets import Set as set

from django import template
from django.http import Http404
from django.core.paginator import Paginator, InvalidPage
from django.conf import settings

from __init__ import build_url

register = template.Library()

DEFAULT_PAGINATION = getattr(settings, 'PAGINATION_DEFAULT_PAGINATION', 20)
DEFAULT_WINDOW = getattr(settings, 'PAGINATION_DEFAULT_WINDOW', 4)
DEFAULT_MARGIN = getattr(settings, 'PAGINATION_DEFAULT_MARGIN', DEFAULT_WINDOW)
DEFAULT_ORPHANS = getattr(settings, 'PAGINATION_DEFAULT_ORPHANS', 0)
INVALID_PAGE_RAISES_404 = getattr(settings,
    'PAGINATION_INVALID_PAGE_RAISES_404', False)

def do_autopaginate(parser, token):
    """
    Splits the arguments to the autopaginate tag and formats them correctly.
    """
    split = token.split_contents()
    as_index = None
    context_var = None
    for i, bit in enumerate(split):
        if bit == 'as':
            as_index = i
            break
    if as_index is not None:
        try:
            context_var = split[as_index + 1]
        except IndexError:
            raise template.TemplateSyntaxError("Context variable assignment " +
                "must take the form of {%% %r object.example_set.all ... as " +
                "context_var_name %%}" % split[0])
        del split[as_index:as_index + 2]
    if len(split) == 2:
        return AutoPaginateNode(split[1])
    elif len(split) == 3:
        return AutoPaginateNode(split[1], paginate_by=split[2],
            context_var=context_var)
    elif len(split) == 4:
        try:
            orphans = int(split[3])
        except ValueError:
            raise template.TemplateSyntaxError(u'Got %s, but expected integer.'
                % split[3])
        return AutoPaginateNode(split[1], paginate_by=split[2], orphans=orphans,
            context_var=context_var)
    else:
        raise template.TemplateSyntaxError('%r tag takes one required ' +
            'argument and one optional argument' % split[0])

class AutoPaginateNode(template.Node):
    """
    Emits the required objects to allow for Digg-style pagination.
    
    First, it looks in the current context for the variable specified, and using
    that object, it emits a simple ``Paginator`` and the current page object 
    into the context names ``paginator`` and ``page_obj``, respectively.
    
    It will then replace the variable specified with only the objects for the
    current page.
    
    .. note::
        
        It is recommended to use *{% paginate %}* after using the autopaginate
        tag.  If you choose not to use *{% paginate %}*, make sure to display the
        list of available pages, or else the application may seem to be buggy.
    """
    def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION,
        orphans=DEFAULT_ORPHANS, context_var=None):
        self.queryset_var = template.Variable(queryset_var)
        if isinstance(paginate_by, int):
            self.paginate_by = paginate_by
        else:
            self.paginate_by = template.Variable(paginate_by)
        self.orphans = orphans
        self.context_var = context_var

    def render(self, context):
        key = self.queryset_var.var
        value = self.queryset_var.resolve(context)
        if isinstance(self.paginate_by, int):
            paginate_by = self.paginate_by
        else:
            paginate_by = self.paginate_by.resolve(context)
        paginator = Paginator(value, paginate_by, self.orphans)
        try:
            page_obj = paginator.page(context['request'].page)
        except InvalidPage:
            if INVALID_PAGE_RAISES_404:
                raise Http404('Invalid page requested.  If DEBUG were set to ' +
                    'False, an HTTP 404 page would have been shown instead.')
            context[key] = []
            context['invalid_page'] = True
            return u''
        if self.context_var is not None:
            context[self.context_var] = page_obj.object_list
        else:
            context[key] = page_obj.object_list
        context['paginator'] = paginator
        context['page_obj'] = page_obj
        return u''


def paginate(context, window=DEFAULT_WINDOW, hashtag='', margin=DEFAULT_MARGIN):
    """
    Renders the ``pagination/pagination.html`` template, resulting in a
    Digg-like display of the available pages, given the current page.  If there
    are too many pages to be displayed before and after the current page, then
    elipses will be used to indicate the undisplayed gap between page numbers.
    
    Requires one argument, ``context``, which should be a dictionary-like data
    structure and must contain the following keys:
    
    ``paginator``
        A ``Paginator`` or ``QuerySetPaginator`` object.
    
    ``page_obj``
        This should be the result of calling the page method on the 
        aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
        the current page.
    
    This same ``context`` dictionary-like data structure may also include:
    
    ``getvars``
        A dictionary of all of the **GET** parameters in the current request.
        This is useful to maintain certain types of state, even when requesting
        a different page.
    
    Argument ``window`` is number to pages before/after current page. If window
    exceed pagination border (1 and end), window is move to left or right.
    Argument ``margin``` is number of pages on start/end of pagination. 
    Example:
        window=2, margin=1, current=6     1 ... 4 5 [6] 7 8 ... 11 
        window=2, margin=0, current=1     [1] 2 3 4 5 ...
        window=2, margin=0, current=5     ... 3 4 [5] 6 7 ...
        window=2, margin=0, current=11     ... 7 8 9 10 [11]
        """

    if window < 0:
        raise Exception, 'Parameter "window" cannot be less than zero'
    if margin < 0:
        raise Exception, 'Parameter "margin" cannot be less than zero'
    
    try:
        paginator = context['paginator']
        page_obj = context['page_obj']
        page_range = paginator.page_range
        # Calculate the record range in the current page for display.
        records = {'first': 1 + (page_obj.number - 1) * paginator.per_page}
        records['last'] = records['first'] + paginator.per_page - 1
        if records['last'] + paginator.orphans >= paginator.count:
            records['last'] = paginator.count

        # figure window
        window_start = page_obj.number - window - 1
        window_end = page_obj.number + window
        # solve if window exceeded page range
        if window_end > paginator.num_pages:
            window_start = window_start - (window_end - paginator.num_pages)
            window_end = paginator.num_pages
        if window_start < 0:
            window_end = window_end - window_start
            window_start = 0
        pages = page_range[window_start:window_end]

        # figure margin and add elipses
        if margin > 0:
            # figure margin
            tmp_pages = set(pages)
            tmp_pages = tmp_pages.union(page_range[:margin])
            tmp_pages = tmp_pages.union(page_range[-margin:])
            tmp_pages = list(tmp_pages)
            tmp_pages.sort()
            pages = []
            pages.append(tmp_pages[0])
            for i in range(1, len(tmp_pages)):
                # figure gap size => add elipses or fill in gap
                gap = tmp_pages[i] - tmp_pages[i - 1]
                if gap >= 3:
                    pages.append(None)
                elif gap == 2:
                    pages.append(tmp_pages[i] - 1)
                pages.append(tmp_pages[i])
        else:
            if pages[0] != 1:
                pages.insert(0, None)
            if pages[-1] != paginator.num_pages:
                pages.append(None)
        
        # generate page urls
        page_obj.previous_page_url = build_url(context['request'], page_obj.previous_page_number(), hashtag)
        page_obj.next_page_url = build_url(context['request'], page_obj.next_page_number(), hashtag)
        for i, page in enumerate(pages):
            if page == None:
                continue
            pages[i] = (page, build_url(context['request'], page, hashtag))

        to_return = context
        to_return.update({
            'MEDIA_URL': settings.MEDIA_URL,
            'pages': pages,
            'records': records,
            'page_obj': page_obj,
            'paginator': paginator,
            'hashtag': hashtag,
            'is_paginated': paginator.count > paginator.per_page,
        })
        return to_return
    except KeyError, AttributeError:
        return {}

register.inclusion_tag('pagination/pagination.html', takes_context=True)(
    paginate)
register.tag('autopaginate', do_autopaginate)
