/***************************************************************************
                          irmcsyncthreadbase.cpp  -  description
                             -------------------
    begin                : Sat Feb 14 2003
    copyright            : (C) 2003 by Simone Gotti
    email                : simone.gotti@email.it
 ***************************************************************************/

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

#include <qobject.h>
#include <qapplication.h>
#include <qthread.h>

#include <kdebug.h>
#include <qregexp.h>
#include <qfile.h>
#include <qdir.h>
#include <idhelper.h>

#include "obexclient.h"
#include "irmcsyncthreadbase.h"


#include "changelog.h"
#include "device.h"

using namespace KSync;


/**
   Constructor.
*/
IrMCSyncThreadBase::IrMCSyncThreadBase(const QString& appName, const QString& fileType, const QString& extension, const IrMCSyncHelper::Device::Model phoneModel, QObject* reciver )
		: ClientThread( reciver ) {
	this->fileType = fileType;
	this->extension = extension;
	this->appName = appName;
	deviceDir = "telecom/" + fileType;

  // Create the device helper
  mDeviceHelper = new IrMCSyncHelper::Device(phoneModel);

	//FIXME
	//get the sync anchor type from the devinfo.txt file
	SyncAnchorType = ChangeCounters;
}

/**
   Destructor.
*/
IrMCSyncThreadBase::~IrMCSyncThreadBase() {}

void IrMCSyncThreadBase::setObexClient(Client* obexClient) {
	obex = obexClient;
}

bool IrMCSyncThreadBase::readSyncees() {
	kdDebug() << "readSyncees()" << endl;

	// Remove All the entries in the Syncees */
	while(SyncEntry *entry = mSyncee->firstEntry()) {
		mSyncee->removeEntry(entry);
	}

	// SYNC START!!!

	// get the Initial chengelog "0.log" because we have to retrieve SN a DID infos
	getInitialCalendarChangelog();
	// Now that we have databaseDir we can initialize the uidHelper.
	// FIXME Debugging puorpose: needs to be removed.
	printuids();
	uidHelper = new KonnectorUIDHelper (databaseDir);
	// FIXME Debugging puorpose: needs to be removed.
	printuids();
	uidHelper->save();
	printuids();

	bool firstSync = checkAndSetDirs();

	if(firstSync) {
		// We have to get the current changecounter from the device
		getCurrentChangeCounter();
	} else {
		// We have to get the current changelog with the local saved changecounter
		getCalendarChangelog();
		// Now that we have retrieved the changelog we can update the changecounter with the device value
		getCurrentChangeCounter();
	}
	// Now we can get the records from the device
	getRecords();
	//Now we save the ChangeCounter.
	saveChangeCounter();

	SyncEntry* entry;
	// for all entries set the parent Syncee to mSyncee
	for ( entry = mSyncee->firstEntry(); entry != 0; entry = mSyncee->nextEntry() ) {
		entry->setSyncee(mSyncee);
	}

  uidHelper->save();
  printuids();

	return true;
}

bool IrMCSyncThreadBase::writeSyncees() {
	kdDebug() << "writeSyncees()" << endl;

	writeAddedModifiedEntries();

	return true;
}

QByteArray IrMCSyncThreadBase::getFile(const QString& fileName) {
	kdDebug() << "=====================================================================================" << endl;
	kdDebug() << "Getting file from remote device: " << fileName << endl;
	if(obex->get(fileName)) {
		kdDebug() << "OK" << endl;
		QByteArray data = obex->getData();
		return data;
	}
	else {
		kdDebug() << "Error Retrieving file!" << endl;
		return 0;
	}
}

void IrMCSyncThreadBase::saveChangeCounter() {
	QString changeCounterStr = QString::number(changeCounter);
	QFile file(databaseDir + "/" + "cc.log");
	file.open( IO_WriteOnly );
	file.writeBlock(changeCounterStr.ascii(), changeCounterStr.length());
	file.close();

}

void IrMCSyncThreadBase::loadChangeCounter() {
	QString changeCounterStr;
	QFile file(databaseDir + "/" + "cc.log");
	file.open( IO_ReadOnly );
	file.readLine(changeCounterStr, 1000);
	file.close();
	changeCounter = changeCounterStr.toLong();
}

