/* KPilot
**
** Copyright (C) 2004 by Reinhold Kainhofer
** Based on the addressbook conduit:
** Copyright (C) 2000,2001 by Dan Pilone
** Copyright (C) 2000 Gregory Stern
** Copyright (C) 2002-2003 by Reinhold Kainhofer
**
** This conduit is the base class for all record-based conduits.
** all the sync logic is included in this class, and all child classes
** just have to implement some specific copying and conflict resolution
** methods.
*/

/*
** 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., 51 Franklin Street, Fifth Floor, Boston,
** MA 02110-1301, USA.
*/

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



#include "options.h"

#include <tqtimer.h>
#include <tqfile.h>

#include "pilotAppCategory.h"
#include "pilotSerialDatabase.h"
#include "pilotLocalDatabase.h"
#include "recordConduit.h"


// Something to allow us to check what revision
// the modules are that make up a binary distribution.
//
//
extern "C"
{
long version_record_conduit = Pilot::PLUGIN_API;
}


/* virtual */ bool RecordConduitBase::exec()
{
	FUNCTIONSETUP;
	fState = Initialize;

	setFirstSync(false);

	bool retrieved = false;
	if (!openDatabases( fDBName, &retrieved))
	{
		emit logError(i18n("Unable to open the %1 database on the handheld.").arg( fDBName ) );
		return false;
	}
	if (retrieved) setFirstSync(true);

	if (isFirstSync()) fIDList=fDatabase->idList();
	else fIDList=fDatabase->modifiedIDList();
	fIDListIterator = fIDList.begin();

	fTimer = new TQTimer(this);
	connect(fTimer,TQT_SIGNAL(timeout()),this,TQT_SLOT(process()));
	fTimer->start(0,false); // Fire as often as possible to prompt processing
	return true;
}

/* virtual */ void RecordConduitBase::process()
{
	FUNCTIONSETUP;
	SyncProgress p = Error;

#ifdef DEBUG
	DEBUGKPILOT << fname << ": From state " << name(fState) << endl;
#endif

	switch(fState)
	{
	case Initialize :
		p = loadPC();
		break;
	case PalmToPC :
		p = palmRecToPC();
		break;
	case PCToPalm :
		p = pcRecToPalm();
		break;
	case Cleanup :
		p = cleanup();
		break;
	}

#ifdef DEBUG
	DEBUGKPILOT << fname << ": Step returned " << name(p) << endl;
#endif

	switch(p)
	{
	case Error :
		fTimer->stop();
		delayDone();
		return;
	case NotDone :
		// Return so we get called again.
		return;
	case Done :
		// Get on with it.
		break;
	}

#ifdef DEBUG
	DEBUGKPILOT << fname << ": Step is done, moving to next state." << endl;
#endif

	// Here the previous call was done.
	switch(fState)
	{
	case Initialize :
		switch (syncMode().mode())
		{
		case SyncMode::eRestore :
		case SyncMode::eCopyPCToHH : /* These two don't copy Palm records to the PC */
			fState = PCToPalm;
			break;
		default :
			fState = PalmToPC;
		}
		break;
	case PalmToPC :
		switch (syncMode().mode())
		{
		case SyncMode::eBackup :
		case SyncMode::eCopyHHToPC : /* These modes don't copy PC records back */
			fState = Cleanup;
			break;
		default :
			fState = PCToPalm;
		}
		break;
	case PCToPalm :
		fState = Cleanup;
		break;
	case Cleanup :
		fTimer->stop();
		delayDone();
		// No change in state, timer stopped and we're done.
		break;
	}

#ifdef DEBUG
	DEBUGKPILOT << fname << ": Next state is " << name(fState) << endl;
#endif

}


TQString RecordConduitBase::name(RecordConduitBase::SyncProgress s)
{
	switch(s)
	{
	case RecordConduitBase::NotDone:
		return CSL1("NotDone");
	case RecordConduitBase::Done:
		return CSL1("Done");
	case RecordConduitBase::Error:
		return CSL1("Error");
	}
}


TQString RecordConduitBase::name(RecordConduitBase::States s)
{
	switch(s)
	{
	case RecordConduitBase::Initialize:
		return CSL1("Initialize");
	case RecordConduitBase::PalmToPC:
		return CSL1("Handheld-to-PC");
	case RecordConduitBase::PCToPalm:
		return CSL1("PC-to-Handheld");
	case RecordConduitBase::Cleanup:
		return CSL1("Cleanup");
	}
}


