/* todo-conduit.cc  Todo-Conduit for syncing KPilot and KOrganizer
**
** Copyright (C) 2002 Reinhold Kainhofer
** Copyright (C) 1998-2001 Dan Pilone
** Copyright (C) 1998-2000 Preston Brown
** Copyright (C) 1998 Herwin-Jan Steehouwer
** Copyright (C) 2001 Cornelius Schumacher
**
** This file is part of the todo conduit, a conduit for KPilot that
** synchronises the Pilot's todo application with the outside world,
** which currently means KOrganizer.
*/

/*
** 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 in a file called COPYING; if not, write to
** the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
** MA 02111-1307, USA.
*/

/*
** Bug reports and questions can be sent to kde-pim@kde.org
*/

static const char *TodoConduit_id = "$Id: todo-conduit.cc,v 1.24.4.7 2003/03/12 23:31:13 adridg Exp $";

#include <options.h>
#include <unistd.h>

#include <qdatetime.h>
#include <qtimer.h>
#include <qtextcodec.h>

#include <kconfig.h>

// libkcal includes
#include "libkcal/calendarlocal.h"
#include "libkcal/todo.h"

#include "pilotUser.h"

/*
** KDE 2.2 uses class KORecurrence in a different header file.
*/
#ifdef KDE2
#define DateList_t QDateList
#define DateListIterator_t QDateListIterator
#else
#define DateList_t KCal::DateList
#define DateListIterator_t KCal::DateList::ConstIterator
#endif

#include "pilotSerialDatabase.h"
#include "pilotLocalDatabase.h"
//#include <pilotTodoEntry.h>

//#include "vcal-conduitbase.h"
//#include "todo-factory.h"
#include "todo-conduit.moc"

// define conduit versions, one for the version when categories were synced for the first time, and the current version number
#define CONDUIT_VERSION_CATEGORYSYNC 10
#define CONDUIT_VERSION 10



TodoConduitPrivate::TodoConduitPrivate(KCal::CalendarLocal *b) :
	VCalConduitPrivateBase(b)
{
	fAllTodos.setAutoDelete(false);
}

void TodoConduitPrivate::addIncidence(KCal::Incidence*e)
{
	fAllTodos.append(static_cast<KCal::Todo*>(e));
	fCalendar->addTodo(static_cast<KCal::Todo*>(e));
}

int TodoConduitPrivate::updateIncidences()
{
	fAllTodos = fCalendar->todos();
	fAllTodos.setAutoDelete(false);
	return fAllTodos.count();
}


void TodoConduitPrivate::removeIncidence(KCal::Incidence *e)
{
	fAllTodos.remove(static_cast<KCal::Todo*>(e));
	fCalendar->deleteTodo(static_cast<KCal::Todo*>(e));
}



KCal::Incidence *TodoConduitPrivate::findIncidence(recordid_t id)
{
	KCal::Todo *todo = fAllTodos.first();
	while(todo)
	{
		if ((recordid_t)(todo->pilotId()) == id) return todo;
		todo = fAllTodos.next();
	}

	return 0L;
}



KCal::Incidence *TodoConduitPrivate::findIncidence(PilotAppCategory*tosearch)
{
	PilotTodoEntry*entry=dynamic_cast<PilotTodoEntry*>(tosearch);
	if (!entry) return 0L;
	
	QString title=entry->getDescription();
	QDateTime dt=readTm( entry->getDueDate() );
	
	KCal::Todo *event = fAllTodos.first();
	while (event!=0)
	{
		if ( (event->dtDue().date() == dt.date()) && (event->summary() == title) ) return event;
		event = fAllTodos.next();
	}
	return 0L;
}



KCal::Incidence *TodoConduitPrivate::getNextIncidence()
{
	if (reading) return fAllTodos.next();
	reading=true;
	return fAllTodos.first();
}



KCal::Incidence *TodoConduitPrivate::getNextModifiedIncidence()
{
FUNCTIONSETUP;
	KCal::Todo*e=0L;
	if (!reading)
	{
		reading=true;
		e=fAllTodos.first();
	}
	else
	{
		e=fAllTodos.next();
	}
	while (e && e->syncStatus()!=KCal::Incidence::SYNCMOD)
	{
		e=fAllTodos.next();
#ifdef DEBUG
if (e)
DEBUGCONDUIT<< e->summary()<<" had SyncStatus="<<e->syncStatus()<<endl;
#endif
	}
	return e;
}