void IrMCSyncThreadBase::getInitialCalendarChangelog() {
	kdDebug() << "Getting Initial Changelog (0.log)" << endl;

	QByteArray data = getFile(deviceDir +"/luid/0.log");
	changelog = new Changelog(data, Changelog::ChangeCounters);

	//mSyncee->setFirstSync( false );

	serialNumber = changelog->serialNumber();
	databaseId = changelog->databaseId();

	databaseDir = QDir::homeDirPath() + "/.kitchensync/IrMCSyncKonnector/" + serialNumber + "/" + databaseId;

	kdDebug() << "Serial Number: " << serialNumber << endl;
	kdDebug() << "Database ID: " << databaseId << endl;
}

bool IrMCSyncThreadBase::checkAndSetDirs() {
	bool firstsync;
	QDir di( QDir::homeDirPath() + "/.kitchensync/IrMCSyncKonnector/" + serialNumber + "/" + databaseId );
	/*
	 * if our meta path exists do not recreate it
	 */
	if ( di.exists() ) {
		firstsync = false;
		kdDebug() << "Dirs Already existing!" << endl;
	} else {
		firstsync = true;
		kdDebug() << "Creating Dirs: " << ("/.kitchensync/IrMCSyncKonnector/" + serialNumber + "/" + databaseId) << endl;
		// Create the dirs
		QDir dir;
		dir.mkdir(QDir::homeDirPath() + "/.kitchensync/");
		dir.mkdir(QDir::homeDirPath() + "/.kitchensync/IrMCSyncKonnector/");
		dir.mkdir(QDir::homeDirPath() + "/.kitchensync/IrMCSyncKonnector/" + serialNumber);
		dir.mkdir(QDir::homeDirPath() + "/.kitchensync/IrMCSyncKonnector/" + serialNumber + "/" + databaseId);
	}
	return firstsync;
}

void IrMCSyncThreadBase::getCalendarChangelog() {
	kdDebug() << "getCalendarChangelog()" << endl;
	// open the local file containing the current change counter
	loadChangeCounter();

	QString fileName = deviceDir + "/luid/" + QString::number(changeCounter) + ".log";
	kdDebug() << "Getting Changelog: " << changeCounter << ".log" << endl;
	QByteArray data = getFile(fileName);
	changelog = new Changelog(data, Changelog::ChangeCounters);
}

void IrMCSyncThreadBase::getCurrentChangeCounter() {
	kdDebug() << "Getting current ChangeCounter from the device: " << endl;
	QString fileName = deviceDir + "/luid/cc.log";
	QByteArray data = getFile(fileName);

	changeCounter = QString(data).toLong();

	// Extract the changecounter from the file
	QTextStream changelogStream(data, IO_ReadOnly );
	QString cc = changelogStream.readLine();
	changeCounter = cc.toLong();

	kdDebug() << "changeCounter = " << changeCounter << endl;
}

void IrMCSyncThreadBase::recreateFullSyncee(bool slowsync) {
	// Now we can recreate the full Syncee (Calendar/AddressBook)!!!
	// We get the various entries finding matching file in the serialnumber/database dir.
	kdDebug() << "Recreating the FULL Syncee!" << endl;

	QDir dir(databaseDir);
	dir.setFilter( QDir::Files | QDir::Hidden | QDir::NoSymLinks );

	const QFileInfoList *fileInfolist = dir.entryInfoList();
	QFileInfoListIterator fileInfoIt( *fileInfolist );
	QFileInfo *fi;

	while ( (fi = fileInfoIt.current()) != 0 ) {
		QString fileName = fi->fileName();
		kdDebug() << "Found file: " << fileName << endl;

		QRegExp rxp1 = QRegExp("^"+appName+"-");
		QRegExp rxp2 = QRegExp("~$");
		if(fileName.contains( rxp1 ) && !fileName.contains(rxp2)) {
			kdDebug() << "Selected file: " << fileName << endl;

			KSync::Syncee* syncee = localGetEntry(databaseDir + "/" + fileName);

			KSync::SyncEntry* curEntry;
			for( curEntry = syncee->firstEntry(); curEntry; curEntry = syncee->nextEntry() ) {
				kdDebug() << "Added entry : " << curEntry->id() << endl;
				if(slowsync) {
					// Change the state to "Modified", N.B. The STATE is NEEDED by kitchensyc > 3.3.0
					curEntry->setState(SyncEntry::Modified);
				} else {
					// Change the state to "Undefined", N.B. The STATE is NEEDED by kitchensyc > 3.3.0
					curEntry->setState(SyncEntry::Undefined);
				}
				// The entries are cloned so "entry" isn't direct changed
				mSyncee->addEntry ( curEntry->clone() );
			}
			delete syncee;
		}
		++fileInfoIt;
	}
}

