#include <time.h>

#include <qshared.h>

#include <qtopia/timeconversion.h>

#include "otimezone.h"
#include "orecur.h"

struct ORecur::Data : public QShared {
    Data() : QShared() {
        type = ORecur::NoRepeat;
        freq = -1;
        days = 0;
        pos = 0;
        create = QDateTime::currentDateTime();
        hasEnd = FALSE;
        end = QDate::currentDate();
    }
    char days; // Q_UINT8 for 8 seven days;)
    ORecur::RepeatType type;
    int freq;
    int pos;
    bool hasEnd : 1;
    QDate end;
    QDateTime create;
    int rep;
    QString app;
    ExceptionList list;
    QDate start;
};


ORecur::ORecur() {
    data = new Data;
}

ORecur::ORecur( const QMap<int, QString>& map )
{
	ORecur();
	fromMap( map );
}


ORecur::ORecur( const ORecur& rec)
    : data( rec.data )
{
    data->ref();
}
ORecur::~ORecur() {
    if ( data->deref() ) {
        delete data;
        data = 0l;
    }
}
void ORecur::deref() {
    if ( data->deref() ) {
        delete data;
        data = 0l;
    }
}
bool ORecur::operator==( const ORecur& )const {
    return false;
}
ORecur &ORecur::operator=( const ORecur& re) {
    if ( *this == re ) return *this;

    re.data->ref();
    deref();
    data = re.data;

    return *this;
}
bool ORecur::doesRecur()const {
    return !( type() == NoRepeat );
}
/*
 * we try to be smart here
 *
 */