/****************************************************************************
 *                          TodoConduit class                               *
 ****************************************************************************/

TodoConduit::TodoConduit(KPilotDeviceLink *d,
	const char *n,
	const QStringList &a) : VCalConduitBase(d,n,a)
{
	FUNCTIONSETUP;
#ifdef DEBUG
	DEBUGCONDUIT<<TodoConduit_id<<endl;
#endif
}



TodoConduit::~TodoConduit()
{
//	FUNCTIONSETUP;
}



void TodoConduit::_setAppInfo()
{
	FUNCTIONSETUP;
	// get the address application header information
	unsigned char *buffer =
		new unsigned char[PilotTodoEntry::APP_BUFFER_SIZE];
	int appLen = fDatabase->readAppBlock(buffer,PilotTodoEntry::APP_BUFFER_SIZE);

	unpack_ToDoAppInfo(&fTodoAppInfo, buffer, appLen);
	delete[]buffer;
	buffer = NULL;

#ifdef DEBUG
	DEBUGCONDUIT << fname << " lastUniqueId"
		<< fTodoAppInfo.category.lastUniqueID << endl;
#endif
	for (int i = 0; i < 16; i++)
	{
#ifdef DEBUG
		DEBUGCONDUIT << fname << " cat " << i << " =" <<
			fTodoAppInfo.category.name[i] << endl;
#endif
	}

}



const QString TodoConduit::getTitle(PilotAppCategory*de)
{
	PilotTodoEntry*d=dynamic_cast<PilotTodoEntry*>(de);
	if (d) return QString(d->getDescription());
	return QString::null;
}



void TodoConduit::readConfig()
{
	FUNCTIONSETUP;
	VCalConduitBase::readConfig();
	// determine if the categories have ever been synce. Needed to prevent loosing the categories on the desktop.
	// also use a full sync for the first time to make sure the palm categories are really transferred to the desktop
	categoriesSynced = fConfig->readNumEntry("ConduitVersion", 0)>=CONDUIT_VERSION_CATEGORYSYNC;
	if (!categoriesSynced) fFullSync=true;
#ifdef DEBUG
	DEBUGCONDUIT<<"categoriesSynced="<<categoriesSynced<<endl;
#endif
}



void TodoConduit::postSync()
{
	FUNCTIONSETUP;
	VCalConduitBase::postSync();
	fConfig->setGroup(configGroup());
	// after this successful sync the categories have been synced for sure
	fConfig->writeEntry("ConduitVersion", CONDUIT_VERSION);
}



PilotRecord*TodoConduit::recordFromIncidence(PilotAppCategory*de, const KCal::Incidence*e)
{
	// don't need to check for null pointers here, the recordFromIncidence(PTE*, KCal::Todo*) will do that.
	return recordFromIncidence(dynamic_cast<PilotTodoEntry*>(de), dynamic_cast<const KCal::Todo*>(e));
}



PilotRecord*TodoConduit::recordFromIncidence(PilotTodoEntry*de, const KCal::Todo*todo)
{
	FUNCTIONSETUP;
	if (!de || !todo) {
#ifdef DEBUG
		DEBUGCONDUIT<<fname<<": NULL todo given... Skipping it"<<endl;
#endif
		return NULL;
	}

	// set secrecy, start/end times, alarms, recurrence, exceptions, summary and description:
	if (todo->secrecy()!=KCal::Todo::SecrecyPublic) de->makeSecret();

	// update it from the iCalendar Todo.

	if (todo->hasDueDate()) {
		struct tm t = writeTm(todo->dtDue());
		de->setDueDate(t);
		de->setIndefinite(0);
	} else {
		de->setIndefinite(1);
	}
	
	// TODO: take recurrence (code in VCAlConduit) from ActionNames

	setCategory(de, todo);

	// TODO: sync the alarm from ActionNames. Need to extend PilotTodoEntry
	de->setPriority(todo->priority());

	de->setComplete(todo->isCompleted());

	// what we call summary pilot calls description.
	de->setDescription(todo->summary());

	// what we call description pilot puts as a separate note
	de->setNote(todo->description());

#ifdef DEBUG
DEBUGCONDUIT<<"-------- "<<todo->summary()<<endl;
#endif
	return de->pack();
}



