/*
    This file is part of libkcal.

    Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
    Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>

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

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

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

#include <tqdatetime.h>
#include <tqstring.h>
#include <tqptrlist.h>
#include <tqfile.h>
#include <cstdlib>

#include <kdebug.h>
#include <tdelocale.h>
#include <kmdcodec.h>

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

#include "calendar.h"
#include "journal.h"
#include "icalformat.h"
#include "icalformatimpl.h"
#include "compat.h"

#include "config.h"

#define _ICAL_VERSION "2.0"

using namespace KCal;

/* Static helpers */
static TQDateTime ICalDate2TQDate(const icaltimetype& t)
{
  // Outlook sends dates starting from 1601-01-01, but TQDate()
  // can only handle dates starting 1752-09-14.
  const int year = (t.year>=1754) ? t.year : 1754;
  return TQDateTime(TQDate(year,t.month,t.day), TQTime(t.hour,t.minute,t.second));
}

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

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

ICalFormatImpl::ICalFormatImpl( ICalFormat *parent ) :
  mParent( parent ), mCompat( new Compat )
{
}

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

class ICalFormatImpl::ToComponentVisitor : public IncidenceBase::Visitor
{
  public:
    ToComponentVisitor( ICalFormatImpl *impl, Scheduler::Method 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;
    Scheduler::Method mMethod;
};

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

icalcomponent *ICalFormatImpl::writeTodo(Todo *todo)
{
  TQString tmpStr;
  TQStringList tmpStrList;

  icalcomponent *vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT);

  writeIncidence(vtodo,todo);

  // due date
  if (todo->hasDueDate()) {
    icaltimetype due;
    if (todo->doesFloat()) {
      due = writeICalDate(todo->dtDue(true).date());
    } else {
      due = writeICalDateTime(todo->dtDue(true));
    }
    icalcomponent_add_property(vtodo,icalproperty_new_due(due));
  }

  // start time
  if ( todo->hasStartDate() || todo->doesRecur() ) {
    icaltimetype start;
    if (todo->doesFloat()) {
//      kdDebug(5800) << " Incidence " << todo->summary() << " floats." << endl;
      start = writeICalDate(todo->dtStart(true).date());
    } else {
//      kdDebug(5800) << " incidence " << todo->summary() << " has time." << endl;
      start = writeICalDateTime(todo->dtStart(true));
    }
    icalcomponent_add_property(vtodo,icalproperty_new_dtstart(start));
  }

  // completion date
  if (todo->isCompleted()) {
    if (!todo->hasCompletedDate()) {
      // If todo was created by KOrganizer <2.2 it has no correct completion
      // date. Set it to now.
      todo->setCompleted(TQDateTime::currentDateTime());
    }
    icaltimetype completed = writeICalDateTime(todo->completed());
    icalcomponent_add_property(vtodo,icalproperty_new_completed(completed));
  }

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

  if( todo->doesRecur() ) {
    icalcomponent_add_property(vtodo,
        icalproperty_new_recurrenceid( writeICalDateTime( todo->dtDue())));
  }

  return vtodo;
}

icalcomponent *ICalFormatImpl::writeEvent(Event *event)
{
#if 0
  kdDebug(5800) << "Write Event '" << event->summary() << "' (" << event->uid()
                << ")" << endl;
#endif

  TQString tmpStr;
  TQStringList tmpStrList;

  icalcomponent *vevent = icalcomponent_new(ICAL_VEVENT_COMPONENT);

  writeIncidence(vevent,event);

  // start time
  icaltimetype start;
  if (event->doesFloat()) {
//    kdDebug(5800) << " Incidence " << event->summary() << " floats." << endl;
    start = writeICalDate(event->dtStart().date());
  } else {
//    kdDebug(5800) << " incidence " << event->summary() << " has time." << endl;
    start = writeICalDateTime(event->dtStart());
  }
  icalcomponent_add_property(vevent,icalproperty_new_dtstart(start));

  if (event->hasEndDate()) {
    // End time.
    // RFC2445 says that if DTEND is present, it has to be greater than DTSTART.
    icaltimetype end;
    if (event->doesFloat()) {
//      kdDebug(5800) << " Event " << event->summary() << " floats." << endl;
      // +1 day because end date is non-inclusive.
      end = writeICalDate( event->dtEnd().date().addDays( 1 ) );
      icalcomponent_add_property(vevent,icalproperty_new_dtend(end));
    } else {
//      kdDebug(5800) << " Event " << event->summary() << " has time." << endl;
      if (event->dtEnd() != event->dtStart()) {
        end = writeICalDateTime(event->dtEnd());
        icalcomponent_add_property(vevent,icalproperty_new_dtend(end));
      }
    }
  }

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

#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,
                                             Scheduler::Method method)
{
  kdDebug(5800) << "icalformatimpl: writeFreeBusy: startDate: "
    << freebusy->dtStart().toString("ddd MMMM d yyyy: h:m:s ap") << " End Date: "
    << freebusy->dtEnd().toString("ddd MMMM d yyyy: h:m:s ap") << endl;

  icalcomponent *vfreebusy = icalcomponent_new(ICAL_VFREEBUSY_COMPONENT);

  writeIncidenceBase(vfreebusy,freebusy);

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

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

  if (method == Scheduler::Request) {
    icalcomponent_add_property(vfreebusy,icalproperty_new_uid(
       freebusy->uid().utf8()));
  }

  //Loops through all the periods in the freebusy object
  TQValueList<Period> list = freebusy->busyPeriods();
  TQValueList<Period>::Iterator it;
  icalperiodtype period = icalperiodtype_null_period();
  for (it = list.begin(); it!= list.end(); ++it) {
    period.start = writeICalDateTime((*it).start());
    if ( (*it).hasDuration() ) {
      period.duration = writeICalDuration( (*it).duration().asSeconds() );
    } else {
      period.end = writeICalDateTime((*it).end());
    }
    icalcomponent_add_property(vfreebusy, icalproperty_new_freebusy(period) );
  }

  return vfreebusy;
}

