# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8

"""Des fonction pour gérer les heures.
"""

__all__ = ('pytime',
           'NUM_TIMEF', 'FR_TIMEF', 'HM_TIMEF',
           'Time', 'istime', 'isanytime',
           'parse_time', 'ensure_time',
           )

import re
from time import time as timedbl
from time import localtime
from datetime import time as pytime, datetime as pydatetime, timedelta

from .base import isstr, isnum
from .uio import _s, _u

NUM_TIMEF = '%H%M%S'
FR_TIMEF = '%H:%M:%S'
HM_TIMEF = '%H:%M'

def _fix_second(minute, second):
    while second > 59:
        second -= 60
        minute += 1
    while second < 0:
        second += 60
        minute -= 1
    return minute, second
def _fix_minute(hour, minute):
    while minute > 59:
        minute -= 60
        hour += 1
    while minute < 0:
        minute += 60
        hour -= 1
    return hour, minute
def _fix_hour(hour):
    return hour % 24
def _fix_time(hour, minute, second):
    minute, second = _fix_second(minute, second)
    hour, minute = _fix_minute(hour, minute)
    hour = _fix_hour(hour)
    return hour, minute, second

class Time(object):
    """Un wrapper pour 'datetime.time'.

    Attention! Cet objet est mutable, il ne faut donc pas l'utiliser comme clé
    dans un dictionnaire.
    """
    _t = None

    time = property(lambda self: self._t)
    hour = property(lambda self: self._t.hour)
    minute = property(lambda self: self._t.minute)
    second = property(lambda self: self._t.second)

    def __init__(self, hour=None, minute=None, second=None, t=None, s=None):
        """Initialiser l'objet

        Dans l'ordre, les champs considérés sont:
        - s le nombre de secondes depuis minuit.
        - hour, minute, second la spécification de l'heure.
        - t le nombre de secondes depuis l'epoch, comme retourné par
        time.time().
        - sinon prendre l'heure courante.
        """
        if s is not None:
            hour, s = s / 3600, s % 3600
            minute, second =  s / 60, s % 60
        elif hour is None and minute is None and second is None:
            if t is None: t = timedbl()
            hour, minute, second = localtime(t)[3:6]
        hour, minute, second = _fix_time(hour or 0, minute or 0, second or 0)
        self._t = pytime(hour, minute, second)

    FORMAT_MAP = {'%Y': '%(y)04i', '%m': '%(m)02i', '%d': '%(d)02i',
                  '%H': '%(H)02i', '%M': '%(M)02i', '%S': '%(S)02i',
                  }
    def format(self, format=None):
        """Formater l'heure pour affichage.

        Les champs valides sont %Y, %m, %d qui valent toujours 0, et %H, %M, %S
        qui correspondent à l'heure de cet objet.
        """
        if format is None: format = FR_TIMEF
        y, m, d, H, M, S = 0, 0, 0, self.hour, self.minute, self.second
        for fr, to in self.FORMAT_MAP.items():
            format = format.replace(fr, to)
        return format % locals()

    def nbsecs(self):
        """Retourner le nombre de secondes depuis minuit.
        """
        return self.hour * 3600 + self.minute * 60 + self.second
    nbsecs = property(nbsecs)

    def __repr__(self):
        if self.second == 0: seconds = ''
        else: seconds = ', %i' % self.second
        return '%s(%i, %i%s)' % (self.__class__.__name__, self.hour, self.minute, seconds)
    def __str__(self):
        if self.second == 0: seconds = ''
        else: seconds = ':%02i' % self.second
        return '%02i:%02i%s' % (self.hour, self.minute, seconds)
    def __unicode__(self):
        if self.second == 0: seconds = ''
        else: seconds = u':%02i' % self.second
        return u'%02i:%02i%s' % (self.hour, self.minute, seconds)

    def __eq__(self, other): return self._t == self._time(other, False)
    def __ne__(self, other): return self._t != self._time(other, False)
    def __lt__(self, other):
        if other is None: return False
        else: return self._t < self._time(other)
    def __le__(self, other):
        if other is None: return False
        else: return self._t <= self._time(other)
    def __gt__(self, other):
        if other is None: return True
        else: return self._t > self._time(other)
    def __ge__(self, other):
        if other is None: return True
        else: return self._t >= self._time(other)
    def __cmp__(self, other):
        if other is None: return 1
        else: return cmp(self._t, self._time(other))
    def __hash__(self): return hash(self._t)

    def _time(self, t, required=True):
        """Retourner l'instance de datetime.time correspondant à l'objet t.
        """
        if isinstance(t, pytime): return t
        elif isinstance(t, pydatetime): return t.timetz()
        elif isinstance(t, Time): return t._t
        elif required: raise ValueError("Expected datetime.*time or Time instance, got %s" % repr(t))
        else: return None

    def _s(self, t=None):
        """Obtenir le nombre de secondes depuis minuit de t.
        """
        if t is None: t = self
        if isinstance(t, Time): t = t._t
        if isinstance(t, pytime):
            return t.hour * 3600 + t.minute * 60 + t.second
        elif isinstance(t, pydatetime):
            return t.hour * 3600 + t.minute * 60 + t.second
        elif isinstance(t, timedelta): return t.seconds
        elif isnum(t): return t
        else: raise ValueError("Expected number, datetime.*time, datetime.timedelta or Time instance, got %s" % repr(t))

    def _new(cls, t=None, s=None):
        """Constructeur.
        * t est une instance de Time, datetime.time ou datetime.datetime.
        * s est un nombre de secondes depuis minuit.
        """
        if t is not None:
            if isinstance(t, pytime): return cls(t.hour, t.minute, t.second)
            elif isinstance(t, pydatetime): return cls(t.hour, t.minute, t.second)
            elif isinstance(t, Time): return cls(t.hour, t.minute, t.second)
            else: raise ValueError("Expected datetime.*time or Time instance, got %s" % repr(t))
        elif s is not None: return cls(s=s)
        else: return cls()
    _new = classmethod(_new)

    def replace(self, hour=None, minute=None, second=None):
        """Retourner une nouvelle instance avec les champs spécifiés modifiés.
        """
        kw = {}
        for name, value in [('hour', hour), ('minute', minute), ('second', second)]:
            if value is not None: kw[name] = value
        return self._new(self._t.replace(**kw))

    def __add__(self, other): return self._new(s=self._s() + self._s(other))
    __radd__ = __add__
    def add(self, seconds=1): return self + seconds

    def __sub__(self, other): return self._new(s=self._s() - self._s(other))
    __rsub__ = __sub__
    def sub(self, seconds=1): return self - seconds

    def diff(self, other): return self._s() - self._s(other)

