# -*- coding: utf-8 -*-
"""This module contains the parser/generators (or coders/encoders if you
prefer) for the classes/datatypes that are used in iCalendar:

###########################################################################
# This module defines these property value data types and property parameters

4.2 Defined property parameters are:

     ALTREP, CN, CUTYPE, DELEGATED-FROM, DELEGATED-TO, DIR, ENCODING, FMTTYPE,
     FBTYPE, LANGUAGE, MEMBER, PARTSTAT, RANGE, RELATED, RELTYPE, ROLE, RSVP,
     SENT-BY, TZID, VALUE

4.3 Defined value data types are:

    BINARY, BOOLEAN, CAL-ADDRESS, DATE, DATE-TIME, DURATION, FLOAT, INTEGER,
    PERIOD, RECUR, TEXT, TIME, URI, UTC-OFFSET

###########################################################################

iCalendar properties has values. The values are strongly typed. This module
defines these types, calling val.to_ical() on them, Will render them as defined
in rfc2445.

If you pass any of these classes a Python primitive, you will have an object
that can render itself as iCalendar formatted date.

Property Value Data Types starts with a 'v'. they all have an to_ical() and
from_ical() method. The to_ical() method generates a text string in the
iCalendar format. The from_ical() method can parse this format and return a
primitive Python datatype. So it should allways be true that:

    x == vDataType.from_ical(VDataType(x).to_ical())

These types are mainly used for parsing and file generation. But you can set
them directly.
"""
from datetime import date
from datetime import datetime
from datetime import time
from datetime import timedelta
from datetime import tzinfo

try:
    from dateutil.tz import tzutc
except ImportError:
    tzutc = None

from icalendar import compat
from icalendar.caselessdict import CaselessDict
from icalendar.parser import Parameters
from icalendar.parser import escape_char
from icalendar.parser import tzid_from_dt
from icalendar.parser import unescape_char
from icalendar.parser_tools import DEFAULT_ENCODING
from icalendar.parser_tools import SEQUENCE_TYPES
from icalendar.parser_tools import to_unicode

import base64
import binascii
import pytz
import re
import time as _time


DATE_PART = r'(\d+)D'
TIME_PART = r'T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?'
DATETIME_PART = '(?:%s)?(?:%s)?' % (DATE_PART, TIME_PART)
WEEKS_PART = r'(\d+)W'
DURATION_REGEX = re.compile(r'([-+]?)P(?:%s|%s)$'
                            % (WEEKS_PART, DATETIME_PART))
WEEKDAY_RULE = re.compile('(?P<signal>[+-]?)(?P<relative>[\d]?)'
                          '(?P<weekday>[\w]{2})$')


##################################################
##
# handy tzinfo classes you can use.
#

ZERO = t
imedelta(0)
HOUR = timedelta(hours=1)
STDOFFSET = timedelta(seconds=-_time.timezone)
if _time.daylight:
    DSTOFFSET = timedelta(seconds=-_time.altzone)
else:
    DSTOFFSET = STDOFFSET
DSTDIFF = DSTOFFSET - STDOFFSET


class FixedOffset(tzinfo):
    """Fixed offset in minutes east from UTC.
    """
    def __init__(self, offset, name):
        self.__offset = timedelta(minutes=offset)
        self.__name = name

    def utcoffset(self, dt):
        return self.__offset

    def tzname(self, dt):
        return self.__name

    def dst(self, dt):
        return ZERO


class LocalTimezone(tzinfo):
    """Timezone of the machine where the code is running.
    """
    def utcoffset(self, dt):
        if self._isdst(dt):
            return DSTOFFSET
        else:
            return STDOFFSET

    def dst(self, dt):
        if self._isdst(dt):
            return DSTDIFF
        else:
            return ZERO

    def tzname(self, dt):
        return _time.tzname[self._isdst(dt)]

    def _isdst(self, dt):
        tt = (dt.year, dt.month, dt.day,
              dt.hour, dt.minute, dt.second,
              dt.weekday(), 0, -1)
        stamp = _time.mktime(tt)
        tt = _time.localtime(stamp)
        return tt.tm_isdst > 0


