#include "stdafx.h"

void luaM_pushdbvt(lua_State *L, const DBVARIANT &value)
{
	switch (value.type)
	{
	case DBVT_BYTE:
		lua_pushinteger(L, value.bVal);
		break;
	case DBVT_WORD:
		lua_pushinteger(L,value.wVal);
		break;
	case DBVT_DWORD:
		lua_pushnumber(L, value.dVal);
		break;
	case DBVT_ASCIIZ:
		lua_pushstring(L, ptrA(mir_utf8encode(value.pszVal)));
		break;
	case DBVT_UTF8:
		lua_pushstring(L, value.pszVal);
		break;
	case DBVT_WCHAR:
		lua_pushstring(L, ptrA(mir_utf8encodeW(value.pwszVal)));
		break;
	case DBVT_BLOB:
	{
		lua_createtable(L, value.cpbVal, 0);
		for (int i = 0; i < value.cpbVal; i++)
		{
			lua_pushinteger(L, value.pbVal[i]);
			lua_rawseti(L, -2, i + 1);
		}
	}
	break;
	default:
		lua_pushnil(L);
	}
}

/***********************************************/

static int db_FindFirstContact(lua_State *L)
{
	ObsoleteMethod(L, "Use Contacts method");

	const char *szProto = lua_tostring(L, 1);

	MCONTACT res = db_find_first(szProto);
	lua_pushinteger(L, res);

	return 1;
}

static int db_FindNextContact(lua_State *L)
{
	ObsoleteMethod(L, "Use Contacts method");

	MCONTACT hContact = luaL_checkinteger(L, 1);
	const char *szProto = lua_tostring(L, 2);

	MCONTACT res = db_find_next(hContact, szProto);
	lua_pushinteger(L, res);

	return 1;
}

static int db_ContactIterator(lua_State *L)
{
	MCONTACT hContact = lua_tointeger(L, lua_upvalueindex(1));
	const char *szModule = lua_tostring(L, lua_upvalueindex(2));

	hContact = hContact == NULL
		? db_find_first(szModule)
		: db_find_next(hContact, szModule);

	if (hContact)
	{
		lua_pushinteger(L, hContact);
		lua_pushvalue(L, -1);
		lua_replace(L, lua_upvalueindex(1));
	}
	else
		lua_pushnil(L);

	return 1;
}

static int db_Contacts(lua_State *L)
{
	const char *szModule = NULL;

	switch (lua_type(L, 1))
	{
	case LUA_TNONE:
		break;
	case LUA_TSTRING:
		szModule = lua_tostring(L, 1);
		break;
	case LUA_TUSERDATA:
	{
		PROTOACCOUNT **pa = (PROTOACCOUNT**)luaL_checkudata(L, 1, MT_PROTOACCOUNT);
		szModule = (*pa)->szModuleName;
		break;
	}
	default:
		luaL_argerror(L, 1, luaL_typename(L, 1));
	}

	lua_pushinteger(L, 0);
	lua_pushstring(L, szModule);
	lua_pushcclosure(L, db_ContactIterator, 2);

	return 1;
}

static const char *mods[] =
{
	"FirstName",
	"LastName",
	"Nick",
	"CustomNick",
	"Email",
	"City",
	"State",
	"Country",
	"Phone",
	"Homepage",
	"About",
	"Gender",
	"Age",
	"FullName",
	"Uid",
	"DisplayName",
	NULL
};

static int db_GetContactInfo(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, 1);

	int type = 0;
	switch (lua_type(L, 2))
	{
	case LUA_TNUMBER:
		type = luaL_checkinteger(L, 2);
		break;
	case LUA_TSTRING:
		type = luaL_checkoption(L, 2, NULL, mods) + 1;
		break;
	default:
		luaL_argerror(L, 2, luaL_typename(L, 2));
	}

	ptrW value(Contact_GetInfo(type, hContact));
	if (value)
		lua_pushstring(L, ptrA(mir_utf8encodeW(value)));
	else
	{
		lua_pushnil(L);
		return 1;
	}

	return 1;
}

/***********************************************/

static int db_GetEventCount(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, 1);

	int res = db_event_count(hContact);
	lua_pushinteger(L, res);

	return 1;
}