void IrMCSyncThreadBase::getRecords() {
	kdDebug() << "=====================================================================================" << endl;
	kdDebug() << "getRecords()" << endl;
	kdDebug() << "SLOW SYNC: " << changelog->meta() << endl;

	if (changelog->meta() == true) {
		// SLOW SYNC!!! or SEMISLOW SYNC
		// We have to get all the Records!!!
		kdDebug() << "Getting Whole Calendar" << endl;
		/* Set the Sync Mode to MetaLess */
		//mSyncee->setSyncMode( KSync::Syncee::MetaLess );

		QString calendarFileName = "telecom/" + fileType + "." + extension;
		getSyncees(calendarFileName);
		// We recreate the full Calendar setting ALL entries to Modified (because we don't know their state)
		recreateFullSyncee(changelog->meta());

		// Now we have ALL the LUIDS, if there's a file with a different LUID
		// we have to delete it because it was deleted on the device
	} else {
		// FAST SYNC!!!
		QString recordFileName;
		QString LUID, Uid, dstFileName;
		// FIRST: We have to read all the saved entries and set them as undefined.
		recreateFullSyncee(changelog->meta());
		// THEN we can retrieve the added/modifed ones and delete the deleted ones

		// Set the Sync Mode to MetaMode
		//mSyncee->setSyncMode( KSync::Syncee::MetaMode );

		kdDebug() << "Getting Record List" << endl;
		QValueList<Changelog::record> mRecordsList = changelog->recordsList();

		kdDebug() << "Getting curRecord Iterator" << endl;

		QValueList<Changelog::record>::Iterator curRecordIt;
		for(curRecordIt = mRecordsList.begin(); curRecordIt != mRecordsList.end(); ++curRecordIt) {
			recordFileName = QString(deviceDir + "/luid/") + (*curRecordIt).LUID() + "." + extension ;

			if((*curRecordIt).isModified()) {
				kdDebug() << "Getting " << recordFileName << endl;
				SyncEntry* curEntry = getSyncees(recordFileName);
				SyncEntry* savedEntry = 0;
				if(curEntry)
					savedEntry = mSyncee->findEntry(curEntry->id());
				if(!savedEntry) {
					kdDebug() << "The entry isn't already in mSyncee: mark as \"Added\"" << endl;
					if(curEntry) {
						curEntry->setState(SyncEntry::Added);
						mSyncee->addEntry ( curEntry->clone() );
					}
				} else {
					kdDebug() << "The entry is already in mSyncee: mark as \"Modified\" and substitute" << endl;
					curEntry->setState(SyncEntry::Modified);
					mSyncee->removeEntry (savedEntry);
					mSyncee->addEntry ( curEntry->clone() );
				}
			} else if((*curRecordIt).isDeleted() || (*curRecordIt).isHardDeleted()) {
				kdDebug() << "=====================================================================================" << endl;
				kdDebug() << "Record with LUID: " << (*curRecordIt).LUID() << " Reported by the changelog as DELETED" << endl;
				QString Uid = uidHelper->kdeId(appName, (*curRecordIt).LUID());
        // Now we have the UID and we can remove from the idhelper.
        // We'll save the idhelper only at the end of the ReadSyncee.
        while ( uidHelper->kdeId(appName, (*curRecordIt).LUID()) != QString::null || uidHelper->kdeId(appName, Uid) != QString::null )
          uidHelper->removeId(appName, (*curRecordIt).LUID());
				dstFileName = GenerateFullPathFileName((*curRecordIt).LUID());
				if( QFile::exists(dstFileName) ) {
					SyncEntry* savedEntry = mSyncee->findEntry(Uid);
					if(!savedEntry) {
						kdDebug() << "This can't HAPPEN!!! while trying to get an entry to delete from mSyncee" << endl;
					} else {
						savedEntry->setState(SyncEntry::Removed);
					}
					// Remove the entry from the disk
					kdDebug() << "Deleting: " << dstFileName << endl;
					QDir dir;
					dir.remove(dstFileName);
				} else {
					kdDebug() << "Error! There's no file for LUID: " << (*curRecordIt).LUID() << endl;
					//kdDebug() << "Creating an empty entry, so we can set his status to \"Deleted\"" << endl;
					kdDebug() << "This should'n happen... Perhaps was it deleted by hand?" << endl;
					/*SyncEntry* curEntry = new SyncEntry(mSyncee);
					curEntry->setState(SyncEntry::Deleted);
					mSyncee->addEntry ( curEntry );*/
				}
			}
		}
	}
}

