/*
 *     Copyright (C) 2003, 2004 by Mark Bucciarelli <mark@hubcapconsutling.com>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program 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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License along
 *   with this program; if not, write to the
 *      Free Software Foundation, Inc.
 *      51 Franklin Street, Fifth Floor
 *      Boston, MA  02110-1301  USA.
 *
 */

#include <cassert>
#include "edithistorydialog.h"
#include <fcntl.h>
#include <kcal/incidence.h>
#include <KApplication>       // kapp
#include "karmstorage.h"
#include "ktimetrackerutility.h"
#include <KDebug>
#include <kemailsettings.h>
#include <KLocale>            // i18n
#include <KMessageBox>
#include <kpimprefs.h>        // for timezone
#include <KProgressDialog>
#include <KTemporaryFile>
#include "ktimetracker.h"
#include <KUrl>
#include <kio/netaccess.h>
#include <QByteArray>
#include <QDateTime>
#include <QFile>
#include <QList>
#include <QMultiHash>
#include <QSize>
#include <QString>
#include <QStringList>
#include <QTableWidget>
#include <QTextStream>
#include <kcal/resourcecalendar.h>
#include <kcal/resourcelocal.h>
#include <resourceremote.h>
#include <sys/stat.h>
#include "storageadaptor.h"
#include "task.h"
#include "taskview.h"
#include "timekard.h"
#include <sys/types.h>
#include <unistd.h>


//@cond PRIVATE
class KarmStorage::Private {
  public:
    Private() : mCalendar( 0 ) {}
    ~Private() {
      delete mCalendar;
    }
    KCal::ResourceCalendar *mCalendar;
    QString mICalFile;
};
//@endcond

KarmStorage::KarmStorage() : d( new Private() )
{
}

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

QString KarmStorage::load( TaskView* view, const QString &fileName )
// loads data from filename into view. If no filename is given, filename from preferences is used.
// filename might be of use if this program is run as embedded konqueror plugin.
{
  kDebug(5970) << "Entering function";
  QString err;
  KEMailSettings settings;
  QString lFileName = fileName;

  assert( !( lFileName.isEmpty() ) );

  // If same file, don't reload
  if ( lFileName == d->mICalFile ) return err;


  // If file doesn't exist, create a blank one to avoid ResourceLocal load
  // error.  We make it user and group read/write, others read.  This is
  // masked by the users umask.  (See man creat)
  if ( !( remoteResource( lFileName ) ) ) {
    int handle;
    handle = open (
               QFile::encodeName( lFileName ),
               O_CREAT | O_EXCL | O_WRONLY,
               S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH
             );
    if ( handle != -1 ) {
      close( handle );
    }
  }

  if ( d->mCalendar)
    closeStorage();

  // Create local file resource and add to resources
  d->mICalFile = lFileName;

  KCal::ResourceCached *resource;
  if ( remoteResource( d->mICalFile ) )
  {
    KUrl url( d->mICalFile );
    resource = new KCal::ResourceRemote( url, url ); // same url for upload and download
  }
  else
  {
    resource = new KCal::ResourceLocal( d->mICalFile );
  }
  d->mCalendar = resource;

  QObject::connect (d->mCalendar, SIGNAL(resourceChanged(ResourceCalendar *)),
  	            view, SLOT(iCalFileModified(ResourceCalendar *)));
  d->mCalendar->setTimeSpec( KPIM::KPimPrefs::timeSpec() );
  d->mCalendar->setResourceName( QString::fromLatin1("KTimeTracker") );
  d->mCalendar->open();
  d->mCalendar->load();

  // Claim ownership of iCalendar file if no one else has.
  KCal::Person owner = resource->owner();
  if ( owner.isEmpty() )
  {
    resource->setOwner( KCal::Person(
          settings.getSetting( KEMailSettings::RealName ),
          settings.getSetting( KEMailSettings::EmailAddress ) ) );
  }

  // Build task view from iCal data
  if (!err.isEmpty())
  {
    KCal::Todo::List todoList;
    KCal::Todo::List::ConstIterator todo;
    QMultiHash< QString, Task* > map;

    // Build dictionary to look up Task object from Todo uid.  Each task is a
    // QListViewItem, and is initially added with the view as the parent.
    todoList = d->mCalendar->rawTodos();
    kDebug(5970) << "KarmStorage::load"
      << "rawTodo count (includes completed todos) ="
      << todoList.count();
    for( todo = todoList.constBegin(); todo != todoList.constEnd(); ++todo )
    {
      Task* task = new Task(*todo, view);
      map.insert( (*todo)->uid(), task );
      view->setRootIsDecorated(true);
      task->setPixmapProgress();
    }

    // Load each task under it's parent task.
    for( todo = todoList.constBegin(); todo != todoList.constEnd(); ++todo )
    {
      Task* task = map.value( (*todo)->uid() );

      // No relatedTo incident just means this is a top-level task.
      if ( (*todo)->relatedTo() )
      {
        Task* newParent = map.value( (*todo)->relatedToUid() );

        // Complete the loading but return a message
        if ( !newParent )
          err = i18n("Error loading \"%1\": could not find parent (uid=%2)",
             task->name(),
             (*todo)->relatedToUid());

        if (!err.isEmpty()) task->move( newParent );
      }
    }

    kDebug(5970) << "KarmStorage::load - loaded" << view->count()
      << "tasks from" << d->mICalFile;
  }

  if ( view ) buildTaskView(d->mCalendar, view);
  return err;
}

