////////////////////////////////////////////////////////////////////////////////
// Gadu-Gadu Plugin for Miranda IM
//
// Copyright (c) 2003-2009 Adam Strzelecki <ono+miranda@java.pl>
// Copyright (c) 2009-2011 Bartosz Białek
//
// 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 "gg.h"
#include "version.h"
#include <errno.h>

// Plugin info
PLUGININFOEX pluginInfo = {
	sizeof(PLUGININFOEX),
	"Gadu-Gadu Protocol",
	__VERSION_DWORD,
	"Provides support for Gadu-Gadu protocol",
	"Bartosz Białek, Adam Strzelecki",
	"dezred"/*antispam*/"@"/*antispam*/"gmail"/*antispam*/"."/*antispam*/"com",
	"© 2009-2011 Bartosz Białek, 2003-2009 Adam Strzelecki",
	"http://www.miranda-im.pl/",
	0,
	0,
	// {F3FF65F3-250E-416A-BEE9-58C93F85AB33}
	{ 0xf3ff65f3, 0x250e, 0x416a, { 0xbe, 0xe9, 0x58, 0xc9, 0x3f, 0x85, 0xab, 0x33 } }
};
static const MUUID interfaces[] = {MIID_PROTOCOL, MIID_LAST};

// Other variables
HINSTANCE hInstance;
PLUGINLINK *pluginLink;
struct MM_INTERFACE mmi;
struct SHA1_INTERFACE sha1i;
struct MD5_INTERFACE md5i;
struct LIST_INTERFACE li;
XML_API xi;
SSL_API si;
CLIST_INTERFACE *pcli;

// Event hooks
static HANDLE hHookModulesLoaded = NULL;
static HANDLE hHookPreShutdown = NULL;

static unsigned long crc_table[256];

//////////////////////////////////////////////////////////
// Extra winsock function for error description
char *ws_strerror(int code)
{
	static char err_desc[160];

	// Not a windows error display WinSock
	if(code == 0)
	{
		char buff[128];
		int len;
		len = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
				  NULL, WSAGetLastError(), 0, buff,
				  sizeof(buff), NULL);
		if(len == 0)
			mir_snprintf(err_desc, sizeof(err_desc), "WinSock %u: Unknown error.", WSAGetLastError());
		else
			mir_snprintf(err_desc, sizeof(err_desc), "WinSock %d: %s", WSAGetLastError(), buff);
		return err_desc;
	}

	// Return normal error
	return strerror(code);
}

//////////////////////////////////////////////////////////
// Build the crc table
void crc_gentable(void)
{
	unsigned long crc, poly;
	int	i, j;

	poly = 0xEDB88320L;
	for (i = 0; i < 256; i++)
	{
		crc = i;
		for (j = 8; j > 0; j--)
		{
			if (crc & 1)
				crc = (crc >> 1) ^ poly;
			else
				crc >>= 1;
		}
		crc_table[i] = crc;
	}
}

//////////////////////////////////////////////////////////
// Calculate the crc value
unsigned long crc_get(char *mem)
{
	register unsigned long crc = 0xFFFFFFFF;
	while(mem && *mem)
		crc = ((crc>>8) & 0x00FFFFFF) ^ crc_table[(crc ^ *(mem++)) & 0xFF];

	return (crc ^ 0xFFFFFFFF);
}

//////////////////////////////////////////////////////////
// http_error_string()
//
// returns http error text
const char *http_error_string(int h)
{
	switch (h)
	{
		case 0:
			return Translate((errno == ENOMEM) ? "HTTP failed memory" : "HTTP failed connecting");
		case GG_ERROR_RESOLVING:
			return Translate("HTTP failed resolving");
		case GG_ERROR_CONNECTING:
			return Translate("HTTP failed connecting");
		case GG_ERROR_READING:
			return Translate("HTTP failed reading");
		case GG_ERROR_WRITING:
			return Translate("HTTP failed writing");
	}

	return Translate("Unknown HTTP error");
}

