/*
    This file is part of libkcal.

    Copyright (c) 2005 Reinhold Kainhofer <reinhold@kainhofe.com>


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

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "recurrencerule.h"

#include <kdebug.h>
#include <tdeglobal.h>
#include <tqdatetime.h>
#include <tqstringlist.h>

#include <limits.h>
#include <math.h>

using namespace KCal;

// Maximum number of intervals to process
const int LOOP_LIMIT = 10000;

// FIXME: If TQt is ever changed so that TQDateTime:::addSecs takes into account
//        DST shifts, we need to use our own addSecs method, too, since we
//        need to caalculate things in UTC!
/** Workaround for broken TQDateTime::secsTo (at least in TQt 3.3). While
   TQDateTime::secsTo does take time zones into account, TQDateTime::addSecs
   does not, so we have a problem:
      TQDateTime d1(TQDate(2005, 10, 30), TQTime(1, 30, 0) );
      TQDateTime d2(TQDate(2005, 10, 30), TQTime(3, 30, 0) );

      kdDebug(5800) << "d1=" << d1 << ", d2=" << d2 << endl;
      kdDebug(5800) << "d1.secsTo(d2)=" << d1.secsTo(d2) << endl;
      kdDebug(5800) << "d1.addSecs(d1.secsTo(d2))=" << d1.addSecs(d1.secsTo(d2)) << endl;
   This code generates the following output:
      libkcal: d1=Son Okt 30 01:30:00 2005, d2=Son Okt 30 03:30:00 2005
      libkcal: d1.secsTo(d2)=10800
      libkcal: d1.addSecs(d1.secsTo(d2))=Son Okt 30 04:30:00 2005
   Notice that secsTo counts the hour between 2:00 and 3:00 twice, while
   adddSecs doesn't and so has one additional hour. This basically makes it
   impossible to use TQDateTime for *any* calculations, in local time zone as
   well as in UTC. Since we don't want to use
   time zones anyway, but do all secondsly/minutely/hourly calculations in UTC,
   we simply use our own secsTo, which ignores all time zone shifts. */
long long ownSecsTo( const TQDateTime &dt1, const TQDateTime &dt2 )
{
  long long res = static_cast<long long>( dt1.date().daysTo( dt2.date() ) ) * 24*3600;
  res += dt1.time().secsTo( dt2.time() );
  return res;
}



/**************************************************************************
 *                               DateHelper                               *
 **************************************************************************/


class DateHelper {
  public:
#ifndef NDEBUG
    static TQString dayName( short day );
#endif
    static TQDate getNthWeek( int year, int weeknumber, short weekstart = 1 );
    static int weekNumbersInYear( int year, short weekstart = 1 );
    static int getWeekNumber( const TQDate &date, short weekstart, int *year = 0 );
    static int getWeekNumberNeg( const TQDate &date, short weekstart, int *year = 0 );
};


#ifndef NDEBUG
TQString DateHelper::dayName( short day )
{
  switch ( day ) {
    case 1: return "MO"; break;
    case 2: return "TU"; break;
    case 3: return "WE"; break;
    case 4: return "TH"; break;
    case 5: return "FR"; break;
    case 6: return "SA"; break;
    case 7: return "SU"; break;
    default: return "??";
  }
}
#endif


TQDate DateHelper::getNthWeek( int year, int weeknumber, short weekstart )
{
  if ( weeknumber == 0 ) return TQDate();
  // Adjust this to the first day of week #1 of the year and add 7*weekno days.
  TQDate dt( year, 1, 4 ); // Week #1 is the week that contains Jan 4
  int adjust = -(7 + dt.dayOfWeek() - weekstart) % 7;
  if ( weeknumber > 0 ) {
    dt = dt.addDays( 7 * (weeknumber-1) + adjust );
  } else if ( weeknumber < 0 ) {
    dt = dt.addYears( 1 );
    dt = dt.addDays( 7 * weeknumber + adjust );
  }
  return dt;
}


int DateHelper::getWeekNumber( const TQDate &date, short weekstart, int *year )
{
// kdDebug(5800) << "Getting week number for " << date << " with weekstart="<<weekstart<<endl;
  if ( year ) *year = date.year();
  TQDate dt( date.year(), 1, 4 ); // <= definitely in week #1
  dt = dt.addDays( -(7 + dt.dayOfWeek() - weekstart) % 7 ); // begin of week #1
  TQDate dtn( date.year()+1, 1, 4 ); // <= definitely first week of next year
  dtn = dtn.addDays( -(7 + dtn.dayOfWeek() - weekstart) % 7 );

  int daysto = dt.daysTo( date );
  int dayston = dtn.daysTo( date );
  if ( daysto < 0 ) {
    if ( year ) *year = date.year()-1;
    dt = TQDate( date.year()-1, 1, 4 );
    dt = dt.addDays( -(7 + dt.dayOfWeek() - weekstart) % 7 ); // begin of week #1
    daysto = dt.daysTo( date );
  } else if ( dayston >= 0 ) {
    // in first week of next year;
    if ( year ) *year = date.year() + 1;
    dt = dtn;
    daysto = dayston;
  }
  return daysto / 7 + 1;
}

int DateHelper::weekNumbersInYear( int year, short weekstart )
{
  TQDate dt( year, 1, weekstart );
  TQDate dt1( year + 1, 1, weekstart );
  return dt.daysTo( dt1 ) / 7;
}

// Week number from the end of the year
int DateHelper::getWeekNumberNeg( const TQDate &date, short weekstart, int *year )
{
  int weekpos = getWeekNumber( date, weekstart, year );
  return weekNumbersInYear( *year, weekstart ) - weekpos - 1;
}





/**************************************************************************
 *                       RecurrenceRule::Constraint                       *
 **************************************************************************/


RecurrenceRule::Constraint::Constraint( int wkst )
{
  weekstart = wkst;
  clear();
}

RecurrenceRule::Constraint::Constraint( const TQDateTime &preDate, PeriodType type, int wkst )
{
  weekstart = wkst;
  readDateTime( preDate, type );
}

void RecurrenceRule::Constraint::clear()
{
  year = 0;
  month = 0;
  day = 0;
  hour = -1;
  minute = -1;
  second = -1;
  weekday = 0;
  weekdaynr = 0;
  weeknumber = 0;
  yearday = 0;
}