Task* KarmStorage::task( const QString& uid, TaskView* view )
// return the tasks with the uid uid out of view.
// If !view, return the todo with the uid uid.
{
  kDebug(5970) << "Entering function";
  KCal::Todo::List todoList;
  KCal::Todo::List::ConstIterator todo;
  todoList = d->mCalendar->rawTodos();
  todo = todoList.constBegin();
  Task* result=0;
  bool konsolemode=false;
  if ( view == 0 ) konsolemode=true;
  while ( todo != todoList.constEnd() && ( (*todo)->uid() != uid ) ) ++todo;
  if ( todo != todoList.constEnd() ) result = new Task((*todo), view, konsolemode);
  kDebug(5970) << "Leaving function, returning " << result;
  return result;
}

QString KarmStorage::icalfile()
{
  kDebug(5970) << "Entering function";
  return d->mICalFile;
}

QString KarmStorage::buildTaskView(KCal::ResourceCalendar *rc, TaskView *view)
// makes *view contain the tasks out of *rc.
{
  kDebug(5970) << "Entering function";
  QString err;
  KCal::Todo::List todoList;
  KCal::Todo::List::ConstIterator todo;
  QMultiHash< QString, Task* > map;
  QVector<QString> runningTasks;
  QVector<QDateTime> startTimes;

  // remember tasks that are running and their start times
  QTreeWidgetItemIterator it( view );
  while ( *it ) 
  {
    Task *task = static_cast< Task* >( *it );
    if ( task->isRunning() ) {
      runningTasks.append( task->uid() );
      startTimes.append( task->startTime() );
    }
    ++it;
  }

  view->clear();
  todoList = rc->rawTodos();
  for( todo = todoList.constBegin(); todo != todoList.constEnd(); ++todo )
  {
    Task* task = new Task(*todo, view);
    task->setWhatsThis(0,"The task name is how you call the task, it can be chosen freely.");
    task->setWhatsThis(1,"The session time is the time since you last chose \"start new session.\"");
    map.insert( (*todo)->uid(), task );
    view->setRootIsDecorated(true);
    task->setPixmapProgress();
  }

  // 1.1. Load each task under it's parent task.
  for( todo = todoList.constBegin(); todo != todoList.constEnd(); ++todo )
  {
    Task* task = map.value( (*todo)->uid() );
    // No relatedTo incident just means this is a top-level task.
    if ( (*todo)->relatedTo() )
    {
      Task* newParent = map.value( (*todo)->relatedToUid() );

      // Complete the loading but return a message
      if ( !newParent )
        err = i18n("Error loading \"%1\": could not find parent (uid=%2)",
           task->name(),
           (*todo)->relatedToUid());
      else task->move( newParent );
    }
  }

  view->clearActiveTasks();
  // restart tasks that have been running with their start times
  for ( int i=0; i<view->count(); i++) {
    for ( int n = 0; n < runningTasks.count(); ++n ) {
      if ( runningTasks[n] == view->itemAt(i)->uid() ) {
        view->startTimerFor( view->itemAt(i), startTimes[n] );
      }
    }
  }

  view->refresh();

  return err;
}

QString KarmStorage::buildTaskView(TaskView *view)
// makes *view contain the tasks out of mCalendar
{
  return buildTaskView(d->mCalendar, view);
}