icalcomponent *ICalFormatImpl::writeJournal(Journal *journal)
{
  icalcomponent *vjournal = icalcomponent_new(ICAL_VJOURNAL_COMPONENT);

  writeIncidence(vjournal,journal);

  // start time
  if (journal->dtStart().isValid()) {
    icaltimetype start;
    if (journal->doesFloat()) {
//      kdDebug(5800) << " Incidence " << event->summary() << " floats." << endl;
      start = writeICalDate(journal->dtStart().date());
    } else {
//      kdDebug(5800) << " incidence " << event->summary() << " has time." << endl;
      start = writeICalDateTime(journal->dtStart());
    }
    icalcomponent_add_property(vjournal,icalproperty_new_dtstart(start));
  }

  return vjournal;
}

void ICalFormatImpl::writeIncidence(icalcomponent *parent,Incidence *incidence)
{
  // pilot sync stuff
// TODO: move this application-specific code to kpilot
  if (incidence->pilotId()) {
    // NOTE: we can't do setNonKDECustomProperty here because this changes
    // data and triggers an updated() event...
    // incidence->setNonKDECustomProperty("X-PILOTSTAT", TQString::number(incidence->syncStatus()));
    // incidence->setNonKDECustomProperty("X-PILOTID", TQString::number(incidence->pilotId()));

    icalproperty *p = 0;
    p = icalproperty_new_x(TQString::number(incidence->syncStatus()).utf8());
    icalproperty_set_x_name(p,"X-PILOTSTAT");
    icalcomponent_add_property(parent,p);

    p = icalproperty_new_x(TQString::number(incidence->pilotId()).utf8());
    icalproperty_set_x_name(p,"X-PILOTID");
    icalcomponent_add_property(parent,p);
  }

  TQString modifiedUid;
  if ( incidence->hasRecurrenceID() ) {
    // Recurring incidences are special; they must match their parent's UID
    // Each child has the parent set as the first item in the list
    // So, get and set the UID...
    IncidenceList il = incidence->childIncidences();
    IncidenceListIterator it;
    it = il.begin();
    modifiedUid = (*it);
  }
  else {
    modifiedUid = incidence->uid();
  }

  if ( incidence->schedulingID() != modifiedUid )
    // We need to store the UID in here. The rawSchedulingID will
    // go into the iCal UID component
    incidence->setCustomProperty( "LIBKCAL", "ID", modifiedUid );
  else
    incidence->removeCustomProperty( "LIBKCAL", "ID" );

  writeIncidenceBase(parent,incidence);

  // creation date
  icalcomponent_add_property(parent,icalproperty_new_created(
      writeICalDateTime(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->hasRecurrenceID() ) {
    // Recurring incidences are special; they must match their parent's UID
    icalcomponent_add_property(parent,icalproperty_new_uid(modifiedUid.utf8()));
  }
  else {
    if ( !incidence->schedulingID().isEmpty() ) {
      icalcomponent_add_property(parent,icalproperty_new_uid(
          incidence->schedulingID().utf8()));
    }
  }

  // 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,icalproperty_new_lastmodified(
       writeICalDateTime(incidence->lastModified())));
  }

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

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

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

  // 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().utf8());
      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));
  }

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

  // categories
  TQStringList categories = incidence->categories();
  TQStringList::Iterator it;
  for(it = categories.begin(); it != categories.end(); ++it ) {
    icalcomponent_add_property(parent,icalproperty_new_categories((*it).utf8()));
  }

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

  // recurrenceid
  if ( incidence->hasRecurrenceID() ) {
    icalcomponent_add_property(parent, icalproperty_new_recurrenceid( writeICalDateTime( incidence->recurrenceID() ) ));
  }

//   kdDebug(5800) << "Write recurrence for '" << incidence->summary() << "' (" << incidence->uid()
//             << ")" << endl;

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

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

  DateList dateList = incidence->recurrence()->exDates();
  DateList::ConstIterator exIt;
  for(exIt = dateList.begin(); exIt != dateList.end(); ++exIt) {
    icalcomponent_add_property(parent,icalproperty_new_exdate(
        writeICalDate(*exIt)));
  }
  DateTimeList dateTimeList = incidence->recurrence()->exDateTimes();
  DateTimeList::ConstIterator extIt;
  for(extIt = dateTimeList.begin(); extIt != dateTimeList.end(); ++extIt) {
    icalcomponent_add_property(parent,icalproperty_new_exdate(
        writeICalDateTime(*extIt)));
  }


  dateList = incidence->recurrence()->rDates();
  DateList::ConstIterator rdIt;
  for( rdIt = dateList.begin(); rdIt != dateList.end(); ++rdIt) {
     icalcomponent_add_property( parent, icalproperty_new_rdate(
         writeICalDatePeriod(*rdIt) ) );
  }
  dateTimeList = incidence->recurrence()->rDateTimes();
  DateTimeList::ConstIterator rdtIt;
  for( rdtIt = dateTimeList.begin(); rdtIt != dateTimeList.end(); ++rdtIt) {
     icalcomponent_add_property( parent, icalproperty_new_rdate(
         writeICalDateTimePeriod(*rdtIt) ) );
  }

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

  // alarms
  Alarm::List::ConstIterator alarmIt;
  for ( alarmIt = incidence->alarms().begin();
        alarmIt != incidence->alarms().end(); ++alarmIt ) {
    if ( (*alarmIt)->enabled() ) {
//      kdDebug(5800) << "Write alarm for " << incidence->summary() << endl;
      icalcomponent_add_component( parent, writeAlarm( *alarmIt ) );
    }
  }

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

