/* KPilot
**
** Copyright (C) 2000,2001 by Dan Pilone
** Copyright (C) 2002-2003 by Reinhold Kainhofer
** Copyright (C) 2007 by Adriaan de Groot <groot@kde.org>
**
** The abbrowser conduit copies addresses from the Pilot's address book to
** the KDE addressbook maintained via the tdeabc library.
*/

/*
** 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 <tqtextcodec.h>
#include <tqfile.h>
#include <tqregexp.h>

#include <tdeabc/stdaddressbook.h>
#include <tdeabc/resourcefile.h>
#include <tdeio/netaccess.h>
#include <ksavefile.h>

#include <pilotSerialDatabase.h>
#include <pilotLocalDatabase.h>

#include "resolutionDialog.h"
#include "resolutionTable.h"
#include "abbrowserSettings.h"
#include "tdeabcRecord.h"

#include "abbrowser-conduit.moc"

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


/* This is partly stolen from the boost libraries, partly from
*  "Modern C++ design" for doing compile time checks; we need
*  to make sure that the enum values in TDEABCSync:: and in the
*  AbbrowserSettings class are the same so that both interpret
*  configuration values the same way.
*/
template<bool> struct EnumerationMismatch;
template<> struct EnumerationMismatch<true>{};

#define CHECK_ENUM(a) (void)sizeof(EnumerationMismatch<((int)TDEABCSync::a)==((int)AbbrowserSettings::a)>)

static inline void compile_time_check()
{
	// Mappings for other phone
	CHECK_ENUM(eOtherPhone);
	CHECK_ENUM(eOtherPhone);
	CHECK_ENUM(eAssistant);
	CHECK_ENUM(eBusinessFax);
	CHECK_ENUM(eCarPhone);
	CHECK_ENUM(eEmail2);
	CHECK_ENUM(eHomeFax);
	CHECK_ENUM(eTelex);
	CHECK_ENUM(eTTYTTDPhone);

	// Mappings for custom fields
	CHECK_ENUM(eCustomField);
	CHECK_ENUM(eCustomBirthdate);
	CHECK_ENUM(eCustomURL);
	CHECK_ENUM(eCustomIM);
}

inline int faxTypeOnPC()
{
	return TDEABC::PhoneNumber::Fax |
		( (AbbrowserSettings::pilotFax()==0) ?
			TDEABC::PhoneNumber::Home :
			TDEABC::PhoneNumber::Work );
}


using namespace TDEABC;

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


AbbrowserConduit::AbbrowserConduit(KPilotLink * o, const char *n, const TQStringList & a):
		ConduitAction(o, n, a),
		aBook(0L),
		fAddressAppInfo(0L),
		addresseeMap(),
		syncedIds(),
		abiter(),
		fTicket(0L),
		fCreatedBook(false),
		fBookResource(0L)
{
	FUNCTIONSETUP;
	fConduitName=i18n("Addressbook");
}



AbbrowserConduit::~AbbrowserConduit()
{
	FUNCTIONSETUP;

	if (fTicket)
	{
		DEBUGKPILOT << fname << ": Releasing ticket" << endl;
		aBook->releaseSaveTicket(fTicket);
		fTicket=0L;
	}

	_cleanupAddressBookPointer();
	// unused function warnings.
	compile_time_check();
}



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



/* Builds the map which links record ids to uid's of Addressee
*/
void AbbrowserConduit::_mapContactsToPilot(TQMap < recordid_t, TQString > &idContactMap)
{
	FUNCTIONSETUP;

	idContactMap.clear();

	for(AddressBook::Iterator contactIter = aBook->begin();
		contactIter != aBook->end(); ++contactIter)
	{
		Addressee aContact = *contactIter;
		TQString recid = aContact.custom(TDEABCSync::appString, TDEABCSync::idString);
		if(!recid.isEmpty())
		{
			recordid_t id = recid.toULong();
			// safety check:  make sure that we don't already have a map for this pilot id.
			// if we do (this can come from a copy/paste in kaddressbook, etc.), then we need
			// to reset our Addressee so that we can assign him a new pilot Id later and sync
			// him properly.  if we don't do this, we'll lose one of these on the pilot.
			if (!idContactMap.contains(id))
			{
				idContactMap.insert(id, aContact.uid());
			}
			else
			{
		DEBUGKPILOT << fname << ": found duplicate pilot key: ["
						<< id << "], removing pilot id from addressee: ["
						<< aContact.realName() << "]" << endl;
				aContact.removeCustom(TDEABCSync::appString, TDEABCSync::idString);
				aBook->insertAddressee(aContact);
				abChanged = true;
			}
		}
	}
	DEBUGKPILOT << fname << ": Loaded " << idContactMap.size() <<
	    " addresses from the addressbook. " << endl;
}



bool AbbrowserConduit::_prepare()
{
	FUNCTIONSETUP;

	readConfig();
	syncedIds.clear();
	pilotindex = 0;

	return true;
}



void AbbrowserConduit::readConfig()
{
	FUNCTIONSETUP;
	AbbrowserSettings::self()->readConfig();

	// Conflict page
	SyncAction::ConflictResolution res = (SyncAction::ConflictResolution)AbbrowserSettings::conflictResolution();
	setConflictResolution(res);

	DEBUGKPILOT << fname
		<< ": Reading addressbook "
		<< ( AbbrowserSettings::addressbookType() == AbbrowserSettings::eAbookFile ?
			AbbrowserSettings::fileName() : CSL1("Standard") )
		<< endl;
	DEBUGKPILOT << fname
		<< ": "
		<< " fConflictResolution=" << getConflictResolution()
		<< " fArchive=" << AbbrowserSettings::archiveDeleted()
		<< " fFirstTime=" << isFirstSync()
		<< endl;
	DEBUGKPILOT << fname
		<< ": "
		<< " fPilotStreetHome=" << AbbrowserSettings::pilotStreet()
		<< " fPilotFaxHome=" << AbbrowserSettings::pilotFax()
		<< " eCustom[0]=" << AbbrowserSettings::custom0()
		<< " eCustom[1]=" << AbbrowserSettings::custom1()
		<< " eCustom[2]=" << AbbrowserSettings::custom2()
		<< " eCustom[3]=" << AbbrowserSettings::custom3()
		<< endl;
}



bool isDeleted(const PilotAddress *addr)
{
	if (!addr)
	{
		return true;
	}
	if (addr->isDeleted() && !addr->isArchived())
	{
		return true;
	}
	if (addr->isArchived())
	{
		return !AbbrowserSettings::archiveDeleted();
	}
	return false;
}

bool isArchived(const PilotAddress *addr)
{
	if (addr && addr->isArchived())
	{
		return AbbrowserSettings::archiveDeleted();
	}
	else
	{
		return false;
	}
}



