/*
  Copyright (C) 2004-2005 SKYRIX Software AG

  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.
*/

#include "iCalRecurrenceRule.h"
#include "iCalDateHolder.h"
#include "NSCalendarDate+ICal.h"
#include "common.h"

/*
 freq       = rrFreq;
 until      = rrUntil;
 count      = rrCount;
 interval   = rrInterval;
 bysecond   = rrBySecondList;
 byminute   = rrByMinuteList;
 byhour     = rrByHourList;
 byday      = rrByDayList;
 bymonthday = rrByMonthDayList;
 byyearday  = rrByYearDayList;
 byweekno   = rrByWeekNumberList;
 bymonth    = rrByMonthList;
 bysetpos   = rrBySetPosList;
 wkst       = rrWeekStart;
 */

@interface iCalDateHolder (PrivateAPI)
- (void)setString:(NSString *)_value;
- (id)awakeAfterUsingSaxDecoder:(id)_decoder;
@end

@interface iCalRecurrenceRule (PrivateAPI)
- (iCalWeekDay)weekDayFromICalRepresentation:(NSString *)_day;
- (NSString *)iCalRepresentationForWeekDay:(iCalWeekDay)_weekDay;
- (NSString *)freq;
- (NSString *)wkst;
- (NSString *)byDayList;

- (void)_processRule;
- (void)setRrule:(NSString *)_rrule;
@end

@implementation iCalRecurrenceRule

+ (void)initialize {
  static BOOL didInit = NO;
  
  if (didInit) return;
  didInit = YES;
}

+ (id)recurrenceRuleWithICalRepresentation:(NSString *)_iCalRep {
  iCalRecurrenceRule *r;
  
  r = [[[self alloc] init] autorelease];
  [r setRrule:_iCalRep];
  return r;
}

- (id)init {
  self = [super init];
  if (self) {
    self->byDay.weekStart = iCalWeekDayMonday;
    self->interval        = 1;
  }
  return self;
}

- (void)dealloc {
  [self->untilDate release];
  [self->rrule     release];
  [super dealloc];
}


/* Accessors */

- (void)setFrequency:(iCalRecurrenceFrequency)_frequency {
  self->frequency = _frequency;
}
- (iCalRecurrenceFrequency)frequency {
  return self->frequency;
}

- (void)setRepeatCount:(unsigned)_repeatCount {
  self->repeatCount = _repeatCount;
}
- (unsigned)repeatCount {
  return self->repeatCount;
}

- (void)setUntilDate:(NSCalendarDate *)_untilDate {
  ASSIGN(self->untilDate, _untilDate);
}
- (NSCalendarDate *)untilDate {
  return self->untilDate;
}

- (void)setRepeatInterval:(int)_repeatInterval {
  self->interval = _repeatInterval;
}
- (int)repeatInterval {
  return self->interval;
}

- (void)setWeekStart:(iCalWeekDay)_weekStart {
  self->byDay.weekStart = _weekStart;
}
- (iCalWeekDay)weekStart {
  return self->byDay.weekStart;
}

- (void)setByDayMask:(unsigned)_mask {
  self->byDay.mask = _mask;
}
- (unsigned)byDayMask {
  return self->byDay.mask;
}

- (BOOL)isInfinite {
  return (self->repeatCount != 0 || self->untilDate) ? NO : YES;
}


/* Private */

- (iCalWeekDay)weekDayFromICalRepresentation:(NSString *)_day {
  _day = [_day uppercaseString];
  if ([_day isEqualToString:@"MO"])
    return iCalWeekDayMonday;
  else if ([_day isEqualToString:@"TU"])
    return iCalWeekDayTuesday;
  else if ([_day isEqualToString:@"WE"])
    return iCalWeekDayWednesday;
  else if ([_day isEqualToString:@"TH"])
    return iCalWeekDayThursday;
  else if ([_day isEqualToString:@"FR"])
    return iCalWeekDayFriday;
  else if ([_day isEqualToString:@"SA"])
    return iCalWeekDaySaturday;
  else if ([_day isEqualToString:@"SU"])
    return iCalWeekDaySunday;
  else
    [NSException raise:NSGenericException
                 format:@"Incorrect weekDay '%@' specified!", _day];
  return iCalWeekDayMonday; /* keep compiler happy */
}

- (NSString *)iCalRepresentationForWeekDay:(iCalWeekDay)_weekDay {
  switch (_weekDay) {
    case iCalWeekDayMonday:
      return @"MO";
    case iCalWeekDayTuesday:
      return @"TU";
    case iCalWeekDayWednesday:
      return @"WE";
    case iCalWeekDayThursday:
      return @"TH";
    case iCalWeekDayFriday:
      return @"FR";
    case iCalWeekDaySaturday:
      return @"SA";
    case iCalWeekDaySunday:
      return @"SU";
    default:
      return @"MO";
  }
}

- (NSString *)freq {
  switch (self->frequency) {
    case iCalRecurrenceFrequenceWeekly:
      return @"WEEKLY";
    case iCalRecurrenceFrequenceMonthly:
      return @"MONTHLY";
    case iCalRecurrenceFrequenceDaily:
      return @"DAILY";
    case iCalRecurrenceFrequenceYearly:
      return @"YEARLY";
    case iCalRecurrenceFrequenceHourly:
      return @"HOURLY";
    case iCalRecurrenceFrequenceMinutely:
      return @"MINUTELY";
    case iCalRecurrenceFrequenceSecondly:
      return @"SECONDLY";
    default:
      return @"UNDEFINED?";
  }
}