bool RecurrenceRule::Constraint::matches( const TQDate &dt, RecurrenceRule::PeriodType type ) const
{
  // If the event recurs in week 53 or 1, the day might not belong to the same
  // year as the week it is in. E.g. Jan 1, 2005 is in week 53 of year 2004.
  // So we can't simply check the year in that case!
  if ( weeknumber == 0 ) {
    if ( year > 0 && year != dt.year() ) return false;
  } else {
    int y;
    if ( weeknumber > 0 &&
         weeknumber != DateHelper::getWeekNumber( dt, weekstart, &y ) ) return false;
    if ( weeknumber < 0 &&
         weeknumber != DateHelper::getWeekNumberNeg( dt, weekstart, &y ) ) return false;
    if ( year > 0 && year != y ) return false;
  }

  if ( month > 0 && month != dt.month() ) return false;
  if ( day > 0 && day != dt.day() ) return false;
  if ( day < 0 && dt.day() != (dt.daysInMonth() + day + 1 ) ) return false;
  if ( weekday > 0 ) {
    if ( weekday != dt.dayOfWeek() ) return false;
    if ( weekdaynr != 0 ) {
      // If it's a yearly recurrence and a month is given, the position is
      // still in the month, not in the year.
      bool inMonth = (type == rMonthly) || ( type == rYearly && month > 0 );
      // Monthly
      if ( weekdaynr > 0 && inMonth &&
           weekdaynr != (dt.day() - 1)/7 + 1 ) return false;
      if ( weekdaynr < 0 && inMonth &&
           weekdaynr != -((dt.daysInMonth() - dt.day() )/7 + 1 ) )
        return false;
      // Yearly
      if ( weekdaynr > 0 && !inMonth &&
           weekdaynr != (dt.dayOfYear() - 1)/7 + 1 ) return false;
      if ( weekdaynr < 0 && !inMonth &&
           weekdaynr != -((dt.daysInYear() - dt.dayOfYear() )/7 + 1 ) )
        return false;
    }
  }
  if ( yearday > 0 && yearday != dt.dayOfYear() ) return false;
  if ( yearday < 0 && yearday != dt.daysInYear() - dt.dayOfYear() + 1 )
    return false;
  return true;
}

bool RecurrenceRule::Constraint::matches( const TQDateTime &dt, RecurrenceRule::PeriodType type ) const
{
  if ( !matches( dt.date(), type ) ) return false;
  if ( hour >= 0 && hour != dt.time().hour() ) return false;
  if ( minute >= 0 && minute != dt.time().minute() ) return false;
  if ( second >= 0 && second != dt.time().second() ) return false;
  return true;
}

bool RecurrenceRule::Constraint::isConsistent( PeriodType /*period*/) const
{
  // TODO: Check for consistency, e.g. byyearday=3 and bymonth=10
  return true;
}

TQDateTime RecurrenceRule::Constraint::intervalDateTime( RecurrenceRule::PeriodType type ) const
{
  TQDateTime dt;
  dt.setTime( TQTime( 0, 0, 0 ) );
  dt.setDate( TQDate( year, (month>0)?month:1, (day>0)?day:1 ) );
  if ( day < 0 )
    dt = dt.addDays( dt.date().daysInMonth() + day );
  switch ( type ) {
    case rSecondly:
      dt.setTime( TQTime( hour, minute, second ) ); break;
    case rMinutely:
      dt.setTime( TQTime( hour, minute, 1 ) ); break;
    case rHourly:
      dt.setTime( TQTime( hour, 1, 1 ) ); break;
    case rDaily:
      break;
    case rWeekly:
      dt = DateHelper::getNthWeek( year, weeknumber, weekstart ); break;
    case rMonthly:
      dt.setDate( TQDate( year, month, 1 ) ); break;
    case rYearly:
      dt.setDate( TQDate( year, 1, 1 ) ); break;
    default:
      break;
  }
  return dt;
}


//           Y  M  D | H  Mn S | WD #WD | WN | YD
// required:
//           x       | x  x  x |        |    |
// 0) Trivial: Exact date given, maybe other restrictions
//           x  x  x | x  x  x |        |    |
// 1) Easy case: no weekly restrictions -> at most a loop through possible dates
//           x  +  + | x  x  x |  -  -  |  - |  -
// 2) Year day is given -> date known
//           x       | x  x  x |        |    |  +
// 3) week number is given -> loop through all days of that week. Further
//    restrictions will be applied in the end, when we check all dates for
//    consistency with the constraints
//           x       | x  x  x |        |  + | (-)
// 4) week day is specified ->
//           x       | x  x  x |  x  ?  | (-)| (-)
// 5) All possiblecases have already been treated, so this must be an error!

DateTimeList RecurrenceRule::Constraint::dateTimes( RecurrenceRule::PeriodType type ) const
{
// kdDebug(5800) << "              RecurrenceRule::Constraint::dateTimes: " << endl;
  DateTimeList result;
  bool done = false;
  // TODO_Recurrence: Handle floating
  TQTime tm( hour, minute, second );
  if ( !isConsistent( type ) ) return result;

  if ( !done && day > 0 && month > 0 ) {
    TQDateTime dt( TQDate( year, month, day ), tm );
    if ( dt.isValid() ) result.append( dt );
    done = true;
  }
  if ( !done && day < 0 && month > 0 ) {
    TQDateTime dt( TQDate( year, month, 1 ), tm );
    dt = dt.addDays( dt.date().daysInMonth() + day );
    if ( dt.isValid() ) result.append( dt );
    done = true;
  }


  if ( !done && weekday == 0 && weeknumber == 0 && yearday == 0 ) {
    // Easy case: date is given, not restrictions by week or yearday
    uint mstart = (month>0) ? month : 1;
    uint mend = (month <= 0) ? 12 : month;
    for ( uint m = mstart; m <= mend; ++m ) {
      uint dstart, dend;
      if ( day > 0 ) {
        dstart = dend = day;
      } else if ( day < 0 ) {
        TQDate date( year, month, 1 );
        dstart = dend = date.daysInMonth() + day + 1;
      } else {
        TQDate date( year, month, 1 );
        dstart = 1;
        dend = date.daysInMonth();
      }
      for ( uint d = dstart; d <= dend; ++d ) {
        TQDateTime dt( TQDate( year, m, d ), tm );
        if ( dt.isValid() ) result.append( dt );
      }
    }
    done = true;
  }

  // Else: At least one of the week / yearday restrictions was given...
  // If we have a yearday (and of course a year), we know the exact date
  if ( !done && yearday != 0 ) {
    // yearday < 0 means from end of year, so we'll need Jan 1 of the next year
    TQDate d( year + ((yearday>0)?0:1), 1, 1 );
    d = d.addDays( yearday - ((yearday>0)?1:0) );
    result.append( TQDateTime( d, tm ) );
    done = true;
  }

  // Else: If we have a weeknumber, we have at most 7 possible dates, loop through them
  if ( !done && weeknumber != 0 ) {
    TQDate wst( DateHelper::getNthWeek( year, weeknumber, weekstart ) );
    if ( weekday != 0 ) {
      wst = wst.addDays( (7 + weekday - weekstart ) % 7 );
      result.append( TQDateTime( wst, tm ) );
    } else {
      for ( int i = 0; i < 7; ++i ) {
        result.append( TQDateTime( wst, tm ) );
        wst = wst.addDays( 1 );
      }
    }
    done = true;
  }

  // weekday is given
  if ( !done && weekday != 0 ) {
    TQDate dt( year, 1, 1 );
    // If type == yearly and month is given, pos is still in month not year!
    // TODO_Recurrence: Correct handling of n-th  BYDAY...
    int maxloop = 53;
    bool inMonth = ( type == rMonthly) || ( type == rYearly && month > 0 );
    if ( inMonth && month > 0 ) {
      dt = TQDate( year, month, 1 );
      maxloop = 5;
    }
    if ( weekdaynr < 0 ) {
      // From end of period (month, year) => relative to begin of next period
      if ( inMonth )
        dt = dt.addMonths( 1 );
      else
        dt = dt.addYears( 1 );
    }
    int adj = ( 7 + weekday - dt.dayOfWeek() ) % 7;
    dt = dt.addDays( adj ); // correct first weekday of the period

    if ( weekdaynr > 0 ) {
      dt = dt.addDays( ( weekdaynr - 1 ) * 7 );
      result.append( TQDateTime( dt, tm ) );
    } else if ( weekdaynr < 0 ) {
      dt = dt.addDays( weekdaynr * 7 );
      result.append( TQDateTime( dt, tm ) );
    } else {
      // loop through all possible weeks, non-matching will be filtered later
      for ( int i = 0; i < maxloop; ++i ) {
        result.append( TQDateTime( dt, tm ) );
        dt = dt.addDays( 7 );
      }
    }
  } // weekday != 0


  // Only use those times that really match all other constraints, too
  DateTimeList valid;
  DateTimeList::Iterator it;
  for ( it = result.begin(); it != result.end(); ++it ) {
    if ( matches( *it, type ) ) valid.append( *it );
  }
  // Don't sort it here, would be unnecessary work. The results from all
  // constraints will be merged to one big list of the interval. Sort that one!
  return valid;
}