void KarmStorage::closeStorage()
{
  kDebug(5970) << "Entering function";
  if ( d->mCalendar )
  {
    d->mCalendar->close();
    delete d->mCalendar;
    d->mCalendar = 0;
  }
  kDebug(5970) << "Leaving function";
}

KCal::Event::List KarmStorage::rawevents()
{
  kDebug(5970) << "Entering function";
  return d->mCalendar->rawEvents();
}

bool KarmStorage::allEventsHaveEndTiMe(Task* task)
{
  kDebug(5970) << "Entering function";
  KCal::Event::List eventList = d->mCalendar->rawEvents();
  for(KCal::Event::List::iterator i = eventList.begin();
      i != eventList.end();
      ++i)
  {
    if ( (*i)->relatedToUid() == task->uid()
        || ( (*i)->relatedTo()
            && (*i)->relatedTo()->uid() == task->uid() ) )
    {
      if ( !(*i)->hasEndDate() ) return false;
    }
  }
  return true;
}

bool KarmStorage::allEventsHaveEndTiMe()
{
  kDebug(5970) << "Entering function";
  KCal::Event::List eventList = d->mCalendar->rawEvents();
  for(KCal::Event::List::iterator i = eventList.begin();
      i != eventList.end();
      ++i)
  {
    if ( !(*i)->hasEndDate() ) return false;
  }
  return true;
}

QString KarmStorage::save(TaskView* taskview)
{
  kDebug(5970) << "Entering function";
  QString err;

  QStack<KCal::Todo*> parents;
  if ( taskview ) // we may also be in konsole mode
  {
    for (int i = 0; i < taskview->topLevelItemCount(); ++i ) 
    {
      Task *task = static_cast< Task* >( taskview->topLevelItem( i ) );
      kDebug( 5970 ) << "write task" << task->name();
      err = writeTaskAsTodo( task, parents );
    }
  }

  err=saveCalendar();

  if ( err.isEmpty() )
  {
    kDebug(5970)
      << "KarmStorage::save : wrote tasks to" << d->mICalFile;
  }
  else
  {
    kWarning(5970) << "KarmStorage::save :" << err;
  }

  return err;
}

QString KarmStorage::setTaskParent( Task* task, Task* parent )
{
  kDebug(5970) << "Entering function";
  QString err;
  KCal::Todo* toDo;
  toDo = d->mCalendar->todo(task->uid());
  if (parent==0) toDo->removeRelation(toDo->relatedTo());
  else toDo->setRelatedTo(d->mCalendar->todo(parent->uid()));
  kDebug(5970) << "Leaving function";
  return err;
}

QString KarmStorage::writeTaskAsTodo(Task* task, QStack<KCal::Todo*>& parents )
{
  kDebug(5970) << "Entering function";
  QString err;
  KCal::Todo* todo;

  todo = d->mCalendar->todo(task->uid());
  if ( !todo )
  {
    kDebug(5970) << "Could not get todo from calendar";
    return "Could not get todo from calendar";
  }
  task->asTodo(todo);
  if ( !parents.isEmpty() ) todo->setRelatedTo( parents.top() );
  parents.push( todo );

  for ( int i = 0; i < task->childCount(); ++i ) 
  {
    Task *nextTask = static_cast< Task* >( task->child( i ) );
    err = writeTaskAsTodo( nextTask, parents );
  }

  parents.pop();
  return err;
}

bool KarmStorage::isEmpty()
{
  kDebug(5970) << "Entering function";
  KCal::Todo::List todoList;

  todoList = d->mCalendar->rawTodos();
  return todoList.empty();
}