void ICalFormatImpl::writeIncidenceBase( icalcomponent *parent,
                                         IncidenceBase * incidenceBase )
{
  icalcomponent_add_property( parent, icalproperty_new_dtstamp(
      writeICalDateTime( TQDateTime::currentDateTime() ) ) );

  // organizer stuff
  if ( !incidenceBase->organizer().isEmpty() ) {
    icalcomponent_add_property( parent, writeOrganizer( incidenceBase->organizer() ) );
  }

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

  // comments
  TQStringList comments = incidenceBase->comments();
  for (TQStringList::Iterator it=comments.begin(); it!=comments.end(); ++it) {
    icalcomponent_add_property(parent, icalproperty_new_comment((*it).utf8()));
  }

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

void ICalFormatImpl::writeCustomProperties(icalcomponent *parent,CustomProperties *properties)
{
  TQMap<TQCString, TQString> custom = properties->customProperties();
  for (TQMap<TQCString, TQString>::Iterator c = custom.begin();  c != custom.end();  ++c) {
    icalproperty *p = icalproperty_new_x(c.data().utf8());
    icalproperty_set_x_name(p,c.key());
    icalcomponent_add_property(parent,p);
  }
}

icalproperty *ICalFormatImpl::writeOrganizer( const Person &organizer )
{
  icalproperty *p = icalproperty_new_organizer("MAILTO:" + organizer.email().utf8());

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

  return p;
}


icalproperty *ICalFormatImpl::writeAttendee(Attendee *attendee)
{
  icalproperty *p = icalproperty_new_attendee("mailto:" + attendee->email().utf8());

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


  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().utf8());
    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().utf8() );
    icalproperty_add_parameter( p, icalparameter_delegate );
  }

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

  return p;
}

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

  if ( !att->mimeType().isEmpty() ) {
    icalproperty_add_parameter( p,
        icalparameter_new_fmttype( att->mimeType().utf8().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().utf8() );
    icalparameter_set_xname( icalparameter_label, "X-LABEL" );
    icalproperty_add_parameter( p, icalparameter_label );
  }

  return p;
}

icalrecurrencetype ICalFormatImpl::writeRecurrenceRule( RecurrenceRule *recur )
{
//  kdDebug(5800) << "ICalFormatImpl::writeRecurrenceRule()" << endl;

  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;
      kdDebug(5800) << "ICalFormatImpl::writeRecurrence(): no recurrence" << endl;
      break;
  }

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

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

  bys = recur->byMinutes();
  index = 0;
  for ( it = bys.begin(); it != bys.end(); ++it ) {
    r.by_minute[index++] = *it;
  }

  bys = recur->byHours();
  index = 0;
  for ( it = bys.begin(); it != bys.end(); ++it ) {
    r.by_hour[index++] = *it;
  }

  bys = recur->byMonthDays();
  index = 0;
  for ( it = bys.begin(); it != bys.end(); ++it ) {
    r.by_month_day[index++] = icalrecurrencetype_day_position( (*it) * 8 );
  }

  bys = recur->byYearDays();
  index = 0;
  for ( it = bys.begin(); it != bys.end(); ++it ) {
    r.by_year_day[index++] = *it;
  }

  bys = recur->byWeekNumbers();
  index = 0;
  for ( it = bys.begin(); it != bys.end(); ++it ) {
     r.by_week_no[index++] = *it;
  }

  bys = recur->byMonths();
  index = 0;
  for ( it = bys.begin(); it != bys.end(); ++it ) {
    r.by_month[index++] = *it;
  }

  bys = recur->bySetPos();
  index = 0;
  for ( it = bys.begin(); it != bys.end(); ++it ) {
     r.by_set_pos[index++] = *it;
  }


  TQValueList<RecurrenceRule::WDayPos> byd = recur->byDays();
  int day;
  index = 0;
  for ( TQValueList<RecurrenceRule::WDayPos>::ConstIterator dit = byd.begin();
        dit != byd.end(); ++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++] = 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 = recur->frequency();
  }

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

// Debug output
#if 0
  const char *str = icalrecurrencetype_as_string(&r);
  if (str) {
    kdDebug(5800) << " String: " << str << endl;
  } else {
    kdDebug(5800) << " No String" << endl;
  }
#endif

  return r;
}


icalcomponent *ICalFormatImpl::writeAlarm(Alarm *alarm)
{
// kdDebug(5800) << " ICalFormatImpl::writeAlarm" << endl;
  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(TQFile::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().utf8()));
      }
      break;
    case Alarm::Audio:
      action = ICAL_ACTION_AUDIO;
// kdDebug(5800) << " It's an audio action, file: " << alarm->audioFile() << endl;
      if (!alarm->audioFile().isEmpty()) {
        attach = icalattach_new_from_url(TQFile::encodeName( alarm->audioFile() ).data());
        icalcomponent_add_property(a,icalproperty_new_attach(attach));
      }
      break;
    case Alarm::Email: {
      action = ICAL_ACTION_EMAIL;
      TQValueList<Person> addresses = alarm->mailAddresses();
      for (TQValueList<Person>::Iterator ad = addresses.begin();  ad != addresses.end();  ++ad) {
        icalproperty *p = icalproperty_new_attendee("MAILTO:" + (*ad).email().utf8());
        if (!(*ad).name().isEmpty()) {
          icalproperty_add_parameter(p,icalparameter_new_cn((*ad).name().utf8()));
        }
        icalcomponent_add_property(a,p);
      }
      icalcomponent_add_property(a,icalproperty_new_summary(alarm->mailSubject().utf8()));
      icalcomponent_add_property(a,icalproperty_new_description(alarm->mailText().utf8()));
      TQStringList attachments = alarm->mailAttachments();
      if (attachments.count() > 0) {
        for (TQStringList::Iterator at = attachments.begin();  at != attachments.end();  ++at) {
          attach = icalattach_new_from_url(TQFile::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().utf8()));
      break;
    case Alarm::Invalid:
    default:
      kdDebug(5800) << "Unknown type of alarm" << endl;
      action = ICAL_ACTION_NONE;
      break;
  }
  icalcomponent_add_property(a,icalproperty_new_action(action));

  // Trigger time
  icaltriggertype trigger;
  if ( alarm->hasTime() ) {
    trigger.time = writeICalDateTime(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.asSeconds() );
  }
  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().value())));
  }

  // Custom properties
  TQMap<TQCString, TQString> custom = alarm->customProperties();
  for (TQMap<TQCString, TQString>::Iterator c = custom.begin();  c != custom.end();  ++c) {
    icalproperty *p = icalproperty_new_x(c.data().utf8());
    icalproperty_set_x_name(p,c.key());
    icalcomponent_add_property(a,p);
  }

  return a;
}

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

  readIncidence(vtodo, 0, todo); // FIXME timezone

  icalproperty *p = icalcomponent_get_first_property(vtodo,ICAL_ANY_PROPERTY);