class vBinary(object):
    """Binary property values are base 64 encoded.
    """

    def __init__(self, obj):
        self.obj = to_unicode(obj)
        self.params = Parameters(encoding='BASE64', value="BINARY")

    def __repr__(self):
        return "vBinary('%s')" % self.to_ical()

    def to_ical(self):
        return binascii.b2a_base64(self.obj.encode('utf-8'))[:-1]

    @staticmethod
    def from_ical(ical):
        try:
            return base64.b64decode(ical)
        except UnicodeError:
            raise ValueError('Not valid base 64 encoding.')


class vBoolean(int):
    """Returns specific string according to state.
    """
    BOOL_MAP = CaselessDict({'true': True, 'false': False})

    def __new__(cls, *args, **kwargs):
        self = super(vBoolean, cls).__new__(cls, *args, **kwargs)
        self.params = Parameters()
        return self

    def to_ical(self):
        if self:
            return b'TRUE'
        return b'FALSE'

    @classmethod
    def from_ical(cls, ical):
        try:
            return cls.BOOL_MAP[ical]
        except:
            raise ValueError("Expected 'TRUE' or 'FALSE'. Got %s" % ical)


class vCalAddress(compat.unicode_type):
    """This just returns an unquoted string.
    """
    def __new__(cls, value, encoding=DEFAULT_ENCODING):
        value = to_unicode(value, encoding=encoding)
        self = super(vCalAddress, cls).__new__(cls, value)
        self.params = Parameters()
        return self

    def __repr__(self):
        return "vCalAddress('%s')" % self.to_ical()

    def to_ical(self):
        return self.encode(DEFAULT_ENCODING)

    @classmethod
    def from_ical(cls, ical):
        return cls(ical)


class vFloat(float):
    """Just a float.
    """
    def __new__(cls, *args, **kwargs):
        self = super(vFloat, cls).__new__(cls, *args, **kwargs)
        self.params = Parameters()
        return self

    def to_ical(self):
        return compat.unicode_type(self).encode('utf-8')

    @classmethod
    def from_ical(cls, ical):
        try:
            return cls(ical)
        except:
            raise ValueError('Expected float value, got: %s' % ical)


class vInt(int):
    """Just an int.
    """
    def __new__(cls, *args, **kwargs):
        self = super(vInt, cls).__new__(cls, *args, **kwargs)
        self.params = Parameters()
        return self

    def to_ical(self):
        return compat.unicode_type(self).encode('utf-8')

    @classmethod
    def from_ical(cls, ical):
        try:
            return cls(ical)
        except:
            raise ValueError('Expected int, got: %s' % ical)


class vDDDLists(object):
    """A list of vDDDTypes values.
    """
    def __init__(self, dt_list):
        if not hasattr(dt_list, '__iter__'):
            dt_list = [dt_list]
        vDDD = []
        tzid = None
        for dt in dt_list:
            dt = vDDDTypes(dt)
            vDDD.append(dt)
            if 'TZID' in dt.params:
                tzid = dt.params['TZID']

        if tzid:
            # NOTE: no support for multiple timezones here!
            self.params = Parameters({'TZID': tzid})
        self.dts = vDDD

    def to_ical(self):
        dts_ical = (dt.to_ical() for dt in self.dts)
        return b",".join(dts_ical)

    @staticmethod
    def from_ical(ical, timezone=None):
        out = []
        ical_dates = ical.split(",")
        for ical_dt in ical_dates:
            out.append(vDDDTypes.from_ical(ical_dt, timezone=timezone))
        return out


class vDDDTypes(object):
    """A combined Datetime, Date or Duration parser/generator. Their format
    cannot be confused, and often values can be of either types.
    So this is practical.
    """
    def __init__(self, dt):
        if not isinstance(dt, (datetime, date, timedelta, time)):
            raise ValueError('You must use datetime, date, timedelta or time')
        if isinstance(dt, datetime):
            self.params = Parameters(dict(value='DATE-TIME'))
        elif isinstance(dt, date):
            self.params = Parameters(dict(value='DATE'))
        elif isinstance(dt, time):
            self.params = Parameters(dict(value='TIME'))

        if (isinstance(dt, datetime) or isinstance(dt, time))\
                and getattr(dt, 'tzinfo', False):
   