/* abbrowser-conduit.cc                           KPilot
**
** Copyright (C) 2000,2001 by Dan Pilone
** Copyright (C) 2002-2003 by Reinhold Kainhofer
**
** The abbrowser conduit copies addresses from the Pilot's address book to
** the KDE addressbook maintained via the kabc 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., 59 Temple Place, Suite 330, Boston,
** MA 02111-1307, USA.
*/

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



#include "options.h"

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

#include <kabc/stdaddressbook.h>
#include <kabc/resourcefile.h>
#include <kio/netaccess.h>

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

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

#include "abbrowser-conduit.moc"

// Something to allow us to check what revision
// the modules are that make up a binary distribution.
//
//
extern "C"
{
long version_conduit_address = KPILOT_PLUGIN_API;
const char *id_conduit_address="$Id: abbrowser-conduit.cc,v 1.99 2004/05/12 11:53:32 coles Exp $";
}


using namespace KABC;

const QString AbbrowserConduit::appString=CSL1("KPILOT");
const QString AbbrowserConduit::flagString=CSL1("Flag");
const QString AbbrowserConduit::idString=CSL1("RecordID");

AddressBook*AbbrowserConduit::aBook=0L;

/// This macro just sets the phone number of type "type" to "phone"
/// Use a macro, because that saves two lines for each call, but does not
/// have the overhead of a function call
#define _setPhoneNumber(abEntry, type, nr) \
	{ PhoneNumber phone = abEntry.phoneNumber(type); \
	phone.setNumber(nr); \
	abEntry.insertPhoneNumber(phone); }


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





AbbrowserConduit::AbbrowserConduit(KPilotDeviceLink * o, const char *n, const QStringList & a):
		ConduitAction(o, n, a),
		addresseeMap(),
		syncedIds(),
		abiter(),
		ticket(0L)
{
	FUNCTIONSETUP;
#ifdef DEBUG
	DEBUGCONDUIT<<id_conduit_address<<endl;
#endif
	fConduitName=i18n("Addressbook");
}



AbbrowserConduit::~AbbrowserConduit()
{
}