//----------------------------------------------------------------------------
// Routines that handle Comma-Separated Values export file format.
//
QString KarmStorage::exportcsvFile( TaskView *taskview,
                                    const ReportCriteria &rc )
{
  kDebug(5970) << "Entering function";
  QString delim = rc.delimiter;
  QString dquote = rc.quote;
  QString double_dquote = dquote + dquote;
  bool to_quote = true;

  QString err;
  Task* task;
  int maxdepth=0;

  QString title = i18n("Export Progress");
  KProgressDialog dialog( taskview, 0, title );
  dialog.setAutoClose( true );
  dialog.setAllowCancel( true );
  dialog.progressBar()->setMaximum( 2 * taskview->count() );

  // The default dialog was not displaying all the text in the title bar.
  int width = taskview->fontMetrics().width(title) * 3;
  QSize dialogsize;
  dialogsize.setWidth(width);
  dialog.setInitialSize( dialogsize );

  if ( taskview->count() > 1 ) dialog.show();

  QString retval;

  // Find max task depth
  int tasknr = 0;
  while ( tasknr < taskview->count() && !dialog.wasCancelled() )
  {
    dialog.progressBar()->setValue( dialog.progressBar()->value() + 1 );
    if ( tasknr % 15 == 0 ) kapp->processEvents(); // repainting is slow
    if ( taskview->itemAt(tasknr)->depth() > maxdepth )
      maxdepth = taskview->itemAt(tasknr)->depth();
    tasknr++;
  }

  // Export to file
  tasknr = 0;
  while ( tasknr < taskview->count() && !dialog.wasCancelled() )
  {
    task = taskview->itemAt( tasknr );
    dialog.progressBar()->setValue( dialog.progressBar()->value() + 1 );
    if ( tasknr % 15 == 0 ) kapp->processEvents();

    // indent the task in the csv-file:
    for ( int i=0; i < task->depth(); ++i ) retval += delim;

    /*
    // CSV compliance
    // Surround the field with quotes if the field contains
    // a comma (delim) or a double quote
    if (task->name().contains(delim) || task->name().contains(dquote))
      to_quote = true;
    else
      to_quote = false;
    */
    to_quote = true;

    if (to_quote)
      retval += dquote;

    // Double quotes replaced by a pair of consecutive double quotes
    retval += task->name().replace( dquote, double_dquote );

    if (to_quote)
      retval += dquote;

    // maybe other tasks are more indented, so to align the columns:
    for ( int i = 0; i < maxdepth - task->depth(); ++i ) retval += delim;

    retval += delim + formatTime( task->sessionTime(),
                                   rc.decimalMinutes )
           + delim + formatTime( task->time(),
                                   rc.decimalMinutes )
           + delim + formatTime( task->totalSessionTime(),
                                   rc.decimalMinutes )
           + delim + formatTime( task->totalTime(),
                                   rc.decimalMinutes )
           + '\n';
    tasknr++;
  }

  // save, either locally or remote
  if ((rc.url.isLocalFile()) || (!rc.url.url().contains("/")))
  {
    QString filename=rc.url.path();
    if (filename.isEmpty()) filename=rc.url.url();
    QFile f( filename );
    if( !f.open( QIODevice::WriteOnly ) ) {
        err = i18n( "Could not open \"%1\".", filename );
    }
    if (err.length()==0)
    {
      QTextStream stream(&f);
      // Export to file
      stream << retval;
      f.close();
    }
  }
  else // use remote file
  {
    KTemporaryFile tmpFile;
    if ( !tmpFile.open() ) err = QString::fromLatin1( "Unable to get temporary file" );
    else
    {
      QTextStream stream ( &tmpFile );
      stream << retval;
      stream.flush();
      if (!KIO::NetAccess::upload( tmpFile.fileName(), rc.url, 0 )) err=QString::fromLatin1("Could not upload");
    }
  }

  return err;
}

//----------------------------------------------------------------------------
// Routines that handle logging KArm history
//

//
// public routines:
//

QString KarmStorage::addTask(const Task* task, const Task* parent)
{
  kDebug(5970) << "Entering function";
  KCal::Todo* todo;
  QString uid;

  todo = new KCal::Todo();
  if ( d->mCalendar->addTodo( todo ) )
  {
    task->asTodo( todo  );
    if (parent)
      todo->setRelatedTo(d->mCalendar->todo(parent->uid()));
    uid = todo->uid();
  }
  else
  {
    // Most likely a lock could not be pulled, although there are other
    // possiblities (like a really confused resource manager).
    uid = "";
  }

  return uid;
}

QStringList KarmStorage::taskidsfromname(QString taskname)
{
  kDebug(5970) << "Entering function";
  QStringList result;
  KCal::Todo::List todoList = d->mCalendar->rawTodos();
  for(KCal::Todo::List::iterator i = todoList.begin();
      i != todoList.end();
      ++i)
  {
    kDebug(5970) << (*i)->uid();
    if ( (*i)->summary() == taskname ) result << (*i)->uid();
  }
  return result;
}

