/*
  This file is part of the kcal library.

  Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
  Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
  Copyright (c) 2006 David Jarvie <software@astrojar.org.uk>

  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 internal ICalFormat classes.

  @brief
  This class provides the libical dependent functions for ICalFormat.

  @author Cornelius Schumacher \<schumacher@kde.org\>
  @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
  @author David Jarvie \<software@astrojar.org.uk\>
*/

#include "icalformat_p.h"
#include "icalformat.h"
#include "icaltimezones.h"
#include "calendar.h"
#include "compat.h"
#include "journal.h"

extern "C" {
  #include <libical/ical.h>
  #include <libical/icalparser.h>
  #include <libical/icalrestriction.h>
}

#include <QtCore/QString>
#include <QtCore/QFile>
#include <QtCore/QList>
#include <QtCore/QByteArray>

#include <kdebug.h>
#include <kdatetime.h>
#include <ktzfiletimezone.h>
#include <ksystemtimezone.h>
#include <klocalizedstring.h>
#include <kcodecs.h>

#include <cstdlib>

using namespace KCal;

/* Static helpers */
/*
static void _dumpIcaltime( const icaltimetype& t)
{
  kDebug() << "--- Y:" << t.year << "M:" << t.month << "D:" << t.day;
  kDebug() << "--- H:" << t.hour << "M:" << t.minute << "S:" << t.second;
  kDebug() << "--- isUtc:" << icaltime_is_utc( t );
  kDebug() << "--- zoneId:" << icaltimezone_get_tzid( const_cast<icaltimezone*>( t.zone ) );
}
*/

//@cond PRIVATE
static QString quoteForParam( const QString &text )
{
  QString tmp = text;
  tmp.remove( '"' );
  if ( tmp.contains( ';' ) || tmp.contains( ':' ) || tmp.contains( ',' ) ) {
    return tmp; // libical quotes in this case already, see icalparameter_as_ical_string()
  }
  return QString::fromLatin1( "\"" ) + tmp + QString::fromLatin1( "\"" );
}

const int gSecondsPerMinute = 60;
const int gSecondsPerHour   = gSecondsPerMinute * 60;
const int gSecondsPerDay    = gSecondsPerHour   * 24;
const int gSecondsPerWeek   = gSecondsPerDay    * 7;

class ToComponentVisitor : public IncidenceBase::Visitor
{
  public:
    ToComponentVisitor( ICalFormatImpl *impl, iTIPMethod m )
      : mImpl( impl ), mComponent( 0 ), mMethod( m ) {}

    bool visit( Event *e )
    {
      mComponent = mImpl->writeEvent( e );
      return true;
    }
    bool visit( Todo *e )
    {
      mComponent = mImpl->writeTodo( e );
      return true;
    }
    bool visit( Journal *e )
    {
      mComponent = mImpl->writeJournal( e );
      return true;
    }
    bool visit( FreeBusy *fb )
    {
      mComponent = mImpl->writeFreeBusy( fb, mMethod );
      return true;
    }

    icalcomponent *component()
    {
      return mComponent;
    }

  private:
    ICalFormatImpl *mImpl;
    icalcomponent *mComponent;
    iTIPMethod mMethod;
};

class ICalFormatImpl::Private
{
  public:
    Private( ICalFormatImpl *impl, ICalFormat *parent )
      : mImpl( impl ), mParent( parent ), mCompat( new Compat ) {}
    ~Private()  { delete mCompat; }
    void writeIncidenceBase( icalcomponent *parent, IncidenceBase * );
    void readIncidenceBase( icalcomponent *parent, IncidenceBase * );
    void writeCustomProperties( icalcomponent *parent, CustomProperties * );
    void readCustomProperties( icalcomponent *parent, CustomProperties * );

    ICalFormatImpl *mImpl;
    ICalFormat *mParent;
    QString mLoadedProductId;         // PRODID string loaded from calendar file
    Event::List mEventsRelate;        // events with relations
    Todo::List  mTodosRelate;         // todos with relations
    Compat *mCompat;
};
//@endcond

inline icaltimetype ICalFormatImpl::writeICalUtcDateTime ( const KDateTime &dt )
{
  return writeICalDateTime( dt.toUtc() );
}

ICalFormatImpl::ICalFormatImpl( ICalFormat *parent )
  : d( new Private( this, parent ) )
{
}

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

QString ICalFormatImpl::loadedProductId() const
{
  return d->mLoadedProductId;
}

icalcomponent *ICalFormatImpl::writeIncidence( IncidenceBase *incidence,
                                               iTIPMethod method )
{
  ToComponentVisitor v( this, method );
  if ( incidence->accept(v) ) {
    return v.component();
  } else {
    return 0;
  }
}

icalcomponent *ICalFormatImpl::writeTodo( Todo *todo, ICalTimeZones *tzlist,
                                          ICalTimeZones *tzUsedList )
{
  QString tmpStr;
  QStringList tmpStrList;

  icalcomponent *vtodo = icalcomponent_new( ICAL_VTODO_COMPONENT );

  writeIncidence( vtodo, todo, tzlist, tzUsedList );

  // due date
  icalproperty *prop;
  if ( todo->hasDueDate() ) {
    icaltimetype due;
    if ( todo->allDay() ) {
      due = writeICalDate( todo->dtDue( true ).date() );
      prop = icalproperty_new_due(due);
    } else {
      prop = writeICalDateTimeProperty(
        ICAL_DUE_PROPERTY, todo->dtDue(true), tzlist, tzUsedList );
    }
    icalcomponent_add_property( vtodo, prop );
  }

  // start time
  if ( todo->hasStartDate() ) {
    icaltimetype start;
    if ( todo->allDay() ) {
      start = writeICalDate( todo->dtStart( true ).date() );
      prop = icalproperty_new_dtstart( start );
    } else {
      prop = writeICalDateTimeProperty(
        ICAL_DTSTART_PROPERTY, todo->dtStart( true ), tzlist, tzUsedList );
    }
    icalcomponent_add_property( vtodo, prop );
  }

  // completion date (UTC)
  if ( todo->isCompleted() ) {
    if ( !todo->hasCompletedDate() ) {
      // If the todo was created by KOrganizer<2.2 it does not have
      // a correct completion date. Set one now.
      todo->setCompleted( KDateTime::currentUtcDateTime() );
    }
    icaltimetype completed = writeICalUtcDateTime( todo->completed() );
    icalcomponent_add_property(
      vtodo, icalproperty_new_completed ( completed ) );
  }

  icalcomponent_add_property(
    vtodo, icalproperty_new_percentcomplete( todo->percentComplete() ) );

  if ( todo->recurs() ) {
    icalcomponent_add_property(
      vtodo, writeICalDateTimeProperty(
        ICAL_RECURRENCEID_PROPERTY, todo->dtDue(), tzlist, tzUsedList ) );
  }

  return vtodo;
}

icalcomponent *ICalFormatImpl::writeEvent( Event *event,
                                           ICalTimeZones *tzlist,
                                           ICalTimeZones *tzUsedList )
{
  icalcomponent *vevent = icalcomponent_new( ICAL_VEVENT_COMPONENT );

  writeIncidence( vevent, event, tzlist, tzUsedList );

  // start time
  icalproperty *prop;
  icaltimetype start;
  if ( event->allDay() ) {
    start = writeICalDate( event->dtStart().date() );
    prop = icalproperty_new_dtstart( start );
  } else {
    prop = writeICalDateTimeProperty(
      ICAL_DTSTART_PROPERTY, event->dtStart(), tzlist, tzUsedList );
  }
  icalcomponent_add_property( vevent, prop );

  if ( event->hasEndDate() ) {
    // End time.
    // RFC2445 says that if DTEND is present, it has to be greater than DTSTART.
    icaltimetype end;
    KDateTime dt = event->dtEnd();
    if ( event->allDay() ) {
      // +1 day because end date is non-inclusive.
      end = writeICalDate( dt.date().addDays( 1 ) );
      icalcomponent_add_property( vevent, icalproperty_new_dtend(end) );
    } else {
      if ( dt != event->dtStart() ) {
        icalcomponent_add_property(
          vevent, writeICalDateTimeProperty(
            ICAL_DTEND_PROPERTY, dt, tzlist, tzUsedList ) );
      }
    }
  }

// TODO: resources
#if 0
  // resources
  QStringList tmpStrList = anEvent->resources();
  QString tmpStr = tmpStrList.join( ";" );
  if ( !tmpStr.isEmpty() ) {
    addPropValue( vevent, VCResourcesProp, tmpStr.toUtf8() );
  }

#endif

  // Transparency
  switch( event->transparency() ) {
  case Event::Transparent:
    icalcomponent_add_property(
      vevent,
      icalproperty_new_transp( ICAL_TRANSP_TRANSPARENT ) );
    break;
  case Event::Opaque:
    icalcomponent_add_property(
      vevent,
      icalproperty_new_transp( ICAL_TRANSP_OPAQUE ) );
    break;
  }

  return vevent;
}

icalcomponent *ICalFormatImpl::writeFreeBusy( FreeBusy *freebusy,
                                              iTIPMethod method )
{
  icalcomponent *vfreebusy = icalcomponent_new( ICAL_VFREEBUSY_COMPONENT );

  d->writeIncidenceBase( vfreebusy, freebusy );

  icalcomponent_add_property(
    vfreebusy, icalproperty_new_dtstart( writeICalUtcDateTime( freebusy->dtStart() ) ) );

  icalcomponent_add_property(
    vfreebusy, icalproperty_new_dtend( writeICalUtcDateTime( freebusy->dtEnd() ) ) );

  if ( method == iTIPRequest ) {
    icalcomponent_add_property(
      vfreebusy, icalproperty_new_uid( freebusy->uid().toUtf8() ) );
  }

  //Loops through all the periods in the freebusy object
  QList<Period> list = freebusy->busyPeriods();
  icalperiodtype period = icalperiodtype_null_period();
  for ( int i = 0, count = list.count(); i < count; ++i ) {
    period.start = writeICalUtcDateTime( list[i].start() );
    if ( list[i].hasDuration() ) {
      period.duration = writeICalDuration( list[i].duration() );
    } else {
      period.end = writeICalUtcDateTime( list[i].end() );
    }
    icalcomponent_add_property(
      vfreebusy, icalproperty_new_freebusy( period ) );
  }

  return vfreebusy;
}

icalcomponent *ICalFormatImpl::writeJournal( Journal *journal,
                                             ICalTimeZones *tzlist,
                                             ICalTimeZones *tzUsedList )
{
  icalcomponent *vjournal = icalcomponent_new( ICAL_VJOURNAL_COMPONENT );

  writeIncidence( vjournal, journal, tzlist, tzUsedList );

  // start time
  icalproperty *prop;
  KDateTime dt = journal->dtStart();
  if ( dt.isValid() ) {
    icaltimetype start;
    if ( journal->allDay() ) {
      start = writeICalDate( dt.date() );
      prop = icalproperty_new_dtstart( start );
    } else {
      prop = writeICalDateTimeProperty(
        ICAL_DTSTART_PROPERTY, dt, tzlist, tzUsedList );
    }
    icalcomponent_add_property( vjournal, prop );
  }

  return vjournal;
}

