/*

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 "skype.h"
#include "debug.h"
#include "skypeapi.h"
#include "skypesvc.h"
#include "contacts.h"
#include "utf8.h"
#include "pthread.h"
#include "gchat.h"
#include "m_toptoolbar.h"
#include "voiceservice.h"
#include "msglist.h"
#include "memlist.h"
#include <sys/timeb.h>
#ifndef INVALID_FILE_ATTRIBUTES
#define INVALID_FILE_ATTRIBUTES 0xFFFFFFFF
#endif
/*#ifdef _WIN64
#pragma comment (lib, "bufferoverflowU.lib")
#endif*/

#pragma warning (disable: 4706) // assignment within conditional expression

POPUPDATAT MessagePopup;

// Exported Globals
HWND hSkypeWnd=NULL, g_hWnd=NULL, hSkypeWndSecondary=NULL, hForbiddenSkypeWnd = NULL;
HANDLE SkypeReady, SkypeMsgReceived, hInitChat=NULL, httbButton=NULL, FetchMessageEvent=NULL;
BOOL SkypeInitialized=FALSE, MirandaShuttingDown=FALSE, PopupServiceExists=FALSE;
BOOL UseSockets=FALSE, bSkypeOut=FALSE, bProtocolSet=FALSE, bIsImoproxy=FALSE;
char skype_path[MAX_PATH], protocol=2, *pszProxyCallout=NULL, g_szProtoName[_MAX_FNAME]="SKYPE";
int SkypeStatus=ID_STATUS_OFFLINE, hSearchThread=-1, receivers=1;
long sendwatchers = 0, rcvwatchers = 0;
UINT ControlAPIAttach, ControlAPIDiscover;
LONG AttachStatus=-1;
HINSTANCE hInst;
HANDLE hProtocolAvatarsFolder;
char DefaultAvatarsFolder[MAX_PATH+1];
DWORD mirandaVersion;
int hLangpack = 0;

CRITICAL_SECTION RingAndEndcallMutex, QueryThreadMutex, TimeMutex;

// Module Internal Globals
HANDLE MessagePumpReady;
HANDLE hChatEvent=NULL, hChatMenu=NULL;
HANDLE hEvInitChat=NULL, hBuddyAdded=NULL;
HANDLE hMenuAddSkypeContact=NULL;

DWORD msgPumpThreadId = 0;
#ifdef SKYPEBUG_OFFLN
HANDLE GotUserstatus;
#endif

BOOL bModulesLoaded=FALSE;
char *RequestedStatus=NULL;	// To fix Skype-API Statusmode-bug
char cmdMessage[12]="MESSAGE", cmdPartner[8]="PARTNER";	// Compatibility commands



// Direct assignment of user properties to a DB-Setting
static const settings_map m_settings[]= {
		{"LANGUAGE", "Language1"},
		{"PROVINCE", "State"},
		{"CITY", "City"},
		{"PHONE_HOME", "Phone"},
		{"PHONE_OFFICE", "CompanyPhone"},
		{"PHONE_MOBILE", "Cellular"},
		{"HOMEPAGE", "Homepage"},
		{"ABOUT", "About"}
	};

// Imported Globals
extern status_map status_codes[];

BOOL (WINAPI *MyEnableThemeDialogTexture)(HANDLE, DWORD) = 0;

HMODULE hUxTheme = 0;

// function pointers, use typedefs for casting to shut up the compiler when using GetProcAddress()

typedef BOOL (WINAPI *PITA)();
typedef HANDLE (WINAPI *POTD)(HWND, LPCWSTR);
typedef UINT (WINAPI *PDTB)(HANDLE, HDC, int, int, RECT *, RECT *);
typedef UINT (WINAPI *PCTD)(HANDLE);
typedef UINT (WINAPI *PDTT)(HANDLE, HDC, int, int, LPCWSTR, int, DWORD, DWORD, RECT *);

PITA pfnIsThemeActive = 0;
POTD pfnOpenThemeData = 0;
PDTB pfnDrawThemeBackground = 0;
PCTD pfnCloseThemeData = 0;
PDTT pfnDrawThemeText = 0;

#define FIXED_TAB_SIZE 100                  // default value for fixed width tabs

typedef struct {
	char msgnum[16];
	BOOL getstatus;
	BOOL bIsRead;
	BOOL bDontMarkSeen;
	BOOL QueryMsgDirection;
	TYP_MSGLENTRY *pMsgEntry;
} fetchmsg_arg;

typedef struct {
	HANDLE hContact;
	char szId[16];
} msgsendwt_arg;

/*
 * visual styles support (XP+)
 * returns 0 on failure
 */

int InitVSApi()
{
    if((hUxTheme = LoadLibraryA("uxtheme.dll")) == 0)
        return 0;

    pfnIsThemeActive = (PITA)GetProcAddress(hUxTheme, "IsThemeActive");
    pfnOpenThemeData = (POTD)GetProcAddress(hUxTheme, "OpenThemeData");
    pfnDrawThemeBackground = (PDTB)GetProcAddress(hUxTheme, "DrawThemeBackground");
    pfnCloseThemeData = (PCTD)GetProcAddress(hUxTheme, "CloseThemeData");
    pfnDrawThemeText = (PDTT)GetProcAddress(hUxTheme, "DrawThemeText");

    MyEnableThemeDialogTexture = (BOOL (WINAPI *)(HANDLE, DWORD))GetProcAddress(hUxTheme, "EnableThemeDialogTexture");
    if(pfnIsThemeActive != 0 && pfnOpenThemeData != 0 && pfnDrawThemeBackground != 0 && pfnCloseThemeData != 0 && pfnDrawThemeText != 0) {
        return 1;
    }
    return 0;
}

/*
 * unload uxtheme.dll
 */

int FreeVSApi()
{
    if(hUxTheme != 0)
        FreeLibrary(hUxTheme);
    return 0;
}

// Plugin Info
PLUGININFOEX pluginInfo = {
	sizeof(PLUGININFOEX),
	"Skype Protocol",
	PLUGIN_MAKE_VERSION(0,0,0,52),
	"Support for Skype network",
	"leecher - tweety - jls17",
	"leecher@dose.0wnz.at - tweety@user.berlios.de",
	"� 2004-2011 leecher - tweety",
	"http://developer.berlios.de/projects/mgoodies/",
	UNICODE_AWARE,
	{ 0xa71f8335, 0x7b87, 0x4432, { 0xb8, 0xa3, 0x81, 0x47, 0x94, 0x31, 0xc6, 0xf5 } } // {A71F8335-7B87-4432-B8A3-81479431C6F5}
};

#define MAPDND	1	// Map Occupied to DND status and say that you support it
//#define MAPNA   1 // Map NA status to Away and say that you support it

/*                           P R O G R A M                                */

void RegisterToDbeditorpp(void)
{
    // known modules list
    if (ServiceExists("DBEditorpp/RegisterSingleModule"))
        CallService("DBEditorpp/RegisterSingleModule", (WPARAM)SKYPE_PROTONAME, 0);
}

/*void RegisterToUpdate(void)
{
	//Use for the Updater plugin
	if(ServiceExists(MS_UPDATE_REGISTER))
	{
		Update update = {0};
		char szVersion[16];

		update.szComponentName = pluginInfo.shortName;
		update.pbVersion = (BYTE *)CreateVersionStringPlugin((PLUGININFO *)&pluginInfo, szVersion);
		update.cpbVersion = (DWORD)strlen((char *)update.pbVersion);

#ifdef _WIN64
#ifdef _UNICODE
		update.szBetaUpdateURL = "http://dose.0wnz.at/miranda/Skype/Skype_protocol_unicode_x64.zip";
#else
		update.szBetaUpdateURL = "http://dose.0wnz.at/miranda/Skype/Skype_protocol_x64.zip";
#endif
		update.szBetaVersionURL = "http://dose.0wnz.at/miranda/Skype/";
		update.pbBetaVersionPrefix = (BYTE *)"SKYPE version ";
		update.szUpdateURL = update.szBetaUpdateURL;	// FIXME!!
		update.szVersionURL = update.szBetaVersionURL; // FIXME
		update.pbVersionPrefix = update.pbBetaVersionPrefix; //FIXME
#else 
#ifdef _UNICODE
		update.szBetaUpdateURL = "http://dose.0wnz.at/miranda/Skype/Skype_protocol_unicode.zip";
#else
	    update.szBetaUpdateURL = "http://dose.0wnz.at/miranda/Skype/Skype_protocol.zip";
#endif
		update.szBetaVersionURL = "http://dose.0wnz.at/miranda/Skype/";
		update.pbBetaVersionPrefix = (BYTE *)"SKYPE version ";
#ifdef _UNICODE
		update.szUpdateURL = update.szBetaUpdateURL;	// FIXME!!
		update.szVersionURL = update.szBetaVersionURL; // FIXME
		update.pbVersionPrefix = update.pbBetaVersionPrefix; //FIXME
#else
		update.szUpdateURL = "http://addons.miranda-im.org/feed.php?dlfile=3200";
		update.szVersionURL = "http://addons.miranda-im.org/details.php?action=viewfile&id=3200";
		update.pbVersionPrefix = (BYTE *)"<span class=\"fileNameHeader\">Skype Protocol</span>";
#endif
#endif

		update.cpbVersionPrefix = (DWORD)strlen((char *)update.pbVersionPrefix);
		update.cpbBetaVersionPrefix = (DWORD)strlen((char *)update.pbBetaVersionPrefix);

		CallService(MS_UPDATE_REGISTER, 0, (WPARAM)&update);

	}
}*/

/*
 * ShowMessage
 *
 * Shows a popup, if the popup plugin is enabled.
 * mustShow: 1 -> If Popup-Plugin is not available/disabled, show Message
 *                in a Messagewindow
 *                If the Popup-Plugin is enabled, let the message stay on
 *                screen until someone clicks it away.
 *           0 -> If Popup-Plugin is not available/disabled, skip message
 * Returns 0 on success, -1 on failure
 *
 */
int ShowMessage(int iconID, TCHAR *lpzText, int mustShow) {



	if (DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "SuppressErrors", 0)) return -1;
	lpzText=TranslateTS(lpzText);

	if (bModulesLoaded && PopupServiceExists && ServiceExists(MS_POPUP_ADDPOPUPT) && DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "UsePopup", 0) && !MirandaShuttingDown) {
		BOOL showPopup, popupWindowColor;
		unsigned int popupBackColor, popupTextColor;
		int popupTimeSec;

		popupTimeSec = DBGetContactSettingDword(NULL, SKYPE_PROTONAME, "popupTimeSecErr", 4);
		popupTextColor = DBGetContactSettingDword(NULL, SKYPE_PROTONAME, "popupTextColorErr", GetSysColor(COLOR_WINDOWTEXT));
		popupBackColor = DBGetContactSettingDword(NULL, SKYPE_PROTONAME, "popupBackColorErr", GetSysColor(COLOR_BTNFACE));
		popupWindowColor = ( 0 != DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "popupWindowColorErr", TRUE));
		showPopup = ( 0 != DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "showPopupErr", TRUE));

		MessagePopup.lchContact = NULL;
		MessagePopup.lchIcon = LoadIcon(hInst,MAKEINTRESOURCE(iconID));
		MessagePopup.colorBack = ! popupWindowColor ? popupBackColor : GetSysColor(COLOR_BTNFACE);
		MessagePopup.colorText = ! popupWindowColor ? popupTextColor : GetSysColor(COLOR_WINDOWTEXT);
		MessagePopup.iSeconds = popupTimeSec;
		MessagePopup.PluginData = (void *)1;

		lstrcpy(MessagePopup.lptzText, lpzText);

#ifdef _UNICODE
		mbstowcs (MessagePopup.lptzContactName, SKYPE_PROTONAME, strlen(SKYPE_PROTONAME)+1);
#else
		lstrcpy(MessagePopup.lptzContactName, SKYPE_PROTONAME);
#endif

		if(showPopup)
			CallService(MS_POPUP_ADDPOPUPT,(WPARAM)&MessagePopup,0);

		return 0;
	}
	else {

		if (mustShow==1) MessageBox(NULL,lpzText,_T("Skype Protocol"), MB_OK | MB_ICONWARNING);
		return 0;
	}
}
#ifdef _UNICODE
int ShowMessageA(int iconID, char *lpzText, int mustShow) {
	WCHAR *lpwText;
	int iRet;
	size_t len = mbstowcs (NULL, lpzText, strlen(lpzText));
	if (len == -1 || !(lpwText = calloc(len+1,sizeof(WCHAR)))) return -1;
	mbstowcs (lpwText, lpzText, strlen(lpzText));
	iRet = ShowMessage(iconID, lpwText, mustShow);
	free (lpwText);
	return iRet;
}
#endif

// processing Hooks

int HookContactAdded(WPARAM wParam, LPARAM lParam) {
	char *szProto;

	UNREFERENCED_PARAMETER(lParam);

	szProto = (char*)CallService( MS_PROTO_GETCONTACTBASEPROTO, wParam, 0 );
	if (szProto!=NULL && !strcmp(szProto, SKYPE_PROTONAME))
		add_contextmenu((HANDLE)wParam);
	return 0;
}

int HookContactDeleted(WPARAM wParam, LPARAM lParam) {
	char *szProto;

	UNREFERENCED_PARAMETER(lParam);

	szProto = (char*)CallService( MS_PROTO_GETCONTACTBASEPROTO, wParam, 0 );
	if (szProto!=NULL && !strcmp(szProto, SKYPE_PROTONAME)) {
		DBVARIANT dbv;
		int retval;

		if (DBGetContactSettingString((HANDLE)wParam, SKYPE_PROTONAME, SKYPE_NAME, &dbv)) return 1;
		retval=SkypeSend("SET USER %s BUDDYSTATUS 1", dbv.pszVal);
		DBFreeVariant(&dbv);
		if (retval) return 1;
	}
	return 0;
}

void GetInfoThread(HANDLE hContact) {
	DBVARIANT dbv;
	int i;
	char *ptr;
	BOOL bSetNick = FALSE;
	// All properties are already handled in the WndProc, so we just consume the
	// messages here to do proper ERROR handling
	// If you add something here, be sure to handle it in WndProc, but let it
	// fall through there so that message gets added to the queue in order to be
	// consumed by SkypeGet
	char *pszProps[] = {
		"BIRTHDAY", "COUNTRY", "SEX", "MOOD_TEXT", "TIMEZONE", "IS_VIDEO_CAPABLE"};


	LOG (("GetInfoThread started."));
	EnterCriticalSection (&QueryThreadMutex);
	if (DBGetContactSettingString(hContact, SKYPE_PROTONAME, SKYPE_NAME, &dbv))
	{
		LOG (("GetInfoThread terminated, cannot find Skype Name for contact %08X.", hContact));
		LeaveCriticalSection (&QueryThreadMutex);
		return;
	}

	if (ptr=SkypeGet ("USER", dbv.pszVal, "DISPLAYNAME")) {
		// WndProc sets Nick accordingly
		if (*ptr) bSetNick = TRUE;
		free (ptr);
	}

	if (ptr=SkypeGet ("USER", dbv.pszVal, "FULLNAME")) {
		if (*ptr && !bSetNick && DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "ShowFullname", 1)) {
			// No Displayname and FULLNAME requested
			SkypeDBWriteContactSettingUTF8String(hContact, SKYPE_PROTONAME, "Nick", ptr);
			bSetNick = TRUE;
		}
		free (ptr);
	}

	if (!bSetNick) {
		// Still no nick set, so use SKYPE Nickname
		DBWriteContactSettingString(hContact, SKYPE_PROTONAME, "Nick", dbv.pszVal);
	}


	if (!bIsImoproxy)
	{
		for (i=0; i<sizeof(pszProps)/sizeof(pszProps[0]); i++)
			if (ptr=SkypeGet ("USER", dbv.pszVal, pszProps[i])) free (ptr);
	} else {
		if (ptr=SkypeGet ("USER", dbv.pszVal, "MOOD_TEXT")) free (ptr);
	}

	if (protocol >= 7 || bIsImoproxy) {
		// Notify about the possibility of an avatar
		ACKDATA ack = {0};
		ack.cbSize = sizeof( ACKDATA );
		ack.szModule = SKYPE_PROTONAME;
		ack.hContact = hContact;
		ack.type = ACKTYPE_AVATAR;
		ack.result = ACKRESULT_STATUS;

		CallService( MS_PROTO_BROADCASTACK, 0, ( LPARAM )&ack );
		//if (ptr=SkypeGet ("USER", dbv.pszVal, "RICH_MOOD_TEXT")) free (ptr);
	}

	if (!bIsImoproxy)
	{
		for (i=0; i<sizeof(m_settings)/sizeof(m_settings[0]); i++)
			if (ptr=SkypeGet ("USER", dbv.pszVal, m_settings[i].SkypeSetting)) free (ptr);
	}

	ProtoBroadcastAck(SKYPE_PROTONAME, hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE) 1, 0);
	LeaveCriticalSection(&QueryThreadMutex);
	DBFreeVariant(&dbv);
    LOG (("GetInfoThread terminated gracefully."));
}

time_t SkypeTime(time_t *timer) {
	struct _timeb tb;

	EnterCriticalSection (&TimeMutex);
	_ftime(&tb);
	if (timer) *timer=tb.time;
	LeaveCriticalSection (&TimeMutex);
	return tb.time;
}