//  int intvalue;
  icaltimetype icaltime;

  TQStringList categories;

  while (p) {
    icalproperty_kind kind = icalproperty_isa(p);
    switch (kind) {

      case ICAL_DUE_PROPERTY:  // due date
        icaltime = icalproperty_get_due(p);
        if (icaltime.is_date) {
          todo->setDtDue(TQDateTime(readICalDate(icaltime),TQTime(0,0,0)),true);
        } else {
          todo->setDtDue(readICalDateTime(p, icaltime),true);
          todo->setFloats(false);
        }
        todo->setHasDueDate(true);
        break;

      case ICAL_COMPLETED_PROPERTY:  // completion date
        icaltime = icalproperty_get_completed(p);
        todo->setCompleted(readICalDateTime(p, icaltime));
        break;

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

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

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

      case ICAL_RECURRENCEID_PROPERTY:
        icaltime = icalproperty_get_recurrenceid(p);
        todo->setDtRecurrence( readICalDateTime(p, icaltime) );
        break;

      default:
//        kdDebug(5800) << "ICALFormat::readTodo(): Unknown property: " << kind
//                  << endl;
        break;
    }

    p = icalcomponent_get_next_property(vtodo,ICAL_ANY_PROPERTY);
  }

  if (mCompat) mCompat->fixEmptySummary( todo );

  return todo;
}

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

  // FIXME where is this freed?
  icaltimezone *tz = icaltimezone_new();
  if ( !icaltimezone_set_component( tz, vtimezone ) ) {
    icaltimezone_free( tz, 1 );
    tz = 0;
  }

  readIncidence( vevent, tz, event);

  icalproperty *p = icalcomponent_get_first_property( vevent, ICAL_ANY_PROPERTY );

  // int intvalue;
  icaltimetype icaltime;

  TQStringList categories;
  icalproperty_transp transparency;

  bool dtEndProcessed = false;

  while ( p ) {
    icalproperty_kind kind = icalproperty_isa( p );
    switch ( kind ) {

      case ICAL_DTEND_PROPERTY:  // start date and time
        icaltime = icalproperty_get_dtend( p );
        if ( icaltime.is_date ) {
          // End date is non-inclusive
          TQDate endDate = readICalDate( icaltime ).addDays( -1 );
          if ( mCompat ) {
            mCompat->fixFloatingEnd( endDate );
          }

          if ( endDate < event->dtStart().date() ) {
            endDate = event->dtStart().date();
          }
          event->setDtEnd( TQDateTime( endDate, TQTime( 0, 0, 0 ) ) );
        } else {
          event->setDtEnd(readICalDateTime(p, icaltime, tz));
          event->setFloats( false );
        }
        dtEndProcessed = true;
        break;

      case ICAL_RELATEDTO_PROPERTY:  // related event (parent)
        event->setRelatedToUid( TQString::fromUtf8( icalproperty_get_relatedto( p ) ) );
        mEventsRelate.append( event );
        break;

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

      default:
        //  kdDebug(5800) << "ICALFormat::readEvent(): Unknown property: " << kind
        //                << endl;
        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() );
  }

  const TQString msade = event->nonKDECustomProperty("X-MICROSOFT-CDO-ALLDAYEVENT");
  if ( !msade.isEmpty() ) {
    const bool floats = ( msade == TQString::fromLatin1("TRUE") );
    event->setFloats(floats);
  }

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

  return event;
}

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

  readIncidenceBase(vfreebusy, freebusy);

  icalproperty *p = icalcomponent_get_first_property(vfreebusy,ICAL_ANY_PROPERTY);

  icaltimetype icaltime;
  PeriodList periods;

  while (p) {
    icalproperty_kind kind = icalproperty_isa(p);
    switch (kind) {

      case ICAL_DTSTART_PROPERTY:  // start date and time
        icaltime = icalproperty_get_dtstart(p);
        freebusy->setDtStart(readICalDateTime(p, icaltime));
        break;

      case ICAL_DTEND_PROPERTY:  // end Date and Time
        icaltime = icalproperty_get_dtend(p);
        freebusy->setDtEnd(readICalDateTime(p, icaltime));
        break;

      case ICAL_FREEBUSY_PROPERTY:  //Any FreeBusy Times
      {
        icalperiodtype icalperiod = icalproperty_get_freebusy(p);
        TQDateTime period_start = readICalDateTime(p, icalperiod.start);
        Period period;
        if ( !icaltime_is_null_time(icalperiod.end) ) {
          TQDateTime period_end = readICalDateTime(p, icalperiod.end);
          period = Period(period_start, period_end);
        } else {
          Duration duration = readICalDuration( icalperiod.duration );
          period = Period(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( TQString::fromUtf8(
                                 KCodecs::base64Decode( TQCString( icalparameter_get_xvalue( param ) ) ) ) );
          }
          if ( strncmp( icalparameter_get_xname( param ), "X-LOCATION", 10 ) == 0 ) {
            period.setLocation( TQString::fromUtf8(
                                  KCodecs::base64Decode( TQCString( icalparameter_get_xvalue( param ) ) ) ) );
          }
          param = icalproperty_get_next_parameter( p, ICAL_X_PARAMETER );
        }
        periods.append( period );
        break;
      }

      default:
//        kdDebug(5800) << "ICalFormatImpl::readFreeBusy(): Unknown property: "
//                      << kind << endl;
      break;
    }
    p = icalcomponent_get_next_property(vfreebusy,ICAL_ANY_PROPERTY);
  }
  freebusy->addPeriods( periods );

  return freebusy;
}

Journal *ICalFormatImpl::readJournal(icalcomponent *vjournal)
{
  Journal *journal = new Journal;

  readIncidence(vjournal, 0, journal); // FIXME tz?

  return journal;
}