void ICalFormatImpl::writeIncidence( icalcomponent *parent,
                                     Incidence *incidence,
                                     ICalTimeZones *tzlist,
                                     ICalTimeZones *tzUsedList )
{
  if ( incidence->schedulingID() != incidence->uid() ) {
    // We need to store the UID in here. The rawSchedulingID will
    // go into the iCal UID component
    incidence->setCustomProperty( "LIBKCAL", "ID", incidence->uid() );
  } else {
    incidence->removeCustomProperty( "LIBKCAL", "ID" );
  }

  d->writeIncidenceBase( parent, incidence );

  // creation date
  icalcomponent_add_property(
    parent, writeICalDateTimeProperty(
      ICAL_CREATED_PROPERTY, incidence->created() ) );

  // unique id
  // If the scheduling ID is different from the real UID, the real
  // one is stored on X-REALID above
  if ( !incidence->schedulingID().isEmpty() ) {
    icalcomponent_add_property(
      parent, icalproperty_new_uid( incidence->schedulingID().toUtf8() ) );
  }

  // revision
  if ( incidence->revision() > 0 ) { // 0 is default, so don't write that out
    icalcomponent_add_property(
      parent, icalproperty_new_sequence( incidence->revision() ) );
  }

  // last modification date
  if ( incidence->lastModified().isValid() ) {
    icalcomponent_add_property(
      parent, writeICalDateTimeProperty(
        ICAL_LASTMODIFIED_PROPERTY, incidence->lastModified() ) );
  }

  // description
  if ( !incidence->description().isEmpty() ) {
    icalcomponent_add_property(
      parent, writeDescription(
        incidence->description(), incidence->descriptionIsRich() ) );
  }

  // summary
  if ( !incidence->summary().isEmpty() ) {
    icalcomponent_add_property(
      parent, writeSummary(
        incidence->summary(), incidence->summaryIsRich() ) );
  }

  // location
  if ( !incidence->location().isEmpty() ) {
    icalcomponent_add_property(
      parent, writeLocation(
        incidence->location(), incidence->locationIsRich() ) );
  }

  // status
  icalproperty_status status = ICAL_STATUS_NONE;
  switch ( incidence->status() ) {
  case Incidence::StatusTentative:
    status = ICAL_STATUS_TENTATIVE;
    break;
  case Incidence::StatusConfirmed:
    status = ICAL_STATUS_CONFIRMED;
    break;
  case Incidence::StatusCompleted:
    status = ICAL_STATUS_COMPLETED;
    break;
  case Incidence::StatusNeedsAction:
    status = ICAL_STATUS_NEEDSACTION;
    break;
  case Incidence::StatusCanceled:
    status = ICAL_STATUS_CANCELLED;
    break;
  case Incidence::StatusInProcess:
    status = ICAL_STATUS_INPROCESS;
    break;
  case Incidence::StatusDraft:
    status = ICAL_STATUS_DRAFT;
    break;
  case Incidence::StatusFinal:
    status = ICAL_STATUS_FINAL;
    break;
  case Incidence::StatusX:
  {
    icalproperty *p = icalproperty_new_status( ICAL_STATUS_X );
    icalvalue_set_x( icalproperty_get_value( p ), incidence->statusStr().toUtf8() );
    icalcomponent_add_property( parent, p );
    break;
  }
  case Incidence::StatusNone:
  default:
    break;
  }
  if ( status != ICAL_STATUS_NONE ) {
    icalcomponent_add_property( parent, icalproperty_new_status( status ) );
  }

  // secrecy
  icalproperty_class secClass;
  switch ( incidence->secrecy() ) {
  case Incidence::SecrecyPublic:
    secClass = ICAL_CLASS_PUBLIC;
    break;
  case Incidence::SecrecyConfidential:
    secClass = ICAL_CLASS_CONFIDENTIAL;
    break;
  case Incidence::SecrecyPrivate:
  default:
    secClass = ICAL_CLASS_PRIVATE;
    break;
  }
  if ( secClass != ICAL_CLASS_PUBLIC ) {
    icalcomponent_add_property( parent, icalproperty_new_class( secClass ) );
  }

  // geo
  if ( incidence->hasGeo() ) {
    icalgeotype geo;
    geo.lat = incidence->geoLatitude();
    geo.lon = incidence->geoLongitude();
    icalcomponent_add_property( parent, icalproperty_new_geo( geo ) );
  }

  // priority
  if ( incidence->priority() > 0 ) { // 0 is undefined priority
    icalcomponent_add_property(
      parent, icalproperty_new_priority( incidence->priority() ) );
  }

  // categories
  QString categories = incidence->categories().join( "," );
  if ( !categories.isEmpty() ) {
    icalcomponent_add_property(
      parent, icalproperty_new_categories( categories.toUtf8() ) );
  }

  // related event
  if ( !incidence->relatedToUid().isEmpty() ) {
    icalcomponent_add_property(
      parent, icalproperty_new_relatedto( incidence->relatedToUid().toUtf8() ) );
  }

  RecurrenceRule::List rrules( incidence->recurrence()->rRules() );
  RecurrenceRule::List::ConstIterator rit;
  for ( rit = rrules.constBegin(); rit != rrules.constEnd(); ++rit ) {
    icalcomponent_add_property(
      parent, icalproperty_new_rrule( writeRecurrenceRule( (*rit) ) ) );
  }

  RecurrenceRule::List exrules( incidence->recurrence()->exRules() );
  RecurrenceRule::List::ConstIterator exit;
  for ( exit = exrules.constBegin(); exit != exrules.constEnd(); ++exit ) {
    icalcomponent_add_property(
      parent, icalproperty_new_exrule( writeRecurrenceRule( (*exit) ) ) );
  }

  DateList dateList = incidence->recurrence()->exDates();
  DateList::ConstIterator exIt;
  for ( exIt = dateList.constBegin(); exIt != dateList.constEnd(); ++exIt ) {
    icalcomponent_add_property(
      parent, icalproperty_new_exdate( writeICalDate(*exIt) ) );
  }

  DateTimeList dateTimeList = incidence->recurrence()->exDateTimes();
  DateTimeList::ConstIterator extIt;
  for ( extIt = dateTimeList.constBegin(); extIt != dateTimeList.constEnd(); ++extIt ) {
    icalcomponent_add_property(
      parent, writeICalDateTimeProperty( ICAL_EXDATE_PROPERTY, *extIt, tzlist, tzUsedList ) );
  }

  dateList = incidence->recurrence()->rDates();
  DateList::ConstIterator rdIt;
  for ( rdIt = dateList.constBegin(); rdIt != dateList.constEnd(); ++rdIt ) {
    icalcomponent_add_property(
      parent, icalproperty_new_rdate( writeICalDatePeriod(*rdIt) ) );
  }
  dateTimeList = incidence->recurrence()->rDateTimes();
  DateTimeList::ConstIterator rdtIt;
  for ( rdtIt = dateTimeList.constBegin(); rdtIt != dateTimeList.constEnd(); ++rdtIt ) {
    icalcomponent_add_property(
      parent, writeICalDateTimeProperty( ICAL_RDATE_PROPERTY, *rdtIt, tzlist, tzUsedList ) );
  }

  // attachments
  Attachment::List attachments = incidence->attachments();
  Attachment::List::ConstIterator atIt;
  for ( atIt = attachments.constBegin(); atIt != attachments.constEnd(); ++atIt ) {
    icalcomponent_add_property( parent, writeAttachment( *atIt ) );
  }

  // alarms
  Alarm::List::ConstIterator alarmIt;
  for ( alarmIt = incidence->alarms().constBegin();
        alarmIt != incidence->alarms().constEnd(); ++alarmIt ) {
    if ( (*alarmIt)->enabled() ) {
      icalcomponent_add_component( parent, writeAlarm( *alarmIt ) );
    }
  }

  // duration
  if ( incidence->hasDuration() ) {
    icaldurationtype duration;
    duration = writeICalDuration( incidence->duration() );
    icalcomponent_add_property( parent, icalproperty_new_duration( duration ) );
  }
}

//@cond PRIVATE
void ICalFormatImpl::Private::writeIncidenceBase( icalcomponent *parent,
                                                  IncidenceBase *incidenceBase )
{
  icalcomponent_add_property(
    parent, writeICalDateTimeProperty(
      ICAL_DTSTAMP_PROPERTY, KDateTime::currentUtcDateTime() ) );

  // organizer stuff
  if ( !incidenceBase->organizer().isEmpty() ) {
    icalproperty *p = mImpl->writeOrganizer( incidenceBase->organizer() );
    if ( p ) {
      icalcomponent_add_property( parent, p );
    }
  }

  // attendees
  if ( incidenceBase->attendeeCount() > 0 ) {
    Attendee::List::ConstIterator it;
    for ( it = incidenceBase->attendees().constBegin();
          it != incidenceBase->attendees().constEnd(); ++it ) {
      icalproperty *p = mImpl->writeAttendee( *it );
      if ( p ) {
        icalcomponent_add_property( parent, p );
      }
    }
  }

  // comments
  QStringList comments = incidenceBase->comments();
  for ( QStringList::const_iterator it = comments.constBegin(); it != comments.constEnd(); ++it ) {
    icalcomponent_add_property( parent, icalproperty_new_comment( (*it).toUtf8() ) );
  }

  // custom properties
  writeCustomProperties( parent, incidenceBase );
}

void ICalFormatImpl::Private::writeCustomProperties( icalcomponent *parent,
                                                     CustomProperties *properties )
{
  const QMap<QByteArray, QString> custom = properties->customProperties();
  for ( QMap<QByteArray, QString>::ConstIterator c = custom.begin();  c != custom.end();  ++c ) {

    icalproperty *p = icalproperty_new_x( c.value().toUtf8() );
    if ( !c.key().startsWith( "X-KDE" ) &&          //krazy:exclude=strings
         !c.key().startsWith( "X-LibKCal" ) &&      //krazy:exclude=strings
         !c.key().startsWith( "X-MICROSOFT" ) &&    //krazy:exclude=strings
         !c.key().startsWith( "X-MOZ" ) &&          //krazy:exclude=strings
         !c.key().startsWith( "X-PILOT" ) ) {       //krazy:exclude=strings
      // use text values for the typical X-FOO property.
      // except for vendor specific X-FOO properties.
      icalvalue *text = icalvalue_new_text( c.value().toUtf8().data() );
      icalproperty_set_value( p, text );
    }
    icalproperty_set_x_name( p, c.key() );
    icalcomponent_add_property( parent, p );
  }
}
//@endcond

icalproperty *ICalFormatImpl::writeOrganizer( const Person &organizer )
{
  if ( organizer.email().isEmpty() ) {
    return 0;
  }

  icalproperty *p = icalproperty_new_organizer( QByteArray(QByteArray("MAILTO:") + organizer.email().toUtf8()) );

  if ( !organizer.name().isEmpty() ) {
    icalproperty_add_parameter(
      p, icalparameter_new_cn( quoteForParam( organizer.name() ).toUtf8() ) );
  }
  // TODO: Write dir, sent-by and language

  return p;
}

icalproperty *ICalFormatImpl::writeDescription( const QString &description, bool isRich )
{
  icalproperty *p = icalproperty_new_description( description.toUtf8() );
  if ( isRich ) {
    icalproperty_add_parameter( p, icalparameter_new_from_string( "X-KDE-TEXTFORMAT=HTML" ) );
  }
  return p;
}

icalproperty *ICalFormatImpl::writeSummary( const QString &summary, bool isRich )
{
  icalproperty *p = icalproperty_new_summary( summary.toUtf8() );
  if ( isRich ) {
    icalproperty_add_parameter( p, icalparameter_new_from_string( "X-KDE-TEXTFORMAT=HTML" ) );
  }
  return p;
}

icalproperty *ICalFormatImpl::writeLocation( const QString &location, bool isRich )
{
  icalproperty *p = icalproperty_new_location( location.toUtf8() );
  if ( isRich ) {
    icalproperty_add_parameter( p, icalparameter_new_from_string( "X-KDE-TEXTFORMAT=HTML" ) );
  }
  return p;
}