bool RecurrenceRule::Constraint::increase( RecurrenceRule::PeriodType type, int freq )
{
  // convert the first day of the interval to TQDateTime
  // Sub-daily types need to be converted to UTC to correctly handle DST shifts
  TQDateTime dt( intervalDateTime( type ) );

  // Now add the intervals
  switch ( type ) {
    case rSecondly:
      dt = dt.addSecs( freq ); break;
    case rMinutely:
      dt = dt.addSecs( 60*freq ); break;
    case rHourly:
      dt = dt.addSecs( 3600 * freq ); break;
    case rDaily:
      dt = dt.addDays( freq ); break;
    case rWeekly:
      dt = dt.addDays( 7*freq ); break;
    case rMonthly:
      dt = dt.addMonths( freq ); break;
    case rYearly:
      dt = dt.addYears( freq ); break;
    default:
      break;
  }
  // Convert back from TQDateTime to the Constraint class
  readDateTime( dt, type );

  return true;
}

bool RecurrenceRule::Constraint::readDateTime( const TQDateTime &preDate, PeriodType type )
{
  clear();
  switch ( type ) {
    // Really fall through! Only weekly needs to be treated differentely!
    case rSecondly:
      second = preDate.time().second();
    case rMinutely:
      minute = preDate.time().minute();
    case rHourly:
      hour = preDate.time().hour();
    case rDaily:
      day = preDate.date().day();
    case rMonthly:
      month = preDate.date().month();
    case rYearly:
      year = preDate.date().year();
      break;

    case rWeekly:
      // Determine start day of the current week, calculate the week number from that
      weeknumber = DateHelper::getWeekNumber( preDate.date(), weekstart, &year );
      break;
    default:
      break;
  }
  return true;
}


RecurrenceRule::RecurrenceRule( )
: mPeriod( rNone ), mFrequency( 0 ), mIsReadOnly( false ),
  mFloating( false ),
  mWeekStart(1)
{
}

RecurrenceRule::RecurrenceRule( const RecurrenceRule &r )
{
  mRRule = r.mRRule;
  mPeriod = r.mPeriod;
  mDateStart = r.mDateStart;
  mDuration = r.mDuration;
  mDateEnd = r.mDateEnd;
  mFrequency = r.mFrequency;

  mIsReadOnly = r.mIsReadOnly;
  mFloating = r.mFloating;

  mBySeconds = r.mBySeconds;
  mByMinutes = r.mByMinutes;
  mByHours = r.mByHours;
  mByDays = r.mByDays;
  mByMonthDays = r.mByMonthDays;
  mByYearDays = r.mByYearDays;
  mByWeekNumbers = r.mByWeekNumbers;
  mByMonths = r.mByMonths;
  mBySetPos = r.mBySetPos;
  mWeekStart = r.mWeekStart;

  setDirty();
}

RecurrenceRule::~RecurrenceRule()
{
}

bool RecurrenceRule::operator==( const RecurrenceRule& r ) const
{
  if ( mPeriod != r.mPeriod ) return false;
  if ( mDateStart != r.mDateStart ) return false;
  if ( mDuration != r.mDuration ) return false;
  if ( mDateEnd != r.mDateEnd ) return false;
  if ( mFrequency != r.mFrequency ) return false;

  if ( mIsReadOnly != r.mIsReadOnly ) return false;
  if ( mFloating != r.mFloating ) return false;

  if ( mBySeconds != r.mBySeconds ) return false;
  if ( mByMinutes != r.mByMinutes ) return false;
  if ( mByHours != r.mByHours ) return false;
  if ( mByDays != r.mByDays ) return false;
  if ( mByMonthDays != r.mByMonthDays ) return false;
  if ( mByYearDays != r.mByYearDays ) return false;
  if ( mByWeekNumbers != r.mByWeekNumbers ) return false;
  if ( mByMonths != r.mByMonths ) return false;
  if ( mBySetPos != r.mBySetPos ) return false;
  if ( mWeekStart != r.mWeekStart ) return false;

  return true;
}

void RecurrenceRule::addObserver( Observer *observer )
{
  if ( !mObservers.contains( observer ) )
    mObservers.append( observer );
}

void RecurrenceRule::removeObserver( Observer *observer )
{
  if ( mObservers.contains( observer ) )
    mObservers.remove( observer );
}



void RecurrenceRule::setRecurrenceType( PeriodType period )
{
  if ( isReadOnly() ) return;
  mPeriod = period;
  setDirty();
}

TQDateTime RecurrenceRule::endDt( bool *result ) const
{
  if ( result ) *result = false;
  if ( mPeriod == rNone ) return TQDateTime();
  if ( mDuration < 0 ) return TQDateTime();
  if ( mDuration == 0 ) {
    if ( result ) *result = true;
    return mDateEnd;
  }
  // N occurrences. Check if we have a full cache. If so, return the cached end date.
  if ( ! mCached ) {
    // If not enough occurrences can be found (i.e. inconsistent constraints)
    if ( !buildCache() ) return TQDateTime();
  }
  if ( result ) *result = true;
  return mCachedDateEnd;
}

void RecurrenceRule::setEndDt( const TQDateTime &dateTime )
{
  if ( isReadOnly() ) return;
  mDateEnd = dateTime;
  mDuration = 0; // set to 0 because there is an end date/time
  setDirty();
}

void RecurrenceRule::setDuration(int duration)
{
  if ( isReadOnly() ) return;
  mDuration = duration;
  setDirty();
}

void RecurrenceRule::setFloats( bool floats )
{
  if ( isReadOnly() ) return;
  mFloating = floats;
  setDirty();
}

void RecurrenceRule::clear()
{
  if ( isReadOnly() ) return;
  mPeriod = rNone;
  mBySeconds.clear();
  mByMinutes.clear();
  mByHours.clear();
  mByDays.clear();
  mByMonthDays.clear();
  mByYearDays.clear();
  mByWeekNumbers.clear();
  mByMonths.clear();
  mBySetPos.clear();
  mWeekStart = 1;

  setDirty();
}

void RecurrenceRule::setDirty()
{
  mConstraints.clear();
  buildConstraints();
  mDirty = true;
  mCached = false;
  mCachedDates.clear();
  for ( TQValueList<Observer*>::ConstIterator it = mObservers.begin();
        it != mObservers.end(); ++it ) {
    if ( (*it) ) (*it)->recurrenceChanged( this );
  }
}

