##############################################################################
#
# Copyright (c) 2007 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################

from datetime import datetime
from datetime import timedelta

import pytz
import pytz.reference
from pytz.tzinfo import StaticTzInfo
from pytz.tzinfo import memorized_timedelta

from .interfaces import DateTimeError


EPOCH = datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc)

_numeric_timezone_data = {
    'GMT':      ('GMT',      0, 1, [], '', [(0, 0, 0)],      'GMT\000'),
    'GMT+0':    ('GMT+0',    0, 1, [], '', [(0, 0, 0)],      'GMT+0000\000'),
    'GMT+1':    ('GMT+1',    0, 1, [], '', [(3600, 0, 0)],   'GMT+0100\000'),
    'GMT+2':    ('GMT+2',    0, 1, [], '', [(7200, 0, 0)],   'GMT+0200\000'),
    'GMT+3':    ('GMT+3',    0, 1, [], '', [(10800, 0, 0)],  'GMT+0300\000'),
    'GMT+4':    ('GMT+4',    0, 1, [], '', [(14400, 0, 0)],  'GMT+0400\000'),
    'GMT+5':    ('GMT+5',    0, 1, [], '', [(18000, 0, 0)],  'GMT+0500\000'),
    'GMT+6':    ('GMT+6',    0, 1, [], '', [(21600, 0, 0)],  'GMT+0600\000'),
    'GMT+7':    ('GMT+7',    0, 1, [], '', [(25200, 0, 0)],  'GMT+0700\000'),
    'GMT+8':    ('GMT+8',    0, 1, [], '', [(28800, 0, 0)],  'GMT+0800\000'),
    'GMT+9':    ('GMT+9',    0, 1, [], '', [(32400, 0, 0)],  'GMT+0900\000'),
    'GMT+10':   ('GMT+10',   0, 1, [], '', [(36000, 0, 0)],  'GMT+1000\000'),
    'GMT+11':   ('GMT+11',   0, 1, [], '', [(39600, 0, 0)],  'GMT+1100\000'),
    'GMT+12':   ('GMT+12',   0, 1, [], '', [(43200, 0, 0)],  'GMT+1200\000'),
    'GMT+13':   ('GMT+13',   0, 1, [], '', [(46800, 0, 0)],  'GMT+1300\000'),

    'GMT-1':    ('GMT-1',    0, 1, [], '', [(-3600, 0, 0)],  'GMT-0100\000'),
    'GMT-2':    ('GMT-2',    0, 1, [], '', [(-7200, 0, 0)],  'GMT-0200\000'),
    'GMT-3':    ('GMT-3',    0, 1, [], '', [(-10800, 0, 0)], 'GMT-0300\000'),
    'GMT-4':    ('GMT-4',    0, 1, [], '', [(-14400, 0, 0)], 'GMT-0400\000'),
    'GMT-5':    ('GMT-5',    0, 1, [], '', [(-18000, 0, 0)], 'GMT-0500\000'),
    'GMT-6':    ('GMT-6',    0, 1, [], '', [(-21600, 0, 0)], 'GMT-0600\000'),
    'GMT-7':    ('GMT-7',    0, 1, [], '', [(-25200, 0, 0)], 'GMT-0700\000'),
    'GMT-8':    ('GMT-8',    0, 1, [], '', [(-28800, 0, 0)], 'GMT-0800\000'),
    'GMT-9':    ('GMT-9',    0, 1, [], '', [(-32400, 0, 0)], 'GMT-0900\000'),
    'GMT-10':   ('GMT-10',   0, 1, [], '', [(-36000, 0, 0)], 'GMT-1000\000'),
    'GMT-11':   ('GMT-11',   0, 1, [], '', [(-39600, 0, 0)], 'GMT-1100\000'),
    'GMT-12':   ('GMT-12',   0, 1, [], '', [(-43200, 0, 0)], 'GMT-1200\000'),

    'GMT+0130': ('GMT+0130', 0, 1, [], '', [(5400,  0, 0)],  'GMT+0130\000'),
    'GMT+0230': ('GMT+0230', 0, 1, [], '', [(9000,  0, 0)],  'GMT+0230\000'),
    'GMT+0330': ('GMT+0330', 0, 1, [], '', [(12600, 0, 0)],  'GMT+0330\000'),
    'GMT+0430': ('GMT+0430', 0, 1, [], '', [(16200, 0, 0)],  'GMT+0430\000'),
    'GMT+0530': ('GMT+0530', 0, 1, [], '', [(19800, 0, 0)],  'GMT+0530\000'),
    'GMT+0630': ('GMT+0630', 0, 1, [], '', [(23400, 0, 0)],  'GMT+0630\000'),
    'GMT+0730': ('GMT+0730', 0, 1, [], '', [(27000, 0, 0)],  'GMT+0730\000'),
    'GMT+0830': ('GMT+0830', 0, 1, [], '', [(30600, 0, 0)],  'GMT+0830\000'),
    'GMT+0930': ('GMT+0930', 0, 1, [], '', [(34200, 0, 0)],  'GMT+0930\000'),
    'GMT+1030': ('GMT+1030', 0, 1, [], '', [(37800, 0, 0)],  'GMT+1030\000'),
    'GMT+1130': ('GMT+1130', 0, 1, [], '', [(41400, 0, 0)],  'GMT+1130\000'),
    'GMT+1230': ('GMT+1230', 0, 1, [], '', [(45000, 0, 0)],  'GMT+1230\000'),

    'GMT-0130': ('GMT-0130', 0, 1, [], '', [(-5400,  0, 0)], 'GMT-0130\000'),
    'GMT-0230': ('GMT-0230', 0, 1, [], '', [(-9000,  0, 0)], 'GMT-0230\000'),
    'GMT-0330': ('GMT-0330', 0, 1, [], '', [(-12600, 0, 0)], 'GMT-0330\000'),
    'GMT-0430': ('GMT-0430', 0, 1, [], '', [(-16200, 0, 0)], 'GMT-0430\000'),
    'GMT-0530': ('GMT-0530', 0, 1, [], '', [(-19800, 0, 0)], 'GMT-0530\000'),
    'GMT-0630': ('GMT-0630', 0, 1, [], '', [(-23400, 0, 0)], 'GMT-0630\000'),
    'GMT-0730': ('GMT-0730', 0, 1, [], '', [(-27000, 0, 0)], 'GMT-0730\000'),
    'GMT-0830': ('GMT-0830', 0, 1, [], '', [(-30600, 0, 0)], 'GMT-0830\000'),
    'GMT-0930': ('GMT-0930', 0, 1, [], '', [(-34200, 0, 0)], 'GMT-0930\000'),
    'GMT-1030': ('GMT-1030', 0, 1, [], '', [(-37800, 0, 0)], 'GMT-1030\000'),
    'GMT-1130': ('GMT-1130', 0, 1, [], '', [(-41400, 0, 0)], 'GMT-1130\000'),
    'GMT-1230': ('GMT-1230', 0, 1, [], '', [(-45000, 0, 0)], 'GMT-1230\000'),
}