void BasicSearchThread(char *nick) {
	PROTOSEARCHRESULT psr={0};
	char *cmd=NULL, *token=NULL, *ptr=NULL, *nextoken;
	time_t st;

    LOG (("BasicSearchThread started."));
	EnterCriticalSection (&QueryThreadMutex);
	SkypeTime(&st);
	if (SkypeSend("SEARCH USERS %s", nick)==0 && (cmd=SkypeRcvTime("USERS", st, INFINITE))) {
		if (strncmp(cmd, "ERROR", 5)) {
			psr.cbSize=sizeof(psr);
			for (token=strtok_r(cmd+5, ", ", &nextoken); token; token=strtok_r(NULL, ", ", &nextoken)) {
				TCHAR *t = mir_utf8decodeT(token), *_ptr = NULL;
				psr.nick=psr.id=t;
				psr.lastName=NULL;
				psr.firstName=NULL;
				psr.email=NULL;
				if (ptr=SkypeGet("USER", token, "FULLNAME")) {
					// We cannot use strtok() to seperate first & last name here,
					// because we already use it for parsing the user list
					// So we use our own function
					_ptr = mir_utf8decodeT(ptr);
					if (psr.lastName=_tcschr(_ptr, ' ')) {
						*psr.lastName=0;
						psr.lastName++;
						LOG(("BasicSearchThread: lastName=%s", psr.lastName));
					}
					psr.firstName=_ptr;
					LOG(("BasicSearchThread: firstName=%s", psr.firstName));
				}
				ProtoBroadcastAck(SKYPE_PROTONAME, NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)hSearchThread, (LPARAM)(PROTOSEARCHRESULT*)&psr);
				if (ptr) free(ptr);
				if(t) mir_free(t);
				if(_ptr) mir_free(_ptr);
			}
		} else {
			OUT(cmd);
		}
		free(cmd);
	}
	ProtoBroadcastAck(SKYPE_PROTONAME, NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)hSearchThread, 0);
	free(nick);
	LeaveCriticalSection(&QueryThreadMutex);
    LOG (("BasicSearchThread terminated gracefully."));
	return;
}

INT_PTR SkypeDBWriteContactSettingUTF8String(HANDLE hContact,const char *szModule,const char *szSetting,const char *val)
{
	DBCONTACTWRITESETTING cws;
	INT_PTR iRet;

	// Try to save it as UTF8 sting to DB. If this doesn't succeed (i.e. older Miranda version), we convert
	// accordingly and try to save again.

	cws.szModule=szModule;
	cws.szSetting=szSetting;
	cws.value.type=DBVT_UTF8;
	cws.value.pszVal=(char*)val;
	// DBVT_UTF8 support started with version 0.5.0.0, right...?
	if (mirandaVersion < 0x050000 || (iRet = CallService(MS_DB_CONTACT_WRITESETTING,(WPARAM)hContact,(LPARAM)&cws)))
	{
		// Failed, try to convert and then try again
		cws.value.type=DBVT_TCHAR;
		if (!(cws.value.ptszVal = make_tchar_string((const unsigned char*)val))) return -1;
		iRet = CallService(MS_DB_CONTACT_WRITESETTING,(WPARAM)hContact,(LPARAM)&cws);
		free (cws.value.pszVal);
	}
	return iRet;
}


// added by TioDuke
void GetDisplaynameThread(char *dummy) {
	DBVARIANT dbv;
	char *ptr;

	UNREFERENCED_PARAMETER(dummy);

	LOG(("GetDisplaynameThread started."));
	if (DBGetContactSettingString(NULL, SKYPE_PROTONAME, SKYPE_NAME, &dbv)) {
		LOG(("GetDisplaynameThread terminated."));
		return;
	}
	EnterCriticalSection(&QueryThreadMutex);
    if ((ptr=SkypeGet("USER", dbv.pszVal, "FULLNAME"))) {
		if (*ptr) SkypeDBWriteContactSettingUTF8String(NULL, SKYPE_PROTONAME, "Nick", ptr);
		free(ptr);
	}
	DBFreeVariant(&dbv);
	LeaveCriticalSection(&QueryThreadMutex);
    LOG(("GetDisplaynameThread terminated gracefully."));
}


// Starts importing history from Skype
INT_PTR ImportHistory(WPARAM wParam, LPARAM lParam) {
	DBVARIANT dbv;

	UNREFERENCED_PARAMETER(lParam);

	if (DBGetContactSettingByte((HANDLE)wParam, SKYPE_PROTONAME, "ChatRoom", 0)) {
		if (DBGetContactSettingString((HANDLE)wParam, SKYPE_PROTONAME, "ChatRoomID", &dbv)) return 0;
		SkypeSend ("GET CHAT %s CHATMESSAGES", dbv.pszVal);
	} else {
		if (DBGetContactSettingString((HANDLE)wParam, SKYPE_PROTONAME, SKYPE_NAME, &dbv)) return 0;
		SkypeSend("SEARCH %sS %s", cmdMessage, dbv.pszVal);
	}
	DBFreeVariant(&dbv);
	return 0;
}

int SearchFriends(void) {
	char *ptr, *token, *pStat, *nextoken;
	int iRet = 0;
	time_t st;

	SkypeTime(&st);
	if (SkypeSend("SEARCH FRIENDS")!=-1 && (ptr=SkypeRcvTime("USERS", st, INFINITE)))
	{
		if (strncmp(ptr, "ERROR", 5)) {
			if (ptr+5) {
				for (token=strtok_r(ptr+5, ", ", &nextoken); token; token=strtok_r(NULL, ", ", &nextoken)) {
					if (!(pStat = SkypeGet("USER", token, "ONLINESTATUS")))
					{
						iRet = -1;
						break;
					}
					free (pStat);
				}
			}
		} else iRet=-1;
		free(ptr);
	} else iRet=-1;
	return iRet;
}

void __cdecl SearchUsersWaitingMyAuthorization(void *dummy) {
	char *cmd, *token, *nextoken;

	UNREFERENCED_PARAMETER(dummy);

	if (SkypeSend("#UWA SEARCH USERSWAITINGMYAUTHORIZATION")) return;
	if (!(cmd=SkypeRcv("#UWA USERS", INFINITE))) return;
	if (!strncmp(cmd, "ERROR", 5)) {
		free(cmd);
		return;
	}

	token=strtok_r(cmd+10, ", ", &nextoken);
	while (token) {
		CCSDATA ccs={0};
		PROTORECVEVENT pre={0};
		HANDLE hContact;
		char *firstname=NULL, *lastname=NULL, *pCurBlob;

		LOG(("Awaiting auth: %s", token));
		ccs.szProtoService=PSR_AUTH;
		ccs.hContact=hContact=add_contact(token, PALF_TEMPORARY);
		ccs.wParam=0;
		ccs.lParam=(LPARAM)&pre;
		pre.flags=0;
		pre.timestamp=(DWORD)SkypeTime(NULL);

		/* blob is: */
		//DWORD protocolSpecific HANDLE hContact
		//ASCIIZ nick, firstName, lastName, e-mail, requestReason
		if (firstname=SkypeGet("USER", token, "FULLNAME"))
			if (lastname=strchr(firstname, ' ')) {
				*lastname=0;
				lastname++;
			}

		pre.lParam=sizeof(DWORD)*2 + strlen(token)+5;
		if (firstname) pre.lParam += strlen(firstname);
		if (lastname) pre.lParam += strlen(lastname);
		if (pre.szMessage  = pCurBlob = (char *)calloc(1, pre.lParam)) {
			pCurBlob+=sizeof(DWORD); // Not used
			*(DWORD*)pCurBlob = (DWORD)hContact;  pCurBlob += sizeof(DWORD);
			strcpy((char *)pCurBlob,token);       pCurBlob += strlen((char *)pCurBlob)+1;
			if (firstname) {
				strcpy((char *)pCurBlob,firstname);
				if (lastname) {
					pCurBlob+=strlen((char *)pCurBlob)+1;
					strcpy((char *)pCurBlob,lastname);
				}
			}
			CallService(MS_PROTO_CHAINRECV,0,(LPARAM)&ccs);
			free(pre.szMessage);
		}
		if (firstname) free(firstname);
		token=strtok_r(NULL, ", ", &nextoken);
	}
	free(cmd);
	return;
}

void SearchFriendsThread(char *dummy) {
	UNREFERENCED_PARAMETER(dummy);

	if (!SkypeInitialized) return;
    LOG(("SearchFriendsThread started."));
	EnterCriticalSection(&QueryThreadMutex);
	SkypeInitialized=FALSE;
	SearchFriends();
	SkypeInitialized=TRUE;
	LeaveCriticalSection(&QueryThreadMutex);
    LOG(("SearchFriendsThread terminated gracefully."));
}

void __cdecl SearchRecentChats(void *dummy) {
	char *cmd, *token, *nextoken;

	UNREFERENCED_PARAMETER(dummy);

	if (SkypeSend("#RCH SEARCH RECENTCHATS")) return;
	if (!(cmd=SkypeRcv("#RCH CHATS", INFINITE))) return;
	if (!strncmp(cmd, "ERROR", 5)) {
		free(cmd);
		return;
	}

	for (token=strtok_r(cmd+10, ", ", &nextoken); token; token=strtok_r(NULL, ", ", &nextoken)) {
		char *pszStatus = SkypeGet ("CHAT", token, "STATUS");

		if (pszStatus) {
			if (!strcmp(pszStatus, "MULTI_SUBSCRIBED")) {
				// Add chatrooms for active multisubscribed chats
				/*if (!find_chatA(token)) */{
					char *pszTopic;

					EnterCriticalSection (&QueryThreadMutex);
					ChatStart(token, TRUE);
					if (pszTopic = SkypeGet ("CHAT", token, "TOPIC")) {
						TCHAR *psztChatName, *psztTopic;

						if (!*pszTopic) {
							free (pszTopic);
							if (pszTopic = SkypeGet ("CHAT", token, "FRIENDLYNAME"));
						}
						psztChatName = make_nonutf_tchar_string((const unsigned char*)token);
						psztTopic = make_tchar_string((const unsigned char*)pszTopic);
						SetChatTopic (psztChatName, psztTopic, FALSE);
						free_nonutf_tchar_string(psztChatName);
						free (psztTopic);
						free (pszTopic);
					}
					LeaveCriticalSection (&QueryThreadMutex);
				}
			}
			free (pszStatus);
		}
	}
	free(cmd);
	return;
}


void __cdecl SkypeSystemInit(char *dummy) {
	static BOOL Initializing=FALSE;
	DBVARIANT dbv={0};

	UNREFERENCED_PARAMETER(dummy);

    LOG (("SkypeSystemInit thread started."));
	if (SkypeInitialized || Initializing)
	{
		LOG (("SkypeSystemInit terminated, nothing to do."));
		return;
	}
	Initializing=TRUE;
// Do initial Skype-Tasks
	logoff_contacts(FALSE);
// Add friends

	// Clear currentuserhandle entries from queue
	while (testfor ("CURRENTUSERHANDLE", 0));
	if (SkypeSend(SKYPE_PROTO)==-1 || !testfor("PROTOCOL", INFINITE) ||
		SkypeSend("GET CURRENTUSERHANDLE")==-1 ||
		SkypeSend("GET PRIVILEGE SKYPEOUT")==-1) {
		Initializing=FALSE;
        LOG (("SkypeSystemInit thread stopped with failure."));
		return;
	}

	if(DBGetContactSettingString(NULL,SKYPE_PROTONAME,"LoginUserName",&dbv) == 0)
	{
		if (*dbv.pszVal)
		{
			char *pszUser;

			// Username is set in Plugin, therefore we need to match it
			// against CURRENTUSERHANDLE
			if (pszUser = SkypeRcv ("CURRENTUSERHANDLE", INFINITE))
			{
				memmove (pszUser, pszUser+18, strlen(pszUser+17));
				if (_stricmp(dbv.pszVal, pszUser))
				{
					char szError[256];

					// Doesn't match, maybe we have a second Skype instance we have to take
					// care of? If in doubt, let's wait a while for it to report its hWnd to us.
					LOG (("Userhandle %s doesn't match username %s from settings", pszUser, dbv.pszVal));
					if (!hSkypeWndSecondary) Sleep(3000);
					if (hSkypeWndSecondary)
					{
						hSkypeWnd = hSkypeWndSecondary;
						hSkypeWndSecondary = NULL;
						Initializing=FALSE;
						while (testfor ("CURRENTUSERHANDLE", 0));
						LOG (("Trying to init secondary Skype instance"));
						SkypeSystemInit(dummy);
					}
					else
					{
						hForbiddenSkypeWnd = hSkypeWnd;

						// If we need to start Skype as secondary instance, we should do it now
						if (DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "StartSkype", 1) &&
							DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "secondary", 0))
						{
							int oldstatus;

							hSkypeWnd = NULL;
							AttachStatus=-1;
							if (g_hWnd) KillTimer (g_hWnd, 1);
							oldstatus = SkypeStatus;
							InterlockedExchange((long *)&SkypeStatus, ID_STATUS_CONNECTING);
							ProtoBroadcastAck(SKYPE_PROTONAME, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) oldstatus, SkypeStatus);
							ConnectToSkypeAPI(skype_path, 1);
						}
						if (hForbiddenSkypeWnd == hSkypeWnd && !hSkypeWndSecondary)
						{
							int oldstatus;

							sprintf (szError, "Username '%s' provided by Skype API doesn't match username '%s' in "
								"your settings. Please either remove username setting in you configuration or correct "
								"it. Will not connect!", pszUser, dbv.pszVal);
							OUTPUTA (szError);
							Initializing=FALSE;
							AttachStatus=-1;
							logoff_contacts(FALSE);
							if (g_hWnd) KillTimer (g_hWnd, 1);
							hSkypeWnd = NULL;
							oldstatus = SkypeStatus;
							InterlockedExchange((long *)&SkypeStatus, ID_STATUS_OFFLINE);
							ProtoBroadcastAck(SKYPE_PROTONAME, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) oldstatus, SkypeStatus);
						}
					}
				}
				free (pszUser);
			}
		}
		DBFreeVariant(&dbv);
		if (!Initializing) return;
	}

#ifdef SKYPEBUG_OFFLN
    if (!ResetEvent(GotUserstatus) || SkypeSend("GET USERSTATUS")==-1 ||
		WaitForSingleObject(GotUserstatus, INFINITE)==WAIT_FAILED)
	{
        LOG (("SkypeSystemInit thread stopped with failure."));
		Initializing=FALSE;
		return;
	}
	if (SkypeStatus!=ID_STATUS_OFFLINE)
#endif
	if (SearchFriends()==-1) {
        LOG (("SkypeSystemInit thread stopped with failure."));
		Initializing=FALSE;
		return;
	}
	if (protocol>=5 || bIsImoproxy) {
		SkypeSend ("CREATE APPLICATION libpurple_typing");
		testfor ("CREATE APPLICATION libpurple_typing", 2000);
	}
	if (protocol>=5 || bIsImoproxy) {
		SearchUsersWaitingMyAuthorization(NULL);
		if (DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "UseGroupchat", 0))
			SearchRecentChats(NULL);
	}
	SkypeSend("SEARCH MISSED%sS", cmdMessage);


#ifndef SKYPEBUG_OFFLN
	if (SkypeSend("GET USERSTATUS")==-1)
	{
        LOG (("SkypeSystemInit thread stopped with failure."));
		Initializing=FALSE;
		return;
	}
#endif
	SetTimer (g_hWnd, 1, PING_INTERVAL, NULL);
	SkypeInitialized=TRUE;
	Initializing=FALSE;
	LOG (("SkypeSystemInit thread terminated gracefully."));
	return;
}

void FirstLaunch(char *dummy) {
	int counter=0;

	UNREFERENCED_PARAMETER(dummy);

	LOG (("FirstLaunch thread started."));
	if (!DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "StartSkype", 1) || ConnectToSkypeAPI(skype_path, FALSE)==-1)
	{
		int oldstatus=SkypeStatus;

		LOG(("OnModulesLoaded starting offline.."));
		InterlockedExchange((long *)&SkypeStatus, ID_STATUS_OFFLINE);
		ProtoBroadcastAck(SKYPE_PROTONAME, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) oldstatus, SkypeStatus);
	}
	if (AttachStatus==-1 || AttachStatus==SKYPECONTROLAPI_ATTACH_REFUSED || AttachStatus==SKYPECONTROLAPI_ATTACH_NOT_AVAILABLE) {
		LOG (("FirstLaunch thread stopped because of invalid Attachstatus."));
		return;
	}

	// When you launch Skype and Attach is Successfull, it still takes some time
	// until it becomes available for receiving messages.
	// Let's probe this with PINGing
	LOG(("CheckIfApiIsResponding Entering test loop"));
	for ( ;; ) {
		LOG(("Test #%d", counter));
		if (SkypeSend("PING")==-1) counter ++; else break;
		if (counter>=20) {
			OUTPUT(_T("Cannot reach Skype API, plugin disfunct."));
			LOG (("FirstLaunch thread stopped: cannot reach Skype API."));
			return;
		}
		Sleep(500);
	}
	LOG(("CheckIfApiIsResponding: Testing for PONG"));
	testfor("PONG", 2000); // Flush PONG from MsgQueue

	forkthread(( pThreadFunc )SkypeSystemInit, 0, NULL);
	LOG (("FirstLaunch thread terminated gracefully."));
}

int CreateTopToolbarButton(WPARAM wParam, LPARAM lParam) {
	TTBButton ttb={0};

	UNREFERENCED_PARAMETER(wParam);
	UNREFERENCED_PARAMETER(lParam);

	ttb.cbSize = sizeof(ttb);
	ttb.dwFlags = TTBBF_VISIBLE|TTBBF_SHOWTOOLTIP|TTBBF_DRAWBORDER;
	ttb.hbBitmapDown = ttb.hbBitmapUp = LoadBitmap(hInst,MAKEINTRESOURCE(IDB_CALL));
	ttb.pszServiceDown = ttb.pszServiceUp = SKYPEOUT_CALL;
	ttb.name=Translate("Do a SkypeOut-call");
	if ((int)(httbButton=(HANDLE)CallService(MS_TTB_ADDBUTTON, (WPARAM)&ttb, 0))==-1) httbButton=0;
	return 0;
}