icalproperty *ICalFormatImpl::writeAttendee( Attendee *attendee )
{
  if ( attendee->email().isEmpty() ) {
    return 0;
  }

  icalproperty *p =
    icalproperty_new_attendee( QByteArray(QByteArray("mailto:") + attendee->email().toUtf8()) );

  if ( !attendee->name().isEmpty() ) {
    icalproperty_add_parameter(
      p, icalparameter_new_cn( quoteForParam( attendee->name() ).toUtf8() ) );
  }

  icalproperty_add_parameter(
    p, icalparameter_new_rsvp( attendee->RSVP() ? ICAL_RSVP_TRUE : ICAL_RSVP_FALSE ) );

  icalparameter_partstat status = ICAL_PARTSTAT_NEEDSACTION;
  switch ( attendee->status() ) {
  default:
  case Attendee::NeedsAction:
    status = ICAL_PARTSTAT_NEEDSACTION;
    break;
  case Attendee::Accepted:
    status = ICAL_PARTSTAT_ACCEPTED;
    break;
  case Attendee::Declined:
    status = ICAL_PARTSTAT_DECLINED;
    break;
  case Attendee::Tentative:
    status = ICAL_PARTSTAT_TENTATIVE;
    break;
  case Attendee::Delegated:
    status = ICAL_PARTSTAT_DELEGATED;
    break;
  case Attendee::Completed:
    status = ICAL_PARTSTAT_COMPLETED;
    break;
  case Attendee::InProcess:
    status = ICAL_PARTSTAT_INPROCESS;
    break;
  }
  icalproperty_add_parameter( p, icalparameter_new_partstat( status ) );

  icalparameter_role role = ICAL_ROLE_REQPARTICIPANT;
  switch ( attendee->role() ) {
  case Attendee::Chair:
    role = ICAL_ROLE_CHAIR;
    break;
  default:
  case Attendee::ReqParticipant:
    role = ICAL_ROLE_REQPARTICIPANT;
    break;
  case Attendee::OptParticipant:
    role = ICAL_ROLE_OPTPARTICIPANT;
    break;
  case Attendee::NonParticipant:
    role = ICAL_ROLE_NONPARTICIPANT;
    break;
  }
  icalproperty_add_parameter( p, icalparameter_new_role( role ) );

  if ( !attendee->uid().isEmpty() ) {
    icalparameter *icalparameter_uid =
      icalparameter_new_x( attendee->uid().toUtf8() );

    icalparameter_set_xname( icalparameter_uid, "X-UID" );
    icalproperty_add_parameter( p, icalparameter_uid );
  }

  if ( !attendee->delegate().isEmpty() ) {
    icalparameter *icalparameter_delegate =
      icalparameter_new_delegatedto( attendee->delegate().toUtf8() );
    icalproperty_add_parameter( p, icalparameter_delegate );
  }

  if ( !attendee->delegator().isEmpty() ) {
    icalparameter *icalparameter_delegator =
      icalparameter_new_delegatedfrom( attendee->delegator().toUtf8() );
    icalproperty_add_parameter( p, icalparameter_delegator );
  }

  const QMap<QByteArray, QString> customMap = attendee->customProperties().customProperties();
  for ( QMap<QByteArray, QString>::ConstIterator cit = customMap.constBegin();
        cit != customMap.constEnd(); ++cit ) {
    icalparameter *icalparameter_x = icalparameter_new_x( cit.value().toUtf8() );
    icalparameter_set_xname( icalparameter_x, cit.key() );
    icalproperty_add_parameter( p, icalparameter_x );
  }

  return p;
}

icalproperty *ICalFormatImpl::writeAttachment( Attachment *att )
{
  icalattach *attach;
  if ( att->isUri() ) {
    attach = icalattach_new_from_url( att->uri().toUtf8().data() );
  } else {
    attach = icalattach_new_from_data ( ( const char * )att->data(), 0, 0 );
  }
  icalproperty *p = icalproperty_new_attach( attach );

  if ( !att->mimeType().isEmpty() ) {
    icalproperty_add_parameter(
      p, icalparameter_new_fmttype( att->mimeType().toUtf8().data() ) );
  }

  if ( att->isBinary() ) {
    icalproperty_add_parameter(
      p, icalparameter_new_value( ICAL_VALUE_BINARY ) );
    icalproperty_add_parameter(
      p, icalparameter_new_encoding( ICAL_ENCODING_BASE64 ) );
  }

  if ( att->showInline() ) {
    icalparameter *icalparameter_inline = icalparameter_new_x( "inline" );
    icalparameter_set_xname( icalparameter_inline, "X-CONTENT-DISPOSITION" );
    icalproperty_add_parameter( p, icalparameter_inline );
  }

  if ( !att->label().isEmpty() ) {
    icalparameter *icalparameter_label =
      icalparameter_new_x( att->label().toUtf8() );
    icalparameter_set_xname( icalparameter_label, "X-LABEL" );
    icalproperty_add_parameter( p, icalparameter_label );
  }

  if ( att->isLocal() ) {
    icalparameter *icalparameter_local = icalparameter_new_x( "local" );
    icalparameter_set_xname( icalparameter_local, "X-KONTACT-TYPE" );
    icalproperty_add_parameter( p, icalparameter_local );
  }

  return p;
}

icalrecurrencetype ICalFormatImpl::writeRecurrenceRule( RecurrenceRule *recur )
{
  icalrecurrencetype r;
  icalrecurrencetype_clear( &r );

  switch( recur->recurrenceType() ) {
  case RecurrenceRule::rSecondly:
    r.freq = ICAL_SECONDLY_RECURRENCE;
    break;
  case RecurrenceRule::rMinutely:
    r.freq = ICAL_MINUTELY_RECURRENCE;
    break;
  case RecurrenceRule::rHourly:
    r.freq = ICAL_HOURLY_RECURRENCE;
    break;
  case RecurrenceRule::rDaily:
    r.freq = ICAL_DAILY_RECURRENCE;
    break;
  case RecurrenceRule::rWeekly:
    r.freq = ICAL_WEEKLY_RECURRENCE;
    break;
  case RecurrenceRule::rMonthly:
    r.freq = ICAL_MONTHLY_RECURRENCE;
    break;
  case RecurrenceRule::rYearly:
    r.freq = ICAL_YEARLY_RECURRENCE;
    break;
  default:
    r.freq = ICAL_NO_RECURRENCE;
    kDebug() << "no recurrence";
    break;
  }

  int index = 0;
  QList<int> bys;
  QList<int>::ConstIterator it;

  // Now write out the BY* parts:
  bys = recur->bySeconds();
  index = 0;
  for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) {
    r.by_second[index++] = static_cast<short>( *it );
  }

  bys = recur->byMinutes();
  index = 0;
  for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) {
    r.by_minute[index++] = static_cast<short>( *it );
  }

  bys = recur->byHours();
  index = 0;
  for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) {
    r.by_hour[index++] = static_cast<short>( *it );
  }

  bys = recur->byMonthDays();
  index = 0;
  for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) {
    short dShort = static_cast<short>( ( *it ) * 8 );
    r.by_month_day[index++] = static_cast<short>( icalrecurrencetype_day_position( dShort ) );
  }

  bys = recur->byYearDays();
  index = 0;
  for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) {
    r.by_year_day[index++] = static_cast<short>( *it );
  }

  bys = recur->byWeekNumbers();
  index = 0;
  for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) {
    r.by_week_no[index++] = static_cast<short>( *it );
  }

  bys = recur->byMonths();
  index = 0;
  for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) {
    r.by_month[index++] = static_cast<short>( *it );
  }

  bys = recur->bySetPos();
  index = 0;
  for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) {
    r.by_set_pos[index++] = static_cast<short>( *it );
  }

  QList<RecurrenceRule::WDayPos> byd = recur->byDays();
  int day;
  index = 0;
  for ( QList<RecurrenceRule::WDayPos>::ConstIterator dit = byd.constBegin();
        dit != byd.constEnd(); ++dit ) {
    day = (*dit).day() % 7 + 1;     // convert from Monday=1 to Sunday=1
    if ( (*dit).pos() < 0 ) {
      day += ( -(*dit).pos() ) * 8;
      day = -day;
    } else {
      day += (*dit).pos() * 8;
    }
    r.by_day[index++] = static_cast<short>( day );
  }

  r.week_start =
    static_cast<icalrecurrencetype_weekday>( recur->weekStart() % 7 + 1 );

  if ( recur->frequency() > 1 ) {
    // Dont' write out INTERVAL=1, because that's the default anyway
    r.interval = static_cast<short>( recur->frequency() );
  }

  if ( recur->duration() > 0 ) {
    r.count = recur->duration();
  } else if ( recur->duration() == -1 ) {
    r.count = 0;
  } else {
    if ( recur->allDay() ) {
      r.until = writeICalDate( recur->endDt().date() );
    } else {
      r.until = writeICalUtcDateTime( recur->endDt() );
    }
  }

  return r;
}

icalcomponent *ICalFormatImpl::writeAlarm( Alarm *alarm )
{
  icalcomponent *a = icalcomponent_new( ICAL_VALARM_COMPONENT );

  icalproperty_action action;
  icalattach *attach = 0;

  switch ( alarm->type() ) {
  case Alarm::Procedure:
    action = ICAL_ACTION_PROCEDURE;
    attach = icalattach_new_from_url(
      QFile::encodeName( alarm->programFile() ).data() );
    icalcomponent_add_property( a, icalproperty_new_attach( attach ) );
    if ( !alarm->programArguments().isEmpty() ) {
      icalcomponent_add_property(
        a, icalproperty_new_description( alarm->programArguments().toUtf8() ) );
    }
    break;
  case Alarm::Audio:
    action = ICAL_ACTION_AUDIO;
    if ( !alarm->audioFile().isEmpty() ) {
      attach = icalattach_new_from_url(
        QFile::encodeName( alarm->audioFile() ).data() );
      icalcomponent_add_property( a, icalproperty_new_attach( attach ) );
    }
    break;
  case Alarm::Email:
  {
    action = ICAL_ACTION_EMAIL;
    const QList<Person> addresses = alarm->mailAddresses();
    for ( QList<Person>::ConstIterator ad = addresses.constBegin();
          ad != addresses.constEnd();  ++ad ) {
      if ( !(*ad).email().isEmpty() ) {
        icalproperty *p = icalproperty_new_attendee( QByteArray(QByteArray("MAILTO:") + (*ad).email().toUtf8()) );
        if ( !(*ad).name().isEmpty() ) {
          icalproperty_add_parameter(
            p, icalparameter_new_cn( quoteForParam( (*ad).name() ).toUtf8() ) );
        }
        icalcomponent_add_property( a, p );
      }
    }
    icalcomponent_add_property(
      a, icalproperty_new_summary( alarm->mailSubject().toUtf8() ) );
    icalcomponent_add_property(
      a, icalproperty_new_description( alarm->mailText().toUtf8() ) );
    QStringList attachments = alarm->mailAttachments();
    if ( attachments.count() > 0 ) {
      for ( QStringList::const_iterator at = attachments.constBegin();
            at != attachments.constEnd();  ++at ) {
        attach = icalattach_new_from_url( QFile::encodeName( *at ).data() );
        icalcomponent_add_property( a, icalproperty_new_attach( attach ) );
      }
    }
    break;
  }
  case Alarm::Display:
    action = ICAL_ACTION_DISPLAY;
    icalcomponent_add_property(
      a, icalproperty_new_description( alarm->text().toUtf8() ) );
    break;
  case Alarm::Invalid:
  default:
    kDebug() << "Unknown type of alarm";
    action = ICAL_ACTION_NONE;
    break;
  }
  icalcomponent_add_property( a, icalproperty_new_action( action ) );

  // Trigger time
  icaltriggertype trigger;
  if ( alarm->hasTime() ) {
    trigger.time = writeICalUtcDateTime( alarm->time() );
    trigger.duration = icaldurationtype_null_duration();
  } else {
    trigger.time = icaltime_null_time();
    Duration offset;
    if ( alarm->hasStartOffset() ) {
      offset = alarm->startOffset();
    } else {
      offset = alarm->endOffset();
    }
    trigger.duration = writeICalDuration( offset );
  }
  icalproperty *p = icalproperty_new_trigger( trigger );
  if ( alarm->hasEndOffset() ) {
    icalproperty_add_parameter( p, icalparameter_new_related( ICAL_RELATED_END ) );
  }
  icalcomponent_add_property( a, p );

  // Repeat count and duration
  if ( alarm->repeatCount() ) {
    icalcomponent_add_property(
      a, icalproperty_new_repeat( alarm->repeatCount() ) );
    icalcomponent_add_property(
      a, icalproperty_new_duration( writeICalDuration( alarm->snoozeTime() ) ) );
  }

  // Custom properties
  const QMap<QByteArray, QString> custom = alarm->customProperties();
  for ( QMap<QByteArray, QString>::ConstIterator c = custom.begin();  c != custom.end();  ++c ) {
    icalproperty *p = icalproperty_new_x( c.value().toUtf8() );
    icalproperty_set_x_name( p, c.key() );
    icalcomponent_add_property( a, p );
  }

  return a;
}

