/*
  This file is part of the kcal library.

  Copyright (c) 1998 Preston Brown <pbrown@kde.org>
  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., 51 Franklin Street, Fifth Floor,
  Boston, MA 02110-1301, USA.
*/
/**
  @file
  This file is part of the API for handling calendar data and
  defines the VCalFormat base class.

  This class implements the vCalendar format. It provides methods for
  loading/saving/converting vCalendar format data into the internal
  representation as Calendar and Incidences.

  @brief
  vCalendar format implementation.

  @author Preston Brown \<pbrown@kde.org\>
  @author Cornelius Schumacher \<schumacher@kde.org\>
*/

#include "vcalformat.h"
#include "calendar.h"
#include "versit/vcc.h"
#include "versit/vobject.h"

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

#include <QtCore/QString>
#include <QtCore/QRegExp>
#include <QtCore/QFile>
#include <QtCore/QByteArray>
#include <QtGui/QTextDocument>

using namespace KCal;

/**
  Private class that helps to provide binary compatibility between releases.
  @internal
*/
//@cond PRIVATE
class KCal::VCalFormat::Private
{
  public:
    Calendar *mCalendar;
    Event::List mEventsRelate;  // Events with relations
    Todo::List mTodosRelate;    // To-dos with relations
};
//@endcond

VCalFormat::VCalFormat() : d( new KCal::VCalFormat::Private )
{
}

VCalFormat::~VCalFormat()
{
  delete d;
}

bool VCalFormat::load( Calendar *calendar, const QString &fileName )
{
  d->mCalendar = calendar;

  clearException();

  kDebug() << fileName;

  VObject *vcal = 0;

  // this is not necessarily only 1 vcal.  Could be many vcals, or include
  // a vcard...
  vcal = Parse_MIME_FromFileName( const_cast<char *>( QFile::encodeName( fileName ).data() ) );

  if ( !vcal ) {
    setException( new ErrorFormat( ErrorFormat::CalVersionUnknown ) );
    return false;
  }

  // any other top-level calendar stuff should be added/initialized here

  // put all vobjects into their proper places
  populate( vcal );

  // clean up from vcal API stuff
  cleanVObjects( vcal );
  cleanStrTbl();

  return true;
}

bool VCalFormat::save( Calendar *calendar, const QString &fileName )
{
  d->mCalendar = calendar;

  QString tmpStr;
  VObject *vcal, *vo;

  kDebug() << fileName;

  vcal = newVObject( VCCalProp );

  //  addPropValue(vcal,VCLocationProp, "0.0");
  addPropValue( vcal, VCProdIdProp, productId().toLatin1() );
  addPropValue( vcal, VCVersionProp, _VCAL_VERSION );

  // TODO STUFF
  Todo::List todoList = d->mCalendar->rawTodos();
  Todo::List::ConstIterator it;
  for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
    vo = eventToVTodo( *it );
    addVObjectProp( vcal, vo );
  }

  // EVENT STUFF
  Event::List events = d->mCalendar->rawEvents();
  Event::List::ConstIterator it2;
  for ( it2 = events.constBegin(); it2 != events.constEnd(); ++it2 ) {
    vo = eventToVEvent( *it2 );
    addVObjectProp( vcal, vo );
  }

  writeVObjectToFile( QFile::encodeName( fileName ).data(), vcal );
  cleanVObjects( vcal );
  cleanStrTbl();

  if ( QFile::exists( fileName ) ) {
    return true;
  } else {
    return false; // error
  }

  return false;
}

bool VCalFormat::fromString( Calendar *calendar, const QString &text )
{
  return fromRawString( calendar, text.toUtf8() );
}

bool VCalFormat::fromRawString( Calendar *calendar, const QByteArray &string )
{
  d->mCalendar = calendar;

  if ( !string.size() ) {
    return false;
  }

  VObject *vcal = Parse_MIME( string.data(), string.size() );
  if ( !vcal ) {
    return false;
  }

  VObjectIterator i;
  VObject *curvo;
  initPropIterator( &i, vcal );

  // we only take the first object. TODO: parse all incidences.
  do  {
    curvo = nextVObject( &i );
  } while ( strcmp( vObjectName( curvo ), VCEventProp ) &&
            strcmp( vObjectName( curvo ), VCTodoProp ) );

  if ( strcmp( vObjectName( curvo ), VCEventProp ) == 0 ) {
    Event *event = VEventToEvent( curvo );
    calendar->addEvent( event );
  } else {
    kDebug() << "Unknown object type.";
    deleteVObject( vcal );
    return false;
  }

  deleteVObject( vcal );

  return true;
}

QString VCalFormat::toString( Calendar *calendar )
{
  // TODO: Factor out VCalFormat::asString()
  d->mCalendar = calendar;

  VObject *vcal = newVObject( VCCalProp );

  addPropValue( vcal, VCProdIdProp, CalFormat::productId().toLatin1() );
  addPropValue( vcal, VCVersionProp, _VCAL_VERSION );

  // TODO: Use all data.
  Event::List events = calendar->events();
  if( events.isEmpty() ) {
     cleanVObject ( vcal );
     return QString();
  }
  Event *event = events.first();
  if ( !event ) {
    cleanVObject ( vcal );
    return QString();
  }
  VObject *vevent = eventToVEvent( event );

  addVObjectProp( vcal, vevent );

  char *buf = writeMemVObject( 0, 0, vcal );

  QString result( buf );

  cleanVObject( vcal );

  return result;
}

