#
# templatetags/localdt.py
#

from numbers import Number
from datetime import datetime

import pytz
from django import template
from django.template.defaultfilters import timesince
from django.utils import formats

def utcnow():
    # note: needed so we can override this during tests
    return datetime.utcnow()

class LocalBase(template.Node):
    def __init__(self, args):
        self.args = args

    @classmethod
    def usage(cls):
        return template.TemplateSyntaxError(
            cls.usage %(cls.tag_name, )
        )

    @classmethod
    def parse(cls, parser, token):
        args = token.split_contents()[1:]
        if len(args) < 1:
            raise cls.usage()
        return cls(args)

    def _parse(self):
        context = self.context
        in_args = list(self.args)

        if hasattr(context.get(in_args[0]), "localize"):
            tz = context[in_args.pop(0)]
        else:
            tz = context["localtz"]
        self.tz = tz

        # datetime
        self.datetime = context[in_args.pop(0)]

        # arg
        arg = None
        if in_args and in_args[0] != "as":
            arg = in_args.pop(0)
        self.arg = arg

        # output_var
        output_var = None
        if in_args:
            if len(in_args) != 2:
                raise self.usage()
            output_var = in_args[1]
        self.output_var = output_var

    def _localize_datetime(self, tz, value):
        if isinstance(value, Number):
            value = datetime.utcfromtimestamp(value)
        value_utc = value.replace(tzinfo=pytz.utc)
        return tz.normalize(value_utc.astimezone(tz))

    def _render(self):
        raise Exception("Override this!")

    def render(self, context):
        self.context = context
        self._parse()
        value = self._render()

        if self.output_var:
            context[self.output_var] = value
            return ""
        return value

class LocalDateTimeBase(LocalBase):
    """ Returns a localized datetime, date or time.

            {% local{dt,date,time} [tz] datetime [format] [as name] %}

        If ``tz`` is not specified the ``localtz`` context variable is used.

        Formatting is done by ``django.utils.formats``. """

    usage = "usage: {%% %s [tz] datetime [format] [as name] %%}"

    def _render(self):
        format = self.arg
        localized = self._localize_datetime(self.tz, self.datetime)
        return self.format_func(localized, format)


class LocalDateTime(LocalDateTimeBase):
    tag_name = "localdt"

    def format_func(self, value, format=None):
        format = format or "DATETIME_FORMAT"
        return formats.date_format(value, format)


class LocalDate(LocalDateTimeBase):
    tag_name = "localdate"
    format_func = staticmethod(formats.date_format)


class LocalTime(LocalDateTimeBase):
    tag_name = "localtime"
    format_func = staticmethod(formats.time_format)


class LocalTimesince(LocalBase):
    """ Returns a localized timesince.

            {% timesince [tz] datetime [target] [as name] %} """

    tag_name = "localtimesince"
    usage = "usage: {%% %s [tz] datetime [target] [as name] %%}"

    def _render(self):
        target = self.arg
        if target is None:
            target = utcnow()
        else:
            target = self.context[target]
        target = self._localize_datetime(self.tz, target)
        localized = self._localize_datetime(self.tz, self.datetime)
        return timesince(localized, target)


def register_all(register, values):
    for value in values:
        if hasattr(value, "tag_name"):
            register.tag(value.tag_name, value.parse)


register = template.Library()
register_all(register, globals().values())


#
# templatetags/tests/test_localdt.py
#

import pytz
from datetime import datetime

from django.template import Template, Context
from nose.tools import assert_equal

from .. import localdt

import new
from functools import wraps

def parameterized(input):
    """ Parameterize a test case:
        >>> class TestFoo(object):
        ...     data = [ (1, 2), (3, 4) ]
        ...     @parameterized(data)
        ...     def test_add1(self, input, expected):
        ...         assert_equal(add1(input), expected)
        >>>
        """

    if not hasattr(input, "__iter__"):
        raise ValueError("expected iterable input; got %r" %(input, ))

    def parameterized_heper(f):
        attached_instance_method = [False]
        @wraps(f)
        def parameterized_helper_method(self):
            if not attached_instance_method[0]:
                # confusingly, we need to create a named instance method and
                # attach that to the class...
                cls = self.__class__
                im_f = new.instancemethod(f, None, cls)
                setattr(cls, f.__name__, im_f)
                attached_instance_method[0] = True

            for args in input:
                if isinstance(args, basestring):
                    args = [args]
                # ... then pull that named instance method off, turning it into
                # a bound method ...
                args = [getattr(self, f.__name__)] + list(args)
                # ... then yield that as a tuple. If those steps aren't
                # followed precicely, Nose gets upset and doesn't run the test
                # or doesn't run setup methods.
                yield tuple(args)
        f.__name__ = "_helper_for_%s" %(f.__name__, )
        return parameterized_helper_method
    return parameterized_heper


class TestLocalDateTime(object):
    test_datetime = datetime.utcfromtimestamp(1313945312.076319)
    second_datetime = datetime.utcfromtimestamp(1313950000.0)
    test_tz = pytz.timezone("Japan")

    def _render(self, template, context):
        template = Template(template)
        context = Context(context)
        return template.render(context)

    @parameterized([
        ["localdt", "Aug. 22, 2011, 1:48 a.m."],
        ["localdate", "Aug. 22, 2011"],
        ["localtime", "1:48 a.m."],
    ])
    def test_conversion(self, tag, expected):
        template = "{%% load localdt %%}{%% %s some_dt %%}" %(tag, )
        context = {
            "localtz": self.test_tz,
            "some_dt": self.test_datetime,
        }
        result = self._render(template, context)
        assert_equal(result, expected)

    @parameterized([
        ["{% localtime other_tz some_dt %}", "6:48 p.m."],
        ["{% localtime other_tz some_dt as foo %}a{{ foo }}b", "a6:48 p.m.b"],
        ["{% localtime some_dt as foo %}a{{ foo }}b", "a1:48 a.m.b"],
        ["{% localtimesince some_dt %}", "1 hour, 18 minutes"],
        ["{% localtimesince other_tz some_dt %}", "1 hour, 18 minutes"],
        ["{% localtimesince other_tz some_dt other_dt %}", "4 hours, 4 minutes"],
        ["{% localtimesince some_dt as foo %}a{{ foo }}b", "a1 hour, 18 minutesb"],
        ["{% localtimesince other_tz some_dt as foo %}a{{ foo }}b", "a1 hour, 18 minutesb"],
        ["{% localtimesince other_tz some_dt other_dt as foo %}a{{ foo }}b", "a4 hours, 4 minutesb"],
    ])
    def test_parsing(self, input, expected):
        oldutcnow = localdt.utcnow
        localdt.utcnow = lambda: self.second_datetime
        try:
            template = "{% load localdt %}" + input
            context = {
                "localtz": self.test_tz,
                "other_tz": pytz.timezone("Africa/Johannesburg"),
                "some_dt": self.test_datetime,
                "other_dt": datetime.utcfromtimestamp(1313960000.0)
            }
            result = self._render(template, context)
            assert_equal(result, expected)
        finally:
            localdt.utcnow = oldutcnow