# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals

from sqlalchemy import literal

from django.core.exceptions import ImproperlyConfigured
from django.core.paginator import InvalidPage, Paginator
from django.http import Http404
from django.utils.translation import gettext
from django.views.generic.base import TemplateResponseMixin, View

from .base import SQLAlchemyMixin


class MultipleObjectMixin(SQLAlchemyMixin):
    """A mixin for views manipulating multiple objects."""
    allow_empty = True
    paginate_by = None
    paginate_orphans = 0
    paginator_class = Paginator
    page_kwarg = "page"
    ordering = None
    context_object_name = "object_list"

    def get_queryset(self):
        """
        Return the list of items for this view.
        The return value must be an iterable and may be an instance of
        `QuerySet` in which case `QuerySet` specific behavior will be enabled.
        """
        queryset = super(MultipleObjectMixin, self).get_queryset()

        ordering = self.get_ordering()
        if ordering is not None:
            queryset = queryset.order_by(*ordering)

        return queryset

    def get_ordering(self):
        """Return the field or fields to use for ordering the queryset."""
        return self.ordering

    def get_paginate_by(self, queryset):
        """
        Get the number of items to paginate by, or ``None`` for no pagination.
        """
        return self.paginate_by

    def get_paginate_orphans(self):
        """
        Return the maximum number of orphans extend the last page by when
        paginating.
        """
        return self.paginate_orphans

    def get_paginator(self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs):
        """Return an instance of the paginator for this view."""
        return self.paginator_class(
            queryset, per_page, orphans=orphans, allow_empty_first_page=allow_empty_first_page, **kwargs
        )

    def paginate_queryset(self, queryset, page_size):
        """
        Paginate queryset
        """
        paginator = self.get_paginator(
            queryset, page_size, orphans=self.get_paginate_orphans(), allow_empty_first_page=self.get_allow_empty()
        )
        page_kwarg = self.page_kwarg
        page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
        try:
            page_number = int(page)
        except ValueError:
            if page == "last":
                page_number = paginator.num_pages
            else:
                raise Http404(gettext("Page is not 'last', nor can it be converted to an int."))

        try:
            page = paginator.page(page_number)
            return (paginator, page, page.object_list, page.has_other_pages())

        except InvalidPage as e:
            raise Http404(
                gettext("Invalid page (%(page_number)s): %(message)s") % {"page_number": page_number, "message": str(e)}
            )

    def get_context_object_name(self, object_list):
        """Get the name to use for the object."""
        if self.context_object_name:
            return self.context_object_name

        model = self.get_model()
        return "%s_list" % model.__name__.lower()

    def get_allow_empty(self):
        """
        Return ``True`` if the view should display empty lists and ``False``
        if a 404 should be raised instead.
        """
        return self.allow_empty

    def get_context_data(self, object_list=None, **kwargs):
        """Get the context for this view."""
        queryset = object_list if object_list is not None else self.object_list
        page_size = self.get_paginate_by(queryset)
        context_object_name = self.get_context_object_name(queryset)
        if page_size:
            paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
            context = {"paginator": paginator, "page_obj": page, "is_paginated": is_paginated, "object_list": queryset}
        else:
            context = {"paginator": None, "page_obj": None, "is_paginated": False, "object_list": queryset}
        if context_object_name is not None:
            context[context_object_name] = queryset
        context.update(kwargs)
        return super(MultipleObjectMixin, self).get_context_data(**context)


class BaseListView(MultipleObjectMixin, View):
    """A base view for displaying a list of objects."""

    def get(self, request, *args, **kwargs):
        self.object_list = self.get_queryset()
        allow_empty = self.get_allow_empty()

        if not allow_empty:
            # When pagination is enabled and object_list is a queryset,
            # it's better to do a cheap query than to load the unpaginated
            # queryset in memory.
            if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, "exists"):
                session = self.session or self.object_list.session
                is_empty = not session.query(literal(True)).filter(self.object_list.exists()).scalar()
            else:
                is_empty = not self.object_list
            if is_empty:
                raise Http404(
                    gettext("Empty list and '%(class_name)s.allow_empty' is False.")
                    % {"class_name": self.__class__.__name__}
                )

        context = self.get_context_data()
        return self.render_to_response(context)


class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
    """Mixin for responding with a template and list of objects."""
    template_name_suffix = "_list"

    def get_template_names(self):
        """
        Return a list of template names to be used for the request. Must return a list. May not be called if
        render_to_response is overridden.
        """
        try:
            names = super(MultipleObjectTemplateResponseMixin, self).get_template_names()
        except ImproperlyConfigured:
            # If template_name isn't specified, it's not a problem --
            # we just start with an empty list.
            names = []

            # If the list is a queryset, we'll invent a template name based on the
            # app and model name. This name gets put at the end of the template
            # name list so that user-supplied names override the automatically-
            # generated ones.

            if hasattr(self, "get_model_template_name"):
                names.append(self.get_model_template_name())
            elif not names:
                raise ImproperlyConfigured(
                    "%(cls)s requires either a 'template_name' attribute or a get_queryset() method that returns a "
                    "QuerySet." % {"cls": self.__class__.__name__}
                )

        return names


class ListView(MultipleObjectTemplateResponseMixin, BaseListView):
    """
    Render some list of objects, set by `self.model` or `self.queryset`.
    `self.queryset` can actually be any iterable of items, not just a queryset.
    """