SyncEntry* IrMCSyncThreadBase::getSyncees(const QString& fileName) {
	kdDebug() << "=====================================================================================" << endl;
	kdDebug() << "getSyncees()" << endl;

	int entriesCounter = 0;
	QString lastEntryFileName;

	QByteArray data = getFile(fileName);

	QString dataStr(data);
	kdDebug() << dataStr << endl;

	QStringList XIRMCLUIDs;
	KSync::Syncee* syncee;
	syncee = rawDataToSyncee(data, XIRMCLUIDs);

	// And now put them in the full calendar
	QStringList::Iterator XIRMCLUIDsIt = XIRMCLUIDs.begin();
	KSync::SyncEntry* curEntry;
	for( curEntry = syncee->firstEntry(); curEntry; curEntry = syncee->nextEntry() ) {
		// Get the associated LUID
		if(XIRMCLUIDsIt == XIRMCLUIDs.end()) {
			kdDebug() << "Error: More Events then LUIDs!!!" << endl;
		}
		//Save the entry in the serialnumber/database dir.
		QString LUID, Uid, dstFileName;
		LUID = (*XIRMCLUIDsIt);

		// We check if there's a local file with the same LUID -> SEMISLOW SYNC
		// so we can get the real Uid and not the device LUID
		Uid = uidHelper->kdeId(appName, LUID);

		kdDebug() << "LUID = " << LUID << "     UID = " << Uid << endl;
		dstFileName = GenerateFullPathFileName(LUID);
		localPutEntry(curEntry, dstFileName);

		lastEntryFileName = dstFileName;

		*XIRMCLUIDsIt++;
		entriesCounter++;
	}
	// When we get a single entry and not the whole phonebook, we return it.
	if(entriesCounter == 1) {
		syncee = localGetEntry(lastEntryFileName);
		curEntry = syncee->firstEntry()->clone();
		delete syncee;
		return curEntry;
	} else
		delete syncee;
		return 0;
}