QStringList KarmStorage::taskNames() const
{
  kDebug(5970) << "Entering function";
  QStringList result;
  KCal::Todo::List todoList = d->mCalendar->rawTodos();
  for(KCal::Todo::List::iterator i = todoList.begin();
      i != todoList.end();
      ++i) result << (*i)->summary();
  return result;
}

bool KarmStorage::removeTask(Task* task)
{
  kDebug(5970) << "Entering function";
  // delete history
  KCal::Event::List eventList = d->mCalendar->rawEvents();
  for(KCal::Event::List::iterator i = eventList.begin();
      i != eventList.end();
      ++i)
  {
    if ( (*i)->relatedToUid() == task->uid()
        || ( (*i)->relatedTo()
            && (*i)->relatedTo()->uid() == task->uid()))
    {
      d->mCalendar->deleteEvent(*i);
    }
  }

  // delete todo
  KCal::Todo *todo = d->mCalendar->todo(task->uid());
  d->mCalendar->deleteTodo(todo);

  // Save entire file
  saveCalendar();

  return true;
}

bool KarmStorage::removeTask(QString taskid)
{
  kDebug(5970) << "Entering function";
  // delete history
  KCal::Event::List eventList = d->mCalendar->rawEvents();
  for(KCal::Event::List::iterator i = eventList.begin();
      i != eventList.end();
      ++i)
  {
    if ( (*i)->relatedToUid() == taskid
        || ( (*i)->relatedTo()
            && (*i)->relatedTo()->uid() == taskid))
    {
      d->mCalendar->deleteEvent(*i);
    }
  }

  // delete todo
  KCal::Todo *todo = d->mCalendar->todo(taskid);
  d->mCalendar->deleteTodo(todo);

  // Save entire file
  saveCalendar();

  return true;
}

void KarmStorage::addComment(const Task* task, const QString& comment)
{
  kDebug(5970) << "Entering function";
  KCal::Todo* todo;

  todo = d->mCalendar->todo(task->uid());

  // Do this to avoid compiler warnings about comment not being used.  once we
  // transition to using the addComment method, we need this second param.
  QString s = comment;

  // TODO: Use libkcal comments
  // todo->addComment(comment);
  // temporary
  todo->setDescription(task->comment());

  saveCalendar();
}

QString KarmStorage::report( TaskView *taskview, const ReportCriteria &rc )
{
  kDebug(5970) << "Entering function";
  QString err;
  if ( rc.reportType == ReportCriteria::CSVHistoryExport )
  {
    err = exportcsvHistory( taskview, rc.from, rc.to, rc );
  }
  else // if ( rc.reportType == ReportCriteria::CSVTotalsExport )
  {
    if ( !rc.bExPortToClipBoard )
      err = exportcsvFile( taskview, rc );
    else
      err = taskview->clipTotals( rc );
  }
  return err;
}

