/*
    This file is part of libkcal.
    Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>

    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., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.
*/

#include <kglobal.h>
#include <klocale.h>
#include <kdebug.h>

#include "calformat.h"

#include "incidence.h"
#include "todo.h"

using namespace KCal;

Incidence::Incidence() :
  IncidenceBase(),
  mRelatedTo(0), mSecrecy(SecrecyPublic), mPriority(3)
{
  mRecurrence = new Recurrence(this);
  mCancelled = false;
  recreate();
  mHasStartDate = true;
  mAlarms.setAutoDelete(true);
  mAttachments.setAutoDelete(true);
  mHasRecurrenceID = false;
}

Incidence::Incidence( const Incidence &i ) : IncidenceBase( i )
{
// TODO: reenable attributes currently commented out.
  mRevision = i.mRevision;
  mCreated = i.mCreated;
  mDescription = i.mDescription;
  mSummary = i.mSummary;
  mCategories = i.mCategories;
//  Incidence *mRelatedTo;          Incidence *mRelatedTo;
  mRelatedTo = 0;
  mRelatedToUid = i.mRelatedToUid;
//  QPtrList<Incidence> mRelations;    QPtrList<Incidence> mRelations;
  mExDates = i.mExDates;
  mAttachments = i.mAttachments;
  mResources = i.mResources;
  mSecrecy = i.mSecrecy;
  mPriority = i.mPriority;
  mLocation = i.mLocation;
  mCancelled = i.mCancelled;
  mHasStartDate = i.mHasStartDate;
  QPtrListIterator<Alarm> it( i.mAlarms );
  const Alarm *a;
  while( (a = it.current()) ) {
    Alarm *b = new Alarm( *a );
    b->setParent( this );
    mAlarms.append( b );

    ++it;
  }
  mAlarms.setAutoDelete(true);
  mHasRecurrenceID = i.mHasRecurrenceID;
  mRecurrenceID = i.mRecurrenceID;
  mRecurrence = new Recurrence( *(i.mRecurrence), this );
}

Incidence::~Incidence()
{
   
  Incidence *ev;
   QPtrList<Incidence> Relations = relations();
  for (ev=Relations.first();ev;ev=Relations.next()) {
    if (ev->relatedTo() == this) ev->setRelatedTo(0);
  } 
  if (relatedTo()) relatedTo()->removeRelation(this);
  delete mRecurrence; 
 
}
bool Incidence::hasRecurrenceID() const
{
    return mHasRecurrenceID;
}

void Incidence::setHasRecurrenceID( bool b )
{
    mHasRecurrenceID = b;
}
   
void Incidence::setRecurrenceID(QDateTime d)
{
    mRecurrenceID = d;
    mHasRecurrenceID = true;
    updated(); 
}
QDateTime Incidence::recurrenceID () const
{
    return mRecurrenceID;
}

bool Incidence::cancelled() const
{
    return mCancelled;
}
void Incidence::setCancelled( bool b )
{
    mCancelled = b;
    updated();
}
bool Incidence::hasStartDate() const
{
  return mHasStartDate;
}

void Incidence::setHasStartDate(bool f)
{
  if (mReadOnly) return;
  mHasStartDate = f;
  updated();
}

// A string comparison that considers that null and empty are the same
static bool stringCompare( const QString& s1, const QString& s2 )
{
    if ( s1.isEmpty() && s2.isEmpty() )
        return true;
    return s1 == s2;
}