#if 0
/** make that entry on the pc archived (i.e. deleted on the handheld,
 *  while present on the pc, but not synced to the handheld */
bool RecordConduit::PCData::makeArchived( RecordConduit::PCEntry *pcEntry )
{
	if ( pcEntry ) {
		pcEntry->makeArchived();
		setChanged( true );
		return true;
	} else return false;
}


/* Builds the map which links record ids to uid's of PCEntry. This is the slow implementation,
 * that should always work. subclasses should reimplement it to speed things up.
*/
bool RecordConduit::PCData::mapContactsToPilot( TQMap<recordid_t,TQString> &idContactMap )
{
	FUNCTIONSETUP;

	idContactMap.clear();

	Iterator it = begin();
	PCEntry *ent;
	while ( !atEnd( it ) ) {
		ent = *it;
		recordid_t id( ent->recid() );
		if ( id != 0 ) {
			idContactMap.insert( id, ent->uid() );
		}
		++it;
	}
#ifdef DEBUG
	DEBUGKPILOT << fname << ": Loaded " << idContactMap.size() <<
	    " Entries on the pc and mapped them to records on the handheld. " << endl;
#endif
	return true;
}



/*********************************************************************
                        C O N S T R U C T O R
 *********************************************************************/



bool RecordConduit::mArchiveDeleted = false;

RecordConduit::RecordConduit(TQString name, KPilotDeviceLink * o, const char *n, const TQStringList & a):
		ConduitAction(o, n, a),
		mPCData(0), mPalmIndex(0),
		mEntryMap(), mSyncedIds(), mAllIds()
{
	FUNCTIONSETUP;
	fConduitName = name;
}



RecordConduit::~RecordConduit()
{
	if ( mPCData ) KPILOT_DELETE(mPCData);
}






/*********************************************************************
                S Y N C   S T R U C T U R E
 *********************************************************************/



/* virtual */ bool RecordConduit::exec()
{
	FUNCTIONSETUP;

	if ( !_prepare() ) return false;

	fFirstSync = false;
	// Database names probably in latin1.
	if( !openDatabases( dbName(), &fFirstSync ) )
	{
		emit logError(i18n("Unable to open the %1 database on the handheld.").arg( dbName() ) );
		return false;
	}
	_getAppInfo();
	if( !mPCData->loadData() )
	{
		emit logError( i18n("Unable to open %1.").arg( mPCData->description() ) );
		return false;
	}
	// get the addresseMap which maps Pilot unique record(address) id's to
	// a Abbrowser Addressee; allows for easy lookup and comparisons
	if ( mPCData->isEmpty() )
		fFirstSync = true;
	else
		mPCData->mapContactsToPilot( mEntryMap );
	fFirstSync = fFirstSync || ( mPCData->isEmpty() );

	// perform syncing from palm to abbrowser
	// iterate through all records in palm pilot
	mPalmIndex = 0;

#ifdef DEBUG
	DEBUGKPILOT << fname << ": fullsync=" << isFullSync() << ", firstSync=" << isFirstSync() << endl;
	DEBUGKPILOT << fname << ": "
		<< "syncDirection=" << getSyncDirection() << ", "
//		<< "archive = " << AbbrowserSettings::archiveDeleted()
		<< endl;
	DEBUGKPILOT << fname << ": conflictRes="<< getConflictResolution() << endl;
//	DEBUGKPILOT << fname << ": PilotStreetHome=" << AbbrowserSettings::pilotStreet() << ", PilotFaxHOme" << AbbrowserSettings::pilotFax() << endl;
#endif

	if ( !isFirstSync() )
		mAllIds=fDatabase->idList();

	/* Note:
	   if eCopyPCToHH or eCopyHHToPC, first sync everything, then lookup
	   those entries on the receiving side that are not yet syncced and delete
	   them. Use slotDeleteUnsyncedPCRecords and slotDeleteUnsyncedHHRecords
	   for this, and no longer purge the whole addressbook before the sync to
	   prevent data loss in case of connection loss. */

	TQTimer::singleShot(0, this, TQT_SLOT(slotPalmRecToPC()));

	return true;
}