bool AbbrowserConduit::_loadAddressBook()
{
	FUNCTIONSETUP;

	startTickle();
	switch ( AbbrowserSettings::addressbookType() )
	{
		case AbbrowserSettings::eAbookResource:
			DEBUGKPILOT<<"Loading standard addressbook"<<endl;
			aBook = StdAddressBook::self( true );
			fCreatedBook=false;
			break;
		case AbbrowserSettings::eAbookFile:
		{ // initialize the abook with the given file
			DEBUGKPILOT<<"Loading custom addressbook"<<endl;
			KURL kurl(AbbrowserSettings::fileName());
			if(!TDEIO::NetAccess::download(AbbrowserSettings::fileName(), fABookFile, 0L) &&
				!kurl.isLocalFile())
			{
				emit logError(i18n("You chose to sync with the file \"%1\", which "
							"cannot be opened. Please make sure to supply a "
							"valid file name in the conduit's configuration dialog. "
							"Aborting the conduit.").arg(AbbrowserSettings::fileName()));
				TDEIO::NetAccess::removeTempFile(fABookFile);
				stopTickle();
				return false;
			}

			aBook = new AddressBook();
			if (!aBook)
			{
				stopTickle();
				return false;
			}
			fBookResource = new ResourceFile(fABookFile, CSL1("vcard") );

			bool r = aBook->addResource( fBookResource );
			if ( !r )
			{
				DEBUGKPILOT << "Unable to open resource for file " << fABookFile << endl;
				KPILOT_DELETE( aBook );
				stopTickle();
				return false;
			}
			fCreatedBook=true;
			break;
		}
		default: break;
	}
	// find out if this can fail for reasons other than a non-existent
	// vcf file. If so, how can I determine if the missing file was the problem
	// or something more serious:
	if ( !aBook || !aBook->load() )
	{
		// Something went wrong, so tell the user and return false to exit the conduit
		emit logError(i18n("Unable to initialize and load the addressbook for the sync.") );
		addSyncLogEntry(i18n("Unable to initialize and load the addressbook for the sync.") );
		WARNINGKPILOT << "Unable to initialize the addressbook for the sync." << endl;
		_cleanupAddressBookPointer();
		stopTickle();
		return false;
	}
	abChanged = false;

	fTicket=aBook->requestSaveTicket();
	if (!fTicket)
	{
		WARNINGKPILOT << "Unable to lock addressbook for writing " << endl;
		emit logError(i18n("Unable to lock addressbook for writing.  Can't sync!"));
		addSyncLogEntry(i18n("Unable to lock addressbook for writing.  Can't sync!"));
		_cleanupAddressBookPointer();
		stopTickle();
		return false;
	}

	fCtrPC->setStartCount(aBook->allAddressees().count());

	// get the addresseMap which maps Pilot unique record(address) id's to
	// a Abbrowser Addressee; allows for easy lookup and comparisons
	if(aBook->begin() == aBook->end())
	{
		setFirstSync( true );
	}
	else
	{
		_mapContactsToPilot(addresseeMap);
	}
	stopTickle();
	return(aBook != 0L);
}

bool AbbrowserConduit::_saveAddressBook()
{
	FUNCTIONSETUP;

	bool saveSuccessful = false;

	fCtrPC->setEndCount(aBook->allAddressees().count());

	Q_ASSERT(fTicket);

	if (abChanged)
	{
		saveSuccessful = aBook->save(fTicket);
	}
	else
	{
		DEBUGKPILOT << fname
			<< "Addressbook not changed, no need to save it" << endl;
	}
	// XXX: KDE4: release ticket in all cases (save no longer releases it)
	if ( !saveSuccessful ) // didn't save, delete ticket manually
	{
		aBook->releaseSaveTicket(fTicket);
	}
	fTicket=0L;

	if ( AbbrowserSettings::addressbookType()!= AbbrowserSettings::eAbookResource )
	{
		KURL kurl(AbbrowserSettings::fileName());
		if(!kurl.isLocalFile())
		{
			DEBUGKPILOT << fname << "Deleting local addressbook tempfile" << endl;
			if(!TDEIO::NetAccess::upload(fABookFile, AbbrowserSettings::fileName(), 0L)) {
				emit logError(i18n("An error occurred while uploading \"%1\". You can try to upload "
					"the temporary local file \"%2\" manually")
					.arg(AbbrowserSettings::fileName()).arg(fABookFile));
			}
			else {
				TDEIO::NetAccess::removeTempFile(fABookFile);
			}
			TQFile backup(fABookFile + CSL1("~"));
			backup.remove();
		}

	}

	// now try to remove the resource from the addressbook...
	if (fBookResource) 
	{
		bool r = aBook->removeResource( fBookResource );
		if ( !r )
		{
			DEBUGKPILOT << fname <<": Unable to close resource." << endl;
		}
	}

	return saveSuccessful;
}



void AbbrowserConduit::_getAppInfo()
{
	FUNCTIONSETUP;

	delete fAddressAppInfo;
	fAddressAppInfo = new PilotAddressInfo(fDatabase);
	fAddressAppInfo->dump();
}

void AbbrowserConduit::_setAppInfo()
{
	FUNCTIONSETUP;
	if (fDatabase) fAddressAppInfo->writeTo(fDatabase);
	if (fLocalDatabase) fAddressAppInfo->writeTo(fLocalDatabase);
}


void AbbrowserConduit::_cleanupAddressBookPointer()
{
        if (fCreatedBook)
        {
                KPILOT_DELETE(aBook);
                fCreatedBook=false;
        }
        else
        {
                aBook=0L;
	}												         
}




/*********************************************************************
                     D E B U G   O U T P U T
 *********************************************************************/





void AbbrowserConduit::showPilotAddress(const PilotAddress *pilotAddress)
{
	FUNCTIONSETUPL(3);
	if (debug_level < 3)
	{
		return;
	}
	if (!pilotAddress)
	{
		DEBUGKPILOT<< fname << "| EMPTY"<<endl;
		return;
	}
	DEBUGKPILOT << fname << "\n"
			<< pilotAddress->getTextRepresentation(
				fAddressAppInfo,TQt::PlainText) << endl;
}