bool ORecur::doesRecur( const QDate& date ) {
    /* the day before the recurrance */
    QDate da = date.addDays(-1);

    QDate recur;
    if (!nextOcurrence( da, recur ) )
        return false;

    return (recur == date);
}
// FIXME unuglify!
// GPL from Datebookdb.cpp
// FIXME exception list!
bool ORecur::nextOcurrence( const QDate& from, QDate& next ) {
    bool stillLooking;
    stillLooking  = p_nextOccurrence( from, next );
    while ( stillLooking && data->list.contains(next) )
        stillLooking = p_nextOccurrence( next.addDays(1), next );

    return stillLooking;
}
bool ORecur::p_nextOccurrence( const QDate& from, QDate& next ) {

   // easy checks, first are we too far in the future or too far in the past?
    QDate tmpDate;
    int freq = frequency();
    int diff, diff2, a;
    int iday, imonth, iyear;
    int dayOfWeek = 0;
    int firstOfWeek = 0;
    int weekOfMonth;


    if (hasEndDate() && endDate() < from)
	return FALSE;

    if (start() >= from  ) {
	next = start();
	return TRUE;
    }

    switch ( type() ) {
	case Weekly:
	    /* weekly is just daily by 7 */
	    /* first convert the repeatPattern.Days() mask to the next
	       day of week valid after from */
            dayOfWeek = from.dayOfWeek();
	    dayOfWeek--; /* we want 0-6, doco for above specs 1-7 */

	    /* this is done in case freq > 1 and from in week not
	       for this round */
	    // firstOfWeek = 0; this is already done at decl.
	    while(!((1 << firstOfWeek) & days() ))
		firstOfWeek++;

	    /* there is at least one 'day', or there would be no event */
	    while(!((1 << (dayOfWeek % 7)) & days() ))
		dayOfWeek++;

	    dayOfWeek = dayOfWeek % 7; /* the actual day of week */
	    dayOfWeek -= start().dayOfWeek() -1;

	    firstOfWeek = firstOfWeek % 7; /* the actual first of week */
	    firstOfWeek -= start().dayOfWeek() -1;

	    // dayOfWeek may be negitive now
	    // day of week is number of days to add to start day

	    freq *= 7;
	    // FALL-THROUGH !!!!!
	case Daily:
	    // the add is for the possible fall through from weekly */
	    if(start().addDays(dayOfWeek) > from) {
		/* first week exception */
		next = QDate(start().addDays(dayOfWeek) );
		if ((next > endDate())
		    && hasEndDate() )
		    return FALSE;
		return TRUE;
	    }
	    /* if from is middle of a non-week */

	    diff = start().addDays(dayOfWeek).daysTo(from) % freq;
	    diff2 = start().addDays(firstOfWeek).daysTo(from) % freq;

	    if(diff != 0)
		diff = freq - diff;
	    if(diff2 != 0)
		diff2 = freq - diff2;
	    diff = QMIN(diff, diff2);

	    next = QDate(from.addDays(diff));
	    if ( (next > endDate())
		 && hasEndDate() )
		return FALSE;
	    return TRUE;
	case MonthlyDay:
	    iday = from.day();
	    iyear = from.year();
	    imonth = from.month();
	    /* find equivelent day of month for this month */
	    dayOfWeek = start().dayOfWeek();
	    weekOfMonth = (start().day() - 1) / 7;

	    /* work out when the next valid month is */
	    a = from.year() - start().year();
	    a *= 12;
	    a = a + (imonth - start().month());
	    /* a is e.start()monthsFrom(from); */
	    if(a % freq) {
		a = freq - (a % freq);
		imonth = from.month() + a;
		if (imonth > 12) {
		    imonth--;
		    iyear += imonth / 12;
		    imonth = imonth % 12;
		    imonth++;
		}
	    }
	    /* imonth is now the first month after or on
	       from that matches the frequency given */

	    /* find for this month */
	    tmpDate = QDate( iyear, imonth, 1 );

	    iday = 1;
	    iday += (7 + dayOfWeek - tmpDate.dayOfWeek()) % 7;
	    iday += 7 * weekOfMonth;
	    while (iday > tmpDate.daysInMonth()) {
		imonth += freq;
		if (imonth > 12) {
		    imonth--;
		    iyear += imonth / 12;
		    imonth = imonth % 12;
		    imonth++;
		}
		tmpDate = QDate( iyear, imonth, 1 );
		/* these loops could go for a while, check end case now */
		if ((tmpDate > endDate()) && hasEndDate() )
		    return FALSE;
		iday = 1;
		iday += (7 + dayOfWeek - tmpDate.dayOfWeek()) % 7;
		iday += 7 * weekOfMonth;
	    }
	    tmpDate = QDate(iyear, imonth, iday);

	    if (tmpDate >= from) {
		next = tmpDate;
		if ((next > endDate() ) && hasEndDate() )
		    return FALSE;
		return TRUE;
	    }

	    /* need to find the next iteration */
	    do {
		imonth += freq;
		if (imonth > 12) {
		    imonth--;
		    iyear += imonth / 12;
		    imonth = imonth % 12;
		    imonth++;
		}
		tmpDate = QDate( iyear, imonth, 1 );
		/* these loops could go for a while, check end case now */
		if ((tmpDate > endDate()) && hasEndDate() )
		    return FALSE;
		iday = 1;
		iday += (7 + dayOfWeek - tmpDate.dayOfWeek()) % 7;
		iday += 7 * weekOfMonth;
	    } while (iday > tmpDate.daysInMonth());
	    tmpDate = QDate(iyear, imonth, iday);

	    next = tmpDate;
	    if ((next > endDate()) && hasEndDate() )
		return FALSE;
	    return TRUE;
	case MonthlyDate:
	    iday = start().day();
	    iyear = from.year();
	    imonth = from.month();

	    a = from.year() - start().year();
	    a *= 12;
	    a = a + (imonth - start().month());
	    /* a is e.start()monthsFrom(from); */
	    if(a % freq) {
		a = freq - (a % freq);
		imonth = from.month() + a;
		if (imonth > 12) {
		    imonth--;
		    iyear += imonth / 12;
		    imonth = imonth % 12;
		    imonth++;
		}
	    }
	    /* imonth is now the first month after or on
	       from that matches the frequencey given */

	    /* this could go for a while, worse case, 4*12 iterations, probably */
	    while(!QDate::isValid(iyear, imonth, iday) ) {
		imonth += freq;
		if (imonth > 12) {
		    imonth--;
		    iyear += imonth / 12;
		    imonth = imonth % 12;
		    imonth++;
		}
		/* these loops could go for a while, check end case now */
		if ((QDate(iyear, imonth, 1) > endDate()) && hasEndDate() )
		    return FALSE;
	    }

	    if(QDate(iyear, imonth, iday) >= from) {
		/* done */
		next = QDate(iyear, imonth, iday);
		if ((next > endDate()) && hasEndDate() )
		    return FALSE;
		return TRUE;
	    }

	    /* ok, need to cycle */
	    imonth += freq;
	    imonth--;
	    iyear += imonth / 12;
	    imonth = imonth % 12;
	    imonth++;

	    while(!QDate::isValid(iyear, imonth, iday) ) {
		imonth += freq;
		imonth--;
		iyear += imonth / 12;
		imonth = imonth % 12;
		imonth++;
		if ((QDate(iyear, imonth, 1) > endDate()) && hasEndDate() )
		    return FALSE;
	    }

	    next = QDate(iyear, imonth, iday);
	    if ((next > endDate()) && hasEndDate() )
		return FALSE;
	    return TRUE;
	case Yearly:
	    iday = start().day();
	    imonth = start().month();
	    iyear = from.year(); // after all, we want to start in this year

	    diff = 1;
	    if(imonth == 2 && iday > 28) {
		/* leap year, and it counts, calculate actual frequency */
		if(freq % 4)
		    if (freq % 2)
			freq = freq * 4;
		    else
			freq = freq * 2;
		/* else divides by 4 already, leave freq alone */
		diff = 4;
	    }

	    a = from.year() - start().year();
	    if(a % freq) {
		a = freq - (a % freq);
		iyear = iyear + a;
	    }

	    /* under the assumption we won't hit one of the special not-leap years twice */
	    if(!QDate::isValid(iyear, imonth, iday)) {
		/* must have been skipping by leap years and hit one that wasn't, (e.g. 2100) */
		iyear += freq;
	    }

	    if(QDate(iyear, imonth, iday) >= from) {
		next = QDate(iyear, imonth, iday);

		if ((next > endDate()) && hasEndDate() )
		    return FALSE;
		return TRUE;
	    }
	    /* iyear == from.year(), need to advance again */
	    iyear += freq;
	    /* under the assumption we won't hit one of the special not-leap years twice */
	    if(!QDate::isValid(iyear, imonth, iday)) {
		/* must have been skipping by leap years and hit one that wasn't, (e.g. 2100) */
		iyear += freq;
	    }

	    next = QDate(iyear, imonth, iday);
	    if ((next > endDate()) && hasEndDate() )
		return FALSE;
	    return TRUE;
	default:
	    return FALSE;
    }
}
ORecur::RepeatType ORecur::type()const{
    return data->type;
}
int ORecur::frequency()const {
    return data->freq;
}
int ORecur::position()const {
    return data->pos;
}
char ORecur::days() const{
    return data->days;
}
bool ORecur::hasEndDate()const {
    return data->hasEnd;
}
QDate ORecur::endDate()const {
    return data->end;
}
QDate ORecur::start()const{
    return data->start;
}
QDateTime ORecur::createdDateTime()const {
    return data->create;
}
int ORecur::repetition()const {
    return data->rep;
}
QString ORecur::service()const {
    return data->app;
}
ORecur::ExceptionList& ORecur::exceptions() {
    return data->list;
}
void ORecur::setType( const RepeatType& z) {
    checkOrModify();
    data->type = z;
}
void ORecur::setFrequency( int freq ) {
    checkOrModify();
    data->freq = freq;
}
void ORecur::setPosition( int pos ) {
    checkOrModify();
    data->pos = pos;
}
void ORecur::setDays( char c ) {
    checkOrModify();
    data->days = c;
}
void ORecur::setEndDate( const QDate& dt) {
    checkOrModify();
    data->end = dt;
}
void ORecur::setCreatedDateTime( const QDateTime& t) {
    checkOrModify();
    data->create = t;
}
void ORecur::setHasEndDate( bool b) {
    checkOrModify();
    data->hasEnd = b;
}
void ORecur::setRepitition( int rep ) {
    checkOrModify();
    data->rep = rep;
}
void ORecur::setService( const QString& app ) {
    checkOrModify();
    data->app = app;
}
void ORecur::setStart( const QDate& dt ) {
    checkOrModify();
    data->start = dt;
}
void ORecur::checkOrModify() {
    if ( data->count !=  1 ) {
        data->deref();
        Data* d2 = new Data;
        d2->days = data->days;
        d2->type = data->type;
        d2->freq = data->freq;
        d2->pos  = data->pos;
        d2->hasEnd = data->hasEnd;
        d2->end  = data->end;
        d2->create = data->create;
        d2->rep = data->rep;
        d2->app = data->app;
        d2->list = data->list;
        d2->start = data->start;
        data = d2;
    }
}
QString ORecur::toString()const {
    QString buf;
    QMap<int, QString> recMap = toMap();

    buf += " rtype=\"";
    buf += recMap[ORecur::RType];
    buf += "\"";
    if (data->days > 0 )
	buf += " rweekdays=\"" + recMap[ORecur::RWeekdays] + "\"";
    if ( data->pos != 0 )
	buf += " rposition=\"" + recMap[ORecur::RPosition] + "\"";

    buf += " rfreq=\"" + recMap[ORecur::RFreq] + "\"";
    buf += " rhasenddate=\"" + recMap[ORecur::RHasEndDate]+ "\"";
    if ( data->hasEnd )
	buf += " enddt=\""
	       + recMap[ORecur::EndDate]
	       + "\"";
    buf += " created=\"" + recMap[ORecur::Created] + "\"";

    if ( data->list.isEmpty() ) return buf;
    buf += " exceptions=\"";
    buf += recMap[ORecur::Exceptions];
    buf += "\" ";

    return buf;
}