/*********************************************************************
                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(QMap < recordid_t, QString > &idContactMap) const
{
	FUNCTIONSETUP;

	idContactMap.clear();

	for(AddressBook::Iterator contactIter = aBook->begin();
		contactIter != aBook->end(); ++contactIter)
	{
		Addressee aContact = *contactIter;
		QString recid = aContact.custom(appString, idString);
		if(!recid.isEmpty())
		{
			recordid_t id = recid.toULong();
			idContactMap.insert(id, aContact.uid());
		}
	}
#ifdef DEBUG
	DEBUGCONDUIT << fname << ": Loaded " << idContactMap.size() <<
	    " addresses from the addressbook. " << endl;
#endif
}



bool AbbrowserConduit::_prepare()
{
	FUNCTIONSETUP;

	readConfig();
	syncedIds.clear();

	return true;
}



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

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

#ifdef DEBUG
	DEBUGCONDUIT << fname
		<< ": Settings "
		<< " fConflictResolution=" << getConflictResolution()
		<< " fPilotStreetHome=" << AbbrowserSettings::pilotStreet()
		<< " fPilotFaxHome=" << AbbrowserSettings::pilotFax()
		<< " fArchive=" << AbbrowserSettings::archiveDeleted()
		<< " eCustom[0]=" << AbbrowserSettings::custom0()
		<< " eCustom[1]=" << AbbrowserSettings::custom1()
		<< " eCustom[2]=" << AbbrowserSettings::custom2()
		<< " eCustom[3]=" << AbbrowserSettings::custom3()
		<< " fFirstTime=" << isFirstSync()
		<< endl;
#endif
}



bool AbbrowserConduit::isDeleted(const PilotAddress*addr)
{
	if (!addr) return true;
	if (addr->isDeleted() && !addr->isArchived()) return true;
	if (addr->isArchived()) return !AbbrowserSettings::archiveDeleted();
	return false;
}
bool AbbrowserConduit::isArchived(const PilotAddress*addr)
{
	if (addr && addr->isArchived()) return AbbrowserSettings::archiveDeleted();
	else return false;
}
bool AbbrowserConduit::isArchived(const Addressee &addr)
{
	return addr.custom(appString, flagString) == QString::number(SYNCDEL);
}
bool AbbrowserConduit::makeArchived(Addressee &addr)
{
	FUNCTIONSETUP;
	addr.insertCustom(appString, flagString, QString::number(SYNCDEL));
	addr.removeCustom(appString, idString);
	return true;
}



bool AbbrowserConduit::_loadAddressBook()
{
	FUNCTIONSETUP;

	switch ( AbbrowserSettings::addressbookType() )
	{
		case AbbrowserSettings::eAbookResource:
			DEBUGCONDUIT<<"Loading standard addressbook"<<endl;
			aBook = StdAddressBook::self();
			break;
		case AbbrowserSettings::eAbookFile: { // initialize the abook with the given file
			DEBUGCONDUIT<<"Loading custom addressbook"<<endl;
			KURL kurl(AbbrowserSettings::fileName());
			if(!KIO::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()));
				KIO::NetAccess::removeTempFile(fABookFile);
				return false;
			}

			aBook = new AddressBook();
			if (!aBook) return false;
			KABC::Resource *res = new ResourceFile(fABookFile, "vcard" );
			if ( !aBook->addResource( res ) ) {
				DEBUGCONDUIT << "Unable to open resource for file " << fABookFile << endl;
				KPILOT_DELETE( aBook );
				return false;
			}
			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.") );
		kdWarning()<<k_funcinfo<<": Unable to initialize the addressbook for the sync."<<endl;
		KPILOT_DELETE(aBook);
		return false;
	}
	abChanged = false;
	ticket=aBook->requestSaveTicket();
	if (!ticket)
	{
		kdWarning()<<k_funcinfo<<": Unable to lock addressbook for writing "<<endl;
		KPILOT_DELETE(aBook);
		return false;
	}
	// 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())
	{
		fFirstSync = true;
	}
	else
	{
		_mapContactsToPilot(addresseeMap);
	}
	return(aBook != 0L);
}
bool AbbrowserConduit::_saveAddressBook()
{
	FUNCTIONSETUP;
#ifdef DEBUG
			DEBUGCONDUIT<<"Addressbook not changed, freeing ticket"<<endl;
#endif

	bool res=false;

	if (ticket)
	{
		if (abChanged)
		{
			res=aBook->save(ticket);
		}
		else
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"Addressbook not changed, no need to save it"<<endl;
#endif
		}
		// XXX: KDE4: release ticket in all cases (save no longer releases it)
		if ( !res ) // didn't save, delete ticket manually
		{
			aBook->releaseSaveTicket(ticket);
		}
		ticket=0;
	}
	else
	{
		kdWarning()<<k_funcinfo<<": No ticket available to save the "
		<<"addressbook."<<endl;
	}
	if ( AbbrowserSettings::addressbookType()!= AbbrowserSettings::eAbookResource )
	{
		KURL kurl(AbbrowserSettings::fileName());
		if(!kurl.isLocalFile())
		{
#ifdef DEBUG
			DEBUGCONDUIT << "Deleting local addressbook tempfile" << endl;
#endif
			if(!KIO::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 {
				KIO::NetAccess::removeTempFile(fABookFile);
			}
			QFile backup(fABookFile + "~");
			backup.remove();
		}

#ifdef DEBUG
		DEBUGCONDUIT<<"Deleting addressbook"<<endl;
#endif
		KPILOT_DELETE(aBook);
	}

	return res;
}



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

	unpack_AddressAppInfo(&fAddressAppInfo, buffer, appLen);
	delete[]buffer;
	buffer = NULL;

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

	for(int x = 0; x < 8; x++)
	{
		DEBUGCONDUIT << fname << " phone[" << x << "] = " << fAddressAppInfo.phoneLabels[x] << endl;
	}
#endif
}
void AbbrowserConduit::_setAppInfo()
{
	FUNCTIONSETUP;
	// get the address application header information
	int appLen = pack_AddressAppInfo(&fAddressAppInfo, 0, 0);
	unsigned char *buffer = new unsigned char[appLen];
	pack_AddressAppInfo(&fAddressAppInfo, buffer, appLen);
	if (fDatabase) fDatabase->writeAppBlock(buffer, appLen);
	if (fLocalDatabase) fLocalDatabase->writeAppBlock(buffer, appLen);
	delete[] buffer;
}

int AbbrowserConduit::getCustom(const int index)
{
	FUNCTIONSETUP;

	int customEnum;
	switch(index) {
		case 0:
			customEnum = AbbrowserSettings::custom0();
			break;
		case 1:
			customEnum = AbbrowserSettings::custom1();
			break;
		case 2:
			customEnum = AbbrowserSettings::custom2();
			break;
		case 3:
			customEnum = AbbrowserSettings::custom3();
			break;
		default:
			customEnum = index;
			break;
	}

#ifdef DEBUG
	DEBUGCONDUIT << fname << "Index: " << index << " -> customEnum: "
		<< customEnum << endl;
#endif

	return customEnum;
}

QString AbbrowserConduit::getCustomField(const Addressee &abEntry, const int index)
{
	FUNCTIONSETUP;

	switch (getCustom(index)) {
		case AbbrowserSettings::eCustomBirthdate: {
			QDateTime bdate(abEntry.birthday().date());
			if (!bdate.isValid())
				return abEntry.custom(appString, CSL1("CUSTOM")+QString::number(index));
			QString tmpfmt(KGlobal::locale()->dateFormat());
			if (!AbbrowserSettings::customDateFormat().isEmpty())
				KGlobal::locale()->setDateFormat(AbbrowserSettings::customDateFormat());
#ifdef DEBUG
			DEBUGCONDUIT<<"Birthdate: "<<KGlobal::locale()->formatDate(bdate.date())<<" (QDate: "<<bdate.toString()<<endl;
#endif
			QString ret(KGlobal::locale()->formatDate(bdate.date()));
			KGlobal::locale()->setDateFormat(tmpfmt);
			return ret;
		}
		case AbbrowserSettings::eCustomURL:
			return abEntry.url().url();
			break;
		case AbbrowserSettings::eCustomIM:
			return abEntry.custom(CSL1("KADDRESSBOOK"), CSL1("X-IMAddress"));
			break;
		case AbbrowserSettings::eCustomField:
		default:
			return abEntry.custom(appString, CSL1("CUSTOM")+QString::number(index));
			break;
	}
}

void AbbrowserConduit::setCustomField(Addressee &abEntry,  int index, QString cust)
{
	FUNCTIONSETUP;

	switch (getCustom(index)) {
		case AbbrowserSettings::eCustomBirthdate: {
			QDate bdate;
			bool ok=false;
			if (AbbrowserSettings::customDateFormat().isEmpty())
			{
				// empty format means use locale setting
				bdate=KGlobal::locale()->readDate(cust, &ok);
			}
			else
			{
				// use given format
				bdate=KGlobal::locale()->readDate(cust, AbbrowserSettings::customDateFormat(), &ok);
			}
#ifdef DEBUG
			DEBUGCONDUIT<<"Birthdate from "<<index<<"-th custom field: "<<bdate.toString()<<endl;
			DEBUGCONDUIT<<"Is Valid: "<<bdate.isValid()<<endl;
#endif
			if (bdate.isValid())
				return abEntry.setBirthday(bdate);
			else
				return abEntry.insertCustom(CSL1("KADDRESSBOOK"), CSL1("X-IMAddress"), cust);
			break; }
		case AbbrowserSettings::eCustomURL: {
			return abEntry.setUrl(cust);
			break;}
		case AbbrowserSettings::eCustomIM: {
			return abEntry.insertCustom(CSL1("KADDRESSBOOK"), CSL1("X-IMAddress"), cust);
			break;}
		case AbbrowserSettings::eCustomField:
		default: {
			return abEntry.insertCustom(appString, CSL1("CUSTOM")+QString::number(index), cust);
			break;}
	}
	return;
}



QString AbbrowserConduit::getOtherField(const Addressee & abEntry)
{
	switch(AbbrowserSettings::pilotOther())
	{
		case AbbrowserSettings::eOtherPhone:
			return abEntry.phoneNumber(0).number();
		case AbbrowserSettings::eAssistant:
			return abEntry.custom(CSL1("KADDRESSBOOK"), CSL1("AssistantsName"));
		case AbbrowserSettings::eBusinessFax:
			return abEntry.phoneNumber(PhoneNumber::Fax | PhoneNumber::Work).number();
		case AbbrowserSettings::eCarPhone:
			return abEntry.phoneNumber(PhoneNumber::Car).number();
		case AbbrowserSettings::eEmail2:
			return abEntry.emails().first();
		case AbbrowserSettings::eHomeFax:
			return abEntry.phoneNumber(PhoneNumber::Fax | PhoneNumber::Home).number();
		case AbbrowserSettings::eTelex:
			return abEntry.phoneNumber(PhoneNumber::Bbs).number();
		case AbbrowserSettings::eTTYTTDPhone:
			return abEntry.phoneNumber(PhoneNumber::Pcs).number();
		default:
			return QString::null;
	}
}
void AbbrowserConduit::setOtherField(Addressee & abEntry, QString nr)
{
//	PhoneNumber phone;
	switch (AbbrowserSettings::pilotOther())
	{
		case AbbrowserSettings::eOtherPhone:
			_setPhoneNumber(abEntry, 0, nr)
			break;
		case AbbrowserSettings::eAssistant:
			abEntry.insertCustom(CSL1("KADDRESSBOOK"), CSL1("AssistantsName"), nr);
			break;
		case AbbrowserSettings::eBusinessFax:
			_setPhoneNumber(abEntry, PhoneNumber::Fax | PhoneNumber::Work, nr)
			break;
		case AbbrowserSettings::eCarPhone:
			_setPhoneNumber(abEntry, PhoneNumber::Car, nr)
			break;
		case AbbrowserSettings::eEmail2:
			return abEntry.insertEmail(nr);
		case AbbrowserSettings::eHomeFax:
			_setPhoneNumber(abEntry, PhoneNumber::Fax|PhoneNumber::Home, nr)
			break;
		case AbbrowserSettings::eTelex:
			_setPhoneNumber(abEntry, PhoneNumber::Bbs, nr)
			break;
		case AbbrowserSettings::eTTYTTDPhone:
			_setPhoneNumber(abEntry, PhoneNumber::Pcs, nr)
			break;
	}
}



PhoneNumber AbbrowserConduit::getFax(const Addressee & abEntry)
{
	return abEntry.phoneNumber( PhoneNumber::Fax |
		( (AbbrowserSettings::pilotFax()==0) ?(PhoneNumber::Home) :(PhoneNumber::Work)));
}
void AbbrowserConduit::setFax(Addressee & abEntry, QString fax)
{
	_setPhoneNumber(abEntry, PhoneNumber::Fax | ( (AbbrowserSettings::pilotFax()==0) ? PhoneNumber::Home : PhoneNumber::Work ), fax);
}


/** First search for a preferred  address. If we don't have one, search
 *  for home or work as specified in the config dialog. If we don't have
 *  such one, either, search for the other type. If we still have no luck,
 *  return an address with preferred + home/work flag (from config dlg). */