Attendee *ICalFormatImpl::readAttendee(icalproperty *attendee)
{
  icalparameter *p = 0;

  TQString email = TQString::fromUtf8(icalproperty_get_attendee(attendee));
  if ( email.startsWith( "mailto:", false ) ) {
    email = email.mid( 7 );
  }

  TQString name;
  TQString uid = TQString();
  p = icalproperty_get_first_parameter(attendee,ICAL_CN_PARAMETER);
  if (p) {
    name = TQString::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);
  uid = icalparameter_get_xvalue(p);
  // This should be added, but there seems to be a libical bug here.
  // TODO: does this work now in libical-0.24 or greater?
  /*while (p) {
   // if (icalparameter_get_xname(p) == "X-UID") {
    uid = icalparameter_get_xvalue(p);
    p = icalproperty_get_next_parameter(attendee,ICAL_X_PARAMETER);
  } */

  Attendee *a = new Attendee( name, email, rsvp, status, role, uid );

  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 )
{
  TQString email = TQString::fromUtf8(icalproperty_get_organizer(organizer));
  if ( email.startsWith( "mailto:", false ) ) {
    email = email.mid( 7 );
  }
  TQString cn;

  icalparameter *p = icalproperty_get_first_parameter(
             organizer, ICAL_CN_PARAMETER );

  if ( p ) {
    cn = TQString::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( TQString::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( TQString::fromUtf8( p ) );
    break;
  default:
    break;
  }

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

    p = icalproperty_get_first_parameter( attach, ICAL_X_PARAMETER );
    while ( p ) {
      TQString xname = TQString( icalparameter_get_xname( p ) ).upper();
      TQString xvalue = TQString::fromUtf8( icalparameter_get_xvalue( p ) );
      if ( xname == "X-CONTENT-DISPOSITION" ) {
        attachment->setShowInline( xvalue.lower() == "inline" );
      }
      if ( xname == "X-LABEL" ) {
        attachment->setLabel( xvalue );
      }
      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( TQString::fromUtf8( icalparameter_get_xvalue( p ) ) );
      }
      p = icalproperty_get_next_parameter( attach, ICAL_X_PARAMETER );
    }
  }

  return attachment;
}

void ICalFormatImpl::readIncidence(icalcomponent *parent, icaltimezone *tz, Incidence *incidence)
{
  readIncidenceBase(parent,incidence);

  icalproperty *p = icalcomponent_get_first_property(parent,ICAL_ANY_PROPERTY);

  const char *text;
  int intvalue, inttext;
  icaltimetype icaltime;
  icaldurationtype icalduration;

  TQStringList categories;

  while (p) {
    icalproperty_kind kind = icalproperty_isa(p);
    switch (kind) {

      case ICAL_CREATED_PROPERTY:
        icaltime = icalproperty_get_created(p);
        incidence->setCreated(readICalDateTime(p, icaltime, tz));
        break;

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

      case ICAL_LASTMODIFIED_PROPERTY:  // last modification date
        icaltime = icalproperty_get_lastmodified(p);
        incidence->setLastModified(readICalDateTime(p, icaltime, tz));
        break;

      case ICAL_DTSTART_PROPERTY:  // start date and time
        icaltime = icalproperty_get_dtstart(p);
        if (icaltime.is_date) {
          incidence->setDtStart(TQDateTime(readICalDate(icaltime),TQTime(0,0,0)));
          incidence->setFloats( true );
        } else {
          incidence->setDtStart(readICalDateTime(p, icaltime, tz));
          incidence->setFloats( false );
        }
        break;

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

      case ICAL_DESCRIPTION_PROPERTY:  // description
        text = icalproperty_get_description(p);
        incidence->setDescription(TQString::fromUtf8(text));
        break;

      case ICAL_SUMMARY_PROPERTY:  // summary
        text = icalproperty_get_summary(p);
        incidence->setSummary(TQString::fromUtf8(text));
        break;

      case ICAL_LOCATION_PROPERTY:  // location
        text = icalproperty_get_location(p);
        incidence->setLocation(TQString::fromUtf8(text));
        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(TQString::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_PRIORITY_PROPERTY:  // priority
        intvalue = icalproperty_get_priority( p );
        if ( mCompat )
          intvalue = mCompat->fixPriority( intvalue );
        incidence->setPriority( intvalue );
        break;

      case ICAL_CATEGORIES_PROPERTY:  // categories
        text = icalproperty_get_categories(p);
        categories.append(TQString::fromUtf8(text));
        break;

      case ICAL_RECURRENCEID_PROPERTY:  // recurrenceID
        icaltime = icalproperty_get_recurrenceid(p);
        incidence->setRecurrenceID( readICalDateTime( p, icaltime ) );
        incidence->setHasRecurrenceID( true );
        break;

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

//       case ICAL_CONTACT_PROPERTY:
//         incidenceBase->addContact(
//           TQString::fromUtf8( icalproperty_get_contact( p ) ) );
//         break;

      case ICAL_RDATE_PROPERTY: {
        icaldatetimeperiodtype rd = icalproperty_get_rdate( p );
        if ( icaltime_is_valid_time( rd.time ) ) {
          if ( icaltime_is_date( rd.time ) ) {
            incidence->recurrence()->addRDate( readICalDate( rd.time ) );
          } else {
            incidence->recurrence()->addRDateTime( readICalDateTime(p, rd.time, tz ) );
          }
        } else {
          // TODO: RDates as period are not yet implemented!
        }
        break; }

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

      case ICAL_EXDATE_PROPERTY:
        icaltime = icalproperty_get_exdate(p);
        if ( icaltime_is_date(icaltime) ) {
          incidence->recurrence()->addExDate( readICalDate(icaltime) );
        } else {
          incidence->recurrence()->addExDateTime( readICalDateTime(p, icaltime, tz) );
        }
        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:
//        kdDebug(5800) << "ICALFormat::readIncidence(): Unknown property: " << kind
//                  << endl;
        break;
    }

    p = icalcomponent_get_next_property(parent,ICAL_ANY_PROPERTY);
  }

  // Set the scheduling ID
  const TQString 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->doesRecur() && mCompat )
      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);
  }
  // Fix incorrect alarm settings by other applications (like outloook 9)
  if ( mCompat ) mCompat->fixAlarms( incidence );

}