static int db_GetFirstEvent(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, 1);

	MEVENT res = db_event_first(hContact);
	lua_pushinteger(L, res);

	return 1;
}

static int db_GetPrevEvent(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, 1);
	MEVENT hDbEvent = luaL_checkinteger(L, 2);

	MEVENT res = db_event_prev(hContact, hDbEvent);
	lua_pushinteger(L, res);

	return 1;
}

static int db_GetNextEvent(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, 1);
	MEVENT hDbEvent = luaL_checkinteger(L, 2);

	MEVENT res = db_event_next(hContact, hDbEvent);
	lua_pushinteger(L, res);

	return 1;
}

static int db_GetLastEvent(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, 1);

	MEVENT res = db_event_last(hContact);
	lua_pushinteger(L, res);

	return 1;
}

static int db_GetFirstUnreadEvent(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, 1);

	MEVENT res = db_event_firstUnread(hContact);
	lua_pushinteger(L, res);

	return 1;
}

static int db_EventIterator(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, lua_upvalueindex(1));
	MEVENT hDbEvent = luaL_checkinteger(L, lua_upvalueindex(2));

	hDbEvent = hDbEvent == NULL
		? db_event_first(hContact)
		: db_event_next(hContact, hDbEvent);

	if (hDbEvent)
	{
		lua_pushinteger(L, hDbEvent);
		lua_pushvalue(L, -1);
		lua_replace(L, lua_upvalueindex(2));
	}
	else
		lua_pushnil(L);

	return 1;
}

static int db_Events(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, 1);

	lua_pushinteger(L, hContact);
	lua_pushinteger(L, NULL);
	lua_pushcclosure(L, db_EventIterator, 2);

	return 1;
}

static int db_EventReverseIterator(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, lua_upvalueindex(1));
	MEVENT hDbEvent = luaL_checkinteger(L, lua_upvalueindex(2));

	hDbEvent = hDbEvent == NULL
		? db_event_last(hContact)
		: db_event_prev(hContact, hDbEvent);

	if (hDbEvent)
	{
		lua_pushinteger(L, hDbEvent);
		lua_pushvalue(L, -1);
		lua_replace(L, lua_upvalueindex(2));
	}
	else
		lua_pushnil(L);

	return 1;
}

static int db_EventsFromEnd(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, 1);

	lua_pushinteger(L, hContact);
	lua_pushinteger(L, NULL);
	lua_pushcclosure(L, db_EventReverseIterator, 2);

	return 1;
}

static int db_UnreadEventIterator(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, lua_upvalueindex(1));
	MEVENT hDbEvent = luaL_checkinteger(L, lua_upvalueindex(2));

	hDbEvent = db_event_firstUnread(hContact);

	if (hDbEvent)
	{
		lua_pushinteger(L, hDbEvent);
		lua_pushvalue(L, -1);
		lua_replace(L, lua_upvalueindex(2));
	}
	else
		lua_pushnil(L);

	return 1;
}

static int db_UnreadEvents(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, 1);

	lua_pushinteger(L, hContact);
	lua_pushinteger(L, NULL);
	lua_pushcclosure(L, db_UnreadEventIterator, 2);

	return 1;
}

void MakeDbEvent(lua_State *L, DBEVENTINFO &dbei)
{
	lua_getfield(L, -1, "Module");
	dbei.szModule = mir_strdup(lua_tostring(L, -1));
	lua_pop(L, 1);

	lua_getfield(L, -1, "Type");
	dbei.eventType = lua_tointeger(L, -1);
	lua_pop(L, 1);

	lua_getfield(L, -1, "Timestamp");
	dbei.timestamp = lua_tonumber(L, -1);
	lua_pop(L, 1);

	lua_getfield(L, -1, "Flags");
	dbei.flags = lua_tointeger(L, -1);
	lua_pop(L, 1);

	lua_getfield(L, -1, "Blob");
	switch (lua_type(L, -1))
	{
	case LUA_TTABLE:
		dbei.cbBlob = (DWORD)lua_rawlen(L, 4);
		dbei.pBlob = (BYTE*)mir_calloc(dbei.cbBlob);
		for (DWORD i = 0; i < dbei.cbBlob; i++)
		{
			lua_geti(L, 4, i + 1);
			dbei.pBlob[i] = lua_tointeger(L, -1);
			lua_pop(L, 1);
		}
		break;
	case LUA_TSTRING:
		size_t nLen;
		const char *str = lua_tolstring(L, -1, &nLen);
		dbei.cbBlob = (DWORD)nLen;
		dbei.pBlob = (BYTE*)mir_alloc(nLen);
		memcpy(dbei.pBlob, str, nLen);
		break;
	}
	lua_pop(L, 1);
}

