# -*- coding: utf-8 -*-
#########################################################################
#
# Copyright (C) 2016 OSGeo
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#########################################################################
from fields import MultiThesauriField
from widgets import MultiThesauriWidget

from autocomplete_light.widgets import ChoiceWidget
from autocomplete_light.contrib.taggit_field import TaggitField, TaggitWidget

from django import forms
from django.conf import settings
from django.core import validators
from django.forms import models
from django.forms.fields import ChoiceField
from django.forms.utils import flatatt
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _
from django.db.models import Q

from django.utils.encoding import (
    force_text,
)

from bootstrap3_datetime.widgets import DateTimePicker
from modeltranslation.forms import TranslationModelForm

from geonode.base.models import HierarchicalKeyword, TopicCategory, Region, License
from geonode.people.models import Profile
from geonode.base.enumerations import ALL_LANGUAGES
from django.contrib.auth.models import Group
from django.contrib.auth import get_user_model


def get_tree_data():
    def rectree(parent, path):
        children_list_of_tuples = list()
        c = Region.objects.filter(parent=parent)
        for child in c:
            children_list_of_tuples.append(
                tuple((path + parent.name, tuple((child.id, child.name))))
            )
            childrens = rectree(child, parent.name + '/')
            if childrens:
                children_list_of_tuples.extend(childrens)

        return children_list_of_tuples

    data = list()
    try:
        t = Region.objects.filter(Q(level=0) | Q(parent=None))
        for toplevel in t:
            data.append(
                tuple((toplevel.id, toplevel.name))
            )
            childrens = rectree(toplevel, '')
            if childrens:
                data.append(
                    tuple((toplevel.name, childrens))
                )
    except BaseException:
        pass

    return tuple(data)


class AdvancedModelChoiceIterator(models.ModelChoiceIterator):
    def choice(self, obj):
        return (
            self.field.prepare_value(obj),
            self.field.label_from_instance(obj),
            obj)


class CategoryChoiceField(forms.ModelChoiceField):
    def _get_choices(self):
        if hasattr(self, '_choices'):
            return self._choices

        return AdvancedModelChoiceIterator(self)

    choices = property(_get_choices, ChoiceField._set_choices)

    def label_from_instance(self, obj):
        return '<i class="fa ' + obj.fa_class + ' fa-2x unchecked"></i>' \
               '<i class="fa ' + obj.fa_class + ' fa-2x checked"></i>' \
               '<span class="has-popover" data-container="body" data-toggle="popover" data-placement="top" ' \
               'data-content="' + obj.description + '" trigger="hover">' \
               '<br/><strong>' + obj.gn_description + '</strong></span>'


class TreeWidget(TaggitWidget):
    input_type = 'text'

    def render(self, name, value, attrs=None):
        if isinstance(value, basestring):
            vals = value
        elif value:
            vals = ','.join([i.tag.name for i in value])
        else:
            vals = ""
        output = ["""<div class="keywords-container"><span class="input-group">
                <input class="form-control"
                       id="id_resource-keywords"
                       name="resource-keywords"
                       value="%s"><br/>""" % (vals)]
        output.append(
            '<div id="treeview" class="" style="display: none"></div>')
        output.append(
            '<span class="input-group-addon" id="treeview-toggle"><i class="fa fa-folder"></i></span>')
        output.append('</span></div>')

        return mark_safe(u'\n'.join(output))


class RegionsMultipleChoiceField(forms.MultipleChoiceField):

    def validate(self, value):
        """
        Validates that the input is a list or tuple.
        """
        if self.required and not value:
            raise forms.ValidationError(
                self.error_messages['required'], code='required')