- (NSString *)wkst {
  return [self iCalRepresentationForWeekDay:self->byDay.weekStart];
}

/*
 TODO:
 Each BYDAY value can also be preceded by a positive (+n) or negative
 (-n) integer. If present, this indicates the nth occurrence of the
 specific day within the MONTHLY or YEARLY RRULE. For example, within
 a MONTHLY rule, +1MO (or simply 1MO) represents the first Monday
 within the month, whereas -1MO represents the last Monday of the
 month. If an integer modifier is not present, it means all days of
 this type within the specified frequency. For example, within a
 MONTHLY rule, MO represents all Mondays within the month.
*/
- (NSString *)byDayList {
  NSMutableString *s;
  unsigned        i, mask, day;
  BOOL            needsComma;

  s          = [NSMutableString stringWithCapacity:20];
  needsComma = NO;
  mask       = self->byDay.mask;
  day        = iCalWeekDayMonday;
  for (i = 0; i < 7; i++) {
    if (mask & day) {
      if (needsComma)
        [s appendString:@","];
      [s appendString:[self iCalRepresentationForWeekDay:day]];
      needsComma = YES;
    }
    day = (day << 1);
  }
  return s;
}

/* Rule */

- (void)setRrule:(NSString *)_rrule {
  ASSIGN(self->rrule, _rrule);
  [self _processRule];
}

/* Processing existing rrule */

- (void)_processRule {
  NSArray  *props;
  unsigned i, count;
  
  props = [self->rrule componentsSeparatedByString:@";"];
  count = [props count];
  for (i = 0; i < count; i++) {
    NSString *prop, *key, *value;
    NSRange  r;
    
    prop = [props objectAtIndex:i];
    r    = [prop rangeOfString:@"="];
    if (r.length) {
      key   = [prop substringToIndex:r.location];
      value = [prop substringFromIndex:NSMaxRange(r)];
    }
    else {
      key   = prop;
      value = nil;
    }
    [self takeValue:value forKey:[key lowercaseString]];
  }
}


/* properties */

- (void)setFreq:(NSString *)_freq {
  _freq = [_freq uppercaseString];
  if ([_freq isEqualToString:@"WEEKLY"])
    self->frequency = iCalRecurrenceFrequenceWeekly;
  else if ([_freq isEqualToString:@"MONTHLY"])
    self->frequency = iCalRecurrenceFrequenceMonthly;
  else if ([_freq isEqualToString:@"DAILY"])
    self->frequency = iCalRecurrenceFrequenceDaily;
  else if ([_freq isEqualToString:@"YEARLY"])
    self->frequency = iCalRecurrenceFrequenceYearly;
  else if ([_freq isEqualToString:@"HOURLY"])
    self->frequency = iCalRecurrenceFrequenceHourly;
  else if ([_freq isEqualToString:@"MINUTELY"])
    self->frequency = iCalRecurrenceFrequenceMinutely;
  else if ([_freq isEqualToString:@"SECONDLY"])
    self->frequency = iCalRecurrenceFrequenceSecondly;
  else
    [NSException raise:NSGenericException
                 format:@"Incorrect frequency '%@' specified!", _freq];
}

- (void)setInterval:(NSString *)_interval {
  self->interval = [_interval intValue];
}
- (void)setCount:(NSString *)_count {
  self->repeatCount = [_count unsignedIntValue];
}
- (void)setUntil:(NSString *)_until {
  iCalDateHolder *dh;
  NSCalendarDate *date;

  dh = [[iCalDateHolder alloc] init];
  [dh setString:_until];
  date = [dh awakeAfterUsingSaxDecoder:nil];
  ASSIGN(self->untilDate, date);
  [dh release];
}

- (void)setWkst:(NSString *)_weekStart {
  self->byDay.weekStart = [self weekDayFromICalRepresentation:_weekStart];
}

- (void)setByday:(NSString *)_byDayList {
  NSArray  *days;
  unsigned i, count;

  self->byDay.mask = 0;
  days  = [_byDayList componentsSeparatedByString:@","];
  count = [days count];
  for (i = 0; i < count; i++) {
    NSString    *iCalDay;
    iCalWeekDay day;
    
    iCalDay = [days objectAtIndex:i];
    day     = [self weekDayFromICalRepresentation:iCalDay];
    self->byDay.mask |= day;
  }
}

/* key/value coding */

- (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
  [self warnWithFormat:@"Don't know how to process '%@'!", _key];
}


/* Description */

- (NSString *)iCalRepresentation {
  NSMutableString *s;
  
  s = [NSMutableString stringWithCapacity:80];
  [s appendString:@"FREQ="];
  [s appendString:[self freq]];
  if ([self repeatInterval] != 1) {
    [s appendFormat:@";INTERVAL=%d", [self repeatInterval]];
  }
  if (![self isInfinite]) {
    if ([self repeatCount] > 0) {
      [s appendFormat:@";COUNT=%d", [self repeatCount]];
    }
    else {
      [s appendString:@";UNTIL="];
      [s appendString:[[self untilDate] icalString]];
    }
  }
  if (self->byDay.weekStart != iCalWeekDayMonday) {
    [s appendString:@";WKST="];
    [s appendString:[self iCalRepresentationForWeekDay:self->byDay.weekStart]];
  }
  if (self->byDay.mask != 0) {
    [s appendString:@";BYDAY="];
    [s appendString:[self byDayList]];
  }
  return s;
}


@end
