/*
dbRW

Copyright (c) 2005-2009 Robert Rainwater

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 "dbrw.h"

CRITICAL_SECTION csSettingsDb;
static HANDLE hHeap = 0, hSettingsThread = 0, hSettingsEvent = 0;
static SortedList sSettingNames, sContactSettings, sGlobalSettings, sResidentSettings;
static int mirCp = CP_ACP;

static int settings_cmpSettingNames(void *p1, void *p2);
static int settings_cmpGlobalSettings(void* p1, void* p2);
static int settings_cmpContactSettings(void *p1, void *p2);
static int settings_cmpResidentSettings(void *p1, void *p2);
static char *settings_getCachedSettingName(const char *szModuleName, const char *szSettingName);
static void settings_setCachedValueUpdateStatus(HANDLE hContact, char *szSetting, int update);
static void settings_freeCachedVariant(DBVARIANT* V);
static DBVARIANT *settings_getCachedValue(HANDLE hContact, char *szSetting, int bAllocate);
static int settings_getContactSettingWorker(HANDLE hContact, DBCONTACTGETSETTING *dbcgs, int isStatic);
static void settings_writeToDB(HANDLE hContact, const char *szModule, const char *szSetting, DBVARIANT *value);
static void settings_writeUpdatedSettings();
static unsigned __stdcall settings_threadProc(void *arg);
static int settings_isResident(char *szSetting);

typedef struct {
	char *name;
	DWORD nameHash;
	DBVARIANT value;
	int update;
} DBCachedGlobalValue;

typedef struct DBCachedContactValue_tag {
	char *name;
	DWORD nameHash;
	DBVARIANT value;
	int update;
	struct DBCachedContactValue_tag *next;
} DBCachedContactValue;

typedef struct {
	HANDLE hContact;
	HANDLE hNext;
	DBCachedContactValue *first;
} DBCachedContactValueList;

typedef struct {
    char *name;
    DWORD nameHash;
    char *module;
    DWORD moduleHash;
} DBCachedResidentSettingValue;

enum {
	SQL_SET_STMT_REPLACE=0,
	SQL_SET_STMT_DELETE,
	SQL_SET_STMT_READ,
	SQL_SET_STMT_ENUM,
	SQL_SET_STMT_ENUMMODULES,
	SQL_SET_STMT_SETTINGCHECK,
    SQL_SET_STMT_DELETECONTACT,
	SQL_SET_STMT_NUM
};
char *settings_stmts[SQL_SET_STMT_NUM] = {
	"REPLACE INTO dbrw_settings VALUES(?,?,?,?,?);",
	"DELETE FROM dbrw_settings WHERE setting = ? AND module = ? AND id = ?;",
	"SELECT type,val FROM dbrw_settings WHERE setting = ? AND module = ? AND id = ? LIMIT 1;",
	"SELECT setting from dbrw_settings where id = ? AND module = ? ORDER by setting;",
	"SELECT DISTINCT module from dbrw_settings;",
    "SELECT count(*) FROM dbrw_settings WHERE setting = ? AND module = ? AND id = ?;",
	"DELETE FROM dbrw_settings WHERE id = ?;"
};
static sqlite3_stmt *settings_stmts_prep[SQL_SET_STMT_NUM] = {0};

void settings_init() {
	InitializeCriticalSection(&csSettingsDb);
	hHeap = HeapCreate(0, 0, 0);
	ZeroMemory(&sSettingNames, sizeof(sSettingNames));
	ZeroMemory(&sContactSettings, sizeof(sContactSettings));
	ZeroMemory(&sGlobalSettings, sizeof(sGlobalSettings));
    ZeroMemory(&sResidentSettings, sizeof(sResidentSettings));
	sSettingNames.increment = 100;
	sSettingNames.sortFunc = settings_cmpSettingNames;
	sContactSettings.increment = 100;
	sContactSettings.sortFunc = settings_cmpContactSettings;
	sGlobalSettings.increment = 100;
	sGlobalSettings.sortFunc = settings_cmpGlobalSettings;
    sResidentSettings.increment = 100;
    sResidentSettings.sortFunc = settings_cmpResidentSettings;
	sql_prepare_add(settings_stmts, settings_stmts_prep, SQL_SET_STMT_NUM);
	mirCp = CallService(MS_LANGPACK_GETCODEPAGE, 0, 0);
    hSettingsEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    hSettingsThread = (HANDLE)mir_forkthreadex(settings_threadProc, 0, 0, 0);
}

void settings_destroy() {
    if (hSettingsEvent) {
        SetEvent(hSettingsEvent);
        WaitForSingleObjectEx(hSettingsThread, INFINITE, FALSE);
        CloseHandle(hSettingsThread);
    }
	settings_writeUpdatedSettings();
	HeapDestroy(hHeap);
	li.List_Destroy(&sSettingNames);
	li.List_Destroy(&sContactSettings);
	li.List_Destroy(&sGlobalSettings);
	li.List_Destroy(&sResidentSettings);
	DeleteCriticalSection(&csSettingsDb);
}

static int settings_cmpSettingNames(void *p1, void *p2) {
	return strcmp((char*)p1, (char*)p2);
}

static int settings_cmpGlobalSettings(void *p1, void *p2) {
	DBCachedGlobalValue *v1 = (DBCachedGlobalValue*)p1;
	DBCachedGlobalValue *v2 = (DBCachedGlobalValue*)p2;
	
	if (v1->nameHash!=v2->nameHash)
		return v1->nameHash-v2->nameHash;
	return strcmp(v1->name, v2->name);
}

static int settings_cmpContactSettings(void *p1, void *p2) {
	if (*(long*)p1==*(long*)p2)
		return 0;
	return *(long*)p1-*(long*)p2;
}

static int settings_cmpResidentSettings(void *p1, void *p2) {
	DBCachedResidentSettingValue *v1 = (DBCachedResidentSettingValue*)p1;
	DBCachedResidentSettingValue *v2 = (DBCachedResidentSettingValue*)p2;
	
	if (v1->nameHash!=v2->nameHash)
		return v1->nameHash-v2->nameHash;
	return strcmp(v1->name, v2->name);
}

static char *settings_getCachedSettingName(const char *szModuleName, const char *szSettingName) {
	size_t nameLen = strlen(szModuleName)+strlen(szSettingName)+2;
	int idx;
	char *szFullName = (char*)alloca(nameLen), *ret;

	mir_snprintf(szFullName, nameLen, "%s/%s", szModuleName, szSettingName);
	if (li.List_GetIndex(&sSettingNames, szFullName, &idx))
		return (char*)sSettingNames.items[idx];
	ret = (char*)HeapAlloc(hHeap, 0, nameLen);
	mir_snprintf(ret, nameLen, "%s", szFullName);
	li.List_Insert(&sSettingNames, ret, idx);
	return ret;
}

static void settings_setCachedValueUpdateStatus(HANDLE hContact, char *szSetting, int update) {
	int idx;

	if (hContact==0) {
		DBCachedGlobalValue Vtemp, *V;

		Vtemp.name = szSetting;
		Vtemp.nameHash = utils_hashString(szSetting);
		if (li.List_GetIndex(&sGlobalSettings, &Vtemp, &idx)) {
			V = (DBCachedGlobalValue*)sGlobalSettings.items[idx];
            if (settings_isResident(szSetting)) {
                V->update = 0;
            }
            else {
                V->update = update;
            }
		}
		return;
	}
	else {
		DBCachedContactValue *V;
		DBCachedContactValueList VLtemp,*VL;
		DWORD hash = utils_hashString(szSetting);

		VLtemp.hContact = hContact;
		if (li.List_GetIndex(&sContactSettings, &VLtemp, &idx)) {
			VL = (DBCachedContactValueList*)sContactSettings.items[idx];
		}
		else return;
		for (V = VL->first; V!=NULL; V=V->next)
			if ((hash==V->nameHash)&&(strcmp(V->name, szSetting)==0)) 
				break;
		if (V) {
            if (settings_isResident(szSetting)) {
                V->update = 0;
            }
            else {
                V->update = update;
            }
        }
	}
}

static void settings_freeCachedVariant(DBVARIANT* V) {
	if ((V->type==DBVT_ASCIIZ||V->type==DBVT_UTF8)&&V->pszVal!=NULL)
		HeapFree(hHeap, 0, V->pszVal);
}

static void settings_setCachedVariant(DBVARIANT *VNew, DBVARIANT *VCached) {
	char *szSave = (VCached->type==DBVT_UTF8||VCached->type==DBVT_ASCIIZ) ? VCached->pszVal : NULL;

	memcpy(VCached, VNew, sizeof(DBVARIANT));
	if ((VNew->type==DBVT_UTF8||VNew->type==DBVT_ASCIIZ )&&VNew->pszVal!=NULL) {
		if (szSave!=NULL)
			VCached->pszVal = (char*)HeapReAlloc(hHeap, 0, szSave, strlen(VNew->pszVal)+1);
		else
			VCached->pszVal = (char*)HeapAlloc(hHeap, 0, strlen(VNew->pszVal)+1);
		strcpy(VCached->pszVal, VNew->pszVal);
	}
}

static DBVARIANT *settings_getCachedValue(HANDLE hContact, char *szSetting, int bAllocate) {
	int idx;

	if (hContact==0) {
		DBCachedGlobalValue Vtemp, *V;

		Vtemp.name = szSetting;
		Vtemp.nameHash = utils_hashString(szSetting);
		if (li.List_GetIndex(&sGlobalSettings, &Vtemp, &idx)) {
			V = (DBCachedGlobalValue*)sGlobalSettings.items[idx];
			if (bAllocate==-1) {
				settings_freeCachedVariant(&V->value);
				li.List_Remove(&sGlobalSettings, idx);
				HeapFree(hHeap, 0, V);
				return NULL;
			}
		}
		else {
			if (bAllocate!=1)
				return NULL;
			V = (DBCachedGlobalValue*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(DBCachedGlobalValue));
			V->name = szSetting;
			V->nameHash = utils_hashString(szSetting);
            V->update = 0;
			li.List_Insert(&sGlobalSettings, V, idx);
		}
		return &V->value;
	}
	else {
		DBCachedContactValue *V, *V1;
		DBCachedContactValueList VLtemp,*VL;
		DWORD hash = utils_hashString(szSetting);

		VLtemp.hContact = hContact;
		if (li.List_GetIndex(&sContactSettings, &VLtemp, &idx)) {
			VL = (DBCachedContactValueList*)sContactSettings.items[idx];
		}
		else {
			if (bAllocate==-1) 
				return NULL;
			VL = (DBCachedContactValueList*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(DBCachedContactValueList));
			VL->hContact = hContact;
			li.List_Insert(&sContactSettings, VL, idx);
		}
		for (V = VL->first; V!=NULL; V=V->next)
			if ((hash==V->nameHash)&&(strcmp(V->name, szSetting)==0)) 
				break;
		if (V==NULL) {	
			if (bAllocate!=1)
				return NULL;
			V = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(DBCachedContactValue));
			V->next = VL->first;
			VL->first = V;
			V->name = szSetting;
			V->nameHash = hash;
            V->update = 0;
		}
		else if (bAllocate==-1) {
			settings_freeCachedVariant(&V->value);
			if (VL->first==V)
				VL->first = V->next;
			for (V1=VL->first; V1!=NULL; V1=V1->next) {
				if (V1->next==V) {
					V1->next = V->next;
					break;
				}
			}
			HeapFree(hHeap, 0, V);
			return NULL;
		}
		return &V->value;
	}
}

static void settings_writeToDB(HANDLE hContact, const char *szModule, const char *szSetting, DBVARIANT *value) {
	// TODO: Check the parameters
	sqlite3_bind_int(settings_stmts_prep[SQL_SET_STMT_REPLACE], 1, (int)hContact);
	sqlite3_bind_text(settings_stmts_prep[SQL_SET_STMT_REPLACE], 2, szModule, -1, SQLITE_STATIC);
	sqlite3_bind_text(settings_stmts_prep[SQL_SET_STMT_REPLACE], 3, szSetting, -1, SQLITE_STATIC);
	sqlite3_bind_int(settings_stmts_prep[SQL_SET_STMT_REPLACE], 4, (int)value->type);
	switch (value->type) {
		case DBVT_BYTE:
			sqlite3_bind_int(settings_stmts_prep[SQL_SET_STMT_REPLACE], 5, (int)value->bVal);
			break;
		case DBVT_WORD:
			sqlite3_bind_int(settings_stmts_prep[SQL_SET_STMT_REPLACE], 5, (int)value->wVal);
			break;
		case DBVT_DWORD:
			sqlite3_bind_int(settings_stmts_prep[SQL_SET_STMT_REPLACE], 5, (int)value->dVal);
			break;
		case DBVT_UTF8:
		case DBVT_ASCIIZ:
			if (value->pszVal)
				sqlite3_bind_text(settings_stmts_prep[SQL_SET_STMT_REPLACE], 5, value->pszVal, -1, SQLITE_STATIC);
			break;
		case DBVT_BLOB:
            if (value->pbVal) {
				sqlite3_bind_blob(settings_stmts_prep[SQL_SET_STMT_REPLACE], 5, value->pbVal, value->cpbVal, SQLITE_STATIC);
            }
            break;
		default:
			sql_reset(settings_stmts_prep[SQL_SET_STMT_REPLACE]);
            return;
	}
	if (sql_step(settings_stmts_prep[SQL_SET_STMT_REPLACE])!=SQLITE_DONE) {
		log2("Error writing: %s/%s", szModule, szSetting);
	}
    sql_reset(settings_stmts_prep[SQL_SET_STMT_REPLACE]);
}

static void settings_writeUpdatedSettings() {
	int idx, dbWrite = 0;
	DBCachedGlobalValue *V;
	DBCachedContactValueList *VL;
	DBCachedContactValue *VI;
	char *szTok, *szTokTmp1, *szTokTmp2;
    
	EnterCriticalSection(&csSettingsDb);
	for (idx=0; idx<sGlobalSettings.realCount; idx++) {
		V = (DBCachedGlobalValue*)sGlobalSettings.items[idx];
		if (V->update) {
			szTok = dbrw_alloc(strlen(V->name)+1);
			strcpy(szTok, V->name);
			szTokTmp1 = strtok(szTok, "/");
			if (szTokTmp1) {
				szTokTmp2 = szTok+strlen(szTokTmp1)+1;
				if (szTokTmp2) {
                    if (!dbWrite) {
                        sql_stmt_begin();
                        dbWrite = 1;
                    }
					settings_writeToDB(0, szTokTmp1, szTokTmp2, &V->value);
				}
			}
			dbrw_free(szTok);
			V->update = 0;
		}
	}
	for (idx=0; idx<sContactSettings.realCount; idx++) {
		VL = (DBCachedContactValueList*)sContactSettings.items[idx];
		for (VI=VL->first; VI!=NULL; VI=VI->next) {
			if (VI->update) {
				szTok = dbrw_alloc(strlen(VI->name)+1);
				strcpy(szTok, VI->name);
				szTokTmp1 = strtok(szTok, "/");
				if (szTokTmp1) {
					szTokTmp2 = szTok+strlen(szTokTmp1)+1;
					if (szTokTmp2) {
                        if (!dbWrite) {
                            sql_stmt_begin();
                            dbWrite = 1;
                        }
						settings_writeToDB(VL->hContact, szTokTmp1, szTokTmp2, &VI->value);
					}
				}
				dbrw_free(szTok);
				VI->update = 0;
			}
		}
	}
    if (dbWrite)
        sql_stmt_end();
	LeaveCriticalSection(&csSettingsDb);
}

static unsigned __stdcall settings_threadProc(void *arg) {
    DWORD dwWait;
    
    for(;;) {
        dwWait = WaitForSingleObjectEx(hSettingsEvent, DBRW_SETTINGS_FLUSHCACHE, TRUE);

        if (dwWait==WAIT_OBJECT_0) 
            break;
        else if(dwWait == WAIT_TIMEOUT) {
            settings_writeUpdatedSettings();
        }
        else if (dwWait == WAIT_IO_COMPLETION)
            if (Miranda_Terminated()) 
                break;
    }
    CloseHandle(hSettingsEvent);
    hSettingsEvent = NULL;
    return 0;
}

static int settings_getContactSettingWorker(HANDLE hContact, DBCONTACTGETSETTING *dbcgs, int isStatic) {
	char* szCachedSettingName;

	if (!dbcgs->szSetting||!dbcgs->szModule)
		return 1;
	
	szCachedSettingName = settings_getCachedSettingName(dbcgs->szModule, dbcgs->szSetting);
	{
		DBVARIANT *pCachedValue = settings_getCachedValue(hContact, szCachedSettingName, 0);

		if (pCachedValue!=NULL) {
			if (pCachedValue->type==DBVT_ASCIIZ||pCachedValue->type==DBVT_UTF8) {
                size_t cbOrigLen = dbcgs->pValue->cchVal;
                char *cbOrigPtr = dbcgs->pValue->pszVal;
                
                memcpy(dbcgs->pValue, pCachedValue, sizeof(DBVARIANT));
				if (isStatic) {
					size_t cbLen = 0;

					if (pCachedValue->pszVal!=NULL)
						cbLen = strlen(pCachedValue->pszVal);
					cbOrigLen--;
					dbcgs->pValue->pszVal = cbOrigPtr;
					if (cbLen<cbOrigLen)
						cbOrigLen = cbLen;
					memcpy(dbcgs->pValue->pszVal, pCachedValue->pszVal, cbOrigLen);
					dbcgs->pValue->pszVal[cbOrigLen] = 0;
					dbcgs->pValue->cchVal = (WORD)cbLen;
				}
				else {
					dbcgs->pValue->pszVal = (char*)dbrw_alloc(strlen(pCachedValue->pszVal)+1);
					strcpy(dbcgs->pValue->pszVal, pCachedValue->pszVal);
				}
			}
            else {
                memcpy(dbcgs->pValue, pCachedValue, sizeof(DBVARIANT));
            }
			return (pCachedValue->type==DBVT_DELETED) ? 1 : 0;
		}
	}
    if (settings_isResident(szCachedSettingName)) {
        return 1;
    }
	// Read from db
	sqlite3_bind_text(settings_stmts_prep[SQL_SET_STMT_READ], 1, dbcgs->szSetting, -1, SQLITE_STATIC);
	sqlite3_bind_text(settings_stmts_prep[SQL_SET_STMT_READ], 2, dbcgs->szModule, -1, SQLITE_STATIC);
	sqlite3_bind_int(settings_stmts_prep[SQL_SET_STMT_READ], 3, (int)hContact);
	if (sql_step(settings_stmts_prep[SQL_SET_STMT_READ])!=SQLITE_ROW) {
		if (dbcgs->pValue->type!=DBVT_BLOB) {
			DBVARIANT* pCachedValue = settings_getCachedValue(hContact, szCachedSettingName, 1);

			if (pCachedValue!=NULL)
				pCachedValue->type = DBVT_DELETED;
		}
		sql_reset(settings_stmts_prep[SQL_SET_STMT_READ]);
		return 1;
	}
	dbcgs->pValue->type = (int)sqlite3_column_int(settings_stmts_prep[SQL_SET_STMT_READ], 0);
	switch(dbcgs->pValue->type) {
		case DBVT_BYTE:
			dbcgs->pValue->bVal = (BYTE)sqlite3_column_int(settings_stmts_prep[SQL_SET_STMT_READ], 1);
			break;
		case DBVT_WORD:
			dbcgs->pValue->wVal = (WORD)sqlite3_column_int(settings_stmts_prep[SQL_SET_STMT_READ], 1);
			break;
		case DBVT_DWORD:
			dbcgs->pValue->dVal = (DWORD)sqlite3_column_int(settings_stmts_prep[SQL_SET_STMT_READ], 1);
			break;
		case DBVT_UTF8:
		case DBVT_ASCIIZ:
		{
			const char *p = sqlite3_column_text(settings_stmts_prep[SQL_SET_STMT_READ], 1);

			if (p!=NULL) {
				size_t len = strlen(p) + 1;		
				size_t copylen = isStatic ? (len < dbcgs->pValue->cchVal ? len : dbcgs->pValue->cchVal) : len;
				if (!isStatic) 
					dbcgs->pValue->pszVal = dbrw_alloc(len);
				memmove(dbcgs->pValue->pszVal, p, copylen);
			}
			else {
				dbcgs->pValue->pszVal = 0;
			}
			break;
		}
		case DBVT_BLOB:
		{
			size_t len = sqlite3_column_bytes(settings_stmts_prep[SQL_SET_STMT_READ], 1);
            
			if (len) {		
				size_t copylen = isStatic ? ( len < dbcgs->pValue->cpbVal ? len : dbcgs->pValue->cpbVal ) : len;
				if (!isStatic) 
					dbcgs->pValue->pbVal=dbrw_alloc(copylen);
				CopyMemory(dbcgs->pValue->pbVal, sqlite3_column_blob(settings_stmts_prep[SQL_SET_STMT_READ], 1), copylen);
                dbcgs->pValue->cpbVal = (WORD)copylen;
			}
			else {
				dbcgs->pValue = 0;
			}
		}
	}
	sql_reset(settings_stmts_prep[SQL_SET_STMT_READ]);
	// Insert in cache
	if ( dbcgs->pValue->type!=DBVT_BLOB) {
		DBVARIANT *pCachedValue = settings_getCachedValue(hContact, szCachedSettingName, 1);
		if (pCachedValue!=NULL)
			settings_setCachedVariant(dbcgs->pValue, pCachedValue);
	}
	return 0;
}

INT_PTR setting_getSetting(WPARAM wParam, LPARAM lParam) {
	DBCONTACTGETSETTING* dgs = (DBCONTACTGETSETTING*)lParam;

	EnterCriticalSection(&csSettingsDb);
	dgs->pValue->type = 0;
	if (settings_getContactSettingWorker((HANDLE)wParam, dgs, 0)) {
		LeaveCriticalSection(&csSettingsDb);
		return 1;
	}
	if (dgs->pValue->type==DBVT_UTF8 ) {
		WCHAR* tmp = mir_utf8decodeW(dgs->pValue->pszVal);
		if (tmp) {
			BOOL bUsed = FALSE;
			int  result = WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, tmp, -1, NULL, 0, NULL, &bUsed);

			mir_free(dgs->pValue->pszVal);

			if (bUsed || result == 0) {
				dgs->pValue->type = DBVT_WCHAR;
				dgs->pValue->pwszVal = tmp;
			}
			else {
				dgs->pValue->type = DBVT_ASCIIZ;
				dgs->pValue->pszVal = mir_alloc(result);
				WideCharToMultiByte(mirCp, WC_NO_BEST_FIT_CHARS, tmp, -1, dgs->pValue->pszVal, result, NULL, NULL);
				mir_free(tmp);
			}
		}
		else {
			dgs->pValue->type = DBVT_ASCIIZ;
			mir_free(tmp);
		}
	}
	LeaveCriticalSection(&csSettingsDb);
	return 0;
}

INT_PTR setting_getSettingStr(WPARAM wParam, LPARAM lParam) {
	DBCONTACTGETSETTING* dgs = (DBCONTACTGETSETTING*)lParam;
	int iSaveType = dgs->pValue->type;
	
	EnterCriticalSection(&csSettingsDb);
	if (settings_getContactSettingWorker((HANDLE)wParam, dgs, 0)) {
		LeaveCriticalSection(&csSettingsDb);
		return 1;
	}
	if (iSaveType==0||iSaveType==dgs->pValue->type) {
		LeaveCriticalSection(&csSettingsDb);
		return 0;
	}
	if (dgs->pValue->type!=DBVT_ASCIIZ&&dgs->pValue->type!=DBVT_UTF8) {
		LeaveCriticalSection(&csSettingsDb);
		return 0;
	}
	if (iSaveType==DBVT_WCHAR) {
		if (dgs->pValue->type!=DBVT_UTF8) {
			int len = MultiByteToWideChar(CP_ACP, 0, dgs->pValue->pszVal, -1, NULL, 0);
			wchar_t* wszResult = (wchar_t*)dbrw_alloc((len+1)*sizeof(wchar_t));

			if (wszResult==NULL) {
				LeaveCriticalSection(&csSettingsDb);
				return 1;
			}
			MultiByteToWideChar(CP_ACP, 0, dgs->pValue->pszVal, -1, wszResult, len);
			wszResult[len] = 0;
			dbrw_free(dgs->pValue->pszVal);
			dgs->pValue->pwszVal = wszResult;
		}
		else {
			char *savePtr = dgs->pValue->pszVal;

			mir_utf8decode(dgs->pValue->pszVal, &dgs->pValue->pwszVal);
			dbrw_free(savePtr);
		}
	}
	else if (iSaveType==DBVT_UTF8) {
		char *tmpBuf = mir_utf8encode(dgs->pValue->pszVal);

		if (tmpBuf==NULL) {
			LeaveCriticalSection(&csSettingsDb);
			return 1;
		}
		dbrw_free(dgs->pValue->pszVal);
		dgs->pValue->pszVal = tmpBuf;
	}
	else if (iSaveType==DBVT_ASCIIZ)
		mir_utf8decode(dgs->pValue->pszVal, NULL);
	dgs->pValue->type = iSaveType;
	LeaveCriticalSection(&csSettingsDb);
	return 0;
}

INT_PTR setting_getSettingStatic(WPARAM wParam, LPARAM lParam) {
	DBCONTACTGETSETTING* dgs = (DBCONTACTGETSETTING*)lParam;

	EnterCriticalSection(&csSettingsDb);
	if (settings_getContactSettingWorker((HANDLE)wParam, dgs, 1)) {
		LeaveCriticalSection(&csSettingsDb);
		return 1;
	}
	if (dgs->pValue->type==DBVT_UTF8 ) {
		mir_utf8decode(dgs->pValue->pszVal, NULL);
		dgs->pValue->type = DBVT_ASCIIZ;
	}
	LeaveCriticalSection(&csSettingsDb);
	return 0;
}

INT_PTR setting_freeVariant(WPARAM wParam, LPARAM lParam) {
	DBVARIANT *dbv = (DBVARIANT*)lParam;
	if (dbv==0) 
		return 1;
	switch (dbv->type) {
		case DBVT_ASCIIZ:
		case DBVT_UTF8:
		case DBVT_WCHAR:
		{
			if (dbv->pszVal) 
				dbrw_free(dbv->pszVal);
			dbv->pszVal = 0;
			break;
		}
		case DBVT_BLOB:
		{
			if (dbv->pbVal) 
				dbrw_free(dbv->pbVal);
			dbv->pbVal = 0;
			break;
		}
	}
	dbv->type = 0;
	return 0;
}

INT_PTR setting_writeSetting(WPARAM wParam, LPARAM lParam) {
	HANDLE hContact = (HANDLE)wParam;
	DBCONTACTWRITESETTING *dbcws = (DBCONTACTWRITESETTING*)lParam;
	
	if (dbcws==NULL)
		return 1;
	if (dbcws->value.type==DBVT_WCHAR) {
		if (dbcws->value.pszVal!=NULL) {
			char *val = mir_utf8encodeW(dbcws->value.pwszVal);
			if (val== NULL)
				return 1;
			dbcws->value.pszVal = (char*)alloca(strlen(val)+1);
			strcpy(dbcws->value.pszVal, val);
			dbrw_free(val);
			dbcws->value.type = DBVT_UTF8;
		}
		else return 1;
	}
	if (dbcws->value.type!=DBVT_BYTE&&
		dbcws->value.type!=DBVT_WORD&&
		dbcws->value.type!=DBVT_DWORD&&
		dbcws->value.type!=DBVT_ASCIIZ&&
		dbcws->value.type!=DBVT_UTF8&&
		dbcws->value.type!=DBVT_BLOB)
		return 1;
	if ((!dbcws->szModule)||(!dbcws->szSetting)||
		((dbcws->value.type==DBVT_ASCIIZ||dbcws->value.type==DBVT_UTF8)&&dbcws->value.pszVal == NULL)||
		(dbcws->value.type==DBVT_BLOB&&dbcws->value.pbVal==NULL))
		return 1;
	EnterCriticalSection(&csSettingsDb);
	{
		char *szCachedSettingName = settings_getCachedSettingName(dbcws->szModule, dbcws->szSetting);
		if (dbcws->value.type!=DBVT_BLOB) {
			DBVARIANT *pCachedValue = settings_getCachedValue(hContact, szCachedSettingName, 1);
			
			if (pCachedValue!=NULL) {
				BOOL isIdentical = FALSE;

				if (pCachedValue->type==dbcws->value.type) {
					switch(pCachedValue->type) {
						case DBVT_BYTE:
							isIdentical = pCachedValue->bVal==dbcws->value.bVal;
							break;
						case DBVT_WORD:
							isIdentical = pCachedValue->wVal==dbcws->value.wVal;
							break;
						case DBVT_DWORD:
							isIdentical = pCachedValue->dVal==dbcws->value.dVal;
							break;
						case DBVT_UTF8:
						case DBVT_ASCIIZ:
							isIdentical = strcmp(pCachedValue->pszVal, dbcws->value.pszVal)==0;
							break;
					}
					if (isIdentical) {
						LeaveCriticalSection(&csSettingsDb);
						return 0;
					}
				}
				settings_setCachedVariant(&dbcws->value, pCachedValue);
				// set key to write on timer update
				settings_setCachedValueUpdateStatus(hContact, szCachedSettingName, 1);
			}
		}
		else settings_getCachedValue(hContact, szCachedSettingName, -1);
	}
	// Only write blobs to the db immediately (do we want to cache blobs?)
	if (dbcws->value.type==DBVT_BLOB) {
		settings_writeToDB(hContact, dbcws->szModule, dbcws->szSetting, &dbcws->value);
	}
	LeaveCriticalSection(&csSettingsDb);
	NotifyEventHooks(hSettingChangeEvent, wParam, lParam);
	return 0;
}

INT_PTR setting_deleteSetting(WPARAM wParam, LPARAM lParam) {
	HANDLE hContact = (HANDLE)wParam;
	DBCONTACTGETSETTING *dbcgs = (DBCONTACTGETSETTING*)lParam;

	if (!dbcgs->szModule||!dbcgs->szSetting)
		return 1;
	EnterCriticalSection(&csSettingsDb);
	{
        int rc = 0;
		char *szCachedSettingName = settings_getCachedSettingName(dbcgs->szModule, dbcgs->szSetting);
		
		// remove setting from cache
		if (szCachedSettingName)
			settings_getCachedValue(hContact, szCachedSettingName, -1);
        
        // check if exists
		sqlite3_bind_text(settings_stmts_prep[SQL_SET_STMT_SETTINGCHECK], 1, dbcgs->szSetting, -1, SQLITE_STATIC);
		sqlite3_bind_text(settings_stmts_prep[SQL_SET_STMT_SETTINGCHECK], 2, dbcgs->szModule, -1, SQLITE_STATIC);
		sqlite3_bind_int(settings_stmts_prep[SQL_SET_STMT_SETTINGCHECK], 3, (int)hContact);
        if (sql_step(settings_stmts_prep[SQL_SET_STMT_SETTINGCHECK])==SQLITE_ROW)
            rc = sqlite3_column_int(settings_stmts_prep[SQL_SET_STMT_SETTINGCHECK], 0);
        if (rc==0) { // should really check not 1 but lets be nice
			sql_reset(settings_stmts_prep[SQL_SET_STMT_SETTINGCHECK]);
			LeaveCriticalSection(&csSettingsDb);
			return 1;
        }
        sql_reset(settings_stmts_prep[SQL_SET_STMT_SETTINGCHECK]);

		// Delete from db
		sqlite3_bind_text(settings_stmts_prep[SQL_SET_STMT_DELETE], 1, dbcgs->szSetting, -1, SQLITE_STATIC);
		sqlite3_bind_text(settings_stmts_prep[SQL_SET_STMT_DELETE], 2, dbcgs->szModule, -1, SQLITE_STATIC);
		sqlite3_bind_int(settings_stmts_prep[SQL_SET_STMT_DELETE], 3, (int)hContact);
		if (sql_step(settings_stmts_prep[SQL_SET_STMT_DELETE])!=SQLITE_DONE) {
			sql_reset(settings_stmts_prep[SQL_SET_STMT_DELETE]);
			LeaveCriticalSection(&csSettingsDb);
			return 1;
		}
		sql_reset(settings_stmts_prep[SQL_SET_STMT_DELETE]);
	}
	LeaveCriticalSection(&csSettingsDb);
	{
		DBCONTACTWRITESETTING dbcws;
		dbcws.szModule = dbcgs->szModule;
		dbcws.szSetting = dbcgs->szSetting;
		dbcws.value.type = DBVT_DELETED;
		NotifyEventHooks(hSettingChangeEvent, wParam, (LPARAM)&dbcws);
	}
	return 0;
}

INT_PTR setting_enumSettings(WPARAM wParam, LPARAM lParam) {
	HANDLE hContact = (HANDLE)wParam;
	DBCONTACTENUMSETTINGS *dbces = (DBCONTACTENUMSETTINGS*)lParam;
	int rc = -1;

	if (!dbces->szModule)
		return -1;
    settings_writeUpdatedSettings();
	EnterCriticalSection(&csSettingsDb);
	sqlite3_bind_int(settings_stmts_prep[SQL_SET_STMT_ENUM], 1, (int)hContact);
	sqlite3_bind_text(settings_stmts_prep[SQL_SET_STMT_ENUM], 2, dbces->szModule, -1, SQLITE_STATIC);
	while (sql_step(settings_stmts_prep[SQL_SET_STMT_ENUM])==SQLITE_ROW) {
		const char *sczSetting = sqlite3_column_text(settings_stmts_prep[SQL_SET_STMT_ENUM], 0);
		if (sczSetting) {
            char * szCachedSetting = settings_getCachedSettingName(dbces->szModule, sczSetting);
            if (szCachedSetting&&!settings_isResident(szCachedSetting)) {
                rc = (dbces->pfnEnumProc)(sczSetting,dbces->lParam);
            }
		}
	}
	sql_reset(settings_stmts_prep[SQL_SET_STMT_ENUM]);
	LeaveCriticalSection(&csSettingsDb);
	return rc;
}

INT_PTR setting_modulesEnum(WPARAM wParam, LPARAM lParam) {
	DBMODULEENUMPROC proc = (DBMODULEENUMPROC)lParam;
	LPARAM lParamReal = (LPARAM)wParam;
	int rc = 0;
	int offset = 1;
	const char *szModule;
	
    /* Flush db to disk */
    settings_writeUpdatedSettings();
    /* End flush */
	EnterCriticalSection(&csSettingsDb);
	while (sql_step(settings_stmts_prep[SQL_SET_STMT_ENUMMODULES])==SQLITE_ROW && !rc) {
		szModule = sqlite3_column_text(settings_stmts_prep[SQL_SET_STMT_ENUMMODULES], 0);
		LeaveCriticalSection(&csSettingsDb);
		rc = (proc)(szModule, (DWORD)offset++, lParamReal);
		EnterCriticalSection(&csSettingsDb);
	}
	sql_reset(settings_stmts_prep[SQL_SET_STMT_ENUMMODULES]);
	LeaveCriticalSection(&csSettingsDb);
	return rc;
}