bool KCal::operator==( const Incidence& i1, const Incidence& i2 )
{
  
    if( i1.alarms().count() != i2.alarms().count() ) {
        return false; // no need to check further
    }
    if ( i1.alarms().count() > 0 ) {
        if ( !( *(i1.alarms().first()) == *(i2.alarms().first())) ) 
            {
            qDebug("alarm not equal ");
            return false;
        }
    }
#if 0
    QPtrListIterator<Alarm> a1( i1.alarms() );
    QPtrListIterator<Alarm> a2( i2.alarms() );
    for( ; a1.current() && a2.current(); ++a1, ++a2 ) {
        if( *a1.current() == *a2.current() ) {
            continue;
        }
        else {
            return false;
        }
    }
#endif
  
    if ( i1.hasRecurrenceID() == i2.hasRecurrenceID() ) {
        if ( i1.hasRecurrenceID() ) {
            if ( i1.recurrenceID() != i2.recurrenceID() )
                return false;
        }

    } else {
        return false;
    }

    if ( ! operator==( (const IncidenceBase&)i1, (const IncidenceBase&)i2 ) )
        return false;
    if ( i1.hasStartDate() == i2.hasStartDate() ) {
        if ( i1.hasStartDate() ) {
            if ( i1.dtStart() != i2.dtStart() )
                return false;
        }
    } else {
        return false;
    }
    if (!( *i1.recurrence() == *i2.recurrence()) ) {
        qDebug("recurrence is NOT equal ");
        return false;
    }
    return
        //  i1.created() == i2.created() &&
        stringCompare( i1.description(), i2.description() ) &&
        stringCompare( i1.summary(), i2.summary() ) &&
        i1.categories() == i2.categories() &&
        // no need to compare mRelatedTo
        stringCompare( i1.relatedToUid(), i2.relatedToUid() ) &&
        //      i1.relations() == i2.relations() &&
        i1.exDates() == i2.exDates() &&
        i1.attachments() == i2.attachments() &&
        i1.resources() == i2.resources() &&
        i1.secrecy() == i2.secrecy() &&
        i1.priority() == i2.priority() &&
        i1.cancelled() == i2.cancelled() &&
        stringCompare( i1.location(), i2.location() );
}

Incidence* Incidence::recreateCloneException( QDate d  )
{
    Incidence* newInc = clone();
    newInc->recreate();
    if ( doesRecur() ) {
        addExDate( d );
        newInc->recurrence()->unsetRecurs();
        if ( type() == "Event") {
            int len = dtStart().secsTo( ((Event*)this)->dtEnd());
            QTime tim = dtStart().time();
            newInc->setDtStart( QDateTime(d, tim) );
            ((Event*)newInc)->setDtEnd( newInc->dtStart().addSecs( len ) );
        } else {
            int len = dtStart().secsTo( ((Todo*)this)->dtDue());
            QTime tim = ((Todo*)this)->dtDue().time();
            ((Todo*)newInc)->setDtDue( QDateTime(d, tim) );
            ((Todo*)newInc)->setDtStart( ((Todo*)newInc)->dtDue().addSecs( -len ) );
            ((Todo*)this)->setRecurDates();
        }
    }
    return newInc;
}

void Incidence::recreate()
{
  setCreated(QDateTime::currentDateTime());

  setUid(CalFormat::createUniqueId());

  setRevision(0);
  setIDStr( ":" );
  setLastModified(QDateTime::currentDateTime());
}

void Incidence::setReadOnly( bool readOnly )
{
  IncidenceBase::setReadOnly( readOnly );
  recurrence()->setRecurReadOnly( readOnly);
}

void Incidence::setCreated(QDateTime created)
{
  if (mReadOnly) return;
  mCreated = getEvenTime(created);
}

QDateTime Incidence::created() const
{
  return mCreated;
}

void Incidence::setRevision(int rev)
{
  if (mReadOnly) return;
  mRevision = rev;

  updated();
}

int Incidence::revision() const
{
  return mRevision;
}

void Incidence::setDtStart(const QDateTime &dtStart)
{

  QDateTime  dt = getEvenTime(dtStart);
  recurrence()->setRecurStart( dt);
  IncidenceBase::setDtStart( dt );
}

void Incidence::setDescription(const QString &description)
{
  if (mReadOnly) return;
  mDescription = description;
  updated();
}

QString Incidence::description() const
{
  return mDescription;
}


void Incidence::setSummary(const QString &summary)
{
  if (mReadOnly) return;
  mSummary = summary;
  updated();
}

QString Incidence::summary() const
{
  return mSummary;
}

