# Copyright (c) 2008, Eric Florenzano
# Copyright (c) 2010, 2011 Linaro Limited
# Copyright (c) 2016 Daniel Roy Greenfeld
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
#       copyright notice, this list of conditions and the following
#       disclaimer in the documentation and/or other materials provided
#       with the distribution.
#     * Neither the name of the author nor the names of other
#       contributors may be used to endorse or promote products derived
#       from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.paginator import Paginator, InvalidPage
from django.http import Http404
from django.template import (
    Context,
    Library,
    Node,
    TemplateSyntaxError,
    Variable,
    loader,
)

try:
    from django.template.base import TOKEN_BLOCK
except ImportError:     # Django < 1.8
    from django.template import TOKEN_BLOCK

from django.template.loader import select_template
from django.utils.text import unescape_string_literal

# TODO, import this normally later on
from dj_pagination.settings import *


def do_autopaginate(parser, token):
    """
    Splits the arguments to the autopaginate tag and formats them correctly.

    Syntax is:

        autopaginate QUERYSET [PAGINATE_BY] [ORPHANS] [as NAME]
    """
    # Check whether there are any other autopaginations are later in this template
    expr = lambda obj: (obj.token_type == TOKEN_BLOCK and \
        len(obj.split_contents()) > 0 and obj.split_contents()[0] == "autopaginate")
    multiple_paginations = len([tok for tok in parser.tokens if expr(tok)]) > 0

    i = iter(token.split_contents())
    paginate_by = None
    queryset_var = None
    context_var = None
    orphans = None
    word = None
    try:
        word = next(i)
        assert word == "autopaginate"
        queryset_var = next(i)
        word = next(i)
        if word != "as":
            paginate_by = word
            try:
                paginate_by = int(paginate_by)
            except ValueError:
                pass
            word = next(i)
        if word != "as":
            orphans = word
            try:
                orphans = int(orphans)
            except ValueError:
                pass
            word = next(i)
        assert word == "as"
        context_var = next(i)
    except StopIteration:
        pass
    if queryset_var is None:
        raise TemplateSyntaxError(
            "Invalid syntax. Proper usage of this tag is: "
            "{% autopaginate QUERYSET [PAGINATE_BY] [ORPHANS]"
            " [as CONTEXT_VAR_NAME] %}")
    return AutoPaginateNode(queryset_var, multiple_paginations, paginate_by, orphans, context_var)


class AutoPaginateNode(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,  multiple_paginations, paginate_by=None,
                 orphans=None, context_var=None):
        if paginate_by is None:
            paginate_by = DEFAULT_PAGINATION
        if orphans is None:
            orphans = DEFAULT_ORPHANS
        self.queryset_var = Variable(queryset_var)
        if isinstance(paginate_by, int):
            self.paginate_by = paginate_by
        else:
            self.paginate_by = Variable(paginate_by)
        if isinstance(orphans, int):
            self.orphans = orphans
        else:
            self.orphans = Variable(orphans)
        self.context_var = context_var
        self.multiple_paginations = multiple_paginations

    def render(self, context):
        # Save multiple_paginations state in context
        if self.multiple_paginations and 'multiple_paginations' not in context:
            context['multiple_paginations'] = True

        if context.get('multiple_paginations') or getattr(context, "paginator", None):
            page_suffix = '_%s' % self.queryset_var
        else:
            page_suffix = ''

        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)
        if isinstance(self.orphans, int):
            orphans = self.orphans
        else:
            orphans = self.orphans.resolve(context)
        paginator = Paginator(value, paginate_by, orphans)
        try:
            request = context['request']
        except KeyError:
            raise ImproperlyConfigured(
                "You need to enable 'django.core.context_processors.request'."
                " See linaro-django-pagination/README file for TEMPLATE_CONTEXT_PROCESSORS details")
        try:
            page_obj = paginator.page(request.page(page_suffix))
        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 ''
        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
        context['page_suffix'] = page_suffix
        return ''


class PaginateNode(Node):

    def __init__(self, template=None):
        self.template = template

    def render(self, context):
        template_list = ['pagination/pagination.html']
        new_context = paginate(context)
        if self.template:
            template_list.insert(0, self.template)
        return loader.render_to_string(template_list, new_context)



def do_paginate(parser, token):
    """
    Emits the pagination control for the most recent autopaginate list

    Syntax is:

        paginate [using "TEMPLATE"]

    Where TEMPLATE is a quoted template name. If missing the default template
    is used (paginate/pagination.html).
    """
    argv = token.split_contents()
    argc = len(argv)
    if argc == 1:
        template = None
    elif argc == 3 and argv[1] == 'using':
        template = unescape_string_literal(argv[2])
    else:
        raise TemplateSyntaxError(
            "Invalid syntax. Proper usage of this tag is: "
            "{% paginate [using \"TEMPLATE\"] %}")
    return PaginateNode(template)


def paginate(context, window=DEFAULT_WINDOW, 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
    exceeds pagination border (1 and end), window is moved 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 ValueError('Parameter "window" cannot be less than zero')
    if margin < 0:
        raise ValueError('Parameter "margin" cannot be less than zero')
    try:
        paginator = context['paginator']
        page_obj = context['page_obj']
        page_suffix = context.get('page_suffix', '')
        page_range = list(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_start < 0:
            window_end = window_end - window_start
            window_start = 0
        if window_end > paginator.num_pages:
            window_start = max(0, window_start - (window_end - paginator.num_pages))
            window_end = paginator.num_pages
        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)

        new_context = {
            'MEDIA_URL': settings.MEDIA_URL,
            'STATIC_URL': getattr(settings, "STATIC_URL", None),
            'disable_link_for_first_page': DISABLE_LINK_FOR_FIRST_PAGE,
            'display_disabled_next_link': DISPLAY_DISABLED_NEXT_LINK,
            'display_disabled_previous_link': DISPLAY_DISABLED_PREVIOUS_LINK,
            'display_page_links': DISPLAY_PAGE_LINKS,
            'is_paginated': paginator.count > paginator.per_page,
            'next_link_decorator': NEXT_LINK_DECORATOR,
            'page_obj': page_obj,
            'page_suffix': page_suffix,
            'pages': pages,
            'paginator': paginator,
            'previous_link_decorator': PREVIOUS_LINK_DECORATOR,
            'records': records,
        }
        if 'request' in context:
            getvars = context['request'].GET.copy()
            if 'page%s' % page_suffix in getvars:
                del getvars['page%s' % page_suffix]
            if len(getvars.keys()) > 0:
                new_context['getvars'] = "&%s" % getvars.urlencode()
            else:
                new_context['getvars'] = ''
        return new_context
    except (KeyError, AttributeError):
        return {}


register = Library()
register.tag('paginate', do_paginate)
register.tag('autopaginate', do_autopaginate)