// Assume critical section
void settings_deleteContactData(HANDLE hContact) {
	int idx;
	DBCachedContactValueList VLtemp,*VL;
	DBCachedContactValue *V;
    
    EnterCriticalSection(&csSettingsDb);
	VLtemp.hContact = hContact;
	if (li.List_GetIndex(&sContactSettings, &VLtemp, &idx)) {
		VL = (DBCachedContactValueList*)sContactSettings.items[idx];
		
		V = VL->first;
		while (V) {
			settings_freeCachedVariant(&V->value);
			V->value.type = DBVT_DELETED;
			V = V->next;
		}
	}
    sqlite3_bind_int(settings_stmts_prep[SQL_SET_STMT_DELETECONTACT], 1, (int)hContact);
    sql_step(settings_stmts_prep[SQL_SET_STMT_DELETECONTACT]);
    sql_reset(settings_stmts_prep[SQL_SET_STMT_DELETECONTACT]);
    LeaveCriticalSection(&csSettingsDb);
}

INT_PTR settings_setResident(WPARAM wParam, LPARAM lParam) {
    EnterCriticalSection(&csSettingsDb);
    {
        DBCachedResidentSettingValue Vtemp, *V;
        int resident = (int)wParam, idx;
        char *szSetting = (char*)lParam;
        
        if (!szSetting) {
            LeaveCriticalSection(&csSettingsDb);
            return 0;
        }
        Vtemp.name = szSetting;
        Vtemp.nameHash = utils_hashString(szSetting);
        if (li.List_GetIndex(&sResidentSettings, &Vtemp, &idx)) {
            if (!resident) {
                V = (DBCachedResidentSettingValue*)sResidentSettings.items[idx];
                li.List_Remove(&sResidentSettings, idx);
                HeapFree(hHeap, 0, V->name);
                HeapFree(hHeap, 0, V->module);
				HeapFree(hHeap, 0, V);
            }
            LeaveCriticalSection(&csSettingsDb);
            return 0;
        }
        else {
            size_t nameLen = strlen(szSetting)+1;
            
			V = (DBCachedResidentSettingValue*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(DBCachedResidentSettingValue));
			V->name = (char*)HeapAlloc(hHeap, 0, nameLen);
            mir_snprintf(V->name, nameLen, "%s", szSetting);
			V->nameHash = utils_hashString(szSetting);
            V->module = (char*)HeapAlloc(hHeap, 0, nameLen);
            mir_snprintf(V->module, nameLen, "%s", szSetting);
            V->module = strtok(V->module, "/");
            V->moduleHash = utils_hashString(V->module);
			li.List_Insert(&sResidentSettings, V, idx);
        }
        LeaveCriticalSection(&csSettingsDb);
    }
    return 0;
}

// Assume critical section
static int settings_isResident(char *szSetting) {
    DBCachedResidentSettingValue Vtemp;
    int idx;
    
    Vtemp.name = szSetting;
    Vtemp.nameHash = utils_hashString(szSetting);
    if (li.List_GetIndex(&sResidentSettings, &Vtemp, &idx)) {
        return 1;
    }
    return 0;
}
