/*
    This file is part of libkpimexchange
    Copyright (c) 2002 Jan-Pascal van Best <janpascal@vanbest.org>

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

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

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

#include <tqstring.h>
#include <tqregexp.h>

#include <kurl.h>
#include <kdebug.h>
#include <krfcdate.h>
#include <tdeio/job.h>

#include <tdeio/slave.h>
#include <tdeio/scheduler.h>
#include <tdeio/slavebase.h>
#include <tdeio/davjob.h>
#include <tdeio/http.h>

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

#include <libkcal/event.h>
#include <libkcal/icalformat.h>
#include <libkcal/icalformatimpl.h>
#include <libkcal/recurrence.h>
#include <libkcal/incidence.h>
#include <libkcal/event.h>

#include "exchangeclient.h"
#include "exchangeprogress.h"
#include "exchangeupload.h"
#include "exchangeaccount.h"
#include "utils.h"

using namespace KPIM;

ExchangeUpload::ExchangeUpload( KCal::Event *event, ExchangeAccount *account,
                                const TQString &timeZoneId, TQWidget *window )
  : mTimeZoneId( timeZoneId ), mWindow( window )
{
  kdDebug() << "Called ExchangeUpload" << endl;

  mAccount = account;
  m_currentUpload = event;
  m_currentUploadNumber = 0;

//  kdDebug() << "Trying to add appointment " << m_currentUpload->summary() << endl;

  // TODO: For exisiting events the URL for the uid should already be known.
  // Store it after downloading and keep the mapping

  findUid( m_currentUpload->uid() );
}

ExchangeUpload::~ExchangeUpload()
{
  kdDebug() << "Entering ExchangeUpload destructor" << endl;
  kdDebug() << "Finished ExchangeUpload destructor" << endl;
}

void ExchangeUpload::findUid( TQString const &uid )
{
  TQString query = 
        "SELECT \"DAV:href\", \"urn:schemas:calendar:uid\"\r\n"
        "FROM Scope('shallow traversal of \"\"')\r\n"
        "WHERE \"urn:schemas:calendar:uid\" = '" + uid + "'\r\n";

  kdDebug() << "Find uid query: " << endl << query << endl;
  kdDebug() << "Looking for uid " << uid << endl;
  
  TDEIO::DavJob* job = TDEIO::davSearch( mAccount->calendarURL(), "DAV:", "sql",
                                     query, false );
  job->setWindow( mWindow );
  connect( job, TQ_SIGNAL( result( TDEIO::Job * ) ),
           TQ_SLOT( slotFindUidResult( TDEIO::Job * ) ) );
}

void ExchangeUpload::slotFindUidResult( TDEIO::Job * job )
{
  kdDebug() << "slotFindUidResult()" << endl;

  if ( job->error() ) {
    kdDebug() << "Error: " << job->error() << endl;
    job->showErrorDialog( 0 );
    emit finished( this, ExchangeClient::CommunicationError,
                   "IO Error: " + TQString::number(job->error()) + ":" +
                   job->errorString() );
    return;
  }
  TQDomDocument &response = static_cast<TDEIO::DavJob *>( job )->response();

  kdDebug() << "Search uid result: " << endl << response.toString() << endl;

  TQDomElement item = response.documentElement().firstChild().toElement();
  TQDomElement hrefElement = item.namedItem( "href" ).toElement();
  if ( item.isNull() || hrefElement.isNull() ) {
    // No appointment with this UID in exchange database
    // Create a new filename for this appointment and store it there
    tryExist();
    return;
  }
  // The appointment is already in the exchange database
  // Overwrite it with the new data
  TQString href = hrefElement.text();
  KURL url( href );
  kdDebug() << "Found URL with identical uid: " << url.prettyURL()
            << ", overwriting that one" << endl;

  startUpload( toDAV( url ) );  
}

void ExchangeUpload::tryExist()
{
  // FIXME: we should first check if current's uid is already in the Exchange database
  // Maybe use locking?
  KURL url = mAccount->calendarURL();
  if ( m_currentUploadNumber == 0 )
    url.addPath( m_currentUpload->summary() + ".EML" );
  else
    url.addPath( m_currentUpload->summary() + "-" + TQString::number( m_currentUploadNumber ) + ".EML" );

  kdDebug() << "Trying to see whether " << url.prettyURL() << " exists" << endl;
 
  TQDomDocument doc;
  TQDomElement root = addElement( doc, doc, "DAV:", "propfind" );
  TQDomElement prop = addElement( doc, root, "DAV:", "prop" );
  addElement( doc, prop, "DAV:", "displayname" );
  addElement( doc, prop, "urn:schemas:calendar", "uid" );

  TDEIO::DavJob *job = TDEIO::davPropFind( url, doc, "0", false );
  job->setWindow( mWindow );
  job->addMetaData( "errorPage", "false" );
  connect( job, TQ_SIGNAL( result( TDEIO::Job * ) ),
           TQ_SLOT( slotPropFindResult( TDEIO::Job * ) ) );
}

void ExchangeUpload::slotPropFindResult( TDEIO::Job *job )
{
  kdDebug() << "slotPropFindResult()" << endl;
  int error = job->error(); 
  kdDebug() << "PROPFIND error: " << error << ":" << job->errorString() << endl;
  if ( error && error != TDEIO::ERR_DOES_NOT_EXIST ) {
    job->showErrorDialog( 0 );
    emit finished( this, ExchangeClient::CommunicationError,
                   "IO Error: " + TQString::number(error) + ":" +
                   job->errorString() );
    return;
  }

  if ( !error ) {
    // File exist, try another one
    m_currentUploadNumber++;
    tryExist();
    return;
  }

  // We got a 404 error, resource doesn't exist yet, create it
  // FIXME: race condition possible if resource is created under
  // our nose.

  KURL url = mAccount->calendarURL();
  if ( m_currentUploadNumber == 0 )
    url.addPath( m_currentUpload->summary() + ".EML" );
  else
    url.addPath( m_currentUpload->summary() + "-" +
                 TQString::number( m_currentUploadNumber ) + ".EML" );

  startUpload( url );
}

TQString timezoneid( int offset )
{
  switch ( offset ) {
    case 0: return "0";
    case -60: return "3";
    case -120: return "5";
    case -180: return "51";
    case -210: return "25";
    case -240: return "24"; // Abu Dhabi
    case -270: return "48"; // Kabul
    case -300: return "47"; // Islamabad
    case -330: return "23"; // Bombay
    case -360: return "46"; // Dhaka
    case -420: return "22"; // Bangkok
    case -480: return "45"; // Beijing
    case -540: return "20"; // Tokyo
    case -570: return "44"; // Darwin
    case -600: return "18"; // Brisbane
    case -660: return "41"; // Solomon Islands
    case -720: return "17"; // Auckland
    case 60: return "29"; // Azores
    case 120: return "30"; // Mid Atlantic
    case 180: return "8"; // Brasilia
    case 210: return "28";  // Newfoundland
    case 240: return "9"; // Atlantic time Canada
    case 300: return "10"; // Eastern
    case 360: return "11"; // Central time
    case 420: return "12"; // Mountain time
    case 480: return "13"; // Pacific time
    case 540: return "14"; // Alaska time
    case 600: return "15"; // Hawaii
    case 660: return "16"; // Midway Island
    case 720: return "39"; // Eniwetok
    default: return "52"; // Invalid time zone
  }
}


void ExchangeUpload::startUpload( const KURL &url )
{
  KCal::Event *event = static_cast<KCal::Event *>( m_currentUpload );
  if ( ! event ) {
    kdDebug() << "ERROR: trying to upload a non-Event Incidence" << endl;
    emit finished( this, ExchangeClient::NonEventError, "The incidence that is to be uploaded to the exchange server is not of type KCal::Event" );
    return;
  }

  TQDomDocument doc;
  TQDomElement root = addElement( doc, doc, "DAV:", "propertyupdate" );
  TQDomElement set = addElement( doc, root, "DAV:", "set" );
  TQDomElement prop = addElement( doc, set, "DAV:", "prop" );
  addElement( doc, prop, "DAV:", "contentclass", "urn:content-classes:appointment" );
//  addElement( doc, prop, "http://schemas.microsoft.com/exchange/", "outlookmessageclass", "IPM.appointment" );
  addElement( doc, prop, "http://schemas.microsoft.com/exchange/",
              "outlookmessageclass", "IPM.Appointment" );
 // addElement( doc, prop, "urn:schemas:calendar:", "method", "Add" );
  addElement( doc, prop, "urn:schemas:calendar:", "alldayevent", 
      event->doesFloat() ? "1" : "0" );
  addElement( doc, prop, "urn:schemas:calendar:", "busystatus", 
      event->transparency() ? "Free" : "Busy" );
  // KLUDGE: somehow we need to take the opposite of the
  // value that localUTCOffset() supplies...
  // FIXME: What do we need that offset for anyway???
  int tzOffset = - KRFCDate::localUTCOffset(); 
  TQString offsetString;
  if ( tzOffset == 0 ) 
    offsetString = "Z";
  else if ( tzOffset > 0 ) 
    offsetString = TQString( "+%1:%2" ).arg(tzOffset/60, 2).arg( tzOffset%60, 2 );
  else
    offsetString = TQString( "-%1:%2" ).arg((-tzOffset)/60, 2).arg( (-tzOffset)%60, 2 );
  offsetString = offsetString.replace( TQRegExp(" "), "0" );

  kdDebug() << "Timezone offset: " << tzOffset << " : " << offsetString << endl;
  kdDebug() << "ExchangeUpload::mTimeZoneId=" << mTimeZoneId << endl;

  addElement( doc, prop, "urn:schemas:calendar:", "dtstart", 
      zoneAsUtc( event->dtStart(), mTimeZoneId ).toString( TQt::ISODate ) + "Z" );
  //    event->dtStart().toString( "yyyy-MM-ddThh:mm:ss.zzzZ" ) );
  //    2002-06-04T08:00:00.000Z" );
  addElement( doc, prop, "urn:schemas:calendar:", "dtend", 
      zoneAsUtc( event->dtEnd(), mTimeZoneId ).toString( TQt::ISODate ) + "Z" );
#if 0
  addElement( doc, prop, "urn:schemas:calendar:", "dtstart", 
      event->dtStart().toString( "yyyy-MM-ddThh:mm:ss.zzz" )+ offsetString );
  //    event->dtStart().toString( "yyyy-MM-ddThh:mm:ss.zzzZ" ) );
  //    2002-06-04T08:00:00.000Z" );
  addElement( doc, prop, "urn:schemas:calendar:", "dtend", 
      event->dtEnd().toString( "yyyy-MM-ddThh:mm:ss.zzz" ) + offsetString );
#endif
  addElement( doc, prop, "urn:schemas:calendar:", "lastmodified", zoneAsUtc( event->lastModified(), mTimeZoneId ).toString( TQt::ISODate )+"Z" );

//  addElement( doc, prop, "urn:schemas:calendar:", "meetingstatus", "confirmed" );
  addElement( doc, prop, "urn:schemas:httpmail:", "textdescription", event->description() );
  addElement( doc, prop, "urn:schemas:httpmail:", "subject", event->summary() );
  addElement( doc, prop, "urn:schemas:calendar:", "location", event->location() );
  // addElement( doc, prop, "urn:schemas:mailheader:", "subject", event->summary() );
  addElement( doc, prop, "urn:schemas:calendar:", "uid", event->uid() );
//  addElement( doc, prop, "urn:schemas:calendar:", "organizer", event->organizer() );

  KCal::Recurrence *recurrence = event->recurrence();
  kdDebug() << "Recurrence->doesRecur(): " << recurrence->doesRecur() << endl;
  if ( recurrence->recurrenceType() != KCal::Recurrence::rNone ) {
    addElement( doc, prop, "urn:schemas:calendar:", "instancetype", "1" );
    KCal::ICalFormat *format = new KCal::ICalFormat();
    TQString recurstr = format->toString( recurrence->defaultRRule() );
    // Strip leading "RRULE\n :" and whitespace
    recurstr = recurstr.replace( TQRegExp("^[A-Z]*[\\s]*:"), "").stripWhiteSpace();
    kdDebug() << "Recurrence rule after replace: \"" << recurstr << "\"" << endl;
    delete format;
    TQDomElement rrule = addElement( doc, prop, "urn:schemas:calendar:", "rrule" );
    addElement( doc, rrule, "xml:", "v", recurstr );
    addElement( doc, prop, "urn:schemas:calendar:", "timezoneid", timezoneid( tzOffset ) );
  } else {
    addElement( doc, prop, "urn:schemas:calendar:", "instancetype", "0" );
  }

  KCal::DateList exdates = recurrence->exDates();
  if ( !exdates.isEmpty() ) {
    TQDomElement exdate = addElement( doc, prop, "urn:schemas:calendar:", "exdate" );
    KCal::DateList::iterator it;
    for ( it = exdates.begin(); it != exdates.end(); ++it ) {
      TQString date = (*it).toString( "yyyy-MM-ddT00:00:00.000" )+ offsetString;
//      TQString date = zoneAsUtc( (*it), mTimeZoneId ).toString( TQt::ISODate );
      addElement( doc, exdate, "xml:", "v", date );
    }
  }

  KCal::Alarm::List alarms = event->alarms();
  if ( alarms.count() > 0 ) {
    KCal::Alarm* alarm = alarms.first();
    // TODO: handle multiple alarms
    // TODO: handle end offsets and general alarm times
    // TODO: handle alarm types
    if ( alarm->hasStartOffset() ) {
      int offset = - alarm->startOffset().asSeconds();
      addElement( doc, prop, "urn:schemas:calendar:", "reminderoffset", TQString::number( offset ) );
    }
  }

  kdDebug() << "Uploading event: " << endl;
  kdDebug() << doc.toString() << endl;

  kdDebug() << "Upload url: " << url << endl;

  TDEIO::DavJob *job = TDEIO::davPropPatch( url, doc, false );
  job->setWindow( mWindow );
  connect( job, TQ_SIGNAL( result( TDEIO::Job * ) ),
           TQ_SLOT( slotPatchResult( TDEIO::Job * ) ) );
}

void ExchangeUpload::slotPatchResult( TDEIO::Job *job )
{
  kdDebug() << "slotPropPatchResult()" << endl;
  if ( job->error() ) {
    job->showErrorDialog( 0 );
    kdDebug() << "Error: " << job->error() << endl;
    emit finished( this, ExchangeClient::CommunicationError,
                   "IO Error: " + TQString::number(job->error()) + ":" +
                   job->errorString() );
    return;
  }
  TQDomDocument response = static_cast<TDEIO::DavJob *>( job )->response();
  kdDebug() << "Patch result: " << response.toString() << endl;

  // Either we have a "201 Created" (if a new event has been created) or 
  // we have a "200 OK" (if an existing event has been altered),
  // or else an error has occurred ;)
  TQDomElement status = response.documentElement().namedItem( "response" )
                       .namedItem( "status" ).toElement();
  TQDomElement propstat = response.documentElement().namedItem( "response" )
                         .namedItem( "propstat" ).namedItem( "status" )
                         .toElement();
  kdDebug() << "status: " << status.text() << endl;
  kdDebug() << "propstat: " << propstat.text() << endl;
  if ( ! ( status.text().contains( "201" ) || 
           propstat.text().contains( "200" ) ) )
    emit finished( this, ExchangeClient::EventWriteError,
                   "Upload error response: \n" + response.toString() ); 
  else 
    emit finished( this, ExchangeClient::ResultOK, TQString() );
}

#include "exchangeupload.moc"