//////////////////////////////////////////////////////////
// Gets plugin info
__declspec(dllexport) PLUGININFOEX *MirandaPluginInfoEx(DWORD mirandaVersion)
{
	if (mirandaVersion < PLUGIN_MAKE_VERSION(0, 9, 0, 0))
	{
		MessageBox(
			NULL,
			"The Gadu-Gadu protocol plugin cannot be loaded. It requires Miranda IM 0.9.0.0 or later.",
			"Gadu-Gadu Protocol Plugin",
			MB_OK | MB_ICONWARNING | MB_SETFOREGROUND | MB_TOPMOST
		);
		return NULL;
	}
	return &pluginInfo;
}
__declspec(dllexport) const MUUID* MirandaPluginInterfaces(void)
{
	return interfaces;
}
void CreateProtoService(const char* szService, GGPROTOFUNC serviceProc, GGPROTO *gg)
{
	char str[MAXMODULELABELLENGTH];
	mir_snprintf(str, sizeof(str), "%s%s", gg->proto.m_szModuleName, szService);
	CreateServiceFunctionObj(str, (MIRANDASERVICEOBJ)serviceProc, gg);
}

//////////////////////////////////////////////////////////
// Cleanups from last plugin
void gg_cleanuplastplugin(GGPROTO *gg, DWORD version)
{
	HANDLE hContact;
	char *szProto;

	// Remove bad e-mail and phones from
	if(version < PLUGIN_MAKE_VERSION(0, 0, 1, 4))
	{
#ifdef DEBUGMODE
		gg_netlog(gg, "gg_cleanuplastplugin(%d): Cleaning junk Phone settings from < 0.0.1.4 ...", version);
#endif
		// Look for contact in DB
		hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
		while (hContact)
		{
			szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
			if(szProto != NULL && !strcmp(szProto, GG_PROTO))
			{
				// Do contact cleanup
				DBDeleteContactSetting(hContact, GG_PROTO, GG_KEY_EMAIL);
				DBDeleteContactSetting(hContact, GG_PROTO, "Phone");
			}
			hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0);
		}
	}

	// Remove GG entries for non GG contacts
	if(version < PLUGIN_MAKE_VERSION(0, 0, 3, 5))
	{
#ifdef DEBUGMODE
		gg_netlog(gg, "gg_cleanuplastplugin(%d): Cleaning junk Nick settings from < 0.0.3.5 ...", version);
#endif
		// Look for contact in DB
		hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
		while (hContact)
		{
			szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
			if(szProto != NULL && strcmp(szProto, GG_PROTO))
			{
				// Do nick entry cleanup
				DBDeleteContactSetting(hContact, GG_PROTO, GG_KEY_NICK);
			}
			hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0);
		}
	}

	// Remove old unneeded entry
	if(version < PLUGIN_MAKE_VERSION(0, 0, 5, 3))
		DBDeleteContactSetting(NULL, GG_PROTO, "ShowNotOnMyList");

	// Store this plugin version
	DBWriteContactSettingDword(NULL, GG_PROTO, GG_PLUGINVERSION, pluginInfo.version);
}

//////////////////////////////////////////////////////////
// Custom folders initialization
void gg_initcustomfolders(GGPROTO *gg)
{
	char szPath[MAX_PATH];
	char *tmpPath = Utils_ReplaceVars("%miranda_avatarcache%");
	mir_snprintf(szPath, MAX_PATH, "%s\\%s", tmpPath, GG_PROTO);
	mir_free(tmpPath);
	gg->hAvatarsFolder = FoldersRegisterCustomPath(GG_PROTO, "Avatars", szPath);

	tmpPath = Utils_ReplaceVars("%miranda_userdata%");
	mir_snprintf(szPath, MAX_PATH, "%s\\%s\\ImageCache", tmpPath, GG_PROTO);
	mir_free(tmpPath);
	gg->hImagesFolder = FoldersRegisterCustomPath(GG_PROTO, "Images", szPath);
}