void ICalFormatImpl::readIncidenceBase(icalcomponent *parent,IncidenceBase *incidenceBase)
{
  icalproperty *p = icalcomponent_get_first_property(parent,ICAL_ANY_PROPERTY);

  bool uidProcessed = false;

  while ( p ) {
    icalproperty_kind kind = icalproperty_isa( p );
    switch (kind) {

      case ICAL_UID_PROPERTY:  // unique id
        uidProcessed = true;
        incidenceBase->setUid( TQString::fromUtf8(icalproperty_get_uid( p ) ) );
        break;

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

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

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

      default:
        break;
    }

    p = icalcomponent_get_next_property( parent, ICAL_ANY_PROPERTY );
  }

  if ( !uidProcessed ) {
    kdWarning() << "The incidence didn't have any UID! Report a bug "
                << "to the application that generated this file."
                << endl;

    // Our in-memory incidence has a random uid generated in Event's ctor.
    // Make it empty so it matches what's in the file:
    incidenceBase->setUid( TQString() );

    // Otherwise, next time we read the file, this function will return
    // an event with another random uid and we will have two events in the calendar.
  }

  // kpilot stuff
  // TODO: move this application-specific code to kpilot
  // need to get X-PILOT* attributes out, set correct properties, and get
  // rid of them...
  // Pointer fun, as per libical documentation
  // (documented in UsingLibical.txt)
  icalproperty *next =0;

  for ( p = icalcomponent_get_first_property(parent,ICAL_X_PROPERTY);
       p != 0;
       p = next )
  {

    next = icalcomponent_get_next_property(parent,ICAL_X_PROPERTY);

    TQString value = TQString::fromUtf8(icalproperty_get_x(p));
    TQString name = icalproperty_get_x_name(p);

    if (name == "X-PILOTID" && !value.isEmpty()) {
      incidenceBase->setPilotId(value.toInt());
      icalcomponent_remove_property(parent,p);
    } else if (name == "X-PILOTSTAT" && !value.isEmpty()) {
      incidenceBase->setSyncStatus(value.toInt());
      icalcomponent_remove_property(parent,p);
    }
  }

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

void ICalFormatImpl::readCustomProperties(icalcomponent *parent,CustomProperties *properties)
{
  TQMap<TQCString, TQString> customProperties;
  TQString lastProperty;

  icalproperty *p = icalcomponent_get_first_property(parent,ICAL_X_PROPERTY);

  while (p) {

    TQString value = TQString::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 );
    }
    // kdDebug(5800) << "Set custom property [" << name << '=' << value << ']' << endl;
    p = icalcomponent_get_next_property(parent,ICAL_X_PROPERTY);
    lastProperty = name;
  }

  properties->setCustomProperties(customProperties);
}



void ICalFormatImpl::readRecurrenceRule(icalproperty *rrule,Incidence *incidence )
{
//  kdDebug(5800) << "Read recurrence for " << incidence->summary() << endl;

  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 )
{
//  kdDebug(5800) << "Read recurrence for " << incidence->summary() << endl;

  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->mRRule = TQString( 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;
    t = r.until;
    // Convert to the correct time zone! it's in UTC by specification.
    TQDateTime endDate( readICalDateTime(0, t) );
    recur->setEndDt( endDate );
  } else {
    if (r.count == 0)
      recur->setDuration( -1 );
    else
      recur->setDuration( r.count );
  }

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

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

#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 );

  // 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
  TQValueList<RecurrenceRule::WDayPos> wdlst;
  short day;
  index=0;
  while((day = r.by_day[index++]) != ICAL_RECURRENCE_ARRAY_MAX) {
    RecurrenceRule::WDayPos pos;
    pos.setDay( ( icalrecurrencetype_day_day_of_week( day ) + 5 )%7 + 1 );
    pos.setPos( icalrecurrencetype_day_position( day ) );
//     kdDebug(5800)<< "    o) By day, index="<<index-1<<", pos="<<pos.Pos<<", day="<<pos.Day<<endl;
    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)
{
//   kdDebug(5800) << "Read alarm for " << incidence->summary() << endl;

  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 ) {
    kdDebug(5800) << "Unknown type of alarm, using default" << endl;
//    return;
  } 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:
        kdDebug(5800) << "Unknown type of alarm: " << action << endl;
//        type = Alarm::Invalid;
    }
  }
  ialarm->setType(type);
// kdDebug(5800) << " alarm type =" << type << endl;

  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)) {
          if (icaldurationtype_is_null_duration(trigger.duration)) {
            kdDebug(5800) << "ICalFormatImpl::readAlarm(): Trigger has no time and no duration." << endl;
          } else {
            Duration duration = icaldurationtype_as_int( 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 {
          ialarm->setTime(readICalDateTime(p, trigger.time));
        }
        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;

      // Only in DISPLAY and EMAIL and PROCEDURE alarms
      case ICAL_DESCRIPTION_PROPERTY: {
        TQString description = TQString::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;
      }
      // Only in EMAIL alarm
      case ICAL_SUMMARY_PROPERTY:
        ialarm->setMailSubject(TQString::fromUtf8(icalproperty_get_summary(p)));
        break;

      // Only in EMAIL alarm
      case ICAL_ATTENDEE_PROPERTY: {
        TQString email = TQString::fromUtf8(icalproperty_get_attendee(p));
        if ( email.startsWith("mailto:", false ) ) {
          email = email.mid( 7 );
        }
        TQString name;
        icalparameter *param = icalproperty_get_first_parameter(p,ICAL_CN_PARAMETER);
        if (param) {
          name = TQString::fromUtf8(icalparameter_get_cn(param));
        }
        ialarm->addMailAddress(Person(name, email));
        break;
      }
      // Only in AUDIO and EMAIL and PROCEDURE alarms
      case ICAL_ATTACH_PROPERTY: {
        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 {
          kdDebug() << "Alarm attachments currently only support URIs, but "
                       "no binary data" << endl;
        }
        delete attach;
        break;
      }
      default:
        break;
    }

    p = icalcomponent_get_next_property(alarm,ICAL_ANY_PROPERTY);
  }

  // custom properties
  readCustomProperties(alarm, ialarm);

  // TODO: check for consistency of alarm properties
}

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

icaldatetimeperiodtype ICalFormatImpl::writeICalDateTimePeriod( const TQDateTime &date )
{
  icaldatetimeperiodtype t;
  t.time = writeICalDateTime( date );
  t.period = icalperiodtype_null_period();
  return t;
}