void RecordConduit::slotPalmRecToPC()
{
	FUNCTIONSETUP;
	PilotRecord *palmRec = 0L, *backupRec = 0L;

	if ( getSyncDirection() == SyncAction::eCopyPCToHH )
	{
		mPCIter = mPCData->begin();
		TQTimer::singleShot(0, this, TQT_SLOT(slotPCRecToPalm()));
		return;
	}

	if ( isFullSync() )
		palmRec = fDatabase->readRecordByIndex( mPalmIndex++ );
	else
		palmRec = dynamic_cast <PilotSerialDatabase * >(fDatabase)->readNextModifiedRec();

	if ( !palmRec )
	{
		mPCIter = mPCData->begin();
		TQTimer::singleShot( 0, this, TQT_SLOT( slotPCRecToPalm() ) );
		return;
	}

	// already synced, so skip:
	if ( mSyncedIds.contains( palmRec->id() ) )
	{
		KPILOT_DELETE( palmRec );
		TQTimer::singleShot( 0, this, TQT_SLOT( slotPalmRecToPC() ) );
		return;
	}

	backupRec = fLocalDatabase->readRecordById( palmRec->id() );
	PilotRecord *compareRec = backupRec ? backupRec : palmRec;
	PilotAppCategory *compareEntry = createPalmEntry( compareRec );
	PCEntry *pcEntry = findMatch( compareEntry );
	KPILOT_DELETE( compareEntry );

	PilotAppCategory *backupEntry=0L;
	if ( backupRec )
		backupEntry = createPalmEntry( backupRec );
	PilotAppCategory *palmEntry=0L;
	if ( palmRec )
		palmEntry = createPalmEntry( palmRec );

	syncEntry( pcEntry, backupEntry, palmEntry );

	mSyncedIds.append( palmRec->id() );

	KPILOT_DELETE( pcEntry );
	KPILOT_DELETE( palmEntry );
	KPILOT_DELETE( backupEntry );
	KPILOT_DELETE( palmRec );
	KPILOT_DELETE( backupRec );

	TQTimer::singleShot(0, this, TQT_SLOT(slotPalmRecToPC()));
}



void RecordConduit::slotPCRecToPalm()
{
	FUNCTIONSETUP;

	if ( ( getSyncDirection()==SyncAction::eCopyHHToPC ) ||
		mPCData->atEnd( mPCIter ) )
	{
		mPalmIndex = 0;
		TQTimer::singleShot( 0, this, TQT_SLOT( slotDeletedRecord() ) );
		return;
	}

	PilotRecord *backupRec=0L;
	PCEntry *pcEntry = *mPCIter;
	++mPCIter;

	// If marked as archived, don't sync!
	if ( isArchived( pcEntry ) )
	{
#ifdef DEBUG
		DEBUGKPILOT << fname << ": address with id " << pcEntry->uid() <<
			" marked archived, so don't sync." << endl;
#endif
		KPILOT_DELETE( pcEntry );
		TQTimer::singleShot( 0, this, TQT_SLOT( slotPCRecToPalm() ) );
		return;
	}

	recordid_t recID( pcEntry->recid() );
	if ( recID == 0 )
	{
		// it's a new item(no record ID and not inserted by the Palm -> PC sync), so add it
		syncEntry( pcEntry, 0L, 0L );
		KPILOT_DELETE( pcEntry );
		TQTimer::singleShot( 0, this, TQT_SLOT( slotPCRecToPalm() ) );
		return;
	}

	// look into the list of already synced record ids to see if the PCEntry hasn't already been synced
	if ( mSyncedIds.contains( recID ) )
	{
#ifdef DEBUG
		DEBUGKPILOT << ": address with id " << recID << " already synced." << endl;
#endif
		KPILOT_DELETE( pcEntry );
		TQTimer::singleShot( 0, this, TQT_SLOT( slotPCRecToPalm() ) );
		return;
	}


	backupRec = fLocalDatabase->readRecordById( recID );
	// only update if no backup record or the backup record is not equal to the PCEntry

	PilotAppCategory*backupEntry=0L;
	if ( backupRec )
		backupEntry = createPalmEntry( backupRec );
	if( !backupRec || isFirstSync() || !_equal( backupEntry, pcEntry ) )
	{
		PilotRecord *palmRec = fDatabase->readRecordById( recID );
		PilotAppCategory *palmEntry=0L;
		if (palmRec)
			palmEntry = createPalmEntry( palmRec );
		syncEntry( pcEntry, backupEntry, palmEntry );
		// update the id just in case it changed
		if ( palmRec )
			recID = palmRec->id();
		KPILOT_DELETE( palmRec );
		KPILOT_DELETE( palmEntry );
	}

	KPILOT_DELETE( pcEntry );
	KPILOT_DELETE( backupEntry );
	KPILOT_DELETE( backupRec );
	mSyncedIds.append( recID );

	// done with the sync process, go on with the next one:
	TQTimer::singleShot( 0, this, TQT_SLOT( slotPCRecToPalm() ) );
}