Todo *ICalFormatImpl::readTodo( icalcomponent *vtodo, ICalTimeZones *tzlist )
{
  Todo *todo = new Todo;

  readIncidence( vtodo, todo, tzlist );

  icalproperty *p = icalcomponent_get_first_property( vtodo, ICAL_ANY_PROPERTY );

  while ( p ) {
    icalproperty_kind kind = icalproperty_isa(p);
    switch ( kind ) {
    case ICAL_DUE_PROPERTY:
    { // due date/time
      KDateTime kdt = readICalDateTimeProperty( p, tzlist );
      todo->setDtDue( kdt, true );
      todo->setHasDueDate( true );
      todo->setAllDay( kdt.isDateOnly() );
      break;
    }
    case ICAL_COMPLETED_PROPERTY:  // completion date/time
      todo->setCompleted( readICalDateTimeProperty( p, tzlist ) );
      break;

    case ICAL_PERCENTCOMPLETE_PROPERTY:  // Percent completed
      todo->setPercentComplete( icalproperty_get_percentcomplete( p ) );
      break;

    case ICAL_RELATEDTO_PROPERTY:  // related todo (parent)
      todo->setRelatedToUid( QString::fromUtf8( icalproperty_get_relatedto( p ) ) );
      d->mTodosRelate.append( todo );
      break;

    case ICAL_DTSTART_PROPERTY:
      // Flag that todo has start date. Value is read in by readIncidence().
      if ( todo->comments().filter( "NoStartDate" ).count() ) {
        todo->setHasStartDate( false );
      } else {
        todo->setHasStartDate( true );
      }
      break;

    case ICAL_RECURRENCEID_PROPERTY:
      todo->setDtRecurrence( readICalDateTimeProperty( p, tzlist ) );
      break;

    default:
      // TODO: do something about unknown properties?
      break;
    }

    p = icalcomponent_get_next_property( vtodo, ICAL_ANY_PROPERTY );
  }

  if ( d->mCompat ) {
    d->mCompat->fixEmptySummary( todo );
  }

  return todo;
}

Event *ICalFormatImpl::readEvent( icalcomponent *vevent, ICalTimeZones *tzlist )
{
  Event *event = new Event;

  readIncidence( vevent, event, tzlist );

  icalproperty *p = icalcomponent_get_first_property( vevent, ICAL_ANY_PROPERTY );

  bool dtEndProcessed = false;

  while ( p ) {
    icalproperty_kind kind = icalproperty_isa( p );
    switch ( kind ) {
    case ICAL_DTEND_PROPERTY:
    { // end date and time
      KDateTime kdt = readICalDateTimeProperty( p, tzlist );
      if ( kdt.isDateOnly() ) {
        // End date is non-inclusive
        QDate endDate = kdt.date().addDays( -1 );
        if ( d->mCompat ) {
          d->mCompat->fixFloatingEnd( endDate );
        }
        if ( endDate < event->dtStart().date() ) {
          endDate = event->dtStart().date();
        }
        event->setDtEnd( KDateTime( endDate, event->dtStart().timeSpec() ) );
      } else {
        event->setDtEnd( kdt );
        event->setAllDay( false );
      }
      dtEndProcessed = true;
      break;
    }
    case ICAL_RELATEDTO_PROPERTY:  // related event (parent)
      event->setRelatedToUid( QString::fromUtf8( icalproperty_get_relatedto( p ) ) );
      d->mEventsRelate.append( event );
      break;

    case ICAL_TRANSP_PROPERTY:  // Transparency
    {
      icalproperty_transp transparency = icalproperty_get_transp( p );
      if ( transparency == ICAL_TRANSP_TRANSPARENT ) {
        event->setTransparency( Event::Transparent );
      } else {
        event->setTransparency( Event::Opaque );
      }
      break;
    }

    default:
      // TODO: do something about unknown properties?
      break;
    }

    p = icalcomponent_get_next_property( vevent, ICAL_ANY_PROPERTY );
  }

  // according to rfc2445 the dtend shouldn't be written when it equals
  // start date. so assign one equal to start date.
  if ( !dtEndProcessed && !event->hasDuration() ) {
    event->setDtEnd( event->dtStart() );
    event->setHasEndDate( false );
  }

  QString msade = event->nonKDECustomProperty( "X-MICROSOFT-CDO-ALLDAYEVENT" );
  if ( !msade.isEmpty() ) {
    bool allDay = ( msade == QLatin1String( "TRUE" ) );
    event->setAllDay( allDay );
  }

  if ( d->mCompat ) {
    d->mCompat->fixEmptySummary( event );
  }

  return event;
}

FreeBusy *ICalFormatImpl::readFreeBusy( icalcomponent *vfreebusy )
{
  FreeBusy *freebusy = new FreeBusy;

  d->readIncidenceBase( vfreebusy, freebusy );

  icalproperty *p = icalcomponent_get_first_property( vfreebusy, ICAL_ANY_PROPERTY );

  FreeBusyPeriod::List periods;
  while ( p ) {
    icalproperty_kind kind = icalproperty_isa( p );
    switch ( kind ) {

    case ICAL_DTSTART_PROPERTY:  // start date and time (UTC)
      freebusy->setDtStart( readICalUtcDateTimeProperty( p ) );
      break;

    case ICAL_DTEND_PROPERTY:  // end Date and Time (UTC)
      freebusy->setDtEnd( readICalUtcDateTimeProperty( p ) );
      break;

    case ICAL_FREEBUSY_PROPERTY: //Any FreeBusy Times (UTC)
    {
      icalperiodtype icalperiod = icalproperty_get_freebusy( p );
      KDateTime period_start = readICalUtcDateTime( p, icalperiod.start );
      FreeBusyPeriod period;
      if ( !icaltime_is_null_time( icalperiod.end ) ) {
        KDateTime period_end = readICalUtcDateTime( p, icalperiod.end );
        period = FreeBusyPeriod( period_start, period_end );
      } else {
        Duration duration ( readICalDuration( icalperiod.duration ) );
        period = FreeBusyPeriod( period_start, duration );
      }

      icalparameter *param = icalproperty_get_first_parameter( p, ICAL_X_PARAMETER );
      while ( param ) {
        if ( strncmp( icalparameter_get_xname( param ), "X-SUMMARY", 9 ) == 0 ) {
          period.setSummary( QString::fromUtf8(
                               KCodecs::base64Decode( icalparameter_get_xvalue( param ) ) ) );
        }
        if ( strncmp( icalparameter_get_xname( param ), "X-LOCATION", 10 ) == 0 ) {
          period.setLocation( QString::fromUtf8(
                                KCodecs::base64Decode( icalparameter_get_xvalue( param ) ) ) );
        }
        param = icalproperty_get_next_parameter( p, ICAL_X_PARAMETER );
      }
      periods.append( period );
      break;
    }

    default:
      // TODO: do something about unknown properties?
      break;
    }
    p = icalcomponent_get_next_property( vfreebusy, ICAL_ANY_PROPERTY );
  }
  freebusy->addPeriods( periods );

  return freebusy;
}

Journal *ICalFormatImpl::readJournal( icalcomponent *vjournal,
                                      ICalTimeZones *tzlist )
{
  Journal *journal = new Journal;
  readIncidence( vjournal, journal, tzlist );

  return journal;
}

Attendee *ICalFormatImpl::readAttendee( icalproperty *attendee )
{
  // the following is a hack to support broken calendars (like WebCalendar 1.0.x)
  // that include non-RFC-compliant attendees.  Otherwise libical 0.42 asserts.
  if ( !icalproperty_get_value( attendee ) ) {
    return 0;
  }

  icalparameter *p = 0;

  QString email = QString::fromUtf8( icalproperty_get_attendee( attendee ) );
  if ( email.startsWith( QLatin1String( "mailto:" ), Qt::CaseInsensitive ) ) {
    email = email.mid( 7 );
  }

  QString name;
  QString uid;
  p = icalproperty_get_first_parameter( attendee, ICAL_CN_PARAMETER );
  if ( p ) {
    name = QString::fromUtf8( icalparameter_get_cn( p ) );
  } else {
  }

  bool rsvp = false;
  p = icalproperty_get_first_parameter( attendee, ICAL_RSVP_PARAMETER );
  if ( p ) {
    icalparameter_rsvp rsvpParameter = icalparameter_get_rsvp( p );
    if ( rsvpParameter == ICAL_RSVP_TRUE ) {
      rsvp = true;
    }
  }

  Attendee::PartStat status = Attendee::NeedsAction;
  p = icalproperty_get_first_parameter( attendee, ICAL_PARTSTAT_PARAMETER );
  if ( p ) {
    icalparameter_partstat partStatParameter = icalparameter_get_partstat( p );
    switch( partStatParameter ) {
    default:
    case ICAL_PARTSTAT_NEEDSACTION:
      status = Attendee::NeedsAction;
      break;
    case ICAL_PARTSTAT_ACCEPTED:
      status = Attendee::Accepted;
      break;
    case ICAL_PARTSTAT_DECLINED:
      status = Attendee::Declined;
      break;
    case ICAL_PARTSTAT_TENTATIVE:
      status = Attendee::Tentative;
      break;
    case ICAL_PARTSTAT_DELEGATED:
      status = Attendee::Delegated;
      break;
    case ICAL_PARTSTAT_COMPLETED:
      status = Attendee::Completed;
      break;
    case ICAL_PARTSTAT_INPROCESS:
      status = Attendee::InProcess;
      break;
    }
  }

  Attendee::Role role = Attendee::ReqParticipant;
  p = icalproperty_get_first_parameter( attendee, ICAL_ROLE_PARAMETER );
  if ( p ) {
    icalparameter_role roleParameter = icalparameter_get_role( p );
    switch( roleParameter ) {
    case ICAL_ROLE_CHAIR:
      role = Attendee::Chair;
      break;
    default:
    case ICAL_ROLE_REQPARTICIPANT:
      role = Attendee::ReqParticipant;
      break;
    case ICAL_ROLE_OPTPARTICIPANT:
      role = Attendee::OptParticipant;
      break;
    case ICAL_ROLE_NONPARTICIPANT:
      role = Attendee::NonParticipant;
      break;
    }
  }

  p = icalproperty_get_first_parameter( attendee, ICAL_X_PARAMETER );
  QMap<QByteArray, QString> custom;
  while ( p ) {
    QString xname = QString( icalparameter_get_xname( p ) ).toUpper();
    QString xvalue = QString::fromUtf8( icalparameter_get_xvalue( p ) );
    if ( xname == "X-UID" ) {
      uid = xvalue;
    } else {
      custom[xname.toUtf8()] = xvalue;
    }
    p = icalproperty_get_next_parameter( attendee, ICAL_X_PARAMETER );
  }

  Attendee *a = new Attendee( name, email, rsvp, status, role, uid );
  a->customProperties().setCustomProperties( custom );

  p = icalproperty_get_first_parameter( attendee, ICAL_DELEGATEDTO_PARAMETER );
  if ( p ) {
    a->setDelegate( icalparameter_get_delegatedto( p ) );
  }

  p = icalproperty_get_first_parameter( attendee, ICAL_DELEGATEDFROM_PARAMETER );
  if ( p ) {
    a->setDelegator( icalparameter_get_delegatedfrom( p ) );
  }

  return a;
}

