/*
  Copyright (C) 2004-2005 SKYRIX Software AG
  Copyright (C) 2006-2010 Inverse inc.

  This file is part of SOPE.

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

  SOPE 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 Lesser General Public
  License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with SOPE; see the file COPYING. If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/


#import <NGExtensions/NSCalendarDate+misc.h>
#import <NGExtensions/NGCalendarDateRange.h>

#import "NSCalendarDate+ICal.h"

#import "iCalRecurrenceCalculator.h"
#import "iCalByDayMask.h"

@interface iCalDailyRecurrenceCalculator : iCalRecurrenceCalculator
@end

@interface iCalRecurrenceCalculator (PrivateAPI)
- (NSCalendarDate *) lastInstanceStartDate;
@end

@implementation iCalDailyRecurrenceCalculator

/**
 * TODO : Unsupported conditions for DAILY recurrences :
 *
 *   BYYEAR
 *   BYYEARDAY
 *   BYWEEKNO
 *   BYMONTH
 *   BYMONTHDAY
 *   BYHOUR
 *   BYMINUTE
 *
 * There's no GUI to defined such conditions, so there's no
 * problem for now.
 */
- (NSArray *) recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
{
  NSMutableArray *ranges;
  NSCalendarDate *firStart, *firEnd, *startDate, *endDate, *currentStartDate, *currentEndDate;
  iCalByDayMask *dayMask;
  long i, count, repeatCount;
  unsigned interval;

  //[self logWithFormat: @"Recurrence rule is %@", rrule];

  firStart = [firstRange startDate];
  firEnd = [firstRange endDate];
  startDate = [_r startDate];
  endDate = [_r endDate];
  dayMask = nil;
  repeatCount = 0;

  if (!firstRange)
    // Can happen when event/todo has a rrule with no dtstart
    return nil;

  if ([endDate compare: firStart] == NSOrderedAscending)
    // Range ends before first occurrence
    return nil;

  interval = [rrule repeatInterval];

  if ([[rrule byDay] length])
    dayMask = [rrule byDayMask];

  // If rule is bound, check the bounds
  if (![rrule isInfinite])
    {
      NSCalendarDate *until, *lastStartDate, *lastEndDate;

      lastStartDate = nil;
      lastEndDate = nil;
      until = [rrule untilDate];
      if (until)
        {
          lastStartDate = until;
          lastEndDate = until;
        }
      else
        {
          repeatCount = [rrule repeatCount];
          if (dayMask == nil)
          {
            // If there's no day mask, we can compute the date of the last
            // occurrence of the recurrent rule.
            lastStartDate = [firStart dateByAddingYears: 0 months: 0
                                              days: (interval
                                                     * (repeatCount - 1))];
            lastEndDate = [firEnd dateByAddingYears: 0 months: 0
                                              days: (interval
                                                     * (repeatCount - 1))];
          }                                        
        }

      if (lastStartDate != nil && lastEndDate != nil)
        {
          if ([lastEndDate compare: startDate] == NSOrderedAscending)
            // Range starts after last occurrence
            return nil;

          if ([lastStartDate compare: endDate] == NSOrderedAscending)
            // Range ends after last occurence; adjust end date
            endDate = lastStartDate;
        }
    }

  currentStartDate = [firStart copy];
  [currentStartDate autorelease];
  ranges = [NSMutableArray array];
  i = 1;
  count = 0;

  while ([currentStartDate compare: endDate] == NSOrderedAscending ||
         [currentStartDate compare: endDate] == NSOrderedSame)
    {
      BOOL wrongDay, isFirStart;

      wrongDay = NO;
      isFirStart = NO;

      if (i == 1)
        {
          isFirStart = YES;
          count++;
        }
      else if (repeatCount > 0 && dayMask)
        {
          // If the rule count is defined, stop once the count is reached.
          if ([dayMask occursOnDay: [currentStartDate dayOfWeek]])
            count++;
          else
            wrongDay = YES;

          if (count > repeatCount)
            break;
        }

      if (wrongDay == NO)
        {
          currentEndDate = [currentStartDate addTimeInterval: [firstRange duration]];
          if ([startDate compare: currentStartDate] == NSOrderedAscending ||
              [startDate compare: currentStartDate] == NSOrderedSame ||
              [startDate compare: currentEndDate] == NSOrderedAscending)
            {
              NGCalendarDateRange *r;

              if (isFirStart == NO && dayMask && repeatCount == 0)
                {
                  if (![dayMask occursOnDay: [currentStartDate dayOfWeek]])
                    wrongDay = YES;
                }

              if (isFirStart == YES || wrongDay == NO)
                {
                  r = [NGCalendarDateRange calendarDateRangeWithStartDate: currentStartDate
                                                                  endDate: currentEndDate];
                  [ranges addObject: r];
                }
            }
        }

      currentStartDate = [firStart dateByAddingYears: 0 months: 0
                                                days: (interval * i)];

      if (repeatCount > 0 && count == repeatCount)
        // The count variable is only usefull when a BYDAY constraint is
        // defined; when there's no BYDAY constraint, the endDate has been
        // adjusted to match the repeat count, if defined.
        break;

      i++;
    }

  return ranges;
}

- (NSCalendarDate *) lastInstanceStartDate
{
  NSCalendarDate *firStart, *lastInstanceStartDate;
  NGCalendarDateRange *r;
  NSArray *instances;

  lastInstanceStartDate = nil;
  if ([rrule repeatCount] > 0)
    {
      firStart = [firstRange startDate];
      if ([rrule hasByMask])
        {
          // Must perform the complete calculation
          r = [NGCalendarDateRange calendarDateRangeWithStartDate: firStart
                                                          endDate: [NSCalendarDate distantFuture]];
          instances = [self recurrenceRangesWithinCalendarDateRange: r];
          if ([instances count])
            lastInstanceStartDate = [(NGCalendarDateRange *)[instances lastObject] startDate];
        }
      else
        {
          // No BYxxx mask
          lastInstanceStartDate = [firStart dateByAddingYears: 0 months: 0
                                                         days: ([rrule repeatInterval]
                                                                * ([rrule repeatCount] - 1))];
        }
    }
  else
    lastInstanceStartDate = [super lastInstanceStartDate];

  return lastInstanceStartDate;
}

@end /* iCalDailyRecurrenceCalculator */