icaltimetype ICalFormatImpl::writeICalDate(const TQDate &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;
#ifndef USE_LIBICAL_3_0
	t.is_utc = 0;
#endif
  t.zone = 0;

  return t;
}

icaltimetype ICalFormatImpl::writeICalDateTime(const TQDateTime &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 = icaltimezone_get_builtin_timezone ( mParent->timeZoneId().latin1() );
#ifndef USE_LIBICAL_3_0
	t.is_utc = 0;
#endif

 // _dumpIcaltime( t );
  /* The TQDateTime we get passed in is to be considered in the timezone of
   * the current calendar (mParent's), or, if there is none, to be floating.
   * In the later case store a floating time, in the former normalize to utc. */
  if (mParent->timeZoneId().isEmpty())
    t = icaltime_convert_to_zone( t, 0 ); //make floating timezone
  else {
    icaltimezone* tz = icaltimezone_get_builtin_timezone ( mParent->timeZoneId().latin1() );
    icaltimezone* utc = icaltimezone_get_utc_timezone();
    if ( tz != utc ) {
      t.zone = tz;
      t = icaltime_convert_to_zone( t, utc );
    } else {
#ifndef USE_LIBICAL_3_0
			t.is_utc = 1;
#endif
      t.zone = utc;
    }
  }
//  _dumpIcaltime( t );

  return t;
}

TQDateTime ICalFormatImpl::readICalDateTime( icalproperty *p, icaltimetype& t, icaltimezone* tz )
{
//   kdDebug(5800) << "ICalFormatImpl::readICalDateTime()" << endl;
#ifdef USE_LIBICAL_3_0
  bool time_is_utc = icaltime_is_utc(t);
#else
  bool time_is_utc = t.is_utc;
#endif
	if ( !time_is_utc ) { // Only use the TZ if time is not UTC.{
    // FIXME: We'll need to make sure to apply the appropriate TZ, not just
    //        the first one found.
    icalparameter *param = p ? icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) : 0;
		const char *tzid = param ? icalparameter_get_tzid(param) : 0;
		if ( tzid ) {
				icaltimezone* icaltz;
				// Try to match the ID with the libical time zone's location property
				icaltz = icaltimezone_get_builtin_timezone( tzid );
				if ( icaltz ) {
	          //kdDebug(5800) << "ICalFormatImpl::readICalDateTime(): time zone '" << tzid << "' read from libical database" << endl;
				}
				t.zone = icaltz;
		}
		else {
		    if (tz && tz != icaltimezone_get_utc_timezone()) {
#ifndef USE_LIBICAL_3_0
						t.is_utc = 0;
#endif
						t.zone = tz;
				}
				else {
#ifndef USE_LIBICAL_3_0
						t.is_utc = 1;
#endif
						t.zone = icaltimezone_get_utc_timezone();
				}
		}
  } else {
    t.zone = icaltimezone_get_utc_timezone();
  }
  //_dumpIcaltime( t );

  // Convert to view time
  if ( !mParent->timeZoneId().isEmpty() && t.zone ) {
//    kdDebug(5800) << "--- Converting time from: " << icaltimezone_get_tzid( const_cast<icaltimezone*>( t.zone ) ) << " (" << ICalDate2TQDate(t) << ")." << endl;
    icaltimezone* viewTimeZone = icaltimezone_get_builtin_timezone ( mParent->timeZoneId().latin1() );
    icaltimezone_convert_time(  &t, const_cast<icaltimezone*>(t.zone), viewTimeZone );
//    kdDebug(5800) << "--- Converted to zone " << mParent->timeZoneId() << " (" << ICalDate2TQDate(t) << ")." << endl;
  }

  return ICalDate2TQDate(t);
}

TQDate ICalFormatImpl::readICalDate(icaltimetype t)
{
  return ICalDate2TQDate(t).date();
}

icaldurationtype ICalFormatImpl::writeICalDuration(int seconds)
{
  // 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;

  d.is_neg  = (seconds<0)?1:0;
  if (seconds<0) seconds = -seconds;

  d.weeks    = 0;
  d.days     = seconds / gSecondsPerDay;
  seconds   %= gSecondsPerDay;
  d.hours    = seconds / gSecondsPerHour;
  seconds   %= gSecondsPerHour;
  d.minutes  = seconds / gSecondsPerMinute;
  seconds   %= gSecondsPerMinute;
  d.seconds  = seconds;

  return d;
}

int ICalFormatImpl::readICalDuration(icaldurationtype d)
{
  int result = 0;

  result += d.weeks   * gSecondsPerWeek;
  result += d.days    * gSecondsPerDay;
  result += d.hours   * gSecondsPerHour;
  result += d.minutes * gSecondsPerMinute;
  result += d.seconds;

  if (d.is_neg) result *= -1;

  return result;
}

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().utf8());
  icalcomponent_add_property(calendar,p);

  // TODO: Add time zone

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

  // Custom properties
  if( cal != 0 )
    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) {
    kdDebug(5800) << "No PRODID property found" << endl;
    mLoadedProductId = "";
  } else {
    mLoadedProductId = TQString::fromUtf8(icalproperty_get_prodid(p));
//    kdDebug(5800) << "VCALENDAR prodid: '" << mLoadedProductId << "'" << endl;

    delete mCompat;
    mCompat = CompatFactory::createCompat( mLoadedProductId );
  }

  p = icalcomponent_get_first_property(calendar,ICAL_VERSION_PROPERTY);
  if (!p) {
    kdDebug(5800) << "No VERSION property found" << endl;
    mParent->setException(new ErrorFormat(ErrorFormat::CalVersionUnknown));
    return false;
  } else {
    const char *version = icalproperty_get_version(p);
    if ( !version ) {
      kdDebug(5800) << "No VERSION property found" << endl;
      mParent->setException( new ErrorFormat(
                               ErrorFormat::CalVersionUnknown,
                               i18n( "No VERSION property found" ) ) );
      return false;
    }

//    kdDebug(5800) << "VCALENDAR version: '" << version << "'" << endl;

    if (strcmp(version,"1.0") == 0) {
      kdDebug(5800) << "Expected iCalendar, got vCalendar" << endl;
      mParent->setException(new ErrorFormat(ErrorFormat::CalVersion1,
                            i18n("Expected iCalendar format")));
      return false;
    } else if (strcmp(version,"2.0") != 0) {
      kdDebug(5800) << "Expected iCalendar, got unknown format" << endl;
      mParent->setException(new ErrorFormat(ErrorFormat::CalVersionUnknown));
      return false;
    }
  }

  // custom properties
  readCustomProperties(calendar, cal);