QString ORecur::rTypeString() const
{
	QString retString;
	switch ( data->type ) {
	case ORecur::Daily:
		retString = "Daily";
		break;
	case ORecur::Weekly:
		retString = "Weekly";
		break;
	case ORecur::MonthlyDay:
		retString = "MonthlyDay";
		break;
	case ORecur::MonthlyDate:
		retString = "MonthlyDate";
		break;
	case ORecur::Yearly:
		retString = "Yearly";
		break;
	default:
		retString = "NoRepeat";
		break;

	}

	return retString;
}

QMap<QString, ORecur::RepeatType> ORecur::rTypeValueConvertMap() const
{
	QMap<QString, RepeatType> convertMap;

	convertMap.insert( QString( "Daily" ), ORecur::Daily );
	convertMap.insert( QString( "Weekly" ), ORecur::Weekly );
	convertMap.insert( QString( "MonthlyDay" ), ORecur::MonthlyDay );
	convertMap.insert( QString( "MonthlyDate" ), ORecur::MonthlyDate );
	convertMap.insert( QString( "Yearly" ), ORecur::Yearly );
	convertMap.insert( QString( "NoRepeat" ), ORecur::NoRepeat );

	return convertMap;
}


QMap<int, QString> ORecur::toMap() const
{
	QMap<int, QString> retMap;
	
	retMap.insert( ORecur::RType, rTypeString() );
	retMap.insert( ORecur::RWeekdays, QString::number( static_cast<int>( data->days ) ) );
	retMap.insert( ORecur::RPosition, QString::number(data->pos ) );
	retMap.insert( ORecur::RFreq, QString::number( data->freq ) );
	retMap.insert( ORecur::RHasEndDate, QString::number( static_cast<int>( data->hasEnd ) ) );
	if( data -> hasEnd )
		retMap.insert( ORecur::EndDate, QString::number( OTimeZone::utc().fromUTCDateTime( QDateTime( data->end, QTime(12,0,0) ) ) ) );
	retMap.insert( ORecur::Created, QString::number( OTimeZone::utc().fromUTCDateTime( data->create ) ) );
	
	if ( data->list.isEmpty() ) return retMap;

	// save exceptions list here!!
	ExceptionList::ConstIterator it;
	ExceptionList list = data->list;
	QString exceptBuf;
	QDate date;
	for ( it = list.begin(); it != list.end(); ++it ) {
		date = (*it);
		if ( it != list.begin() ) exceptBuf += " ";
		
		exceptBuf += QCString().sprintf("%04d%02d%02d", date.year(), date.month(), date.day() );
	}

	retMap.insert( ORecur::Exceptions, exceptBuf );

	return retMap;
}

void ORecur::fromMap( const QMap<int, QString>& map )
{
	QMap<QString, RepeatType> repTypeMap = rTypeValueConvertMap(); 

	data -> type  = repTypeMap[ map [ORecur::RType] ];
	data -> days  = (char) map[ ORecur::RWeekdays ].toInt();
	data -> pos   = map[ ORecur::RPosition ].toInt();
	data -> freq = map[ ORecur::RFreq ].toInt();
	data -> hasEnd= map[ ORecur::RHasEndDate ].toInt() ? true : false;
	OTimeZone utc = OTimeZone::utc();
	if ( data -> hasEnd ){
		data -> end = utc.fromUTCDateTime( (time_t) map[ ORecur::EndDate ].toLong() ).date();
	}
	data -> create = utc.fromUTCDateTime( (time_t) map[ ORecur::Created ].toLong() ).date();

#if 0
	// FIXME: Exceptions currently not supported...
	// Convert the list of exceptions from QString into ExceptionList
	data -> list.clear();
	QString exceptStr = map[ ORecur::Exceptions ];
	QStringList exceptList = QStringList::split( " ", exceptStr );
	...
#endif
	
	
}