void RecordConduit::slotDeletedRecord()
{
	FUNCTIONSETUP;

	PilotRecord *backupRec = fLocalDatabase->readRecordByIndex( mPalmIndex++ );
	if( !backupRec || isFirstSync() )
	{
		KPILOT_DELETE(backupRec);
		TQTimer::singleShot( 0, this, TQT_SLOT( slotDeleteUnsyncedPCRecords() ) );
		return;
	}

	// already synced, so skip this record:
	if ( mSyncedIds.contains( backupRec->id() ) )
	{
		KPILOT_DELETE( backupRec );
		TQTimer::singleShot( 0, this, TQT_SLOT( slotDeletedRecord() ) );
		return;
	}

	TQString uid = mEntryMap[ backupRec->id() ];
	PCEntry *pcEntry = mPCData->findByUid( uid );
	PilotRecord *palmRec = fDatabase->readRecordById( backupRec->id() );
	PilotAppCategory *backupEntry = 0L;
	if (backupRec)
		backupEntry = createPalmEntry( backupRec );
	PilotAppCategory*palmEntry=0L;
	if (palmRec)
		palmEntry = createPalmEntry( palmRec );

	mSyncedIds.append( backupRec->id() );
	syncEntry( pcEntry, backupEntry, palmEntry );

	KPILOT_DELETE( pcEntry );
	KPILOT_DELETE( palmEntry );
	KPILOT_DELETE( backupEntry );
	KPILOT_DELETE( palmRec );
	KPILOT_DELETE( backupRec );
	TQTimer::singleShot( 0, this, TQT_SLOT( slotDeletedRecord() ) );
}



void RecordConduit::slotDeleteUnsyncedPCRecords()
{
	FUNCTIONSETUP;
	if ( getSyncDirection() == SyncAction::eCopyHHToPC )
	{
		TQStringList uids;
		RecordIDList::iterator it;
		TQString uid;
		for ( it = mSyncedIds.begin(); it != mSyncedIds.end(); ++it)
		{
			uid = mEntryMap[ *it ];
			if ( !uid.isEmpty() ) uids.append( uid );
		}
		// TODO: Does this speed up anything?
		// qHeapSort( uids );
		const TQStringList alluids( mPCData->uids() );
		TQStringList::ConstIterator uidit;
		for ( uidit = alluids.constBegin(); uidit != alluids.constEnd(); ++uidit )
		{
			if ( !uids.contains( *uidit ) )
			{
#ifdef DEBUG
				DEBUGKPILOT << "Deleting PCEntry with uid " << (*uidit) << " from PC (is not on HH, and syncing with HH->PC direction)" << endl;
#endif
				mPCData->removeEntry( *uidit );
			}
		}
	}
	TQTimer::singleShot(0, this, TQT_SLOT(slotDeleteUnsyncedHHRecords()));
}



void RecordConduit::slotDeleteUnsyncedHHRecords()
{
	FUNCTIONSETUP;
	if ( getSyncDirection() == SyncAction::eCopyPCToHH )
	{
		RecordIDList ids = fDatabase->idList();
		RecordIDList::iterator it;
		for ( it = ids.begin(); it != ids.end(); ++it )
		{
			if ( !mSyncedIds.contains(*it) )
			{
#ifdef DEBUG
				DEBUGKPILOT << "Deleting record with ID " << *it << " from handheld (is not on PC, and syncing with PC->HH direction)" << endl;
#endif
				fDatabase->deleteRecord(*it);
				fLocalDatabase->deleteRecord(*it);
			}
		}
	}
	TQTimer::singleShot( 0, this, TQT_SLOT( slotCleanup() ) );
}