void IrMCSyncThreadBase::writeAddedModifiedEntries() {
	kdDebug() << "=====================================================================================" << endl;
	kdDebug() << "writeAddedModifiedEntries()" << endl;
	QValueList<QObexHeader> headers;
	KSync::SyncEntry *curEntry;

	QString LUID, Uid, changeCounterStr, timeStamp;
	QString dstFileName = QString::null;
	for( curEntry = mSyncee->firstEntry(); curEntry; curEntry = mSyncee->nextEntry() ) {
		kdDebug() << "in the cycle" << endl;
		QByteArray data;
		data = syncEntryToRawData(curEntry, Uid);
		kdDebug() << "Received Entry with Uid: " << Uid << " data:" << endl;
		kdDebug() << QString(data) << endl;
		//Get the file in the local database backup and delete it.
		if(curEntry->wasAdded() || curEntry->wasModified()) {
			LUID = uidHelper->konnectorId(appName, Uid);
			if(LUID != QString::null && QFile::exists(GenerateFullPathFileName(LUID))) {
				dstFileName=GenerateFullPathFileName(LUID);
				kdDebug() << "Entry with Uid: " << Uid << " was Modified" << endl;
				kdDebug() << "Filename: " << dstFileName << endl;
				QByteArray localdata;
				QFile file(dstFileName);
				file.open(IO_ReadOnly);
				localdata = file.readAll();
				file.close();
				kdDebug() << "Local Entry with Uid: " << Uid << " data:" << endl;
				kdDebug() << localdata << endl;
				kdDebug() << QString(localdata) << endl;

				// The file is modified
				// We read the existing file and check if the entries are the same or not
				// because has no sense to save a record modified by us.
				KSync::Syncee* syncee = localGetEntry(dstFileName);
				if(!syncee->firstEntry()->equals(curEntry)) {

					kdDebug() << "Writing Modified entry." << endl;

					headers = sendToDevice(deviceDir + "/luid/" + LUID + "." + extension, data , true);
					kdDebug() << "sendToDevice() end" << endl;
					getInfosFromReturnedHeader(headers, LUID, changeCounterStr, timeStamp);
					kdDebug() << "getInfosFromReturnedHeader() end" << endl;
					kdDebug() << "LUID = " << LUID << endl;

					localPutEntry(curEntry, dstFileName);

					changeCounter = changeCounterStr.toLong();
					saveChangeCounter();
				} else {
					kdDebug() << "The saved record is equal to the mSyncee record." << endl;
				}
			} else {
				kdDebug() << "Entry with Uid: " << Uid << " was Added" << endl;
				//The file must be added
				kdDebug() << "Writing Added entry." << endl;
				//Send the file to the remote device, get the returned LUID and ChangeCounter/TimeStamp
				headers = sendToDevice(deviceDir + "/luid/." + extension, data , false);
				kdDebug() << "sendToDevice() end" << endl;
				getInfosFromReturnedHeader(headers, LUID, changeCounterStr, timeStamp);
				kdDebug() << "getInfosFromReturnedHeader() end" << endl;
				kdDebug() << "LUID = " << LUID << endl;
				
				dstFileName=GenerateFullPathFileName(LUID);
				localPutEntry(curEntry, dstFileName);

				changeCounter = changeCounterStr.toLong();
				saveChangeCounter();
				printuids();
				uidHelper->addId(appName, LUID, Uid);
				uidHelper->save();
				printuids();
			}
		} else if(curEntry->wasRemoved()) {
			// The file is modified
			kdDebug() << "Entry with Uid: " << Uid << " was Removed" << endl;
			LUID = uidHelper->konnectorId(appName, Uid);
			kdDebug() << "The assigned LUID is: " << LUID << endl;
			
			kdDebug() << "Removing Deleted entry." << endl;

			QByteArray data; // An empty bytearray
			headers = sendToDevice(deviceDir + "/luid/" + LUID + "." + extension, data , true);
			getInfosFromReturnedHeader(headers, LUID, changeCounterStr, timeStamp);
			kdDebug() << "getInfosFromReturnedHeader() end" << endl;
			kdDebug() << "LUID = " <<  LUID << endl;

			dstFileName=GenerateFullPathFileName(LUID);
			kdDebug() << "Deleting: " << dstFileName << endl;
			QDir dir;
			dir.remove(dstFileName);

			changeCounter = changeCounterStr.toLong();
			saveChangeCounter();
			printuids();
      while ( uidHelper->kdeId(appName, Uid) != QString::null || uidHelper->kdeId(appName, LUID) != QString::null)
        uidHelper->removeId(appName, LUID);
			uidHelper->save();
			printuids();
			
		} else {
			kdDebug() << "Entry with Uid: " << Uid << " Status Undefined (Probably it's unchanged)." << endl;
		}
	}
}