static int db_AddEvent(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, 1);
	
	DBEVENTINFO dbei;
	MakeDbEvent(L, dbei);
	MEVENT hDbEvent = db_event_add(hContact, &dbei);

	if (hDbEvent)
		lua_pushnumber(L, hDbEvent);
	else
		lua_pushnil(L);

	return 1;
}

static int db_MarkReadEvent(lua_State *L)
{
	MCONTACT hContact = luaL_checkinteger(L, 1);
	MEVENT hDbEvent = luaL_checkinteger(L, 2);

	int res = db_event_markRead(hContact, hDbEvent);
	lua_pushnumber(L, res);

	return 1;
}

/***********************************************/

static int ModulesEnumProc(const char *szModuleName, DWORD, LPARAM lParam)
{
	if (szModuleName)
	{
		LIST<char>* p = (LIST<char>*)lParam;
		p->insert(mir_strdup(szModuleName));
	}

	return 0;
}

static int db_ModulesIterator(lua_State *L)
{
	int i = lua_tointeger(L, lua_upvalueindex(1));
	LIST<char> &param = *(LIST<char>*)lua_touserdata(L, lua_upvalueindex(2));

	if (i < param.getCount())
	{
		lua_pushinteger(L, (i + 1));
		lua_replace(L, lua_upvalueindex(1));
		lua_pushstring(L, ptrA(mir_utf8encode(param[i])));
		mir_free(param[i]);
	}
	else
	{
		lua_pushnil(L);
		delete &param;
	}

	return 1;
}

static int db_Modules(lua_State *L)
{
	LIST<char> *param = new LIST<char>(5, PtrKeySortT);

	db_enum_modules(ModulesEnumProc, param);

	lua_pushinteger(L, 0);
	lua_pushlightuserdata(L, param);
	lua_pushcclosure(L, db_ModulesIterator, 2);

	return 1;
}

static int db_DeleteModule(lua_State *L)
{
	MCONTACT hContact = lua_tointeger(L, 1);
	const char *szModule = luaL_checkstring(L, 2);

	INT_PTR res = db_delete_module(hContact, szModule);
	lua_pushboolean(L, !res);

	return 1;
}

static int SettingsEnumProc(const char* szSetting, LPARAM lParam)
{
	if (szSetting)
	{
		LIST<char>* p = (LIST<char>*)lParam;
		p->insert(mir_strdup(szSetting));
	}
	return 0;
}

static int db_SettingIterator(lua_State *L)
{
	int i = lua_tointeger(L, lua_upvalueindex(1));
	LIST<char> &param = *(LIST<char>*)lua_touserdata(L, lua_upvalueindex(2));

	if (i < param.getCount())
	{
		lua_pushinteger(L, (i + 1));
		lua_replace(L, lua_upvalueindex(1));
		lua_pushstring(L, ptrA(mir_utf8encode(param[i])));
		mir_free(param[i]);
	}
	else
	{
		lua_pushnil(L);
		delete &param;
	}

	return 1;
}

static int db_Settings(lua_State *L)
{
	MCONTACT hContact = lua_tointeger(L, 1);
	const char* szModule = luaL_checkstring(L, 2);

	LIST<char> *param = new LIST<char>(5, PtrKeySortT);
	db_enum_settings(hContact, SettingsEnumProc, szModule, param);

	lua_pushinteger(L, 0);
	lua_pushlightuserdata(L, param);
	lua_pushcclosure(L, db_SettingIterator, 2);

	return 1;
}