class RegionsSelect(forms.Select):
    allow_multiple_selected = True

    def render(self, name, value, attrs=None):
        if value is None:
            value = []
        final_attrs = self.build_attrs(attrs)
        final_attrs["name"] = name
        output = [
            format_html(
                '<select multiple="multiple"{}>',
                flatatt(final_attrs))]
        options = self.render_options(value)
        if options:
            output.append(options)
        output.append('</select>')
        return mark_safe('\n'.join(output))

    def value_from_datadict(self, data, files, name):
        try:
            getter = data.getlist
        except AttributeError:
            getter = data.get
        return getter(name)

    def render_option_value(
            self,
            selected_choices,
            option_value,
            option_label,
            data_section=None):
        if option_value is None:
            option_value = ''
        option_value = force_text(option_value)
        if option_value in selected_choices:
            selected_html = mark_safe(' selected')
            if not self.allow_multiple_selected:
                # Only allow for a single selection.
                selected_choices.remove(option_value)
        else:
            selected_html = ''

        label = force_text(option_label)

        if data_section is None:
            data_section = ''
        else:
            data_section = force_text(data_section)
            if '/' in data_section:
                label = format_html(
                    '{} [{}]', label, data_section.rsplit(
                        '/', 1)[1])

        return format_html(
            '<option data-section="{}" value="{}"{}>{}</option>',
            data_section,
            option_value,
            selected_html,
            label)

    def render_options(self, selected_choices):

        # Normalize to strings.
        def _region_id_from_choice(choice):
            if isinstance(choice, int) or \
            (isinstance(choice, basestring) and choice.isdigit()):
                return int(choice)
            else:
                return choice.id

        selected_choices = set(force_text(_region_id_from_choice(v)) for v in selected_choices)
        output = []

        output.append(format_html('<optgroup label="{}">', 'Global'))
        for option_value, option_label in self.choices:
            if not isinstance(
                    option_label, (list, tuple)) and isinstance(
                    option_label, basestring):
                output.append(
                    self.render_option_value(
                        selected_choices,
                        option_value,
                        option_label))
        output.append('</optgroup>')

        for option_value, option_label in self.choices:
            if isinstance(
                    option_label, (list, tuple)) and not isinstance(
                    option_label, basestring):
                output.append(
                    format_html(
                        '<optgroup label="{}">',
                        force_text(option_value)))
                for option in option_label:
                    if isinstance(
                            option, (list, tuple)) and not isinstance(
                            option, basestring):
                        if isinstance(
                                option[1][0], (list, tuple)) and not isinstance(
                                option[1][0], basestring):
                            for option_child in option[1][0]:
                                output.append(
                                    self.render_option_value(
                                        selected_choices,
                                        *option_child,
                                        data_section=force_text(
                                            option[1][0][0])))
                        else:
                            output.append(
                                self.render_option_value(
                                    selected_choices,
                                    *option[1],
                                    data_section=force_text(
                                        option[0])))
                    else:
                        output.append(
                            self.render_option_value(
                                selected_choices,
                                *option,
                                data_section=force_text(option_value)))
                output.append('</optgroup>')

        return '\n'.join(output)


class CategoryForm(forms.Form):
    category_choice_field = CategoryChoiceField(
        required=False,
        label='*' + _('Category'),
        empty_label=None,
        queryset=TopicCategory.objects.filter(
            is_choice=True) .extra(
            order_by=['description']))

    def clean(self):
        cleaned_data = self.data
        ccf_data = cleaned_data.get("category_choice_field")
        category_mandatory = getattr(settings, 'TOPICCATEGORY_MANDATORY', False)
        if category_mandatory and not ccf_data:
            msg = _("Category is required.")
            self._errors = self.error_class([msg])

        # Always return the full collection of cleaned data.
        return cleaned_data


class TKeywordForm(forms.Form):
    tkeywords = MultiThesauriField(
        label=_("Keywords from Thesauri"),
        required=False,
        help_text=_("List of keywords from Thesauri"),
        widget=MultiThesauriWidget())

    def clean(self):
        cleaned_data = None
        if self.data:
            try:
                cleaned_data = [{key: self.data.getlist(key)} for key, value in self.data.items(
                ) if 'tkeywords-tkeywords' in key.lower() and 'autocomplete' not in key.lower()]
            except BaseException:
                pass

        return cleaned_data


class ResourceBaseDateTimePicker(DateTimePicker):

    def build_attrs(self, base_attrs=None, extra_attrs=None, **kwargs):
        "Helper function for building an attribute dictionary."
        if extra_attrs:
            base_attrs.update(extra_attrs)
        base_attrs.update(kwargs)
        return super(ResourceBaseDateTimePicker, self).build_attrs(base_attrs)
        # return base_attrs