def istime(t):
    """Tester si t est une instance de Time
    """
    return isinstance(t, Time)
def isanytime(t):
    """Tester si t est une instance de Time, datetime.time ou datetime.datetime
    """
    return isinstance(t, Time) or isinstance(t, pytime) or isinstance(t, pydatetime)

RE_TIME = re.compile(r'(\d+)(?:[.:](\d+)(?:[.:](\d+))?)?$')
def parse_time(s):
    """Parser une chaine et retourner une instance de Time.
    """
    mo = RE_TIME.match(s)
    if mo is None: raise ValueError("Invalid time format: %s" % _s(s))
    hour = mo.group(1)
    minute = mo.group(2)
    second = mo.group(3)
    if hour is not None: hour = int(hour)
    if minute is not None: minute = int(minute)
    if second is not None: second = int(second)
    return Time(hour, minute, second)

def ensure_time(t):
    """Retourner une instance de Time, ou None si t==None.

    t peut être une instance de Time, datetime.time, datetime.datetime ou une
    chaine.
    """
    if t is None: return None
    elif isinstance(t, Time): return t
    elif isinstance(t, pytime): return Time._new(t)
    elif isinstance(t, pydatetime): return Time._new(t)
    if not isstr(t): t = _s(t)
    return parse_time(t)
