'''
Task Coach - Your friendly task manager
Copyright (C) 2004-2012 Task Coach developers <developers@taskcoach.org>

Task Coach is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Task Coach is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
'''

''' Utilities for recurring dates. ''' # pylint: disable-msg=W0105

import calendar
import timedelta
import dateandtime as date


class Recurrence(object):
    units = ('daily', 'weekly', 'monthly', 'yearly', '')
    
    def __init__(self, unit='', amount=1, sameWeekday=False, max=0, count=0,
                 recurBasedOnCompletion=False): # pylint: disable-msg=W0622
        assert unit in self.units
        assert amount >= 1
        self.unit = unit
        self.amount = amount
        self.sameWeekday = sameWeekday
        self.max = max # Maximum number of recurrences we give out, 0 == infinite
        self.count = count # Actual number of recurrences given out so far
        self.recurBasedOnCompletion = recurBasedOnCompletion
                
    def __call__(self, *dateTimes, **kwargs):
        result = [self._nextDateTime(dateTime) for dateTime in dateTimes]
        if kwargs.get('next', True):
            # By default we expect our clients to call us once, but we allow
            # the client to tell us to expect more calls
            self.count += 1
            if self.count >= self.max and self.max != 0:
                self.unit = '' # We're done with recurring
                return
        if len(result) > 1:
            return tuple(result)
        elif len(result) == 1:
            return result[0]
        else:
            return
        
    def _nextDateTime(self, dateTime, amount=0):
        if date.DateTime() == dateTime or not self.unit:
            return dateTime 
        amount = amount or self.amount
        if amount > 1:
            dateTime = self._nextDateTime(dateTime, amount-1)
        if self.unit == 'yearly':
            return self._addYear(dateTime)
        elif self.unit == 'monthly':
            return self._addMonth(dateTime)
        else:
            return self._addDays(dateTime)

    def _addDays(self, dateTime):
        nrOfDays = dict(daily=1, weekly=7)[self.unit]
        return dateTime + timedelta.TimeDelta(days=nrOfDays)

    def _addMonth(self, dateTime):
        year, month, day = dateTime.year, dateTime.month, dateTime.day
        details = dateTime.hour, dateTime.minute, dateTime.second, dateTime.microsecond
        if month == 12: # If December, move to January next year
            year += 1
            month = 1
        else:
            month += 1
        if self.sameWeekday:
            weekday = dateTime.weekday()
            weekNr = min(3, (day - 1) / 7) # In what week of the month falls aDate, allowable range 0-3 
            day = weekNr * 7 + 1 # The earliest possible day that is on the same weekday as aDate
            result = date.DateTime(year, month, day, *details)
            while result.weekday() != weekday:
                day += 1
                result = date.DateTime(year, month, day, *details)
            return result
        else:
            while True: # Find a valid date in the next month
                try:
                    return date.DateTime(year, month, day, *details)
                except ValueError:
                    day -= 1

    def _addYear(self, dateTime):
        if (calendar.isleap(dateTime.year) and dateTime.month <= 2 and dateTime.day <=28) or \
           (calendar.isleap(dateTime.year + 1) and dateTime.month >=3): 
            days = 366
        else:
            days = 365
        newDateTime = dateTime + timedelta.TimeDelta(days=days)
        if self.sameWeekday:
            # Find the nearest date in newDate's year that is on the right 
            # weekday:
            weekday, year = dateTime.weekday(), newDateTime.year
            oneDay = timedelta.TimeDelta(days=1)
            newEarlierDateTime = newLaterDateTime = newDateTime
            while newEarlierDateTime.weekday() != weekday:
                newEarlierDateTime = newEarlierDateTime - oneDay
            while newLaterDateTime.weekday() != weekday:
                newLaterDateTime = newLaterDateTime + oneDay
            if newEarlierDateTime.year != year:
                newDateTime = newLaterDateTime
            else:
                newDateTime = newEarlierDateTime
        return newDateTime

    def copy(self):
        return self.__class__(self.unit, self.amount, self.sameWeekday, 
                              self.max, 
                              recurBasedOnCompletion=self.recurBasedOnCompletion)
    
    def __eq__(self, other):
        try:
            return self.unit == other.unit and self.amount == other.amount and \
                   self.sameWeekday == other.sameWeekday and \
                   self.max == other.max and \
                   self.recurBasedOnCompletion == other.recurBasedOnCompletion
        except AttributeError:
            return False
 
    def __lt__(self, other):
        try:
            return self.units.index(self.unit) < self.units.index(other.unit) or \
                (self.units.index(self.unit) == self.units.index(other.unit) and \
                self.amount < other.amount)
        except AttributeError:
            return True
 
    def __nonzero__(self):
        return bool(self.unit)