class ResourceBaseForm(TranslationModelForm):
    """Base form for metadata, should be inherited by childres classes of ResourceBase"""

    owner = forms.ModelChoiceField(
        empty_label="Owner",
        label=_("Owner"),
        required=False,
        queryset=Profile.objects.exclude(
            username='AnonymousUser'),
        widget=ChoiceWidget('ProfileAutocomplete'))

    date = forms.DateTimeField(
        label=_("Date"),
        localize=True,
        input_formats=['%Y-%m-%d %H:%M %p'],
        widget=ResourceBaseDateTimePicker(options={"format": "YYYY-MM-DD HH:mm a"})
    )
    temporal_extent_start = forms.DateTimeField(
        label=_("temporal extent start"),
        required=False,
        localize=True,
        input_formats=['%Y-%m-%d %H:%M %p'],
        widget=ResourceBaseDateTimePicker(options={"format": "YYYY-MM-DD HH:mm a"})
    )
    temporal_extent_end = forms.DateTimeField(
        label=_("temporal extent end"),
        required=False,
        localize=True,
        input_formats=['%Y-%m-%d %H:%M %p'],
        widget=ResourceBaseDateTimePicker(options={"format": "YYYY-MM-DD HH:mm a"})
    )

    poc = forms.ModelChoiceField(
        empty_label=_("Person outside GeoNode (fill form)"),
        label=_("Point of Contact"),
        required=False,
        queryset=Profile.objects.exclude(
            username='AnonymousUser'),
        widget=ChoiceWidget('ProfileAutocomplete'))

    metadata_author = forms.ModelChoiceField(
        empty_label=_("Person outside GeoNode (fill form)"),
        label=_("Metadata Author"),
        required=False,
        queryset=Profile.objects.exclude(
            username='AnonymousUser'),
        widget=ChoiceWidget('ProfileAutocomplete'))

    keywords = TaggitField(
        label=_("Free-text Keywords"),
        required=False,
        help_text=_("A space or comma-separated list of keywords. Use the widget to select from Hierarchical tree."),
        widget=TreeWidget(
            autocomplete='HierarchicalKeywordAutocomplete'))

    """
    regions = TreeNodeMultipleChoiceField(
        label=_("Regions"),
        required=False,
        queryset=Region.objects.all(),
        level_indicator=u'___')
    """
    regions = RegionsMultipleChoiceField(
        label=_("Regions"),
        required=False,
        choices=get_tree_data(),
        widget=RegionsSelect)
    regions.widget.attrs = {"size": 20}

    def __init__(self, *args, **kwargs):
        super(ResourceBaseForm, self).__init__(*args, **kwargs)
        for field in self.fields:
            help_text = self.fields[field].help_text
            self.fields[field].help_text = None
            if help_text != '':
                self.fields[field].widget.attrs.update(
                    {
                        'class': 'has-popover',
                        'data-content': help_text,
                        'data-placement': 'right',
                        'data-container': 'body',
                        'data-html': 'true'})

    def clean_keywords(self):
        import urllib
        import HTMLParser

        def unicode_escape(unistr):
            """
            Tidys up unicode entities into HTML friendly entities
            Takes a unicode string as an argument
            Returns a unicode string
            """
            import htmlentitydefs
            escaped = ""
            for char in unistr:
                if ord(char) in htmlentitydefs.codepoint2name:
                    name = htmlentitydefs.codepoint2name.get(ord(char))
                    escaped += '&%s;' % name if 'nbsp' not in name else ' '
                else:
                    escaped += char
            return escaped

        keywords = self.cleaned_data['keywords']
        _unsescaped_kwds = []
        for k in keywords:
            _k = urllib.unquote(('%s' % k)).split(",")
            if not isinstance(_k, basestring):
                for _kk in [x.strip() for x in _k]:
                    _kk = HTMLParser.HTMLParser().unescape(unicode_escape(_kk))
                    # Simulate JS Unescape
                    _kk = _kk.replace('%u', r'\u').decode('unicode-escape') if '%u' in _kk else _kk
                    _hk = HierarchicalKeyword.objects.filter(name__contains='%s' % _kk.strip())
                    if _hk and len(_hk) > 0:
                        _unsescaped_kwds.append(_hk[0])
                    else:
                        _unsescaped_kwds.append(_kk)
            else:
                _hk = HierarchicalKeyword.objects.filter(name__iexact=_k)
                if _hk and len(_hk) > 0:
                    _unsescaped_kwds.append(_hk[0])
                else:
                    _unsescaped_kwds.append(_k)
        return _unsescaped_kwds

    class Meta:
        exclude = (
            'contacts',
            'name',
            'uuid',
            'bbox_x0',
            'bbox_x1',
            'bbox_y0',
            'bbox_y1',
            'srid',
            'category',
            'csw_typename',
            'csw_schema',
            'csw_mdsource',
            'csw_type',
            'csw_wkt_geometry',
            'metadata_uploaded',
            'metadata_xml',
            'csw_anytext',
            'popular_count',
            'share_count',
            'thumbnail',
            'charset',
            'rating',
            'detail_url',
            'tkeywords',
        )


class ValuesListField(forms.Field):

    def to_python(self, value):
        if value in validators.EMPTY_VALUES:
            return []

        value = [item.strip() for item in value.split(',') if item.strip()]

        return value

    def clean(self, value):
        value = self.to_python(value)
        self.validate(value)
        self.run_validators(value)
        return value


class BatchEditForm(forms.Form):
    LANGUAGES = (('', '--------'),) + ALL_LANGUAGES
    group = forms.ModelChoiceField(
        queryset=Group.objects.all(),
        required=False)
    owner = forms.ModelChoiceField(
        queryset=get_user_model().objects.all(),
        required=False)
    category = forms.ModelChoiceField(
        queryset=TopicCategory.objects.all(),
        required=False)
    license = forms.ModelChoiceField(
        queryset=License.objects.all(),
        required=False)
    regions = forms.ModelChoiceField(
        queryset=Region.objects.all(),
        required=False)
    date = forms.DateTimeField(required=False)
    language = forms.ChoiceField(
        required=False,
        choices=LANGUAGES,
    )
    keywords = forms.CharField(required=False)