VObject *VCalFormat::eventToVTodo( const Todo *anEvent )
{
  VObject *vtodo;
  QString tmpStr;

  vtodo = newVObject( VCTodoProp );

  // due date
  if ( anEvent->hasDueDate() ) {
    tmpStr = kDateTimeToISO( anEvent->dtDue(), !anEvent->allDay() );
    addPropValue( vtodo, VCDueProp, tmpStr.toLocal8Bit() );
  }

  // start date
  if ( anEvent->hasStartDate() ) {
    tmpStr = kDateTimeToISO( anEvent->dtStart(), !anEvent->allDay() );
    addPropValue( vtodo, VCDTstartProp, tmpStr.toLocal8Bit() );
  }

  // creation date
  tmpStr = kDateTimeToISO( anEvent->created() );
  addPropValue( vtodo, VCDCreatedProp, tmpStr.toLocal8Bit() );

  // unique id
  addPropValue( vtodo, VCUniqueStringProp,
                anEvent->uid().toLocal8Bit() );

  // revision
  tmpStr.sprintf( "%i", anEvent->revision() );
  addPropValue( vtodo, VCSequenceProp, tmpStr.toLocal8Bit() );

  // last modification date
  tmpStr = kDateTimeToISO( anEvent->lastModified() );
  addPropValue( vtodo, VCLastModifiedProp, tmpStr.toLocal8Bit() );

  // organizer stuff
  // @TODO: How about the common name?
  if ( !anEvent->organizer().email().isEmpty() ) {
    tmpStr = "MAILTO:" + anEvent->organizer().email();
    addPropValue( vtodo, ICOrganizerProp, tmpStr.toLocal8Bit() );
  }

  // attendees
  if ( anEvent->attendeeCount() > 0 ) {
    Attendee::List::ConstIterator it;
    Attendee *curAttendee;
    for ( it = anEvent->attendees().begin(); it != anEvent->attendees().end();
          ++it ) {
      curAttendee = *it;
      if ( !curAttendee->email().isEmpty() &&
           !curAttendee->name().isEmpty() ) {
        tmpStr = "MAILTO:" + curAttendee->name() + " <" + curAttendee->email() + '>';
      } else if ( curAttendee->name().isEmpty() ) {
        tmpStr = "MAILTO: " + curAttendee->email();
      } else if ( curAttendee->email().isEmpty() ) {
        tmpStr = "MAILTO: " + curAttendee->name();
      } else if ( curAttendee->name().isEmpty() && curAttendee->email().isEmpty() ) {
        kDebug() << "warning! this Event has an attendee w/o name or email!";
      }
      VObject *aProp = addPropValue( vtodo, VCAttendeeProp, tmpStr.toLocal8Bit() );
      addPropValue( aProp, VCRSVPProp, curAttendee->RSVP() ? "TRUE" : "FALSE" );
      addPropValue( aProp, VCStatusProp, writeStatus( curAttendee->status() ) );
    }
  }

  // description BL:
  if ( !anEvent->description().isEmpty() ) {
    VObject *d = addPropValue( vtodo, VCDescriptionProp,
                               anEvent->description().toLocal8Bit() );
    if ( anEvent->description().indexOf( '\n' ) != -1 ) {
      addPropValue( d, VCEncodingProp, VCQuotedPrintableProp );
    }
  }

  // summary
  if ( !anEvent->summary().isEmpty() ) {
    addPropValue( vtodo, VCSummaryProp, anEvent->summary().toLocal8Bit() );
  }

  // location
  if ( !anEvent->location().isEmpty() ) {
    addPropValue( vtodo, VCLocationProp, anEvent->location().toLocal8Bit() );
  }

  // completed status
  // backward compatibility, KOrganizer used to interpret only these two values
  addPropValue( vtodo, VCStatusProp, anEvent->isCompleted() ? "COMPLETED" : "NEEDS_ACTION" );

  // completion date
  if ( anEvent->hasCompletedDate() ) {
    tmpStr = kDateTimeToISO( anEvent->completed() );
    addPropValue( vtodo, VCCompletedProp, tmpStr.toLocal8Bit() );
  }

  // priority
  tmpStr.sprintf( "%i", anEvent->priority() );
  addPropValue( vtodo, VCPriorityProp, tmpStr.toLocal8Bit() );

  // related event
  if ( anEvent->relatedTo() ) {
    addPropValue( vtodo, VCRelatedToProp,
                  anEvent->relatedTo()->uid().toLocal8Bit() );
  }

  // categories
  const QStringList tmpStrList = anEvent->categories();
  tmpStr = "";
  QString catStr;
  QStringList::const_iterator its;
  for ( its = tmpStrList.constBegin(); its != tmpStrList.constEnd(); ++its ) {
    catStr = *its;
    if ( catStr[0] == ' ' ) {
      tmpStr += catStr.mid( 1 );
    } else {
      tmpStr += catStr;
    }
    // this must be a ';' character as the vCalendar specification requires!
    // vcc.y has been hacked to translate the ';' to a ',' when the vcal is
    // read in.
    tmpStr += ';';
  }
  if ( !tmpStr.isEmpty() ) {
    tmpStr.truncate( tmpStr.length() - 1 );
    addPropValue( vtodo, VCCategoriesProp, tmpStr.toLocal8Bit() );
  }

  // alarm stuff
  Alarm::List::ConstIterator it;
  for ( it = anEvent->alarms().begin(); it != anEvent->alarms().end(); ++it ) {
    Alarm *alarm = *it;
    if ( alarm->enabled() ) {
      VObject *a = addProp( vtodo, VCDAlarmProp );
      tmpStr = kDateTimeToISO( alarm->time() );
      addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
      addPropValue( a, VCRepeatCountProp, "1" );
      addPropValue( a, VCDisplayStringProp, "beep!" );
      if ( alarm->type() == Alarm::Audio ) {
        a = addProp( vtodo, VCAAlarmProp );
        addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
        addPropValue( a, VCRepeatCountProp, "1" );
        addPropValue( a, VCAudioContentProp, QFile::encodeName( alarm->audioFile() ) );
      } else if ( alarm->type() == Alarm::Procedure ) {
        a = addProp( vtodo, VCPAlarmProp );
        addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
        addPropValue( a, VCRepeatCountProp, "1" );
        addPropValue( a, VCProcedureNameProp, QFile::encodeName( alarm->programFile() ) );
      }
    }
  }

  QString pilotId = anEvent->nonKDECustomProperty( KPilotIdProp );
  if ( !pilotId.isEmpty() ) {
    // pilot sync stuff
    addPropValue( vtodo, KPilotIdProp, pilotId.toLocal8Bit() );
    addPropValue( vtodo, KPilotStatusProp,
                  anEvent->nonKDECustomProperty( KPilotStatusProp ).toLocal8Bit() );
  }

  return vtodo;
}