# These are the timezones not in pytz.common_timezones
_old_zlst = [
    'AST', 'AT', 'BST', 'BT', 'CCT',
    'CET', 'CST', 'Cuba', 'EADT', 'EAST',
    'EEST', 'EET', 'EST', 'Egypt', 'FST',
    'FWT', 'GB-Eire', 'GMT+0100', 'GMT+0130', 'GMT+0200',
    'GMT+0230', 'GMT+0300', 'GMT+0330', 'GMT+0400', 'GMT+0430',
    'GMT+0500', 'GMT+0530', 'GMT+0600', 'GMT+0630', 'GMT+0700',
    'GMT+0730', 'GMT+0800', 'GMT+0830', 'GMT+0900', 'GMT+0930',
    'GMT+1', 'GMT+1000', 'GMT+1030', 'GMT+1100', 'GMT+1130',
    'GMT+1200', 'GMT+1230', 'GMT+1300', 'GMT-0100', 'GMT-0130',
    'GMT-0200', 'GMT-0300', 'GMT-0400', 'GMT-0500', 'GMT-0600',
    'GMT-0630', 'GMT-0700', 'GMT-0730', 'GMT-0800', 'GMT-0830',
    'GMT-0900', 'GMT-0930', 'GMT-1000', 'GMT-1030', 'GMT-1100',
    'GMT-1130', 'GMT-1200', 'GMT-1230', 'GST', 'Greenwich',
    'Hongkong', 'IDLE', 'IDLW', 'Iceland', 'Iran',
    'Israel', 'JST', 'Jamaica', 'Japan', 'MEST',
    'MET', 'MEWT', 'MST', 'NT', 'NZDT',
    'NZST', 'NZT', 'PST', 'Poland', 'SST',
    'SWT', 'Singapore', 'Turkey', 'UCT', 'UT',
    'Universal', 'WADT', 'WAST', 'WAT', 'WET',
    'ZP4', 'ZP5', 'ZP6',
]

