/*

Miranda NG: the free IM client for Microsoft* Windows*

Copyright (�) 2012-16 Miranda NG project (http://miranda-ng.org)
all portions of this codebase are copyrighted to the people
listed in contributors.txt.

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; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "commonheaders.h"

#define DBHEADER_SIGNATURE _T("KyotoCabinet")

#define CMP_UINT32(A,B) if(A!=B) return (A<B)?-1:1;

struct SettingsComparator : public Comparator
{
	SettingsComparator() {}
	virtual int32_t compare(const char *akbuf, size_t, const char *bkbuf, size_t)
	{
		DBSettingSortingKey *k1 = (DBSettingSortingKey*)akbuf, *k2 = (DBSettingSortingKey*)bkbuf;
		CMP_UINT32(k1->dwContactID, k2->dwContactID);
		CMP_UINT32(k1->dwOfsModule, k2->dwOfsModule);
		return strcmp(k1->szSettingName, k2->szSettingName);
	}
}
static _settingsComparator;

struct EventsComparator : public Comparator
{
	EventsComparator() {}
	virtual int32_t compare(const char *akbuf, size_t, const char *bkbuf, size_t)
	{
		DBEventSortingKey *k1 = (DBEventSortingKey*)akbuf, *k2 = (DBEventSortingKey*)bkbuf;
		CMP_UINT32(k1->dwContactId, k2->dwContactId);
		CMP_UINT32(k1->ts, k2->ts);
		CMP_UINT32(k1->dwEventId, k2->dwEventId);
		return 0;
	}
}
static _eventsComparator;

struct LongComparator : public Comparator
{
	LongComparator() {}
	virtual int32_t compare(const char *akbuf, size_t, const char *bkbuf, size_t)
	{
		return *(LONG*)akbuf - *(LONG*)bkbuf;
	}
}
static _longComparator;

static int ModCompare(const ModuleName *mn1, const ModuleName *mn2)
{
	return strcmp(mn1->name, mn2->name);
}

static int OfsCompare(const ModuleName *mn1, const ModuleName *mn2)
{
	return (mn1->ofs - mn2->ofs);
}

static int stringCompare2(const char *p1, const char *p2)
{
	return strcmp(p1, p2);
}

CDbxKyoto::CDbxKyoto(const TCHAR *tszFileName, int iMode) :
	m_safetyMode(true),
	m_bReadOnly((iMode & DBMODE_READONLY) != 0),
	m_bShared((iMode & DBMODE_SHARED) != 0),
	m_dwMaxContactId(1),
	m_lMods(50, ModCompare),
	m_lOfs(50, OfsCompare),
	m_lResidentSettings(50, stringCompare2)
{
	m_tszProfileName = mir_tstrdup(tszFileName);
	InitDbInstance(this);

	m_codePage = Langpack_GetDefaultCodePage();
	m_hModHeap = HeapCreate(0, 0, 0);
}

CDbxKyoto::~CDbxKyoto()
{
	delete m_evCursor;

	m_dbContacts.close();
	m_dbModules.close();
	m_dbEvents.close();
	m_dbEventsSort.close();
	m_dbSettings.close();

	// destroy modules
	HeapDestroy(m_hModHeap);

	// automatically closes all tables
	DestroyServiceFunction(hService);
	UnhookEvent(hHook);

	if (m_crypto)
		m_crypto->destroy();

	DestroyHookableEvent(hContactDeletedEvent);
	DestroyHookableEvent(hContactAddedEvent);
	DestroyHookableEvent(hSettingChangeEvent);
	DestroyHookableEvent(hEventMarkedRead);

	DestroyHookableEvent(hEventAddedEvent);
	DestroyHookableEvent(hEventDeletedEvent);
	DestroyHookableEvent(hEventFilterAddedEvent);

	DestroyDbInstance(this);
	mir_free(m_tszProfileName);
}

int CDbxKyoto::Load(bool bSkipInit)
{
	if (!bSkipInit) {
		int iFlags = TreeDB::OREADER | TreeDB::ONOREPAIR | TreeDB::OCREATE;
		if (!m_bReadOnly)
			iFlags |= TreeDB::OWRITER;

		std::string szFilename((char*)_T2A(m_tszProfileName));
		m_dbContacts.tune_map(256 * 1024);
		if (!m_dbContacts.open(szFilename + ".cnt", iFlags))
			return EGROKPRF_DAMAGED;

		m_dbModules.tune_comparator(&_longComparator);
		if (!m_dbModules.open(szFilename + ".mod", iFlags))
			return EGROKPRF_DAMAGED;

		m_dbEvents.tune_comparator(&_longComparator);
		if (!m_dbEvents.open(szFilename + ".evt", iFlags))
			return EGROKPRF_DAMAGED;

		m_dbEventsSort.tune_comparator(&_eventsComparator);
		if (!m_dbEventsSort.open(szFilename + ".evs", iFlags))
			return EGROKPRF_DAMAGED;
		
		m_dbSettings.tune_comparator(&_settingsComparator);
		if (!m_dbSettings.open(szFilename + ".set", iFlags))
			return EGROKPRF_DAMAGED;

		if (_taccess(m_tszProfileName, 0) == 0) {
			TCHAR buf[100];
			if (0 == GetPrivateProfileString(_T("Database"), _T("Signature"), _T(""), buf, SIZEOF(buf), m_tszProfileName))
				return EGROKPRF_CANTREAD;
			if (_tcscmp(buf, DBHEADER_SIGNATURE))
				return EGROKPRF_DAMAGED;

			m_dwVersion = GetPrivateProfileInt(_T("Database"), _T("Version"), 1, m_tszProfileName);
		}
		else {
			WritePrivateProfileString(_T("Database"), _T("Version"), _T("1"), m_tszProfileName);
			WritePrivateProfileString(_T("Database"), _T("Signature"), DBHEADER_SIGNATURE, m_tszProfileName);

			DWORD keyVal = 0;
			DBContact dbc = { DBCONTACT_SIGNATURE, 0, 0, 0 };
			m_dbContacts.set((LPCSTR)&keyVal, sizeof(keyVal), (LPCSTR)&dbc, sizeof(dbc));
		}

		m_evCursor = m_dbEventsSort.cursor();

		if (InitModuleNames()) return EGROKPRF_CANTREAD;
		if (InitCrypt())       return EGROKPRF_CANTREAD;

		// everything is ok, go on
		if (!m_bReadOnly) {
			// we don't need events in the service mode
			if (ServiceExists(MS_DB_SETSAFETYMODE)) {
				hContactDeletedEvent = CreateHookableEvent(ME_DB_CONTACT_DELETED);
				hContactAddedEvent = CreateHookableEvent(ME_DB_CONTACT_ADDED);
				hSettingChangeEvent = CreateHookableEvent(ME_DB_CONTACT_SETTINGCHANGED);
				hEventMarkedRead = CreateHookableEvent(ME_DB_EVENT_MARKED_READ);

				hEventAddedEvent = CreateHookableEvent(ME_DB_EVENT_ADDED);
				hEventDeletedEvent = CreateHookableEvent(ME_DB_EVENT_DELETED);
				hEventFilterAddedEvent = CreateHookableEvent(ME_DB_EVENT_FILTER_ADD);
			}
		}

		FillContacts();
	}

	return ERROR_SUCCESS;
}

int CDbxKyoto::Create(void)
{
	WritePrivateProfileString(_T("Database"), _T("Version"), _T("1"), m_tszProfileName);
	WritePrivateProfileString(_T("Database"), _T("Signature"), DBHEADER_SIGNATURE, m_tszProfileName);
	return 0;
}

int CDbxKyoto::Check(void)
{
	if (_taccess(m_tszProfileName, 0) != 0)
		return EGROKPRF_CANTREAD;

	TCHAR buf[100];
	if (0 == GetPrivateProfileString(_T("Database"), _T("Signature"), _T(""), buf, SIZEOF(buf), m_tszProfileName))
		return EGROKPRF_UNKHEADER;
	if (_tcscmp(buf, DBHEADER_SIGNATURE))
		return EGROKPRF_UNKHEADER;

	return 0;
}

int CDbxKyoto::PrepareCheck(int*)
{
	InitModuleNames();
	return InitCrypt();
}

STDMETHODIMP_(void) CDbxKyoto::SetCacheSafetyMode(BOOL bIsSet)
{
	mir_cslock lck(m_csDbAccess);
	m_safetyMode = bIsSet != 0;
}

/////////////////////////////////////////////////////////////////////////////////////////

static DWORD DatabaseCorrupted = 0;
static const TCHAR *msg = NULL;
static DWORD dwErr = 0;
static TCHAR tszPanic[] = LPGENT("Miranda has detected corruption in your database. This corruption may be fixed by DbChecker plugin. Please download it from http://miranda-ng.org/p/DbChecker/. Miranda will now shut down.");

void __cdecl dbpanic(void *)
{
	if (msg) {
		if (dwErr == ERROR_DISK_FULL)
			msg = TranslateT("Disk is full. Miranda will now shut down.");

		TCHAR err[256];
		mir_sntprintf(err, SIZEOF(err), msg, TranslateT("Database failure. Miranda will now shut down."), dwErr);

		MessageBox(0, err, TranslateT("Database Error"), MB_SETFOREGROUND | MB_TOPMOST | MB_APPLMODAL | MB_ICONWARNING | MB_OK);
	}
	else MessageBox(0, TranslateTS(tszPanic), TranslateT("Database Panic"), MB_SETFOREGROUND | MB_TOPMOST | MB_APPLMODAL | MB_ICONWARNING | MB_OK);
	TerminateProcess(GetCurrentProcess(), 255);
}

void CDbxKyoto::DatabaseCorruption(const TCHAR *text)
{
	int kill = 0;

	mir_cslockfull lck(m_csDbAccess);
	if (DatabaseCorrupted == 0) {
		DatabaseCorrupted++;
		kill++;
		msg = text;
		dwErr = GetLastError();
	}
	else {
		/* db is already corrupted, someone else is dealing with it, wait here
		so that we don't do any more damage */
		Sleep(INFINITE);
		return;
	}
	lck.unlock();

	if (kill) {
		_beginthread(dbpanic, 0, NULL);
		Sleep(INFINITE);
	}
}

///////////////////////////////////////////////////////////////////////////////
// MIDatabaseChecker

typedef int (CDbxKyoto::*CheckWorker)(int);

int CDbxKyoto::Start(DBCHeckCallback *callback)
{
	cb = callback;
	return ERROR_SUCCESS;
}

int CDbxKyoto::CheckDb(int, int)
{
	return ERROR_OUT_OF_PAPER;

	// return (this->*Workers[phase])(firstTime);
}

void CDbxKyoto::Destroy()
{
	delete this;
}