void TodoConduit::preRecord(PilotRecord*r) 
{
	FUNCTIONSETUP;
	if (!categoriesSynced && r) 
	{
		const PilotAppCategory*de=newPilotEntry(r);
		KCal::Incidence *e = fP->findIncidence(r->getID());
		setCategory(dynamic_cast<KCal::Todo*>(e), dynamic_cast<const PilotTodoEntry*>(de));
	}
}
 
 

void TodoConduit::setCategory(PilotTodoEntry*de, const KCal::Todo*todo)
{
	if (!de || !todo) return;
	de->setCat(_getCat(de->getCat(), todo->categories()));
#ifdef DEBUG
	DEBUGCONDUIT<<"old Category="<<de->getCat()<<", new cat will be "<<_getCat(de->getCat(), todo->categories())<<endl;
	DEBUGCONDUIT<<"Available Categories: "<<todo->categories().join(CSL1(" - "))<<endl;
#endif
}



/** 
 * _getCat returns the id of the category from the given categories list. If none of the categories exist 
 * on the palm, the "Nicht abgelegt" (don't know the english name) is used.
 */
int TodoConduit::_getCat(int cat, const QStringList cats) const
{
	FUNCTIONSETUP;
	int j;
	if (cats.contains(PilotAppCategory::codec()->toUnicode(fTodoAppInfo.category.name[cat]))) 
		return cat;
	for ( QStringList::ConstIterator it = cats.begin(); it != cats.end(); ++it ) {
		for (j=1; j<=15; j++) 
		{
			if (!(*it).isEmpty() && 
				! (*it).compare( PilotAppCategory::codec()->toUnicode(
					fTodoAppInfo.category.name[j]) ) ) 
			{
				return j;
			}
		}
	}
	return 0;
}



KCal::Incidence *TodoConduit::incidenceFromRecord(KCal::Incidence *e, const PilotAppCategory *de)
{
	return dynamic_cast<KCal::Incidence*>(incidenceFromRecord(dynamic_cast<KCal::Todo*>(e), dynamic_cast<const PilotTodoEntry*>(de)));
}



KCal::Todo *TodoConduit::incidenceFromRecord(KCal::Todo *e, const PilotTodoEntry *de)
{
	FUNCTIONSETUP;

	KCal::Todo*vtodo=e;
	if (!vtodo)
	{
#ifdef DEBUG
		DEBUGCONDUIT<<fname<<": null todo entry given. skipping..."<<endl;
#endif
		return NULL;
	}

	e->setOrganizer(fCalendar->getEmail());
	e->setPilotId(de->getID());
	e->setSyncStatus(KCal::Incidence::SYNCNONE);
	e->setSecrecy(de->isSecret() ? KCal::Todo::SecrecyPrivate : KCal::Todo::SecrecyPublic);

	// we don't want to modify the vobject with pilot info, because it has
	// already been  modified on the desktop.  The VObject's modified state
	// overrides the PilotRec's modified state.
	// TODO: Also include this in the vcal conduit!!!
//	if (e->syncStatus() != KCal::Incidence::SYNCNONE) return e;

	// otherwise, the vObject hasn't been touched.  Updated it with the
	// info from the PilotRec.
	if (de->getIndefinite()) {
		e->setHasDueDate(false);
	} else {
		e->setDtDue(readTm(de->getDueDate()));
		e->setHasDueDate(true);
	}
	
	// Categories
	// TODO: Sync categories
	// first remove all categories and then add only the appropriate one
	setCategory(e, de);
	
	// PRIORITY //
	e->setPriority(de->getPriority());

	// COMPLETED? //
	e->setCompleted(de->getComplete());

	e->setSummary(de->getDescription());
	e->setDescription(de->getNote());

	e->setSyncStatus(KCal::Incidence::SYNCNONE);

	return e;
}



void TodoConduit::setCategory(KCal::Todo *e, const PilotTodoEntry *de)
{
	if (!e || !de) return;
	QStringList cats=e->categories();
	if (!categoriesSynced)
	{
		// TODO: This is not optimal because it has the consequence that only one of the
		// palm categories can be set on the desktop, all others will be deleted from the desktop entry!
		for (int j=1; j<=15; j++) 
		{
			cats.remove(PilotAppCategory::codec()->toUnicode(fTodoAppInfo.category.name[j]));
		}
	}
	int cat=de->getCat();
	if (0<cat && cat<=15) 
	{
		cats.append( PilotAppCategory::codec()->toUnicode(fTodoAppInfo.category.name[cat]) );
	}
	e->setCategories(cats);
}