KABC::Address AbbrowserConduit::getAddress(const Addressee & abEntry)
{
	int type=(AbbrowserSettings::pilotStreet==0)?(KABC::Address::Home):(KABC::Address::Work);
	KABC::Address ad(abEntry.address(KABC::Address::Pref));
	if (!ad.isEmpty()) return ad;
	ad=abEntry.address(type);
	if (!ad.isEmpty()) return ad;
	ad=abEntry.address((AbbrowserSettings::pilotStreet==0) ?(KABC::Address::Work):(KABC::Address::Home));
	if (!ad.isEmpty()) return ad;

	return abEntry.address(type | KABC::Address::Pref);
}



/**
 * _getCat returns the id of the category from the given categories list.
 * If the address has no categories on the PC, QString::null 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, QString::null is returned
 */
QString AbbrowserConduit::_getCatForHH(const QStringList cats, const QString curr) const
{
	FUNCTIONSETUP;
	int j;
	if (cats.size()<1) return QString::null;
	if (cats.contains(curr)) return curr;
	for(QStringList::ConstIterator it = cats.begin(); it != cats.end(); ++it)
	{
		for(j = 0; j <= 15; j++)
		{
			QString catName = PilotAppCategory::codec()->
				toUnicode(fAddressAppInfo.category.name[j]);
			if(!(*it).isEmpty() && !_compare(*it, catName))
			{
				return catName;
			}
		}
	}
	// If we have a free label, return the first possible cat
	QString lastCat(fAddressAppInfo.category.name[15]);
	if (lastCat.isEmpty()) return cats.first();
	return QString::null;
}
void AbbrowserConduit::_setCategory(Addressee & abEntry, QString cat)
{
	if ( (!cat.isEmpty()))
	// &&  (cat!=QString(fAddressAppInfo.category.name[0])) )
		abEntry.insertCategory(cat);
}



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



#ifdef DEBUG
void AbbrowserConduit::showAddressee(const Addressee & abAddress)
{
	FUNCTIONSETUP;
	DEBUGCONDUIT << "\tAbbrowser Contact Entry" << endl;
	if (abAddress.isEmpty()) {
		DEBUGCONDUIT<< "\t\tEMPTY"<<endl;
		return;
	}
	DEBUGCONDUIT << "\t\tLast name = " << abAddress.familyName() << endl;
	DEBUGCONDUIT << "\t\tFirst name = " << abAddress.givenName() << endl;
	DEBUGCONDUIT << "\t\tCompany = " << abAddress.organization() << endl;
	DEBUGCONDUIT << "\t\tJob Title = " << abAddress.title() << endl;
	DEBUGCONDUIT << "\t\tNote = " << abAddress.note() << endl;
	DEBUGCONDUIT << "\t\tHome phone = " << abAddress.phoneNumber(PhoneNumber::Home).number() << endl;
	DEBUGCONDUIT << "\t\tWork phone = " << abAddress.phoneNumber(PhoneNumber::Work).number() << endl;
	DEBUGCONDUIT << "\t\tMobile phone = " << abAddress.phoneNumber(PhoneNumber::Cell).number() << endl;
	DEBUGCONDUIT << "\t\tEmail = " << abAddress.preferredEmail() << endl;
	DEBUGCONDUIT << "\t\tFax = " << getFax(abAddress).number() << endl;
	DEBUGCONDUIT << "\t\tPager = " << abAddress.phoneNumber(PhoneNumber::Pager).number() << endl;
	DEBUGCONDUIT << "\t\tCategory = " << abAddress.categories().first() << endl;
}