void RecurrenceRule::setStartDt( const TQDateTime &start )
{
  if ( isReadOnly() ) return;
  mDateStart = start;
  setDirty();
}

void RecurrenceRule::setFrequency(int freq)
{
  if ( isReadOnly() || freq <= 0 ) return;
  mFrequency = freq;
  setDirty();
}

void RecurrenceRule::setBySeconds( const TQValueList<int> bySeconds )
{
  if ( isReadOnly() ) return;
  mBySeconds = bySeconds;
  setDirty();
}

void RecurrenceRule::setByMinutes( const TQValueList<int> byMinutes )
{
  if ( isReadOnly() ) return;
  mByMinutes = byMinutes;
  setDirty();
}

void RecurrenceRule::setByHours( const TQValueList<int> byHours )
{
  if ( isReadOnly() ) return;
  mByHours = byHours;
  setDirty();
}


void RecurrenceRule::setByDays( const TQValueList<WDayPos> byDays )
{
  if ( isReadOnly() ) return;
  mByDays = byDays;
  setDirty();
}

void RecurrenceRule::setByMonthDays( const TQValueList<int> byMonthDays )
{
  if ( isReadOnly() ) return;
  mByMonthDays = byMonthDays;
  setDirty();
}

void RecurrenceRule::setByYearDays( const TQValueList<int> byYearDays )
{
  if ( isReadOnly() ) return;
  mByYearDays = byYearDays;
  setDirty();
}

void RecurrenceRule::setByWeekNumbers( const TQValueList<int> byWeekNumbers )
{
  if ( isReadOnly() ) return;
  mByWeekNumbers = byWeekNumbers;
  setDirty();
}

void RecurrenceRule::setByMonths( const TQValueList<int> byMonths )
{
  if ( isReadOnly() ) return;
  mByMonths = byMonths;
  setDirty();
}

void RecurrenceRule::setBySetPos( const TQValueList<int> bySetPos )
{
  if ( isReadOnly() ) return;
  mBySetPos = bySetPos;
  setDirty();
}

void RecurrenceRule::setWeekStart( short weekStart )
{
  if ( isReadOnly() ) return;
  mWeekStart = weekStart;
  setDirty();
}



// Taken from recurrence.cpp
// int RecurrenceRule::maxIterations() const
// {
//   /* Find the maximum number of iterations which may be needed to reach the
//    * next actual occurrence of a monthly or yearly recurrence.
//    * More than one iteration may be needed if, for example, it's the 29th February,
//    * the 31st day of the month or the 5th Monday, and the month being checked is
//    * February or a 30-day month.
//    * The following recurrences may never occur:
//    * - For rMonthlyDay: if the frequency is a whole number of years.
//    * - For rMonthlyPos: if the frequency is an even whole number of years.
//    * - For rYearlyDay, rYearlyMonth: if the frequeny is a multiple of 4 years.
//    * - For rYearlyPos: if the frequency is an even number of years.
//    * The maximum number of iterations needed, assuming that it does actually occur,
//    * was found empirically.
//    */
//   switch (recurs) {
//     case rMonthlyDay:
//       return (rFreq % 12) ? 6 : 8;
//
//     case rMonthlyPos:
//       if (rFreq % 12 == 0) {
//         // Some of these frequencies may never occur
//         return (rFreq % 84 == 0) ? 364         // frequency = multiple of 7 years
//              : (rFreq % 48 == 0) ? 7           // frequency = multiple of 4 years
//              : (rFreq % 24 == 0) ? 14 : 28;    // frequency = multiple of 2 or 1 year
//       }
//       // All other frequencies will occur sometime
//       if (rFreq > 120)
//         return 364;    // frequencies of > 10 years will hit the date limit first
//       switch (rFreq) {
//         case 23:   return 50;
//         case 46:   return 38;
//         case 56:   return 138;
//         case 66:   return 36;
//         case 89:   return 54;
//         case 112:  return 253;
//         default:   return 25;       // most frequencies will need < 25 iterations
//       }
//
//     case rYearlyMonth:
//     case rYearlyDay:
//       return 8;          // only 29th Feb or day 366 will need more than one iteration
//
//     case rYearlyPos:
//       if (rFreq % 7 == 0)
//         return 364;    // frequencies of a multiple of 7 years will hit the date limit first
//       if (rFreq % 2 == 0) {
//         // Some of these frequencies may never occur
//         return (rFreq % 4 == 0) ? 7 : 14;    // frequency = even number of years
//       }
//       return 28;
//   }
//   return 1;
// }

void RecurrenceRule::buildConstraints()
{
  mTimedRepetition = 0;
  mNoByRules = mBySetPos.isEmpty();
  mConstraints.clear();
  Constraint con;
  if ( mWeekStart > 0 ) con.weekstart = mWeekStart;
  mConstraints.append( con );

  Constraint::List tmp;
  Constraint::List::const_iterator it;
  TQValueList<int>::const_iterator intit;

  #define intConstraint( list, element ) \
  if ( !list.isEmpty() ) { \
    mNoByRules = false; \
    for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { \
      for ( intit = list.constBegin(); intit != list.constEnd(); ++intit ) { \
        con = (*it); \
        con.element = (*intit); \
        tmp.append( con ); \
      } \
    } \
    mConstraints = tmp; \
    tmp.clear(); \
  }

  intConstraint( mBySeconds, second );
  intConstraint( mByMinutes, minute );
  intConstraint( mByHours, hour );
  intConstraint( mByMonthDays, day );
  intConstraint( mByMonths, month );
  intConstraint( mByYearDays, yearday );
  intConstraint( mByWeekNumbers, weeknumber );
  #undef intConstraint

  if ( !mByDays.isEmpty() ) {
    mNoByRules = false;
    for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) {
      TQValueList<WDayPos>::const_iterator dayit;
      for ( dayit = mByDays.constBegin(); dayit != mByDays.constEnd(); ++dayit ) {
        con = (*it);
        con.weekday = (*dayit).day();
        con.weekdaynr = (*dayit).pos();
        tmp.append( con );
      }
    }
    mConstraints = tmp;
    tmp.clear();
  }

  #define fixConstraint( element, value ) \
  { \
    tmp.clear(); \
    for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { \
      con = (*it); con.element = value; tmp.append( con ); \
    } \
    mConstraints = tmp; \
  }
  // Now determine missing values from DTSTART. This can speed up things,
  // because we have more restrictions and save some loops.

  // TODO: Does RFC 2445 intend to restrict the weekday in all cases of weekly?
  if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
    fixConstraint( weekday, mDateStart.date().dayOfWeek() );
  }

  // Really fall through in the cases, because all smaller time intervals are
  // constrained from dtstart
  switch ( mPeriod ) {
    case rYearly:
      if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() && mByYearDays.isEmpty() && mByMonths.isEmpty() ) {
        fixConstraint( month, mDateStart.date().month() );
      }
    case rMonthly:
      if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() && mByYearDays.isEmpty() && mByMonthDays.isEmpty() ) {
        fixConstraint( day, mDateStart.date().day() );
      }

    case rWeekly:
    case rDaily:
      if ( mByHours.isEmpty() ) {
        fixConstraint( hour, mDateStart.time().hour() );
      }
    case rHourly:
      if ( mByMinutes.isEmpty() ) {
        fixConstraint( minute, mDateStart.time().minute() );
      }
    case rMinutely:
      if ( mBySeconds.isEmpty() ) {
        fixConstraint( second, mDateStart.time().second() );
      }
    case rSecondly:
    default:
      break;
  }
  #undef fixConstraint

  if ( mNoByRules ) {
    switch ( mPeriod ) {
      case rHourly:
        mTimedRepetition = mFrequency * 3600;
        break;
      case rMinutely:
        mTimedRepetition = mFrequency * 60;
        break;
      case rSecondly:
        mTimedRepetition = mFrequency;
        break;
      default:
        break;
    }
  } else {
    Constraint::List::Iterator conit = mConstraints.begin();
    while ( conit != mConstraints.end() ) {
      if ( (*conit).isConsistent( mPeriod ) ) {
        ++conit;
      } else {
        conit = mConstraints.remove( conit );
      }
    }
  }
}