_old_zmap = {
    'aest': 'GMT+10', 'aedt': 'GMT+11',
    'aus eastern standard time': 'GMT+10',
    'sydney standard time': 'GMT+10',
    'tasmania standard time': 'GMT+10',
    'e. australia standard time': 'GMT+10',
    'aus central standard time': 'GMT+0930',
    'cen. australia standard time': 'GMT+0930',
    'w. australia standard time': 'GMT+8',

    'central europe standard time': 'GMT+1',
    'eastern standard time': 'US/Eastern',
    'us eastern standard time': 'US/Eastern',
    'central standard time': 'US/Central',
    'mountain standard time': 'US/Mountain',
    'pacific standard time': 'US/Pacific',
    'mst': 'US/Mountain', 'pst': 'US/Pacific',
    'cst': 'US/Central', 'est': 'US/Eastern',

    'gmt+0000': 'GMT+0', 'gmt+0': 'GMT+0',

    'gmt+0100': 'GMT+1', 'gmt+0200': 'GMT+2', 'gmt+0300': 'GMT+3',
    'gmt+0400': 'GMT+4', 'gmt+0500': 'GMT+5', 'gmt+0600': 'GMT+6',
    'gmt+0700': 'GMT+7', 'gmt+0800': 'GMT+8', 'gmt+0900': 'GMT+9',
    'gmt+1000': 'GMT+10', 'gmt+1100': 'GMT+11', 'gmt+1200': 'GMT+12',
    'gmt+1300': 'GMT+13',
    'gmt-0100': 'GMT-1', 'gmt-0200': 'GMT-2', 'gmt-0300': 'GMT-3',
    'gmt-0400': 'GMT-4', 'gmt-0500': 'GMT-5', 'gmt-0600': 'GMT-6',
    'gmt-0700': 'GMT-7', 'gmt-0800': 'GMT-8', 'gmt-0900': 'GMT-9',
    'gmt-1000': 'GMT-10', 'gmt-1100': 'GMT-11', 'gmt-1200': 'GMT-12',

    'gmt+1': 'GMT+1', 'gmt+2': 'GMT+2', 'gmt+3': 'GMT+3',
    'gmt+4': 'GMT+4', 'gmt+5': 'GMT+5', 'gmt+6': 'GMT+6',
    'gmt+7': 'GMT+7', 'gmt+8': 'GMT+8', 'gmt+9': 'GMT+9',
    'gmt+10': 'GMT+10', 'gmt+11': 'GMT+11', 'gmt+12': 'GMT+12',
    'gmt+13': 'GMT+13',
    'gmt-1': 'GMT-1', 'gmt-2': 'GMT-2', 'gmt-3': 'GMT-3',
    'gmt-4': 'GMT-4', 'gmt-5': 'GMT-5', 'gmt-6': 'GMT-6',
    'gmt-7': 'GMT-7', 'gmt-8': 'GMT-8', 'gmt-9': 'GMT-9',
    'gmt-10': 'GMT-10', 'gmt-11': 'GMT-11', 'gmt-12': 'GMT-12',

    'gmt+130': 'GMT+0130', 'gmt+0130': 'GMT+0130',
    'gmt+230': 'GMT+0230', 'gmt+0230': 'GMT+0230',
    'gmt+330': 'GMT+0330', 'gmt+0330': 'GMT+0330',
    'gmt+430': 'GMT+0430', 'gmt+0430': 'GMT+0430',
    'gmt+530': 'GMT+0530', 'gmt+0530': 'GMT+0530',
    'gmt+630': 'GMT+0630', 'gmt+0630': 'GMT+0630',
    'gmt+730': 'GMT+0730', 'gmt+0730': 'GMT+0730',
    'gmt+830': 'GMT+0830', 'gmt+0830': 'GMT+0830',
    'gmt+930': 'GMT+0930', 'gmt+0930': 'GMT+0930',
    'gmt+1030': 'GMT+1030',
    'gmt+1130': 'GMT+1130',
    'gmt+1230': 'GMT+1230',

    'gmt-130': 'GMT-0130', 'gmt-0130': 'GMT-0130',
    'gmt-230': 'GMT-0230', 'gmt-0230': 'GMT-0230',
    'gmt-330': 'GMT-0330', 'gmt-0330': 'GMT-0330',
    'gmt-430': 'GMT-0430', 'gmt-0430': 'GMT-0430',
    'gmt-530': 'GMT-0530', 'gmt-0530': 'GMT-0530',
    'gmt-630': 'GMT-0630', 'gmt-0630': 'GMT-0630',
    'gmt-730': 'GMT-0730', 'gmt-0730': 'GMT-0730',
    'gmt-830': 'GMT-0830', 'gmt-0830': 'GMT-0830',
    'gmt-930': 'GMT-0930', 'gmt-0930': 'GMT-0930',
    'gmt-1030': 'GMT-1030',
    'gmt-1130': 'GMT-1130',
    'gmt-1230': 'GMT-1230',

    'ut': 'Universal',
    'bst': 'GMT+1', 'mest': 'GMT+2', 'sst': 'GMT+2',
    'fst': 'GMT+2', 'wadt': 'GMT+8', 'eadt': 'GMT+11', 'nzdt': 'GMT+13',
    'wet': 'GMT', 'wat': 'GMT+1', 'at': 'GMT-2', 'ast': 'GMT-4',
    'nt': 'GMT-11', 'idlw': 'GMT-12', 'cet': 'GMT+1', 'cest': 'GMT+2',
    'met': 'GMT+1',
    'mewt': 'GMT+1', 'swt': 'GMT+1', 'fwt': 'GMT+1', 'eet': 'GMT+2',
    'eest': 'GMT+3',
    'bt': 'GMT+3', 'zp4': 'GMT+4', 'zp5': 'GMT+5', 'zp6': 'GMT+6',
    'wast': 'GMT+7', 'cct': 'GMT+8', 'jst': 'GMT+9', 'east': 'GMT+10',
    'gst': 'GMT+10', 'nzt': 'GMT+12', 'nzst': 'GMT+12', 'idle': 'GMT+12',
    'ret': 'GMT+4', 'ist': 'GMT+0530', 'edt': 'GMT-4',

}