// TODO: set time zone

  // read a VTIMEZONE if there is one
  icalcomponent *ctz =
    icalcomponent_get_first_component( calendar, ICAL_VTIMEZONE_COMPONENT );

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

  icalcomponent *c;

  // Iterate through all todos
  c = icalcomponent_get_first_component(calendar,ICAL_VTODO_COMPONENT);
  while (c) {
//    kdDebug(5800) << "----Todo found" << endl;
    Todo *todo = readTodo(c);
    if (todo) {
      if (todo->hasRecurrenceID()) {
        TQString originalUid = todo->uid();
        todo->setUid(originalUid + TQString("-recur-%1").arg(todo->recurrenceID().toTime_t()));
        if (!cal->todo(todo->uid())) {
          if ( !cal->addTodo( todo ) ) {
            cal->endBatchAdding();
            // If the user pressed cancel, return true, it's not an error.
            return cal->exception() && cal->exception()->errorCode() == ErrorFormat::UserCancel;
          }
          if (!cal->event(originalUid)) {
            printf("FIXME! [WARNING] Parent for child event does not yet exist!\n");
          }
          else {
            // Add this todo to its parent
            cal->todo(originalUid)->addChildIncidence(todo->uid());
            // And the parent to the child
            todo->addChildIncidence(cal->todo(originalUid)->uid());
          }
        }
      }
      else {
        if (!cal->todo(todo->uid())) {
          if ( !cal->addTodo( todo ) ) {
            cal->endBatchAdding();
            // If the user pressed cancel, return true, it's not an error.
            return cal->exception() && cal->exception()->errorCode() == ErrorFormat::UserCancel;
          }
        } else {
          delete todo;
          mTodosRelate.remove( 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) {
//    kdDebug(5800) << "----Event found" << endl;
    Event *event = readEvent(c, ctz);
    if (event) {
      if (event->hasRecurrenceID()) {
        TQString originalUid = event->uid();
        event->setUid(originalUid + TQString("-recur-%1").arg(event->recurrenceID().toTime_t()));
        if (!cal->event(event->uid())) {
          cal->addEvent(event);
          if (!cal->event(originalUid)) {
            printf("FIXME! [WARNING] Parent for child event does not yet exist!\n");
          }
          else {
            // Add this event to its parent
            cal->event(originalUid)->addChildIncidence(event->uid());
            // And the parent to the child
            event->addChildIncidence(cal->event(originalUid)->uid());
          }
        }
      }
      else {
        if (!cal->event(event->uid())) {
        if ( !cal->addEvent( event ) ) {
          cal->endBatchAdding();
          // If the user pressed cancel, return true, it's not an error.
          return cal->exception() && cal->exception()->errorCode() == ErrorFormat::UserCancel;
        }
        } else {
          delete event;
          mEventsRelate.remove( 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) {
//    kdDebug(5800) << "----Journal found" << endl;
    Journal *journal = readJournal(c);
    if (journal) {
      if (journal->hasRecurrenceID()) {
        TQString originalUid = journal->uid();
        journal->setUid(originalUid + TQString("-recur-%1").arg(journal->recurrenceID().toTime_t()));
        if (!cal->journal(journal->uid())) {
          cal->addJournal(journal);
          if (!cal->event(originalUid)) {
            printf("FIXME! [WARNING] Parent for child event does not yet exist!\n");
          }
          else {
            // Add this journal to its parent
            cal->journal(originalUid)->addChildIncidence(journal->uid());
            // And the parent to the child
            journal->addChildIncidence(cal->journal(originalUid)->uid());
          }
        }
      }
      else {
        if (!cal->journal(journal->uid())) {
        if ( !cal->addJournal(journal) ) {
          cal->endBatchAdding();
          // If the user pressed cancel, return true, it's not an error.
          return cal->exception() && cal->exception()->errorCode() == ErrorFormat::UserCancel;
        }
        } else {
          delete 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 = mEventsRelate.begin(); eIt != mEventsRelate.end(); ++eIt ) {
    (*eIt)->setRelatedTo( cal->incidence( (*eIt)->relatedToUid() ) );
  }
  Todo::List::ConstIterator tIt;
  for ( tIt = mTodosRelate.begin(); tIt != mTodosRelate.end(); ++tIt ) {
    (*tIt)->setRelatedTo( cal->incidence( (*tIt)->relatedToUid() ) );
   }

  return true;
}

TQString ICalFormatImpl::extractErrorProperty(icalcomponent *c)
{
//  kdDebug(5800) << "ICalFormatImpl:extractErrorProperty: "
//            << icalcomponent_as_ical_string(c) << endl;

  TQString 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);
  }

//  kdDebug(5800) << "ICalFormatImpl:extractErrorProperty: " << errorMessage << endl;

  return errorMessage;
}

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

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

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

  icalproperty_method icalmethod = ICAL_METHOD_NONE;

  switch (method) {
    case Scheduler::Publish:
      icalmethod = ICAL_METHOD_PUBLISH;
      break;
    case Scheduler::Request:
      icalmethod = ICAL_METHOD_REQUEST;
      break;
    case Scheduler::Refresh:
      icalmethod = ICAL_METHOD_REFRESH;
      break;
    case Scheduler::Cancel:
      icalmethod = ICAL_METHOD_CANCEL;
      break;
    case Scheduler::Add:
      icalmethod = ICAL_METHOD_ADD;
      break;
    case Scheduler::Reply:
      icalmethod = ICAL_METHOD_REPLY;
      break;
    case Scheduler::Counter:
      icalmethod = ICAL_METHOD_COUNTER;
      break;
    case Scheduler::Declinecounter:
      icalmethod = ICAL_METHOD_DECLINECOUNTER;
      break;
    default:
      kdDebug(5800) << "ICalFormat::createScheduleMessage(): Unknow method" << endl;
      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;
}