static int db_GetSetting(lua_State *L)
{
	MCONTACT hContact = lua_tointeger(L, 1);
	const char *szModule = luaL_checkstring(L, 2);
	const char *szSetting = luaL_checkstring(L, 3);

	DBVARIANT dbv;
	if (db_get(hContact, szModule, szSetting, &dbv))
	{
		lua_pushvalue(L, 4);
		return 1;
	}

	luaM_pushdbvt(L, dbv);
	db_free(&dbv);

	if (lua_isnil(L, -1) && !lua_isnoneornil(L, 4))
	{
		lua_pop(L, 1);
		lua_pushvalue(L, 4);
	}

	return 1;
}

static int db_WriteSetting(lua_State *L)
{
	MCONTACT hContact = lua_tointeger(L, 1);
	const char *szModule = luaL_checkstring(L, 2);
	const char *szSetting = luaL_checkstring(L, 3);
	luaL_checkany(L, 4);

	DBVARIANT dbv;
	if (lua_isnoneornil(L, 5))
	{
		int type = lua_type(L, 4);
		switch (type)
		{
		case LUA_TBOOLEAN:
			dbv.type = DBVT_BYTE;
			break;
		case LUA_TNUMBER:
			dbv.type = DBVT_DWORD;
			break;
		case LUA_TSTRING:
			dbv.type = DBVT_UTF8;
			break;
		case LUA_TTABLE:
			dbv.type = DBVT_BLOB;
			break;
		default:
			lua_pushboolean(L, false);
			return 1;
		}
	}
	else
		dbv.type = luaL_checkinteger(L, 5);

	switch (dbv.type)
	{
	case DBVT_BYTE:
		dbv.bVal = lua_isboolean(L, 4)
			? lua_toboolean(L, 4)
			: luaL_checknumber(L, 4);
		break;
	case DBVT_WORD:
		dbv.wVal = luaL_checknumber(L, 4);
		break;
	case DBVT_DWORD:
		dbv.dVal = luaL_checknumber(L, 4);
		break;
	case DBVT_UTF8:
		dbv.pszVal = mir_strdup(luaL_checkstring(L, 4));
		break;
	case DBVT_ASCIIZ:
		dbv.pszVal = mir_utf8decodeA(luaL_checkstring(L, 4));
		break;
	case DBVT_WCHAR:
		dbv.pwszVal = mir_utf8decodeW(luaL_checkstring(L, 4));
		break;
	case DBVT_BLOB:
	{
		dbv.cpbVal = (WORD)lua_rawlen(L, 4);
		dbv.pbVal = (BYTE*)mir_calloc(dbv.cpbVal);
		for (int i = 0; i < dbv.cpbVal; i++)
		{
			lua_geti(L, 4, i + 1);
			dbv.pbVal[i] = lua_tointeger(L, -1);
			lua_pop(L, 1);
		}
	}
		break;
	default:
		lua_pushboolean(L, false);
		return 1;
	}

	INT_PTR res = db_set(hContact, szModule, szSetting, &dbv);
	lua_pushboolean(L, !res);

	return 1;
}

static int db_DeleteSetting(lua_State *L)
{
	MCONTACT hContact = lua_tointeger(L, 1);
	LPCSTR szModule = luaL_checkstring(L, 2);
	LPCSTR szSetting = luaL_checkstring(L, 3);

	INT_PTR res = db_unset(hContact, szModule, szSetting);
	lua_pushboolean(L, !res);

	return 1;
}

/***********************************************/

static luaL_Reg databaseApi[] =
{
	{ "FindFirstContact", db_FindFirstContact },
	{ "FindNextContact", db_FindNextContact },
	{ "Contacts", db_Contacts },
	{ "GetContactInfo", db_GetContactInfo },

	{ "GetEventCount", db_GetEventCount },

	{ "GetFirstEvent", db_GetFirstEvent },
	{ "GetPrevEvent", db_GetPrevEvent },
	{ "GetNextEvent", db_GetNextEvent },
	{ "GetLastEvent", db_GetLastEvent },
	{ "GetFirstUnreadEvent", db_GetFirstUnreadEvent },
	{ "Events", db_Events },
	{ "EventsFromEnd", db_EventsFromEnd },
	{ "UnreadEvents", db_UnreadEvents },
	{ "AddEvent", db_AddEvent },
	{ "MarkReadEvent", db_MarkReadEvent },

	{ "Settings", db_Settings },
	{ "Modules", db_Modules },

	{ "DeleteModule", db_DeleteModule },

	{ "GetSetting", db_GetSetting },
	{ "WriteSetting", db_WriteSetting },
	{ "SetSetting", db_WriteSetting },
	{ "DeleteSetting", db_DeleteSetting },

	{ "DBVT_BYTE", NULL },
	{ "DBVT_WORD", NULL },
	{ "DBVT_DWORD", NULL },
	{ "DBVT_ASCIIZ", NULL },
	{ "DBVT_UTF8", NULL },
	{ "DBVT_WCHAR", NULL },

	{ NULL, NULL }
};