void AbbrowserConduit::showAddresses(
	const Addressee &pcAddr,
	const PilotAddress *backupAddr,
	const PilotAddress *palmAddr)
{
	FUNCTIONSETUPL(3);
	if (debug_level >= 3)
	{
		DEBUGKPILOT << fname << "abEntry:" << endl;
		TDEABCSync::showAddressee(pcAddr);
		DEBUGKPILOT << fname << "pilotAddress:" << endl;
		showPilotAddress(palmAddr);
		DEBUGKPILOT << fname << "backupAddress:" << endl;
		showPilotAddress(backupAddr);
		DEBUGKPILOT << fname << "------------------------------------------------" << endl;
	}
}



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



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

	_prepare();

	bool retrieved = false;
	if(!openDatabases(CSL1("AddressDB"), &retrieved))
	{
		emit logError(i18n("Unable to open the addressbook databases on the handheld."));
		return false;
	}
	setFirstSync( retrieved );

	_getAppInfo();

	// Local block
	{
		TQString dbpath = fLocalDatabase->dbPathName();
		DEBUGKPILOT << fname << ": Local database path " << dbpath << endl;
	}

	if ( syncMode().isTest() )
	{
		TQTimer::singleShot(0, this, TQ_SLOT(slotTestRecord()));
		return true;
	}

	if(!_loadAddressBook())
	{
		emit logError(i18n("Unable to open the addressbook."));
		return false;
	}
	setFirstSync( isFirstSync() || (aBook->begin() == aBook->end()) );

	DEBUGKPILOT << fname << ": First sync now " << isFirstSync()
		<< " and addressbook is "
		<< ((aBook->begin() == aBook->end()) ? "" : "non-")
		<< "empty." << endl;

	// perform syncing from palm to abbrowser
	// iterate through all records in palm pilot

	DEBUGKPILOT << fname << ": fullsync=" << isFullSync() << ", firstSync=" <<    isFirstSync() << endl;
	DEBUGKPILOT << fname << ": "
		<< "syncDirection=" << syncMode().name() << ", "
		<< "archive = " << AbbrowserSettings::archiveDeleted() << endl;
	DEBUGKPILOT << fname << ": conflictRes="<< getConflictResolution() << endl;
	DEBUGKPILOT << fname << ": PilotStreetHome=" << AbbrowserSettings::pilotStreet() << ", PilotFaxHOme" << AbbrowserSettings::pilotFax() << endl;

	if (!isFirstSync())
	{
		allIds=fDatabase->idList();
	}

	TQValueVector<int> v(4);
	v[0] = AbbrowserSettings::custom0();
	v[1] = AbbrowserSettings::custom1();
	v[2] = AbbrowserSettings::custom2();
	v[3] = AbbrowserSettings::custom3();

	fSyncSettings.setCustomMapping(v);
	fSyncSettings.setFieldForOtherPhone(AbbrowserSettings::pilotOther());
	fSyncSettings.setDateFormat(AbbrowserSettings::customDateFormat());
	fSyncSettings.setPreferHome(AbbrowserSettings::pilotStreet()==0);
	fSyncSettings.setFaxTypeOnPC(faxTypeOnPC());

	/* 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, TQ_SLOT(slotPalmRecToPC()));

	return true;
}



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

	if ( syncMode() == SyncMode::eCopyPCToHH )
	{
		DEBUGKPILOT << fname << ": Done; change to PCtoHH phase." << endl;
		abiter = aBook->begin();
		TQTimer::singleShot(0, this, TQ_SLOT(slotPCRecToPalm()));
		return;
	}

	if(isFullSync())
	{
		palmRec = fDatabase->readRecordByIndex(pilotindex++);
	}
	else
	{
		palmRec = fDatabase->readNextModifiedRec();
	}

	// no record means we're done going in this direction, so switch to
	// PC->Palm
	if(!palmRec)
	{
		abiter = aBook->begin();
		TQTimer::singleShot(0, this, TQ_SLOT(slotPCRecToPalm()));
		return;
	}

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

	backupRec = fLocalDatabase->readRecordById(palmRec->id());
	PilotRecord*compareRec=(backupRec)?(backupRec):(palmRec);
	Addressee e = _findMatch(PilotAddress(compareRec));

	PilotAddress*backupAddr=0L;
	if (backupRec)
	{
		backupAddr=new PilotAddress(backupRec);
	}

	PilotAddress*palmAddr=0L;
	if (palmRec)
	{
		palmAddr=new PilotAddress(palmRec);
	}

	syncAddressee(e, backupAddr, palmAddr);

	syncedIds.append(palmRec->id());
	KPILOT_DELETE(palmAddr);
	KPILOT_DELETE(backupAddr);
	KPILOT_DELETE(palmRec);
	KPILOT_DELETE(backupRec);

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



void AbbrowserConduit::slotPCRecToPalm()
{
	FUNCTIONSETUP;

	if ( (syncMode()==SyncMode::eCopyHHToPC) ||
		abiter == aBook->end() || (*abiter).isEmpty() )
	{
		DEBUGKPILOT << fname << ": Done; change to delete records." << endl;
		pilotindex = 0;
		TQTimer::singleShot(0, this, TQ_SLOT(slotDeletedRecord()));
		return;
	}

	PilotRecord *palmRec=0L, *backupRec=0L;
	Addressee ad = *abiter;

	abiter++;

	// If marked as archived, don't sync!
	if (TDEABCSync::isArchived(ad))
	{
		DEBUGKPILOT << fname << ": address with id " << ad.uid() <<
			" marked archived, so don't sync." << endl;
		TQTimer::singleShot(0, this, TQ_SLOT(slotPCRecToPalm()));
		return;
	}


	TQString recID(ad.custom(TDEABCSync::appString, TDEABCSync::idString));
	bool ok;
	recordid_t rid = recID.toLong(&ok);
	if (recID.isEmpty() || !ok || !rid)
	{
		DEBUGKPILOT << fname << ": This is a new record." << endl;
		// it's a new item(no record ID and not inserted by the Palm -> PC sync), so add it
		syncAddressee(ad, 0L, 0L);
		TQTimer::singleShot(0, this, TQ_SLOT(slotPCRecToPalm()));
		return;
	}

	// look into the list of already synced record ids to see if the addressee hasn't already been synced
	if (syncedIds.contains(rid))
	{
		DEBUGKPILOT << ": address with id " << rid << " already synced." << endl;
		TQTimer::singleShot(0, this, TQ_SLOT(slotPCRecToPalm()));
		return;
	}


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

	PilotAddress*backupAddr=0L;
	if (backupRec)
	{
		backupAddr=new PilotAddress(backupRec);
	}
	if(!backupRec || isFirstSync() || !_equal(backupAddr, ad)  )
	{
		DEBUGKPILOT << fname << ": Updating entry." << endl;
		palmRec = fDatabase->readRecordById(rid);
		PilotAddress *palmAddr = 0L;
		if (palmRec)
		{
			palmAddr = new PilotAddress(palmRec);
		}
		else
		{
		DEBUGKPILOT << fname << ": No HH record with id " << rid << endl;
		}
		syncAddressee(ad, backupAddr, palmAddr);
		// update the id just in case it changed
		if (palmRec) rid=palmRec->id();
		KPILOT_DELETE(palmRec);
		KPILOT_DELETE(palmAddr);
	}
	else
	{
		DEBUGKPILOT << fname << ": Entry not updated." << endl;
	}
	KPILOT_DELETE(backupAddr);
	KPILOT_DELETE(backupRec);
	
	DEBUGKPILOT << fname << ": adding id:["<< rid << "] to syncedIds." << endl;

	syncedIds.append(rid);
	// done with the sync process, go on with the next one:
	TQTimer::singleShot(0, this, TQ_SLOT(slotPCRecToPalm()));
}



void AbbrowserConduit::slotDeletedRecord()
{
	FUNCTIONSETUP;

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

	recordid_t id = backupRec->id();

	TQString uid = addresseeMap[id];
	Addressee e = aBook->findByUid(uid);

		DEBUGKPILOT << fname << ": now looking at palm id: ["
					<< id << "], tdeabc uid: [" << uid << "]." << endl;

	PilotAddress*backupAddr=0L;
	if (backupRec)
	{
		backupAddr=new PilotAddress(backupRec);
	}
	PilotRecord*palmRec=fDatabase->readRecordById(id);

	if ( e.isEmpty() )
	{
		DEBUGKPILOT << fname << ": no Addressee found for this id." << endl;
		DEBUGKPILOT << fname << "\n"
			<< backupAddr->getTextRepresentation( 
				fAddressAppInfo,TQt::PlainText) << endl;

		if (palmRec) {
		DEBUGKPILOT << fname << ": deleting from database on palm." << endl;
			fDatabase->deleteRecord(id);
			fCtrHH->deleted();
		}
		DEBUGKPILOT << fname << ": deleting from backup database." << endl;
		fLocalDatabase->deleteRecord(id);

		// because we just deleted a record, we need to go back one
		pilotindex--;
	}

	KPILOT_DELETE(palmRec);
	KPILOT_DELETE(backupAddr);
	KPILOT_DELETE(backupRec);
	TQTimer::singleShot(0, this, TQ_SLOT(slotDeletedRecord()));
}



void AbbrowserConduit::slotDeleteUnsyncedPCRecords()
{
	FUNCTIONSETUP;
	if ( syncMode()==SyncMode::eCopyHHToPC )
	{
		TQStringList uids;
		RecordIDList::iterator it;
		TQString uid;
		for ( it = syncedIds.begin(); it != syncedIds.end(); ++it)
		{
			uid=addresseeMap[*it];
			if (!uid.isEmpty()) uids.append(uid);
		}
		// TODO: Does this speed up anything?
		// qHeapSort( uids );
		AddressBook::Iterator abit;
		for (abit = aBook->begin(); abit != aBook->end(); ++abit)
		{
			if (!uids.contains((*abit).uid()))
			{
				DEBUGKPILOT<<"Deleting addressee "<<(*abit).realName()<<" from PC (is not on HH, and syncing with HH->PC direction)"<<endl;
				abChanged = true;
				// TODO: Can I really remove the current iterator???
				aBook->removeAddressee(*abit);
				fCtrPC->deleted();
			}
		}
	}
	TQTimer::singleShot(0, this, TQ_SLOT(slotDeleteUnsyncedHHRecords()));
}



void AbbrowserConduit::slotDeleteUnsyncedHHRecords()
{
	FUNCTIONSETUP;
	if ( syncMode()==SyncMode::eCopyPCToHH )
	{
		RecordIDList ids=fDatabase->idList();
		RecordIDList::iterator it;
		for ( it = ids.begin(); it != ids.end(); ++it )
		{
			if (!syncedIds.contains(*it))
			{
				DEBUGKPILOT<<"Deleting record with ID "<<*it<<" from handheld (is not on PC, and syncing with PC->HH direction)"<<endl;
				fDatabase->deleteRecord(*it);
				fCtrHH->deleted();
				fLocalDatabase->deleteRecord(*it);
			}
		}
	}
	TQTimer::singleShot(0, this, TQ_SLOT(slotCleanup()));
}


void AbbrowserConduit::slotCleanup()
{
	FUNCTIONSETUP;

	// Set the appInfoBlock, just in case the category labels changed
	_setAppInfo();
	if(fDatabase)
	{
		fDatabase->resetSyncFlags();
		fDatabase->cleanup();
	}
	if(fLocalDatabase)
	{
		fLocalDatabase->resetSyncFlags();
		fLocalDatabase->cleanup();
	}

	// Write out the sync maps
	TQString syncFile = fLocalDatabase->dbPathName() + CSL1(".sync");
	DEBUGKPILOT << fname << ": Writing sync map to " << syncFile << endl;
	KSaveFile map( syncFile );
	if ( map.status() == 0 )
	{
		DEBUGKPILOT << fname << ": Writing sync map ..." << endl;
		(*map.dataStream()) << addresseeMap ;
		map.close();
	}
	// This also picks up errors from map.close()
	if ( map.status() != 0 )
	{
		WARNINGKPILOT << "Could not make backup of sync map." << endl;
	}

	_saveAddressBook();
	delayDone();
}



/*********************************************************************
              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 AbbrowserConduit::syncAddressee(Addressee &pcAddr, PilotAddress*backupAddr,
		PilotAddress*palmAddr)
{
	FUNCTIONSETUP;
	showAddresses(pcAddr, backupAddr, palmAddr);

	if ( syncMode() == SyncMode::eCopyPCToHH )
	{
		if (pcAddr.isEmpty())
		{
			return _deleteAddressee(pcAddr, backupAddr, palmAddr);
		}
		else
		{
			return _copyToHH(pcAddr, backupAddr, palmAddr);
		}
	}

	if ( syncMode() == SyncMode::eCopyHHToPC )
	{
		if (!palmAddr)
		{
			return _deleteAddressee(pcAddr, backupAddr, palmAddr);
		}
		else
		{
			return _copyToPC(pcAddr, backupAddr, palmAddr);
		}
	}

	if ( !backupAddr || isFirstSync() )
	{
			DEBUGKPILOT<< fname << ": Special case: no backup." << endl;
		/*
		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  (!palmAddr && TDEABCSync::isArchived(pcAddr) )
		{
			return true;
		}
		else if (!palmAddr && !pcAddr.isEmpty())
		{
			DEBUGKPILOT << fname << ": case: 1a"<<endl;
			// PC->HH
			bool res=_copyToHH(pcAddr, 0L, 0L);
			return res;
		}
		else if (!palmAddr && pcAddr.isEmpty())
		{
			DEBUGKPILOT << fname << ": case: 1b"<<endl;
			// everything's empty -> ERROR
			return false;
		}
		else if ( (isDeleted(palmAddr) || isArchived(palmAddr)) && pcAddr.isEmpty())
		{
			DEBUGKPILOT << fname << ": case: 1c"<<endl;
			if (isArchived(palmAddr))
				return _copyToPC(pcAddr, 0L, palmAddr);
			else
				// this happens if you add a record on the handheld and delete it again before you do the next sync
				return _deleteAddressee(pcAddr, 0L, palmAddr);
		}
		else if ((isDeleted(palmAddr)||isArchived(palmAddr)) && !pcAddr.isEmpty())
		{
			DEBUGKPILOT << fname << ": case: 1d"<<endl;
			// CR (ERROR)
			return _smartMergeAddressee(pcAddr, 0L, palmAddr);
		}
		else if (pcAddr.isEmpty())
		{
			DEBUGKPILOT << fname << ": case: 1e"<<endl;
			// HH->PC
			return _copyToPC(pcAddr, 0L, palmAddr);
		}
		else
		{
			DEBUGKPILOT << fname << ": case: 1f"<<endl;
			// Conflict Resolution
			return _smartMergeAddressee(pcAddr, 0L, palmAddr);
		}
	} // !backupAddr
	else
	{
			DEBUGKPILOT << fname << ": case: 2"<<endl;
		/*
		Resolution matrix:
		  1) if HH.(empty| (deleted &! archived) ) -> { if (PC==B) -> delete, else -> CR }
		     if HH.archied -> {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 addressees are different -> CR
		*/

		if (!palmAddr || isDeleted(palmAddr) )
		{
			DEBUGKPILOT << fname << ": case: 2a"<<endl;
			if (_equal(backupAddr, pcAddr) || pcAddr.isEmpty())
			{
				return _deleteAddressee(pcAddr, backupAddr, 0L);
			}
			else
			{
				return _smartMergeAddressee(pcAddr, backupAddr, 0L);
			}
		}
		else if (pcAddr.isEmpty())
		{
			DEBUGKPILOT << fname << ": case: 2b"<<endl;
			if (*palmAddr == *backupAddr)
			{
				return _deleteAddressee(pcAddr, backupAddr, palmAddr);
			}
			else
			{
				return _smartMergeAddressee(pcAddr, backupAddr, palmAddr);
			}
		}
		else if (_equal(palmAddr, pcAddr))
		{
			DEBUGKPILOT << fname << ": case: 2c"<<endl;
			// update Backup, update ID of PC if neededd
			return _writeBackup(palmAddr);
		}
		else if (_equal(backupAddr, pcAddr))
		{
			DEBUGKPILOT << fname << ": case: 2d"<<endl;
			DEBUGKPILOT << fname << ": Flags: "<<palmAddr->attributes()<<", isDeleted="<<
				isDeleted(palmAddr)<<", isArchived="<<isArchived(palmAddr)<<endl;
			if (isDeleted(palmAddr))
				return _deleteAddressee(pcAddr, backupAddr, palmAddr);
			else
				return _copyToPC(pcAddr, backupAddr, palmAddr);
		}
		else if (*palmAddr == *backupAddr)
		{
			DEBUGKPILOT << fname << ": case: 2e"<<endl;
			return _copyToHH(pcAddr, backupAddr, palmAddr);
		}
		else
		{
			DEBUGKPILOT << fname << ": case: 2f"<<endl;
			// CR, since all are different
			return _smartMergeAddressee(pcAddr, backupAddr, palmAddr);
		}
	} // backupAddr
	return false;
}