Person ICalFormatImpl::readOrganizer( icalproperty *organizer )
{
  QString email = QString::fromUtf8( icalproperty_get_organizer( organizer ) );
  if ( email.startsWith( QLatin1String( "mailto:" ), Qt::CaseInsensitive ) ) {
    email = email.mid( 7 );
  }
  QString cn;

  icalparameter *p = icalproperty_get_first_parameter( organizer, ICAL_CN_PARAMETER );

  if ( p ) {
    cn = QString::fromUtf8( icalparameter_get_cn( p ) );
  }
  Person org( cn, email );
  // TODO: Treat sent-by, dir and language here, too
  return org;
}

Attachment *ICalFormatImpl::readAttachment( icalproperty *attach )
{
  Attachment *attachment = 0;

  const char *p;
  icalvalue *value = icalproperty_get_value( attach );

  switch( icalvalue_isa( value ) ) {
  case ICAL_ATTACH_VALUE:
  {
    icalattach *a = icalproperty_get_attach( attach );
    if ( !icalattach_get_is_url( a ) ) {
      p = (const char *)icalattach_get_data( a );
      if ( p ) {
        attachment = new Attachment( p );
      }
    } else {
      p = icalattach_get_url( a );
      if ( p ) {
        attachment = new Attachment( QString::fromUtf8( p ) );
      }
    }
    break;
  }
  case ICAL_BINARY_VALUE:
  {
    icalattach *a = icalproperty_get_attach( attach );
    p = (const char *)icalattach_get_data( a );
    if ( p ) {
      attachment = new Attachment( p );
    }
    break;
  }
  case ICAL_URI_VALUE:
    p = icalvalue_get_uri( value );
    attachment = new Attachment( QString::fromUtf8( p ) );
    break;
  default:
    break;
  }

  if ( attachment ) {
    icalparameter *p =
      icalproperty_get_first_parameter( attach, ICAL_FMTTYPE_PARAMETER );
    if ( p ) {
      attachment->setMimeType( QString( icalparameter_get_fmttype( p ) ) );
    }

    p = icalproperty_get_first_parameter( attach, ICAL_X_PARAMETER );
    while ( p ) {
      QString xname = QString( icalparameter_get_xname( p ) ).toUpper();
      QString xvalue = QString::fromUtf8( icalparameter_get_xvalue( p ) );
      if ( xname == "X-CONTENT-DISPOSITION" ) {
        attachment->setShowInline( xvalue.toLower() == "inline" );
      }
      if ( xname == "X-LABEL" ) {
        attachment->setLabel( xvalue );
      }
      if ( xname == "X-KONTACT-TYPE" ) {
        attachment->setLocal( xvalue.toLower() == "local" );
      }
      p = icalproperty_get_next_parameter( attach, ICAL_X_PARAMETER );
    }

    p = icalproperty_get_first_parameter( attach, ICAL_X_PARAMETER );
    while ( p ) {
      if ( strncmp( icalparameter_get_xname( p ), "X-LABEL", 7 ) == 0 ) {
        attachment->setLabel( QString::fromUtf8( icalparameter_get_xvalue( p ) ) );
      }
      p = icalproperty_get_next_parameter( attach, ICAL_X_PARAMETER );
    }
  }

  return attachment;
}

void ICalFormatImpl::readIncidence( icalcomponent *parent,
                                    Incidence *incidence,
                                    ICalTimeZones *tzlist )
{
  d->readIncidenceBase( parent, incidence );

  icalproperty *p = icalcomponent_get_first_property( parent, ICAL_ANY_PROPERTY );

  const char *text;
  int intvalue, inttext;
  icaldurationtype icalduration;
  KDateTime kdt;

  QStringList categories;

  while ( p ) {
    icalproperty_kind kind = icalproperty_isa( p );
    switch ( kind ) {
    case ICAL_CREATED_PROPERTY:
      incidence->setCreated( readICalDateTimeProperty( p, tzlist ) );
      break;

    case ICAL_SEQUENCE_PROPERTY:  // sequence
      intvalue = icalproperty_get_sequence( p );
      incidence->setRevision( intvalue );
      break;

    case ICAL_LASTMODIFIED_PROPERTY:  // last modification UTC date/time
      incidence->setLastModified( readICalDateTimeProperty( p, tzlist ) );
      break;

    case ICAL_DTSTART_PROPERTY:  // start date and time
      kdt = readICalDateTimeProperty( p, tzlist );
      incidence->setDtStart( kdt );
      incidence->setAllDay( kdt.isDateOnly() );
      break;

    case ICAL_DURATION_PROPERTY:  // start date and time
      icalduration = icalproperty_get_duration( p );
      incidence->setDuration( readICalDuration( icalduration ) );
      break;

    case ICAL_DESCRIPTION_PROPERTY:  // description
    {
      QString textStr = QString::fromUtf8( icalproperty_get_description( p ) );
      if ( !textStr.isEmpty() ) {
        QString valStr = QString::fromUtf8(
          icalproperty_get_parameter_as_string( p, "X-KDE-TEXTFORMAT" ) );
        if ( !valStr.compare( "HTML", Qt::CaseInsensitive ) ) {
          incidence->setDescription( textStr, true );
        } else {
          incidence->setDescription( textStr, false );
        }
      }
    }
    break;

    case ICAL_SUMMARY_PROPERTY:  // summary
    {
      QString textStr = QString::fromUtf8( icalproperty_get_summary( p ) );
      if ( !textStr.isEmpty() ) {
        QString valStr = QString::fromUtf8(
          icalproperty_get_parameter_as_string( p, "X-KDE-TEXTFORMAT" ) );
        if ( !valStr.compare( "HTML", Qt::CaseInsensitive ) ) {
          incidence->setSummary( textStr, true );
        } else {
          incidence->setSummary( textStr, false );
        }
      }
    }
    break;

    case ICAL_LOCATION_PROPERTY:  // location
    {
      if ( !icalproperty_get_value( p ) ) {
        //Fix for #191472. This is a pre-crash guard in case libical was
        //compiled in superstrict mode (--enable-icalerrors-are-fatal)
        //TODO: pre-crash guard other property getters too.
        break;
      }
      QString textStr = QString::fromUtf8( icalproperty_get_location( p ) );
      if ( !textStr.isEmpty() ) {
        QString valStr = QString::fromUtf8(
          icalproperty_get_parameter_as_string( p, "X-KDE-TEXTFORMAT" ) );
        if ( !valStr.compare( "HTML", Qt::CaseInsensitive ) ) {
          incidence->setLocation( textStr, true );
        } else {
          incidence->setLocation( textStr, false );
        }
      }
    }
    break;

    case ICAL_STATUS_PROPERTY:  // status
    {
      Incidence::Status stat;
      switch ( icalproperty_get_status( p ) ) {
      case ICAL_STATUS_TENTATIVE:
        stat = Incidence::StatusTentative;
        break;
      case ICAL_STATUS_CONFIRMED:
        stat = Incidence::StatusConfirmed;
        break;
      case ICAL_STATUS_COMPLETED:
        stat = Incidence::StatusCompleted;
        break;
      case ICAL_STATUS_NEEDSACTION:
        stat = Incidence::StatusNeedsAction;
        break;
      case ICAL_STATUS_CANCELLED:
        stat = Incidence::StatusCanceled;
        break;
      case ICAL_STATUS_INPROCESS:
        stat = Incidence::StatusInProcess;
        break;
      case ICAL_STATUS_DRAFT:
        stat = Incidence::StatusDraft;
        break;
      case ICAL_STATUS_FINAL:
        stat = Incidence::StatusFinal;
        break;
      case ICAL_STATUS_X:
        incidence->setCustomStatus(
          QString::fromUtf8( icalvalue_get_x( icalproperty_get_value( p ) ) ) );
        stat = Incidence::StatusX;
        break;
      case ICAL_STATUS_NONE:
      default:
        stat = Incidence::StatusNone;
        break;
      }
      if ( stat != Incidence::StatusX ) {
        incidence->setStatus( stat );
      }
      break;
    }

    case ICAL_GEO_PROPERTY:  // geo
    {
      icalgeotype geo = icalproperty_get_geo( p );
      incidence->setGeoLatitude( geo.lat );
      incidence->setGeoLongitude( geo.lon );
      incidence->setHasGeo( true );
      break;
    }

    case ICAL_PRIORITY_PROPERTY:  // priority
      intvalue = icalproperty_get_priority( p );
      if ( d->mCompat ) {
        intvalue = d->mCompat->fixPriority( intvalue );
      }
      incidence->setPriority( intvalue );
      break;

    case ICAL_CATEGORIES_PROPERTY:  // categories
    {
      // We have always supported multiple CATEGORIES properties per component
      // even though the RFC seems to indicate only 1 is permitted.
      // We can't change that -- in order to retain backwards compatibility.
      text = icalproperty_get_categories( p );
      const QString val = QString::fromUtf8( text );
      foreach ( const QString &cat, val.split( ',', QString::SkipEmptyParts ) ) {
        // ensure no duplicates
        if ( !categories.contains( cat ) ) {
          categories.append( cat );
        }
      }
      break;
    }

    case ICAL_RRULE_PROPERTY:
      readRecurrenceRule( p, incidence );
      break;

    case ICAL_RDATE_PROPERTY:
      kdt = readICalDateTimeProperty( p, tzlist );
      if ( kdt.isValid() ) {
        if ( kdt.isDateOnly() ) {
          incidence->recurrence()->addRDate( kdt.date() );
        } else {
          incidence->recurrence()->addRDateTime( kdt );
        }
      } else {
        // TODO: RDates as period are not yet implemented!
      }
      break;

    case ICAL_EXRULE_PROPERTY:
      readExceptionRule( p, incidence );
      break;

    case ICAL_EXDATE_PROPERTY:
      kdt = readICalDateTimeProperty( p, tzlist );
      if ( kdt.isDateOnly() ) {
        incidence->recurrence()->addExDate( kdt.date() );
      } else {
        incidence->recurrence()->addExDateTime( kdt );
      }
      break;

    case ICAL_CLASS_PROPERTY:
      inttext = icalproperty_get_class( p );
      if ( inttext == ICAL_CLASS_PUBLIC ) {
        incidence->setSecrecy( Incidence::SecrecyPublic );
      } else if ( inttext == ICAL_CLASS_CONFIDENTIAL ) {
        incidence->setSecrecy( Incidence::SecrecyConfidential );
      } else {
        incidence->setSecrecy( Incidence::SecrecyPrivate );
      }
      break;

    case ICAL_ATTACH_PROPERTY:  // attachments
      incidence->addAttachment( readAttachment( p ) );
      break;

    default:
      // TODO: do something about unknown properties?
      break;
    }

    p = icalcomponent_get_next_property( parent, ICAL_ANY_PROPERTY );
  }

  // Set the scheduling ID
  const QString uid = incidence->customProperty( "LIBKCAL", "ID" );
  if ( !uid.isNull() ) {
    // The UID stored in incidencebase is actually the scheduling ID
    // It has to be stored in the iCal UID component for compatibility
    // with other iCal applications
    incidence->setSchedulingID( incidence->uid() );
    incidence->setUid( uid );
  }

  // Now that recurrence and exception stuff is completely set up,
  // do any backwards compatibility adjustments.
  if ( incidence->recurs() && d->mCompat ) {
    d->mCompat->fixRecurrence( incidence );
  }

  // add categories
  incidence->setCategories( categories );

  // iterate through all alarms
  for ( icalcomponent *alarm = icalcomponent_get_first_component( parent, ICAL_VALARM_COMPONENT );
       alarm;
       alarm = icalcomponent_get_next_component( parent, ICAL_VALARM_COMPONENT ) ) {
    readAlarm( alarm, incidence, tzlist );
  }
  // Fix incorrect alarm settings by other applications (like outloook 9)
  if ( d->mCompat ) {
    d->mCompat->fixAlarms( incidence );
  }
}