// export history report as csv, all tasks X all dates in one block
QString KarmStorage::exportcsvHistory ( TaskView      *taskview,
                                            const QDate   &from,
                                            const QDate   &to,
                                            const ReportCriteria &rc)
{
  kDebug(5970) << "Entering function";
  QString delim = rc.delimiter;
  const QString cr = QString::fromLatin1("\n");
  QString err=QString::null;
  QString retval;
  Task* task;

  QTableWidget* itab=new QTableWidget(); // hello, ABAP
  itab->setRowCount(taskview->count()+1);
  itab->setColumnCount(from.daysTo(to)+2);
  // parameter-plausi
  if ( from > to )
  {
    err = QString("'to' has to be a date later than or equal to 'from'.");
  }
  else
  {
    itab->setItem(0,0,new QTableWidgetItem("Task name"));
    for ( QDate mdate=from; mdate.daysTo(to)>=0; mdate=mdate.addDays(1) ) 
    {
      kDebug(5970) << mdate.toString();
      itab->setItem(0,1+from.daysTo(mdate),new QTableWidgetItem(mdate.toString()));
      for ( int n=0; n<taskview->count(); n++ )
      {
        task=taskview->itemAt(n);
        itab->setItem(n+1,0,new QTableWidgetItem(taskview->itemAt(n)->name()));
        KCal::Event::List eventList = d->mCalendar->rawEvents();
        for(KCal::Event::List::iterator i = eventList.begin();
            i != eventList.end();
            ++i)
        {
          if ( (*i)->relatedToUid() == task->uid() )
          {
            kDebug(5970) << "found an event for task, event=" << (*i)->uid();
            // dtStart is stored like DTSTART;TZID=Europe/Berlin:20080327T231056
            // dtEnd is stored like DTEND:20080327T231509Z
            // we need to subtract the offset from UTC.  
            KDateTime startTime=(*i)->dtStart().addSecs((*i)->dtStart().utcOffset());
            KDateTime endTime=(*i)->dtEnd().addSecs((*i)->dtEnd().utcOffset());
            KDateTime NextMidNight=startTime;
            NextMidNight.setTime(QTime ( 0,0 ));
            NextMidNight=NextMidNight.addDays(1);
            // LastMidNight := mdate.setTime(0:00) as it would read in a decent programming language
              KDateTime LastMidNight=KDateTime::currentLocalDateTime();
              LastMidNight.setDate(mdate);
              LastMidNight.setTime(QTime(0,0));
            int secsstartTillMidNight=startTime.secsTo(NextMidNight);
            int secondsToAdd=0; // seconds that need to be added to the actual cell
            if ( (startTime.date()==mdate) && ((*i)->dtEnd().date()==mdate) ) // all the event occurred today
              secondsToAdd=startTime.secsTo(endTime);
            if ( (startTime.date()==mdate) && (endTime.date()>mdate) ) // the event started today, but ended later
              secondsToAdd=secsstartTillMidNight;
            if ( (startTime.date()<mdate) && (endTime.date()==mdate) ) // the event started before today and ended today
              secondsToAdd=LastMidNight.secsTo((*i)->dtEnd());
            if ( (startTime.date()<mdate) && (endTime.date()>mdate) ) // the event started before today and ended after
              secondsToAdd=86400;
            int secondsSum=secondsToAdd;
            if (itab->item(n+1,from.daysTo(mdate)+1))
              secondsSum=itab->item(n+1,from.daysTo(mdate)+1)->text().toInt()+secondsToAdd;
            itab->setItem(n+1,from.daysTo(mdate)+1,new QTableWidgetItem(QString::number(secondsSum)));
          };
        }
      } 
    }
    // use the internal table itab to create the return value retval
    for ( int y=0; y<=(taskview->count()); y++ )
    {
      if (itab->item(y,0)) retval.append(itab->item(y,0)->text());  // task names
      for ( int x=1; x<=from.daysTo(to)+1; x++ )
      {
        retval.append(rc.delimiter);
        if (y>0)
        {
          if (itab->item(y,x)) 
          {
            kDebug(5970) << "itab->item(y,x)=" << itab->item(y,x)->text();
            retval.append(formatTime( itab->item(y,x)->text().toInt()/60.0, rc.decimalMinutes ));
          }
        }
        else // heading
        {
          retval.append(itab->item(y,x)->text());
        }
      };
      retval.append("\n");
    }
    kDebug() << "Retval is \n" << retval;
  }
  // itab->show(); // GREAT for debugging purposes :)
  if (rc.bExPortToClipBoard)
    taskview->setClipBoardText(retval);
  else
  {
    // store the file locally or remote
    if ((rc.url.isLocalFile()) || (!rc.url.url().contains("/")))
    {
      kDebug(5970) << "storing a local file";
      QString filename=rc.url.path();
      if (filename.isEmpty()) filename=rc.url.url();
      QFile f( filename );
      if( !f.open( QIODevice::WriteOnly ) ) 
      {
        err = i18n( "Could not open \"%1\".", filename );
        kDebug(5970) << "Could not open file";
      }
      kDebug() << "Err is " << err;
      if (err.length()==0)
      {
        QTextStream stream(&f);
        kDebug(5970) << "Writing to file: " << retval;
        // Export to file
        stream << retval;
        f.close();
      }
    } 
    else // use remote file
    {
      KTemporaryFile tmpFile;
      if ( !tmpFile.open() )
      {
        err = QString::fromLatin1( "Unable to get temporary file" );
      }
      else
      {
        QTextStream stream ( &tmpFile );
        stream << retval;
        stream.flush();
        if (!KIO::NetAccess::upload( tmpFile.fileName(), rc.url, 0 )) err=QString::fromLatin1("Could not upload");
      }
    }
  }
  return err;
}

void KarmStorage::startTimer( const Task* task, const KDateTime &when )
{
  kDebug(5970) << "Entering function; when=" << when;
  KCal::Event* e;
  e = baseEvent(task);
  e->setDtStart(when);
  d->mCalendar->addEvent(e);
  task->taskView()->scheduleSave();
}