int OnModulesLoaded(WPARAM wParam, LPARAM lParam) {
	bModulesLoaded=TRUE;

	UNREFERENCED_PARAMETER(wParam);
	UNREFERENCED_PARAMETER(lParam);

	PopupServiceExists = ServiceExists(MS_POPUP_ADDPOPUPEX);

	logoff_contacts(FALSE);

	HookEventsLoaded();
	//RegisterToUpdate();
	RegisterToDbeditorpp();
	VoiceServiceModulesLoaded();
	GCInit();

	add_contextmenu(NULL);
	if ( ServiceExists( MS_GC_REGISTER ))
	{
		GCREGISTER gcr = {0};
		static COLORREF crCols[1] = {0};
		char szEvent[MAXMODULELABELLENGTH];

		gcr.cbSize = sizeof( GCREGISTER );
		gcr.dwFlags = GC_CHANMGR | GC_TCHAR; // |GC_ACKMSG; // TODO: Not implemented yet
        gcr.ptszModuleDispName = _T("Skype protocol");
		gcr.pszModule = SKYPE_PROTONAME;
		if (CallService(MS_GC_REGISTER, 0, (LPARAM)&gcr))
		{
			OUTPUT(_T("Unable to register with Groupchat module!"));
		}
		_snprintf (szEvent, sizeof(szEvent), "%s\\ChatInit", SKYPE_PROTONAME);
		hInitChat = CreateHookableEvent(szEvent);
		hEvInitChat = HookEvent(szEvent, ChatInit);

		hChatEvent = HookEvent(ME_GC_EVENT, GCEventHook);
		hChatMenu = HookEvent(ME_GC_BUILDMENU, GCMenuHook);
        CreateServiceFunction (SKYPE_CHATNEW, SkypeChatCreate);
		CreateProtoService (PS_LEAVECHAT, GCOnLeaveChat);
		CreateProtoService (PS_JOINCHAT, GCOnJoinChat);
	}
	// Try folder service first
	hProtocolAvatarsFolder = NULL;
	if (ServiceExists(MS_FOLDERS_REGISTER_PATH))
	{
		char *tmpPath;

		if (!ServiceExists (MS_UTILS_REPLACEVARS) || !(tmpPath = Utils_ReplaceVars("%miranda_avatarcache%")))
			tmpPath = PROFILE_PATH;
		mir_snprintf(DefaultAvatarsFolder, sizeof(DefaultAvatarsFolder), "%s\\%s", tmpPath, SKYPE_PROTONAME);
		hProtocolAvatarsFolder = (HANDLE) FoldersRegisterCustomPath(SKYPE_PROTONAME, "Avatars Cache", DefaultAvatarsFolder);
	}

	if (hProtocolAvatarsFolder == NULL)
	{
		// Use defaults
		CallService(MS_DB_GETPROFILEPATH, (WPARAM) MAX_PATH, (LPARAM) DefaultAvatarsFolder);
		mir_snprintf(DefaultAvatarsFolder, sizeof(DefaultAvatarsFolder), "%s\\%s", DefaultAvatarsFolder, SKYPE_PROTONAME);
		CreateDirectoryA(DefaultAvatarsFolder, NULL);
	}

	forkthread(( pThreadFunc )FirstLaunch, 0, NULL);
	return 0;
}

void FetchMessageThread(fetchmsg_arg *pargs) {
	char *who=NULL, *type=NULL, *chat=NULL, *users=NULL, *msg=NULL, *status=NULL;
	char *ptr, *msgptr, szPartnerHandle[32], szBuf[128];
	int direction=0, msglen = 0;
	DWORD timestamp = 0, lwr=0;
    CCSDATA ccs={0};
    PROTORECVEVENT pre={0};
    HANDLE hContact = NULL, hDbEvent, hChat = NULL;
	DBEVENTINFO dbei={0};
	DBVARIANT dbv={0};
	fetchmsg_arg args;
	BOOL bEmoted=FALSE, isGroupChat=FALSE, bHasPartList=FALSE;
	BOOL bUseGroupChat = DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "UseGroupchat", 0);

	if (!pargs) return;
	args = *pargs;
	free (pargs);

	sprintf (szPartnerHandle, "%s_HANDLE", cmdPartner);
	pre.lParam = strtoul(args.msgnum, NULL, 10);
	if (args.bIsRead) pre.flags |= PREF_CREATEREAD;
	//pEvent = MsgList_FindMessage(pre.lParam);

	// Get Timestamp
	if (!args.pMsgEntry || !args.pMsgEntry->tEdited) {
		if (!(ptr=SkypeGet (cmdMessage, args.msgnum, "TIMESTAMP"))) return;
		if (strncmp(ptr, "ERROR", 5)) timestamp=atol(ptr);
		else timestamp=(DWORD)SkypeTime(NULL);
		free(ptr);
	} else timestamp=(DWORD)(args.pMsgEntry->tEdited);

	__try {
		// Get Chatname (also to determine if we need to relay this to a groupchat)
		if (!(chat=SkypeGetErr (cmdMessage, args.msgnum, "CHATNAME"))) __leave;
		if (hChat = find_chatA(chat)) isGroupChat=TRUE;

		// Get chat status
		if ((status=SkypeGetErr ("CHAT", chat, "STATUS")) &&
			!strcmp(status, "MULTI_SUBSCRIBED")) isGroupChat=TRUE;

		// Get chat type
		if (!(type=SkypeGetErr (cmdMessage, args.msgnum, "TYPE"))) __leave;
		bEmoted = strcmp(type, "EMOTED")==0;
		if (strcmp(type, "MULTI_SUBSCRIBED")==0) isGroupChat=TRUE;

		// Group chat handling
		if (isGroupChat && strcmp(type, "TEXT") && strcmp(type, "SAID") && strcmp(type, "UNKNOWN") && !bEmoted) {
			if (bUseGroupChat) {
				BOOL bAddedMembers = FALSE;

				if (!strcmp(type,"SAWMEMBERS") || !strcmp(type, "CREATEDCHATWITH"))
				{
					// We have a new Groupchat
					LOG(("FetchMessageThread CHAT SAWMEMBERS"));
					if (!hChat) ChatStart(chat, FALSE);
					__leave;
				}
				if (!strcmp(type,"KICKED"))
				{
					GCDEST gcd = {0};
					GCEVENT gce = {0};
					CONTACTINFO ci = {0};

					if (!hChat) __leave;
					gcd.pszModule = SKYPE_PROTONAME;
					gcd.ptszID = make_nonutf_tchar_string((const unsigned char*)chat);
					gcd.iType = GC_EVENT_KICK;
					gce.cbSize = sizeof(GCEVENT);
					gce.pDest = &gcd;
					gce.dwFlags = GCEF_ADDTOLOG | GC_TCHAR;
					gce.time = timestamp;

					if (users=SkypeGetErr (cmdMessage, args.msgnum, "USERS")) {
						ci.hContact = find_contact(users);
						gce.ptszUID= make_nonutf_tchar_string((const unsigned char*)users);
						if (who=SkypeGetErr (cmdMessage, args.msgnum, szPartnerHandle)) {
							gce.ptszStatus= make_nonutf_tchar_string((const unsigned char*)who);
							ci.cbSize = sizeof(ci);
							ci.szProto = SKYPE_PROTONAME;
							ci.dwFlag = CNF_DISPLAY | CNF_TCHAR;
							if (ci.hContact && !CallService(MS_CONTACT_GETCONTACTINFO,0,(LPARAM)&ci)) gce.ptszNick=ci.pszVal;
							else gce.ptszNick=gce.ptszUID;

							CallService(MS_GC_EVENT, 0, (LPARAM)&gce);
							RemChatContact (GetChat(gcd.ptszID), gce.ptszUID);
							free_nonutf_tchar_string((void*)gce.ptszStatus);
							if (ci.pszVal) mir_free (ci.pszVal);
						}
						free_nonutf_tchar_string((void*)gce.ptszUID);
					}
					free_nonutf_tchar_string((void*)gcd.ptszID);
					__leave;
				}
				if (!strcmp(type,"SETROLE"))
				{
					GCDEST gcd = {0};
					GCEVENT gce = {0};
					CONTACTINFO ci = {0};
					gchat_contact *gcContact;
					char *pszRole;

					// FROM_HANDLE - Wer hats gesetzt
					// USERS - Wessen Rolle wurde gesetzt
					// ROLE - Die neue Rolle
					if (!hChat) __leave;
					gcd.pszModule = SKYPE_PROTONAME;
					gcd.ptszID = make_nonutf_tchar_string((const unsigned char*)chat);
					gcd.iType = GC_EVENT_REMOVESTATUS;
					gce.cbSize = sizeof(GCEVENT);
					gce.pDest = &gcd;
					gce.dwFlags = GCEF_ADDTOLOG | GC_TCHAR;
					gce.time = timestamp;

					if (users=SkypeGetErr (cmdMessage, args.msgnum, "USERS")) {
						gce.ptszUID= make_nonutf_tchar_string((const unsigned char*)users);
						if (who=SkypeGetErr (cmdMessage, args.msgnum, szPartnerHandle)) {
							ci.cbSize = sizeof(ci);
							ci.szProto = SKYPE_PROTONAME;
							ci.dwFlag = CNF_DISPLAY | CNF_TCHAR;
							ci.hContact = find_contact(who);
							if (ci.hContact && !CallService(MS_CONTACT_GETCONTACTINFO,0,(LPARAM)&ci)) {
								gce.ptszText=_tcsdup(ci.pszVal);
								mir_free (ci.pszVal);
								ci.pszVal = NULL;
							}
							else gce.ptszText=make_tchar_string((const unsigned char*)who);

							ci.hContact = find_contact(users);
							if (ci.hContact && !CallService(MS_CONTACT_GETCONTACTINFO,0,(LPARAM)&ci)) gce.ptszNick=ci.pszVal;
							else gce.ptszNick=gce.ptszUID;

							if (gcContact = GetChatContact(GetChat(gcd.ptszID), gce.ptszUID))
							{
								gce.ptszStatus = gcContact->szRole;
								CallService(MS_GC_EVENT, 0, (LPARAM)&gce);
							}
							if (pszRole=SkypeGetErr (cmdMessage, args.msgnum, "ROLE")) {
								gce.ptszStatus = make_nonutf_tchar_string((const unsigned char*)pszRole);
								gcd.iType = GC_EVENT_ADDSTATUS;
								CallService(MS_GC_EVENT, 0, (LPARAM)&gce);
								free_nonutf_tchar_string((void*)gce.ptszStatus);
								free (pszRole);
							}
							free((void*)gce.ptszText);
							if (ci.pszVal) mir_free (ci.pszVal);
						}
						free_nonutf_tchar_string((void*)gce.ptszUID);
					}
					free_nonutf_tchar_string((void*)gcd.ptszID);
					__leave;
				}
				if (!strcmp(type,"SETTOPIC"))
				{
					GCDEST gcd = {0};
					GCEVENT gce = {0};
					CONTACTINFO ci = {0};

					LOG(("FetchMessageThread CHAT SETTOPIC"));
					gcd.pszModule = SKYPE_PROTONAME;
					gcd.ptszID = make_nonutf_tchar_string((const unsigned char*)chat);
					gcd.iType = GC_EVENT_TOPIC;
					gce.cbSize = sizeof(GCEVENT);
					gce.pDest = &gcd;
					gce.dwFlags = GCEF_ADDTOLOG | GC_TCHAR;
					gce.time = timestamp;
					if (who=SkypeGetErr (cmdMessage, args.msgnum, szPartnerHandle)) {
						ci.hContact = find_contact(who);
						gce.ptszUID = make_nonutf_tchar_string((const unsigned char*)who);
						ci.cbSize = sizeof(ci);
						ci.szProto = SKYPE_PROTONAME;
						ci.dwFlag = CNF_DISPLAY | CNF_TCHAR;
						if (ci.hContact && !CallService(MS_CONTACT_GETCONTACTINFO,0,(LPARAM)&ci)) gce.ptszNick=ci.pszVal;
						else gce.ptszNick=gce.ptszUID;

						if (ptr=SkypeGetErr (cmdMessage, args.msgnum, "BODY")) {
							gce.ptszText = make_tchar_string((const unsigned char*)ptr);
							CallService(MS_GC_EVENT, 0, (LPARAM)&gce);
							free ((void*)gce.ptszText);
							free (ptr);
						}
						free_nonutf_tchar_string ((void*)gce.ptszUID);
						if (ci.pszVal) mir_free (ci.pszVal);
					}
					free_nonutf_tchar_string((void*)gcd.ptszID);
					if (!args.bDontMarkSeen)
					{
						MsgList_Add (pre.lParam, INVALID_HANDLE_VALUE);
						SkypeSend("SET %s %s SEEN", cmdMessage, args.msgnum);
					}
					__leave;
				}
				if (!strcmp(type,"LEFT") || (bAddedMembers = strcmp(type,"ADDEDMEMBERS")==0))
				{
					GCDEST gcd = {0};
					GCEVENT gce = {0};
					CONTACTINFO ci = {0};
					char *pszInvited = Translate("invited ");

					LOG(("FetchMessageThread CHAT LEFT or ADDEDMEMBERS"));
					if (bAddedMembers) {
						gcd.pszModule = SKYPE_PROTONAME;
						gcd.ptszID = make_nonutf_tchar_string((const unsigned char*)chat);
						gcd.iType = GC_EVENT_ACTION;
						gce.cbSize = sizeof(GCEVENT);
						gce.pDest = &gcd;
						gce.dwFlags = GCEF_ADDTOLOG | GC_TCHAR;
						gce.time = timestamp;
						if (users=SkypeGetErr (cmdMessage, args.msgnum, "USERS")) {
							// We assume that users buffer has enough room for "invited" string
							memmove (users+strlen(pszInvited), users, strlen(users)+1);
							memcpy (users, pszInvited, strlen(pszInvited));
							gce.ptszText= make_tchar_string((const unsigned char*)users);
							if (who=SkypeGetErr (cmdMessage, args.msgnum, szPartnerHandle)) {
								DBVARIANT dbv;

								if (DBGetContactSettingString(NULL, SKYPE_PROTONAME, SKYPE_NAME, &dbv)==0) {
									gce.bIsMe = strcmp(who, dbv.pszVal)==0;
									DBFreeVariant(&dbv);
								}
								if (!gce.bIsMe) ci.hContact = find_contact(who);
								gce.ptszUID= make_nonutf_tchar_string((const unsigned char*)who);
								ci.cbSize = sizeof(ci);
								ci.szProto = SKYPE_PROTONAME;
								ci.dwFlag = CNF_DISPLAY | CNF_TCHAR;
								if (!CallService(MS_CONTACT_GETCONTACTINFO,0,(LPARAM)&ci)) gce.ptszNick=ci.pszVal;
								else gce.ptszNick=gce.ptszUID;

								CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
								free_nonutf_tchar_string((void*)gce.ptszUID);
								if (ci.pszVal) mir_free (ci.pszVal);
							}
							if (gce.ptszText) free ((void*)gce.ptszText);
						}
						free_nonutf_tchar_string ((void*)gcd.ptszID);
					}
					if (!args.QueryMsgDirection) SkypeSend ("GET CHAT %s MEMBERS", chat);
					__leave;
				}
			}
			__leave;
		}

		// Need to get the status?
		if (args.getstatus) {
			char *status;

			if (protocol<4) InterlockedIncrement (&rcvwatchers);
			status=SkypeGetID(cmdMessage, args.msgnum, "STATUS");
			if (protocol<4) InterlockedDecrement (&rcvwatchers);
			if (!status) __leave;
			if (!strcmp(status, "SENT")) direction=DBEF_SENT;
			free(status);
		}

		// Who sent it?
		if (!(who=SkypeGetErr (cmdMessage, args.msgnum, szPartnerHandle))) __leave;

		// Get contact handle
		LOG(("FetchMessageThread Finding contact handle"));
		DBGetContactSettingString(NULL, SKYPE_PROTONAME, SKYPE_NAME, &dbv);
		if (dbv.pszVal && !strcmp (who, dbv.pszVal))
		{
			char *pTok, *nextoken;

			// It's from me.. But to whom?
			// CHATMESSAGE .. USERS doesn't return anything, so we have to query the CHAT-Object
			if (!(ptr=SkypeGetErr ("CHAT", chat, "ACTIVEMEMBERS"))) {
				DBFreeVariant (&dbv);
				__leave;
			}

			for (pTok = strtok_r (ptr, " ", &nextoken); pTok; pTok=strtok_r(NULL, " ", &nextoken)) {
				if (strcmp (pTok, dbv.pszVal)) break; // Take the first dude in the list who is not me
			}

			if (!pTok) {
				free (ptr);
				DBFreeVariant (&dbv);
				__leave; // We failed
			}
			free (who);
			who=memmove (ptr, pTok, strlen(pTok)+1);
			direction = DBEF_SENT;
		}
		DBFreeVariant (&dbv);

		if (!(hContact=find_contact(who))) {
			// Permanent adding of user obsolete, we use the BUDDYSTATUS now (bug #0000005)
			ResetEvent(hBuddyAdded);
			SkypeSend("GET USER %s BUDDYSTATUS", who);
			WaitForSingleObject(hBuddyAdded, INFINITE);
			if (!(hContact=find_contact(who))) {
				// Arrgh, the user has been deleted from contact list.
				// In this case, we add him temp. to receive the msg at least.
				hContact=add_contact(who, PALF_TEMPORARY);
			}
		}
		// Text which was sent (on edited msg, BODY may already be in queue, check)
		sprintf (szBuf, "GET %s %s BODY", cmdMessage, args.msgnum);
		if (!args.pMsgEntry || !args.pMsgEntry->tEdited || !(ptr=SkypeRcv(szBuf+4, 1000)))
		{
			if (SkypeSend(szBuf)==-1 || !(ptr=SkypeRcv(szBuf+4, INFINITE)))
				__leave;
		}
		if (strncmp(ptr, "ERROR", 5)) {
			msgptr = ptr+strlen(szBuf+4)+1;
			bHasPartList = strncmp(msgptr,"<partlist ",10)==0;
			if (args.pMsgEntry && args.pMsgEntry->tEdited) {
				// Mark the message as edited
				if (!*msgptr && args.pMsgEntry->hEvent != INVALID_HANDLE_VALUE) {
					// Empty message and edited -> Delete event
					if ((int)(hContact = (HANDLE)CallService (MS_DB_EVENT_GETCONTACT, (WPARAM)args.pMsgEntry->hEvent, 0)) != -1) {
						CallService (MS_DB_EVENT_DELETE, (WPARAM)hContact, (LPARAM)args.pMsgEntry->hEvent);
						free (ptr);
						__leave;
					}
				} else {
					msgptr-=9;
					memcpy (msgptr, "[EDITED] ", 9);
				}
			}
			if( bEmoted && !isGroupChat) {
				CONTACTINFO ci = {0};
				int newlen;
				char *pMsg, *pszUTFnick=NULL;
				ci.cbSize = sizeof(ci);
				ci.szProto = SKYPE_PROTONAME;
				ci.dwFlag = CNF_DISPLAY | CNF_TCHAR;
				if (ci.hContact = hContact) {
					CallService(MS_CONTACT_GETCONTACTINFO,0,(LPARAM)&ci);
					if (ci.pszVal) {
#ifdef _UNICODE
						pszUTFnick = (char*)make_utf8_string(ci.pszVal);
#else
						utf8_encode (ci.pszVal, &pszUTFnick);
#endif
						mir_free (ci.pszVal);
					}
				}
				newlen = strlen(msgptr) + (pszUTFnick?strlen(pszUTFnick):0) + 9;
				if (pMsg = malloc(newlen)) {
					sprintf (pMsg, "** %s%s%s **", (pszUTFnick?pszUTFnick:""),(pszUTFnick?" ":""),(char*)msgptr);
					free (ptr);
					ptr = msgptr = pMsg;
				}
				if (pszUTFnick) free(pszUTFnick);
			}

			if (mirandaVersion >= 0x070000 &&	// 0.7.0+ supports PREF_UTF flag, no need to decode UTF8
				!isGroupChat) {				// I guess Groupchat doesn't support UTF8?
				msg = ptr;
				pre.flags |= PREF_UTF;
			} else {	// Older version has to decode either UTF8->ANSI or UTF8->UNICODE
				// This could be replaced by mir_getUTFI - functions for Miranda 0.5+ builds, but we stay
				// 0.4 compatible for backwards compatibility. Unfortunately this requires us to link with utf8.c
#ifdef _UNICODE
				int wcLen;
#endif

				if (utf8_decode(msgptr, &msg)==-1) {
					free(ptr);
					__leave;
				}
#ifdef _UNICODE
				msglen = strlen(msg)+1;
				msgptr = (char*)make_unicode_string ((const unsigned char*)msgptr);
				wcLen  = (_tcslen((TCHAR*)msgptr)+1)*sizeof(TCHAR);
				msg=realloc(msg, msglen+wcLen);
				memcpy (msg+msglen, msgptr, wcLen);
				free(msgptr);
				pre.flags |= PREF_UNICODE;
#endif
				msgptr = msg;
				free (ptr);
			}
			msglen = strlen(msgptr)+1;
		} else {
			free (ptr);
			__leave;
		}
		// skype sends some xml statics after a call has finished. Check if thats the case and suppress it if necessary...
		if ((DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "SuppressCallSummaryMessage", 1) &&
			bHasPartList) || msgptr[0]==0) __leave;

		if (isGroupChat && bUseGroupChat) {
			GCDEST gcd = {0};
			GCEVENT gce = {0};
			DBVARIANT dbv = {0};
			CONTACTINFO ci = {0};

			LOG(("FetchMessageThread This is a group chat message"));
			if (!hChat) ChatStart(chat, FALSE);
			gcd.pszModule = SKYPE_PROTONAME;
			gcd.ptszID = make_nonutf_tchar_string((const unsigned char*)chat);
			gcd.iType = bEmoted?GC_EVENT_ACTION:GC_EVENT_MESSAGE;
			gce.cbSize = sizeof(GCEVENT);
			gce.pDest = &gcd;
			if ((gce.bIsMe = (direction&DBEF_SENT)?TRUE:FALSE) &&
				DBGetContactSettingString(NULL, SKYPE_PROTONAME, SKYPE_NAME, &dbv)==0)
			{
				free(who);
				who = _strdup(dbv.pszVal);
				DBFreeVariant(&dbv);
			}
			gce.ptszUID = make_nonutf_tchar_string((const unsigned char*)who);
			ci.cbSize = sizeof(ci);
			ci.szProto = SKYPE_PROTONAME;
			ci.dwFlag = CNF_DISPLAY | CNF_TCHAR;
			ci.hContact = !gce.bIsMe?hContact:NULL;
			gce.ptszNick=gce.ptszUID;
			if (!CallService(MS_CONTACT_GETCONTACTINFO,0,(LPARAM)&ci)) gce.ptszNick=ci.pszVal;
			gce.time = timestamp>0?timestamp:(DWORD)SkypeTime(NULL);
			gce.pszText = msgptr;
			if (pre.flags & PREF_UNICODE) gce.pszText += msglen;
			gce.dwFlags = GCEF_ADDTOLOG | GC_TCHAR;
			CallService(MS_GC_EVENT, 0, (LPARAM)&gce);
			MsgList_Add (pre.lParam, INVALID_HANDLE_VALUE);	// Mark as groupchat
			if (ci.pszVal) mir_free (ci.pszVal);
			free_nonutf_tchar_string((void*)gce.ptszUID);
			free_nonutf_tchar_string(gcd.ptszID);

			// Yes, we have successfully read the msg
			if (!args.bDontMarkSeen)
				SkypeSend("SET %s %s SEEN", cmdMessage, args.msgnum);
			__leave;
		}

		if (args.QueryMsgDirection || (direction&DBEF_SENT)) {
			// Check if the timestamp is valid
			dbei.cbSize=sizeof(dbei);
			dbei.cbBlob=0;
			if (hDbEvent=(HANDLE)CallService(MS_DB_EVENT_FINDFIRST,(WPARAM)hContact,0)) {
				CallService(MS_DB_EVENT_GET,(WPARAM)hDbEvent,(LPARAM)&dbei);
				lwr=dbei.timestamp;
			}
			dbei.cbSize=sizeof(dbei);
			dbei.cbBlob=0;
			dbei.timestamp=0;
			if (hDbEvent=(HANDLE)CallService(MS_DB_EVENT_FINDLAST,(WPARAM)hContact,0))
				CallService(MS_DB_EVENT_GET,(WPARAM)hDbEvent,(LPARAM)&dbei);
			LOG(("FetchMessageThread timestamp %ld between %ld and %ld", timestamp, lwr, dbei.timestamp));
			if (timestamp<lwr || (direction&DBEF_SENT)) {
				TYP_MSGLENTRY *pme;

				LOG(("FetchMessageThread Adding event"));
				if (!(dbei.szModule=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0)))
					dbei.szModule=SKYPE_PROTONAME;
				dbei.cbBlob=msglen;
				if (pre.flags & PREF_UNICODE)
					dbei.cbBlob += sizeof(WCHAR)*( (DWORD)wcslen((WCHAR*)&msgptr[dbei.cbBlob])+1);
				dbei.pBlob=(PBYTE)msgptr;
				dbei.timestamp=timestamp>0?timestamp:(DWORD)SkypeTime(NULL);
				dbei.flags=direction;
				if (pre.flags & PREF_CREATEREAD) dbei.flags|=DBEF_READ;
				if (pre.flags & PREF_UTF) dbei.flags|=DBEF_UTF;
				dbei.eventType=EVENTTYPE_MESSAGE;
				pme = MsgList_Add (pre.lParam, (HANDLE)CallServiceSync(MS_DB_EVENT_ADD, (WPARAM)(HANDLE)hContact, (LPARAM)&dbei));

				// We could call MS_PROTO_CHAINSEND if we want to have MetaContact adding the history for us,
				// however we all know that CCSDATA doesn't contain timestamp-information which is
				// really bad on importing history for example, as all messages would be added with current
				// timestamp. This would cause unreliable jumbled timestamps in metacontact, so we better do this
				// ourself.
				if (DBGetContactSettingByte(hContact, "MetaContacts", "IsSubcontact", 0))
				{
					DWORD dwMetaLink = DBGetContactSettingDword(hContact, "MetaContacts", "MetaLink", MAXDWORD);
					HANDLE hMetaContact;

					if (dwMetaLink != MAXDWORD && (hMetaContact = GetMetaHandle(dwMetaLink)))
					{
						dbei.szModule=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hMetaContact, 0);
						pme->hMetaEvent = (HANDLE)CallServiceSync(MS_DB_EVENT_ADD, (WPARAM)(HANDLE)hMetaContact, (LPARAM)&dbei);
					}
				}

				if (!args.QueryMsgDirection && !args.bDontMarkSeen)
					SkypeSend("SET %s %s SEEN", cmdMessage, args.msgnum);
			}
		}


		if (!(direction&DBEF_SENT) && (!args.QueryMsgDirection || (args.QueryMsgDirection && timestamp>dbei.timestamp))) {
			LOG(("FetchMessageThread Normal message add..."));
			// Normal message received, process it
			ccs.szProtoService = PSR_MESSAGE;
			ccs.hContact = hContact;
			ccs.wParam = 0;
			ccs.lParam = (LPARAM)&pre;
			pre.flags |= direction;
			if(isGroupChat && DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "MarkGroupchatRead", 0))
				pre.flags |= PREF_CREATEREAD;
			pre.timestamp = timestamp>0?timestamp:(DWORD)SkypeTime(NULL);
			pre.szMessage = msgptr;
			CallServiceSync(MS_PROTO_CHAINRECV, 0, (LPARAM) &ccs);

			// Yes, we have successfully read the msg
			if (!args.bDontMarkSeen) SkypeSend("SET %s %s SEEN", cmdMessage, args.msgnum);
		}
	}
	__finally {
		if (status) free(status);
		if (msg) free(msg);
		if (users) free(users);
		if (chat) free(chat);
		if (type) free(type);
		if (who) free (who);
	}

}