bool RecurrenceRule::buildCache() const
{
kdDebug(5800) << "         RecurrenceRule::buildCache: " << endl;
  // Build the list of all occurrences of this event (we need that to determine
  // the end date!)
  Constraint interval( getNextValidDateInterval( startDt(), recurrenceType() ) );
  TQDateTime next;

  DateTimeList dts = datesForInterval( interval, recurrenceType() );
  DateTimeList::Iterator it = dts.begin();
  // Only use dates after the event has started (start date is only included
  // if it matches)
  while ( it != dts.end() ) {
    if ( (*it) < startDt() ) it =  dts.remove( it );
    else ++it;
  }
//  dts.prepend( startDt() ); // the start date is always the first occurrence


  int loopnr = 0;
  int dtnr = dts.count();
  // some validity checks to avoid infinite loops (i.e. if we have
  // done this loop already 10000 times and found no occurrence, bail out )
  while ( loopnr < 10000 && dtnr < mDuration ) {
    interval.increase( recurrenceType(), frequency() );
    // The returned date list is already sorted!
    dts += datesForInterval( interval, recurrenceType() );
    dtnr = dts.count();
    ++loopnr;
  }
  if ( int(dts.count()) > mDuration ) {
    // we have picked up more occurrences than necessary, remove them
    it = dts.at( mDuration );
    while ( it != dts.end() ) it = dts.remove( it );
  }
  mCached = true;
  mCachedDates = dts;

kdDebug(5800) << "    Finished Building Cache, cache has " << dts.count() << " entries:" << endl;
// it = dts.begin();
// while ( it != dts.end() ) {
//   kdDebug(5800) << "            -=> " << (*it) << endl;
//   ++it;
// }
  if ( int(dts.count()) == mDuration ) {
    mCachedDateEnd = dts.last();
    return true;
  } else {
    mCachedDateEnd = TQDateTime();
    return false;
  }
}

bool RecurrenceRule::dateMatchesRules( const TQDateTime &qdt ) const
{
  bool match = false;
  for ( Constraint::List::ConstIterator it = mConstraints.begin();
        it!=mConstraints.end(); ++it ) {
    match = match || ( (*it).matches( qdt, recurrenceType() ) );
  }
  return match;
}

bool RecurrenceRule::recursOn( const TQDate &qd ) const
{
  int i, iend;
  if ( doesFloat() ) {
    // It's a date-only rule, so it has no time specification.
    if ( qd < mDateStart.date() ) {
      return false;
    }
    // Start date is only included if it really matches
    TQDate endDate;
    if ( mDuration >= 0 ) {
      endDate =  endDt().date();
      if ( qd > endDate ) {
        return false;
      }
    }

    // The date must be in an appropriate interval (getNextValidDateInterval),
    // Plus it must match at least one of the constraints
    bool match = false;
    for ( i = 0, iend = mConstraints.count();  i < iend && !match;  ++i ) {
      match = mConstraints[i].matches( qd, recurrenceType() );
    }
    if ( !match ) {
      return false;
    }

    TQDateTime start( qd, TQTime( 0, 0, 0 ) );
    Constraint interval( getNextValidDateInterval( start, recurrenceType() ) );
    // Constraint::matches is quite efficient, so first check if it can occur at
    // all before we calculate all actual dates.
    if ( !interval.matches( qd, recurrenceType() ) ) {
      return false;
    }
    // We really need to obtain the list of dates in this interval, since
    // otherwise BYSETPOS will not work (i.e. the date will match the interval,
    // but BYSETPOS selects only one of these matching dates!
    TQDateTime end = start.addDays(1);
    do {
      DateTimeList dts = datesForInterval( interval, recurrenceType() );
      for ( i = 0, iend = dts.count();  i < iend;  ++i ) {
        if ( dts[i].date() >= qd ) {
          return dts[i].date() == qd;
        }
      }
      interval.increase( recurrenceType(), frequency() );
    } while ( interval.intervalDateTime( recurrenceType() ) < end );
    return false;
  }

  // It's a date-time rule, so we need to take the time specification into account.
  TQDateTime start( qd, TQTime( 0, 0, 0 ) );
  TQDateTime end = start.addDays( 1 );
  if ( end < mDateStart ) {
    return false;
  }
  if ( start < mDateStart ) {
    start = mDateStart;
  }

  // Start date is only included if it really matches
  if ( mDuration >= 0 ) {
    TQDateTime endRecur = endDt();
    if ( endRecur.isValid() ) {
      if ( start > endRecur ) {
        return false;
      }
      if ( end > endRecur ) {
        end = endRecur;    // limit end-of-day time to end of recurrence rule
      }
    }
  }

  if ( mTimedRepetition ) {
    // It's a simple sub-daily recurrence with no constraints
    int n = static_cast<int>( ( mDateStart.secsTo( start ) - 1 ) % mTimedRepetition );
    return start.addSecs( mTimedRepetition - n ) < end;
  }

  // Find the start and end dates in the time spec for the rule
  TQDate startDay = start.date();
  TQDate endDay = end.addSecs( -1 ).date();
  int dayCount = startDay.daysTo( endDay ) + 1;

  // The date must be in an appropriate interval (getNextValidDateInterval),
  // Plus it must match at least one of the constraints
  bool match = false;
  for ( i = 0, iend = mConstraints.count();  i < iend && !match;  ++i ) {
    match = mConstraints[i].matches( startDay, recurrenceType() );
    for ( int day = 1;  day < dayCount && !match;  ++day ) {
      match = mConstraints[i].matches( startDay.addDays( day ), recurrenceType() );
    }
  }
  if ( !match ) {
    return false;
  }

  Constraint interval( getNextValidDateInterval( start, recurrenceType() ) );
  // Constraint::matches is quite efficient, so first check if it can occur at
  // all before we calculate all actual dates.
  match = false;
  Constraint intervalm = interval;
  do {
    match = intervalm.matches( startDay, recurrenceType() );
    for ( int day = 1;  day < dayCount && !match;  ++day ) {
      match = intervalm.matches( startDay.addDays( day ), recurrenceType() );
    }
    if ( match ) {
      break;
    }
    intervalm.increase( recurrenceType(), frequency() );
  } while ( intervalm.intervalDateTime( recurrenceType() ) < end );
  if ( !match ) {
    return false;
  }

  // We really need to obtain the list of dates in this interval, since
  // otherwise BYSETPOS will not work (i.e. the date will match the interval,
  // but BYSETPOS selects only one of these matching dates!
  do {
    DateTimeList dts = datesForInterval( interval, recurrenceType() );
    int i = findGE( dts, start, 0 );
    if ( i >= 0 ) {
      return dts[i] < end;
    }
    interval.increase( recurrenceType(), frequency() );
  } while ( interval.intervalDateTime( recurrenceType() ) < end );

  return false;
}