//////////////////////////////////////////////////////////
// When Miranda loaded its modules
int gg_modulesloaded(WPARAM wParam, LPARAM lParam)
{
	// Get SSL API
	mir_getSI(&si);

	// File Association Manager support
	gg_links_init();

	return 0;
}

//////////////////////////////////////////////////////////
// When Miranda starting shutdown sequence
int gg_preshutdown(WPARAM wParam, LPARAM lParam)
{
	gg_links_destroy();

	return 0;
}

//////////////////////////////////////////////////////////
// Menus initialization
void gg_menus_init(GGPROTO *gg)
{
	HGENMENU hGCRoot, hCLRoot, hRoot = MO_GetProtoRootMenu(gg->proto.m_szModuleName);
	CLISTMENUITEM mi = {0};

	mi.cbSize = sizeof(mi);
	if (hRoot == NULL)
	{
		mi.ptszName = GG_PROTONAME;
		mi.position = 500090000;
		mi.hParentMenu = HGENMENU_ROOT;
		mi.flags = CMIF_ICONFROMICOLIB | CMIF_ROOTPOPUP | CMIF_TCHAR | CMIF_KEEPUNTRANSLATED;
		mi.icolibItem = GetIconHandle(IDI_GG);
		hGCRoot = hCLRoot = hRoot = gg->hMenuRoot = (HANDLE)CallService(MS_CLIST_ADDPROTOMENUITEM, 0, (LPARAM) &mi);
	}
	else
	{
		mi.hParentMenu = hRoot;
		mi.flags = CMIF_ICONFROMICOLIB | CMIF_ROOTHANDLE | CMIF_TCHAR;

		mi.ptszName = LPGENT("Conference");
		mi.position = 200001;
		mi.icolibItem = GetIconHandle(IDI_CONFERENCE);
		hGCRoot = (HANDLE)CallService(MS_CLIST_ADDPROTOMENUITEM, 0, (LPARAM) &mi);

		mi.ptszName = LPGENT("Contact list");
		mi.position = 200002;
		mi.icolibItem = GetIconHandle(IDI_LIST);
		hCLRoot = (HANDLE)CallService(MS_CLIST_ADDPROTOMENUITEM, 0, (LPARAM) &mi);

		if (gg->hMenuRoot)
			CallService(MS_CLIST_REMOVEMAINMENUITEM, (WPARAM)gg->hMenuRoot, 0);
		gg->hMenuRoot = NULL;
	}

	gg_gc_menus_init(gg, hGCRoot);
	gg_import_init(gg, hCLRoot);
	gg_sessions_menus_init(gg, hRoot);
}