void RecordConduit::slotCleanup()
{
	FUNCTIONSETUP;

	// Set the appInfoBlock, just in case the category labels changed
	_setAppInfo();
	doPostSync();
	if(fDatabase)
	{
		fDatabase->resetSyncFlags();
		fDatabase->cleanup();
	}
	if(fLocalDatabase)
	{
		fLocalDatabase->resetSyncFlags();
		fLocalDatabase->cleanup();
	}
	KPILOT_DELETE( fDatabase );
	KPILOT_DELETE( fLocalDatabase );
	// TODO: do something if saving fails!
	mPCData->saveData();
	mPCData->cleanup();
	emit syncDone(this);
}


/** Return the list of category names on the handheld
 */
const TQStringList RecordConduit::categories() const
{
	TQStringList cats;
	for ( unsigned int j = 0; j < Pilot::CATEGORY_COUNT; j++ ) {
		TQString catName( category( j ) );
		if ( !catName.isEmpty() ) cats << catName;
	}
	return cats;
}
int RecordConduit::findFlags() const
{
	return eqFlagsAlmostAll;
}


bool RecordConduit::isDeleted( const PilotAppCategory *palmEntry )
{
	if ( !palmEntry )
		return true;
	if ( palmEntry->isDeleted() && !palmEntry->isArchived() )
		return true;
	if ( palmEntry->isArchived() )
		return !archiveDeleted();
	return false;
}
bool RecordConduit::isArchived( const PilotAppCategory *palmEntry )
{
	if ( palmEntry && palmEntry->isArchived() )
		return archiveDeleted();
	else
		return false;
}




/*********************************************************************
                L O A D I N G   T H E   D A T A
 *********************************************************************/



bool RecordConduit::_prepare()
{
	FUNCTIONSETUP;

	readConfig();
	mSyncedIds.clear();
	mPCData = initializePCData();

	return mPCData && doPrepare();
}


void RecordConduit::_getAppInfo()
{
	FUNCTIONSETUP;
	// get the address application header information
	unsigned char *buffer = new unsigned char[Pilot::MAX_APPINFO_SIZE];
	int appLen=fDatabase->readAppBlock(buffer, Pilot::MAX_APPINFO_SIZE);

	doUnpackAppInfo( buffer, appLen );
	delete[] buffer;
	buffer = 0;
}

void RecordConduit::_setAppInfo()
{
	FUNCTIONSETUP;
	// get the address application header information
	int appLen = 0;
	unsigned char *buffer = doPackAppInfo( &appLen );
	if ( buffer )
	{	if (fDatabase)
			fDatabase->writeAppBlock( buffer, appLen );
		if (fLocalDatabase)
			fLocalDatabase->writeAppBlock( buffer, appLen );
		delete[] buffer;
	}
}


int RecordConduit::compareStr( const TQString & str1, const TQString & str2 )
{
//	FUNCTIONSETUP;
	if ( str1.isEmpty() && str2.isEmpty() )
		return 0;
	else
		return str1.compare( str2 );
}


/**
 * _getCat returns the id of the category from the given categories list.
 * If the address has no categories on the PC, TQString() is returned.
 * If the current category exists in the list of cats, it is returned
 * Otherwise the first cat in the list that exists on the HH is returned
 * If none of the categories exists on the palm, TQString() is returned
 */
TQString RecordConduit::getCatForHH( const TQStringList cats, const TQString curr ) const
{
	FUNCTIONSETUP;
	if ( cats.size() < 1 )
		return TQString();
	if ( cats.contains( curr ) )
		return curr;
	for ( TQStringList::ConstIterator it = cats.begin(); it != cats.end(); ++it)
	{
		for ( unsigned int j = 0; j < Pilot::CATEGORY_COUNT; j++ )
		{
			TQString catnm( category( j ) );
			if ( !(*it).isEmpty() && ( (*it)==catnm ) )
			{
				return catnm;
			}
		}
	}
	// If we have a free label, return the first possible cat
	TQString lastCat( category( Pilot::CATEGORY_COUNT-1 ) );
	return ( lastCat.isEmpty() ) ? ( cats.first() ) : ( TQString() );
}

void RecordConduit::setCategory(PCEntry * pcEntry, TQString cat)
{
	if ( !cat.isEmpty() && cat!=category( 0 ) )
		pcEntry->insertCategory(cat);
}