void FetchMessageThreadSync(fetchmsg_arg *pargs) {
	// Secure this thread with a mutex.
	// This is needed to ensure that we get called after an old msg in the queue has
	// been added so that MsgList_FindEntry will find it.
	WaitForSingleObject (FetchMessageEvent, 30000);	// Wait max. 30 sec. for previous message fetch to complete
	if ((pargs->pMsgEntry = MsgList_FindMessage(strtoul(pargs->msgnum, NULL, 10))) && !pargs->pMsgEntry->tEdited) {
		// Better don't do this, as we set the msg as read and with this code, we would
		// mark messages not opened by user as read which isn't that good
		/*
		if (pargs->bIsRead && pMsgEvent->hEvent != INVALID_HANDLE_VALUE)
		{
			HANDLE hContact;
			if ((int)(hContact = (HANDLE)CallService (MS_DB_EVENT_GETCONTACT, (WPARAM)pMsgEntry->hEvent, 0)) != -1)
				CallService (MS_DB_EVENT_MARKREAD, (WPARAM)hContact, (LPARAM)hDBEvent);
		}
		*/
		free (pargs);
	}
	else FetchMessageThread (pargs);
	SetEvent (FetchMessageEvent);
}

static int MsglCmpProc(const void *pstPElement,const void *pstPToFind)
{
	return strcmp ((char*)((fetchmsg_arg*)pstPElement)->pMsgEntry, (char*)((fetchmsg_arg*)pstPToFind)->pMsgEntry);
}

void MessageListProcessingThread(char *str) {
	char *token, *nextoken, *chat=NULL;
	fetchmsg_arg *args;
	TYP_LIST *hListMsgs = List_Init(32);
	int i, nCount;

	// Frst we need to sort the message timestamps
	for ((token=strtok_r(str, ", ", &nextoken)); token; token=strtok_r(NULL, ", ", &nextoken)) {
		if (args=calloc(1, sizeof(fetchmsg_arg)+sizeof(DWORD))) {
			strncpy (args->msgnum, token, sizeof(args->msgnum));
			args->getstatus=TRUE;
			args->bIsRead=TRUE;
			args->bDontMarkSeen=TRUE;
			args->QueryMsgDirection=TRUE;
			(char*)args->pMsgEntry = SkypeGet ("CHATMESSAGE", token, "TIMESTAMP"); // Bad abuse of pointer
			if (!chat) chat=SkypeGet ("CHATMESSAGE", token, "CHATNAME");
			if (args->pMsgEntry) List_InsertSort (hListMsgs, MsglCmpProc, args);
			else free(args);
		}
	}
	for (i=0, nCount=List_Count(hListMsgs); i<nCount; i++) {
		args = List_ElementAt (hListMsgs, i);
		free (args->pMsgEntry);
		args->pMsgEntry = NULL;
		FetchMessageThreadSync (args);
	}
	if (chat) {
		SkypeSend ("GET CHAT %s MEMBERS", chat);
		free (chat);
	}
	List_Exit (hListMsgs);
	free (str);
}

char *GetCallerHandle(char *szSkypeMsg) {
	return SkypeGet(szSkypeMsg, "PARTNER_HANDLE", "");
}


HANDLE GetCallerContact(char *szSkypeMsg) {
	char *szHandle;
	HANDLE hContact=NULL;

	if (!(szHandle=GetCallerHandle(szSkypeMsg))) return NULL;
	if (!(hContact=find_contact(szHandle))) {
		// If it's a SkypeOut-contact, PARTNER_HANDLE = SkypeOUT number
		DBVARIANT dbv;
		int tCompareResult;

		for (hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);hContact != NULL;hContact=(HANDLE)CallService( MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0)) {
			if (DBGetContactSettingString(hContact, SKYPE_PROTONAME, "SkypeOutNr", &dbv)) continue;
			tCompareResult = strcmp(dbv.pszVal, szHandle);
			DBFreeVariant(&dbv);
			if (tCompareResult) continue; else break;
		}
	}
	free(szHandle);
	if (!hContact) {LOG(("GetCallerContact Not found!"));}
	return hContact;
}

HANDLE GetMetaHandle(DWORD dwId) {
    HANDLE hContact;
    char *szProto;

	for (hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);hContact != NULL;hContact=(HANDLE)CallService( MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0)) {
		szProto = (char*)CallService( MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0 );
		if (szProto!=NULL && !strcmp(szProto, "MetaContacts") &&
			DBGetContactSettingDword(hContact, "MetaContacts", "MetaID", MAXDWORD)==dwId)
				return hContact;
    }
    return 0;
}

LRESULT CALLBACK InCallPopUpProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
	switch(msg)
	{
		case WM_COMMAND:
			break;

		case WM_CONTEXTMENU:
			SendMessage(hwnd,UM_DESTROYPOPUP,0,0);
			break;
		case UM_FREEPLUGINDATA:
			//Here we'd free our own data, if we had it.
			return FALSE;
		case UM_INITPOPUP:
			break;
		case UM_DESTROYPOPUP:
			break;
		case WM_NOTIFY:
		default:
			break;
	}
	return DefWindowProc(hwnd,msg,wParam,lParam);
}

void RingThread(char *szSkypeMsg) {
	HANDLE hContact;
	DBEVENTINFO dbei={0};
	DBVARIANT dbv;
	char *ptr = NULL;

	// We use a single critical section for the RingThread- and the EndCallThread-functions
	// so that only one function is running at the same time. This is needed, because when
	// a initated and unaccepted call (which is still ringing) is hangup/canceled, skype
	// sends two messages. First "CALL xxx STATUS RINGING" .. second "CALL xx STATUS CANCELED".
	// This starts two independend threads (first: RingThread; second: EndCallThread). Now
	// the two message are processed in reverse order sometimes. This causes the EndCallThread to
	// delete the contacts "CallId" property and after that the RingThread saves the contacts
	// "CallId" again. After that its not possible to call this contact, because the plugin
	// thinks that there is already a call going and the hangup-function isnt working, because
	// skype doesnt accept status-changes for finished calls. The CriticalSection syncronizes
	// the threads and the messages are processed in correct order.
	// Not the best solution, but it works.
	EnterCriticalSection (&RingAndEndcallMutex);

  LOG(("RingThread started."));
	if (protocol >= 5) SkypeSend ("MINIMIZE");
	if (hContact=GetCallerContact(szSkypeMsg)) {
		// Make sure that an answering thread is not already in progress so that we don't get
		// the 'Incoming call' event twice
		if (!DBGetContactSettingString(hContact, SKYPE_PROTONAME, "CallId", &dbv)) {
			DBFreeVariant(&dbv);
            LOG(("RingThread terminated."));
			goto l_exitRT;
		}
		DBWriteContactSettingString(hContact, SKYPE_PROTONAME, "CallId", szSkypeMsg);
	}

	if (!(ptr=SkypeGet(szSkypeMsg, "TYPE", ""))) {
        LOG(("RingThread terminated."));
		goto l_exitRT;;
	}

	if (!strncmp(ptr, "INCOMING", 8))
		NofifyVoiceService(hContact, szSkypeMsg, VOICE_STATE_RINGING);
	else
		NofifyVoiceService(hContact, szSkypeMsg, VOICE_STATE_CALLING);

	if (!strncmp(ptr, "INCOMING", 8)) {
		if (!hContact) {
			char *szHandle;

			if (szHandle=GetCallerHandle(szSkypeMsg)) {
				if (!(hContact=add_contact(szHandle, PALF_TEMPORARY))) {
					free(szHandle);
					goto l_exitRT;
				}
				DBDeleteContactSetting(hContact, "CList", "Hidden");
				DBWriteContactSettingWord(hContact, SKYPE_PROTONAME, "Status", (WORD)SkypeStatusToMiranda("SKYPEOUT"));
				DBWriteContactSettingString(hContact, SKYPE_PROTONAME, "SkypeOutNr", szHandle);
				free(szHandle);
			} else goto l_exitRT;
		}
	}

	if (HasVoiceService()) {
		// Voice service will handle it
		goto l_exitRT;
	}

	dbei.cbSize=sizeof(dbei);
	dbei.eventType=EVENTTYPE_CALL;
	dbei.szModule=SKYPE_PROTONAME;
	dbei.timestamp=(DWORD)SkypeTime(NULL);
	dbei.pBlob=(unsigned char*)Translate("Phonecall");
	dbei.cbBlob=strlen((const char*)dbei.pBlob)+1;
	if (!strncmp(ptr, "INCOMING", 8))
	{
		CLISTEVENT cle={0};
		char toolTip[256];

		if(PopupServiceExists)
		{
			BOOL showPopup, popupWindowColor;
			unsigned int popupBackColor, popupTextColor;
			int popupTimeSec;
			POPUPDATAT InCallPopup;
			TCHAR * lpzContactName = (TCHAR*)CallService(MS_CLIST_GETCONTACTDISPLAYNAME,(WPARAM)hContact,GCDNF_TCHAR);

			popupTimeSec = DBGetContactSettingDword(NULL, SKYPE_PROTONAME, "popupTimeSec", 4);
			popupTextColor = DBGetContactSettingDword(NULL, SKYPE_PROTONAME, "popupTextColor", GetSysColor(COLOR_WINDOWTEXT));
			popupBackColor = DBGetContactSettingDword(NULL, SKYPE_PROTONAME, "popupBackColor", GetSysColor(COLOR_BTNFACE));
			popupWindowColor = (0 != DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "popupWindowColor", TRUE));
			showPopup = (0 != DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "showPopup", TRUE));

			InCallPopup.lchContact = hContact;
			InCallPopup.lchIcon = LoadIcon(hInst,MAKEINTRESOURCE(IDI_CALL));
			InCallPopup.colorBack = ! popupWindowColor ? popupBackColor : GetSysColor(COLOR_BTNFACE);
			InCallPopup.colorText = ! popupWindowColor ? popupTextColor : GetSysColor(COLOR_WINDOWTEXT);
			InCallPopup.iSeconds = popupTimeSec;
			InCallPopup.PluginWindowProc = (WNDPROC)InCallPopUpProc;
			InCallPopup.PluginData = (void *)1;

			lstrcpy(InCallPopup.lptzText, TranslateT("Incoming Skype Call"));

			lstrcpy(InCallPopup.lptzContactName, lpzContactName);

			if(showPopup)
				CallService(MS_POPUP_ADDPOPUPT,(WPARAM)&InCallPopup,0);

		}
		cle.cbSize=sizeof(cle);
		cle.hIcon=LoadIcon(hInst,MAKEINTRESOURCE(IDI_CALL));
		cle.pszService=SKYPE_ANSWERCALL;
		dbei.flags=DBEF_READ;
		cle.hContact=hContact;
		cle.hDbEvent=(HANDLE)CallService(MS_DB_EVENT_ADD,(WPARAM)hContact,(LPARAM)&dbei);
		_snprintf(toolTip,sizeof(toolTip),Translate("Incoming call from %s"),(char*)CallService(MS_CLIST_GETCONTACTDISPLAYNAME,(WPARAM)hContact,0));
		cle.pszTooltip=toolTip;
		CallServiceSync(MS_CLIST_ADDEVENT,0,(LPARAM)&cle);
	}
	else
	{
		dbei.flags=DBEF_SENT;
		CallService(MS_DB_EVENT_ADD,(WPARAM)hContact,(LPARAM)&dbei);
	}