//@cond PRIVATE
void ICalFormatImpl::Private::readIncidenceBase( icalcomponent *parent,
                                                 IncidenceBase *incidenceBase )
{
  icalproperty *p = icalcomponent_get_first_property( parent, ICAL_ANY_PROPERTY );

  while ( p ) {
    icalproperty_kind kind = icalproperty_isa( p );
    switch ( kind ) {
    case ICAL_UID_PROPERTY:  // unique id
      incidenceBase->setUid( QString::fromUtf8( icalproperty_get_uid( p ) ) );
      break;

    case ICAL_ORGANIZER_PROPERTY:  // organizer
      incidenceBase->setOrganizer( mImpl->readOrganizer( p ) );
      break;

    case ICAL_ATTENDEE_PROPERTY:  // attendee
      incidenceBase->addAttendee( mImpl->readAttendee( p ) );
      break;

    case ICAL_COMMENT_PROPERTY:
      incidenceBase->addComment(
        QString::fromUtf8( icalproperty_get_comment( p ) ) );
      break;

    default:
      break;
    }

    p = icalcomponent_get_next_property( parent, ICAL_ANY_PROPERTY );
  }

  // custom properties
  readCustomProperties( parent, incidenceBase );
}

void ICalFormatImpl::Private::readCustomProperties( icalcomponent *parent,
                                                    CustomProperties *properties )
{
  QMap<QByteArray, QString> customProperties;
  QString lastProperty;

  icalproperty *p = icalcomponent_get_first_property( parent, ICAL_X_PROPERTY );
  while ( p ) {
    QString value = QString::fromUtf8( icalproperty_get_x( p ) );
    const char *name = icalproperty_get_x_name( p );
    if ( lastProperty != name ) {
      customProperties[name] = value;
    } else {
      customProperties[name] = customProperties[name].append( "," ).append( value );
    }
    p = icalcomponent_get_next_property( parent, ICAL_X_PROPERTY );
    lastProperty = name;
  }

  properties->setCustomProperties( customProperties );
}
//@endcond

void ICalFormatImpl::readRecurrenceRule( icalproperty *rrule,
                                         Incidence *incidence )
{
  Recurrence *recur = incidence->recurrence();

  struct icalrecurrencetype r = icalproperty_get_rrule( rrule );
  // dumpIcalRecurrence(r);

  RecurrenceRule *recurrule = new RecurrenceRule( /*incidence*/);
  recurrule->setStartDt( incidence->dtStart() );
  readRecurrence( r, recurrule );
  recur->addRRule( recurrule );
}

void ICalFormatImpl::readExceptionRule( icalproperty *rrule,
                                        Incidence *incidence )
{
  struct icalrecurrencetype r = icalproperty_get_exrule( rrule );
  // dumpIcalRecurrence(r);

  RecurrenceRule *recurrule = new RecurrenceRule( /*incidence*/);
  recurrule->setStartDt( incidence->dtStart() );
  readRecurrence( r, recurrule );

  Recurrence *recur = incidence->recurrence();
  recur->addExRule( recurrule );
}

