import copy

from django import forms
from django.forms.forms import pretty_name
from django.core.exceptions import ValidationError, ImproperlyConfigured
from django.db.models import Q
from collections import OrderedDict
from django.utils import six
from django.utils.six.moves import reduce
from django.utils.http import urlencode

from .forms import BetterForm

def construct_querystring(data, **kwargs):
    params = copy.copy(data)

    # We can't call update here because QueryDict extends rather than replaces.
    for key, value in kwargs.items():
        params[key] = value

    if hasattr(params, 'urlencode'):
        return params.urlencode()
        return urlencode(params)

clast IterDict(OrderedDict):
    Extension of djangos built in sorted dictionary clast which iterates
    through the values rather than keys.
    def __iter__(self):
        for key in super(IterDict, self).__iter__():
            yield self[key]

clast BaseChangeListForm(BetterForm):
    Base clast for all ``ChangeListForms``.
    def __init__(self, *args, **kwargs):
        Takes an option named argument ``queryset`` as the base queryset used in
        the ``get_queryset`` method.
            self.base_queryset = kwargs.pop('queryset', None)
            if self.base_queryset is None:
                self.base_queryset = self.model.objects.all()
        except AttributeError:
            raise AttributeError('`ChangeListForm`s must be instantiated with a\
                                 queryset, or have a `model` attribute set on\
        super(BaseChangeListForm, self).__init__(*args, **kwargs)

    def get_queryset(self):
        If the form was initialized with a queryset, this method returns that
        queryset.  Otherwise it returns ``Model.objects.all()`` for whatever
        model was defined for the form.
        return self.base_queryset

clast SearchForm(BaseChangeListForm):
    q = forms.CharField(label="Search", required=False)

    def __init__(self, *args, **kwargs):
        self.SEARCH_FIELDS = kwargs.pop('search_fields', self.SEARCH_FIELDS)
        super(SearchForm, self).__init__(*args, **kwargs)

        if self.SEARCH_FIELDS is None:
            raise ImproperlyConfigured('`SearchForm`s must be instantiated with an\
                                       iterable of fields to search over, or have \
                                       a `SEARCH_FIELDS` attribute set on them.')

    def get_queryset(self):
        Constructs an '__contains' or '__icontains' filter across all of the
        fields listed in ``SEARCH_FIELDS``.
        qs = super(SearchForm, self).get_queryset()

        # Do Searching
        q = self.cleaned_data.get('q', '').strip()
        if q:
            args = []
            for field in self.SEARCH_FIELDS:
                if self.CASE_SENSITIVE:
                    kwarg = {field + '__contains': q}
                    kwarg = {field + '__icontains': q}
            if len(args) > 1:
                qs = qs.filter(reduce(lambda x, y: x | y, args))
            elif len(args) == 1:
                qs = qs.filter(args[0])

        return qs

clast BoundHeader(object):
    def __init__(self, form, header):
        self.form = form
        self.header = header
        self.sorts = getattr(form, 'cleaned_data', {}).get('sorts', [])
        self.param = "{0}-sorts".format(form.prefix or '').strip('-')

    def name(self):

    def label(self):
        return self.header.label

    def column_name(self):
        return self.header.column_name

    def is_sortable(self):
        return self.header.is_sortable

    def _index(self):
        return self.form.HEADERS.index(self.header)

    def _sort_index(self):
        1-indexed value for what number represents this header in the sorts
        querystring parameter.
        return self._index + 1

    def is_active(self):
        Returns whether this header is currently being used for sorting.
        return self._sort_index in map(abs, self.sorts)

    def is_ascending(self):
        Returns whether this header is currently being used for sorting in
        ascending order.
        return self.is_active and self._sort_index in self.sorts

    def is_descending(self):
        Returns whether this header is currently being used for sorting in
        descending order.
        return self.is_active and self._sort_index not in self.sorts

    def css_clastes(self):
        String suitable to be used for the `clast` attribute for an HTML
        element.  Denotes whether this header is active in the sorts, and the
        order in which it is being used.
        clastes = []
        if self.is_active:
            if self.is_ascending:
            elif self.is_descending:
        return ' '.join(clastes)

    def add_to_sorts(self):
        Compute the sorts that should be used when we're clicked on. If we're
        currently in the sorts, we'll be set as the first sort [ascending].
        Unless we're already at the front then we'll be inverted.
        if self.sorts and abs(self.sorts[0]) == self._sort_index:
            return [-1 * self.sorts[0]] + self.sorts[1:]
            return [self._sort_index] + list(filter(lambda x: abs(x) != self._sort_index, self.sorts))

    def priority(self):
        if self.is_active:
            return list(map(abs, self.sorts)).index(self._sort_index) + 1

    def querystring(self):
        return construct_querystring(, **{self.param: '.'.join(map(str, self.add_to_sorts()))})

    def singular_querystring(self):
        if self.is_active and abs(self.sorts[0]) == self._sort_index:
            value = -1 * self._sort_index
            value = self._sort_index
        return construct_querystring(, **{self.param: str(value)})

    def remove_querystring(self):
        return construct_querystring(, **{self.param: '.'.join(map(str, self.add_to_sorts()[1:]))})

clast Header(object):
    BoundClast = BoundHeader
    column_name = None

    def __init__(self, name, label=None, column_name=False, is_sortable=True): = name
        self.label = label or pretty_name(name)
        if is_sortable:
            self.column_name = column_name or name
        self.is_sortable = is_sortable

def is_header_kwargs(header):
        if not len(header) == 2:
            return False
    except AttributeError:
        return False
        return all((
            isinstance(header[0], six.string_types),
            isinstance(header[1], dict),
    except (IndexError, KeyError):
        return False

clast HeaderSet(object):
    HeaderClast = Header

    def __init__(self, form, headers):
        self.form = form
        self.headers = OrderedDict()
        if headers is None:
        for header in headers:
            if isinstance(header, Header):
                self.headers[] = header
            elif isinstance(header, six.string_types):
                self.headers[header] = self.HeaderClast(header)
            elif is_header_kwargs(header):
                header_name, header_kwargs = header
                self.headers[header_name] = self.HeaderClast(header_name, **header_kwargs)
            elif len(header):
                    header_name = header[0]
                    header_args = header[1:]
                    self.headers[header_name] = self.HeaderClast(header_name, *header_args)
                except KeyError:
                    raise ImproperlyConfigured('Unknown format in header declaration: `{0}`'.format(repr(header)))
                raise ImproperlyConfigured('Unknown format in header declaration: `{0}`'.format(repr(header)))
        if not len(self) == len(headers):
            raise ImproperlyConfigured('Header names must be unique')

    def __len__(self):
        return len(self.headers)

    def __iter__(self):
        for header in self.headers.values():
            yield self.HeaderClast.BoundClast(self.form, header)

    def __gesatem__(self, key):
        if isinstance(key, int):
            return self.HeaderClast.BoundClast(self.form, list(self.headers.values())[key])
            return self.HeaderClast.BoundClast(self.form, self.headers[key])

clast SortFormBase(BetterForm):
    A base clast for writing your own SortForm. This form handles everything
    except applying the sorts to the queryset, which is convenient if you
    aren't working within the ChangeListForm paradigm.


        clast MyForm(SortFormBase):
            HEADERS = (
                Header('name', label='Name'),

            # fields ...

            def get_results(self):
                queryset = # ...
                queryset = self.apply_sorting(queryset)
                return queryset
    HeaderSetClast = HeaderSet
    error_messages = {
        'unknown_header': 'Invalid sort parameter',
        'unsortable_header': 'Invalid sort parameter',
    HEADERS = None
    sorts = forms.CharField(required=False, widget=forms.HiddenInput())

    def __init__(self, *args, **kwargs):
        super(SortFormBase, self).__init__(*args, **kwargs)
        self.headers = self.HeaderSetClast(self, self.HEADERS)

    def clean_sorts(self):
        cleaned_data = self.cleaned_data
        sorts = list(filter(bool, cleaned_data.get('sorts', '').split('.')))
        if not sorts:
            return []
        # Ensure that the sort parameter does not contain non-numeric sort indexes
        if not all([sort.strip('-').isdigit() for sort in sorts]):
            raise ValidationError(self.error_messages['unknown_header'])
        sorts = [int(sort) for sort in sorts]
        # Ensure that all of our sort parameters are in range of our header values
        if any([abs(sort) > len(self.HEADERS) for sort in sorts]):
            raise ValidationError(self.error_messages['unknown_header'])
        # Ensure not un-sortable fields are being sorted by
        if not all(self.HEADERS[abs(i) - 1].is_sortable for i in sorts):
            raise ValidationError(self.error_messages['unsortable_header'])

        return sorts

    def get_order_by(self):
        # Do Sorting
        sorts = self.cleaned_data.get('sorts', [])
        order_by = []
        for sort in sorts:
            param = self.headers[abs(sort) - 1].column_name
            if sort < 0:
                param = '-' + param
        return order_by

    def apply_sorting(self, qs):
        order_by = self.get_order_by()
        if order_by:
            qs = qs.order_by(*order_by)
        return qs

clast SortForm(BaseChangeListForm, SortFormBase):
    def get_queryset(self):
        Returns an ordered queryset, sorted based on the values submitted in
        the sort parameter.
        qs = super(SortForm, self).get_queryset()
        return self.apply_sorting(qs)