from django.utils import simplejson
from django import forms
from django.forms.util import flatatt
from django.utils import safestring
from django.template.loader import render_to_string

__all__ = ['AutocompleteWidget']


class AutocompleteWidget(forms.SelectMultiple):
    """
    Widget suitable for ModelChoiceField and ModelMultipleChoiceField.

    Example usage::

        from django import forms

        import autocomplete_light

        from models import Author

        class AuthorsForm(forms.Form):
            lead_author = forms.ModelChoiceField(Author.objects.all(), widget=
                autocomplete_light.AutocompleteWidget(
                    'AuthorChannel', max_items=1))

            contributors = forms.ModelMultipleChoiceField(Author.objects.all(),
                widget=autocomplete_light.AutocompleteWidget('AuthorChannel'))
    """

    payload = {}

    def __init__(self, channel_name, *args, **kwargs):
        """
        AutocompleteWidget constructor decorates SelectMultiple constructor

        Arguments:
        channel_name -- the name of the channel that this widget should use.

        Keyword arguments are passed to javascript via data attributes of the
        autocomplete wrapper element:

        max_items
            The number of items that this autocomplete allows. If set to 0,
            then it allows any number of selected items like a multiple select,
            well suited for ManyToMany relations or any kind of
            ModelMultipleChoiceField. If set to 3 for example, then it will
            only allow 3 selected items. It should be set to 1 if the widget is
            for a ModelChoiceField or ForeignKey, in that case it would be like
            a normal select. Default is 0.

        min_characters
            The minimum number of characters before the autocomplete box shows
            up. If set to 2 for example, then the autocomplete box will show up
            when the input receives the second character, for example 'ae'. If
            set to 0, then the autocomplete box will show up as soon as the
            input is focused, even if it's empty, behaving like a normal
            select. Default is 0.

        bootstrap
            The name of the bootstrap kind. By default, deck.js will only
            initialize decks for wrappers that have data-bootstrap="normal". If
            you want to implement your own bootstrapping logic in javascript,
            then you set bootstrap to anything that is not "normal". By
            default, its value is copied from channel.bootstrap.

        placeholder
            The initial value of the autocomplete input field. It can be
            something like 'type your search here'. By default, it is copied
            from channel.placeholder.

        payload
            A dict of data that will be exported to JSON, and parsed into the
            Deck instance in javascript. It allows to pass variables from
            Python to Javascript.
        """
        self.channel_name = channel_name

        from autocomplete_light import registry
        self.channel = registry[channel_name]()

        self.payload.update(kwargs.pop('payload', {}))
        self.max_items = kwargs.pop('max_items', 0)
        self.min_characters = kwargs.pop('min_characters', 0)
        self.bootstrap = kwargs.pop('bootstrap', self.channel.bootstrap)
        self.placeholder = kwargs.pop('placeholder', self.channel.placeholder)

        super(AutocompleteWidget, self).__init__(*args, **kwargs)

    def render(self, name, value, attrs=None):
        """
        Render the autocomplete widget.

        It will try two templates, like django admin:
        - autocomplete_light/channelname/widget.html
        - autocomplete_light/widget.html

        Note that it will not pass 'value' to the template, because 'value'
        might be a list of model ids in the case of ModelMultipleChoiceField,
        or a model id in the case of ModelChoiceField. To keep things simple,
        it will just pass a list, 'values', to the template context.
        """
        final_attrs = self.build_attrs(attrs)
        self.html_id = final_attrs.pop('id', name)

        if value and not isinstance(value, (list, tuple)):
            values = [value]
        else:
            values = value

        if values and not self.channel.are_valid(values):
            raise forms.ValidationError('%s cannot validate %s' % (
                self.channel_name, values))

        self.payload.update(self.as_dict())
        self.payload['channel'] = self.channel.as_dict()

        channel_name = self.channel_name.lower()
        return safestring.mark_safe(render_to_string([
                'autocomplete_light/%s/widget.html' % channel_name,
                'autocomplete_light/widget.html',
            ], {
                'widget': self,
                'name': name,
                'values': values,
                'channel': self.channel,
                'results': self.channel.get_results(values or []),
                'json_payload': safestring.mark_safe(simplejson.dumps(
                    self.payload)),
                'extra_attrs': safestring.mark_safe(flatatt(final_attrs)),
            }
        ))

    def as_dict(self):
        return {
            'max_items': self.max_items,
            'min_characters': self.min_characters,
            'bootstrap': self.bootstrap,
            # cast to unicode as it might be a gettext proxy
            'placeholder': unicode(self.placeholder),
        }

    # we might want to split up in two widgets for that ... is it necessary ?
    # apparently not yet, but maybe at next django release
    def value_from_datadict(self, data, files, name):
        """Route to Select if max_items is 1, else route to SelectMultiple"""
        if self.max_items == 1:
            return forms.Select.value_from_datadict(self, data, files, name)
        else:
            return forms.SelectMultiple.value_from_datadict(self, data, files,
                name)

    def _has_changed(self, initial, data):
        """Route to Select if max_items is 1, else route to SelectMultiple"""
        if self.max_items == 1:
            return forms.Select._has_changed(self, initial, data)
        else:
            return forms.SelectMultiple._has_changed(self, initial, data)