// Function used to send or delete an entry to/from the IrMC device.
// If the data is empty then we'll delete it. Otherwise we'll send it.
QValueList<QObexHeader> IrMCSyncThreadBase::sendToDevice(const QString& fileName, QByteArray& data, bool modify) {
	kdDebug() << "=====================================================================================" << endl;
	kdDebug() << "sendToDevice()" << endl;

	QValueList<QObexHeader> headers;
	// Send the file to the device
	// Set the appParam to the modified/deleted tag, with max changecounter set.
	if(modify) {
		// FIXME: The specs doesn't says what's the max changecounter value,
		// so we set it to a relatively high number.
		QString maxcc = "999999";
		QByteArray appParam(maxcc.length()+2);
		appParam[0]=0x11;
		appParam[1] = maxcc.length();
		for(uint i = 0; i < maxcc.length(); i++) {
			appParam[2+i]=maxcc[i].latin1();
		}
		QObexHeader header1(QObexHeader::AppParameters, appParam);
		headers.append(header1);
	}

	if(data.size() > 0) {
		QObexHeader header2(QObexHeader::Length, data.size());
		headers.append(header2);
		obex->setData(data);
		obex->put(fileName, headers);
	} else { // If the size is 0 than the record must be deleted
		obex->del(fileName, headers);
	}
	headers = obex->getHeaders();
	kdDebug() << "getHeaders() end" << endl;

	return headers;
}

// Analyse the returned header and get useful infos from it.
void IrMCSyncThreadBase::getInfosFromReturnedHeader(QValueList<QObexHeader> headers, QString& LUID, QString& changeCounterStr, QString& timeStamp) {
	kdDebug() << "=====================================================================================" << endl;
	kdDebug() << "getInfosFromReturnedHeader()" << endl;
	// Parse the returned AppHeader
	QValueList<QObexHeader>::iterator headersIt;

	for(headersIt = headers.begin(); headersIt != headers.end(); ++headersIt) {
		if ((*headersIt).stringHeaderId() == "AppParameters") {
			kdDebug() << "Found an AppParameters Header" << endl;

			QByteArray appHeader = (*headersIt).arrayData();

			int headerSize = appHeader.size();
			int headerPos = 0;
			int valueLength;

			QString unknow;
			while(headerPos < headerSize) {
				if(appHeader[headerPos] == 0x01) { // LUID
					kdDebug() << "Found a LUID in AppParameters Header" << endl;
					valueLength = appHeader[++headerPos];
					LUID = QString::fromAscii((char*)&appHeader[++headerPos], valueLength);
					kdDebug() << "LUID = " << LUID << endl;
					headerPos += valueLength;
				} else if(appHeader[headerPos] == 0x02) { // ChangeCounter
					valueLength = appHeader[++headerPos];
					changeCounterStr = QString::fromAscii((char*)&appHeader[++headerPos], valueLength);
					kdDebug() << "changeCounterStr = " << changeCounterStr << endl;
					headerPos += valueLength;
				} else if(appHeader[headerPos] == 0x03) { // TimeStamp
					valueLength = appHeader[++headerPos];
					timeStamp = QString::fromAscii((char*)&appHeader[++headerPos], valueLength);
					kdDebug() << "timeStamp = " << timeStamp << endl;
					headerPos += valueLength;
				} else { // Unknow response
					valueLength = appHeader[++headerPos];
					unknow = QString::fromAscii((char*)&appHeader[++headerPos], valueLength);
					kdDebug() << "unknow = " << unknow << endl;
					headerPos += valueLength;
				}
			}
		} else {
			kdDebug() << "Warning: Received and UNKNOW Header." << endl;
		}
	}
}

// Given a LUID generate a full Path for the saved entry filename
QString IrMCSyncThreadBase::GenerateFullPathFileName(const QString& LUID) {
	return QString(databaseDir + "/" + appName + "-" + LUID);
}

bool IrMCSyncThreadBase::printuids() {
	QByteArray localdata;
	QFile file(databaseDir + "/" + "konnector-ids.conf");
	file.open(IO_ReadOnly);
	localdata = file.readAll();
	file.close();
	kdDebug() << "konnector-ids.conf file data:" << endl;
	kdDebug() << QString(localdata) << endl;

	return true;
}

#include "irmcsyncthreadbase.moc"