/*********************************************************************
              G E N E R A L   S Y N C   F U N C T I O N
         These functions modify the Handheld and the addressbook
 *********************************************************************/



bool RecordConduit::syncEntry( PCEntry *pcEntry, PilotAppCategory*backupEntry,
	PilotAppCategory*palmEntry)
{
	FUNCTIONSETUP;

	if ( getSyncDirection() == SyncAction::eCopyPCToHH )
	{
		if ( pcEntry->isEmpty() )
		{
			return pcDeleteEntry( pcEntry, backupEntry, palmEntry );
		}
		else
		{
			return pcCopyToPalm( pcEntry, backupEntry, palmEntry );
		}
	}

	if ( getSyncDirection() == SyncAction::eCopyHHToPC )
	{
		if (!palmEntry)
			return pcDeleteEntry(pcEntry, backupEntry, palmEntry);
		else
			return palmCopyToPC(pcEntry, backupEntry, palmEntry);
	}

	if ( !backupEntry || isFirstSync() )
	{
		/*
		Resolution matrix (0..does not exist, E..exists, D..deleted flag set, A..archived):
		  HH    PC  | Resolution
		  ------------------------------------------------------------
		   0     A  |  -
		   0     E  |  PC -> HH, reset ID if not set correctly
		   D     0  |  delete (error, should never occur!!!)
		   D     E  |  CR (ERROR)
		   E/A   0  |  HH -> PC
		   E/A   E/A|  merge/CR
		 */
		if  ( !palmEntry && isArchived( pcEntry ) )
		{
			return true;
		}
		else if ( !palmEntry && !pcEntry->isEmpty() )
		{
			// PC->HH
			bool res = pcCopyToPalm( pcEntry, 0L, 0L );
			return res;
		}
		else if ( !palmEntry && pcEntry->isEmpty() )
		{
			// everything's empty -> ERROR
			return false;
		}
		else if ( ( isDeleted( palmEntry ) || isArchived( palmEntry ) ) && pcEntry->isEmpty())
		{
			if ( isArchived( palmEntry ) )
				return palmCopyToPC( pcEntry, 0L, palmEntry );
			else
				// this happens if you add a record on the handheld and delete it again before you do the next sync
				return pcDeleteEntry( pcEntry, 0L, palmEntry );
		}
		else if ( ( isDeleted(palmEntry) || isArchived( palmEntry ) ) && !pcEntry->isEmpty() )
		{
			// CR (ERROR)
			return smartMergeEntry( pcEntry, 0L, palmEntry );
		}
		else if ( pcEntry->isEmpty() )
		{
			// HH->PC
			return palmCopyToPC( pcEntry, 0L, palmEntry );
		}
		else
		{
			// Conflict Resolution
			return smartMergeEntry( pcEntry, 0L, palmEntry );
		}
	} // !backupEntry
	else
	{
		/*
		Resolution matrix:
		  1) if HH.(empty| (deleted &! archived) ) -> { if (PC==B) -> delete, else -> CR }
		     if HH.archived -> {if (PC==B) -> copyToPC, else -> CR }
		     if PC.empty -> { if (HH==B) -> delete, else -> CR }
		     if PC.archived -> {if (HH==B) -> delete on HH, else CR }
		  2) if PC==HH -> { update B, update ID of PC if needed }
		  3) if PC==B -> { HH!=PC, thus HH modified, so copy HH->PC }
		     if HH==B -> { PC!=HH, thus PC modified, so copy PC->HH }
		  4) else: all three PCEntrys are different -> CR
		*/

		if ( !palmEntry || isDeleted(palmEntry) )
		{
			if ( _equal( backupEntry, pcEntry ) || pcEntry->isEmpty() )
			{
				return pcDeleteEntry( pcEntry, backupEntry, 0L );
			}
			else
			{
				return smartMergeEntry( pcEntry, backupEntry, 0L );
			}
		}
		else if ( pcEntry->isEmpty() )
		{
			if (*palmEntry == *backupEntry)
			{
				return pcDeleteEntry( pcEntry, backupEntry, palmEntry );
			}
			else
			{
				return smartMergeEntry( pcEntry, backupEntry, palmEntry );
			}
		}
		else if ( _equal( palmEntry, pcEntry ) )
		{
			// update Backup, update ID of PC if neededd
			return backupSaveEntry( palmEntry );
		}
		else if ( _equal( backupEntry, pcEntry ) )
		{
#ifdef DEBUG
			DEBUGKPILOT << "Flags: " << palmEntry->getAttrib() << ", isDeleted=" <<
				isDeleted( palmEntry ) << ", isArchived=" << isArchived( palmEntry )
				<< endl;
#endif
			if ( isDeleted( palmEntry ) )
			{
				return pcDeleteEntry( pcEntry, backupEntry, palmEntry );
			}
			else
			{
				return palmCopyToPC( pcEntry, backupEntry, palmEntry );
			}
		}
		else if ( *palmEntry == *backupEntry )
		{
			return pcCopyToPalm( pcEntry, backupEntry, palmEntry );
		}
		else
		{
			// CR, since all are different
			return smartMergeEntry( pcEntry, backupEntry, palmEntry );
		}
	} // backupEntry
	return false;
}