l_exitRT:
	if (ptr) free (ptr);
	free(szSkypeMsg);
	LeaveCriticalSection (&RingAndEndcallMutex);
}

void EndCallThread(char *szSkypeMsg) {
	HANDLE hContact=NULL, hDbEvent;
	DBEVENTINFO dbei={0};
	DBVARIANT dbv;
	int tCompareResult;

	// We use a single critical section for the RingThread- and the EndCallThread-functions
	// so that only one function is running at the same time. This is needed, because when
	// a initated and unaccepted call (which is still ringing) is hangup/canceled, skype
	// sends two messages. First "CALL xxx STATUS RINGING" .. second "CALL xx STATUS CANCELED".
	// This starts two independend threads (first: RingThread; second: EndCallThread). Now
	// the two message are processed in reverse order sometimes. This causes the EndCallThread to
	// delete the contacts "CallId" property and after that the RingThread saves the contacts
	// "CallId" again. After that its not possible to call this contact, because the plugin
	// thinks that there is already a call going and the hangup-function isnt working, because
	// skype doesnt accept status-changes for finished calls. The CriticalSection syncronizes
	// the threads and the messages are processed in correct order.
	// Not the best solution, but it works.
	EnterCriticalSection (&RingAndEndcallMutex);

  LOG(("EndCallThread started."));
	if (szSkypeMsg) {
		for (hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);hContact != NULL;hContact=(HANDLE)CallService( MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0)) {
			if (DBGetContactSettingString(hContact, SKYPE_PROTONAME, "CallId", &dbv)) continue;
			tCompareResult = strcmp(dbv.pszVal, szSkypeMsg);
			DBFreeVariant(&dbv);
			if (tCompareResult) continue; else break;
		}
	}
	if (hContact)
	{
		NofifyVoiceService(hContact, szSkypeMsg, VOICE_STATE_ENDED);

		DBDeleteContactSetting(hContact, SKYPE_PROTONAME, "CallId");

		if (!HasVoiceService()) {
			dbei.cbSize=sizeof(dbei);
			hDbEvent=(HANDLE)CallService(MS_DB_EVENT_FINDFIRSTUNREAD,(WPARAM)hContact,0);
			while(hDbEvent) {
				dbei.cbBlob=0;
				CallService(MS_DB_EVENT_GET,(WPARAM)hDbEvent,(LPARAM)&dbei);
					if (!(dbei.flags&(DBEF_SENT|DBEF_READ)) && dbei.eventType==EVENTTYPE_CALL) {
					CallService(MS_DB_EVENT_MARKREAD,(WPARAM)hContact,(LPARAM)hDbEvent);
					CallService(MS_CLIST_REMOVEEVENT,(WPARAM)hContact,(LPARAM)hDbEvent);
				}
				if (dbei.pBlob) free(dbei.pBlob);
				hDbEvent=(HANDLE)CallService(MS_DB_EVENT_FINDNEXT,(WPARAM)hDbEvent,0);
			}
		}

		if (!DBGetContactSettingString(hContact, SKYPE_PROTONAME, "SkypeOutNr", &dbv)) {
			DBFreeVariant(&dbv);
			if (!strcmp((char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0), SKYPE_PROTONAME) &&
				DBGetContactSettingByte(hContact, "CList", "NotOnList", 0)
			   )
					CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0);
		}
	}
	free(szSkypeMsg);
	LeaveCriticalSection (&RingAndEndcallMutex);
}

void HoldCallThread(char *szSkypeMsg) {
	HANDLE hContact;

	LOG(("HoldCallThread started"));
    if (!szSkypeMsg) {
		LOG(("HoldCallThread terminated."));
		return;
	}
	if (hContact=GetCallerContact(szSkypeMsg)) {
		DBWriteContactSettingByte(hContact, SKYPE_PROTONAME, "OnHold", 1);
		NofifyVoiceService(hContact, szSkypeMsg, VOICE_STATE_ON_HOLD);
	}
	free(szSkypeMsg);
	LOG(("HoldCallThread terminated gracefully"));
}

void ResumeCallThread(char *szSkypeMsg) {
	HANDLE hContact;

	LOG(("ResumeCallThread started"));
	if (!szSkypeMsg) {
		LOG(("ResumeCallThread terminated."));
		return;
	}
	if (hContact=GetCallerContact(szSkypeMsg)) {
		DBDeleteContactSetting(hContact, SKYPE_PROTONAME, "OnHold");
		NofifyVoiceService(hContact, szSkypeMsg, VOICE_STATE_TALKING);
	}
	free(szSkypeMsg);
    LOG(("ResumeCallThread terminated gracefully."));
}

int SetUserStatus(void) {
   if (RequestedStatus && AttachStatus!=-1) {
	if (SkypeSend("SET USERSTATUS %s", RequestedStatus)==-1) return 1;
   }
   return 0;
}

void LaunchSkypeAndSetStatusThread(void *newStatus) {

/*	   if (!DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "UnloadOnOffline", 0)) {
		   logoff_contacts();
		   return 1;
	   }
*/
	int oldStatus=SkypeStatus;
	static BOOL bLaunching = FALSE;

	UNREFERENCED_PARAMETER(newStatus);

	if (bLaunching) return;
	bLaunching = TRUE;
	LOG (("LaunchSkypeAndSetStatusThread started."));
	InterlockedExchange((long *)&SkypeStatus, (int)ID_STATUS_CONNECTING);
	ProtoBroadcastAck(SKYPE_PROTONAME, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) oldStatus, SkypeStatus);

	if (ConnectToSkypeAPI(skype_path, 1)!=-1) {
		forkthread(( pThreadFunc )SkypeSystemInit, 0, NULL);
		//InterlockedExchange((long *)&SkypeStatus, (int)newStatus);
		//ProtoBroadcastAck(SKYPE_PROTONAME, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) oldStatus, SkypeStatus);
		SetUserStatus();
	}

	LOG (("LaunchSkypeAndSetStatusThread terminated gracefully."));
	bLaunching = FALSE;
}