bool RecurrenceRule::recursAt( const TQDateTime &dt ) const
{
  if ( doesFloat() ) {
    return recursOn( dt.date() );
  }
  if ( dt < mDateStart ) {
    return false;
  }
  // Start date is only included if it really matches
  if ( mDuration >= 0 && dt > endDt() ) {
    return false;
  }

  if ( mTimedRepetition ) {
    // It's a simple sub-daily recurrence with no constraints
    return !( mDateStart.secsTo( dt ) % mTimedRepetition );
  }

  // The date must be in an appropriate interval (getNextValidDateInterval),
  // Plus it must match at least one of the constraints
  if ( !dateMatchesRules( dt ) ) {
    return false;
  }
  // if it recurs every interval, speed things up...
//   if ( d->mFrequency == 1 && d->mBySetPos.isEmpty() && d->mByDays.isEmpty() ) return true;
  Constraint interval( getNextValidDateInterval( dt, recurrenceType() ) );
  // TODO_Recurrence: Does this work with BySetPos???
  if ( interval.matches( dt, recurrenceType() ) ) {
    return true;
  }
  return false;
}

TimeList RecurrenceRule::recurTimesOn( const TQDate &date ) const
{
  TimeList lst;
  if ( doesFloat() ) {
    return lst;
  }
  TQDateTime start( date, TQTime( 0, 0, 0 ) );
  TQDateTime end = start.addDays( 1 ).addSecs( -1 );
  DateTimeList dts = timesInInterval( start, end );   // returns between start and end inclusive
  for ( int i = 0, iend = dts.count();  i < iend;  ++i ) {
    lst += dts[i].time();
  }
  return lst;
}

/** Returns the number of recurrences up to and including the date/time specified. */
int RecurrenceRule::durationTo( const TQDateTime &dt ) const
{
// kdDebug(5800) << "         RecurrenceRule::durationTo: " << dt << endl;
  // Easy cases: either before start, or after all recurrences and we know
  // their number
  if ( dt < startDt() ) return 0;
  // Start date is only included if it really matches
//   if ( dt == startDt() ) return 1;
  if ( mDuration > 0 && dt >= endDt() ) return mDuration;

  TQDateTime next( startDt() );
  int found = 0;
  while ( next.isValid() && next <= dt ) {
    ++found;
    next = getNextDate( next );
  }
  return found;
}


TQDateTime RecurrenceRule::getPreviousDate( const TQDateTime& afterDate ) const
{
// kdDebug(5800) << "         RecurrenceRule::getPreviousDate: " << afterDate << endl;
  // Beyond end of recurrence
  if ( afterDate < startDt() )
    return TQDateTime();

  // If we have a cache (duration given), use that
  TQDateTime prev;
  if ( mDuration > 0 ) {
    if ( !mCached ) buildCache();
    DateTimeList::ConstIterator it = mCachedDates.begin();
    while ( it != mCachedDates.end() && (*it) < afterDate ) {
      prev = *it;
      ++it;
    }
    if ( prev.isValid() && prev < afterDate ) return prev;
    else return TQDateTime();
  }

// kdDebug(5800) << "    getNext date after " << preDate << endl;
  prev = afterDate;
  if ( mDuration >= 0 && endDt().isValid() && afterDate > endDt() )
    prev = endDt().addSecs( 1 );

  Constraint interval( getPreviousValidDateInterval( prev, recurrenceType() ) );
// kdDebug(5800) << "Previous Valid Date Interval for date " << prev << ": " << endl;
// interval.dump();
  DateTimeList dts = datesForInterval( interval, recurrenceType() );
  DateTimeList::Iterator dtit = dts.end();
  if ( dtit != dts.begin() ) {
    do {
      --dtit;
    } while ( dtit != dts.begin() && (*dtit) >= prev );
    if ( (*dtit) < prev ) {
      if ( (*dtit) >= startDt() ) return (*dtit);
      else return TQDateTime();
    }
  }

  // Previous interval. As soon as we find an occurrence, we're done.
  while ( interval.intervalDateTime( recurrenceType() ) > startDt() ) {
    interval.increase( recurrenceType(), -frequency() );
// kdDebug(5800) << "Decreased interval: " << endl;
// interval.dump();
    // The returned date list is sorted
    DateTimeList dts = datesForInterval( interval, recurrenceType() );
    // The list is sorted, so take the last one.
    if ( dts.count() > 0 ) {
      prev = dts.last();
      if ( prev.isValid() && prev >= startDt() ) return prev;
      else return TQDateTime();
    }
  }
  return TQDateTime();
}


TQDateTime RecurrenceRule::getNextDate( const TQDateTime &preDate ) const
{
// kdDebug(5800) << "         RecurrenceRule::getNextDate: " << preDate << endl;
  // Beyond end of recurrence
  if ( mDuration >= 0 && endDt().isValid() && preDate >= endDt() )
    return TQDateTime();

  // Start date is only included if it really matches
  TQDateTime adjustedPreDate;
  if ( preDate < startDt() )
    adjustedPreDate = startDt().addSecs( -1 );
  else
    adjustedPreDate = preDate;

  if ( mDuration > 0 ) {
    if ( !mCached ) buildCache();
    DateTimeList::ConstIterator it = mCachedDates.begin();
    while ( it != mCachedDates.end() && (*it) <= adjustedPreDate ) ++it;
    if ( it != mCachedDates.end() ) {
//  kdDebug(5800) << "    getNext date after " << adjustedPreDate << ", cached date: " << *it << endl;
      return (*it);
    }
  }

// kdDebug(5800) << "    getNext date after " << adjustedPreDate << endl;
  Constraint interval( getNextValidDateInterval( adjustedPreDate, recurrenceType() ) );
  DateTimeList dts = datesForInterval( interval, recurrenceType() );
  DateTimeList::Iterator dtit = dts.begin();
  while ( dtit != dts.end() && (*dtit) <= adjustedPreDate ) ++dtit;
  if ( dtit != dts.end() ) {
    if ( mDuration >= 0 && (*dtit) > endDt() ) return TQDateTime();
    else return (*dtit);
  }

  // Increase the interval. The first occurrence that we find is the result (if
  // if's before the end date).
    // TODO: some validity checks to avoid infinite loops for contradictory constraints
  int loopnr = 0;
  while ( loopnr < 10000 ) {
    interval.increase( recurrenceType(), frequency() );
    DateTimeList dts = datesForInterval( interval, recurrenceType() );
    if ( dts.count() > 0 ) {
      TQDateTime ret( dts.first() );
      if ( mDuration >= 0 && ret > endDt() ) return TQDateTime();
      else return ret;
    }
    ++loopnr;
  }
  return TQDateTime();
}