VObject *VCalFormat::eventToVEvent( const Event *anEvent )
{
  VObject *vevent;
  QString tmpStr;

  vevent = newVObject( VCEventProp );

  // start and end time
  tmpStr = kDateTimeToISO( anEvent->dtStart(), !anEvent->allDay() );
  addPropValue( vevent, VCDTstartProp, tmpStr.toLocal8Bit() );

  // events that have time associated but take up no time should
  // not have both DTSTART and DTEND.
  if ( anEvent->dtStart() != anEvent->dtEnd() ) {
    tmpStr = kDateTimeToISO( anEvent->dtEnd(), !anEvent->allDay() );
    addPropValue( vevent, VCDTendProp, tmpStr.toLocal8Bit() );
  }

  // creation date
  tmpStr = kDateTimeToISO( anEvent->created() );
  addPropValue( vevent, VCDCreatedProp, tmpStr.toLocal8Bit() );

  // unique id
  addPropValue( vevent, VCUniqueStringProp,
                anEvent->uid().toLocal8Bit() );

  // revision
  tmpStr.sprintf( "%i", anEvent->revision() );
  addPropValue( vevent, VCSequenceProp, tmpStr.toLocal8Bit() );

  // last modification date
  tmpStr = kDateTimeToISO( anEvent->lastModified() );
  addPropValue( vevent, VCLastModifiedProp, tmpStr.toLocal8Bit() );

  // attendee and organizer stuff
  // TODO: What to do with the common name?
  if ( !anEvent->organizer().email().isEmpty() ) {
    tmpStr = "MAILTO:" + anEvent->organizer().email();
    addPropValue( vevent, ICOrganizerProp, tmpStr.toLocal8Bit() );
  }

  // TODO: Put this functionality into Attendee class
  if ( anEvent->attendeeCount() > 0 ) {
    Attendee::List::ConstIterator it;
    for ( it = anEvent->attendees().constBegin(); it != anEvent->attendees().constEnd();
          ++it ) {
      Attendee *curAttendee = *it;
      if ( !curAttendee->email().isEmpty() && !curAttendee->name().isEmpty() ) {
        tmpStr = "MAILTO:" + curAttendee->name() + " <" + curAttendee->email() + '>';
      } else if ( curAttendee->name().isEmpty() ) {
        tmpStr = "MAILTO: " + curAttendee->email();
      } else if ( curAttendee->email().isEmpty() ) {
        tmpStr = "MAILTO: " + curAttendee->name();
      } else if ( curAttendee->name().isEmpty() && curAttendee->email().isEmpty() ) {
        kDebug() << "warning! this Event has an attendee w/o name or email!";
      }
      VObject *aProp = addPropValue( vevent, VCAttendeeProp, tmpStr.toLocal8Bit() );
      addPropValue( aProp, VCRSVPProp, curAttendee->RSVP() ? "TRUE" : "FALSE" );
      addPropValue( aProp, VCStatusProp, writeStatus( curAttendee->status() ) );
    }
  }

  // recurrence rule stuff
  const Recurrence *recur = anEvent->recurrence();
  if ( recur->recurs() ) {
    bool validRecur = true;
    QString tmpStr2;
    switch ( recur->recurrenceType() ) {
    case Recurrence::rDaily:
      tmpStr.sprintf( "D%i ", recur->frequency() );
      break;
    case Recurrence::rWeekly:
      tmpStr.sprintf( "W%i ", recur->frequency() );
      for ( int i = 0; i < 7; ++i ) {
        QBitArray days ( recur->days() );
        if ( days.testBit(i) ) {
          tmpStr += dayFromNum( i );
        }
      }
      break;
    case Recurrence::rMonthlyPos:
    {
      tmpStr.sprintf( "MP%i ", recur->frequency() );
      // write out all rMonthPos's
      QList<RecurrenceRule::WDayPos> tmpPositions = recur->monthPositions();
      for ( QList<RecurrenceRule::WDayPos>::ConstIterator posit = tmpPositions.constBegin();
            posit != tmpPositions.constEnd(); ++posit ) {
        int pos = (*posit).pos();
        tmpStr2.sprintf( "%i", ( pos > 0 ) ? pos : (-pos) );
        if ( pos < 0 ) {
          tmpStr2 += "- ";
        } else {
          tmpStr2 += "+ ";
        }
        tmpStr += tmpStr2;
        tmpStr += dayFromNum( (*posit).day() - 1 );
      }
      break;
    }
    case Recurrence::rMonthlyDay:
    {
      tmpStr.sprintf( "MD%i ", recur->frequency() );
      // write out all rMonthDays;
      const QList<int> tmpDays = recur->monthDays();
      for ( QList<int>::ConstIterator tmpDay = tmpDays.constBegin();
            tmpDay != tmpDays.constEnd(); ++tmpDay ) {
        tmpStr2.sprintf( "%i ", *tmpDay );
        tmpStr += tmpStr2;
      }
      break;
    }
    case Recurrence::rYearlyMonth:
    {
      tmpStr.sprintf( "YM%i ", recur->frequency() );
      // write out all the months;'
      // TODO: Any way to write out the day within the month???
      const QList<int> months = recur->yearMonths();
      for ( QList<int>::ConstIterator mit = months.constBegin();
            mit != months.constEnd(); ++mit ) {
        tmpStr2.sprintf( "%i ", *mit );
        tmpStr += tmpStr2;
      }
      break;
    }
    case Recurrence::rYearlyDay:
    {
      tmpStr.sprintf( "YD%i ", recur->frequency() );
      // write out all the rYearNums;
      const QList<int> tmpDays = recur->yearDays();
      for ( QList<int>::ConstIterator tmpDay = tmpDays.begin();
            tmpDay != tmpDays.end(); ++tmpDay ) {
        tmpStr2.sprintf( "%i ", *tmpDay );
        tmpStr += tmpStr2;
      }
      break;
    }
    default:
      // TODO: Write rYearlyPos and arbitrary rules!
      kDebug() << "ERROR, it should never get here in eventToVEvent!";
      validRecur = false;
      break;
    } // switch

    if ( recur->duration() > 0 ) {
      tmpStr2.sprintf( "#%i", recur->duration() );
      tmpStr += tmpStr2;
    } else if ( recur->duration() == -1 ) {
      tmpStr += "#0"; // defined as repeat forever
    } else {
      tmpStr += kDateTimeToISO( recur->endDateTime(), false );
    }
    // Only write out the rrule if we have a valid recurrence (i.e. a known
    // type in thee switch above)
    if ( validRecur ) {
      addPropValue( vevent, VCRRuleProp, tmpStr.toLocal8Bit() );
    }

  } // event repeats

  // exceptions to recurrence
  DateList dateList = recur->exDates();
  DateList::ConstIterator it;
  QString tmpStr2;

  for ( it = dateList.constBegin(); it != dateList.constEnd(); ++it ) {
    tmpStr = qDateToISO(*it) + ';';
    tmpStr2 += tmpStr;
  }
  if ( !tmpStr2.isEmpty() ) {
    tmpStr2.truncate( tmpStr2.length() - 1 );
    addPropValue( vevent, VCExpDateProp, tmpStr2.toLocal8Bit() );
  }

  // description
  if ( !anEvent->description().isEmpty() ) {
    VObject *d = addPropValue( vevent, VCDescriptionProp,
                               anEvent->description().toLocal8Bit() );
    if ( anEvent->description().indexOf( '\n' ) != -1 ) {
      addPropValue( d, VCEncodingProp, VCQuotedPrintableProp );
    }
  }

  // summary
  if ( !anEvent->summary().isEmpty() ) {
    addPropValue( vevent, VCSummaryProp, anEvent->summary().toLocal8Bit() );
  }

  // location
  if ( !anEvent->location().isEmpty() ) {
    addPropValue( vevent, VCLocationProp, anEvent->location().toLocal8Bit() );
  }

  // status
// TODO: define Event status
//  addPropValue( vevent, VCStatusProp, anEvent->statusStr().toLocal8Bit() );

  // secrecy
  const char *text = 0;
  switch ( anEvent->secrecy() ) {
  case Incidence::SecrecyPublic:
    text = "PUBLIC";
    break;
  case Incidence::SecrecyPrivate:
    text = "PRIVATE";
    break;
  case Incidence::SecrecyConfidential:
    text = "CONFIDENTIAL";
    break;
  }
  if ( text ) {
    addPropValue( vevent, VCClassProp, text );
  }

  // categories
  QStringList tmpStrList = anEvent->categories();
  tmpStr = "";
  QString catStr;
  for ( QStringList::const_iterator it = tmpStrList.constBegin(); it != tmpStrList.constEnd();
        ++it ) {
    catStr = *it;
    if ( catStr[0] == ' ' ) {
      tmpStr += catStr.mid( 1 );
    } else {
      tmpStr += catStr;
    }
    // this must be a ';' character as the vCalendar specification requires!
    // vcc.y has been hacked to translate the ';' to a ',' when the vcal is
    // read in.
    tmpStr += ';';
  }
  if ( !tmpStr.isEmpty() ) {
    tmpStr.truncate( tmpStr.length() - 1 );
    addPropValue( vevent, VCCategoriesProp, tmpStr.toLocal8Bit() );
  }

  // attachments
  // TODO: handle binary attachments!
  Attachment::List attachments = anEvent->attachments();
  Attachment::List::ConstIterator atIt;
  for ( atIt = attachments.constBegin(); atIt != attachments.constEnd(); ++atIt ) {
    addPropValue( vevent, VCAttachProp, (*atIt)->uri().toLocal8Bit() );
  }

  // resources
  tmpStrList = anEvent->resources();
  tmpStr = tmpStrList.join( ";" );
  if ( !tmpStr.isEmpty() ) {
    addPropValue( vevent, VCResourcesProp, tmpStr.toLocal8Bit() );
  }

  // alarm stuff
  Alarm::List::ConstIterator it2;
  for ( it2 = anEvent->alarms().constBegin(); it2 != anEvent->alarms().constEnd(); ++it2 ) {
    Alarm *alarm = *it2;
    if ( alarm->enabled() ) {
      VObject *a = addProp( vevent, VCDAlarmProp );
      tmpStr = kDateTimeToISO( alarm->time() );
      addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
      addPropValue( a, VCRepeatCountProp, "1" );
      addPropValue( a, VCDisplayStringProp, "beep!" );
      if ( alarm->type() == Alarm::Audio ) {
        a = addProp( vevent, VCAAlarmProp );
        addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
        addPropValue( a, VCRepeatCountProp, "1" );
        addPropValue( a, VCAudioContentProp, QFile::encodeName( alarm->audioFile() ) );
      }
      if ( alarm->type() == Alarm::Procedure ) {
        a = addProp( vevent, VCPAlarmProp );
        addPropValue( a, VCRunTimeProp, tmpStr.toLocal8Bit() );
        addPropValue( a, VCRepeatCountProp, "1" );
        addPropValue( a, VCProcedureNameProp, QFile::encodeName( alarm->programFile() ) );
      }
    }
  }

  // priority
  tmpStr.sprintf( "%i", anEvent->priority() );
  addPropValue( vevent, VCPriorityProp, tmpStr.toLocal8Bit() );

  // transparency
  tmpStr.sprintf( "%i", anEvent->transparency() );
  addPropValue( vevent, VCTranspProp, tmpStr.toLocal8Bit() );

  // related event
  if ( anEvent->relatedTo() ) {
    addPropValue( vevent, VCRelatedToProp, anEvent->relatedTo()->uid().toLocal8Bit() );
  }

  QString pilotId = anEvent->nonKDECustomProperty( KPilotIdProp );
  if ( !pilotId.isEmpty() ) {
    // pilot sync stuff
    addPropValue( vevent, KPilotIdProp, pilotId.toLocal8Bit() );
    addPropValue( vevent, KPilotStatusProp,
                  anEvent->nonKDECustomProperty( KPilotStatusProp ).toLocal8Bit() );
  }

  return vevent;
}

