import datetime
import re

from django.conf import settings
from django.forms.extras import SelectDateWidget
from django.forms.widgets import Widget, Select
from django.utils import datetime_safe, six
from django.utils.dates import MONTHS
from django.utils.formats import get_format
from django.utils.safestring import mark_safe


RE_DATE = re.compile(
    r'(?P<year>[1-9]\d{3})-(?P<month>[1-9]\d?)-(?P<day>[1-9]\d?)'
)


def _parse_date_fmt():
    """
    Parse the date format in the settings for a year, month, and day.
    """
    fmt = get_format('DATE_FORMAT')
    escaped = False
    for char in fmt:
        if escaped:
            escaped = False
        elif char == '\\':
            escaped = True
        elif char in 'Yy':
            yield 'year'
        elif char in 'bEFMmNn':
            yield 'month'
        elif char in 'dj':
            yield 'day'


class IterableSelectDateWidget(Widget, object):
    """
    A widget for selecting dates with sub-widgets for the year/month/day.
    """
    none_value = (0, '---')
    month_field = '{0}_month'
    day_field = '{0}_day'
    year_field = '{0}_year'

    def __init__(self, attrs=None, years=None, required=True):
        """
        Initialise the widget and sub-widgets.
        """
        self.attrs = attrs or {}
        self.required = required
        if years:
            self.years = years
        else:
            this_year = datetime.date.today().year
            self.years = range(this_year, this_year+10)

        self._subwidgets = [
            self.create_individual(component)
            for component in _parse_date_fmt()
        ]

    def create_individual(self, component_name):
        """
        Create an individual widget.
        """
        id_template = getattr(self, "{0}_field".format(component_name))
        choices = []

        if not self.required:
            choices.insert(0, self.none_value)

        if component_name == "day":
            choices.extend([(i, i) for i in range(1, 32)])
        elif component_name == "month":
            choices.extend(list(six.iteritems(MONTHS)))
        elif component_name == "year":
            choices.extend([(i, i) for i in self.years])

        return SelectDateSubWidget(
            component_name, choices, id_template, self.attrs
        )

    def render(self, name, value, attrs=None):
        """
        Render the combined widget.
        """
        return mark_safe("\n".join((
            widget.render(name, value, attrs) for widget in self._subwidgets
        )))

    def subwidgets(self, name, value, attrs=None):
        """
        Return a generator for the rendered sub-widgets.
        """
        for subwidget in self._subwidgets:
            yield subwidget.render(name, value, attrs)

    def id_for_label(self, id_):
        """
        Return the first sub-widget's ID.
        """
        try:
            return '{0}_{1}'.format(id_, self._subwidgets[0].component)
        except IndexError:
            return '{0}_month'.format(id_)

    def value_from_datadict(self, data, files, name):
        """
        Parse the given data for the individual values, and combine them.
        """
        y = data.get(self.year_field.format(name))
        m = data.get(self.month_field.format(name))
        d = data.get(self.day_field.format(name))

        if y == m == d == "0":
            return None
        if y and m and d:
            if settings.USE_L10N:
                input_format = get_format('DATE_INPUT_FORMATS')[0]
                try:
                    date_value = datetime.date(int(y), int(m), int(d))
                except ValueError:
                    return '{0}-{1}-{2}'.format(y, m, d)
                else:
                    date_value = datetime_safe.new_date(date_value)
                    return date_value.strftime(input_format)
            else:
                return '{0}-{1}-{2}'.format(y, m, d)
        return data.get(name, None)

    def _has_changed(self, initial, data):
        """
        Attempt to parse the given date to determine whether its changed.
        """
        try:
            input_format = get_format('DATE_INPUT_FORMATS')[0]
            data = datetime_safe.datetime.strptime(data, input_format).date()
        except (TypeError, ValueError):
            pass
        return super(IterableSelectDateWidget, self)._has_changed(
            initial, data
        )


class SelectDateSubWidget(Select, object):
    """
    An individual component (year/month/day) for the select date widget.
    """
    def __init__(self, component, choices, id_template, attrs):
        super(SelectDateSubWidget, self).__init__(choices=choices, attrs=attrs)
        self.attrs = attrs
        self.component = component
        self.id_template = id_template

    def render(self, name, value, attrs=None):
        """
        Render the individual select widget.
        """
        value = self._parse_value(value)
        id_ = attrs['id'] if "id" in self.attrs else "id_{0}".format(name)
        local_attrs = self.build_attrs(id=self.id_template.format(id_))
        return super(SelectDateSubWidget, self).render(
            self.id_template.format(name), value, local_attrs
        )

    def _parse_value(self, value):
        """
        Get the sub-value for the given component (day/month/year).
        """
        try:
            return getattr(value, self.component)
        except AttributeError:
            if isinstance(value, six.string_types):
                if settings.USE_L10N:
                    try:
                        input_format = get_format('DATE_INPUT_FORMATS')[0]
                        v = datetime.datetime.strptime(value, input_format)
                        return getattr(v, self.component)
                    except ValueError:
                        pass
                else:
                    match = RE_DATE.match(value)
                    if match:
                        return match.groupdict()[self.component]