void KarmStorage::startTimer( QString taskID )
{
  kDebug(5970) << "Entering function";
  KCal::Todo::List todoList;
  KCal::Todo::List::ConstIterator todo;
  todoList = d->mCalendar->rawTodos();
  for( todo = todoList.constBegin(); todo != todoList.constEnd(); ++todo )
  {
    kDebug(5970) << (*todo)->uid();
    kDebug(5970) << taskID;
    if ( (*todo)->uid() == taskID )
    {
      kDebug(5970) << "adding event";
      KCal::Event* e;
      e = baseEvent((*todo));
      e->setDtStart(KDateTime::currentLocalDateTime());
      d->mCalendar->addEvent(e);
    }
  }
  saveCalendar();
}

void KarmStorage::stopTimer( const Task* task, const QDateTime &when )
{
  kDebug(5970) << "Entering function; when=" << when;
  KCal::Event::List eventList = d->mCalendar->rawEvents();
  for(KCal::Event::List::iterator i = eventList.begin();
      i != eventList.end();
      ++i)
  {
    if ( (*i)->relatedToUid() == task->uid() )
    {
      kDebug(5970) << "found an event for task, event=" << (*i)->uid();
      if (!(*i)->hasEndDate())
      {
        kDebug(5970) << "this event has no enddate";
	QString s=when.toString("yyyy-MM-ddThh:mm:ss.zzzZ"); // need the KDE standard from the ISO standard, not the QT one
	KDateTime kwhen=KDateTime::fromString(s);
	kDebug() << "kwhen ==" <<  kwhen;
        (*i)->setDtEnd(kwhen);
      }
      else 
      {
        kDebug(5970) << "this event has an enddate";
        kDebug(5970) << "end date is " << (*i)->dtEnd();
      }
    };
  }
  saveCalendar();
}

bool KarmStorage::bookTime(const Task* task,
                           const QDateTime& startDateTime,
                           const long durationInSeconds)
{
  kDebug(5970) << "Entering function";
  // Ignores preferences setting re: logging history.
  KCal::Event* e;
  QDateTime end;
  KDateTime start( startDateTime, KDateTime::Spec::LocalZone() ); //??? is LocalZone correct ???

  e = baseEvent( task );
  e->setDtStart( start );
  e->setDtEnd( start.addSecs( durationInSeconds ) );

  // Use a custom property to keep a record of negative durations
  e->setCustomProperty( KGlobal::mainComponent().componentName().toUtf8(),
      QByteArray("duration"),
      QString::number(durationInSeconds));

  return d->mCalendar->addEvent(e);
}

void KarmStorage::changeTime(const Task* task, const long deltaSeconds)
{
  kDebug(5970) << "Entering function; deltaSeconds=" << deltaSeconds;
  KCal::Event* e;
  QDateTime end;

  e = baseEvent(task);

  // Don't use duration, as ICalFormatImpl::writeIncidence never writes a
  // duration, even though it looks like it's used in event.cpp.
  end = task->startTime();
  if ( deltaSeconds > 0 ) end = task->startTime().addSecs(deltaSeconds);
  e->setDtEnd(KDateTime(end, KDateTime::Spec::LocalZone()));

  // Use a custom property to keep a record of negative durations
  e->setCustomProperty( KGlobal::mainComponent().componentName().toUtf8(),
      QByteArray("duration"),
      QString::number(deltaSeconds));

  d->mCalendar->addEvent(e);

  task->taskView()->scheduleSave();
}


KCal::Event* KarmStorage::baseEvent(const Task * task)
{
  kDebug(5970) << "Entering function";
  KCal::Event* e;
  QStringList categories;

  e = new KCal::Event;
  e->setSummary(task->name());

  // Can't use setRelatedToUid()--no error, but no RelatedTo written to disk
  e->setRelatedTo(d->mCalendar->todo(task->uid()));

  // Debugging: some events where not getting a related-to field written.
  assert(e->relatedTo()->uid() == task->uid());

  // Have to turn this off to get datetimes in date fields.
  e->setAllDay(false);
  e->setDtStart(KDateTime(task->startTime(), KDateTime::Spec::LocalZone()));

  // So someone can filter this mess out of their calendar display
  categories.append(i18n("KTimeTracker"));
  e->setCategories(categories);

  return e;
}