void Incidence::setCategories(const QStringList &categories)
{
  if (mReadOnly) return;
  mCategories = categories;
  updated();
}

// TODO: remove setCategories(QString) function
void Incidence::setCategories(const QString &catStr)
{
  if (mReadOnly) return;
  mCategories.clear();

  if (catStr.isEmpty()) return;

  mCategories = QStringList::split(",",catStr);

  QStringList::Iterator it;
  for(it = mCategories.begin();it != mCategories.end(); ++it) {
    *it = (*it).stripWhiteSpace();
  }

  updated();
}

QStringList Incidence::categories() const
{
  return mCategories;
}

QString Incidence::categoriesStr()
{
  return mCategories.join(",");
}

void Incidence::setRelatedToUid(const QString &relatedToUid)
{
  if (mReadOnly) return;
  mRelatedToUid = relatedToUid;
}

QString Incidence::relatedToUid() const
{
  return mRelatedToUid;
}

void Incidence::setRelatedTo(Incidence *relatedTo)
{
    //qDebug("Incidence::setRelatedTo %d ", relatedTo);
    //qDebug("setRelatedTo(Incidence *relatedTo) %s %s", summary().latin1(), relatedTo->summary().latin1() );
  if (mReadOnly || mRelatedTo == relatedTo) return;
  if(mRelatedTo) {
      // updated();
    mRelatedTo->removeRelation(this);
  }
  mRelatedTo = relatedTo;
  if (mRelatedTo) mRelatedTo->addRelation(this);
}

Incidence *Incidence::relatedTo() const
{
  return mRelatedTo;
}

QPtrList<Incidence> Incidence::relations() const
{
  return mRelations;
}

void Incidence::addRelation(Incidence *event)
{
  if( mRelations.findRef( event ) == -1 ) {
    mRelations.append(event);
    //updated();
  }
}

void Incidence::removeRelation(Incidence *event)
{
  
  mRelations.removeRef(event); 
 
//  if (event->getRelatedTo() == this) event->setRelatedTo(0);
}

bool Incidence::recursOn(const QDate &qd) const
{
  if (recurrence()->recursOnPure(qd) && !isException(qd)) return true;
  else return false;
}

void Incidence::setExDates(const DateList &exDates)
{
  if (mReadOnly) return;
  mExDates = exDates;

  recurrence()->setRecurExDatesCount(mExDates.count());

  updated();
}

void Incidence::addExDate(const QDate &date)
{
  if (mReadOnly) return;
  mExDates.append(date);

  recurrence()->setRecurExDatesCount(mExDates.count());

  updated();
}

DateList Incidence::exDates() const
{
  return mExDates;
}

bool Incidence::isException(const QDate &date) const
{
  DateList::ConstIterator it;
  for( it = mExDates.begin(); it != mExDates.end(); ++it ) {
    if ( (*it) == date ) {
      return true;
    }
  }

  return false;
}

void Incidence::addAttachment(Attachment *attachment)
{
  if (mReadOnly || !attachment) return;
  mAttachments.append(attachment);
  updated();
}

void Incidence::deleteAttachment(Attachment *attachment)
{
  mAttachments.removeRef(attachment);
}

void Incidence::deleteAttachments(const QString& mime)
{
  Attachment *at = mAttachments.first();
  while (at) {
    if (at->mimeType() == mime)
      mAttachments.remove();
    else
      at = mAttachments.next();
  }
}

QPtrList<Attachment> Incidence::attachments() const
{
  return mAttachments;
}

QPtrList<Attachment> Incidence::attachments(const QString& mime) const
{
  QPtrList<Attachment> attachments;
  QPtrListIterator<Attachment> it( mAttachments );
  Attachment *at;
  while ( (at = it.current()) ) {
    if (at->mimeType() == mime)
      attachments.append(at);
    ++it;
  }

  return attachments;
}

void Incidence::setResources(const QStringList &resources)
{
  if (mReadOnly) return;
  mResources = resources;
  updated();
}

QStringList Incidence::resources() const
{
  return mResources;
}