void AbbrowserConduit::showPilotAddress(PilotAddress *pilotAddress)
{
	FUNCTIONSETUP;
	DEBUGCONDUIT << "\tPilot Address" << endl;
	if (!pilotAddress) {
		DEBUGCONDUIT<< "\t\tEMPTY"<<endl;
		return;
	}
	DEBUGCONDUIT << "\t\tLast name = " << pilotAddress->getField(entryLastname) << endl;
	DEBUGCONDUIT << "\t\tFirst name = " << pilotAddress->getField(entryFirstname) << endl;
	DEBUGCONDUIT << "\t\tCompany = " << pilotAddress->getField(entryCompany) << endl;
	DEBUGCONDUIT << "\t\tJob Title = " << pilotAddress->getField(entryTitle) << endl;
	DEBUGCONDUIT << "\t\tNote = " << pilotAddress->getField(entryNote) << endl;
	DEBUGCONDUIT << "\t\tHome phone = " << pilotAddress->getPhoneField(PilotAddress::eHome, false) << endl;
	DEBUGCONDUIT << "\t\tWork phone = " << pilotAddress->getPhoneField(PilotAddress::eWork, false) << endl;
	DEBUGCONDUIT << "\t\tMobile phone = " << pilotAddress->getPhoneField(PilotAddress::eMobile, false) << endl;
	DEBUGCONDUIT << "\t\tEmail = " << pilotAddress->getPhoneField(PilotAddress::eEmail, false) << endl;
	DEBUGCONDUIT << "\t\tFax = " << pilotAddress->getPhoneField(PilotAddress::eFax, false) << endl;
	DEBUGCONDUIT << "\t\tPager = " << pilotAddress->getPhoneField(PilotAddress::ePager, false) << endl;
	DEBUGCONDUIT << "\t\tOther = " << pilotAddress->getPhoneField(PilotAddress::eOther, false) << endl;
	DEBUGCONDUIT << "\t\tCategory = " << pilotAddress->getCategoryLabel() << endl;
}
#endif


void AbbrowserConduit::showAdresses(Addressee &pcAddr, PilotAddress *backupAddr,
	PilotAddress *palmAddr)
{
#ifdef DEBUG
	DEBUGCONDUIT << "abEntry:" << endl;
	showAddressee(pcAddr);
	DEBUGCONDUIT << "pilotAddress:" << endl;
	showPilotAddress(palmAddr);
	DEBUGCONDUIT << "backupAddress:" << endl;
	showPilotAddress(backupAddr);
	DEBUGCONDUIT << "------------------------------------------------" << endl;
#else
	Q_UNUSED(pcAddr);
	Q_UNUSED(backupAddr);
#endif
}



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



/* virtual */ bool AbbrowserConduit::exec()
{
	FUNCTIONSETUP;
	DEBUGCONDUIT<<id_conduit_address<<endl;

	_prepare();

	fFirstSync = false;
	// Database names probably in latin1.
	if(!openDatabases(QString::fromLatin1("AddressDB"), &fFirstSync))
	{
		emit logError(i18n("Unable to open the addressbook databases on the handheld."));
		return false;
	}
	_getAppInfo();
	if(!_loadAddressBook())
	{
		emit logError(i18n("Unable to open the addressbook."));
		return false;
	}
	fFirstSync = fFirstSync || (aBook->begin() == aBook->end());

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

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

	if (!isFirstSync())
		allIds=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. */

	QTimer::singleShot(0, this, SLOT(slotPalmRecToPC()));

	return true;
}



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

	if (getSyncDirection()==SyncAction::eCopyPCToHH)
	{
		abiter = aBook->begin();
		QTimer::singleShot(0, this, SLOT(slotPCRecToPalm()));
		return;
	}

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

	if(!palmRec)
	{
		abiter = aBook->begin();
		QTimer::singleShot(0, this, SLOT(slotPCRecToPalm()));
		return;
	}

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

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

	PilotAddress*backupAddr=0L;
	if (backupRec) backupAddr=new PilotAddress(fAddressAppInfo, backupRec);
	PilotAddress*palmAddr=0L;
	if (palmRec) palmAddr=new PilotAddress(fAddressAppInfo, palmRec);

	syncAddressee(e, backupAddr, palmAddr);

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

	QTimer::singleShot(0, this, SLOT(slotPalmRecToPC()));
}



void AbbrowserConduit::slotPCRecToPalm()
{
	FUNCTIONSETUP;

	if ( (getSyncDirection()==SyncAction::eCopyHHToPC) ||
		abiter == aBook->end() || (*abiter).isEmpty() )
	{
		pilotindex = 0;
		QTimer::singleShot(0, this, SLOT(slotDeletedRecord()));
		return;
	}

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

	abiter++;

	// If marked as archived, don't sync!
	if (isArchived(ad))
	{
#ifdef DEBUG
		DEBUGCONDUIT << fname << ": address with id " << ad.uid() <<
			" marked archived, so don't sync." << endl;
#endif
		QTimer::singleShot(0, this, SLOT(slotPCRecToPalm()));
		return;
	}


	QString recID(ad.custom(appString, idString));
	bool ok;
	recordid_t rid = recID.toLong(&ok);
	if (recID.isEmpty() || !ok || !rid)
	{
		// it's a new item(no record ID and not inserted by the Palm -> PC sync), so add it
		syncAddressee(ad, 0L, 0L);
		QTimer::singleShot(0, this, 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))
	{
#ifdef DEBUG
		DEBUGCONDUIT << ": address with id " << rid << " already synced." << endl;
#endif
		QTimer::singleShot(0, this, 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(fAddressAppInfo, backupRec);
	if(!backupRec || isFirstSync() || !_equal(backupAddr, ad)  )
	{
		palmRec = fDatabase->readRecordById(rid);
		PilotAddress*palmAddr=0L;
		if (palmRec) palmAddr= new PilotAddress(fAddressAppInfo, palmRec);
		syncAddressee(ad, backupAddr, palmAddr);
		// update the id just in case it changed
		if (palmRec) rid=palmRec->getID();
		KPILOT_DELETE(palmRec);
		KPILOT_DELETE(palmAddr);
	}
	KPILOT_DELETE(backupAddr);
	KPILOT_DELETE(backupRec);
	syncedIds.append(rid);
	// done with the sync process, go on with the next one:
	QTimer::singleShot(0, this, SLOT(slotPCRecToPalm()));
}



void AbbrowserConduit::slotDeletedRecord()
{
	FUNCTIONSETUP;

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

	// already synced, so skip this record:
	if(syncedIds.contains(backupRec->getID()))
	{
		KPILOT_DELETE(backupRec);
		QTimer::singleShot(0, this, SLOT(slotDeletedRecord()));
		return;
	}

	QString uid = addresseeMap[backupRec->getID()];
	Addressee e = aBook->findByUid(uid);
	PilotRecord*palmRec=fDatabase->readRecordById(backupRec->getID());
	PilotAddress*backupAddr=0L;
	if (backupRec) backupAddr=new PilotAddress(fAddressAppInfo, backupRec);
	PilotAddress*palmAddr=0L;
	if (palmRec) palmAddr=new PilotAddress(fAddressAppInfo, palmRec);

	syncedIds.append(backupRec->getID());
	syncAddressee(e, backupAddr, palmAddr);

	KPILOT_DELETE(palmAddr);
	KPILOT_DELETE(backupAddr);
	KPILOT_DELETE(palmRec);
	KPILOT_DELETE(backupRec);
	QTimer::singleShot(0, this, SLOT(slotDeletedRecord()));
}



void AbbrowserConduit::slotDeleteUnsyncedPCRecords()
{
	FUNCTIONSETUP;
	if (getSyncDirection()==SyncAction::eCopyHHToPC)
	{
		QStringList uids;
		RecordIDList::iterator it;
		QString 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()))
			{
#ifdef DEBUG
				DEBUGCONDUIT<<"Deleting addressee "<<(*abit).realName()<<" from PC (is not on HH, and syncing with HH->PC direction)"<<endl;
#endif
				abChanged = true;
				// TODO: Can I really remove the current iterator???
				aBook->removeAddressee(*abit);
			}
		}
	}
	QTimer::singleShot(0, this, SLOT(slotDeleteUnsyncedHHRecords()));
}