bool RecordConduit::pcCopyToPalm( PCEntry *pcEntry, PilotAppCategory *backupEntry,
		PilotAppCategory*palmEntry )
{
	FUNCTIONSETUP;

	if ( pcEntry->isEmpty() ) return false;
	PilotAppCategory *hhEntry = palmEntry;
	bool hhEntryCreated = false;
	if ( !hhEntry )
	{
		hhEntry = createPalmEntry( 0 );
		hhEntryCreated=true;
	}
	_copy( hhEntry, pcEntry );
#ifdef DEBUG
	DEBUGKPILOT << "palmEntry->id=" << hhEntry->id() << ", pcEntry.ID=" <<
		pcEntry->uid() << endl;
#endif

	if( palmSaveEntry( hhEntry, pcEntry ) )
	{
#ifdef DEBUG
		DEBUGKPILOT << "Entry palmEntry->id=" <<
		hhEntry->id() << "saved to palm, now updating pcEntry->uid()=" << pcEntry->uid() << endl;
#endif
		pcSaveEntry( pcEntry, backupEntry, hhEntry );
	}
	if ( hhEntryCreated ) KPILOT_DELETE( hhEntry );
	return true;
}




bool RecordConduit::palmCopyToPC( PCEntry *pcEntry, PilotAppCategory *backupEntry,
		PilotAppCategory *palmEntry )
{
	FUNCTIONSETUP;
	if ( !palmEntry )
	{
		return false;
	}
	_copy( pcEntry, palmEntry );
	pcSaveEntry( pcEntry, backupEntry, palmEntry );
	backupSaveEntry( palmEntry );
	return true;
}



/*********************************************************************
                 l o w - l e v e l   f u n c t i o n s   f o r
                   adding / removing palm/pc records
 *********************************************************************/



bool RecordConduit::palmSaveEntry( PilotAppCategory *palmEntry, PCEntry *pcEntry )
{
	FUNCTIONSETUP;

#ifdef DEBUG
	DEBUGKPILOT << "Saving to pilot " << palmEntry->id() << endl;
#endif

	PilotRecord *pilotRec = palmEntry->pack();
	recordid_t pilotId = fDatabase->writeRecord(pilotRec);
#ifdef DEBUG
	DEBUGKPILOT << "PilotRec nach writeRecord (" << pilotId <<
		": ID=" << pilotRec->id() << endl;
#endif
	fLocalDatabase->writeRecord( pilotRec );
	KPILOT_DELETE( pilotRec );

	// pilotId == 0 if using local db, so don't overwrite the valid id
	if ( pilotId != 0 )
	{
		palmEntry->setID( pilotId );
		if ( !mSyncedIds.contains( pilotId ) )
		{
			mSyncedIds.append( pilotId );
		}
	}

	recordid_t hhId( pcEntry->recid() );
	if ( hhId != pilotId )
	{
		pcEntry->setRecid( pilotId );
		return true;
	}

	return false;
}



bool RecordConduit::backupSaveEntry( PilotAppCategory *backup )
{
	FUNCTIONSETUP;
	if ( !backup ) return false;


#ifdef DEBUG
//	showPilotAppCategory( backup );
#endif
	PilotRecord *pilotRec = backup->pack();
	fLocalDatabase->writeRecord( pilotRec );
	KPILOT_DELETE( pilotRec );
	return true;
}