LONG APIENTRY WndProc(HWND hWndDlg, UINT message, UINT wParam, LONG lParam)
{
    PCOPYDATASTRUCT CopyData;
	char *ptr, *szSkypeMsg=NULL, *nick, *buf;
	static char *onlinestatus=NULL;
	static BOOL RestoreUserStatus=FALSE;
	int sstat, oldstatus, flag;
	HANDLE hContact;
	fetchmsg_arg *args;
	static int iReentranceCnt = 0;

	iReentranceCnt++;
    switch (message)
    {
        case WM_COPYDATA:
		 LOG(("WM_COPYDATA start"));
		 if(hSkypeWnd==(HWND)wParam) {
			char *pData;
			CopyData=(PCOPYDATASTRUCT)lParam;
			pData = (char*)CopyData->lpData;
			while (*pData==' ') pData++;
			szSkypeMsg=_strdup((char*)pData);
			ReplyMessage(1);
			LOG(("< %s", szSkypeMsg));

 			if (!strncmp(szSkypeMsg, "CONNSTATUS", 10)) {
				if (!strncmp(szSkypeMsg+11, "LOGGEDOUT", 9)) {
					SkypeInitialized=FALSE;
					ResetEvent(SkypeReady);
					AttachStatus=-1;
					sstat=ID_STATUS_OFFLINE;
				    if (g_hWnd) KillTimer (g_hWnd, 1);
					logoff_contacts(TRUE);
				} else
					sstat=SkypeStatusToMiranda(szSkypeMsg+11);

				if (sstat) {
					oldstatus=SkypeStatus;
					InterlockedExchange((long*)&SkypeStatus, sstat);
					ProtoBroadcastAck(SKYPE_PROTONAME, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) oldstatus, SkypeStatus);
					if (sstat!=ID_STATUS_OFFLINE) {
						if (sstat!=ID_STATUS_CONNECTING && (oldstatus==ID_STATUS_OFFLINE || oldstatus==ID_STATUS_CONNECTING)) {

							SkypeInitialized=FALSE;
							forkthread(( pThreadFunc )SkypeSystemInit, 0, NULL);
						}
						if (DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "KeepState", 0)) RestoreUserStatus=TRUE;
					}

//					if (SkypeStatus==ID_STATUS_ONLINE) SkypeSend("SEARCH MISSEDMESSAGES");
				}
//				break;
			}
			if (!strncmp(szSkypeMsg, "USERSTATUS", 10)) {
//				if ((sstat=SkypeStatusToMiranda(szSkypeMsg+11)) && SkypeStatus!=ID_STATUS_CONNECTING) {
				if ((sstat=SkypeStatusToMiranda(szSkypeMsg+11))) {
						if (RestoreUserStatus && RequestedStatus) {
							RestoreUserStatus=FALSE;
							SkypeSend ("SET USERSTATUS %s", RequestedStatus);
						}
						oldstatus=SkypeStatus;
						InterlockedExchange((long*)&SkypeStatus, sstat);
						ProtoBroadcastAck(SKYPE_PROTONAME, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) oldstatus, sstat);
#ifdef SKYPEBUG_OFFLN
						if ((oldstatus==ID_STATUS_OFFLINE || oldstatus==ID_STATUS_CONNECTING) &&
							SkypeStatus!=ID_STATUS_CONNECTING && SkypeStatus!=ID_STATUS_OFFLINE)
							forkthread(( pThreadFunc )SearchFriendsThread, 0, NULL);
#endif
				}
#ifdef SKYPEBUG_OFFLN
				SetEvent(GotUserstatus);
#endif
				break;
			}
			if (!strncmp(szSkypeMsg, "APPLICATION libpurple_typing", 28)) {
				char *nextoken, *p;

				if (p=strtok_r(szSkypeMsg+29, " ", &nextoken))
				{
					if (!strcmp (p, "STREAMS")) {
						char *pStr;

						while (p=strtok_r(NULL, " ", &nextoken)) {
							if (pStr = strchr(p, ':')) {
								*pStr=0;
								if (hContact=find_contact(p)) {
									*pStr=':';
									DBWriteContactSettingString(hContact, SKYPE_PROTONAME, "Typing_Stream", p);
								}
							}
						}
					}
					else if (!strcmp (p, "DATAGRAM")) {
						if (p=strtok_r(NULL, " ", &nextoken)) {
							char *pStr;

							if (pStr = strchr(p, ':')) {
								*pStr=0;
								if (hContact=find_contact(p)) {
									*pStr=':';
									DBWriteContactSettingString(hContact, SKYPE_PROTONAME, "Typing_Stream", p);

									if (p=strtok_r(NULL, " ", &nextoken)) {
										LPARAM lTyping = PROTOTYPE_CONTACTTYPING_OFF;

										if (!strcmp(p, "PURPLE_TYPING")) lTyping=PROTOTYPE_CONTACTTYPING_INFINITE;
										CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, lTyping);
										break;
									}
								}
							}
						}
					}
				}
			}
			if (!strncmp(szSkypeMsg, "USER ", 5)) {
				char *nextoken;

				buf=_strdup(szSkypeMsg+5);
				nick=strtok_r(buf, " ", &nextoken);
				ptr=strtok_r(NULL, " ", &nextoken);

				if (strcmp(ptr, "BUDDYSTATUS")) {
					if (!strcmp(ptr, "RECEIVEDAUTHREQUEST")) {
						forkthread(( pThreadFunc )SearchUsersWaitingMyAuthorization, 0, NULL);
						free (buf);
						break;
					}

					if (!(hContact=find_contact(nick)) && strcmp(ptr, "FULLNAME")) {
						SkypeSend("GET USER %s BUDDYSTATUS", nick);
						free (buf);
						break;
					}

					if (!strcmp(ptr, "ONLINESTATUS")) {
						if (SkypeStatus!=ID_STATUS_OFFLINE)
						{
							DBWriteContactSettingWord(hContact, SKYPE_PROTONAME, "Status", (WORD)SkypeStatusToMiranda(ptr+13));
							if((WORD)SkypeStatusToMiranda(ptr+13) != ID_STATUS_OFFLINE)
							{
								LOG(("WndProc Status is not offline so get user info"));
								forkthread(GetInfoThread, 0, hContact);
							}
						}
					}


					/* We handle the following properties right here in the wndProc, in case that
					 * Skype protocol broadcasts them to us.
					 *
					 * However, we still let them be added to the Message queue im memory, as they
					 * may get consumed by GetInfoThread.
					 * This is necessary to have a proper error handling in case the property is
					 * not supported (i.e. imo2sproxy).
					 *
					 * If one of the property GETs returns an error, the error-message has to be
					 * removed from the message queue, as the error is the answer to the query.
					 * If we don't remove the ERRORs from the list, another consumer may see the ERROR
					 * as a reply to his query and process it.
					 * In case the SKYPE Protocol really broadcasts one of these messages without being
					 * requested by GetInfoThread (i.e. MOOD_TEXT), the garbage collector will take
					 * care of them and remove them after some time.
					 * This may not be the most efficient way, but ensures that we finally do proper
					 * error handling.
					 */
					if (!strcmp(ptr, "FULLNAME")) {
						char *nm;

						if (nm = strtok_r(NULL, " ", &nextoken))
						{
							SkypeDBWriteContactSettingUTF8String(hContact, SKYPE_PROTONAME, "FirstName", nm);
							if (!(nm=strtok_r(NULL, "", &nextoken))) DBDeleteContactSetting(hContact, SKYPE_PROTONAME, "LastName");
							else
								SkypeDBWriteContactSettingUTF8String(hContact, SKYPE_PROTONAME, "LastName", nm);
						}
					} else
					if (!strcmp(ptr, "BIRTHDAY")) {
						unsigned int y, m, d;
						if (sscanf(ptr+9, "%04d%02d%02d", &y, &m, &d)==3) {
							DBWriteContactSettingWord(hContact, SKYPE_PROTONAME, "BirthYear", (WORD)y);
							DBWriteContactSettingByte(hContact, SKYPE_PROTONAME, "BirthMonth", (BYTE)m);
							DBWriteContactSettingByte(hContact, SKYPE_PROTONAME, "BirthDay", (BYTE)d);
						} else {
							DBDeleteContactSetting(hContact, SKYPE_PROTONAME, "BirthYear");
							DBDeleteContactSetting(hContact, SKYPE_PROTONAME, "BirthMonth");
							DBDeleteContactSetting(hContact, SKYPE_PROTONAME, "BirthDay");
						}
					} else
					if (!strcmp(ptr, "COUNTRY")) {
						if (ptr[8]) {
							struct CountryListEntry *countries;
							int countryCount, i;

							CallService(MS_UTILS_GETCOUNTRYLIST, (WPARAM)&countryCount, (LPARAM)&countries);
							for (i=0; i<countryCount; i++) {
								if (countries[i].id == 0 || countries[i].id == 0xFFFF) continue;
								if (!_stricmp(countries[i].szName, ptr+8))
								{
									DBWriteContactSettingWord(hContact, SKYPE_PROTONAME, "Country", (BYTE)countries[i].id);
									break;
								}
							}
						} else DBDeleteContactSetting(hContact, SKYPE_PROTONAME, "Country");
					} else
					if (!strcmp(ptr, "SEX")) {
						if (ptr[4]) {
							BYTE sex=0;
							if (!_stricmp(ptr+4, "MALE")) sex=0x4D;
							if (!_stricmp(ptr+4, "FEMALE")) sex=0x46;
							if (sex) DBWriteContactSettingByte(hContact, SKYPE_PROTONAME, "Gender", sex);
						} else DBDeleteContactSetting(hContact, SKYPE_PROTONAME, "Gender");
					} else
	/*				if (!strcmp(ptr, "AVATAR" )){
						LOG("WndProc", "AVATAR");
						if (!(hContact=find_contact(nick)))
							SkypeSend("GET USER %s BUDDYSTATUS", nick);
						else
						{
							TCHAR *unicode = NULL;

							if(utf8_decode((ptr+9), &Avatar)==-1) break;

							if( ServiceExists(MS_AV_SETAVATAR) )
							{
								CallService(MS_AV_SETAVATAR,(WPARAM) hContact,(LPARAM) Avatar);
							}
							else
							{

								if(DBWriteContactSettingTString(hContact, "ContactPhoto", "File", Avatar))
								{
									#if defined( _UNICODE )
										char buff[TEXT_LEN];
										WideCharToMultiByte(code_page, 0, Avatar, -1, buff, TEXT_LEN, 0, 0);
										buff[TEXT_LEN] = 0;
										DBWriteContactSettingString(hContact, "ContactPhoto", "File", buff);
									#endif
								}

							}


						}
						free(buf);
						break;
					}
					*/
					if (!strcmp(ptr, "MOOD_TEXT")){

						LOG(("WndProc MOOD_TEXT"));
						SkypeDBWriteContactSettingUTF8String (hContact, "CList", "StatusMsg", ptr+10);
					} else
					if (!strcmp(ptr, "TIMEZONE")){
						time_t temp;
						struct tm tms;
						int value=atoi(ptr+9), tz;

						LOG(("WndProc: TIMEZONE %s", nick));

						if (value && !DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "IgnoreTimeZones", 0)) {
							temp = SkypeTime(NULL);
							tms = *localtime(&temp);
							//memcpy(&tms,localtime(&temp), sizeof(tm));
							//tms = localtime(&temp)
							tz=(value >= 86400 )?(256-((2*(atoi(ptr+9)-86400))/3600)):((-2*(atoi(ptr+9)-86400))/3600);
							if (tms.tm_isdst == 1 && DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "UseTimeZonePatch", 0))
							{
								LOG(("WndProc: Using the TimeZonePatch"));
								DBWriteContactSettingByte(hContact, "UserInfo", "Timezone", (BYTE)(tz+2));
							}
							else
							{
								LOG(("WndProc: Not using the TimeZonePatch"));
								DBWriteContactSettingByte(hContact, "UserInfo", "Timezone", (BYTE)(tz+0));
							}
						} else 	{
							LOG(("WndProc: Deleting the TimeZone in UserInfo Section"));
							DBDeleteContactSetting(hContact, "UserInfo", "Timezone");
						}
					} else
					if (!strcmp(ptr, "IS_VIDEO_CAPABLE")){
						if (!_stricmp(ptr + 17, "True"))
							DBWriteContactSettingString(hContact, SKYPE_PROTONAME, "MirVer", "Skype 2.0");
						else
							DBWriteContactSettingString(hContact, SKYPE_PROTONAME, "MirVer", "Skype");
					} else
					if (!strcmp(ptr, "RICH_MOOD_TEXT")) {
						DBWriteContactSettingString(hContact, SKYPE_PROTONAME, "MirVer", "Skype 3.0");
					} else
					if (!strcmp(ptr, "DISPLAYNAME")) {
						// Skype Bug? -> If nickname isn't customised in the Skype-App, this won't return anything :-(
						if (ptr[12])
							SkypeDBWriteContactSettingUTF8String(hContact, SKYPE_PROTONAME, "Nick", ptr+12);
					} else	// Other proerties that can be directly assigned to a DB-Value
					{
						int i;
						char *pszProp;

						for (i=0; i<sizeof(m_settings)/sizeof(m_settings[0]); i++) {
							if (!strcmp(ptr, m_settings[i].SkypeSetting)) {
								pszProp = ptr+strlen(m_settings[i].SkypeSetting)+1;
								if (*pszProp)
									SkypeDBWriteContactSettingUTF8String(hContact, SKYPE_PROTONAME, m_settings[i].MirandaSetting, pszProp);
								else
									DBDeleteContactSetting(hContact, SKYPE_PROTONAME, m_settings[i].MirandaSetting);
							}
						}
					}
				} else { // BUDDYSTATUS:
					flag=0;
					switch(atoi(ptr+12)) {
						case 1: if (hContact=find_contact(nick)) CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0); break;
						case 0: break;
						case 2: flag=PALF_TEMPORARY;
						case 3: add_contact(nick, flag);
								SkypeSend("GET USER %s ONLINESTATUS", nick);
								break;
					}
					free(buf);
					if (!SetEvent(hBuddyAdded)) TellError(GetLastError());
					break;
				}
				free(buf);
			}
			if (!strncmp(szSkypeMsg, "CURRENTUSERHANDLE", 17)) {	// My username
				DBVARIANT dbv={0};

				if(DBGetContactSettingString(NULL,SKYPE_PROTONAME,"LoginUserName",&dbv) ||
					!*dbv.pszVal || _stricmp (szSkypeMsg+18, dbv.pszVal)==0)
				{
					DBWriteContactSettingString(NULL, SKYPE_PROTONAME, SKYPE_NAME, szSkypeMsg+18);
					DBWriteContactSettingString(NULL, SKYPE_PROTONAME, "Nick", szSkypeMsg+18);
					forkthread(( pThreadFunc )GetDisplaynameThread, 0, NULL);
				}
				if (dbv.pszVal) DBFreeVariant(&dbv);
			}
			if (strstr(szSkypeMsg, "AUTOAWAY") || !strncmp(szSkypeMsg, "OPEN ",5) ||
				 (SkypeInitialized && !strncmp (szSkypeMsg, "PONG", 4)) ||
				 !strncmp (szSkypeMsg, "MINIMIZE", 8))
			{
				// Currently we do not process these messages
				break;
			}
			if (!strncmp(szSkypeMsg, "CHAT ", 5)) {
				// Currently we only process these notifications
				if (DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "UseGroupchat", 0) &&
					(ptr = strchr (szSkypeMsg, ' ')) && (ptr = strchr (++ptr, ' ')))
				{
					if (strncmp(ptr, " MEMBERS", 8) == 0) {
						LOG(("WndProc AddMembers"));
						forkthread(( pThreadFunc )AddMembersThread, 0, _strdup(szSkypeMsg));
					} else
					if (strncmp(ptr, " FRIENDLYNAME ", 14) == 0) {
						// Chat session name
						HANDLE hContact;

						*ptr=0;
						if (hContact = find_chatA(szSkypeMsg+5))
						{
							GCDEST gcd = {0};
							GCEVENT gce = {0};

							if (DBGetContactSettingWord(hContact, SKYPE_PROTONAME, "Status", ID_STATUS_OFFLINE) !=
								ID_STATUS_OFFLINE)
							{
								gcd.pszModule = SKYPE_PROTONAME;
								gcd.ptszID = make_nonutf_tchar_string((const unsigned char*)szSkypeMsg+5);
								gcd.iType = GC_EVENT_CHANGESESSIONAME;
								gce.cbSize = sizeof(GCEVENT);
								gce.pDest = &gcd;
								gce.ptszText = make_tchar_string((const unsigned char*)ptr+14);
								gce.dwFlags = GC_TCHAR;
								if (gce.ptszText) {
									CallService(MS_GC_EVENT, 0, (LPARAM)&gce);
									DBWriteContactSettingTString (hContact, SKYPE_PROTONAME, "Nick", gce.ptszText);
									free((void*)gce.ptszText);
								}
								free_nonutf_tchar_string((void*)gcd.ptszID);
							}
						}
						*ptr=' ';
					} else
					if (strncmp(ptr, " CHATMESSAGES ", 14) == 0) {
						forkthread(( pThreadFunc )MessageListProcessingThread, 0, _strdup(ptr+14));
						break;
					}
				}
			}
			if (!strncmp(szSkypeMsg, "CALL ",5)) {
				// incoming calls are already processed by Skype, so no need for us
				// to do this.
				// However we can give a user the possibility to hang up a call via Miranda's
				// context menu
				if (ptr=strstr(szSkypeMsg, " STATUS ")) {
					ptr[0]=0; ptr+=8;
					if (!strcmp(ptr, "RINGING") || !strcmp(ptr, "ROUTING")) forkthread(( pThreadFunc )RingThread, 0, _strdup(szSkypeMsg));
					if (!strcmp(ptr, "FAILED") || !strcmp(ptr, "FINISHED") ||
						!strcmp(ptr, "MISSED") || !strcmp(ptr, "REFUSED")  ||
						!strcmp(ptr, "BUSY")   || !strcmp(ptr, "CANCELLED"))
						forkthread(( pThreadFunc )EndCallThread, 0, _strdup(szSkypeMsg));
					if (!strcmp(ptr, "ONHOLD") || !strcmp(ptr, "LOCALHOLD") ||
						!strcmp(ptr, "REMOTEHOLD")) forkthread(( pThreadFunc )HoldCallThread, 0, _strdup(szSkypeMsg));
					if (!strcmp(ptr, "INPROGRESS")) forkthread(( pThreadFunc )ResumeCallThread, 0, _strdup(szSkypeMsg));
					break;
				} else if ((!strstr(szSkypeMsg, "PARTNER_HANDLE") && !strstr(szSkypeMsg, "FROM_HANDLE"))
							&& !strstr(szSkypeMsg, "TYPE")) break;
			}
			if (!strncmp(szSkypeMsg, "PRIVILEGE SKYPEOUT", 18)) {
				if (!strncmp(szSkypeMsg+19, "TRUE", 4)) {
					if (!bSkypeOut) {
						CLISTMENUITEM mi={0};

						bSkypeOut=TRUE;
						mi.cbSize=sizeof(mi);
						mi.position=-2000005000;
						mi.flags=0;
						mi.hIcon=LoadIcon(hInst,MAKEINTRESOURCE(IDI_CALLSKYPEOUT));
						mi.pszContactOwner=SKYPE_PROTONAME;
						mi.pszName=Translate("Do a SkypeOut-call");
						mi.pszService=SKYPEOUT_CALL;
						Menu_AddMainMenuItem(&mi);
					}

				} else {
					bSkypeOut=FALSE;
					if (httbButton) {
						CallService(MS_TTB_REMOVEBUTTON, (WPARAM)httbButton, 0);
						httbButton=0;
					}
				}
				break;
			}
			if (!strncmp(szSkypeMsg, "MESSAGES", 8) || !strncmp(szSkypeMsg, "CHATMESSAGES", 12)) {
				if (strlen(szSkypeMsg)<=(UINT)(strchr(szSkypeMsg, ' ')-szSkypeMsg+1))
				{
					LOG(( "%s %d %s %d", szSkypeMsg,(UINT)(strchr(szSkypeMsg, ' ')-szSkypeMsg+1),
						strchr(szSkypeMsg, ' '), strlen(szSkypeMsg)));
					break;
				}
				LOG(("MessageListProcessingThread launched"));
				forkthread(( pThreadFunc )MessageListProcessingThread, 0, _strdup(strchr(szSkypeMsg, ' ')+1));
				break;
			}
			if (!strncmp(szSkypeMsg, "MESSAGE", 7) || !strncmp(szSkypeMsg, "CHATMESSAGE", 11))
			{
				char *pMsgNum;
				TYP_MSGLENTRY *pEntry;

				if ((pMsgNum = strchr (szSkypeMsg, ' ')) && (ptr = strchr (++pMsgNum, ' ')))
				{
					BOOL bFetchMsg = FALSE;

					if (strncmp(ptr, " EDITED_TIMESTAMP", 17) == 0) {
						ptr[0]=0;
						if (pEntry = MsgList_FindMessage(strtoul(pMsgNum, NULL, 10))) {
							pEntry->tEdited = atol(ptr+18);
						}
						bFetchMsg = TRUE;
					} else bFetchMsg = (strncmp(ptr, " STATUS RE", 10) == 0 && !rcvwatchers) ||
						(strncmp(ptr, " STATUS SENT", 12) == 0 && !sendwatchers);

					if (bFetchMsg) {
						// If new message is available, fetch it
						ptr[0]=0;
						if (!(args=(fetchmsg_arg *)calloc(1, sizeof(*args)))) break;
						strncpy (args->msgnum, pMsgNum, sizeof(args->msgnum));
						args->getstatus=FALSE;
						//args->bIsRead = strncmp(ptr+8, "READ", 4) == 0;
						forkthread(( pThreadFunc )FetchMessageThreadSync, 0, args);
						break;
					}
				}
			}
			if (!strncmp(szSkypeMsg, "ERROR 68", 8)) {
				LOG(("We got a sync problem :( ->  SendMessage() will try to recover..."));
				break;
			}
			if (!strncmp(szSkypeMsg, "PROTOCOL ", 9)) {
				if ((protocol=(char)atoi(szSkypeMsg+9))>=3) {
					strcpy(cmdMessage, "CHATMESSAGE");
					strcpy(cmdPartner, "FROM");
				}
				bProtocolSet = TRUE;

				if (protocol<5 && !hMenuAddSkypeContact &&
					DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "EnableMenu", 1))
				{
					hMenuAddSkypeContact = add_mainmenu();
				}
			}
			LOG(("SkypeMsgAdd launched"));
			SkypeMsgAdd(szSkypeMsg);
			ReleaseSemaphore(SkypeMsgReceived, receivers, NULL);
		}
        break;

        case WM_TIMER:
			if (iReentranceCnt>1) break;
			if (!bIsImoproxy) SkypeSend("PING");
			SkypeMsgCollectGarbage(MAX_MSG_AGE);
			MsgList_CollectGarbage();
			if (receivers>1)
			{
				LOG(("Watchdog WARNING: there are still %d receivers waiting for MSGs", receivers));
			}
			break;

		case WM_CLOSE:
			PostQuitMessage (0);
			break;
		case WM_DESTROY:
			KillTimer (hWndDlg, 1);
			break;

        default:
		 if(message==ControlAPIAttach) {
				// Skype responds with Attach to the discover-message
				 if ((HWND)wParam == hForbiddenSkypeWnd) {
					 ResetEvent(SkypeReady);
					 break;
				 }
				AttachStatus=lParam;
				if (lParam==SKYPECONTROLAPI_ATTACH_SUCCESS) {
					LOG (("AttachStatus success, got hWnd %08X", (HWND)wParam));

					if (hSkypeWnd && (HWND)wParam!=hSkypeWnd && IsWindow(hSkypeWnd))
						hSkypeWndSecondary = (HWND)wParam;
					else {
						hSkypeWnd=(HWND)wParam;	   // Skype gave us the communication window handle
						hSkypeWndSecondary = NULL;
					}
				}
				if (AttachStatus!=SKYPECONTROLAPI_ATTACH_API_AVAILABLE &&
					AttachStatus!=SKYPECONTROLAPI_ATTACH_NOT_AVAILABLE)
				{
					LOG(("Attaching: SkypeReady fired, Attachstatus is %d", AttachStatus));
					SetEvent(SkypeReady);
				}
				AttachStatus=lParam;
				break;
		 }
		 --iReentranceCnt;
		 return (DefWindowProc(hWndDlg, message, wParam, lParam));
    }
	LOG(("WM_COPYDATA exit (%08X)", message));
	if (szSkypeMsg) free(szSkypeMsg);
	--iReentranceCnt;
	return 1;
}

void TellError(DWORD err) {
	LPVOID lpMsgBuf;

		FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, err,
					  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL);
        MessageBox( NULL, (TCHAR*)lpMsgBuf, _T("GetLastError"), MB_OK|MB_ICONINFORMATION );
        LocalFree( lpMsgBuf );
		return;
}


// SERVICES //
INT_PTR SkypeSetStatus(WPARAM wParam, LPARAM lParam)
{
	int oldStatus, iRet;
	BOOL UseCustomCommand, UnloadOnOffline;

	UNREFERENCED_PARAMETER(lParam);

	if (MirandaShuttingDown) return 0;
	LOG (("SkypeSetStatus enter"));
	UseCustomCommand = DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "UseCustomCommand", 0);
	UnloadOnOffline = DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "UnloadOnOffline", 0);

	//if (!SkypeInitialized && !DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "UnloadOnOffline", 0)) return 0;

	// Workaround for Skype status-bug
    if ((int)wParam==ID_STATUS_OFFLINE) logoff_contacts(TRUE);
	if (SkypeStatus==(int)wParam) return 0;
	oldStatus = SkypeStatus;

	if ((int)wParam==ID_STATUS_CONNECTING) return 0;
#ifdef MAPDND
	if ((int)wParam==ID_STATUS_OCCUPIED || (int)wParam==ID_STATUS_ONTHEPHONE) wParam=ID_STATUS_DND;
	if ((int)wParam==ID_STATUS_OUTTOLUNCH) wParam=ID_STATUS_NA;
#endif
#ifdef MAPNA
	if ((int)wParam==ID_STATUS_NA) wParam = ID_STATUS_AWAY;
#endif

   RequestedStatus=MirandaStatusToSkype((int)wParam);

   /*
   if (SkypeStatus != ID_STATUS_OFFLINE)
   {
     InterlockedExchange((long*)&SkypeStatus, (int)wParam);
     ProtoBroadcastAck(SKYPE_PROTONAME, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) oldStatus, SkypeStatus);
   }
   */

   if ((int)wParam==ID_STATUS_OFFLINE && UnloadOnOffline)
   {
			if(UseCustomCommand)
			{
				DBVARIANT dbv;
				if(!DBGetContactSettingString(NULL,SKYPE_PROTONAME,"CommandLine",&dbv))
				{
					CloseSkypeAPI(dbv.pszVal);
					DBFreeVariant(&dbv);
				}
			}
			else
			{
				CloseSkypeAPI(skype_path);
			}

   } else if (AttachStatus==-1)
   {
	   forkthread(LaunchSkypeAndSetStatusThread, 0, (void *)wParam);
	   return 0;
   }

   iRet = SetUserStatus();
   LOG (("SkypeSetStatus exit"));
   return iRet;
}

INT64 __stdcall SendBroadcast( HANDLE hContact, int type, int result, HANDLE hProcess, LPARAM lParam )
{
	ACKDATA ack = {0};
	ack.cbSize = sizeof( ACKDATA );
	ack.szModule = SKYPE_PROTONAME;
	ack.hContact = hContact;
	ack.type = type;
	ack.result = result;
	ack.hProcess = hProcess;
	ack.lParam = lParam;
	return CallService( MS_PROTO_BROADCASTACK, 0, ( LPARAM )&ack );
}

static void __cdecl SkypeGetAwayMessageThread( HANDLE hContact )
{
	DBVARIANT dbv;
	if ( !DBGetContactSettingString( hContact, "CList", "StatusMsg", &dbv )) {
		SendBroadcast( hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, ( HANDLE )1, ( LPARAM )dbv.pszVal );
		DBFreeVariant( &dbv );
	}
	else SendBroadcast( hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, ( HANDLE )1, ( LPARAM )0 );
}

