/* memofile-conduit.cc			KPilot
**
** Copyright (C) 2004-2007 by Jason 'vanRijn' Kasper
**
** This file does the actual conduit work.
*/

/*
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU Lesser General Public License as published by
** the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser 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"

// Only include what we really need:
// First UNIX system stuff, then std C++,
// then TQt, then KDE, then local includes.
//
//

#include <time.h>  // required by pilot-link includes

#include <pi-memo.h>

#include "pilotMemo.h"

#include <tqfile.h>
#include <tqdir.h>
#include <tqtextcodec.h>

#include <kconfig.h>
#include <kdebug.h>

#include "pilotRecord.h"
#include "pilotSerialDatabase.h"
#include "memofile-factory.h"
#include "memofile-conduit.h"
#include "memofileSettings.h"


/**
 * Our workhorse.  This is the main driver for the conduit.
 */
MemofileConduit::MemofileConduit(KPilotLink *d,
                                 const char *n,
                                 const TQStringList &l) :
		ConduitAction(d,n,l),
		_DEFAULT_MEMODIR(TQDir::homeDirPath() + CSL1("/MyMemos")),
		fMemoAppInfo(0L),
		_memofiles(0L)
{
	FUNCTIONSETUP;
	fConduitName=i18n("Memofile");
	fMemoList.setAutoDelete(true);
}

MemofileConduit::~MemofileConduit()
{
	FUNCTIONSETUP;
	KPILOT_DELETE(_memofiles);
}

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

	setFirstSync( false );
	// try new format first...
	// DEBUGKPILOT << fname << ": trying new format database first." << endl;
	bool _open = false;
	/*
	_open = openDatabases(CSL1("MemosDB-PMem"));
	if(!_open) {
		DEBUGKPILOT << fname << ": unable to open new format database. trying old one." << endl;
	*/
		_open = openDatabases(CSL1("MemoDB"));
	/*
	} else {
		DEBUGKPILOT << fname << ": able to open new format database." << endl;
	}
	*/

	if(!_open) {
		emit logError(i18n("Unable to open the memo databases on the handheld."));
		DEBUGKPILOT << fname << ": unable to open new or old format database." << endl;
		return false;
	}

	readConfig();

	if (! initializeFromPilot()) {
		emit logError(i18n("Cannot initialize from pilot."));
		return false;
	}

	_memofiles = new Memofiles(fCategories, *fMemoAppInfo,
		_memo_directory, *fCtrHH);
	if (! _memofiles || ! _memofiles->isReady()) {
		emit logError(i18n("Cannot initialize the memo files from disk."));
		return false;
	}

	fCtrPC->setStartCount(_memofiles->count());

	setFirstSync( _memofiles->isFirstSync() );
	addSyncLogEntry(i18n(" Syncing with %1.").arg(_memo_directory));

	if ( (syncMode() == SyncAction::SyncMode::eCopyHHToPC) || _memofiles->isFirstSync() ) {
		addSyncLogEntry(i18n(" Copying Pilot to PC..."));
		DEBUGKPILOT << fname << ": copying Pilot to PC." << endl;
		copyHHToPC();
	} else if ( syncMode() == SyncAction::SyncMode::eCopyPCToHH ) {
		DEBUGKPILOT << fname << ": copying PC to Pilot." << endl;
		addSyncLogEntry(i18n(" Copying PC to Pilot..."));
		copyPCToHH();
	} else {
		DEBUGKPILOT << fname << ": doing regular sync." << endl;
		addSyncLogEntry(i18n(" Doing regular sync..."));
		sync();
	}

	cleanup();

	return delayDone();
}

bool MemofileConduit::readConfig()
{
	FUNCTIONSETUP;

	TQString dir(MemofileConduitSettings::directory());
	if (dir.isEmpty()) {
		dir = _DEFAULT_MEMODIR;

		DEBUGKPILOT << fname
			<< ": no directory given to us.  defaulting to: ["
			<< _DEFAULT_MEMODIR
			<< "]" << endl;
	}

	_memo_directory = dir;
	_sync_private = MemofileConduitSettings::syncPrivate();


	DEBUGKPILOT << fname
		<< ": Settings... "
		<< "  directory: [" << _memo_directory
		<< "], first sync: [" << isFirstSync()
		<< "], sync private: [" << _sync_private
		<< "]" << endl;

	return true;

}