bool RecordConduit::pcSaveEntry( PCEntry *pcEntry, PilotAppCategory *,
	PilotAppCategory * )
{
	FUNCTIONSETUP;

#ifdef DEBUG
	DEBUGKPILOT << "Before _savepcEntry, pcEntry->uid()=" <<
		pcEntry->uid() << endl;
#endif
	if ( pcEntry->recid() != 0 )
	{
		mEntryMap.insert( pcEntry->recid(), pcEntry->uid() );
	}

	mPCData->updateEntry( pcEntry );
	return true;
}



bool RecordConduit::pcDeleteEntry( PCEntry *pcEntry, PilotAppCategory *backupEntry,
	PilotAppCategory *palmEntry )
{
	FUNCTIONSETUP;

	if ( palmEntry )
	{
		if ( !mSyncedIds.contains( palmEntry->id() ) )
		{
			mSyncedIds.append(palmEntry->id());
		}
		palmEntry->makeDeleted();
		PilotRecord *pilotRec = palmEntry->pack();
		pilotRec->setDeleted();
		mPalmIndex--;
		fDatabase->writeRecord( pilotRec );
		fLocalDatabase->writeRecord( pilotRec );
		mSyncedIds.append( pilotRec->id() );
		KPILOT_DELETE( pilotRec );
	}
	else if ( backupEntry )
	{
		if ( !mSyncedIds.contains( backupEntry->id() ) )
		{
			mSyncedIds.append( backupEntry->id() );
		}
		backupEntry->makeDeleted();
		PilotRecord *pilotRec = backupEntry->pack();
		pilotRec->setDeleted();
		mPalmIndex--;
		fLocalDatabase->writeRecord( pilotRec );
		mSyncedIds.append( pilotRec->id() );
		KPILOT_DELETE( pilotRec );
	}
	if ( !pcEntry->isEmpty() )
	{
#ifdef DEBUG
		DEBUGKPILOT << fname << " removing " << pcEntry->uid() << endl;
#endif
		mPCData->removeEntry( pcEntry );
	}
	return true;
}



/*********************************************************************
                   C O P Y   R E C O R D S
 *********************************************************************/





/*********************************************************************
 C O N F L I C T   R E S O L U T I O N   a n d   M E R G I N G
 *********************************************************************/




// TODO: right now entries are equal if both first/last name and organization are
//  equal. This rules out two entries for the same person(e.g. real home and weekend home)
//  or two persons with the same name where you don't know the organization.!!!
RecordConduit::PCEntry *RecordConduit::findMatch( PilotAppCategory *palmEntry ) const
{
	FUNCTIONSETUP;
	if ( !palmEntry )
		return 0;

	// TODO: also search with the pilotID
	// first, use the pilotID to UID map to find the appropriate record
	if( !isFirstSync() && ( palmEntry->id() > 0) )
	{
		TQString id( mEntryMap[palmEntry->id()] );
#ifdef DEBUG
		DEBUGKPILOT << fname << ": PilotRecord has id " << palmEntry->id() << ", mapped to " << id << endl;
#endif
		if( !id.isEmpty() )
		{
			PCEntry *res = mPCData->findByUid( id );
			if ( !res && !res->isEmpty() ) return res;
			KPILOT_DELETE( res );
#ifdef DEBUG
			DEBUGKPILOT << fname << ": PilotRecord has id " << palmEntry->id() <<
				", but could not be found on the PC side" << endl;
#endif
		}
	}

	for ( PCData::Iterator iter = mPCData->begin(); !mPCData->atEnd( iter ); ++iter )
	{
		PCEntry *abEntry = *iter;
		recordid_t rid( abEntry->recid() );
		if ( rid>0 )
		{
			if ( rid == palmEntry->id() )
				return abEntry;// yes, we found it
			// skip this PCEntry, as it has a different corresponding address on the handheld
			//if ( mAllIds.contains( rid ) ) continue;
		}

		if ( _equal( palmEntry, abEntry, eqFlagsAlmostAll ) )
		{
			return abEntry;
		}
		KPILOT_DELETE( abEntry );
	}
#ifdef DEBUG
	DEBUGKPILOT << fname << ": Could not find any entry matching Palm record with id " << TQString::number( palmEntry->id() ) << endl;
#endif
	return 0;
}

#endif




#include "recordConduit.moc"