void ICalFormatImpl::readRecurrence( const struct icalrecurrencetype &r,
                                     RecurrenceRule *recur )
{
  // Generate the RRULE string
  recur->setRRule(
    QString( icalrecurrencetype_as_string( const_cast<struct icalrecurrencetype*>( &r ) ) ) );
  // Period
  switch ( r.freq ) {
  case ICAL_SECONDLY_RECURRENCE:
    recur->setRecurrenceType( RecurrenceRule::rSecondly );
    break;
  case ICAL_MINUTELY_RECURRENCE:
    recur->setRecurrenceType( RecurrenceRule::rMinutely );
    break;
  case ICAL_HOURLY_RECURRENCE:
    recur->setRecurrenceType( RecurrenceRule::rHourly );
    break;
  case ICAL_DAILY_RECURRENCE:
    recur->setRecurrenceType( RecurrenceRule::rDaily );
    break;
  case ICAL_WEEKLY_RECURRENCE:
    recur->setRecurrenceType( RecurrenceRule::rWeekly );
    break;
  case ICAL_MONTHLY_RECURRENCE:
    recur->setRecurrenceType( RecurrenceRule::rMonthly );
    break;
  case ICAL_YEARLY_RECURRENCE:
    recur->setRecurrenceType( RecurrenceRule::rYearly );
    break;
  case ICAL_NO_RECURRENCE:
  default:
    recur->setRecurrenceType( RecurrenceRule::rNone );
  }
  // Frequency
  recur->setFrequency( r.interval );

  // Duration & End Date
  if ( !icaltime_is_null_time( r.until ) ) {
    icaltimetype t = r.until;
    recur->setEndDt( readICalUtcDateTime( 0, t ) );
  } else {
    if ( r.count == 0 ) {
      recur->setDuration( -1 );
    } else {
      recur->setDuration( r.count );
    }
  }

  // Week start setting
  short wkst = static_cast<short>( ( r.week_start + 5 ) % 7 + 1 );
  recur->setWeekStart( wkst );

  // And now all BY*
  QList<int> lst;
  int i;
  int index = 0;

//@cond PRIVATE
#define readSetByList( rrulecomp, setfunc )                             \
  index = 0;                                                            \
  lst.clear();                                                          \
  while ( ( i = r.rrulecomp[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { \
    lst.append( i );                                                    \
  }                                                                     \
  if ( !lst.isEmpty() ) {                                               \
    recur->setfunc( lst );                                              \
  }
//@endcond

  // BYSECOND, MINUTE and HOUR, MONTHDAY, YEARDAY, WEEKNUMBER, MONTH
  // and SETPOS are standard int lists, so we can treat them with the
  // same macro
  readSetByList( by_second, setBySeconds );
  readSetByList( by_minute, setByMinutes );
  readSetByList( by_hour, setByHours );
  readSetByList( by_month_day, setByMonthDays );
  readSetByList( by_year_day, setByYearDays );
  readSetByList( by_week_no, setByWeekNumbers );
  readSetByList( by_month, setByMonths );
  readSetByList( by_set_pos, setBySetPos );
#undef readSetByList

  // BYDAY is a special case, since it's not an int list
  QList<RecurrenceRule::WDayPos> wdlst;
  short day;
  index = 0;
  while ( ( day = r.by_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
    RecurrenceRule::WDayPos pos;
    pos.setDay( static_cast<short>( ( icalrecurrencetype_day_day_of_week( day ) + 5 ) % 7 + 1 ) );
    pos.setPos( icalrecurrencetype_day_position( day ) );
    wdlst.append( pos );
  }
  if ( !wdlst.isEmpty() ) {
    recur->setByDays( wdlst );
  }

  // TODO: Store all X- fields of the RRULE inside the recurrence (so they are
  // preserved
}

void ICalFormatImpl::readAlarm( icalcomponent *alarm,
                                Incidence *incidence,
                                ICalTimeZones *tzlist )
{
  Alarm *ialarm = incidence->newAlarm();
  ialarm->setRepeatCount( 0 );
  ialarm->setEnabled( true );

  // Determine the alarm's action type
  icalproperty *p = icalcomponent_get_first_property( alarm, ICAL_ACTION_PROPERTY );
  Alarm::Type type = Alarm::Display;
  icalproperty_action action = ICAL_ACTION_DISPLAY;
  if ( !p ) {
    kDebug() << "Unknown type of alarm, using default";
    // TODO: do something about unknown alarm type?
  } else {

    action = icalproperty_get_action( p );
    switch ( action ) {
    case ICAL_ACTION_DISPLAY:
      type = Alarm::Display;
      break;
    case ICAL_ACTION_AUDIO:
      type = Alarm::Audio;
      break;
    case ICAL_ACTION_PROCEDURE:
      type = Alarm::Procedure;
      break;
    case ICAL_ACTION_EMAIL:
      type = Alarm::Email;
      break;
    default:
      break;
     // TODO: do something about invalid alarm type?
    }
  }
  ialarm->setType( type );

  p = icalcomponent_get_first_property( alarm, ICAL_ANY_PROPERTY );
  while ( p ) {
    icalproperty_kind kind = icalproperty_isa( p );

    switch ( kind ) {
    case ICAL_TRIGGER_PROPERTY:
    {
      icaltriggertype trigger = icalproperty_get_trigger( p );
      if ( !icaltime_is_null_time( trigger.time ) ) {
        //set the trigger to a specific time (which is not in rfc2445, btw)
        ialarm->setTime( readICalUtcDateTime( p, trigger.time, tzlist ) );
      } else {
        //set the trigger to an offset from the incidence start or end time.
        if ( !icaldurationtype_is_bad_duration( trigger.duration ) ) {
          Duration duration( readICalDuration( trigger.duration ) );
          icalparameter *param =
            icalproperty_get_first_parameter( p, ICAL_RELATED_PARAMETER );
          if ( param && icalparameter_get_related( param ) == ICAL_RELATED_END ) {
            ialarm->setEndOffset( duration );
          } else {
            ialarm->setStartOffset( duration );
          }
        } else {
          // a bad duration was encountered, just set a 0 duration from start
          ialarm->setStartOffset( Duration( 0 ) );
        }
      }
      break;
    }
    case ICAL_DURATION_PROPERTY:
    {
      icaldurationtype duration = icalproperty_get_duration( p );
      ialarm->setSnoozeTime( readICalDuration( duration ) );
      break;
    }
    case ICAL_REPEAT_PROPERTY:
      ialarm->setRepeatCount( icalproperty_get_repeat( p ) );
      break;

    case ICAL_DESCRIPTION_PROPERTY:
    { // Only in DISPLAY and EMAIL and PROCEDURE alarms
      QString description = QString::fromUtf8( icalproperty_get_description( p ) );
      switch ( action ) {
      case ICAL_ACTION_DISPLAY:
        ialarm->setText( description );
        break;
      case ICAL_ACTION_PROCEDURE:
        ialarm->setProgramArguments( description );
        break;
      case ICAL_ACTION_EMAIL:
        ialarm->setMailText( description );
        break;
      default:
        break;
      }
      break;
    }
    case ICAL_SUMMARY_PROPERTY:
      // Only in EMAIL alarm
      ialarm->setMailSubject( QString::fromUtf8( icalproperty_get_summary( p ) ) );
      break;

    case ICAL_ATTENDEE_PROPERTY:
    { // Only in EMAIL alarm
      QString email = QString::fromUtf8( icalproperty_get_attendee( p ) );
      if ( email.startsWith( QLatin1String( "mailto:" ), Qt::CaseInsensitive ) ) {
        email = email.mid( 7 );
      }
      QString name;
      icalparameter *param = icalproperty_get_first_parameter( p, ICAL_CN_PARAMETER );
      if ( param ) {
        name = QString::fromUtf8( icalparameter_get_cn( param ) );
      }
      ialarm->addMailAddress( Person( name, email ) );
      break;
    }

    case ICAL_ATTACH_PROPERTY:
    { // Only in AUDIO and EMAIL and PROCEDURE alarms
      Attachment *attach = readAttachment( p );
      if ( attach && attach->isUri() ) {
        switch ( action ) {
        case ICAL_ACTION_AUDIO:
          ialarm->setAudioFile( attach->uri() );
          break;
        case ICAL_ACTION_PROCEDURE:
          ialarm->setProgramFile( attach->uri() );
          break;
        case ICAL_ACTION_EMAIL:
          ialarm->addMailAttachment( attach->uri() );
          break;
        default:
          break;
        }
      } else {
        kDebug() << "Alarm attachments currently only support URIs,"
                 << "but no binary data";
      }
      delete attach;
      break;
    }
    default:
      break;
    }

    p = icalcomponent_get_next_property( alarm, ICAL_ANY_PROPERTY );
  }

  // custom properties
  d->readCustomProperties( alarm, ialarm );

  // TODO: check for consistency of alarm properties
}

icaldatetimeperiodtype ICalFormatImpl::writeICalDatePeriod( const QDate &date )
{
  icaldatetimeperiodtype t;
  t.time = writeICalDate( date );
  t.period = icalperiodtype_null_period();
  return t;
}

icaltimetype ICalFormatImpl::writeICalDate( const QDate &date )
{
  icaltimetype t = icaltime_null_time();

  t.year = date.year();
  t.month = date.month();
  t.day = date.day();

  t.hour = 0;
  t.minute = 0;
  t.second = 0;

  t.is_date = 1;
  t.is_utc = 0;
  t.zone = 0;

  return t;
}

icaltimetype ICalFormatImpl::writeICalDateTime( const KDateTime &datetime )
{
  icaltimetype t = icaltime_null_time();

  t.year = datetime.date().year();
  t.month = datetime.date().month();
  t.day = datetime.date().day();

  t.hour = datetime.time().hour();
  t.minute = datetime.time().minute();
  t.second = datetime.time().second();

  t.is_date = 0;
  t.zone = 0;   // zone is NOT set
  t.is_utc = datetime.isUtc() ? 1 : 0;

  // _dumpIcaltime( t );

  return t;
}

icalproperty *ICalFormatImpl::writeICalDateTimeProperty( const icalproperty_kind type,
                                                         const KDateTime &dt,
                                                         ICalTimeZones *tzlist,
                                                         ICalTimeZones *tzUsedList )
{
  icaltimetype t;

  switch ( type ) {
  case ICAL_DTSTAMP_PROPERTY:
  case ICAL_CREATED_PROPERTY:
  case ICAL_LASTMODIFIED_PROPERTY:
    t = writeICalDateTime( dt.toUtc() );
    break;
  default:
    t = writeICalDateTime( dt );
    break;
  }

  icalproperty *p;
  switch ( type ) {
  case ICAL_DTSTAMP_PROPERTY:
    p = icalproperty_new_dtstamp( t );
    break;
  case ICAL_CREATED_PROPERTY:
    p = icalproperty_new_created( t );
    break;
  case ICAL_LASTMODIFIED_PROPERTY:
    p = icalproperty_new_lastmodified( t );
    break;
  case ICAL_DTSTART_PROPERTY:  // start date and time
    p = icalproperty_new_dtstart( t );
    break;
  case ICAL_DTEND_PROPERTY:    // end date and time
    p = icalproperty_new_dtend( t );
    break;
  case ICAL_DUE_PROPERTY:
    p = icalproperty_new_due( t );
    break;
  case ICAL_RECURRENCEID_PROPERTY:
    p = icalproperty_new_recurrenceid( t );
    break;
  case ICAL_EXDATE_PROPERTY:
    p = icalproperty_new_exdate( t );
    break;
  default:
  {
    icaldatetimeperiodtype tp;
    tp.time = t;
    tp.period = icalperiodtype_null_period();
    switch ( type ) {
    case ICAL_RDATE_PROPERTY:
      p = icalproperty_new_rdate( tp );
      break;
    default:
      return 0;
    }
  }
  }

  KTimeZone ktz;
  if ( !t.is_utc ) {
    ktz = dt.timeZone();
  }

  if ( ktz.isValid() ) {
    if ( tzlist ) {
      ICalTimeZone tz = tzlist->zone( ktz.name() );
      if ( !tz.isValid() ) {
        // The time zone isn't in the list of known zones for the calendar
        // - add it to the calendar's zone list
        ICalTimeZone tznew( ktz );
        tzlist->add( tznew );
        tz = tznew;
      }
      if ( tzUsedList ) {
        tzUsedList->add( tz );
      }
    }
    icalproperty_add_parameter(
      p, icalparameter_new_tzid( ktz.name().toUtf8() ) );
  }
  return p;
}

KDateTime ICalFormatImpl::readICalDateTime( icalproperty *p,
                                            const icaltimetype &t,
                                            ICalTimeZones *tzlist,
                                            bool utc )
{
//  kDebug();
//  _dumpIcaltime( t );

  KDateTime::Spec timeSpec;
  if ( t.is_utc  ||  t.zone == icaltimezone_get_utc_timezone() ) {
    timeSpec = KDateTime::UTC;   // the time zone is UTC
    utc = false;    // no need to convert to UTC
  } else {
    if ( !tzlist ) {
      utc = true;   // should be UTC, but it isn't
    }
    icalparameter *param =
      p ? icalproperty_get_first_parameter( p, ICAL_TZID_PARAMETER ) : 0;
    const char *tzid = param ? icalparameter_get_tzid( param ) : 0;
    if ( !tzid ) {
      timeSpec = KDateTime::ClockTime;
    } else {
      QString tzidStr = QString::fromUtf8( tzid );
      ICalTimeZone tz;
      if ( tzlist ) {
        tz = tzlist->zone( tzidStr );
      }
      if ( !tz.isValid() ) {
        // The time zone is not in the existing list for the calendar.
        // Try to read it from the system or libical databases.
        ICalTimeZoneSource tzsource;
        ICalTimeZone newtz = tzsource.standardZone( tzidStr );
        if ( newtz.isValid() && tzlist ) {
          tzlist->add( newtz );
        }
        tz = newtz;
      }
      timeSpec = tz.isValid() ? KDateTime::Spec( tz ) : KDateTime::LocalZone;
    }
  }
  KDateTime result( QDate( t.year, t.month, t.day ),
                    QTime( t.hour, t.minute, t.second ), timeSpec );
  return utc ? result.toUtc() : result;
}

QDate ICalFormatImpl::readICalDate( icaltimetype t )
{
  return QDate( t.year, t.month, t.day );
}

KDateTime ICalFormatImpl::readICalDateTimeProperty( icalproperty *p,
                                                    ICalTimeZones *tzlist,
                                                    bool utc )
{
  icaldatetimeperiodtype tp;
  icalproperty_kind kind = icalproperty_isa( p );
  switch ( kind ) {
  case ICAL_CREATED_PROPERTY:   // UTC date/time
    tp.time = icalproperty_get_created( p );
    utc = true;
    break;
  case ICAL_LASTMODIFIED_PROPERTY:  // last modification UTC date/time
    tp.time = icalproperty_get_lastmodified( p );
    utc = true;
    break;
  case ICAL_DTSTART_PROPERTY:  // start date and time (UTC for freebusy)
    tp.time = icalproperty_get_dtstart( p );
    break;
  case ICAL_DTEND_PROPERTY:    // end date and time (UTC for freebusy)
    tp.time = icalproperty_get_dtend( p );
    break;
  case ICAL_DUE_PROPERTY:      // due date/time
    tp.time = icalproperty_get_due( p );
    break;
  case ICAL_COMPLETED_PROPERTY:  // UTC completion date/time
    tp.time = icalproperty_get_completed( p );
    utc = true;
    break;
  case ICAL_RECURRENCEID_PROPERTY:
    tp.time = icalproperty_get_recurrenceid( p );
    break;
  case ICAL_EXDATE_PROPERTY:
    tp.time = icalproperty_get_exdate( p );
    break;
  default:
    switch ( kind ) {
    case ICAL_RDATE_PROPERTY:
      tp = icalproperty_get_rdate( p );
      break;
    default:
      return KDateTime();
    }
    if ( !icaltime_is_valid_time( tp.time ) ) {
      return KDateTime();   // a time period was found (not implemented yet)
    }
    break;
  }
  if ( tp.time.is_date ) {
    return KDateTime( readICalDate( tp.time ), KDateTime::Spec::ClockTime() );
  } else {
    return readICalDateTime( p, tp.time, tzlist, utc );
  }
}

icaldurationtype ICalFormatImpl::writeICalDuration( const Duration &duration )
{
  // should be able to use icaldurationtype_from_int(), except we know
  // that some older tools do not properly support weeks. So we never
  // set a week duration, only days

  icaldurationtype d;

  int value = duration.value();
  d.is_neg = ( value < 0 ) ? 1 : 0;
  if ( value < 0 ) {
    value = -value;
  }

  if ( duration.isDaily() ) {
    d.weeks = 0;
    d.days  = value;
    d.hours = d.minutes = d.seconds = 0;
  } else {
    d.weeks   = 0;
    d.days    = value / gSecondsPerDay;
    value    %= gSecondsPerDay;
    d.hours   = value / gSecondsPerHour;
    value    %= gSecondsPerHour;
    d.minutes = value / gSecondsPerMinute;
    value    %= gSecondsPerMinute;
    d.seconds = value;
  }

  return d;
}

Duration ICalFormatImpl::readICalDuration( icaldurationtype d )
{
  int days = d.weeks * 7;
  days += d.days;
  int seconds = d.hours * gSecondsPerHour;
  seconds += d.minutes * gSecondsPerMinute;
  seconds += d.seconds;
  if ( seconds ) {
    seconds += days * gSecondsPerDay;
    if ( d.is_neg ) {
      seconds = -seconds;
    }
    return Duration( seconds, Duration::Seconds );
  } else {
    if ( d.is_neg ) {
      days = -days;
    }
    return Duration( days, Duration::Days );
  }
}

icalcomponent *ICalFormatImpl::createCalendarComponent( Calendar *cal )
{
  icalcomponent *calendar;

  // Root component
  calendar = icalcomponent_new( ICAL_VCALENDAR_COMPONENT );

  icalproperty *p;

  // Product Identifier
  p = icalproperty_new_prodid( CalFormat::productId().toUtf8() );
  icalcomponent_add_property( calendar, p );

  // iCalendar version (2.0)
  p = icalproperty_new_version( const_cast<char *>(_ICAL_VERSION) );
  icalcomponent_add_property( calendar, p );

  // Add time zone
  if ( cal && cal->timeZones() ) {
    const ICalTimeZones::ZoneMap zmaps = cal->timeZones()->zones();
    for ( ICalTimeZones::ZoneMap::ConstIterator it=zmaps.constBegin();
          it != zmaps.constEnd(); ++it ) {
      icaltimezone *icaltz = (*it).icalTimezone();
      if ( !icaltz ) {
        kError() << "bad time zone";
      } else {
        icalcomponent *tz = icalcomponent_new_clone( icaltimezone_get_component( icaltz ) );
        icalcomponent_add_component( calendar, tz );
        icaltimezone_free( icaltz, 1 );
      }
    }
  }

  // Custom properties
  if( cal != 0 ) {
    d->writeCustomProperties( calendar, cal );
  }

  return calendar;
}

// take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc.
// and break it down from its tree-like format into the dictionary format
// that is used internally in the ICalFormatImpl.
bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar )
{
  // this function will populate the caldict dictionary and other event
  // lists. It turns vevents into Events and then inserts them.

  if ( !calendar ) {
    return false;
  }

// TODO: check for METHOD

  icalproperty *p;

  p = icalcomponent_get_first_property( calendar, ICAL_PRODID_PROPERTY );
  if ( !p ) {
    kDebug() << "No PRODID property found";
    d->mLoadedProductId = "";
  } else {
    d->mLoadedProductId = QString::fromUtf8( icalproperty_get_prodid( p ) );

    delete d->mCompat;
    d->mCompat = CompatFactory::createCompat( d->mLoadedProductId );
  }

  p = icalcomponent_get_first_property( calendar, ICAL_VERSION_PROPERTY );
  if ( !p ) {
    kDebug() << "No VERSION property found";
    d->mParent->setException( new ErrorFormat( ErrorFormat::CalVersionUnknown ) );
    return false;
  } else {
    const char *version = icalproperty_get_version( p );
    if ( !version ) {
      kDebug() << "No VERSION property found";
      d->mParent->setException( new ErrorFormat(
                                  ErrorFormat::CalVersionUnknown,
                                  i18n( "No VERSION property found" ) ) );
      return false;
    }
    if ( strcmp( version, "1.0" ) == 0 ) {
      kDebug() << "Expected iCalendar, got vCalendar";
      d->mParent->setException( new ErrorFormat(
                                  ErrorFormat::CalVersion1,
                                  i18n( "Expected iCalendar, got vCalendar format" ) ) );
      return false;
    } else if ( strcmp( version, "2.0" ) != 0 ) {
      kDebug() << "Expected iCalendar, got unknown format";
      d->mParent->setException( new ErrorFormat(
                                  ErrorFormat::CalVersionUnknown,
                                  i18n( "Expected iCalendar, got unknown format" ) ) );
      return false;
    }
  }

  // Populate the calendar's time zone collection with all VTIMEZONE components
  ICalTimeZones *tzlist = cal->timeZones();
  ICalTimeZoneSource tzs;
  tzs.parse( calendar, *tzlist );

  // custom properties
  d->readCustomProperties( calendar, cal );

  // Store all events with a relatedTo property in a list for post-processing
  d->mEventsRelate.clear();
  d->mTodosRelate.clear();
  // TODO: make sure that only actually added events go to this lists.

  icalcomponent *c;

  // Iterate through all todos
  cal->beginBatchAdding();

  c = icalcomponent_get_first_component( calendar, ICAL_VTODO_COMPONENT );
  while ( c ) {
    Todo *todo = readTodo( c, tzlist );
    if ( todo ) {
      Todo *old = cal->todo( todo->uid() );
      if ( old ) {
        cal->deleteTodo( old );
        d->mTodosRelate.removeAll( old );
      }
      cal->addTodo( todo );
    }
    c = icalcomponent_get_next_component( calendar, ICAL_VTODO_COMPONENT );
  }

  // Iterate through all events
  c = icalcomponent_get_first_component( calendar, ICAL_VEVENT_COMPONENT );
  while ( c ) {
    Event *event = readEvent( c, tzlist );
    if ( event ) {
      Event *old = cal->event( event->uid() );
      if ( old ) {
        cal->deleteEvent( old );
        d->mEventsRelate.removeAll( old );
      }
      cal->addEvent( event );
    }
    c = icalcomponent_get_next_component( calendar, ICAL_VEVENT_COMPONENT );
  }

  // Iterate through all journals
  c = icalcomponent_get_first_component( calendar, ICAL_VJOURNAL_COMPONENT );
  while ( c ) {
    Journal *journal = readJournal( c, tzlist );
    if ( journal ) {
      Journal *old = cal->journal( journal->uid() );
      if ( old ) {
        cal->deleteJournal( old );
      }
      cal->addJournal( journal );
    }
    c = icalcomponent_get_next_component( calendar, ICAL_VJOURNAL_COMPONENT );
  }

  cal->endBatchAdding();

  // 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( cal->incidence( (*eIt)->relatedToUid() ) );
  }
  Todo::List::ConstIterator tIt;
  for ( tIt = d->mTodosRelate.constBegin(); tIt != d->mTodosRelate.constEnd(); ++tIt ) {
    (*tIt)->setRelatedTo( cal->incidence( (*tIt)->relatedToUid() ) );
  }

  // TODO: Remove any previous time zones no longer referenced in the calendar

  return true;
}

QString ICalFormatImpl::extractErrorProperty( icalcomponent *c )
{
  QString errorMessage;

  icalproperty *error;
  error = icalcomponent_get_first_property( c, ICAL_XLICERROR_PROPERTY );
  while ( error ) {
    errorMessage += icalproperty_get_xlicerror( error );
    errorMessage += '\n';
    error = icalcomponent_get_next_property( c, ICAL_XLICERROR_PROPERTY );
  }

  return errorMessage;
}

void ICalFormatImpl::dumpIcalRecurrence( icalrecurrencetype r )
{
  int i;

  kDebug() << " Freq:" << int( r.freq );
  kDebug() << " Until:" << icaltime_as_ical_string( r.until );
  kDebug() << " Count:" << r.count;
  if ( r.by_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
    int index = 0;
    QString out = " By Day: ";
    while ( ( i = r.by_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
      out.append( QString::number( i ) + ' ' );
    }
    kDebug() << out;
  }
  if ( r.by_month_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
    int index = 0;
    QString out = " By Month Day: ";
    while ( ( i = r.by_month_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
      out.append( QString::number( i ) + ' ' );
    }
    kDebug() << out;
  }
  if ( r.by_year_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
    int index = 0;
    QString out = " By Year Day: ";
    while ( ( i = r.by_year_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
      out.append( QString::number( i ) + ' ' );
    }
    kDebug() << out;
  }
  if ( r.by_month[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
    int index = 0;
    QString out = " By Month: ";
    while ( ( i = r.by_month[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
      out.append( QString::number( i ) + ' ' );
    }
    kDebug() << out;
  }
  if ( r.by_set_pos[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
    int index = 0;
    QString out = " By Set Pos: ";
    while ( ( i = r.by_set_pos[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
      kDebug() << "=========" << i;
      out.append( QString::number( i ) + ' ' );
    }
    kDebug() << out;
  }
}

icalcomponent *ICalFormatImpl::createScheduleComponent( IncidenceBase *incidence,
                                                        iTIPMethod method )
{
  icalcomponent *message = createCalendarComponent();

#if 0
  //
  // Old versions of Outlook 2003 cannot handle TZIDs. So rely on UTC times.
  // Keep the code here in case we ever need it.
  //

  // Create VTIMEZONE components for this incidence
  ICalTimeZones zones;
  if ( incidence ) {
    if ( incidence->type() == "Event" ) {
      Event *ev = static_cast<Event *>( incidence );
      if ( ev ) {
        if ( ev->dtStart().isValid() ) {
          zones.add( ICalTimeZone( ev->dtStart().timeZone() ) );
        }
        if ( ev->hasEndDate() && ev->dtEnd().isValid() ) {
          zones.add( ICalTimeZone( ev->dtEnd().timeZone() ) );
        }
      }
    } else if ( incidence->type() == "Todo" ) {
      Todo *t = static_cast<Todo *>( incidence );
      if ( t ) {
        if ( t->hasStartDate() && t->dtStart().isValid() ) {
          zones.add( ICalTimeZone( t->dtStart( true ).timeZone() ) );
        }
        if ( t->hasDueDate() && t->dtDue().isValid() ) {
          zones.add( ICalTimeZone( t->dtDue().timeZone() ) );
        }
      }
    } else if ( incidence->type() == "Journal" ) {
      Journal *j = static_cast<Journal *>( incidence );
      if ( j ) {
        if ( j->dtStart().isValid() ) {
          zones.add( ICalTimeZone( j->dtStart().timeZone() ) );
        }
      }
    }
    const ICalTimeZones::ZoneMap zmaps = zones.zones();
    for ( ICalTimeZones::ZoneMap::ConstIterator it=zmaps.constBegin();
          it != zmaps.constEnd(); ++it ) {
      icaltimezone *icaltz = (*it).icalTimezone();
      if ( !icaltz ) {
        kError() << "bad time zone";
      } else {
        icalcomponent *tz = icalcomponent_new_clone( icaltimezone_get_component( icaltz ) );
        icalcomponent_add_component( message, tz );
        icaltimezone_free( icaltz, 1 );
      }
    }
  }
#else
  if ( incidence ) {
    if ( incidence->type() == "Event" ) {
      Event *ev = static_cast<Event *>( incidence );
      if ( ev ) {
        if ( ev->dtStart().isValid() ) {
          ev->setDtStart( ev->dtStart().toUtc() );
        }
        if ( ev->hasEndDate() && ev->dtEnd().isValid() ) {
          ev->setDtEnd( ev->dtEnd().toUtc() );
        }
      }
    } else if ( incidence->type() == "Todo" ) {
      Todo *t = static_cast<Todo *>( incidence );
      if ( t ) {
        if ( t->hasStartDate() && t->dtStart().isValid() ) {
          t->setDtStart( t->dtStart().toUtc() );
        }
        if ( t->hasDueDate() && t->dtDue().isValid() ) {
          t->setDtDue( t->dtDue().toUtc() );
        }
      }
    } else if ( incidence->type() == "Journal" ) {
      Journal *j = static_cast<Journal *>( incidence );
      if ( j ) {
        if ( j->dtStart().isValid() ) {
          j->setDtStart( j->dtStart().toUtc() );
        }
      }
    }
  }
#endif
  icalproperty_method icalmethod = ICAL_METHOD_NONE;

  switch ( method ) {
  case iTIPPublish:
    icalmethod = ICAL_METHOD_PUBLISH;
    break;
  case iTIPRequest:
    icalmethod = ICAL_METHOD_REQUEST;
    break;
  case iTIPRefresh:
    icalmethod = ICAL_METHOD_REFRESH;
    break;
  case iTIPCancel:
    icalmethod = ICAL_METHOD_CANCEL;
    break;
  case iTIPAdd:
    icalmethod = ICAL_METHOD_ADD;
    break;
  case iTIPReply:
    icalmethod = ICAL_METHOD_REPLY;
    break;
  case iTIPCounter:
    icalmethod = ICAL_METHOD_COUNTER;
    break;
  case iTIPDeclineCounter:
    icalmethod = ICAL_METHOD_DECLINECOUNTER;
    break;
  default:
    kDebug() << "Unknown method";
    return message;
  }

  icalcomponent_add_property( message, icalproperty_new_method( icalmethod ) );

  icalcomponent *inc = writeIncidence( incidence, method );
  /*
   * RFC 2446 states in section 3.4.3 ( REPLY to a VTODO ), that
   * a REQUEST-STATUS property has to be present. For the other two, event and
   * free busy, it can be there, but is optional. Until we do more
   * fine grained handling, assume all is well. Note that this is the
   * status of the _request_, not the attendee. Just to avoid confusion.
   * - till
   */
  if ( icalmethod == ICAL_METHOD_REPLY ) {
    struct icalreqstattype rst;
    rst.code = ICAL_2_0_SUCCESS_STATUS;
    rst.desc = 0;
    rst.debug = 0;
    icalcomponent_add_property( inc, icalproperty_new_requeststatus( rst ) );
  }
  icalcomponent_add_component( message, inc );

  return message;
}