void AbbrowserConduit::slotDeleteUnsyncedHHRecords()
{
	FUNCTIONSETUP;
	if (getSyncDirection()==SyncAction::eCopyPCToHH)
	{
		RecordIDList ids=fDatabase->idList();
		RecordIDList::iterator it;
		for ( it = ids.begin(); it != ids.end(); ++it )
		{
			if (!syncedIds.contains(*it))
			{
#ifdef DEBUG
				DEBUGCONDUIT<<"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);
			}
		}
	}
	QTimer::singleShot(0, this, 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();
	}
	KPILOT_DELETE(fDatabase);
	KPILOT_DELETE(fLocalDatabase);
	_saveAddressBook();
	emit syncDone(this);
}



/*********************************************************************
              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;
showAdresses(pcAddr, backupAddr, palmAddr);

	if (getSyncDirection()==SyncAction::eCopyPCToHH)
	{
		if (pcAddr.isEmpty())
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"0a "<<endl;
#endif
			return _deleteAddressee(pcAddr, backupAddr, palmAddr);
		}
		else
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"0b "<<endl;
#endif
			return _copyToHH(pcAddr, backupAddr, palmAddr);
		}
	}

	if (getSyncDirection()==SyncAction::eCopyHHToPC)
	{
#ifdef DEBUG
			DEBUGCONDUIT<<"0c "<<endl;
#endif
		if (!palmAddr)
			return _deleteAddressee(pcAddr, backupAddr, palmAddr);
		else
			return _copyToPC(pcAddr, backupAddr, palmAddr);
	}

	if ( !backupAddr || isFirstSync() )
	{
#ifdef DEBUG
			DEBUGCONDUIT<<"1"<<endl;
#endif
		/*
		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 && isArchived(pcAddr) )
		{
			return true;
		}
		else if (!palmAddr && !pcAddr.isEmpty())
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"1a"<<endl;
#endif
			// PC->HH
			bool res=_copyToHH(pcAddr, 0L, 0L);
			return res;
		}
		else if (!palmAddr && pcAddr.isEmpty())
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"1b"<<endl;
#endif
			// everything's empty -> ERROR
			return false;
		}
		else if ( (isDeleted(palmAddr) || isArchived(palmAddr)) && pcAddr.isEmpty())
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"1c"<<endl;
#endif
			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())
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"1d"<<endl;
#endif
			// CR (ERROR)
			return _smartMergeAddressee(pcAddr, 0L, palmAddr);
		}
		else if (pcAddr.isEmpty())
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"1e"<<endl;
#endif
			// HH->PC
			return _copyToPC(pcAddr, 0L, palmAddr);
		}
		else
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"1f"<<endl;
#endif
			// Conflict Resolution
			return _smartMergeAddressee(pcAddr, 0L, palmAddr);
		}
	} // !backupAddr
	else
	{
#ifdef DEBUG
			DEBUGCONDUIT<<"2"<<endl;
#endif
		/*
		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) )
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"2a"<<endl;
#endif
			if (_equal(backupAddr, pcAddr) || pcAddr.isEmpty())
			{
				return _deleteAddressee(pcAddr, backupAddr, 0L);
			}
			else
			{
				return _smartMergeAddressee(pcAddr, backupAddr, 0L);
			}
		}
		else if (pcAddr.isEmpty())
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"2b"<<endl;
#endif
			if (*palmAddr == *backupAddr)
			{
				return _deleteAddressee(pcAddr, backupAddr, palmAddr);
			}
			else
			{
				return _smartMergeAddressee(pcAddr, backupAddr, palmAddr);
			}
		}
		else if (_equal(palmAddr, pcAddr))
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"2c"<<endl;
#endif
			// update Backup, update ID of PC if neededd
			return _writeBackup(palmAddr);
		}
		else if (_equal(backupAddr, pcAddr))
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"2d"<<endl;
			DEBUGCONDUIT<<"Flags: "<<palmAddr->getAttrib()<<", isDeleted="<<
				isDeleted(palmAddr)<<", isArchived="<<isArchived(palmAddr)<<endl;
#endif
			if (isDeleted(palmAddr))
				return _deleteAddressee(pcAddr, backupAddr, palmAddr);
			else
				return _copyToPC(pcAddr, backupAddr, palmAddr);
		}
		else if (*palmAddr == *backupAddr)
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"2e"<<endl;
#endif
			return _copyToHH(pcAddr, backupAddr, palmAddr);
		}
		else
		{
#ifdef DEBUG
			DEBUGCONDUIT<<"2f"<<endl;
#endif
			// 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(fAddressAppInfo);
		paddrcreated=true;
	}
	_copy(paddr, pcAddr);
#ifdef DEBUG
	DEBUGCONDUIT<<"palmAddr->id="<<paddr->getID()<<", pcAddr.ID="<<
		pcAddr.custom(appString, idString)<<endl;
#endif

	if(_savePalmAddr(paddr, pcAddr))
	{
#ifdef DEBUG
		DEBUGCONDUIT<<"Vor _saveAbEntry, palmAddr->id="<<
		paddr->getID()<<", pcAddr.ID="<<pcAddr.custom(appString, idString)<<endl;
#endif
		_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;
	}
#ifdef DEBUG
	showPilotAddress(palmAddr);
#endif
	_copy(pcAddr, palmAddr);
	_savePCAddr(pcAddr, backupAddr, palmAddr);
	_writeBackup(palmAddr);
	return true;
}



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


#ifdef DEBUG
	showPilotAddress(backup);
#endif
	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->getID())) syncedIds.append(palmAddr->getID());
		palmAddr->makeDeleted();
		PilotRecord *pilotRec = palmAddr->pack();
		pilotRec->makeDeleted();
		pilotindex--;
		fDatabase->writeRecord(pilotRec);
		fLocalDatabase->writeRecord(pilotRec);
		syncedIds.append(pilotRec->getID());
		KPILOT_DELETE(pilotRec);
	}
	else if (backupAddr)
	{
		if (!syncedIds.contains(backupAddr->getID())) syncedIds.append(backupAddr->getID());
		backupAddr->makeDeleted();
		PilotRecord *pilotRec = backupAddr->pack();
		pilotRec->makeDeleted();
		pilotindex--;
		fLocalDatabase->writeRecord(pilotRec);
		syncedIds.append(pilotRec->getID());
		KPILOT_DELETE(pilotRec);
	}
	if (!pcAddr.isEmpty())
	{
#ifdef DEBUG
		DEBUGCONDUIT << fname << " removing " << pcAddr.formattedName() << endl;
#endif
		abChanged = true;
		aBook->removeAddressee(pcAddr);
	}
	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;

#ifdef DEBUG
	DEBUGCONDUIT << "Saving to pilot " << palmAddr->id()
		<< " " << palmAddr->getField(entryFirstname)
		<< " " << palmAddr->getField(entryLastname)<< endl;
#endif

	PilotRecord *pilotRec = palmAddr->pack();
	recordid_t pilotId = fDatabase->writeRecord(pilotRec);
#ifdef DEBUG
	DEBUGCONDUIT<<"PilotRec nach writeRecord ("<<pilotId<<": ID="<<pilotRec->getID()<<endl;
#endif
	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)) syncedIds.append(pilotId);
	}

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

	return false;
}



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

#ifdef DEBUG
	DEBUGCONDUIT<<"Before _savePCAddr, pcAddr.custom="<<pcAddr.custom(appString, idString)<<endl;
#endif
	if(!pcAddr.custom(appString, idString).isEmpty())
	{
		addresseeMap.insert(pcAddr.custom(appString, idString).toLong(), pcAddr.uid());
	}

	aBook->insertAddressee(pcAddr);

	abChanged = true;
	return true;
}



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


int AbbrowserConduit::_compare(const QString & str1, const QString & str2) const
{
//	FUNCTIONSETUP;
	if(str1.isEmpty() && str2.isEmpty()) return 0;
	else return str1.compare(str2);
}


bool AbbrowserConduit::_equal(const PilotAddress *piAddress, const Addressee &abEntry,
	enum eqFlagsType flags) const
{
	FUNCTIONSETUP;
	// empty records are never equal!
	if (!piAddress) return false;
	if (abEntry.isEmpty()) return false;
	//  Archived records match anything so they won't be copied to the HH again
	if (flags & eqFlagsFlags)
		if (isArchived(piAddress) && isArchived(abEntry) ) return true;

	if (flags & eqFlagsName)
	{
		if(_compare(abEntry.familyName(), piAddress->getField(entryLastname)))
			return false;
		if(_compare(abEntry.givenName(), piAddress->getField(entryFirstname)))
			return false;
		if(_compare(abEntry.title(), piAddress->getField(entryTitle)))
			return false;
		if(_compare(abEntry.organization(), piAddress->getField(entryCompany)))
			return false;
	}
	if (flags & eqFlagsNote)
		if(_compare(abEntry.note(), piAddress->getField(entryNote)))
			return false;

	if (flags & eqFlagsNote)
	{
		QString cat = _getCatForHH(abEntry.categories(), piAddress->getCategoryLabel());
		if(_compare(cat, piAddress->getCategoryLabel())) return false;
	}

	if (flags & eqFlagsPhones)
	{
		if(_compare(abEntry.phoneNumber(PhoneNumber::Work).number(),
			piAddress->getPhoneField(PilotAddress::eWork, false))) return false;
		if(_compare(abEntry.phoneNumber(PhoneNumber::Home).number(),
			piAddress->getPhoneField(PilotAddress::eHome, false))) return false;
		if(_compare(getOtherField(abEntry),
			piAddress->getPhoneField(PilotAddress::eOther, false))) return false;
		if(_compare(abEntry.preferredEmail(),
			piAddress->getPhoneField(PilotAddress::eEmail, false))) return false;
		if(_compare(getFax(abEntry).number(),
			piAddress->getPhoneField(PilotAddress::eFax, false))) return false;
		if(_compare(abEntry.phoneNumber(PhoneNumber::Cell).number(),
			piAddress->getPhoneField(PilotAddress::eMobile, false))) return false;
	}

	if (flags & eqFlagsAdress)
	{
		KABC::Address address = getAddress(abEntry);
		if(_compare(address.street(), piAddress->getField(entryAddress)))
			return false;
		if(_compare(address.locality(), piAddress->getField(entryCity)))
			return false;
		if(_compare(address.region(), piAddress->getField(entryState)))
			return false;
		if(_compare(address.postalCode(), piAddress->getField(entryZip)))
			return false;
		if(_compare(address.country(), piAddress->getField(entryCountry)))
			return false;
	}

	if (flags & eqFlagsCustom)
	{
		if(_compare(getCustomField(abEntry, 0),
			piAddress->getField(entryCustom1))) return false;
		if(_compare(getCustomField(abEntry, 1),
			piAddress->getField(entryCustom2))) return false;
		if(_compare(getCustomField(abEntry, 2),
			piAddress->getField(entryCustom3))) return false;
		if(_compare(getCustomField(abEntry, 3),
			piAddress->getField(entryCustom4))) return false;
	}

	// if any side is marked archived, but the other is not, the two
	// are not equal.
	if (flags & eqFlagsFlags)
		if (isArchived(piAddress) || isArchived(abEntry) ) return false;

	return true;
}



void AbbrowserConduit::_copy(PilotAddress *toPilotAddr, Addressee &fromAbEntry)
{
	FUNCTIONSETUP;
	if (!toPilotAddr) return;

	toPilotAddr->setAttrib(toPilotAddr->getAttrib() & ~(dlpRecAttrDeleted));

	// don't do a reset since this could wipe out non copied info
	//toPilotAddr->reset();
	toPilotAddr->setField(entryLastname, fromAbEntry.familyName());
	QString firstAndMiddle = fromAbEntry.givenName();
	if(!fromAbEntry.additionalName().isEmpty()) firstAndMiddle += CSL1(" ") + fromAbEntry.additionalName();
	toPilotAddr->setField(entryFirstname, firstAndMiddle);
	toPilotAddr->setField(entryCompany, fromAbEntry.organization());
	toPilotAddr->setField(entryTitle, fromAbEntry.title());
	toPilotAddr->setField(entryNote, fromAbEntry.note());

	// do email first, to ensure its gets stored
	toPilotAddr->setPhoneField(PilotAddress::eEmail, fromAbEntry.preferredEmail(), false);
	toPilotAddr->setPhoneField(PilotAddress::eWork,
		fromAbEntry.phoneNumber(PhoneNumber::Work).number(), false);
	toPilotAddr->setPhoneField(PilotAddress::eHome,
		fromAbEntry.phoneNumber(PhoneNumber::Home).number(), false);
	toPilotAddr->setPhoneField(PilotAddress::eMobile,
		fromAbEntry.phoneNumber(PhoneNumber::Cell).number(), false);
	toPilotAddr->setPhoneField(PilotAddress::eFax, getFax(fromAbEntry).number(), false);
	toPilotAddr->setPhoneField(PilotAddress::ePager,
		fromAbEntry.phoneNumber(PhoneNumber::Pager).number(), false);
	toPilotAddr->setPhoneField(PilotAddress::eOther, getOtherField(fromAbEntry), false);
	toPilotAddr->setShownPhone(PilotAddress::eMobile);

	KABC::Address homeAddress = getAddress(fromAbEntry);
	_setPilotAddress(toPilotAddr, homeAddress);

	// Process the additional entries from the Palm(the palm database app block tells us the name of the fields)
	toPilotAddr->setField(entryCustom1, getCustomField(fromAbEntry, 0));
	toPilotAddr->setField(entryCustom2, getCustomField(fromAbEntry, 1));
	toPilotAddr->setField(entryCustom3, getCustomField(fromAbEntry, 2));
	toPilotAddr->setField(entryCustom4, getCustomField(fromAbEntry, 3));

	toPilotAddr->setCategory(_getCatForHH(fromAbEntry.categories(), toPilotAddr->getCategoryLabel()));

	if (isArchived(fromAbEntry))
		toPilotAddr->makeArchived();
	else
		toPilotAddr->setAttrib(toPilotAddr->getAttrib() & ~(dlpRecAttrArchived));
}



void AbbrowserConduit::_setPilotAddress(PilotAddress *toPilotAddr, const KABC::Address & abAddress)
{
	toPilotAddr->setField(entryAddress, abAddress.street());
	toPilotAddr->setField(entryCity, abAddress.locality());
	toPilotAddr->setField(entryState, abAddress.region());
	toPilotAddr->setField(entryZip, abAddress.postalCode());
	toPilotAddr->setField(entryCountry, abAddress.country());
}



void AbbrowserConduit::_copyPhone(Addressee &toAbEntry,
			      PhoneNumber phone, QString palmphone)
{
	if(!palmphone.isEmpty())
	{
		phone.setNumber(palmphone);
		toAbEntry.insertPhoneNumber(phone);
	}
	else
	{
		toAbEntry.removePhoneNumber(phone);
	}
}



void AbbrowserConduit::_copy(Addressee &toAbEntry, PilotAddress *fromPiAddr)
{
	FUNCTIONSETUP;
	if (!fromPiAddr) return;
	// copy straight forward values
	toAbEntry.setFamilyName(fromPiAddr->getField(entryLastname));
	toAbEntry.setGivenName(fromPiAddr->getField(entryFirstname));
	toAbEntry.setOrganization(fromPiAddr->getField(entryCompany));
	toAbEntry.setTitle(fromPiAddr->getField(entryTitle));
	toAbEntry.setNote(fromPiAddr->getField(entryNote));

	// copy the phone stuff
	toAbEntry.removeEmail(toAbEntry.preferredEmail());
	toAbEntry.insertEmail(fromPiAddr->getPhoneField(PilotAddress::eEmail, false), true);

	_copyPhone(toAbEntry,
		toAbEntry.phoneNumber(PhoneNumber::Home),
		fromPiAddr->getPhoneField(PilotAddress::eHome, false));
	_copyPhone(toAbEntry,
		toAbEntry.phoneNumber(PhoneNumber::Work),
		fromPiAddr->getPhoneField(PilotAddress::eWork, false));
	_copyPhone(toAbEntry,
		toAbEntry.phoneNumber(PhoneNumber::Cell),
		fromPiAddr->getPhoneField(PilotAddress::eMobile, false));
	_copyPhone(toAbEntry,
		getFax(toAbEntry),
		fromPiAddr->getPhoneField(PilotAddress::eFax, false));
	_copyPhone(toAbEntry,
		toAbEntry.phoneNumber(PhoneNumber::Pager),
		fromPiAddr->getPhoneField(PilotAddress::ePager, false));
	setOtherField(toAbEntry, fromPiAddr->getPhoneField(PilotAddress::eOther, false));

	KABC::Address homeAddress = getAddress(toAbEntry);
	homeAddress.setStreet(fromPiAddr->getField(entryAddress));
	homeAddress.setLocality(fromPiAddr->getField(entryCity));
	homeAddress.setRegion(fromPiAddr->getField(entryState));
	homeAddress.setPostalCode(fromPiAddr->getField(entryZip));
	homeAddress.setCountry(fromPiAddr->getField(entryCountry));
	toAbEntry.insertAddress(homeAddress);

	setCustomField(toAbEntry, 0, fromPiAddr->getField(entryCustom1));
	setCustomField(toAbEntry, 1, fromPiAddr->getField(entryCustom2));
	setCustomField(toAbEntry, 2, fromPiAddr->getField(entryCustom3));
	setCustomField(toAbEntry, 3, fromPiAddr->getField(entryCustom4));

	// copy the fromPiAddr pilot id to the custom field KPilot_Id;
	// pilot id may be zero(since it could be new) but couldn't hurt
	// to even assign it to zero; let's us know what state the
	// toAbEntry is in
	toAbEntry.insertCustom(appString, idString, QString::number(fromPiAddr->getID()));


	int cat = fromPiAddr->getCat();
	QString category;
	if (0 < cat && cat <= 15) category = fAddressAppInfo.category.name[cat];
	_setCategory(toAbEntry, category);
#ifdef DEBUG
	showAddressee(toAbEntry);
#endif
	if (isArchived(fromPiAddr))
		makeArchived(toAbEntry);
}



/*********************************************************************
 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.
*/
QString AbbrowserConduit::_smartMergeString(const QString &pc, const QString & backup,
	const QString & 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 QString::null;
		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;
	}