//////////////////////////////////////////////////////////
// Custom protocol event
int gg_event(PROTO_INTERFACE *proto, PROTOEVENTTYPE eventType, WPARAM wParam, LPARAM lParam)
{
	GGPROTO *gg = (GGPROTO *)proto;
	switch( eventType )
	{
		case EV_PROTO_ONLOAD:
		{
			gg->hookOptsInit = HookProtoEvent(ME_OPT_INITIALISE, gg_options_init, gg);
			gg->hookUserInfoInit = HookProtoEvent(ME_USERINFO_INITIALISE, gg_details_init, gg);
			gg->hookSettingDeleted = HookProtoEvent(ME_DB_CONTACT_DELETED, gg_userdeleted, gg);
			gg->hookSettingChanged = HookProtoEvent(ME_DB_CONTACT_SETTINGCHANGED, gg_dbsettingchanged, gg);
#ifdef DEBUGMODE
			gg_netlog(gg, "gg_event(EV_PROTO_ONLOAD): loading modules...");
#endif
			// Init misc stuff
			gg_icolib_init();
			gg_gc_init(gg);
			gg_keepalive_init(gg);
			gg_img_init(gg);

			// Try to fetch user avatar
			gg_getuseravatar(gg);
			break;
		}
		case EV_PROTO_ONEXIT:
#ifdef DEBUGMODE
			gg_netlog(gg, "gg_event(EV_PROTO_ONEXIT)/gg_preshutdown(): signalling shutdown...");
#endif
			// Stop avatar request thread
			gg_uninitavatarrequestthread(gg);
			// Stop main connection session thread
#ifdef DEBUGMODE
			gg_netlog(gg, "gg_event(EV_PROTO_ONEXIT): Waiting until Server Thread finished, if needed.");
#endif
			gg_threadwait(gg, &gg->pth_sess);
			gg_img_shutdown(gg);
			gg_sessions_closedlg(gg);
			break;

		case EV_PROTO_ONOPTIONS:
			return gg_options_init(gg, wParam, lParam);

		case EV_PROTO_ONMENU:
			gg_menus_init(gg);
			break;

		case EV_PROTO_ONRENAME:
#ifdef DEBUGMODE
			gg_netlog(gg, "gg_event(EV_PROTO_ONRENAME): renaming account...");
#endif
			mir_free(gg->name);
			gg->name = gg_t2a(gg->proto.m_tszUserName);
			if (gg->hMenuRoot)
			{
				CLISTMENUITEM mi = {0};
				mi.cbSize = sizeof(mi);
				mi.flags = CMIM_NAME | CMIF_TCHAR | CMIF_KEEPUNTRANSLATED;
				mi.ptszName = GG_PROTONAME;
				CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)gg->hMenuRoot, (LPARAM)&mi);
			}
			break;
	}
	return TRUE;
}

//////////////////////////////////////////////////////////
// Module instance initialization
static GGPROTO *gg_proto_init(const char* pszProtoName, const TCHAR* tszUserName)
{
	DWORD dwVersion;
	GGPROTO *gg = (GGPROTO *)mir_alloc(sizeof(GGPROTO));
	char szVer[MAX_PATH];
	NETLIBUSER nlu = { 0 };

	ZeroMemory(gg, sizeof(GGPROTO));
	gg->proto.vtbl = (PROTO_INTERFACE_VTBL*)mir_alloc(sizeof(PROTO_INTERFACE_VTBL));
	// Are we running under unicode Miranda core ?
	CallService(MS_SYSTEM_GETVERSIONTEXT, MAX_PATH, (LPARAM)szVer);
	_strlwr(szVer); // make sure it is lowercase
	gg->unicode_core = (strstr(szVer, "unicode") != NULL);

	// Init mutexes
	InitializeCriticalSection(&gg->sess_mutex);
	InitializeCriticalSection(&gg->ft_mutex);
	InitializeCriticalSection(&gg->img_mutex);
	InitializeCriticalSection(&gg->modemsg_mutex);
	InitializeCriticalSection(&gg->avatar_mutex);
	InitializeCriticalSection(&gg->sessions_mutex);

	// Init instance names
	gg->proto.m_szModuleName = mir_strdup(pszProtoName);
	gg->proto.m_szProtoName = GGDEF_PROTONAME;
	gg->proto.m_iVersion = 2;

/* Anyway we won't get Unicode in GG yet */
#ifdef _UNICODE
	gg->name = gg->proto.m_tszUserName = mir_tstrdup(tszUserName);
#else
	gg->proto.m_tszUserName = gg->unicode_core ? (TCHAR *)mir_wstrdup((wchar_t *)tszUserName) : (TCHAR *)mir_strdup((char *)tszUserName);
	gg->name = gg_t2a(tszUserName);
#endif

	// Register netlib user
	nlu.cbSize = sizeof(nlu);
	nlu.flags = NUF_OUTGOING | NUF_INCOMING | NUF_HTTPCONNS;
	nlu.szSettingsModule = gg->proto.m_szModuleName;
	if (gg->unicode_core) {
		WCHAR name[128];
		_snwprintf(name, SIZEOF(name), TranslateW(L"%s connection"), gg->proto.m_tszUserName);
		nlu.ptszDescriptiveName = (TCHAR *)name;
		nlu.flags |= NUF_UNICODE;
	} else {
		char name[128];
		mir_snprintf(name, SIZEOF(name), Translate("%s connection"), gg->proto.m_tszUserName);
		nlu.ptszDescriptiveName = name;
	}
	gg->netlib = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu);

	// Register services
	gg_registerservices(gg);
	gg_setalloffline(gg);

	if((dwVersion = DBGetContactSettingDword(NULL, GG_PROTO, GG_PLUGINVERSION, 0)) < pluginInfo.version)
		gg_cleanuplastplugin(gg, dwVersion);

	gg_links_instance_init(gg);
	gg_initcustomfolders(gg);
	gg_initavatarrequestthread(gg);

	return gg;
}