bool AbbrowserConduit::_copyToHH(Addressee &pcAddr, PilotAddress*backupAddr,
		PilotAddress*palmAddr)
{
	FUNCTIONSETUP;

	if (pcAddr.isEmpty()) return false;
	PilotAddress*paddr=palmAddr;
	bool paddrcreated=false;
	if (!paddr)
	{
		paddr=new PilotAddress();
		paddrcreated=true;
		fCtrHH->created();
	}
	else
	{
		fCtrHH->updated();
	}
	TDEABCSync::copy(*paddr, pcAddr, *fAddressAppInfo, fSyncSettings);

	DEBUGKPILOT << fname << "palmAddr->id=" << paddr->id()
		<< ", pcAddr.ID=" << pcAddr.custom(TDEABCSync::appString, TDEABCSync::idString) << endl;

	if(_savePalmAddr(paddr, pcAddr))
	{
		_savePCAddr(pcAddr, backupAddr, paddr);
	}
	if (paddrcreated) KPILOT_DELETE(paddr);
	return true;
}



bool AbbrowserConduit::_copyToPC(Addressee &pcAddr, PilotAddress*backupAddr,
		PilotAddress*palmAddr)
{
	FUNCTIONSETUP;
	if (!palmAddr)
	{
		return false;
	}
	// keep track of CUD's...
	if (pcAddr.isEmpty())
	{
		fCtrPC->created();
	}
	else
	{
		fCtrPC->updated();
	}
	showPilotAddress(palmAddr);

	TDEABCSync::copy(pcAddr, *palmAddr, *fAddressAppInfo, fSyncSettings);
	if (isArchived(palmAddr))
	{
		TDEABCSync::makeArchived(pcAddr);
	}

	_savePCAddr(pcAddr, backupAddr, palmAddr);
	_writeBackup(palmAddr);
	return true;
}