#ifdef DEBUG
	DEBUGCONDUIT<<"pc="<<pc<<", backup="<<backup<<", palm="<<
		palm<<", ConfRes="<<confRes<<endl;
	DEBUGCONDUIT<<"Use conflict resolution :"<<confRes<<
		", PC="<<SyncAction::ePCOverrides<<endl;
#endif
	switch(confRes) {
		case SyncAction::ePCOverrides: return pc; break;
		case SyncAction::eHHOverrides: return palm; break;
		case SyncAction::ePreviousSyncOverrides: return backup; break;
		default: break;
	}
	return QString::null;
}



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):(QString::null), \
		(palmAddr)?(palmAddr->palmfield):(QString::null), \
		(backupAddr)?(backupAddr->palmfield):(QString::null) ))
#define appendAddr(desc, abfield, palmfield) \
	appendGen(desc, abfield, getField(palmfield))
#define appendGenPhone(desc, abfield, palmfield) \
	appendGen(desc, abfield, getPhoneField(PilotAddress::palmfield, false))
#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.title(), entryTitle);
	appendAddr(i18n("Note"), pcAddr.note(), entryNote);
	appendAddr(i18n("Custom 1"), getCustomField(pcAddr, 0), entryCustom1);
	appendAddr(i18n("Custom 2"), getCustomField(pcAddr, 1), entryCustom2);
	appendAddr(i18n("Custom 3"), getCustomField(pcAddr, 2), entryCustom3);
	appendAddr(i18n("Custom 4"), getCustomField(pcAddr, 3), entryCustom4);
	appendPhone(i18n("Work Phone"), Work, eWork);
	appendPhone(i18n("Home Phone"), Home, eHome);
	appendPhone(i18n("Mobile Phone"), Cell, eMobile);
	appendGenPhone(i18n("Fax"), getFax(pcAddr).number(), eFax);
	appendPhone(i18n("Pager"), Pager, ePager);
	appendGenPhone(i18n("Other"), getOtherField(pcAddr), eOther);
	appendGenPhone(i18n("Email"), pcAddr.preferredEmail(), eEmail);

	KABC::Address abAddress = getAddress(pcAddr);
	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);

	appendGen(i18n("Category"),
		_getCatForHH(pcAddr.categories(), (palmAddr)?(palmAddr->getCategoryLabel()):(QString::null)),
		getCategoryLabel());