INT_PTR SkypeGetAwayMessage(WPARAM wParam,LPARAM lParam)
{
	CCSDATA* ccs = ( CCSDATA* )lParam;

	UNREFERENCED_PARAMETER(wParam);

	forkthread( SkypeGetAwayMessageThread, 0, ccs->hContact );
	return 1;
}

#define POLYNOMIAL (0x488781ED) /* This is the CRC Poly */
#define TOPBIT (1 << (WIDTH - 1)) /* MSB */
#define WIDTH 32

static int GetFileHash(char* filename)
{
	HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
	int remainder = 0, byte, bit;
	char data[1024];
	DWORD dwRead;

	if(hFile == INVALID_HANDLE_VALUE) return 0;

	do
	{
		// Read file chunk
		dwRead = 0;
		ReadFile(hFile, data, 1024, &dwRead, NULL);

		/* loop through each byte of data */
		for (byte = 0; byte < (int) dwRead; ++byte) {
			/* store the next byte into the remainder */
			remainder ^= (data[byte] << (WIDTH - 8));
			/* calculate for all 8 bits in the byte */
			for ( bit = 8; bit > 0; --bit) {
				/* check if MSB of remainder is a one */
				if (remainder & TOPBIT)
					remainder = (remainder << 1) ^ POLYNOMIAL;
				else
					remainder = (remainder << 1);
			}
		}
	} while(dwRead == 1024);

	CloseHandle(hFile);

	return remainder;
}

static int _GetFileSize(char* filename)
{
	HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
	int size;

	if(hFile == INVALID_HANDLE_VALUE)
		return 0;
	size = GetFileSize(hFile, NULL);
	CloseHandle(hFile);
	return size;
}

/* RetrieveUserAvatar
 *
 * Purpose: Get a user avatar from skype itself
 * Params : param=(void *)(HANDLE)hContact
 */
void RetrieveUserAvatar(void *param)
{
	HANDLE hContact = (HANDLE) param, file;
	PROTO_AVATAR_INFORMATION AI={0};
	ACKDATA ack = {0};
	DBVARIANT dbv;
	char AvatarFile[MAX_PATH+1], AvatarTmpFile[MAX_PATH+10], *ptr, *pszTempFile;

	if (hContact == NULL)
		return;

	// Mount default ack
	ack.cbSize = sizeof( ACKDATA );
	ack.szModule = SKYPE_PROTONAME;
	ack.hContact = hContact;
	ack.type = ACKTYPE_AVATAR;
	ack.result = ACKRESULT_FAILED;

	AI.cbSize = sizeof( AI );
	AI.hContact = hContact;

	// Get skype name
	if (DBGetContactSettingString(hContact, SKYPE_PROTONAME, SKYPE_NAME, &dbv) == 0)
	{
		if (dbv.pszVal)
		{
			// Get filename
			FoldersGetCustomPath(hProtocolAvatarsFolder, AvatarFile, sizeof(AvatarFile), DefaultAvatarsFolder);
			mir_snprintf(AvatarTmpFile, sizeof(AvatarTmpFile), "AVATAR 1 %s\\%s_tmp.jpg", AvatarFile, dbv.pszVal);
			pszTempFile = AvatarTmpFile+9;
			mir_snprintf(AvatarFile, sizeof(AvatarFile), "%s\\%s.jpg", AvatarFile, dbv.pszVal);

			// Just to be sure
			DeleteFileA(pszTempFile);
			file = CreateFileA(pszTempFile, 0, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
			if (file != INVALID_HANDLE_VALUE)
			{
				CloseHandle(file);
				if (ptr=SkypeGet ("USER", dbv.pszVal, AvatarTmpFile))
				{
					if (strncmp(ptr, "ERROR", 5) &&
						GetFileAttributesA(pszTempFile) != INVALID_FILE_ATTRIBUTES)
					{
						ack.result = ACKRESULT_SUCCESS;

						// Is no avatar image?
						if (!DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "ShowDefaultSkypeAvatar", 0)
							&& GetFileHash(pszTempFile) == 0x8d34e05d && _GetFileSize(pszTempFile) == 3751)
						{
							// Has no avatar
							AI.format = PA_FORMAT_UNKNOWN;
							ack.hProcess = (HANDLE)&AI;
							DeleteFileA(AvatarFile);
						}
						else
						{
							// Got it
							MoveFileExA(pszTempFile, AvatarFile, MOVEFILE_REPLACE_EXISTING);
							AI.format = PA_FORMAT_JPEG;
							strcpy(AI.filename, AvatarFile);
							ack.hProcess = (HANDLE)&AI;
						}

					}
					free (ptr);
				}
				DeleteFileA(pszTempFile);
			}

		}
		DBFreeVariant(&dbv);
	}
	CallService( MS_PROTO_BROADCASTACK, 0, ( LPARAM )&ack );
}


/* SkypeGetAvatarInfo
 *
 * Purpose: Set user avatar in profile
 * Params : wParam=0
 *			lParam=(LPARAM)(const char*)filename
 * Returns: 0 - Success
 *		   -1 - Failure
 */
INT_PTR SkypeGetAvatarInfo(WPARAM wParam,LPARAM lParam)
{

	DBVARIANT dbv;
	PROTO_AVATAR_INFORMATION* AI = ( PROTO_AVATAR_INFORMATION* )lParam;
	if (AI->hContact == NULL) // User
	{
		if (!DBGetContactSettingString(NULL,SKYPE_PROTONAME, "AvatarFile", &dbv))
		{
			lstrcpynA(AI->filename, dbv.pszVal, sizeof(AI->filename));
			DBFreeVariant(&dbv);
			return GAIR_SUCCESS;
		}
		else
			return GAIR_NOAVATAR;
	}
	else // Contact
	{
		DBVARIANT dbv;
		char AvatarFile[MAX_PATH+1];

		if (protocol < 7 && !bIsImoproxy)
			return GAIR_NOAVATAR;

		if (wParam & GAIF_FORCE)
		{
			// Request anyway
			forkthread(RetrieveUserAvatar, 0, (void *) AI->hContact);
			return GAIR_WAITFOR;
		}

		if (DBGetContactSettingString(AI->hContact, SKYPE_PROTONAME, SKYPE_NAME, &dbv))
			// No skype name ??
			return GAIR_NOAVATAR;

		if (dbv.pszVal == NULL)
		{
			// No skype name ??
			DBFreeVariant(&dbv);
			return GAIR_NOAVATAR;
		}

		// Get filename
		FoldersGetCustomPath(hProtocolAvatarsFolder, AvatarFile, sizeof(AvatarFile), DefaultAvatarsFolder);
		mir_snprintf(AvatarFile, sizeof(AvatarFile), "%s\\%s.jpg", AvatarFile, dbv.pszVal);
		DBFreeVariant(&dbv);

		// Check if the file exists
		if (GetFileAttributesA(AvatarFile) == INVALID_FILE_ATTRIBUTES)
			return GAIR_NOAVATAR;

		// Return the avatar
		AI->format = PA_FORMAT_JPEG;
		strcpy(AI->filename, AvatarFile);
		return GAIR_SUCCESS;
	}
}


/* SkypeGetAvatarCaps
 *
 * Purpose: Query avatar caps for a protocol
 * Params : wParam=One of AF_*
 *			lParam=Depends on wParam
 * Returns: Depends on wParam
 */
INT_PTR SkypeGetAvatarCaps(WPARAM wParam, LPARAM lParam)
{
	switch(wParam)
	{
		case AF_MAXSIZE:
		{
			POINT *p = (POINT *) lParam;
			if (p == NULL)
				return -1;

			p->x = 96;
			p->y = 96;
			return 0;
		}
		case AF_PROPORTION:
		{
			return PIP_NONE;
		}
		case AF_FORMATSUPPORTED:
		{
			if (lParam == PA_FORMAT_PNG || lParam == PA_FORMAT_JPEG)
				return TRUE;
			else
				return FALSE;
		}
		case AF_ENABLED:
		{
			return TRUE;
		}
		case AF_DONTNEEDDELAYS:
		{
			return FALSE;
		}
	}
	return -1;
}


INT_PTR SkypeGetStatus(WPARAM wParam, LPARAM lParam) {
	UNREFERENCED_PARAMETER(wParam);
	UNREFERENCED_PARAMETER(lParam);

	return SkypeStatus;
}

INT_PTR SkypeGetInfo(WPARAM wParam,LPARAM lParam) {
    CCSDATA *ccs = (CCSDATA *) lParam;

	UNREFERENCED_PARAMETER(wParam);

	forkthread(GetInfoThread, 0, ccs->hContact);
	return 0;
}

INT_PTR SkypeAddToList(WPARAM wParam, LPARAM lParam) {
	PROTOSEARCHRESULT *psr=(PROTOSEARCHRESULT*)lParam;
	char *name = NULL;
	HANDLE ret = INVALID_HANDLE_VALUE;

	LOG(("SkypeAddToList Adding API function called"));
	if (psr->cbSize!=sizeof(PROTOSEARCHRESULT) || !psr->nick) return 0;
	LOG(("SkypeAddToList OK"));
	name = mir_utf8encodeT(psr->nick);
	ret = add_contact(name, wParam);
	mir_free(name);
    return (INT_PTR)ret;
}

INT_PTR SkypeBasicSearch(WPARAM wParam, LPARAM lParam) {
	UNREFERENCED_PARAMETER(wParam);

	LOG(("SkypeBasicSearch %s", (char *)lParam));
	if (!SkypeInitialized) return 0;
	return (hSearchThread=forkthread(( pThreadFunc )BasicSearchThread, 0, _strdup((char *)lParam)));
}

void MessageSendWatchThread(msgsendwt_arg *arg) {
	char *str, *err;

	// sendwatchers need to be incremented before starting this thread
	LOG(("MessageSendWatchThread started."));
	str=SkypeRcvMsg(arg->szId, SkypeTime(NULL)-1, arg->hContact, DBGetContactSettingDword(NULL,"SRMsg","MessageTimeout",TIMEOUT_MSGSEND)+1000);
	InterlockedDecrement (&sendwatchers);
	if (str)
	{
		if (!DBGetContactSettingByte(arg->hContact, SKYPE_PROTONAME, "ChatRoom", 0)) {
			if (err=GetSkypeErrorMsg(str)) {
				ProtoBroadcastAck(SKYPE_PROTONAME, arg->hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE) 1, (LPARAM)Translate(err));
				free(err);
				free(str);
				free(arg);
				LOG(("MessageSendWatchThread terminated."));
				return;
			}
			ProtoBroadcastAck(SKYPE_PROTONAME, arg->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE) 1, 0);
		}
		free(str);
		LOG(("MessageSendWatchThread terminated gracefully."));
	}
	free (arg);
}

INT_PTR SkypeSendMessage(WPARAM wParam, LPARAM lParam) {
    CCSDATA *ccs = (CCSDATA *) lParam;
	DBVARIANT dbv;
	BOOL sendok=TRUE;
    char *msg = (char *) ccs->lParam, *utfmsg=NULL, *mymsgcmd=cmdMessage, szId[16]={0};
	static DWORD dwMsgNum = 0;
	BYTE bIsChatroom = 0 != DBGetContactSettingByte(ccs->hContact, SKYPE_PROTONAME, "ChatRoom", 0);

	UNREFERENCED_PARAMETER(wParam);

	if (bIsChatroom)
	{
		if (DBGetContactSettingString(ccs->hContact, SKYPE_PROTONAME, "ChatRoomID", &dbv))
			return 0;
		mymsgcmd="CHATMESSAGE";
	}
	else
	{
		if (DBGetContactSettingString(ccs->hContact, SKYPE_PROTONAME, SKYPE_NAME, &dbv))
			return 0;
		mymsgcmd="MESSAGE";
	}

	if (ccs->wParam & PREF_UTF) {
		utfmsg = msg;
	} else if (ccs->wParam & PREF_UNICODE) {
		utfmsg = (char*)make_utf8_string((WCHAR*)(msg+strlen(msg)+1));
	} else {
		if (utf8_encode(msg, &utfmsg)==-1) utfmsg=NULL;
	}
	if (protocol>=4) {
		InterlockedIncrement ((LONG*)&dwMsgNum);
		sprintf (szId, "#M%d ", dwMsgNum++);
	}
	InterlockedIncrement (&sendwatchers);
	if (!utfmsg || SkypeSend("%s%s %s %s", szId, mymsgcmd, dbv.pszVal, utfmsg)) sendok=FALSE;
	if (utfmsg && utfmsg!=msg) free(utfmsg);
	DBFreeVariant(&dbv);

	if (sendok) {
		msgsendwt_arg *psendarg = calloc(1, sizeof(msgsendwt_arg));

		if (psendarg) {
			psendarg->hContact = ccs->hContact;
			strcpy (psendarg->szId, szId);
			forkthread(MessageSendWatchThread, 0, psendarg);
		} else InterlockedDecrement (&sendwatchers);
		return 1;
	} else InterlockedDecrement (&sendwatchers);
	if (!bIsChatroom)
		ProtoBroadcastAck(SKYPE_PROTONAME, ccs->hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE) 1, (LPARAM)Translate("Connection to Skype lost"));
	return 0;
}

INT_PTR SkypeRecvMessage(WPARAM wParam, LPARAM lParam)
{
    DBEVENTINFO dbei={0};
    CCSDATA *ccs = (CCSDATA *) lParam;
    PROTORECVEVENT *pre = (PROTORECVEVENT *) ccs->lParam;

	UNREFERENCED_PARAMETER(wParam);

    DBDeleteContactSetting(ccs->hContact, "CList", "Hidden");
    dbei.cbSize = sizeof(dbei);
    dbei.szModule = SKYPE_PROTONAME;
    dbei.timestamp = pre->timestamp;
	if (pre->flags & PREF_CREATEREAD) dbei.flags|=DBEF_READ;
    if (pre->flags & PREF_UTF) dbei.flags|=DBEF_UTF;
    dbei.eventType = EVENTTYPE_MESSAGE;
    dbei.cbBlob = strlen(pre->szMessage) + 1;
	if (pre->flags & PREF_UNICODE)
		dbei.cbBlob += sizeof( wchar_t )*( (DWORD)wcslen(( wchar_t* )&pre->szMessage[dbei.cbBlob] )+1 );
    dbei.pBlob = (PBYTE) pre->szMessage;
    MsgList_Add (pre->lParam, (HANDLE)CallService(MS_DB_EVENT_ADD, (WPARAM)ccs->hContact, (LPARAM)&dbei));
    return 0;
}

INT_PTR SkypeUserIsTyping(WPARAM wParam, LPARAM lParam) {
	DBVARIANT dbv={0};
	HANDLE hContact = (HANDLE)wParam;

	if (protocol<5 && !bIsImoproxy) return 0;
	if (DBGetContactSettingString(hContact, SKYPE_PROTONAME, "Typing_Stream", &dbv)) {
		if (DBGetContactSettingString(hContact, SKYPE_PROTONAME, SKYPE_NAME, &dbv) == 0) {
			char szCmd[256];
			_snprintf (szCmd, sizeof(szCmd),
				"ALTER APPLICATION libpurple_typing CONNECT %s", dbv.pszVal);
			SkypeSend (szCmd);
			DBFreeVariant (&dbv);
			testfor (szCmd, 2000);
			// TODO: We should somehow cache the typing notify result and send it
			// after we got a connection, but in the meantime this notification won't
			// get sent on first run
		}
		return 0;
	}

	SkypeSend ("ALTER APPLICATION libpurple_typing DATAGRAM %s %s", dbv.pszVal,
		(lParam==PROTOTYPE_SELFTYPING_ON?"PURPLE_TYPING":"PURPLE_NOT_TYPING"));
	DBFreeVariant(&dbv);
	return 0;
}


INT_PTR SkypeSendAuthRequest(WPARAM wParam, LPARAM lParam) {
	CCSDATA* ccs = (CCSDATA*)lParam;
	DBVARIANT dbv;
	int retval;

	UNREFERENCED_PARAMETER(wParam);

	if (!ccs->lParam || DBGetContactSettingString(ccs->hContact, SKYPE_PROTONAME, SKYPE_NAME, &dbv))
		return 1;
	retval = SkypeSend("SET USER %s BUDDYSTATUS 2 %s", dbv.pszVal, (char *)ccs->lParam);
	DBFreeVariant(&dbv);
	if (retval) return 1; else return 0;
}

INT_PTR SkypeRecvAuth(WPARAM wParam, LPARAM lParam) {
	DBEVENTINFO dbei = {0};
	CCSDATA* ccs = (CCSDATA*)lParam;
	PROTORECVEVENT* pre = (PROTORECVEVENT*)ccs->lParam;

	UNREFERENCED_PARAMETER(wParam);

	DBDeleteContactSetting(ccs->hContact, "CList", "Hidden");

	dbei.cbSize    = sizeof(dbei);
	dbei.szModule  = SKYPE_PROTONAME;
	dbei.timestamp = pre->timestamp;
	dbei.flags     = ((pre->flags & PREF_CREATEREAD)?DBEF_READ:0);
	dbei.eventType = EVENTTYPE_AUTHREQUEST;
	dbei.cbBlob	   = pre->lParam;
	dbei.pBlob     = (PBYTE)pre->szMessage;

	CallService(MS_DB_EVENT_ADD, (WPARAM)NULL, (LPARAM)&dbei);
	return 0;
}

char *__skypeauth(WPARAM wParam) {
	DBEVENTINFO dbei={0};

	if (!SkypeInitialized) return NULL;

	dbei.cbSize = sizeof(dbei);
	if ((dbei.cbBlob = CallService(MS_DB_EVENT_GETBLOBSIZE, wParam, 0))==-1 ||
		!(dbei.pBlob = (unsigned char*)malloc(dbei.cbBlob)))
	{	return NULL; }

	if (CallService(MS_DB_EVENT_GET, wParam, (LPARAM)&dbei) ||
		dbei.eventType != EVENTTYPE_AUTHREQUEST ||
		strcmp(dbei.szModule, SKYPE_PROTONAME))
	{
		free(dbei.pBlob);
		return NULL;
	}
	return (char *)dbei.pBlob;
}

INT_PTR SkypeAuthAllow(WPARAM wParam, LPARAM lParam) {
	char *pBlob;

	UNREFERENCED_PARAMETER(lParam);

	if (pBlob=__skypeauth(wParam))
	{
		int retval=SkypeSend("SET USER %s ISAUTHORIZED TRUE", pBlob+sizeof(DWORD)*2);
		free(pBlob);
		if (!retval) return 0;
	}
	return 1;
}

