"""Django EL Pagination utility functions."""

from __future__ import unicode_literals

from el_pagination import exceptions
from el_pagination.settings import (
    DEFAULT_CALLABLE_AROUNDS, DEFAULT_CALLABLE_ARROWS,
    DEFAULT_CALLABLE_EXTREMES, PAGE_LABEL,
)


def get_data_from_context(context):
    """Get the django paginator data object from the given *context*.

    The context is a dict-like object. If the context key ``endless``
    is not found, a *PaginationError* is raised.
    """
    try:
        return context['endless']
    except KeyError:
        raise exceptions.PaginationError(
            'Cannot find endless data in context.')


def get_page_number_from_request(
        request, querystring_key=PAGE_LABEL, default=1):
    """Retrieve the current page number from *GET* or *POST* data.

    If the page does not exists in *request*, or is not a number,
    then *default* number is returned.
    """
    try:
        return int(request.GET.get(querystring_key,
            request.POST.get(querystring_key)))
    except (KeyError, TypeError, ValueError):
        return default


def get_page_numbers(
        current_page, num_pages,
        extremes=DEFAULT_CALLABLE_EXTREMES,
        arounds=DEFAULT_CALLABLE_AROUNDS,
        arrows=DEFAULT_CALLABLE_ARROWS):
    """Default callable for page listing.

    Produce a Digg-style pagination.
    """
    page_range = range(1, num_pages + 1)
    pages = []
    if current_page != 1:
        if arrows:
            pages.append('first')
        pages.append('previous')

    # Get first and last pages (extremes).
    first = page_range[:extremes]
    pages.extend(first)
    last = page_range[-extremes:]

    # Get the current pages (arounds).
    current_start = current_page - 1 - arounds
    if current_start < 0:
        current_start = 0
    current_end = current_page + arounds
    if current_end > num_pages:
        current_end = num_pages
    current = page_range[current_start:current_end]

    # Mix first with current pages.
    to_add = current
    if extremes:
        diff = current[0] - first[-1]
        if diff > 1:
            pages.append(None)
        elif diff < 1:
            to_add = current[abs(diff) + 1:]
    pages.extend(to_add)

    # Mix current with last pages.
    if extremes:
        diff = last[0] - current[-1]
        to_add = last
        if diff > 1:
            pages.append(None)
        elif diff < 1:
            to_add = last[abs(diff) + 1:]
        pages.extend(to_add)

    if current_page != num_pages:
        pages.append('next')
        if arrows:
            pages.append('last')
    return pages


def _iter_factors(starting_factor=1):
    """Generator yielding something like 1, 3, 10, 30, 100, 300 etc.

    The series starts from starting_factor.
    """
    while True:
        yield starting_factor
        yield starting_factor * 3
        starting_factor *= 10


def _make_elastic_range(begin, end):
    """Generate an S-curved range of pages.

    Start from both left and right, adding exponentially growing indexes,
    until the two trends collide.
    """
    # Limit growth for huge numbers of pages.
    starting_factor = max(1, (end - begin) // 100)
    factor = _iter_factors(starting_factor)
    left_half, right_half = [], []
    left_val, right_val = begin, end

    while left_val < right_val:
        left_half.append(left_val)
        right_half.append(right_val)
        next_factor = next(factor)
        left_val = begin + next_factor
        right_val = end - next_factor
    # If the trends happen to meet exactly at one point, retain it.
    if left_val == right_val:
        left_half.append(left_val)
    right_half.reverse()
    return left_half + right_half


def get_elastic_page_numbers(current_page, num_pages):
    """Alternative callable for page listing.

    Produce an adaptive pagination, useful for big numbers of pages, by
    splitting the num_pages ranges in two parts at current_page. Each part
    will have its own S-curve.
    """
    if num_pages <= 10:
        return list(range(1, num_pages + 1))
    if current_page == 1:
        pages = [1]
    else:
        pages = ['first', 'previous']
        pages.extend(_make_elastic_range(1, current_page))
    if current_page != num_pages:
        pages.extend(_make_elastic_range(current_page, num_pages)[1:])
        pages.extend(['next', 'last'])
    return pages


def get_querystring_for_page(
        request, page_number, querystring_key, default_number=1):
    """Return a querystring pointing to *page_number*."""
    querydict = request.GET.copy()
    querydict[querystring_key] = page_number
    # For the default page number (usually 1) the querystring is not required.
    if page_number == default_number:
        del querydict[querystring_key]
    if 'querystring_key' in querydict:
        del querydict['querystring_key']
    if querydict:
        return '?' + querydict.urlencode()
    return ''


def normalize_page_number(page_number, page_range):
    """Handle a negative *page_number*.

    Return a positive page number contained in *page_range*.
    If the negative index is out of range, return the page number 1.
    """
    try:
        return page_range[page_number]
    except IndexError:
        return page_range[0]