bool MemofileConduit::setAppInfo()
{
	FUNCTIONSETUP;

	// reset our category mapping from the filesystem
	MemoCategoryMap map = _memofiles->readCategoryMetadata();

	if (map.count() <=0) {
		DEBUGKPILOT << fname
			<< ": category metadata map is empty, nothing to do." << endl;
		return true;
	}

	fCategories = map;

	for (unsigned int i = 0; i < Pilot::CATEGORY_COUNT; i++)
	{
		if (fCategories.contains(i)) {
			fMemoAppInfo->setCategoryName(i,fCategories[i]);
		}
	}

	if (fDatabase)
	{
		fMemoAppInfo->writeTo(fDatabase);
	}
	if (fLocalDatabase)
	{
		fMemoAppInfo->writeTo(fLocalDatabase);
	}

	return true;
}

bool MemofileConduit::getAppInfo()
{
	FUNCTIONSETUP;

	KPILOT_DELETE(fMemoAppInfo);
	fMemoAppInfo = new PilotMemoInfo(fDatabase);
	fMemoAppInfo->dump();
	return true;
}


/**
 * Methods related to getting set up from the Pilot.
 */

bool MemofileConduit::initializeFromPilot()
{

	if (!getAppInfo()) return false;

	if (!loadPilotCategories()) return false;

	return true;
}

bool MemofileConduit::loadPilotCategories()
{
	FUNCTIONSETUP;

	fCategories.clear();

	TQString _category_name;
	int _category_id=0;
	int _category_num=0;

	for (unsigned int i = 0; i < Pilot::CATEGORY_COUNT; i++)
	{
		_category_name = fMemoAppInfo->categoryName(i);
		if (!_category_name.isEmpty())
		{
			_category_name = Memofiles::sanitizeName( _category_name );
			_category_id   = fMemoAppInfo->categoryInfo()->ID[i];
			_category_num  = i;
			fCategories[_category_num] = _category_name;

			DEBUGKPILOT << fname
				<< ": Category #"
				<< _category_num
				<< " has ID "
				<< _category_id
				<< " and name "
				<<_category_name << endl;
		}
	}
	return true;
}

/**
 *  Read all memos in from Pilot.
 */
void MemofileConduit::getAllFromPilot()
{
	FUNCTIONSETUP;

	DEBUGKPILOT << fname
		<< ": Database has " << fDatabase->recordCount()
		<< " records." << endl;

	fMemoList.clear();

	int currentRecord = 0;
	PilotRecord *pilotRec;
	PilotMemo *memo = 0;

	while ((pilotRec = fDatabase->readRecordByIndex(currentRecord)) != NULL) {
		if ((!pilotRec->isSecret()) || _sync_private) {
			memo = new PilotMemo(pilotRec);
			fMemoList.append(memo);

			DEBUGKPILOT << fname
				<< ": Added memo: ["
				<< currentRecord
				<< "], id: ["
				<< memo->id()
				<< "], category: ["
				<< fCategories[memo->category()]
				<< "], title: ["
				<< memo->getTitle()
				<< "]" << endl;
		} else {
			DEBUGKPILOT << fname
				<< ": Skipped secret record: ["
				<< currentRecord
				<< "], title: ["
				<< memo->getTitle()
				<< "]" << endl;
		}

		KPILOT_DELETE(pilotRec);

		currentRecord++;
	}

	DEBUGKPILOT << fname
		<< ": read: [" << fMemoList.count()
		<< "] records from palm." << endl;
}

/**
 *  Read all modified memos in from Pilot.
 */
void MemofileConduit::getModifiedFromPilot()
{
	FUNCTIONSETUP;

	fMemoList.clear();

	int currentRecord = 0;
	PilotRecord *pilotRec;
	PilotMemo *memo = 0;

	while ((pilotRec = fDatabase->readNextModifiedRec()) != NULL) {
		memo = new PilotMemo(pilotRec);
		// we are syncing to both our filesystem and to the local
		// database, so take care of the local database here
		if (memo->isDeleted()) {
			fLocalDatabase->deleteRecord(memo->id());
		} else {
			fLocalDatabase->writeRecord(pilotRec);
		}

		if ((!pilotRec->isSecret()) || _sync_private) {
			fMemoList.append(memo);

			DEBUGKPILOT << fname
				<< ": modified memo id: ["
				<< memo->id()
				<< "], title: ["
				<< memo->getTitle()
				<< "]" << endl;
		} else {
			DEBUGKPILOT << fname
				<< ": skipped secret modified record id: ["
				<< memo->id()
				<< "], title: ["
				<< memo->getTitle()
				<< "]" << endl;
		}

		KPILOT_DELETE(pilotRec);

		currentRecord++;
	}

	DEBUGKPILOT << fname
		<< ": read: [" << fMemoList.count()
		<< "] modified records from palm." << endl;
}


/* slot */ void MemofileConduit::process()
{
	FUNCTIONSETUP;

	DEBUGKPILOT << fname << ": Now in state " << fActionStatus << endl;
}