Todo *VCalFormat::VTodoToEvent( VObject *vtodo )
{
  VObject *vo;
  VObjectIterator voi;
  char *s;

  Todo *anEvent = new Todo;

  // creation date
  if ( ( vo = isAPropertyOf( vtodo, VCDCreatedProp ) ) != 0 ) {
      anEvent->setCreated( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
      deleteStr( s );
  }

  // unique id
  vo = isAPropertyOf( vtodo, VCUniqueStringProp );
  // while the UID property is preferred, it is not required.  We'll use the
  // default Event UID if none is given.
  if ( vo ) {
    anEvent->setUid( s = fakeCString( vObjectUStringZValue( vo ) ) );
    deleteStr( s );
  }

  // last modification date
  if ( ( vo = isAPropertyOf( vtodo, VCLastModifiedProp ) ) != 0 ) {
    anEvent->setLastModified( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
    deleteStr( s );
  } else {
    anEvent->setLastModified( KDateTime::currentUtcDateTime() );
  }

  // organizer
  // if our extension property for the event's ORGANIZER exists, add it.
  if ( ( vo = isAPropertyOf( vtodo, ICOrganizerProp ) ) != 0 ) {
    anEvent->setOrganizer( s = fakeCString( vObjectUStringZValue( vo ) ) );
    deleteStr( s );
  } else {
    anEvent->setOrganizer( d->mCalendar->owner() );
  }

  // attendees.
  initPropIterator( &voi, vtodo );
  while ( moreIteration( &voi ) ) {
    vo = nextVObject( &voi );
    if ( strcmp( vObjectName( vo ), VCAttendeeProp ) == 0 ) {
      Attendee *a;
      VObject *vp;
      s = fakeCString( vObjectUStringZValue( vo ) );
      QString tmpStr = QString::fromLocal8Bit( s );
      deleteStr( s );
      tmpStr = tmpStr.simplified();
      int emailPos1, emailPos2;
      if ( ( emailPos1 = tmpStr.indexOf( '<' ) ) > 0 ) {
        // both email address and name
        emailPos2 = tmpStr.lastIndexOf( '>' );
        a = new Attendee( tmpStr.left( emailPos1 - 1 ),
                          tmpStr.mid( emailPos1 + 1,
                                      emailPos2 - ( emailPos1 + 1 ) ) );
      } else if ( tmpStr.indexOf( '@' ) > 0 ) {
        // just an email address
        a = new Attendee( 0, tmpStr );
      } else {
        // just a name
        // WTF??? Replacing the spaces of a name and using this as email?
        QString email = tmpStr.replace( ' ', '.' );
        a = new Attendee( tmpStr, email );
      }

      // is there an RSVP property?
      if ( ( vp = isAPropertyOf( vo, VCRSVPProp ) ) != 0 ) {
        a->setRSVP( vObjectStringZValue( vp ) );
      }
      // is there a status property?
      if ( ( vp = isAPropertyOf( vo, VCStatusProp ) ) != 0 ) {
        a->setStatus( readStatus( vObjectStringZValue( vp ) ) );
      }
      // add the attendee
      anEvent->addAttendee( a );
    }
  }

  // description for todo
  if ( ( vo = isAPropertyOf( vtodo, VCDescriptionProp ) ) != 0 ) {
    s = fakeCString( vObjectUStringZValue( vo ) );
    anEvent->setDescription( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
    deleteStr( s );
  }

  // summary
  if ( ( vo = isAPropertyOf( vtodo, VCSummaryProp ) ) ) {
    s = fakeCString( vObjectUStringZValue( vo ) );
    anEvent->setSummary( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
    deleteStr( s );
  }

  // location
  if ( ( vo = isAPropertyOf( vtodo, VCLocationProp ) ) != 0 ) {
    s = fakeCString( vObjectUStringZValue( vo ) );
    anEvent->setLocation( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
    deleteStr( s );
  }

  // completed
  // was: status
  if ( ( vo = isAPropertyOf( vtodo, VCStatusProp ) ) != 0 ) {
    s = fakeCString( vObjectUStringZValue( vo ) );
    if ( strcmp( s, "COMPLETED" ) == 0 ) {
      anEvent->setCompleted( true );
    } else {
      anEvent->setCompleted( false );
    }
    deleteStr( s );
  } else {
    anEvent->setCompleted( false );
  }

  // completion date
  if ( ( vo = isAPropertyOf( vtodo, VCCompletedProp ) ) != 0 ) {
    anEvent->setCompleted( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
    deleteStr( s );
  }

  // priority
  if ( ( vo = isAPropertyOf( vtodo, VCPriorityProp ) ) ) {
    anEvent->setPriority( atoi( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
    deleteStr( s );
  }

  // due date
  if ( ( vo = isAPropertyOf( vtodo, VCDueProp ) ) != 0 ) {
    anEvent->setDtDue( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
    deleteStr( s );
    anEvent->setHasDueDate( true );
  } else {
    anEvent->setHasDueDate( false );
  }

  // start time
  if ( ( vo = isAPropertyOf( vtodo, VCDTstartProp ) ) != 0 ) {
    anEvent->setDtStart( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
    deleteStr( s );
    anEvent->setHasStartDate( true );
  } else {
    anEvent->setHasStartDate( false );
  }

  // alarm stuff
  if ( ( vo = isAPropertyOf( vtodo, VCDAlarmProp ) ) ) {
    Alarm *alarm = anEvent->newAlarm();
    VObject *a;
    if ( ( a = isAPropertyOf( vo, VCRunTimeProp ) ) ) {
      alarm->setTime( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( a ) ) ) );
      deleteStr( s );
    }
    alarm->setEnabled( true );
    if ( ( vo = isAPropertyOf( vtodo, VCPAlarmProp ) ) ) {
      if ( ( a = isAPropertyOf( vo, VCProcedureNameProp ) ) ) {
        s = fakeCString( vObjectUStringZValue( a ) );
        alarm->setProcedureAlarm( QFile::decodeName( s ) );
        deleteStr( s );
      }
    }
    if ( ( vo = isAPropertyOf( vtodo, VCAAlarmProp ) ) ) {
      if ( ( a = isAPropertyOf( vo, VCAudioContentProp ) ) ) {
        s = fakeCString( vObjectUStringZValue( a ) );
        alarm->setAudioAlarm( QFile::decodeName( s ) );
        deleteStr( s );
      }
    }
  }

  // related todo
  if ( ( vo = isAPropertyOf( vtodo, VCRelatedToProp ) ) != 0 ) {
    anEvent->setRelatedToUid( s = fakeCString( vObjectUStringZValue( vo ) ) );
    deleteStr( s );
    d->mTodosRelate.append( anEvent );
  }

  // categories
  if ( ( vo = isAPropertyOf( vtodo, VCCategoriesProp ) ) != 0 ) {
    s = fakeCString( vObjectUStringZValue( vo ) );
    QString categories = QString::fromLocal8Bit( s );
    deleteStr( s );
    QStringList tmpStrList = categories.split( ';' );
    anEvent->setCategories( tmpStrList );
  }

  /* PILOT SYNC STUFF */
  if ( ( vo = isAPropertyOf( vtodo, KPilotIdProp ) ) ) {
    anEvent->setNonKDECustomProperty(
      KPilotIdProp, QString::fromLocal8Bit( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
    deleteStr( s );
    if ( ( vo = isAPropertyOf( vtodo, KPilotStatusProp ) ) ) {
      anEvent->setNonKDECustomProperty(
        KPilotStatusProp, QString::fromLocal8Bit( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
      deleteStr( s );
    } else {
      anEvent->setNonKDECustomProperty( KPilotStatusProp, QString::number( SYNCMOD ) );
    }
  }

  return anEvent;
}

Event *VCalFormat::VEventToEvent( VObject *vevent )
{
  VObject *vo;
  VObjectIterator voi;
  char *s;

  Event *anEvent = new Event;

  // creation date
  if ( ( vo = isAPropertyOf( vevent, VCDCreatedProp ) ) != 0 ) {
      anEvent->setCreated( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
      deleteStr( s );
  }

  // unique id
  vo = isAPropertyOf( vevent, VCUniqueStringProp );
  // while the UID property is preferred, it is not required.  We'll use the
  // default Event UID if none is given.
  if ( vo ) {
    anEvent->setUid( s = fakeCString( vObjectUStringZValue( vo ) ) );
    deleteStr( s );
  }

  // revision
  // again NSCAL doesn't give us much to work with, so we improvise...
  if ( ( vo = isAPropertyOf( vevent, VCSequenceProp ) ) != 0 ) {
    anEvent->setRevision( atoi( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
    deleteStr( s );
  } else {
    anEvent->setRevision( 0 );
  }

  // last modification date
  if ( ( vo = isAPropertyOf( vevent, VCLastModifiedProp ) ) != 0 ) {
    anEvent->setLastModified( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
    deleteStr( s );
  } else {
    anEvent->setLastModified( KDateTime::currentUtcDateTime() );
  }

  // organizer
  // if our extension property for the event's ORGANIZER exists, add it.
  if ( ( vo = isAPropertyOf( vevent, ICOrganizerProp ) ) != 0 ) {
    // FIXME:  Also use the full name, not just the email address
    anEvent->setOrganizer( s = fakeCString( vObjectUStringZValue( vo ) ) );
    deleteStr( s );
  } else {
    anEvent->setOrganizer( d->mCalendar->owner() );
  }

  // deal with attendees.
  initPropIterator( &voi, vevent );
  while ( moreIteration( &voi ) ) {
    vo = nextVObject( &voi );
    if ( strcmp( vObjectName( vo ), VCAttendeeProp ) == 0 ) {
      Attendee *a;
      VObject *vp;
      s = fakeCString( vObjectUStringZValue( vo ) );
      QString tmpStr = QString::fromLocal8Bit( s );
      deleteStr( s );
      tmpStr = tmpStr.simplified();
      int emailPos1, emailPos2;
      if ( ( emailPos1 = tmpStr.indexOf( '<' ) ) > 0 ) {
        // both email address and name
        emailPos2 = tmpStr.lastIndexOf( '>' );
        a = new Attendee( tmpStr.left( emailPos1 - 1 ),
                          tmpStr.mid( emailPos1 + 1,
                                      emailPos2 - ( emailPos1 + 1 ) ) );
      } else if ( tmpStr.indexOf( '@' ) > 0 ) {
        // just an email address
        a = new Attendee( 0, tmpStr );
      } else {
        // just a name
        QString email = tmpStr.replace( ' ', '.' );
        a = new Attendee( tmpStr, email );
      }

      // is there an RSVP property?
      if ( ( vp = isAPropertyOf( vo, VCRSVPProp ) ) != 0 ) {
        a->setRSVP( vObjectStringZValue( vp ) );
      }
      // is there a status property?
      if ( ( vp = isAPropertyOf( vo, VCStatusProp ) ) != 0 ) {
        a->setStatus( readStatus( vObjectStringZValue( vp ) ) );
      }
      // add the attendee
      anEvent->addAttendee( a );
    }
  }

  // This isn't strictly true.  An event that doesn't have a start time
  // or an end time isn't all-day, it has an anchor in time but it doesn't
  // "take up" any time.
  /*if ((isAPropertyOf(vevent, VCDTstartProp) == 0) ||
      (isAPropertyOf(vevent, VCDTendProp) == 0)) {
    anEvent->setAllDay(true);
    } else {
    }*/

  anEvent->setAllDay( false );

  // start time
  if ( ( vo = isAPropertyOf( vevent, VCDTstartProp ) ) != 0 ) {
    anEvent->setDtStart( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
    deleteStr( s );
    if ( anEvent->dtStart().time().isNull() ) {
      anEvent->setAllDay( true );
    }
  }

  // stop time
  if ( ( vo = isAPropertyOf( vevent, VCDTendProp ) ) != 0 ) {
    anEvent->setDtEnd( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
    deleteStr( s );
    if ( anEvent->dtEnd().time().isNull() ) {
      anEvent->setAllDay( true );
    }
  }

  // at this point, there should be at least a start or end time.
  // fix up for events that take up no time but have a time associated
  if ( !( vo = isAPropertyOf( vevent, VCDTstartProp ) ) ) {
    anEvent->setDtStart( anEvent->dtEnd() );
  }
  if ( !( vo = isAPropertyOf( vevent, VCDTendProp ) ) ) {
    anEvent->setDtEnd( anEvent->dtStart() );
  }

  ///////////////////////////////////////////////////////////////////////////

  // repeat stuff
  if ( ( vo = isAPropertyOf( vevent, VCRRuleProp ) ) != 0 ) {
    QString tmpStr = ( s = fakeCString( vObjectUStringZValue( vo ) ) );
    deleteStr( s );
    tmpStr.simplified();
    tmpStr = tmpStr.toUpper();

    // first, read the type of the recurrence
    int typelen = 1;
    uint type = Recurrence::rNone;
    if ( tmpStr.left(1) == "D" ) {
      type = Recurrence::rDaily;
    } else if ( tmpStr.left(1) == "W" ) {
      type = Recurrence::rWeekly;
    } else {
      typelen = 2;
      if ( tmpStr.left(2) == "MP" ) {
        type = Recurrence::rMonthlyPos;
      } else if ( tmpStr.left(2) == "MD" ) {
        type = Recurrence::rMonthlyDay;
      } else if ( tmpStr.left(2) == "YM" ) {
        type = Recurrence::rYearlyMonth;
      } else if ( tmpStr.left(2) == "YD" ) {
        type = Recurrence::rYearlyDay;
      }
    }

    if ( type != Recurrence::rNone ) {

      // Immediately after the type is the frequency
      int index = tmpStr.indexOf( ' ' );
      int last = tmpStr.lastIndexOf( ' ' ) + 1; // find last entry
      int rFreq = tmpStr.mid( typelen, ( index - 1 ) ).toInt();
      ++index; // advance to beginning of stuff after freq

      // Read the type-specific settings
      switch ( type ) {
      case Recurrence::rDaily:
        anEvent->recurrence()->setDaily(rFreq);
        break;

      case Recurrence::rWeekly:
      {
        QBitArray qba(7);
        QString dayStr;
        if ( index == last ) {
          // e.g. W1 #0
          qba.setBit( anEvent->dtStart().date().dayOfWeek() - 1 );
        } else {
          // e.g. W1 SU #0
          while ( index < last ) {
            dayStr = tmpStr.mid( index, 3 );
            int dayNum = numFromDay( dayStr );
            qba.setBit( dayNum );
            index += 3; // advance to next day, or possibly "#"
          }
        }
        anEvent->recurrence()->setWeekly( rFreq, qba );
        break;
      }

      case Recurrence::rMonthlyPos:
      {
        anEvent->recurrence()->setMonthly( rFreq );

        QBitArray qba(7);
        short tmpPos;
        if ( index == last ) {
          // e.g. MP1 #0
          tmpPos = anEvent->dtStart().date().day() / 7 + 1;
          if ( tmpPos == 5 ) {
            tmpPos = -1;
          }
          qba.setBit( anEvent->dtStart().date().dayOfWeek() - 1 );
          anEvent->recurrence()->addMonthlyPos( tmpPos, qba );
        } else {
          // e.g. MP1 1+ SU #0
          while ( index < last ) {
            tmpPos = tmpStr.mid( index, 1 ).toShort();
            index += 1;
            if ( tmpStr.mid( index, 1 ) == "-" ) {
              // convert tmpPos to negative
              tmpPos = 0 - tmpPos;
            }
            index += 2; // advance to day(s)
            while ( numFromDay( tmpStr.mid( index, 3 ) ) >= 0 ) {
              int dayNum = numFromDay( tmpStr.mid( index, 3 ) );
              qba.setBit( dayNum );
              index += 3; // advance to next day, or possibly pos or "#"
            }
            anEvent->recurrence()->addMonthlyPos( tmpPos, qba );
            qba.detach();
            qba.fill( false ); // clear out
          } // while != "#"
        }
        break;
      }

      case Recurrence::rMonthlyDay:
        anEvent->recurrence()->setMonthly( rFreq );
        if( index == last ) {
          // e.g. MD1 #0
          short tmpDay = anEvent->dtStart().date().day();
          anEvent->recurrence()->addMonthlyDate( tmpDay );
        } else {
          // e.g. MD1 3 #0
          while ( index < last ) {
            int index2 = tmpStr.indexOf( ' ', index );
            short tmpDay = tmpStr.mid( index, ( index2 - index ) ).toShort();
            index = index2 - 1;
            if ( tmpStr.mid( index, 1 ) == "-" ) {
              tmpDay = 0 - tmpDay;
            }
            index += 2; // advance the index;
            anEvent->recurrence()->addMonthlyDate( tmpDay );
          } // while != #
        }
        break;

      case Recurrence::rYearlyMonth:
        anEvent->recurrence()->setYearly( rFreq );

        if ( index == last ) {
          // e.g. YM1 #0
          short tmpMonth = anEvent->dtStart().date().month();
          anEvent->recurrence()->addYearlyMonth( tmpMonth );
        } else {
          // e.g. YM1 3 #0
          while ( index < last ) {
            int index2 = tmpStr.indexOf( ' ', index );
            short tmpMonth = tmpStr.mid( index, ( index2 - index ) ).toShort();
            index = index2 + 1;
            anEvent->recurrence()->addYearlyMonth( tmpMonth );
          } // while != #
        }
        break;

      case Recurrence::rYearlyDay:
        anEvent->recurrence()->setYearly( rFreq );

        if ( index == last ) {
          // e.g. YD1 #0
          short tmpDay = anEvent->dtStart().date().dayOfYear();
          anEvent->recurrence()->addYearlyDay( tmpDay );
        } else {
          // e.g. YD1 123 #0
          while ( index < last ) {
            int index2 = tmpStr.indexOf( ' ', index );
            short tmpDay = tmpStr.mid( index, ( index2 - index ) ).toShort();
            index = index2 + 1;
            anEvent->recurrence()->addYearlyDay( tmpDay );
          } // while != #
        }
        break;

      default:
        break;
      }

      // find the last field, which is either the duration or the end date
      index = last;
      if ( tmpStr.mid( index, 1 ) == "#" ) {
        // Nr of occurrences
        index++;
        int rDuration = tmpStr.mid( index, tmpStr.length() - index ).toInt();
        if ( rDuration > 0 ) {
          anEvent->recurrence()->setDuration( rDuration );
        }
      } else if ( tmpStr.indexOf( 'T', index ) != -1 ) {
        KDateTime rEndDate = ISOToKDateTime( tmpStr.mid( index, tmpStr.length() - index ) );
        rEndDate.setDateOnly( true );
        anEvent->recurrence()->setEndDateTime( rEndDate );
      }
// anEvent->recurrence()->dump();

    } else {
      kDebug() << "we don't understand this type of recurrence!";
    } // if known recurrence type
  } // repeats

  // recurrence exceptions
  if ( ( vo = isAPropertyOf( vevent, VCExpDateProp ) ) != 0 ) {
    s = fakeCString( vObjectUStringZValue( vo ) );
    QStringList exDates = QString::fromLocal8Bit( s ).split( ',' );
    QStringList::ConstIterator it;
    for ( it = exDates.constBegin(); it != exDates.constEnd(); ++it ) {
      anEvent->recurrence()->addExDate( ISOToQDate(*it) );
    }
    deleteStr( s );
  }

  // summary
  if ( ( vo = isAPropertyOf( vevent, VCSummaryProp ) ) ) {
    s = fakeCString( vObjectUStringZValue( vo ) );
    anEvent->setSummary( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
    deleteStr( s );
  }

  // description
  if ( ( vo = isAPropertyOf( vevent, VCDescriptionProp ) ) != 0 ) {
    s = fakeCString( vObjectUStringZValue( vo ) );
    bool isRich = Qt::mightBeRichText( s );
    if ( !anEvent->description().isEmpty() ) {
      anEvent->setDescription(
        anEvent->description() + '\n' + QString::fromLocal8Bit( s ), isRich );
    } else {
      anEvent->setDescription( QString::fromLocal8Bit( s ), isRich );
    }
    deleteStr( s );
  }

  // location
  if ( ( vo = isAPropertyOf( vevent, VCLocationProp ) ) != 0 ) {
    s = fakeCString( vObjectUStringZValue( vo ) );
    anEvent->setLocation( QString::fromLocal8Bit( s ), Qt::mightBeRichText( s ) );
    deleteStr( s );
  }

  // some stupid vCal exporters ignore the standard and use Description
  // instead of Summary for the default field.  Correct for this.
  if ( anEvent->summary().isEmpty() && !( anEvent->description().isEmpty() ) ) {
    QString tmpStr = anEvent->description().simplified();
    anEvent->setDescription( "" );
    anEvent->setSummary( tmpStr );
  }

#if 0
  // status
  if ( ( vo = isAPropertyOf( vevent, VCStatusProp ) ) != 0 ) {
    QString tmpStr( s = fakeCString( vObjectUStringZValue( vo ) ) );
    deleteStr( s );
// TODO: Define Event status
//    anEvent->setStatus( tmpStr );
  } else {
//    anEvent->setStatus( "NEEDS ACTION" );
  }
#endif

  // secrecy
  Incidence::Secrecy secrecy = Incidence::SecrecyPublic;
  if ( ( vo = isAPropertyOf( vevent, VCClassProp ) ) != 0 ) {
    s = fakeCString( vObjectUStringZValue( vo ) );
    if ( strcmp( s, "PRIVATE" ) == 0 ) {
      secrecy = Incidence::SecrecyPrivate;
    } else if ( strcmp( s, "CONFIDENTIAL" ) == 0 ) {
      secrecy = Incidence::SecrecyConfidential;
    }
    deleteStr( s );
  }
  anEvent->setSecrecy( secrecy );

  // categories
  if ( ( vo = isAPropertyOf( vevent, VCCategoriesProp ) ) != 0 ) {
    s = fakeCString( vObjectUStringZValue( vo ) );
    QString categories = QString::fromLocal8Bit( s );
    deleteStr( s );
    QStringList tmpStrList = categories.split( ',' );
    anEvent->setCategories( tmpStrList );
  }

  // attachments
  initPropIterator( &voi, vevent );
  while ( moreIteration( &voi ) ) {
    vo = nextVObject( &voi );
    if ( strcmp( vObjectName( vo ), VCAttachProp ) == 0 ) {
      s = fakeCString( vObjectUStringZValue( vo ) );
      anEvent->addAttachment( new Attachment( QString( s ) ) );
      deleteStr( s );
    }
  }

  // resources
  if ( ( vo = isAPropertyOf( vevent, VCResourcesProp ) ) != 0 ) {
    QString resources = ( s = fakeCString( vObjectUStringZValue( vo ) ) );
    deleteStr( s );
    QStringList tmpStrList = resources.split( ';' );
    anEvent->setResources( tmpStrList );
  }

  // alarm stuff
  if ( ( vo = isAPropertyOf( vevent, VCDAlarmProp ) ) ) {
    Alarm *alarm = anEvent->newAlarm();
    VObject *a;
    if ( ( a = isAPropertyOf( vo, VCRunTimeProp ) ) ) {
      alarm->setTime( ISOToKDateTime( s = fakeCString( vObjectUStringZValue( a ) ) ) );
      deleteStr( s );
    }
    alarm->setEnabled( true );
    if ( ( vo = isAPropertyOf( vevent, VCPAlarmProp ) ) ) {
      if ( ( a = isAPropertyOf( vo, VCProcedureNameProp ) ) ) {
        s = fakeCString( vObjectUStringZValue( a ) );
        alarm->setProcedureAlarm( QFile::decodeName( s ) );
        deleteStr( s );
      }
    }
    if ( ( vo = isAPropertyOf( vevent, VCAAlarmProp ) ) ) {
      if ( ( a = isAPropertyOf( vo, VCAudioContentProp ) ) ) {
        s = fakeCString( vObjectUStringZValue( a ) );
        alarm->setAudioAlarm( QFile::decodeName( s ) );
        deleteStr( s );
      }
    }
  }

  // priority
  if ( ( vo = isAPropertyOf( vevent, VCPriorityProp ) ) ) {
    anEvent->setPriority( atoi( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
    deleteStr( s );
  }

  // transparency
  if ( ( vo = isAPropertyOf( vevent, VCTranspProp ) ) != 0 ) {
    int i = atoi( s = fakeCString( vObjectUStringZValue( vo ) ) );
    anEvent->setTransparency( i == 1 ? Event::Transparent : Event::Opaque );
    deleteStr( s );
  }

  // related event
  if ( ( vo = isAPropertyOf( vevent, VCRelatedToProp ) ) != 0 ) {
    anEvent->setRelatedToUid( s = fakeCString( vObjectUStringZValue( vo ) ) );
    deleteStr( s );
    d->mEventsRelate.append( anEvent );
  }

  /* PILOT SYNC STUFF */
  if ( ( vo = isAPropertyOf( vevent, KPilotIdProp ) ) ) {
    anEvent->setNonKDECustomProperty(
      KPilotIdProp, QString::fromLocal8Bit( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
    deleteStr( s );
    if ( ( vo = isAPropertyOf( vevent, KPilotStatusProp ) ) ) {
      anEvent->setNonKDECustomProperty(
        KPilotStatusProp, QString::fromLocal8Bit( s = fakeCString( vObjectUStringZValue( vo ) ) ) );
      deleteStr( s );
    } else {
      anEvent->setNonKDECustomProperty( KPilotStatusProp, QString::number( SYNCMOD ) );
    }
  }

  return anEvent;
}

QString VCalFormat::qDateToISO( const QDate &qd )
{
  QString tmpStr;

  if ( !qd.isValid() ) {
    return QString();
  }

  tmpStr.sprintf( "%.2d%.2d%.2d", qd.year(), qd.month(), qd.day() );
  return tmpStr;

}

QString VCalFormat::kDateTimeToISO( const KDateTime &dt, bool zulu )
{
  QString tmpStr;

  if ( !dt.isValid() ) {
    return QString();
  }

  QDateTime tmpDT;
  if ( zulu ) {
    tmpDT = dt.toUtc().dateTime();
  } else {
    tmpDT = dt.toTimeSpec( d->mCalendar->timeSpec() ).dateTime();
  }
  tmpStr.sprintf( "%.2d%.2d%.2dT%.2d%.2d%.2d",
                  tmpDT.date().year(), tmpDT.date().month(),
                  tmpDT.date().day(), tmpDT.time().hour(),
                  tmpDT.time().minute(), tmpDT.time().second() );
  if ( zulu ) {
    tmpStr += 'Z';
  }
  return tmpStr;
}

KDateTime VCalFormat::ISOToKDateTime( const QString &dtStr )
{
  QDate tmpDate;
  QTime tmpTime;
  QString tmpStr;
  int year, month, day, hour, minute, second;

  tmpStr = dtStr;
  year = tmpStr.left( 4 ).toInt();
  month = tmpStr.mid( 4, 2 ).toInt();
  day = tmpStr.mid( 6, 2 ).toInt();
  hour = tmpStr.mid( 9, 2 ).toInt();
  minute = tmpStr.mid( 11, 2 ).toInt();
  second = tmpStr.mid( 13, 2 ).toInt();
  tmpDate.setYMD( year, month, day );
  tmpTime.setHMS( hour, minute, second );

  if ( tmpDate.isValid() && tmpTime.isValid() ) {
    // correct for GMT if string is in Zulu format
    if ( dtStr.at( dtStr.length() - 1 ) == 'Z' ) {
      return KDateTime( tmpDate, tmpTime, KDateTime::UTC );
    } else {
      return KDateTime( tmpDate, tmpTime, d->mCalendar->timeSpec() );
    }
  } else {
    return KDateTime();
  }
}

QDate VCalFormat::ISOToQDate( const QString &dateStr )
{
  int year, month, day;

  year = dateStr.left( 4 ).toInt();
  month = dateStr.mid( 4, 2 ).toInt();
  day = dateStr.mid( 6, 2 ).toInt();

  return QDate( year, month, day );
}

// take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc.
// and break it down from it's tree-like format into the dictionary format
// that is used internally in the VCalFormat.
void VCalFormat::populate( VObject *vcal )
{
  // this function will populate the caldict dictionary and other event
  // lists. It turns vevents into Events and then inserts them.

  VObjectIterator i;
  VObject *curVO, *curVOProp;
  Event *anEvent;

  if ( ( curVO = isAPropertyOf( vcal, ICMethodProp ) ) != 0 ) {
    char *methodType = 0;
    methodType = fakeCString( vObjectUStringZValue( curVO ) );
    kDebug() << "This calendar is an iTIP transaction of type '"
             << methodType << "'";
    deleteStr( methodType );
  }

  // warn the user that we might have trouble reading non-known calendar.
  if ( ( curVO = isAPropertyOf( vcal, VCProdIdProp ) ) != 0 ) {
    char *s = fakeCString( vObjectUStringZValue( curVO ) );
    if ( strcmp( productId().toLocal8Bit(), s ) != 0 ) {
      kDebug() << "This vCalendar file was not created by KOrganizer or"
               << "any other product we support. Loading anyway...";
    }
    setLoadedProductId( s );
    deleteStr( s );
  }

  // warn the user we might have trouble reading this unknown version.
  if ( ( curVO = isAPropertyOf( vcal, VCVersionProp ) ) != 0 ) {
    char *s = fakeCString( vObjectUStringZValue( curVO ) );
    if ( strcmp( _VCAL_VERSION, s ) != 0 ) {
      kDebug() << "This vCalendar file has version" << s
               << "We only support" << _VCAL_VERSION;
    }
    deleteStr( s );
  }

#if 0
  // set the time zone (this is a property of the view, so just discard!)
  if ( ( curVO = isAPropertyOf( vcal, VCTimeZoneProp ) ) != 0 ) {
    char *s = fakeCString( vObjectUStringZValue( curVO ) );
    d->mCalendar->setTimeZone( s );
    deleteStr( s );
  }
#endif

  // Store all events with a relatedTo property in a list for post-processing
  d->mEventsRelate.clear();
  d->mTodosRelate.clear();

  initPropIterator( &i, vcal );

  // go through all the vobjects in the vcal
  while ( moreIteration( &i ) ) {
    curVO = nextVObject( &i );

    /************************************************************************/

    // now, check to see that the object is an event or todo.
    if ( strcmp( vObjectName( curVO ), VCEventProp ) == 0 ) {

      if ( ( curVOProp = isAPropertyOf( curVO, KPilotStatusProp ) ) != 0 ) {
        char *s;
        s = fakeCString( vObjectUStringZValue( curVOProp ) );
        // check to see if event was deleted by the kpilot conduit
        if ( atoi( s ) == SYNCDEL ) {
          deleteStr( s );
          kDebug() << "skipping pilot-deleted event";
          goto SKIP;
        }
        deleteStr( s );
      }

      // this code checks to see if we are trying to read in an event
      // that we already find to be in the calendar.  If we find this
      // to be the case, we skip the event.
      if ( ( curVOProp = isAPropertyOf( curVO, VCUniqueStringProp ) ) != 0 ) {
        char *s = fakeCString( vObjectUStringZValue( curVOProp ) );
        QString tmpStr( s );
        deleteStr( s );

        if ( d->mCalendar->incidence( tmpStr ) ) {
          goto SKIP;
        }
      }

      if ( ( !( curVOProp = isAPropertyOf( curVO, VCDTstartProp ) ) ) &&
           ( !( curVOProp = isAPropertyOf( curVO, VCDTendProp ) ) ) ) {
        kDebug() << "found a VEvent with no DTSTART and no DTEND! Skipping...";
        goto SKIP;
      }

      anEvent = VEventToEvent( curVO );
      // we now use addEvent instead of insertEvent so that the
      // signal/slot get connected.
      if ( anEvent ) {
        if ( anEvent->dtStart().isValid() && anEvent->dtEnd().isValid() ) {
          d->mCalendar->addEvent( anEvent );
        }
      } else {
        // some sort of error must have occurred while in translation.
        goto SKIP;
      }
    } else if ( strcmp( vObjectName( curVO ), VCTodoProp ) == 0 ) {
      Todo *aTodo = VTodoToEvent( curVO );

      Todo *old = d->mCalendar->todo( aTodo->uid() );
      if ( old ) {
        d->mCalendar->deleteTodo( old );
        d->mTodosRelate.removeAll( old );
      }

      d->mCalendar->addTodo( aTodo );
    } else if ( ( strcmp( vObjectName( curVO ), VCVersionProp ) == 0 ) ||
                ( strcmp( vObjectName( curVO ), VCProdIdProp ) == 0 ) ||
                ( strcmp( vObjectName( curVO ), VCTimeZoneProp ) == 0 ) ) {
      // do nothing, we know these properties and we want to skip them.
      // we have either already processed them or are ignoring them.
      ;
    } else {
      kDebug() << "Ignoring unknown vObject \"" << vObjectName(curVO) << "\"";
    }
  SKIP:
    ;
  } // while

  // Post-Process list of events with relations, put Event objects in relation
  Event::List::ConstIterator eIt;
  for ( eIt = d->mEventsRelate.constBegin(); eIt != d->mEventsRelate.constEnd(); ++eIt ) {
    (*eIt)->setRelatedTo( d->mCalendar->incidence( (*eIt)->relatedToUid() ) );
  }
  Todo::List::ConstIterator tIt;
  for ( tIt = d->mTodosRelate.constBegin(); tIt != d->mTodosRelate.constEnd(); ++tIt ) {
    (*tIt)->setRelatedTo( d->mCalendar->incidence( (*tIt)->relatedToUid() ) );
   }
}

const char *VCalFormat::dayFromNum( int day )
{
  const char *days[7] = { "MO ", "TU ", "WE ", "TH ", "FR ", "SA ", "SU " };

  return days[day];
}

int VCalFormat::numFromDay( const QString &day )
{
  if ( day == "MO " ) {
    return 0;
  }
  if ( day == "TU " ) {
    return 1;
  }
  if ( day == "WE " ) {
    return 2;
  }
  if ( day == "TH " ) {
    return 3;
  }
  if ( day == "FR " ) {
    return 4;
  }
  if ( day == "SA " ) {
    return 5;
  }
  if ( day == "SU " ) {
    return 6;
  }

  return -1; // something bad happened. :)
}

Attendee::PartStat VCalFormat::readStatus( const char *s ) const
{
  QString statStr = s;
  statStr = statStr.toUpper();
  Attendee::PartStat status;

  if ( statStr == "X-ACTION" ) {
    status = Attendee::NeedsAction;
  } else if ( statStr == "NEEDS ACTION" ) {
    status = Attendee::NeedsAction;
  } else if ( statStr == "ACCEPTED" ) {
    status = Attendee::Accepted;
  } else if ( statStr == "SENT" ) {
    status = Attendee::NeedsAction;
  } else if ( statStr == "TENTATIVE" ) {
    status = Attendee::Tentative;
  } else if ( statStr == "CONFIRMED" ) {
    status = Attendee::Accepted;
  } else if ( statStr == "DECLINED" ) {
    status = Attendee::Declined;
  } else if ( statStr == "COMPLETED" ) {
    status = Attendee::Completed;
  } else if ( statStr == "DELEGATED" ) {
    status = Attendee::Delegated;
  } else {
    kDebug() << "error setting attendee mStatus, unknown mStatus!";
    status = Attendee::NeedsAction;
  }

  return status;
}

QByteArray VCalFormat::writeStatus( Attendee::PartStat status ) const
{
  switch( status ) {
  default:
  case Attendee::NeedsAction:
    return "NEEDS ACTION";
    break;
  case Attendee::Accepted:
    return "ACCEPTED";
    break;
  case Attendee::Declined:
    return "DECLINED";
    break;
  case Attendee::Tentative:
    return "TENTATIVE";
    break;
  case Attendee::Delegated:
    return "DELEGATED";
    break;
  case Attendee::Completed:
    return "COMPLETED";
    break;
  case Attendee::InProcess:
    return "NEEDS ACTION";
    break;
  }
}