KCal::Event* KarmStorage::baseEvent(const Todo* todo)
{
  kDebug(5970) << "Entering function";
  KCal::Event* e;
  QStringList categories;

  e = new KCal::Event;
  e->setSummary(todo->summary());

  // Can't use setRelatedToUid()--no error, but no RelatedTo written to disk
  e->setRelatedTo(d->mCalendar->todo(todo->uid()));

  // Have to turn this off to get datetimes in date fields.
  e->setAllDay(false);
  e->setDtStart(KDateTime(todo->dtStart()));

  // So someone can filter this mess out of their calendar display
  categories.append(i18n("KTimeTracker"));
  e->setCategories(categories);

  return e;
}

HistoryEvent::HistoryEvent( const QString &uid, const QString &name,
                            long duration, const KDateTime &start,
                            const KDateTime &stop, const QString &todoUid )
{
  _uid = uid;
  _name = name;
  _duration = duration;
  _start = start;
  _stop = stop;
  _todoUid = todoUid;
}


QList<HistoryEvent> KarmStorage::getHistory(const QDate& from,
    const QDate& to)
{
  kDebug(5970) << "Entering function";
  QList<HistoryEvent> retval;
  QStringList processed;
  KCal::Event::List events;
  KCal::Event::List::iterator event;
  QString duration;

  for( QDate date = from; date <= to; date = date.addDays( 1 ) ) {
    events = d->mCalendar->rawEventsForDate( date, KPIM::KPimPrefs::timeSpec() );
    for (event = events.begin(); event != events.end(); ++event) 
    {

      // KArm events have the custom property X-KDE-Karm-duration
      if (! processed.contains( (*event)->uid())) 
      {
        // If an event spans multiple days, CalendarLocal::rawEventsForDate
        // will return the same event on both days.  To avoid double-counting
        // such events, we (arbitrarily) attribute the hours from both days on
        // the first day.  This mis-reports the actual time spent, but it is
        // an easy fix for a (hopefully) rare situation.
        processed.append( (*event)->uid());

        // ktimetracker was name karm till KDE 3 incl.
        // if a KDE-karm-duRation exists and no KDE-ktimetracker-Duration, change this
        if (
          (*event)->customProperty( KGlobal::mainComponent().componentName().toUtf8(),
          QByteArray( "duration" )) == QString::null && (*event)->customProperty( "karm",
          QByteArray( "duration" )) != QString::null )

          (*event)->setCustomProperty(  
            KGlobal::mainComponent().componentName().toUtf8(),
            QByteArray( "duration" ), (*event)->customProperty( "karm",
            QByteArray( "duration" )));

        duration = (*event)->customProperty(KGlobal::mainComponent().componentName().toUtf8(),
            QByteArray("duration"));
        if ( ! duration.isNull() )
        {
          if ( (*event)->relatedTo()
              &&  ! (*event)->relatedTo()->uid().isEmpty() )
          {
            retval.append(HistoryEvent(
                (*event)->uid(),
                (*event)->summary(),
                duration.toLong(),
                (*event)->dtStart(),
                (*event)->dtEnd(),
                (*event)->relatedTo()->uid()
                ));
          }
          else
            // Something is screwy with the ics file, as this KArm history event
            // does not have a todo related to it.  Could have been deleted
            // manually?  We'll continue with report on with report ...
            kDebug(5970) <<"KarmStorage::getHistory():"
              << "The event" << (*event)->uid()
              << "is not related to a todo.  Dropped.";
        }
      }
    }
  }

  return retval;
}

bool KarmStorage::remoteResource( const QString& file ) const
{
  kDebug(5970) << "Entering function";
  QString f = file.toLower();
  bool rval = f.startsWith( "http://" ) || f.startsWith( "ftp://" );

  kDebug(5970) << "KarmStorage::remoteResource(" << file <<" ) returns" << rval;
  return rval;
}

QString KarmStorage::saveCalendar()
{
  kDebug(5970) << "Entering function";
  QString err;
  KABC::Lock *lock = d->mCalendar->lock();
  if ( !lock || !lock->lock() ) err=QString("Could not save. Could not lock file.");

  if ( d->mCalendar->save() ) 
  {
    lock->unlock();
  }
  else err=QString("Could not save. Could lock file.");

  lock->unlock();
  return err;
}