DateTimeList RecurrenceRule::timesInInterval( const TQDateTime &dtStart,
                                              const TQDateTime &dtEnd ) const
{
  TQDateTime start = dtStart;
  TQDateTime end = dtEnd;
  DateTimeList result;
  if ( end < mDateStart ) {
    return result;    // before start of recurrence
  }
  TQDateTime enddt = end;
  if ( mDuration >= 0 ) {
    TQDateTime endRecur = endDt();
    if ( endRecur.isValid() ) {
      if ( start > endRecur ) {
        return result;    // beyond end of recurrence
      }
      if ( end > endRecur ) {
        enddt = endRecur;    // limit end time to end of recurrence rule
      }
    }
  }

  if ( mTimedRepetition ) {
    // It's a simple sub-daily recurrence with no constraints
    int n = static_cast<int>( ( mDateStart.secsTo( start ) - 1 ) % mTimedRepetition );
    TQDateTime dt = start.addSecs( mTimedRepetition - n );
    if ( dt < enddt ) {
      n = static_cast<int>( ( dt.secsTo( enddt ) - 1 ) / mTimedRepetition ) + 1;
      // limit n by a sane value else we can "explode".
      n = TQMIN( n, LOOP_LIMIT );
      for ( int i = 0;  i < n;  dt = dt.addSecs( mTimedRepetition ), ++i ) {
        result += dt;
      }
    }
    return result;
  }

  TQDateTime st = start;
  bool done = false;
  if ( mDuration > 0 ) {
    if ( !mCached ) {
      buildCache();
    }
    if ( mCachedDateEnd.isValid() && start > mCachedDateEnd ) {
      return result;    // beyond end of recurrence
    }
    int i = findGE( mCachedDates, start, 0 );
    if ( i >= 0 ) {
      int iend = findGT( mCachedDates, enddt, i );
      if ( iend < 0 ) {
        iend = mCachedDates.count();
      } else {
        done = true;
      }
      while ( i < iend ) {
        result += mCachedDates[i++];
      }
    }
    if ( mCachedDateEnd.isValid() ) {
      done = true;
    } else if ( !result.isEmpty() ) {
      result += TQDateTime();    // indicate that the returned list is incomplete
      done = true;
    }
    if ( done ) {
      return result;
    }
    // We don't have any result yet, but we reached the end of the incomplete cache
    st = mCachedLastDate.addSecs( 1 );
  }

  Constraint interval( getNextValidDateInterval( st, recurrenceType() ) );
  int loop = 0;
  do {
    DateTimeList dts = datesForInterval( interval, recurrenceType() );
    int i = 0;
    int iend = dts.count();
    if ( loop == 0 ) {
      i = findGE( dts, st, 0 );
      if ( i < 0 ) {
        i = iend;
      }
    }
    int j = findGT( dts, enddt, i );
    if ( j >= 0 ) {
      iend = j;
      loop = LOOP_LIMIT;
    }
    while ( i < iend ) {
      result += dts[i++];
    }
    // Increase the interval.
    interval.increase( recurrenceType(), frequency() );
  } while ( ++loop < LOOP_LIMIT &&
            interval.intervalDateTime( recurrenceType() ) < end );
  return result;
}

RecurrenceRule::Constraint RecurrenceRule::getPreviousValidDateInterval( const TQDateTime &preDate, PeriodType type ) const
{
// kdDebug(5800) << "       (o) getPreviousValidDateInterval after " << preDate << ", type=" << type << endl;
  long periods = 0;
  TQDateTime nextValid = startDt();
  TQDateTime start = startDt();
  int modifier = 1;
  TQDateTime toDate( preDate );
  // for super-daily recurrences, don't care about the time part

  // Find the #intervals since the dtstart and round to the next multiple of
  // the frequency
      // FIXME: All sub-daily periods need to convert to UTC, do the calculations
      //        in UTC, then convert back to the local time zone. Otherwise,
      //        recurrences across DST changes will be determined wrongly
  switch ( type ) {
    // Really fall through for sub-daily, since the calculations only differ
    // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
    case rHourly:   modifier *= 60;
    case rMinutely: modifier *= 60;
    case rSecondly:
        periods = ownSecsTo( start, toDate ) / modifier;
        // round it down to the next lower multiple of frequency():
        periods = ( periods / frequency() ) * frequency();
        nextValid = start.addSecs( modifier * periods );
        break;

    case rWeekly:
        toDate = toDate.addDays( -(7 + toDate.date().dayOfWeek() - mWeekStart) % 7 );
        start = start.addDays( -(7 + start.date().dayOfWeek() - mWeekStart) % 7 );
        modifier *= 7;
    case rDaily:
        periods = start.daysTo( toDate ) / modifier;
        // round it down to the next lower multiple of frequency():
        periods = ( periods / frequency() ) * frequency();
        nextValid = start.addDays( modifier * periods );
        break;

    case rMonthly: {
        periods = 12*( toDate.date().year() - start.date().year() ) +
             ( toDate.date().month() - start.date().month() );
        // round it down to the next lower multiple of frequency():
        periods = ( periods / frequency() ) * frequency();
        // set the day to the first day of the month, so we don't have problems
        // with non-existent days like Feb 30 or April 31
        start.setDate( TQDate( start.date().year(), start.date().month(), 1 ) );
        nextValid.setDate( start.date().addMonths( periods ) );
        break; }
    case rYearly:
        periods = ( toDate.date().year() - start.date().year() );
        // round it down to the next lower multiple of frequency():
        periods = ( periods / frequency() ) * frequency();
        nextValid.setDate( start.date().addYears( periods ) );
        break;
    default:
        break;
  }
// kdDebug(5800) << "    ~~~> date in previous interval is: : " << nextValid << endl;

  return Constraint( nextValid, type, mWeekStart );
}

RecurrenceRule::Constraint RecurrenceRule::getNextValidDateInterval( const TQDateTime &preDate, PeriodType type ) const
{
  // TODO: Simplify this!
  kdDebug(5800) << "       (o) getNextValidDateInterval after " << preDate << ", type=" << type << endl;
  long periods = 0;
  TQDateTime start = startDt();
  TQDateTime nextValid( start );
  int modifier = 1;
  TQDateTime toDate( preDate );
  // for super-daily recurrences, don't care about the time part

  // Find the #intervals since the dtstart and round to the next multiple of
  // the frequency
      // FIXME: All sub-daily periods need to convert to UTC, do the calculations
      //        in UTC, then convert back to the local time zone. Otherwise,
      //        recurrences across DST changes will be determined wrongly
  switch ( type ) {
    // Really fall through for sub-daily, since the calculations only differ
    // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
    case rHourly:   modifier *= 60;
    case rMinutely: modifier *= 60;
    case rSecondly:
        periods = ownSecsTo( start, toDate ) / modifier;
        periods = TQMAX( 0, periods);
        if ( periods > 0 )
          periods += ( frequency() - 1 - ( (periods - 1) % frequency() ) );
        nextValid = start.addSecs( modifier * periods );
        break;

    case rWeekly:
        // correct both start date and current date to start of week
        toDate = toDate.addDays( -(7 + toDate.date().dayOfWeek() - mWeekStart) % 7 );
        start = start.addDays( -(7 + start.date().dayOfWeek() - mWeekStart) % 7 );
        modifier *= 7;
    case rDaily:
        periods = start.daysTo( toDate ) / modifier;
        periods = TQMAX( 0, periods);
        if ( periods > 0 )
          periods += (frequency() - 1 - ( (periods - 1) % frequency() ) );
        nextValid = start.addDays( modifier * periods );
        break;

    case rMonthly: {
        periods = 12*( toDate.date().year() - start.date().year() ) +
             ( toDate.date().month() - start.date().month() );
        periods = TQMAX( 0, periods);
        if ( periods > 0 )
          periods += (frequency() - 1 - ( (periods - 1) % frequency() ) );
        // set the day to the first day of the month, so we don't have problems
        // with non-existent days like Feb 30 or April 31
        start.setDate( TQDate( start.date().year(), start.date().month(), 1 ) );
        nextValid.setDate( start.date().addMonths( periods ) );
        break; }
    case rYearly:
        periods = ( toDate.date().year() - start.date().year() );
        periods = TQMAX( 0, periods);
        if ( periods > 0 )
          periods += ( frequency() - 1 - ( (periods - 1) % frequency() ) );
        nextValid.setDate( start.date().addYears( periods ) );
        break;
    default:
        break;
  }
// kdDebug(5800) << "    ~~~> date in next interval is: : " << nextValid << endl;

  return Constraint( nextValid, type, mWeekStart );
}