static int gg_proto_uninit(PROTO_INTERFACE *proto)
{
	GGPROTO *gg = (GGPROTO *)proto;

#ifdef DEBUGMODE
	gg_netlog(gg, "gg_proto_uninit(): destroying protocol interface");
#endif

	// Destroy modules
	gg_img_destroy(gg);
	gg_keepalive_destroy(gg);
	gg_gc_destroy(gg);
	gg_links_instance_destroy(gg);

	if (gg->hMenuRoot)
		CallService(MS_CLIST_REMOVEMAINMENUITEM, (WPARAM)gg->hMenuRoot, 0);

	// Close handles
	LocalEventUnhook(gg->hookOptsInit);
	LocalEventUnhook(gg->hookUserInfoInit);
	LocalEventUnhook(gg->hookSettingDeleted);
	LocalEventUnhook(gg->hookSettingChanged);
	Netlib_CloseHandle(gg->netlib);

	// Destroy mutexes
	DeleteCriticalSection(&gg->sess_mutex);
	DeleteCriticalSection(&gg->ft_mutex);
	DeleteCriticalSection(&gg->img_mutex);
	DeleteCriticalSection(&gg->modemsg_mutex);
	DeleteCriticalSection(&gg->avatar_mutex);
	DeleteCriticalSection(&gg->sessions_mutex);

	// Free status messages
	if (gg->modemsg.online)    mir_free(gg->modemsg.online);
	if (gg->modemsg.away)      mir_free(gg->modemsg.away);
	if (gg->modemsg.dnd)       mir_free(gg->modemsg.dnd);
	if (gg->modemsg.freechat)  mir_free(gg->modemsg.freechat);
	if (gg->modemsg.invisible) mir_free(gg->modemsg.invisible);
	if (gg->modemsg.offline)   mir_free(gg->modemsg.offline);

	mir_free(gg->proto.m_szModuleName);
	mir_free(gg->proto.m_tszUserName);
	mir_free(gg->name);
	mir_free(gg->proto.vtbl);
	mir_free(gg);

	return 0;
}

//////////////////////////////////////////////////////////
// When plugin is loaded
int __declspec(dllexport) Load(PLUGINLINK * link)
{
	WSADATA wsaData;
	PROTOCOLDESCRIPTOR pd;

	pluginLink = link;
	mir_getMMI(&mmi);
	mir_getSHA1I(&sha1i);
	mir_getMD5I(&md5i);
	mir_getLI(&li);
	mir_getXI(&xi);

	// Init winsock
	if (WSAStartup(MAKEWORD( 1, 1 ), &wsaData))
		return 1;

	pcli = (CLIST_INTERFACE*)CallService(MS_CLIST_RETRIEVE_INTERFACE, 0, (LPARAM)hInstance);

	// Hook system events
	hHookModulesLoaded = HookEvent(ME_SYSTEM_MODULESLOADED, gg_modulesloaded);
	hHookPreShutdown = HookEvent(ME_SYSTEM_PRESHUTDOWN, gg_preshutdown);

	// Prepare protocol name
	ZeroMemory(&pd, sizeof(pd));
	pd.cbSize = sizeof(pd);
	pd.szName = GGDEF_PROTO;
	pd.fnInit = (pfnInitProto)gg_proto_init;
	pd.fnUninit = (pfnUninitProto)gg_proto_uninit;
	pd.type = PROTOTYPE_PROTOCOL;

	// Register module
	CallService(MS_PROTO_REGISTERMODULE, 0, (LPARAM) &pd);
	gg_links_instancemenu_init();

	return 0;
}