void Incidence::setPriority(int priority)
{
  if (mReadOnly) return;
  mPriority = priority;
  updated();
}

int Incidence::priority() const
{
  return mPriority;
}

void Incidence::setSecrecy(int sec)
{
  if (mReadOnly) return;
  mSecrecy = sec;
  updated();
}

int Incidence::secrecy() const
{
  return mSecrecy;
}

QString Incidence::secrecyStr() const
{
  return secrecyName(mSecrecy);
}

QString Incidence::secrecyName(int secrecy)
{
  switch (secrecy) {
    case SecrecyPublic:
      return i18n("Public");
      break;
    case SecrecyPrivate:
      return i18n("Private");
      break;
    case SecrecyConfidential:
      return i18n("Confidential");
      break;
    default:
      return i18n("Undefined");
      break;
  }
}

QStringList Incidence::secrecyList()
{
  QStringList list;
  list << secrecyName(SecrecyPublic);
  list << secrecyName(SecrecyPrivate);
  list << secrecyName(SecrecyConfidential);

  return list;
}


QPtrList<Alarm> Incidence::alarms() const
{
  return mAlarms;
}

Alarm* Incidence::newAlarm()
{
  Alarm* alarm = new Alarm(this);
  mAlarms.append(alarm);
//  updated();
  return alarm;
}

void Incidence::addAlarm(Alarm *alarm)
{
  mAlarms.append(alarm);
  updated();
}

void Incidence::removeAlarm(Alarm *alarm)
{
  mAlarms.removeRef(alarm);
  updated();
}

void Incidence::clearAlarms()
{
  mAlarms.clear();
  updated();
}

bool Incidence::isAlarmEnabled() const
{
  Alarm* alarm;
  for (QPtrListIterator<Alarm> it(mAlarms); (alarm = it.current()) != 0; ++it) {
    if (alarm->enabled())
      return true;
  }
  return false;
}

Recurrence *Incidence::recurrence() const
{
  return mRecurrence;
}
void Incidence::setRecurrence( Recurrence * r) 
{
  delete mRecurrence;
  mRecurrence = r;
}

void Incidence::setLocation(const QString &location)
{
  if (mReadOnly) return;
  mLocation = location;
  updated();
}

QString Incidence::location() const
{
  return mLocation;
}

ushort Incidence::doesRecur() const
{
  if ( mRecurrence ) return mRecurrence->doesRecur();
  else return Recurrence::rNone;
}

QDateTime Incidence::getNextOccurence( const QDateTime& dt, bool* ok ) const
{
    QDateTime incidenceStart = dt;
    *ok = false;
    if ( doesRecur() ) {
        bool last;
         recurrence()->getPreviousDateTime( incidenceStart , &last );
         int count = 0;
         if ( !last ) {
             while ( !last ) {
                 ++count;
                 incidenceStart = recurrence()->getNextDateTime( incidenceStart, &last );
                 if ( recursOn( incidenceStart.date() ) ) {
                     last = true; // exit while llop
                 } else {
                     if ( last ) { // no alarm on last recurrence
                         return QDateTime ();
                     }
                     int year = incidenceStart.date().year();
                     // workaround for bug in recurrence
                     if ( count == 100 || year < 1000 || year > 5000 ) {
                         return QDateTime ();
                     }
                     incidenceStart = incidenceStart.addSecs( 1 );
                 }
             }
         } else {
             return QDateTime ();
         }
    } else {
        if ( hasStartDate () ) {
            incidenceStart = dtStart(); 
        }
        if ( type() =="Todo" ) {
            if ( ((Todo*)this)->hasDueDate() )
                incidenceStart = ((Todo*)this)->dtDue();
        }
    }
    if ( incidenceStart > dt )
        *ok = true;
    return incidenceStart;
}
QDateTime Incidence::dtStart() const
{
    if ( doesRecur() ) {
        if ( type() == "Todo" ) {
            ((Todo*)this)->checkSetCompletedFalse();
        }
    }
    return mDtStart;
}