/*

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

Copyright 2012-14 Miranda NG project,
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"

DBSignature dbSignatureU = { "Miranda NG DBu", 0x1A }; // unencrypted database
DBSignature dbSignatureE = { "Miranda NG DBe", 0x1A }; // encrypted database
DBSignature dbSignatureIM = { "Miranda ICQ DB", 0x1A };
DBSignature dbSignatureSA = { "Miranda ICQ SA", 0x1A };
DBSignature dbSignatureSD = { "Miranda ICQ SD", 0x1A };

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);
}

CDb3Mmap::CDb3Mmap(const TCHAR *tszFileName, int iMode) :
	m_hDbFile(INVALID_HANDLE_VALUE),
	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);

	InitializeCriticalSection(&m_csDbAccess);

	SYSTEM_INFO sinf;
	GetSystemInfo(&sinf);
	m_ChunkSize = sinf.dwAllocationGranularity;

	m_codePage = CallService(MS_LANGPACK_GETCODEPAGE, 0, 0);
	m_hModHeap = HeapCreate(0, 0, 0);
}

CDb3Mmap::~CDb3Mmap()
{
	// destroy modules
	HeapDestroy(m_hModHeap);

	// destroy map
	KillTimer(NULL, m_flushBuffersTimerId);
	if (m_pDbCache) {
		FlushViewOfFile(m_pDbCache, 0);
		UnmapViewOfFile(m_pDbCache);
	}

	DestroyServiceFunction(hService);
	UnhookEvent(hHook);

	if (m_crypto)
		m_crypto->destroy();

	if (m_hMap)
		CloseHandle(m_hMap);

	// update profile last modified time
	if (!m_bReadOnly) {
		DWORD bytesWritten;
		SetFilePointer(m_hDbFile, 0, NULL, FILE_BEGIN);
		WriteFile(m_hDbFile, &dbSignatureU, 1, &bytesWritten, NULL);
	}

	if (m_hDbFile != INVALID_HANDLE_VALUE)
		CloseHandle(m_hDbFile);

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

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

	DeleteCriticalSection(&m_csDbAccess);

	DestroyDbInstance(this);
	mir_free(m_tszProfileName);

	free(m_pNull);
}

int CDb3Mmap::Load(bool bSkipInit)
{
	log0("DB logging running");

	DWORD dummy = 0, dwMode = FILE_SHARE_READ;
	if (m_bShared)
		dwMode |= FILE_SHARE_WRITE;
	if (m_bReadOnly)
		m_hDbFile = CreateFile(m_tszProfileName, GENERIC_READ, dwMode, NULL, OPEN_EXISTING, 0, NULL);
	else
		m_hDbFile = CreateFile(m_tszProfileName, GENERIC_READ | GENERIC_WRITE, dwMode, NULL, OPEN_ALWAYS, 0, NULL);

	if (m_hDbFile == INVALID_HANDLE_VALUE)
		return EGROKPRF_CANTREAD;

	if (!ReadFile(m_hDbFile, &m_dbHeader, sizeof(m_dbHeader), &dummy, NULL)) {
		CloseHandle(m_hDbFile);
		return EGROKPRF_CANTREAD;
	}

	if (!bSkipInit) {
		if (InitMap()) return 1;
		if (InitModuleNames()) return 1;
		if (InitCrypt()) return EGROKPRF_CANTREAD;

		// everything is ok, go on
		if (!m_bReadOnly) {
			if (m_dbHeader.version < DB_095_1_VERSION)
				return EGROKPRF_CANTREAD;

			// 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 CDb3Mmap::Create()
{
	m_hDbFile = CreateFile(m_tszProfileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
	return (m_hDbFile == INVALID_HANDLE_VALUE);
}

int CDb3Mmap::PrepareCheck(int *error)
{
	int ret = CheckDbHeaders(true);
	switch (ret) {
	case ERROR_SUCCESS:
	case EGROKPRF_OBSOLETE:
		*error = ret;
		break;

	default:
		return ret;
	}

	InitMap();
	InitModuleNames();
	if ((ret = InitCrypt()) != ERROR_SUCCESS)
		return ret;

	return ERROR_SUCCESS;
}

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

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

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

static CheckWorker Workers[6] = 
{
	&CDb3Mmap::WorkInitialChecks,
	&CDb3Mmap::WorkModuleChain,
	&CDb3Mmap::WorkUser,
	&CDb3Mmap::WorkContactChain,
	&CDb3Mmap::WorkAggressive,
	&CDb3Mmap::WorkFinalTasks
};

int CDb3Mmap::Start(DBCHeckCallback *callback)
{
	cb = callback;
	ReMap(0);
	return ERROR_SUCCESS;
}

int CDb3Mmap::CheckDb(int phase, int firstTime)
{
	if (phase >= SIZEOF(Workers))
		return ERROR_OUT_OF_PAPER;

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

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