#undef appendGen
#undef appendAddr
#undef appendGenPhone
#undef appendPhone

	return true;
}



bool AbbrowserConduit::_applyResolutionTable(ResolutionTable*tab, Addressee &pcAddr,
	PilotAddress *backupAddr, PilotAddress *palmAddr)
{
	FUNCTIONSETUP;
	if (!tab) return false;
	if (!palmAddr) {
#ifdef DEBUG
		DEBUGCONDUIT<<"Empty palmAddr after conf res. ERROR!!!!"<<endl;
#endif
		kdWarning()<<"Empty palmAddr after conf res. ERROR!!!!"<<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(setCustomField(pcAddr, abfield, item->fResolved), palmfield)
#define SETGENPHONE(abfield, palmfield) \
	if (item) { \
		abfield; \
		palmAddr->setPhoneField(PilotAddress::palmfield, item->fResolved, false); \
	}\
	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(Title, 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(setFax(pcAddr, item->fResolved), eFax);
	SETPHONEFIELD(Pager, ePager);
	SETGENPHONE(setOtherField(pcAddr, item->fResolved), eOther);

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

	KABC::Address abAddress = getAddress(pcAddr);
	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(item->fResolved);
		_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)
	{
		QString 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(appString, 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:
			_copy(pcAddr, backupAddr);
			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(fAddressAppInfo);
				pAddrCreated=true;
			}
			result &= _applyResolutionTable(&tab, pcAddr, backupAddr, pAddr);
showAdresses(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) )
	{
		QString id(addresseeMap[pilotAddress.id()]);
#ifdef DEBUG
		DEBUGCONDUIT << fname << ": PilotRecord has id " << pilotAddress.id() << ", mapped to " << id << endl;
#endif
		if(!id.isEmpty())
		{
			Addressee res(aBook->findByUid(id));
			if(!res.isEmpty()) return res;
#ifdef DEBUG
			DEBUGCONDUIT << fname << ": PilotRecord has id " << pilotAddress.id() << ", but could not be found in the addressbook" << endl;
#endif
		}
	}

	for(AddressBook::Iterator iter = aBook->begin(); iter != aBook->end(); ++iter)
	{
		Addressee abEntry = *iter;
		QString recID(abEntry.custom(appString, 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;
		}
	}
#ifdef DEBUG
	DEBUGCONDUIT << fname << ": Could not find any addressbook enty matching " << pilotAddress.getField(entryLastname) << endl;
#endif
	return Addressee();
}