//////////////////////////////////////////////////////////
// When plugin is unloaded
int __declspec(dllexport) Unload()
{
	LocalEventUnhook(hHookModulesLoaded);
	LocalEventUnhook(hHookPreShutdown);

	// Cleanup WinSock
	WSACleanup();

	return 0;
}

//////////////////////////////////////////////////////////
// Forks a thread
void gg_forkthread(GGPROTO *gg, GGThreadFunc pFunc, void *param)
{
	CloseHandle((HANDLE)mir_forkthreadowner((pThreadFuncOwner)&pFunc, gg, param, NULL));
}

//////////////////////////////////////////////////////////
// Forks a thread and returns a pseudo handle for it
HANDLE gg_forkthreadex(GGPROTO *gg, GGThreadFunc pFunc, void *param, UINT *threadId)
{
	return (HANDLE)mir_forkthreadowner((pThreadFuncOwner)&pFunc, gg, param, threadId);
}

////////////////////////////////////////////////////////////
// Wait for thread to stop
void gg_threadwait(GGPROTO *gg, GGTHREAD *thread)
{
	if (!thread->hThread) return;
	while (WaitForSingleObjectEx(thread->hThread, INFINITE, TRUE) != WAIT_OBJECT_0);
	CloseHandle(thread->hThread);
	ZeroMemory(thread, sizeof(GGTHREAD));
}

//////////////////////////////////////////////////////////
// DEBUGING FUNCTIONS