# some timezone definitions of the "-0400" are not working
# when upgrading
for hour in range(0, 13):
    hour = hour
    fhour = str(hour)
    if len(fhour) == 1:
        fhour = '0' + fhour
    _old_zmap['-%s00' % fhour] = 'GMT-%i' % hour
    _old_zmap['+%s00' % fhour] = 'GMT+%i' % hour


def _static_timezone_factory(data):
    zone = data[0]
    cls = type(zone, (StaticTzInfo,), dict(
        zone=zone,
        _utcoffset=memorized_timedelta(data[5][0][0]),
        _tzname=data[6][:-1]))  # strip the trailing null
    return cls()


_numeric_timezones = dict((key, _static_timezone_factory(data))
                          for key, data in _numeric_timezone_data.items())


class Timezone:
    """
    Timezone information returned by PytzCache.__getitem__
    Adapts datetime.tzinfo object to DateTime._timezone interface
    """

    def __init__(self, tzinfo):
        self.tzinfo = tzinfo

    def info(self, t=None):
        if t is None:
            dt = datetime.utcnow().replace(tzinfo=pytz.utc)
        else:
            # can't use utcfromtimestamp past 2038
            dt = EPOCH + timedelta(0, t)

        # need to normalize tzinfo for the datetime to deal with
        # daylight savings time.
        normalized_dt = self.tzinfo.normalize(dt.astimezone(self.tzinfo))
        normalized_tzinfo = normalized_dt.tzinfo

        offset = normalized_tzinfo.utcoffset(normalized_dt)
        secs = offset.days * 24 * 60 * 60 + offset.seconds
        dst = normalized_tzinfo.dst(normalized_dt)
        if dst == timedelta(0):
            is_dst = 0
        else:
            is_dst = 1
        return secs, is_dst, normalized_tzinfo.tzname(normalized_dt)


class PytzCache:
    """
    Reimplementation of the DateTime._cache class that uses for timezone info
    """

    _zlst = pytz.common_timezones + _old_zlst  # used by DateTime.TimeZones
    _zmap = dict((name.lower(), name) for name in pytz.all_timezones)
    _zmap.update(_old_zmap)  # These must take priority
    _zidx = _zmap.keys()

    def __getitem__(self, key):
        name = self._zmap.get(key.lower(), key)  # fallback to key
        try:
            return Timezone(pytz.timezone(name))
        except pytz.UnknownTimeZoneError:
            try:
                return Timezone(_numeric_timezones[name])
            except KeyError:
                raise DateTimeError('Unrecognized timezone: %s' % key)