void MemofileConduit::listPilotMemos()
{
	FUNCTIONSETUP;

	PilotMemo *memo;
	for ( memo = fMemoList.first(); memo; memo = fMemoList.next() ) {
		TQString _category_name = fCategories[memo->category()];

		DEBUGKPILOT << fConduitName
			<< ": listing record id: [" << memo->id()
			<< "] category id: [" << memo->category()
			<< "] category name: [" << _category_name
			<< "] title: [" << memo->getTitle()
			<< "]" << endl;
	}
}

bool MemofileConduit::copyHHToPC()
{
	FUNCTIONSETUP;

	getAllFromPilot();

	_memofiles->eraseLocalMemos();

	_memofiles->setPilotMemos(fMemoList);

	_memofiles->save();

	return true;

}

bool MemofileConduit::copyPCToHH()
{
	FUNCTIONSETUP;

	// set category info from the filesystem, if we can.
	// Note: This will reset both fCategories and fMemoAppInfo, so
	//       after this, we need to reinitialize our memofiles object...
	setAppInfo();

	// re-create our memofiles helper...
	KPILOT_DELETE(_memofiles);
	_memofiles = new Memofiles(fCategories, *fMemoAppInfo,
		_memo_directory, *fCtrHH);

	_memofiles->load(true);

	TQPtrList<Memofile> memofiles = _memofiles->getAll();

	Memofile * memofile;

	for ( memofile = memofiles.first(); memofile; memofile = memofiles.next() ) {
		writeToPilot(memofile);
	}

	_memofiles->save();

	// now that we've copied from the PC to our handheld, remove anything extra from the
	// handheld...
	deleteUnsyncedHHRecords();

	return true;

}

void MemofileConduit::deleteUnsyncedHHRecords()
{
	FUNCTIONSETUP;
	if ( syncMode()==SyncMode::eCopyPCToHH )
	{
		Pilot::RecordIDList ids=fDatabase->idList();
		Pilot::RecordIDList::iterator it;
		for ( it = ids.begin(); it != ids.end(); ++it )
		{
			if (!_memofiles->find(*it))
			{
				DEBUGKPILOT << fname
					<< "Deleting record with ID "<< *it <<" from handheld "
					<< "(is not on PC, and syncing with PC->HH direction)"
					<< endl;
				fDatabase->deleteRecord(*it);
				fLocalDatabase->deleteRecord(*it);
			}
		}
	}
}

int MemofileConduit::writeToPilot(Memofile * memofile)
{
	FUNCTIONSETUP;

	int oldid = memofile->id();

	PilotRecord *r = memofile->pack();

	if (!r) {
		DEBUGKPILOT << fname
			<< ": ERROR: [" << memofile->toString()
			<< "] could not be written to the pilot."
			<< endl;
		return -1;
	}

	int newid = fDatabase->writeRecord(r);
	fLocalDatabase->writeRecord(r);

	KPILOT_DELETE(r);

	memofile->setID(newid);

	TQString status;
	if (oldid <=0) {
		fCtrHH->created();
		status = "new to pilot";
	} else {
		fCtrHH->updated();
		status = "updated";
	}

	DEBUGKPILOT << fname
		<< ": memofile: [" << memofile->toString()
		<< "] written to the pilot, [" << status << "]."
		<< endl;

	return newid;
}

void MemofileConduit::deleteFromPilot(PilotMemo * memo)
{
	FUNCTIONSETUP;

	PilotRecord *r = memo->pack();
	if (r) {
		r->setDeleted(true);
		fDatabase->writeRecord(r);
		fLocalDatabase->writeRecord(r);
	}
	KPILOT_DELETE(r);

	fCtrHH->deleted();

	DEBUGKPILOT << fname
		<< ": memo: [" << memo->getTitle()
		<< "] deleted from the pilot."
		<< endl;
}

bool MemofileConduit::sync()
{
	FUNCTIONSETUP;

	_memofiles->load(false);

	getModifiedFromPilot();

	PilotMemo *memo;
	for ( memo = fMemoList.first(); memo; memo = fMemoList.next() ) {
		_memofiles->addModifiedMemo(memo);
	}

	TQPtrList<Memofile> memofiles = _memofiles->getModified();

	Memofile *memofile;
	for ( memofile = memofiles.first(); memofile; memofile = memofiles.next() ) {
		if (memofile->isDeleted()) {
			deleteFromPilot(memofile);
		} else {
			writeToPilot(memofile);
		}
	}

	_memofiles->save();

	return true;
}

void MemofileConduit::cleanup()
{
	FUNCTIONSETUP;

	fDatabase->resetSyncFlags();
	fDatabase->cleanup();
	fLocalDatabase->resetSyncFlags();
	fLocalDatabase->cleanup();

	fCtrPC->setEndCount(_memofiles->count());
}


#include "memofile-conduit.moc"