bool AbbrowserConduit::_writeBackup(PilotAddress *backup)
{
	FUNCTIONSETUP;
	if (!backup) return false;

	showPilotAddress(backup);

	PilotRecord *pilotRec = backup->pack();
	fLocalDatabase->writeRecord(pilotRec);
	KPILOT_DELETE(pilotRec);
	return true;
}



bool AbbrowserConduit::_deleteAddressee(Addressee &pcAddr, PilotAddress*backupAddr,
		PilotAddress*palmAddr)
{
	FUNCTIONSETUP;

	if (palmAddr)
	{
		if (!syncedIds.contains(palmAddr->id())) {
		DEBUGKPILOT << fname << ": adding id:["<< palmAddr->id() << "] to syncedIds." << endl;
			syncedIds.append(palmAddr->id());
		}
		fDatabase->deleteRecord(palmAddr->id());
		fCtrHH->deleted();
		fLocalDatabase->deleteRecord(palmAddr->id());
	}
	else if (backupAddr)
	{
		if (!syncedIds.contains(backupAddr->id())) {
		DEBUGKPILOT << fname << ": adding id:["<< backupAddr->id() << "] to syncedIds." << endl;
			syncedIds.append(backupAddr->id());
		}
		fLocalDatabase->deleteRecord(backupAddr->id());
	}
	if (!pcAddr.isEmpty())
	{
		DEBUGKPILOT << fname << " removing " << pcAddr.formattedName() << endl;
		abChanged = true;
		aBook->removeAddressee(pcAddr);
		fCtrPC->deleted();
	}
	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 AbbrowserConduit::_savePalmAddr(PilotAddress *palmAddr, Addressee &pcAddr)
{
	FUNCTIONSETUP;

	DEBUGKPILOT << fname << ": Saving to pilot " << palmAddr->id()
		<< " " << palmAddr->getField(entryFirstname)
		<< " " << palmAddr->getField(entryLastname)<< endl;

	PilotRecord *pilotRec = palmAddr->pack();
	DEBUGKPILOT << fname << ": record with id=" << pilotRec->id()
		<< " len=" << pilotRec->size() << endl;
	recordid_t pilotId = fDatabase->writeRecord(pilotRec);
	DEBUGKPILOT << fname << ": Wrote "<<pilotId<<": ID="<<pilotRec->id()<<endl;
	fLocalDatabase->writeRecord(pilotRec);
	KPILOT_DELETE(pilotRec);

	// pilotId == 0 if using local db, so don't overwrite the valid id
	if(pilotId != 0)
	{
		palmAddr->setID(pilotId);
		if (!syncedIds.contains(pilotId)) {
		DEBUGKPILOT << fname << ": adding id:["<< pilotId << "] to syncedIds." << endl;
			syncedIds.append(pilotId);
		}
	}

	recordid_t abId = 0;
	abId = pcAddr.custom(TDEABCSync::appString, TDEABCSync::idString).toUInt();
	if(abId != pilotId)
	{
		pcAddr.insertCustom(TDEABCSync::appString, TDEABCSync::idString, TQString::number(pilotId));
		return true;
	}

	return false;
}



bool AbbrowserConduit::_savePCAddr(Addressee &pcAddr, PilotAddress*,
	PilotAddress*)
{
	FUNCTIONSETUP;

	DEBUGKPILOT<<"Before _savePCAddr, pcAddr.custom="<<pcAddr.custom(TDEABCSync::appString, TDEABCSync::idString)<<endl;
	TQString pilotId = pcAddr.custom(TDEABCSync::appString, TDEABCSync::idString);
	long pilotIdL = pilotId.toLong();
	if(!pilotId.isEmpty())
	{
		// because we maintain a mapping between pilotId -> tdeabc uid, whenever we add
		// a new relationship, we have to remove any old mapping that would tie a different
		// pilot id -> this tdeabc uid
		TQMap < recordid_t, TQString>::iterator it;
		for ( it = addresseeMap.begin(); it != addresseeMap.end(); ++it ) {
			TQString tdeabcUid = it.data();
			if (tdeabcUid == pcAddr.uid()) {
				addresseeMap.remove(it);
				break;
			}
		}

		// now put the new mapping in
		addresseeMap.insert(pilotIdL,  pcAddr.uid());
	}

	aBook->insertAddressee(pcAddr);

	abChanged = true;
	return true;
}




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



bool AbbrowserConduit::_equal(const PilotAddress *piAddress, const Addressee &abEntry,
	enum eqFlagsType flags) const
{
	FUNCTIONSETUP;

	// empty records are never equal!
	if (!piAddress) {
		DEBUGKPILOT << fname  << ": no pilot address passed" << endl;
		return false;
	}
	if (abEntry.isEmpty()) {
		DEBUGKPILOT << fname  << ":abEntry.isEmpty()" << endl;
		return false;
	}
	//  Archived records match anything so they won't be copied to the HH again
	if (flags & eqFlagsFlags)
		if (isArchived(piAddress) && TDEABCSync::isArchived(abEntry) ) return true;

	if (flags & eqFlagsName)
	{
		if(!_equal(abEntry.familyName(), piAddress->getField(entryLastname)))
		{
		DEBUGKPILOT << fname  << ": last name not equal" << endl;
			return false;
		}
		if(!_equal(abEntry.givenName(), piAddress->getField(entryFirstname)))
		{
		DEBUGKPILOT << fname  << ": first name not equal" << endl;
			return false;
		}
		if(!_equal(abEntry.prefix(), piAddress->getField(entryTitle)))
		{
		DEBUGKPILOT << fname  << ": title/prefix not equal" << endl;
			return false;
		}
		if(!_equal(abEntry.organization(), piAddress->getField(entryCompany)))
		{
		DEBUGKPILOT << fname  << ": company/organization not equal" << endl;
			return false;
		}
	}
	if (flags & eqFlagsNote)
		if(!_equal(abEntry.note(), piAddress->getField(entryNote)))
	{
		DEBUGKPILOT << fname  << ": note not equal" << endl;
			return false;
	}

	if (flags & eqFlagsCategory)
	{
		// Check that the name of the category of the HH record
		// is one matching the PC record.
		TQString addressCategoryLabel = fAddressAppInfo->categoryName(piAddress->category());
		TQString cat = TDEABCSync::bestMatchedCategoryName(abEntry.categories(),
			*fAddressAppInfo, piAddress->category());
		if(!_equal(cat, addressCategoryLabel))
		{
			DEBUGKPILOT << fname  << ": category not equal" << endl;
			return false;
		}
	}

	if (flags & eqFlagsPhones)
	{
		// first, look for missing e-mail addresses on either side
		TQStringList abEmails(abEntry.emails());
		TQStringList piEmails(piAddress->getEmails());

		if (abEmails.count() != piEmails.count())
		{
		DEBUGKPILOT << fname  << ": email count not equal" << endl;
			return false;
		}
		for (TQStringList::Iterator it = abEmails.begin(); it != abEmails.end(); it++) {
			if (!piEmails.contains(*it))
			{
		DEBUGKPILOT << fname  << ": pilot e-mail missing" << endl;
			return false;
			}
		}
		for (TQStringList::Iterator it = piEmails.begin(); it != piEmails.end(); it++) {
			if (!abEmails.contains(*it))
			{
		DEBUGKPILOT << fname  << ": tdeabc e-mail missing" << endl;
			return false;
			}
		}

		// now look for differences in phone numbers.  Note:  we can't just compare one
		// of each kind of phone number, because there's no guarantee that if the user
		// has more than one of a given type, we're comparing the correct two.

		PhoneNumber::List abPhones(abEntry.phoneNumbers());
		PhoneNumber::List piPhones = TDEABCSync::getPhoneNumbers(*piAddress);
		// first make sure that all of the pilot phone numbers are in tdeabc
		for (PhoneNumber::List::Iterator it = piPhones.begin(); it != piPhones.end(); it++) {
			PhoneNumber piPhone = *it;
			bool found=false;
			for (PhoneNumber::List::Iterator it = abPhones.begin(); it != abPhones.end(); it++) {
				PhoneNumber abPhone = *it;
				// see if we have the same number here...
				// * Note * We used to check for preferred number matching, but
				//     this seems to have broke in tdepim 3.5 and I don't have time to
				//     figure out why, so we won't check to see if preferred number match
				if ( _equal(piPhone.number(), abPhone.number()) ) {
					found = true;
					break;
				}
			}
			if (!found) {
		DEBUGKPILOT << fname  << ": not equal because tdeabc phone not found." << endl;
				return false;
			}
		}
		// now the other way.  *cringe*  tdeabc has the capacity to store way more addresses
		// than the Pilot, so this might give false positives more than we'd want....
		for (PhoneNumber::List::Iterator it = abPhones.begin(); it != abPhones.end(); it++) {
			PhoneNumber abPhone = *it;
			bool found=false;
			for (PhoneNumber::List::Iterator it = piPhones.begin(); it != piPhones.end(); it++) {
				PhoneNumber piPhone = *it;
				if ( _equal(piPhone.number(), abPhone.number()) ) {
					found = true;
					break;
				}
			}
			if (!found)
			{
				DEBUGKPILOT << fname  << ": not equal because pilot phone not found." << endl;
				return false;
			}
		}

		if(!_equal(TDEABCSync::getFieldForHHOtherPhone(abEntry,fSyncSettings),
			piAddress->getPhoneField(PilotAddressInfo::eOther)))
		{
			DEBUGKPILOT << fname  << ": not equal because of other phone field." << endl;
		   	return false;
		}
	}

	if (flags & eqFlagsAdress)
	{
		TDEABC::Address address = TDEABCSync::getAddress(abEntry,fSyncSettings);
		if(!_equal(address.street(), piAddress->getField(entryAddress)))
		{
			DEBUGKPILOT << fname  << ": address not equal" << endl;
			return false;
		}
		if(!_equal(address.locality(), piAddress->getField(entryCity)))
		{
			DEBUGKPILOT << fname  << ": city not equal" << endl;
			return false;
		}
		if(!_equal(address.region(), piAddress->getField(entryState)))
		{
		DEBUGKPILOT << fname  << ": state not equal" << endl;
			return false;
		}
		if(!_equal(address.postalCode(), piAddress->getField(entryZip)))
		{
		DEBUGKPILOT << fname  << ": zip not equal" << endl;
			return false;
		}
		if(!_equal(address.country(), piAddress->getField(entryCountry)))
		{
		DEBUGKPILOT << fname  << ": country not equal" << endl;
			return false;
		}
	}

	if (flags & eqFlagsCustom)
	{
		unsigned int customIndex = 0;
		unsigned int hhField = entryCustom1;

		for ( ; customIndex<4; ++customIndex,++hhField )
		{
			if (!_equal(TDEABCSync::getFieldForHHCustom(customIndex, abEntry, fSyncSettings),
				piAddress->getField(hhField)))
			{
				DEBUGKPILOT << fname << ": Custom field " << customIndex
					<< " (HH field " << hhField << ") differs." << endl;
				return false;
			}
		}
	}

	// if any side is marked archived, but the other is not, the two
	// are not equal.
	if ( (flags & eqFlagsFlags) && (isArchived(piAddress) || TDEABCSync::isArchived(abEntry) ) )
	{
		DEBUGKPILOT << fname  << ": archived flags don't match" << endl;
		return false;
	}

	return true;
}










/*********************************************************************
 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
 *********************************************************************/



/** smartly merge the given field for the given entry. use the backup record to determine which record has been modified
	@pc, @backup, @palm ... entries of the according databases
	@returns string of the merged entries.
*/
TQString AbbrowserConduit::_smartMergeString(const TQString &pc, const TQString & backup,
	const TQString & palm, ConflictResolution confRes)
{
	FUNCTIONSETUP;

	// if both entries are already the same, no need to do anything
	if(pc == palm) return pc;

	// If this is a first sync, we don't have a backup record, so
	if(isFirstSync() || backup.isEmpty()) {
		if (pc.isEmpty() && palm.isEmpty() ) return TQString();
		if(pc.isEmpty()) return palm;
		if(palm.isEmpty()) return pc;
	} else {
		// only one side modified, so return that string, no conflict
		if(palm == backup) return pc;
		if(pc == backup) return palm;
	}

	DEBUGKPILOT<<"pc="<<pc<<", backup="<<backup<<", palm="<<
		palm<<", ConfRes="<<confRes<<endl;
	DEBUGKPILOT<<"Use conflict resolution :"<<confRes<<
		", PC="<<SyncAction::ePCOverrides<<endl;
	switch(confRes) {
		case SyncAction::ePCOverrides: return pc; break;
		case SyncAction::eHHOverrides: return palm; break;
		case SyncAction::ePreviousSyncOverrides: return backup; break;
		default: break;
	}
	return TQString();
}



bool AbbrowserConduit::_buildResolutionTable(ResolutionTable*tab, const Addressee &pcAddr,
	PilotAddress *backupAddr, PilotAddress *palmAddr)
{
	FUNCTIONSETUP;
	if (!tab) return false;
	tab->setAutoDelete( true );
	tab->labels[0]=i18n("Item on PC");
	tab->labels[1]=i18n("Handheld");
	tab->labels[2]=i18n("Last sync");
	if (!pcAddr.isEmpty())
		tab->fExistItems=(eExistItems)(tab->fExistItems|eExistsPC);
	if (backupAddr)
		tab->fExistItems=(eExistItems)(tab->fExistItems|eExistsBackup);
	if (palmAddr)
		tab->fExistItems=(eExistItems)(tab->fExistItems|eExistsPalm);

#define appendGen(desc, abfield, palmfield) \
	tab->append(new ResolutionItem(desc, tab->fExistItems, \
		(!pcAddr.isEmpty())?(abfield):(TQString()), \
		(palmAddr)?(palmAddr->palmfield):(TQString()), \
		(backupAddr)?(backupAddr->palmfield):(TQString()) ))
#define appendAddr(desc, abfield, palmfield) \
	appendGen(desc, abfield, getField(palmfield))
#define appendGenPhone(desc, abfield, palmfield) \
	appendGen(desc, abfield, getPhoneField(PilotAddressInfo::palmfield))
#define appendPhone(desc, abfield, palmfield) \
	appendGenPhone(desc, pcAddr.phoneNumber(PhoneNumber::abfield).number(), palmfield)


	appendAddr(i18n("Last name"), pcAddr.familyName(), entryLastname);
	appendAddr(i18n("First name"), pcAddr.givenName(), entryFirstname);
	appendAddr(i18n("Organization"), pcAddr.organization(), entryCompany);
	appendAddr(i18n("Title"), pcAddr.prefix(), entryTitle);
	appendAddr(i18n("Note"), pcAddr.note(), entryNote);

	appendAddr(i18n("Custom 1"), TDEABCSync::getFieldForHHCustom(0, pcAddr, fSyncSettings), entryCustom1);
	appendAddr(i18n("Custom 2"), TDEABCSync::getFieldForHHCustom(1, pcAddr, fSyncSettings), entryCustom2);
	appendAddr(i18n("Custom 3"), TDEABCSync::getFieldForHHCustom(2, pcAddr, fSyncSettings), entryCustom3);
	appendAddr(i18n("Custom 4"), TDEABCSync::getFieldForHHCustom(3, pcAddr, fSyncSettings), entryCustom4);

	appendPhone(i18n("Work Phone"), Work, eWork);
	appendPhone(i18n("Home Phone"), Home, eHome);
	appendPhone(i18n("Mobile Phone"), Cell, eMobile);
	appendGenPhone(i18n("Fax"), pcAddr.phoneNumber(faxTypeOnPC()).number(), eFax);
	appendPhone(i18n("Pager"), Pager, ePager);
	appendGenPhone(i18n("Other"), TDEABCSync::getFieldForHHOtherPhone(pcAddr,fSyncSettings), eOther);
	appendGenPhone(i18n("Email"), pcAddr.preferredEmail(), eEmail);

	TDEABC::Address abAddress = TDEABCSync::getAddress(pcAddr,fSyncSettings);
	appendAddr(i18n("Address"), abAddress.street(), entryAddress);
	appendAddr(i18n("City"), abAddress.locality(), entryCity);
	appendAddr(i18n("Region"), abAddress.region(), entryState);
	appendAddr(i18n("Postal code"), abAddress.postalCode(), entryZip);
	appendAddr(i18n("Country"), abAddress.country(), entryCountry);

	TQString palmAddrCategoryLabel;
	if (palmAddr)
	{
		 palmAddrCategoryLabel = fAddressAppInfo->categoryName(palmAddr->category());
	}
	TQString backupAddrCategoryLabel;
	if (backupAddr)
	{
		backupAddrCategoryLabel = fAddressAppInfo->categoryName(backupAddr->category());
	}
	int category = palmAddr ? palmAddr->category() : 0;
	tab->append(new ResolutionItem(
		i18n("Category"),
		tab->fExistItems,
		!pcAddr.isEmpty() ?
			TDEABCSync::bestMatchedCategoryName(pcAddr.categories(), *fAddressAppInfo, category) :
			TQString(),
		palmAddrCategoryLabel,
		backupAddrCategoryLabel));
#undef appendGen
#undef appendAddr
#undef appendGenPhone
#undef appendPhone

	return true;
}


/// This function just sets the phone number of type "type" to "phone"
static inline void setPhoneNumber(Addressee &abEntry, int type, const TQString &nr)
{
	PhoneNumber phone = abEntry.phoneNumber(type);
	phone.setNumber(nr);
	abEntry.insertPhoneNumber(phone);
}


bool AbbrowserConduit::_applyResolutionTable(ResolutionTable*tab, Addressee &pcAddr,
	PilotAddress *backupAddr, PilotAddress *palmAddr)
{
	FUNCTIONSETUP;
	if (!tab) return false;
	if (!palmAddr) {
		WARNINGKPILOT << "Empty palmAddr after conflict resolution." << endl;
		return false;
	}

	ResolutionItem*item=tab->first();
#define SETGENFIELD(abfield, palmfield) \
	if (item) {\
		abfield; \
		palmAddr->setField(palmfield, item->fResolved); \
	}\
	item=tab->next();
#define SETFIELD(abfield, palmfield) \
	SETGENFIELD(pcAddr.set##abfield(item->fResolved), palmfield)
#define SETCUSTOMFIELD(abfield, palmfield) \
	SETGENFIELD(TDEABCSync::setFieldFromHHCustom(abfield, pcAddr, item->fResolved, fSyncSettings), palmfield)
#define SETGENPHONE(abfield, palmfield) \
	if (item) { \
		abfield; \
		palmAddr->setPhoneField(PilotAddressInfo::palmfield, item->fResolved, PilotAddress::Replace); \
	}\
	item=tab->next();
#define SETPHONEFIELD(abfield, palmfield) \
	SETGENPHONE(setPhoneNumber(pcAddr, PhoneNumber::abfield, item->fResolved), palmfield)
#define SETADDRESSFIELD(abfield, palmfield) \
	SETGENFIELD(abAddress.abfield(item->fResolved), palmfield)

	SETFIELD(FamilyName, entryLastname);
	SETFIELD(GivenName, entryFirstname);
	SETFIELD(Organization, entryCompany);
	SETFIELD(Prefix, entryTitle);
	SETFIELD(Note, entryNote);

	SETCUSTOMFIELD(0, entryCustom1);
	SETCUSTOMFIELD(1, entryCustom2);
	SETCUSTOMFIELD(2, entryCustom3);
	SETCUSTOMFIELD(3, entryCustom4);

	SETPHONEFIELD(Work, eWork);
	SETPHONEFIELD(Home, eHome);
	SETPHONEFIELD(Cell, eMobile);
	SETGENPHONE(setPhoneNumber(pcAddr, faxTypeOnPC(), item->fResolved), eFax);
	SETPHONEFIELD(Pager, ePager);
	SETGENPHONE(TDEABCSync::setFieldFromHHOtherPhone(pcAddr, item->fResolved, fSyncSettings), eOther);

	// TODO: fix email
	if (item)
	{
		palmAddr->setPhoneField(PilotAddressInfo::eEmail, item->fResolved, PilotAddress::Replace);
		if (backupAddr)
		{
			pcAddr.removeEmail(backupAddr->getPhoneField(PilotAddressInfo::eEmail));
		}
		pcAddr.removeEmail(palmAddr->getPhoneField(PilotAddressInfo::eEmail));
		pcAddr.insertEmail(item->fResolved, true);
	}
	item=tab->next();

	TDEABC::Address abAddress = TDEABCSync::getAddress(pcAddr, fSyncSettings);
	SETADDRESSFIELD(setStreet, entryAddress);
	SETADDRESSFIELD(setLocality, entryCity);
	SETADDRESSFIELD(setRegion, entryState);
	SETADDRESSFIELD(setPostalCode, entryZip);
	SETADDRESSFIELD(setCountry, entryCountry);
	pcAddr.insertAddress(abAddress);

	// TODO: Is this correct?
	if (item)
	{
		palmAddr->setCategory( fAddressAppInfo->findCategory(item->fResolved) );
		TDEABCSync::setCategory(pcAddr, item->fResolved);
	}


#undef SETGENFIELD
#undef SETFIELD
#undef SETCUSTOMFIELD
#undef SETGENPHONE
#undef SETPHONEFIELD
#undef SETADDRESSFIELD

	return true;
}



bool AbbrowserConduit::_smartMergeTable(ResolutionTable*tab)
{
	FUNCTIONSETUP;
	if (!tab) return false;
	bool noconflict=true;
	ResolutionItem*item;
	for ( item = tab->first(); item; item = tab->next() )
	{
		// try to merge the three strings
		item->fResolved=_smartMergeString(item->fEntries[0],
			item->fEntries[2], item->fEntries[1], getConflictResolution());
		// if a conflict occurred, set the default to something sensitive:
		if (item->fResolved.isNull() && !(item->fEntries[0].isEmpty() &&
			item->fEntries[1].isEmpty() && item->fEntries[2].isEmpty() ) )
		{
			item->fResolved=item->fEntries[0];
			noconflict=false;
		}
		if (item->fResolved.isNull()) item->fResolved=item->fEntries[1];
		if (item->fResolved.isNull()) item->fResolved=item->fEntries[2];
	}
	return  noconflict;
}



/** Merge the palm and the pc entries with the additional information of
 *  the backup.
 *  return value: no meaning yet
 */
bool AbbrowserConduit::_smartMergeAddressee(Addressee &pcAddr,
	PilotAddress *backupAddr, PilotAddress *palmAddr)
{
	FUNCTIONSETUP;

	// Merge them, then look which records have to be written to device or abook
	int res = SyncAction::eAskUser;
 	bool result=true;
	ResolutionTable tab;

	result &= _buildResolutionTable(&tab, pcAddr, backupAddr, palmAddr);
	// Now attempt a smart merge. If that fails, let conflict resolution do the job
	bool mergeOk=_smartMergeTable(&tab);

	if (!mergeOk)
	{
		TQString dlgText;
		if (!palmAddr)
		{
			dlgText=i18n("The following address entry was changed, but does no longer exist on the handheld. Please resolve this conflict:");
		}
		else if (pcAddr.isEmpty())
		{
			dlgText=i18n("The following address entry was changed, but does no longer exist on the PC. Please resolve this conflict:");
		}
		else
		{
			dlgText=i18n("The following address entry was changed on the handheld as well as on the PC side. The changes could not be merged automatically, so please resolve the conflict yourself:");
		}
		ResolutionDlg*resdlg=new ResolutionDlg(0L, fHandle, i18n("Address conflict"), dlgText, &tab);
		resdlg->exec();
		KPILOT_DELETE(resdlg);
	}
	res=tab.fResolution;

	// Disallow some resolution under certain conditions, fix wrong values:
	switch (res) {
		case SyncAction::eHHOverrides:
			if (!palmAddr) res=SyncAction::eDelete;
			break;
		case SyncAction::ePCOverrides:
			if (pcAddr.isEmpty()) res=SyncAction::eDelete;
			break;
		case SyncAction::ePreviousSyncOverrides:
			if (!backupAddr) res=SyncAction::eDoNothing;
			break;
	}

	PilotAddress*pAddr=palmAddr;
	bool pAddrCreated=false;
	// Now that we have done a possible conflict resolution, apply the changes
	switch (res) {
		case SyncAction::eDuplicate:
			// Set the Palm ID to 0 so we don't overwrite the existing record.
			pcAddr.removeCustom(TDEABCSync::appString, TDEABCSync::idString);
			result &= _copyToHH(pcAddr, 0L, 0L);
			{
			Addressee pcadr;
			result &= _copyToPC(pcadr, backupAddr, palmAddr);
			}
			break;
		case SyncAction::eDoNothing:
			break;
		case SyncAction::eHHOverrides:
				result &= _copyToPC(pcAddr, backupAddr, palmAddr);
				break;
		case SyncAction::ePCOverrides:
			result &= _copyToHH(pcAddr, backupAddr, pAddr);
			break;
		case SyncAction::ePreviousSyncOverrides:
			TDEABCSync::copy(pcAddr, *backupAddr, *fAddressAppInfo, fSyncSettings);
			if (palmAddr && backupAddr) *palmAddr=*backupAddr;
			result &= _savePalmAddr(backupAddr, pcAddr);
			result &= _savePCAddr(pcAddr, backupAddr, backupAddr);
			break;
		case SyncAction::eDelete:
			result &= _deleteAddressee(pcAddr, backupAddr, palmAddr);
			break;
		case SyncAction::eAskUser:
		default:
			if (!pAddr)
			{
				pAddr=new PilotAddress();
				pAddrCreated=true;
			}
			result &= _applyResolutionTable(&tab, pcAddr, backupAddr, pAddr);
showAddresses(pcAddr, backupAddr, pAddr);
			// savePalmAddr sets the RecordID custom field already
			result &= _savePalmAddr(pAddr, pcAddr);
			result &= _savePCAddr(pcAddr, backupAddr, pAddr);
			if (pAddrCreated) KPILOT_DELETE(pAddr);
			break;
	}

	return result;
}



// 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.!!!
Addressee AbbrowserConduit::_findMatch(const PilotAddress & pilotAddress) const
{
	FUNCTIONSETUP;
	// TODO: also search with the pilotID
	// first, use the pilotID to UID map to find the appropriate record
	if( !isFirstSync() && (pilotAddress.id() > 0) )
	{
		TQString id(addresseeMap[pilotAddress.id()]);
		DEBUGKPILOT << fname << ": PilotRecord has id " << pilotAddress.id() << ", mapped to " << id << endl;
		if(!id.isEmpty())
		{
			Addressee res(aBook->findByUid(id));
			if(!res.isEmpty()) return res;
			DEBUGKPILOT << fname << ": PilotRecord has id " << pilotAddress.id() << ", but could not be found in the addressbook" << endl;
		}
	}

	for(AddressBook::Iterator iter = aBook->begin(); iter != aBook->end(); ++iter)
	{
		Addressee abEntry = *iter;
		TQString recID(abEntry.custom(TDEABCSync::appString, TDEABCSync::idString));
		bool ok;
		if (!recID.isEmpty() )
		{
			recordid_t rid = recID.toLong(&ok);
			if (ok && rid)
			{
				if (rid==pilotAddress.id()) return abEntry;// yes, we found it
				// skip this addressee, as it can an other corresponding address on the handheld
				if (allIds.contains(rid)) continue;
			}
		}

		if (_equal(&pilotAddress, abEntry, eqFlagsAlmostAll))
		{
			return abEntry;
		}
	}
	DEBUGKPILOT << fname << ": Could not find any addressbook enty matching " << pilotAddress.getField(entryLastname) << endl;
	return Addressee();
}

void AbbrowserConduit::slotTestRecord()
{
	FUNCTIONSETUP;

	// Get a record and interpret it as an address.
	PilotRecord *r = fDatabase->readRecordByIndex( pilotindex );
	if (!r)
	{
		delayDone();
		return;
	}
	PilotAddress a(r);
	KPILOT_DELETE(r);

	// Process this record.
	showPilotAddress(&a);

	// Schedule more work.
	++pilotindex;
	TQTimer::singleShot(0, this, TQ_SLOT(slotTestRecord()));
}