/***********************************************/

#define MT_DBCONTACTWRITESETTING "DBCONTACTWRITESETTING"

template <>
int MT<DBCONTACTWRITESETTING>::Get(lua_State *L, DBCONTACTWRITESETTING *dbcw)
{
	const char *key = luaL_checkstring(L, 2);

	if (mir_strcmpi(key, "Value") == 0)
		luaM_pushdbvt(L, dbcw->value);
	else
		lua_pushnil(L);

	return 1;
}

/***********************************************/

#define MT_DBEVENTINFO "DBEVENTINFO"

template <>
DBEVENTINFO* MT<DBEVENTINFO>::Init(lua_State *L)
{
	MEVENT hDbEvent = luaL_checkinteger(L, 1);

	DBEVENTINFO* dbei = (DBEVENTINFO*)mir_calloc(sizeof(DBEVENTINFO));
	dbei->cbBlob = db_event_getBlobSize((MEVENT)hDbEvent);
	dbei->pBlob = (PBYTE)mir_calloc(dbei->cbBlob);
	db_event_get((MEVENT)hDbEvent, dbei);

	return dbei;
}

template <>
int MT<DBEVENTINFO>::Get(lua_State *L, DBEVENTINFO *dbei)
{
	const char *key = luaL_checkstring(L, 2);

	if (mir_strcmpi(key, "Blob") == 0)
	{
		lua_createtable(L, dbei->cbBlob, 0);
		for (DWORD i = 0; i < dbei->cbBlob; i++)
		{
			lua_pushinteger(L, dbei->pBlob[i]);
			lua_rawseti(L, -2, i + 1);
		}
	}
	else
		lua_pushnil(L);

	return 1;
}

template <>
void MT<DBEVENTINFO>::Free(lua_State*, DBEVENTINFO **dbei)
{
	mir_free((*dbei)->pBlob);
	mir_free(*dbei);
}

/***********************************************/

LUAMOD_API int luaopen_m_database(lua_State *L)
{
	luaL_newlib(L, databaseApi);

	lua_pushnumber(L, DBVT_BYTE);
	lua_setfield(L, -2, "DBVT_BYTE");
	lua_pushnumber(L, DBVT_WORD);
	lua_setfield(L, -2, "DBVT_WORD");
	lua_pushnumber(L, DBVT_DWORD);
	lua_setfield(L, -2, "DBVT_DWORD");
	lua_pushnumber(L, DBVT_ASCIIZ);
	lua_setfield(L, -2, "DBVT_ASCIIZ");
	lua_pushnumber(L, DBVT_UTF8);
	lua_setfield(L, -2, "DBVT_UTF8");
	lua_pushnumber(L, DBVT_WCHAR);
	lua_setfield(L, -2, "DBVT_WCHAR");

	MT<DBCONTACTWRITESETTING>(L, MT_DBCONTACTWRITESETTING)
		.Field(&DBCONTACTWRITESETTING::szModule, "Module", LUA_TSTRINGA)
		.Field(&DBCONTACTWRITESETTING::szSetting, "Setting", LUA_TSTRINGA);

	MT<DBEVENTINFO>(L, MT_DBEVENTINFO)
		.Field(&DBEVENTINFO::szModule, "Module", LUA_TSTRINGA)
		.Field(&DBEVENTINFO::timestamp, "Timestamp", LUA_TINTEGER)
		.Field(&DBEVENTINFO::eventType, "Type", LUA_TINTEGER)
		.Field(&DBEVENTINFO::flags, "Flags", LUA_TINTEGER);

	return 1;
}