struct
{
	int type;
	char *text;
}
static const ggdebug_eventype2string[] =
{
	{GG_EVENT_NONE,					"GG_EVENT_NONE"},
	{GG_EVENT_MSG,					"GG_EVENT_MSG"},
	{GG_EVENT_NOTIFY,				"GG_EVENT_NOTIFY"},
	{GG_EVENT_NOTIFY_DESCR,			"GG_EVENT_NOTIFY_DESCR"},
	{GG_EVENT_STATUS,				"GG_EVENT_STATUS"},
	{GG_EVENT_ACK,					"GG_EVENT_ACK"},
	{GG_EVENT_PONG,					"GG_EVENT_PONG"},
	{GG_EVENT_CONN_FAILED,			"GG_EVENT_CONN_FAILED"},
	{GG_EVENT_CONN_SUCCESS,			"GG_EVENT_CONN_SUCCESS"},
	{GG_EVENT_DISCONNECT,			"GG_EVENT_DISCONNECT"},
	{GG_EVENT_DCC_NEW,				"GG_EVENT_DCC_NEW"},
	{GG_EVENT_DCC_ERROR,			"GG_EVENT_DCC_ERROR"},
	{GG_EVENT_DCC_DONE,				"GG_EVENT_DCC_DONE"},
	{GG_EVENT_DCC_CLIENT_ACCEPT,	"GG_EVENT_DCC_CLIENT_ACCEPT"},
	{GG_EVENT_DCC_CALLBACK,			"GG_EVENT_DCC_CALLBACK"},
	{GG_EVENT_DCC_NEED_FILE_INFO,	"GG_EVENT_DCC_NEED_FILE_INFO"},
	{GG_EVENT_DCC_NEED_FILE_ACK,	"GG_EVENT_DCC_NEED_FILE_ACK"},
	{GG_EVENT_DCC_NEED_VOICE_ACK,	"GG_EVENT_DCC_NEED_VOICE_ACK"},
	{GG_EVENT_DCC_VOICE_DATA,		"GG_EVENT_DCC_VOICE_DATA"},
	{GG_EVENT_PUBDIR50_SEARCH_REPLY,"GG_EVENT_PUBDIR50_SEARCH_REPLY"},
	{GG_EVENT_PUBDIR50_READ,		"GG_EVENT_PUBDIR50_READ"},
	{GG_EVENT_PUBDIR50_WRITE,		"GG_EVENT_PUBDIR50_WRITE"},
	{GG_EVENT_STATUS60,				"GG_EVENT_STATUS60"},
	{GG_EVENT_NOTIFY60,				"GG_EVENT_NOTIFY60"},
	{GG_EVENT_USERLIST,				"GG_EVENT_USERLIST"},
	{GG_EVENT_IMAGE_REQUEST,		"GG_EVENT_IMAGE_REQUEST"},
	{GG_EVENT_IMAGE_REPLY,			"GG_EVENT_IMAGE_REPLY"},
	{GG_EVENT_DCC_ACK,				"GG_EVENT_DCC_ACK"},
	{GG_EVENT_DCC7_NEW,				"GG_EVENT_DCC7_NEW"},
	{GG_EVENT_DCC7_ACCEPT,			"GG_EVENT_DCC7_ACCEPT"},
	{GG_EVENT_DCC7_REJECT,			"GG_EVENT_DCC7_REJECT"},
	{GG_EVENT_DCC7_CONNECTED,		"GG_EVENT_DCC7_CONNECTED"},
	{GG_EVENT_DCC7_ERROR,			"GG_EVENT_DCC7_ERROR"},
	{GG_EVENT_DCC7_DONE,			"GG_EVENT_DCC7_DONE"},
	{GG_EVENT_DCC7_PENDING,			"GG_EVENT_DCC7_PENDING"},
	{GG_EVENT_XML_EVENT,			"GG_EVENT_XML_EVENT"},
	{GG_EVENT_DISCONNECT_ACK,		"GG_EVENT_DISCONNECT_ACK"},
	{GG_EVENT_XML_ACTION,			"GG_EVENT_XML_ACTION"},
	{GG_EVENT_TYPING_NOTIFICATION,	"GG_EVENT_TYPING_NOTIFICATION"},
	{GG_EVENT_USER_DATA,			"GG_EVENT_USER_DATA"},
	{GG_EVENT_MULTILOGON_MSG,		"GG_EVENT_MULTILOGON_MSG"},
	{GG_EVENT_MULTILOGON_INFO,		"GG_EVENT_MULTILOGON_INFO"},
	{-1,							"<unknown event>"}
};

const char *ggdebug_eventtype(struct gg_event *e)
{
	int i;
	for(i = 0; ggdebug_eventype2string[i].type != -1; i++)
		if(ggdebug_eventype2string[i].type == e->type)
			return ggdebug_eventype2string[i].text;
	return ggdebug_eventype2string[i].text;
}

#ifdef DEBUGMODE
void gg_debughandler(int level, const char *format, va_list ap)
{
	char szText[1024], *szFormat = _strdup(format);
	// Kill end line
	char *nl = strrchr(szFormat, '\n');
	if(nl) *nl = 0;

	strncpy(szText, "[libgadu] \0", sizeof(szText));

	mir_vsnprintf(szText + strlen(szText), sizeof(szText) - strlen(szText), szFormat, ap);
	CallService(MS_NETLIB_LOG, (WPARAM) NULL, (LPARAM) szText);
	free(szFormat);
}
#endif

////////////////////////////////////////////////////////////
// Log funcion
int gg_netlog(const GGPROTO *gg, const char *fmt, ...)
{
	va_list va;
	char szText[1024];

	va_start(va, fmt);
	mir_vsnprintf(szText, sizeof(szText), fmt, va);
	va_end(va);
	return CallService(MS_NETLIB_LOG, (WPARAM) gg->netlib, (LPARAM) szText);
}

//////////////////////////////////////////////////////////
// main DLL function
BOOL APIENTRY DllMain(HINSTANCE hInst, DWORD reason, LPVOID reserved)
{
	crc_gentable();
	hInstance = hInst;
#ifdef DEBUGMODE
	gg_debug_handler = gg_debughandler;
#endif
	return TRUE;
}