INT_PTR SkypeAuthDeny(WPARAM wParam, LPARAM lParam) {
	char *pBlob;

	UNREFERENCED_PARAMETER(lParam);

	if (pBlob=__skypeauth(wParam))
	{
		int retval=SkypeSend("SET USER %s ISAUTHORIZED FALSE", pBlob+sizeof(DWORD)*2);
		free(pBlob);
		if (!retval) return 0;
	}
	return 1;
}


INT_PTR SkypeAddToListByEvent(WPARAM wParam, LPARAM lParam) {
	char *pBlob;

	UNREFERENCED_PARAMETER(lParam);

	if (pBlob=__skypeauth(wParam))
	{
		HANDLE hContact = add_contact(pBlob+sizeof(DWORD)*2, LOWORD(wParam));
		free(pBlob);
		if (hContact) return (int)hContact;
	}
	return 0;
}

INT_PTR SkypeRegisterProxy(WPARAM wParam, LPARAM lParam) {
	UNREFERENCED_PARAMETER(wParam);

	if (!lParam) {
		free (pszProxyCallout);
		pszProxyCallout = NULL;
	}
	pszProxyCallout = _strdup((char*)lParam);
	bIsImoproxy = TRUE;
	return 0;
}


void CleanupNicknames(char *dummy) {
	HANDLE hContact;
	char *szProto;
	DBVARIANT dbv, dbv2;

	UNREFERENCED_PARAMETER(dummy);

	LOG(("CleanupNicknames Cleaning up..."));
	for (hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);hContact != NULL;hContact=(HANDLE)CallService( MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0)) {
		szProto = (char*)CallService( MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0 );
		if (szProto!=NULL && !strcmp(szProto, SKYPE_PROTONAME) &&
			DBGetContactSettingByte(hContact, SKYPE_PROTONAME, "ChatRoom", 0)==0)
		{
			if (DBGetContactSettingString(hContact, SKYPE_PROTONAME, SKYPE_NAME, &dbv)) continue;
			if (DBGetContactSettingString(hContact, SKYPE_PROTONAME, "Nick", &dbv2)) {
				DBFreeVariant(&dbv);
				continue;
			}
			DBDeleteContactSetting(hContact, SKYPE_PROTONAME, "Nick");
			GetInfoThread(hContact);
			DBFreeVariant(&dbv);
			DBFreeVariant(&dbv2);
		}
	}
	OUTPUT(_T("Cleanup finished."));
}

/////////////////////////////////////////////////////////////////////////////////////////
// EnterBitmapFileName - enters a bitmap filename

int __stdcall EnterBitmapFileName( char* szDest )
{
	char szFilter[ 512 ];
	OPENFILENAMEA ofn = {0};
	*szDest = 0;

	CallService( MS_UTILS_GETBITMAPFILTERSTRINGS, sizeof szFilter, ( LPARAM )szFilter );
	ofn.lStructSize = sizeof( OPENFILENAME );
	ofn.lpstrFilter = szFilter;
	ofn.lpstrFile = szDest;
	ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
	ofn.nMaxFile = MAX_PATH;
	ofn.nMaxFileTitle = MAX_PATH;
	ofn.lpstrDefExt = "bmp";
	if ( !GetOpenFileNameA( &ofn ))
		return 1;

	return ERROR_SUCCESS;
}

int MirandaExit(WPARAM wParam, LPARAM lParam) {
	UNREFERENCED_PARAMETER(wParam);
	UNREFERENCED_PARAMETER(lParam);

	MirandaShuttingDown=TRUE;
	return 0;
}

int OkToExit(WPARAM wParam, LPARAM lParam) {
	UNREFERENCED_PARAMETER(wParam);
	UNREFERENCED_PARAMETER(lParam);

//	logoff_contacts();
	MirandaShuttingDown=TRUE;

	// Trigger all semaphores and events just to be sure that there is no deadlock
	ReleaseSemaphore(SkypeMsgReceived, receivers, NULL);
    SetEvent (SkypeReady);
	SetEvent (MessagePumpReady);
#ifdef SKYPEBUG_OFFLN
	SetEvent(GotUserstatus);
#endif
	SetEvent (hBuddyAdded);

	SkypeFlush ();
	PostMessage (g_hWnd, WM_CLOSE, 0, 0);
	return 0;
}


struct PLUGINDI {
	char **szSettings;
	int dwCount;
};

// Taken from pluginopts.c and modified
int EnumOldPluginName(const char *szSetting,LPARAM lParam)
{
	struct PLUGINDI *pdi=(struct PLUGINDI*)lParam;
	if (pdi && lParam) {
			pdi->szSettings=(char**)realloc(pdi->szSettings,(pdi->dwCount+1)*sizeof(char*));
			pdi->szSettings[pdi->dwCount++]=_strdup(szSetting);
	}
	return 0;
}

// Are there any Skype users on list?
// 1 --> Yes
// 0 --> No
int AnySkypeusers(void)
{
	HANDLE hContact;
	DBVARIANT dbv;
	int tCompareResult;

	// already on list?
	for (hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
		 hContact != NULL;
		 hContact=(HANDLE)CallService( MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0))
	{
		// GETCONTACTBASEPROTO doesn't work on not loaded protocol, therefore get
		// protocol from DB
		if (DBGetContactSettingString(hContact, "Protocol", "p", &dbv)) continue;
        tCompareResult = !strcmp(dbv.pszVal, SKYPE_PROTONAME);
		DBFreeVariant(&dbv);
		if (tCompareResult) return 1;
	}
	return 0;
}


void UpgradeName(char *OldName)
{
	DBCONTACTENUMSETTINGS cns;
	DBCONTACTWRITESETTING cws;
	DBVARIANT dbv;
	HANDLE hContact=NULL;
	struct PLUGINDI pdi;

	LOG(("Updating old database settings if there are any..."));
	cns.pfnEnumProc=EnumOldPluginName;
	cns.lParam=(LPARAM)&pdi;
	cns.szModule=OldName;
	cns.ofsSettings=0;

	hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);

	for ( ;; ) {
		memset(&pdi,0,sizeof(pdi));
		CallService(MS_DB_CONTACT_ENUMSETTINGS,(WPARAM)hContact,(LPARAM)&cns);
		// Upgrade Protocol settings to new string
		if (pdi.szSettings) {
			int i;

			LOG(("We're currently upgrading..."));
			for (i=0;i<pdi.dwCount;i++) {
				if (!DBGetContactSettingString(hContact, OldName, pdi.szSettings[i], &dbv)) {
					cws.szModule=SKYPE_PROTONAME;
					cws.szSetting=pdi.szSettings[i];
					cws.value=dbv;
					if (!CallService(MS_DB_CONTACT_WRITESETTING,(WPARAM)hContact,(LPARAM)&cws))
						DBDeleteContactSetting(hContact,OldName,pdi.szSettings[i]);
					DBFreeVariant(&dbv);
				}
				free(pdi.szSettings[i]);
			}
			free(pdi.szSettings);
		}
		// Upgrade Protocol assignment, if we are not main contact
		if (hContact && !DBGetContactSettingString(hContact, "Protocol", "p", &dbv)) {
			if (!strcmp(dbv.pszVal, OldName))
				DBWriteContactSettingString(hContact, "Protocol", "p", SKYPE_PROTONAME);
			DBFreeVariant(&dbv);
		}
		if (!hContact) break;
		hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0);
	}

	DBWriteContactSettingByte(NULL, SKYPE_PROTONAME, "UpgradeDone", (BYTE)1);
	return;
}

void __cdecl MsgPump (char *dummy)
{
  MSG msg;

  WNDCLASS WndClass;

  UNREFERENCED_PARAMETER(dummy);

  // Create window class
  WndClass.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;
  WndClass.lpfnWndProc = (WNDPROC)WndProc;
  WndClass.cbClsExtra = 0;
  WndClass.cbWndExtra = 0;
  WndClass.hInstance =  hInst;
  WndClass.hIcon = NULL;
  WndClass.hCursor = NULL;
  WndClass.hbrBackground = NULL;
  WndClass.lpszMenuName = NULL;
  WndClass.lpszClassName = _T("SkypeApiDispatchWindow");
  RegisterClass(&WndClass);
  // Do not check the retval of RegisterClass, because on non-unicode
  // win98 it will fail, as it is a stub that returns false() there

  // Create main window
  g_hWnd=CreateWindowEx( WS_EX_APPWINDOW|WS_EX_WINDOWEDGE,
		_T("SkypeApiDispatchWindow"), _T(""), WS_BORDER|WS_SYSMENU|WS_MINIMIZEBOX,
		CW_USEDEFAULT, CW_USEDEFAULT, 128, 128, NULL, 0, (HINSTANCE)WndClass.hInstance, 0);

  LOG (("Created Dispatch window with handle %08X", (long)g_hWnd));
  if (!g_hWnd) {
	OUTPUT(_T("Cannot create window."));
	TellError(GetLastError());
	SetEvent(MessagePumpReady);
    return;
  }
  ShowWindow(g_hWnd, 0);
  UpdateWindow(g_hWnd);
  msgPumpThreadId = GetCurrentThreadId();
  SetEvent(MessagePumpReady);

  LOG (("Messagepump started."));
  while (GetMessage (&msg, NULL, 0, 0) > 0 && !Miranda_Terminated()) {
	  TranslateMessage (&msg);
	  DispatchMessage (&msg);
  }
  UnregisterClass (WndClass.lpszClassName, hInst);
  LOG (("Messagepump stopped."));
}

// DLL Stuff //

__declspec(dllexport) PLUGININFOEX* MirandaPluginInfo(DWORD mirVersion)
{
	mirandaVersion = mirVersion;

	pluginInfo.cbSize = sizeof(PLUGININFOEX);
	return (PLUGININFOEX*) &pluginInfo;
}

__declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD mirVersion)
{
	mirandaVersion = mirVersion;

	return &pluginInfo;
}

static const MUUID interfaces[] = {MUUID_SKYPE_CALL, MIID_LAST};
__declspec(dllexport) const MUUID * MirandaPluginInterfaces(void)
{
	return interfaces;
}


BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
{
	UNREFERENCED_PARAMETER(fdwReason);
	UNREFERENCED_PARAMETER(lpvReserved);

	hInst = hinstDLL;
	return TRUE;
}


int PreShutdown(WPARAM wParam, LPARAM lParam) {
	UNREFERENCED_PARAMETER(wParam);
	UNREFERENCED_PARAMETER(lParam);

	PostThreadMessage(msgPumpThreadId, WM_QUIT, 0, 0);
	return 0;
}

int __declspec(dllexport) Load()
{
	PROTOCOLDESCRIPTOR pd = {0};
	DWORD Buffsize;
	HKEY MyKey;
	BOOL SkypeInstalled;
	BOOL UseCustomCommand;
	WSADATA wsaData = {0};
	char path[MAX_PATH];

	mir_getLP(&pluginInfo);

	GetModuleFileNameA( hInst, path, sizeof( path ));
	_splitpath (path, NULL, NULL, SKYPE_PROTONAME, NULL);
	CharUpperA( SKYPE_PROTONAME );

	InitializeCriticalSection(&RingAndEndcallMutex);
	InitializeCriticalSection(&QueryThreadMutex);
	InitializeCriticalSection(&TimeMutex);


#ifdef _DEBUG
	init_debug();
#endif

	LOG(("Load: Skype Plugin loading..."));

	// We need to upgrade SKYPE_PROTOCOL internal name to Skype if not already done
	if (!DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "UpgradeDone", 0))
		UpgradeName("SKYPE_PROTOCOL");

    // Initialisation of Skype MsgQueue must be done because of Cleanup in end and
	// Mutex is also initialized here.
	LOG(("SkypeMsgInit initializing Skype MSG-queue"));
	if (SkypeMsgInit()==-1) {
		OUTPUT(_T("Memory allocation error on startup."));
		return 0;
	}

	// On first run on new profile, ask user, if he wants to enable the plugin in
	// this profile
	// --> Fixing Issue #0000006 from bugtracker.
	if (!DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "FirstRun", 0)) {
		DBWriteContactSettingByte(NULL, SKYPE_PROTONAME, "FirstRun", 1);
		if (AnySkypeusers()==0) // First run, it seems :)
			if (MessageBox(NULL, TranslateT("This seems to be the first time that you're running the Skype protocol plugin. Do you want to enable the protocol for this Miranda-Profile? (If you chose NO, you can always enable it in the plugin options later."), _T("Welcome!"), MB_ICONQUESTION|MB_YESNO)==IDNO) {
				char path[MAX_PATH], *filename;
				GetModuleFileNameA(hInst, path, sizeof(path));
				if (filename = strrchr(path,'\\')+1)
					DBWriteContactSettingByte(NULL,"PluginDisable",filename,1);
				return 0;
			}
	}


	// Check if Skype is installed
	SkypeInstalled=TRUE;
	UseCustomCommand = (BYTE)DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "UseCustomCommand", 0);
	UseSockets = (BOOL)DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "UseSkype2Socket", 0);

	if (!UseSockets && !UseCustomCommand)
	{
		BOOL is_key_opened = FALSE;
		if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Skype\\Phone"), 0, KEY_READ, &MyKey)!=ERROR_SUCCESS)
		{
			if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("Software\\Skype\\Phone"), 0, KEY_READ, &MyKey)!=ERROR_SUCCESS)
			{
				SkypeInstalled=FALSE;
			}
			else
				is_key_opened = TRUE;
		}
		else
			is_key_opened = TRUE;

		Buffsize=sizeof(skype_path);

		if(is_key_opened)
		{
			if (SkypeInstalled==FALSE)
				skype_path[0]=0;
			else if (RegQueryValueExA(MyKey, "SkypePath", NULL, NULL, (unsigned char *)skype_path,  &Buffsize)!=ERROR_SUCCESS)
			{
				//OUTPUT("Skype was not found installed :( \nMaybe you are using portable skype.");
				RegCloseKey(MyKey);
				skype_path[0]=0;
				//return 0;
			}
			else
				RegCloseKey(MyKey);
		}
	}
	WSAStartup(MAKEWORD(2,2), &wsaData);

	// Start Skype connection
	if (!(ControlAPIAttach=RegisterWindowMessage(_T("SkypeControlAPIAttach"))) || !(ControlAPIDiscover=RegisterWindowMessage(_T("SkypeControlAPIDiscover"))))
	{
			OUTPUT(_T("Cannot register Window message."));
			return 0;
	}

	SkypeMsgReceived=CreateSemaphore(NULL, 0, MAX_MSGS, NULL);
	if (!(SkypeReady=CreateEvent(NULL, TRUE, FALSE, NULL)) ||
		!(MessagePumpReady=CreateEvent(NULL, FALSE, FALSE, NULL)) ||
#ifdef SKYPEBUG_OFFLN
	    !(GotUserstatus=CreateEvent(NULL, TRUE, FALSE, NULL)) ||
#endif
		!(hBuddyAdded=CreateEvent(NULL, FALSE, FALSE, NULL)) ||
		!(FetchMessageEvent=CreateEvent(NULL, FALSE, TRUE, NULL))) {
		 OUTPUT(_T("Unable to create Mutex!"));
		return 0;
	}

	/* Register the module */
	ZeroMemory(&pd, sizeof(pd));
	pd.cbSize = PROTOCOLDESCRIPTOR_V3_SIZE;
	pd.szName = SKYPE_PROTONAME;
	pd.type   = PROTOTYPE_PROTOCOL;
	CallService(MS_PROTO_REGISTERMODULE, 0, (LPARAM)&pd);

	VoiceServiceInit();

	CreateServices();
	HookEvents();
	InitVSApi();
	MsgList_Init();

	HookEvent(ME_SYSTEM_PRESHUTDOWN, PreShutdown);

	// Startup Message-pump
    forkthread (( pThreadFunc )MsgPump, 0, NULL);
	WaitForSingleObject(MessagePumpReady, INFINITE);
	return 0;
}



int __declspec( dllexport ) Unload(void)
{
	BOOL UseCustomCommand = DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "UseCustomCommand", 0);
	BOOL Shutdown = DBGetContactSettingByte(NULL, SKYPE_PROTONAME, "Shutdown", 0);

	LOG (("Unload started"));

	if ( Shutdown && ((skype_path && skype_path[0]) ||UseCustomCommand) ) {

		if(UseCustomCommand)
		{
			DBVARIANT dbv;
			if(!DBGetContactSettingString(NULL,SKYPE_PROTONAME,"CommandLine",&dbv))
			{
				char szAbsolutePath[MAX_PATH];

				TranslateMirandaRelativePathToAbsolute(dbv.pszVal, szAbsolutePath, FALSE);
				_spawnl(_P_NOWAIT, szAbsolutePath, szAbsolutePath, "/SHUTDOWN", NULL);
				LOG (("Unload Sent /shutdown to %s", szAbsolutePath));
				DBFreeVariant(&dbv);
			}
		}
		else
		{
			_spawnl(_P_NOWAIT, skype_path, skype_path, "/SHUTDOWN", NULL);
			LOG (("Unload Sent /shutdown to %s", skype_path));
		}

	}
	SkypeMsgCleanup();
	//WSACleanup();
	FreeVSApi();
	UnhookEvents();
	UnhookEvent(hChatEvent);
	UnhookEvent (hChatMenu);
	UnhookEvent (hEvInitChat);
	DestroyHookableEvent(hInitChat);
	VoiceServiceExit();
	GCExit();
	MsgList_Exit();

	CloseHandle(SkypeReady);
	CloseHandle(SkypeMsgReceived);
#ifdef SKYPEBUG_OFFLN
	CloseHandle(GotUserstatus);
#endif
	CloseHandle(MessagePumpReady);
	CloseHandle(hBuddyAdded);
	CloseHandle(FetchMessageEvent);

	DeleteCriticalSection(&RingAndEndcallMutex);
	DeleteCriticalSection(&QueryThreadMutex);

	SkypeRegisterProxy (0, 0);
	LOG (("Unload: Shutdown complete"));
#ifdef _DEBUG
	end_debug();
#endif
	DeleteCriticalSection(&TimeMutex);
	return 0;
}