bool RecurrenceRule::mergeIntervalConstraint( Constraint *merged,
          const Constraint &conit, const Constraint &interval ) const
{
  Constraint result( interval );

#define mergeConstraint( name, cmparison ) \
  if ( conit.name cmparison ) { \
    if ( !(result.name cmparison) || result.name == conit.name ) { \
      result.name = conit.name; \
    } else return false;\
  }

  mergeConstraint( year, > 0 );
  mergeConstraint( month, > 0 );
  mergeConstraint( day, != 0 );
  mergeConstraint( hour, >= 0 );
  mergeConstraint( minute, >= 0 );
  mergeConstraint( second, >= 0 );

  mergeConstraint( weekday, != 0 );
  mergeConstraint( weekdaynr, != 0 );
  mergeConstraint( weeknumber, != 0 );
  mergeConstraint( yearday, != 0 );

  #undef mergeConstraint
  if ( merged ) *merged = result;
  return true;
}


DateTimeList RecurrenceRule::datesForInterval( const Constraint &interval, PeriodType type ) const
{
  /* -) Loop through constraints,
     -) merge interval with each constraint
     -) if merged constraint is not consistent => ignore that constraint
     -) if complete => add that one date to the date list
     -) Loop through all missing fields => For each add the resulting
  */
// kdDebug(5800) << "         RecurrenceRule::datesForInterval: " << endl;
// interval.dump();
  DateTimeList lst;
  Constraint::List::ConstIterator conit = mConstraints.begin();
  for ( ; conit != mConstraints.end(); ++conit ) {
    Constraint merged;
    bool mergeok = mergeIntervalConstraint( &merged, *conit, interval );
    // If the information is incomplete, we can't use this constraint
    if ( merged.year <= 0 || merged.hour < 0 || merged.minute < 0 || merged.second < 0 )
      mergeok = false;
    if ( mergeok ) {
// kdDebug(5800) << "      -) merged constraint: " << endl;
// merged.dump();
      // We have a valid constraint, so get all datetimes that match it andd
      // append it to all date/times of this interval
      DateTimeList lstnew = merged.dateTimes( type );
      lst += lstnew;
    }
  }
  // Sort it so we can apply the BySetPos. Also some logic relies on this being sorted
  qSortUnique( lst );


/*if ( lst.isEmpty() ) {
  kdDebug(5800) << "         No Dates in Interval " << endl;
} else {
  kdDebug(5800) << "         Dates: " << endl;
  for ( DateTimeList::Iterator it = lst.begin(); it != lst.end(); ++it ) {
    kdDebug(5800)<< "              -) " << (*it).toString() << endl;
  }
  kdDebug(5800) << "       ---------------------" << endl;
}*/
  if ( !mBySetPos.isEmpty() ) {
    DateTimeList tmplst = lst;
    lst.clear();
    TQValueList<int>::ConstIterator it;
    for ( it = mBySetPos.begin(); it != mBySetPos.end(); ++it ) {
      int pos = *it;
      if ( pos > 0 ) --pos;
      if ( pos < 0 ) pos += tmplst.count();
      if ( pos >= 0 && uint(pos) < tmplst.count() ) {
        lst.append( tmplst[pos] );
      }
    }
    qSortUnique( lst );
  }

  return lst;
}


void RecurrenceRule::dump() const
{
#ifndef NDEBUG
  kdDebug(5800) << "RecurrenceRule::dump():" << endl;
  if ( !mRRule.isEmpty() )
    kdDebug(5800) << "   RRULE=" << mRRule << endl;
  kdDebug(5800) << "   Read-Only: " << isReadOnly() <<
                   ", dirty: " << mDirty << endl;

  kdDebug(5800) << "   Period type: " << recurrenceType() << ", frequency: " << frequency() << endl;
  kdDebug(5800) << "   #occurrences: " << duration() << endl;
  kdDebug(5800) << "   start date: " << startDt() <<", end date: " << endDt() << endl;


#define dumpByIntList(list,label) \
  if ( !list.isEmpty() ) {\
    TQStringList lst;\
    for ( TQValueList<int>::ConstIterator it = list.begin();\
          it != list.end(); ++it ) {\
      lst.append( TQString::number( *it ) );\
    }\
    kdDebug(5800) << "   " << label << lst.join(", ") << endl;\
  }
  dumpByIntList( mBySeconds,    "BySeconds:  " );
  dumpByIntList( mByMinutes,    "ByMinutes:  " );
  dumpByIntList( mByHours,      "ByHours:    " );
  if ( !mByDays.isEmpty() ) {
    TQStringList lst;
    for ( TQValueList<WDayPos>::ConstIterator it = mByDays.begin();
          it != mByDays.end(); ++it ) {
      lst.append( ( ((*it).pos()!=0) ? TQString::number( (*it).pos() ) : "" ) +
                   DateHelper::dayName( (*it).day() ) );
    }
    kdDebug(5800) << "   ByDays:     " << lst.join(", ") << endl;
  }
  dumpByIntList( mByMonthDays,  "ByMonthDays:" );
  dumpByIntList( mByYearDays,   "ByYearDays: " );
  dumpByIntList( mByWeekNumbers,"ByWeekNr:   " );
  dumpByIntList( mByMonths,     "ByMonths:   " );
  dumpByIntList( mBySetPos,     "BySetPos:   " );
  #undef dumpByIntList

  kdDebug(5800) << "   Week start: " << DateHelper::dayName( mWeekStart ) << endl;

  kdDebug(5800) << "   Constraints:" << endl;
  // dump constraints
  for ( Constraint::List::ConstIterator it = mConstraints.begin();
        it!=mConstraints.end(); ++it ) {
    (*it).dump();
  }
#endif
}

void RecurrenceRule::Constraint::dump() const
{
  kdDebug(5800) << "     ~> Y="<<year<<", M="<<month<<", D="<<day<<", H="<<hour<<", m="<<minute<<", S="<<second<<", wd="<<weekday<<",#wd="<<weekdaynr<<", #w="<<weeknumber<<", yd="<<yearday<<endl;
}
