/*

WinPopup Protocol plugin for Miranda IM.

Copyright (C) 2004-2010 Nikolay Raspopov

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

// ��������� "\\Nick" ��� ������ ShellExecute() ("Nick" �� ��������)
// wParam=hContact
// lParam=0
// ���������� 0
#define PS_EXPLORE		"/Explore"

// ���������� ������ ��� ������� ���������� �������� �� �����/������
// wParam=Position
// lParam=0
// ���������� 0
#define PS_ADDDIALOG	"/AddDialog"

/////////////////////////////////////////////////////////////////////////////
// ��������� �������

// WinPopup Protocol/Explore
INT_PTR Explore(WPARAM wParam, LPARAM lParam);
// WinPopup Protocol/AddDialog
INT_PTR AddDialog(WPARAM wParam, LPARAM lParam);
// WinPopup Protocol/GetCaps
INT_PTR GetCaps(WPARAM flagNum, LPARAM lParam);
// WinPopup Protocol/GetName
INT_PTR GetName(WPARAM cchName, LPARAM szName);
// WinPopup Protocol/LoadIcon
INT_PTR LoadIcon(WPARAM whichIcon, LPARAM lParam);
// WinPopup Protocol/SetStatus
INT_PTR SetStatus(WPARAM newStatus, LPARAM lParam);
// WinPopup Protocol/SetAwayMsg
INT_PTR SetAwayMsg(WPARAM status_mode, LPARAM szMessage);
// WinPopup Protocol/GetAwayMsg
INT_PTR GetAwayMsg(WPARAM wParam, LPARAM lParam);
// WinPopup Protocol/GetStatus
INT_PTR GetStatus(WPARAM wParam, LPARAM lParam);
// WinPopup Protocol/GetInfo
INT_PTR GetInfo(WPARAM flags, LPARAM lParam);
// WinPopup Protocol/RecvMessage
INT_PTR RecvMessage(WPARAM flags, LPARAM lParam);
// WinPopup Protocol/SendMsg
INT_PTR SendMsg(WPARAM flags, LPARAM lParam);
// WinPopup Protocol/BasicSearch
INT_PTR BasicSearch(WPARAM wParam, LPARAM szId);
// WinPopup Protocol/AddToList
INT_PTR AddToList(WPARAM flags, LPARAM lParam);
// WinPopup Protocol/GetAvatarCaps
INT_PTR GetAvatarCaps(WPARAM wParam, LPARAM lParam);
// WinPopup Protocol/GetAvatarInformation
INT_PTR GetAvatarInfo(WPARAM flags, LPARAM lParam);
// WinPopup Protocol/SetMyAvatar
INT_PTR SetMyAvatar(WPARAM wParam, LPARAM lParam);
// WinPopup Protocol/GetMyAvatar
INT_PTR GetMyAvatar(WPARAM wParam, LPARAM lParam);
// WinPopup Protocol/CreateAccMgrUI
INT_PTR CreateAccMgrUI(WPARAM wParam, LPARAM lParam);

/////////////////////////////////////////////////////////////////////////////
// ������������

// ����������� ME_SYSTEM_MODULESLOADED
int __cdecl SYSTEM_MODULESLOADED(WPARAM wParam, LPARAM lParam);
// ����������� ME_SYSTEM_PRESHUTDOWN
int __cdecl SYSTEM_PRESHUTDOWN(WPARAM wParam, LPARAM lParam);

/////////////////////////////////////////////////////////////////////////////
// ������ �������

int hLangpack;

const PROTOCOLDESCRIPTOR pluginPD =
{
	sizeof( PROTOCOLDESCRIPTOR ),
	modname,
	PROTOTYPE_PROTOCOL
};

static PLUGININFOEX pluginInfoEx =
{
	sizeof(PLUGININFOEX),
	modname,
	PLUGIN_MAKE_VERSION (0,0,0,18),
	"Allows you to send and receive messages over Microsoft LAN. "
	"WinPopup and Net Send replacement powered by Miranda IM.",
	"Nikolay Raspopov",
	"ryo-oh-ki@narod.ru",
	"� 2004-2010 Nikolay Raspopov",
	"http://www.cherubicsoft.com/miranda/",
	UNICODE_AWARE,
	// {DE6EE412-ACE3-45db-A329-D618FABB4291}
	{0xde6ee412, 0xace3, 0x45db, {0xa3, 0x29, 0xd6, 0x18, 0xfa, 0xbb, 0x42, 0x91}}
};

extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = {MIID_PROTOCOL, MIID_LAST};

HANDLE			plugin_FnGetCaps			= NULL;
HANDLE			plugin_FnGetName			= NULL;
HANDLE			plugin_FnLoadIcon			= NULL;
HANDLE			plugin_FnSetStatus			= NULL;
HANDLE			plugin_FnGetStatus			= NULL;
HANDLE			plugin_FnGetAwayMsg			= NULL;
HANDLE			plugin_FnSetAwayMsg			= NULL;
HANDLE			plugin_FnGetInfo			= NULL;
HANDLE			plugin_FnSendMsg			= NULL;
HANDLE			plugin_FnRecvMessage		= NULL;
HANDLE			plugin_FnBasicSearch		= NULL;
HANDLE			plugin_FnAddToList			= NULL;
HANDLE			plugin_FnExplore			= NULL;
HANDLE			plugin_FnAddDialog			= NULL;
HANDLE			plugin_FnGetAvatarCaps		= NULL;
HANDLE			plugin_FnGetAvatarInfo		= NULL;
HANDLE			plugin_FnSetMyAvatar		= NULL;
HANDLE			plugin_FnGetMyAvatar		= NULL;
HANDLE			plugin_FnCreateAccMgrUI		= NULL;
HANDLE			plugin_SYSTEM_MODULESLOADED	= NULL;
HANDLE			plugin_SYSTEM_PRESHUTDOWN	= NULL;
HANDLE			plugin_OPT_INITIALISE		= NULL;
HANDLE			plugin_USERINFO_INITIALISE	= NULL;

/////////////////////////////////////////////////////////////////////////////
// ������������ ������� ����������� DllLoader��

DllLoader		pluginNetapi32 ( _T("netapi32.dll"), false);
DllLoader		pluginAdvapi32 ( _T("advapi32.dll"), false);
FuncPtrType(NET_API_STATUS (NET_API_FUNCTION *) (LMSTR, DWORD, LPBYTE*) ) fnNetWkstaGetInfo;
FuncPtrType(DWORD (NET_API_FUNCTION *) (LPVOID) ) fnNetApiBufferFree;
FuncPtrType(DWORD (NET_API_FUNCTION *) (LPCWSTR, LPCWSTR, LPCWSTR, LPBYTE, DWORD) ) fnNetMessageBufferSend;
FuncPtrType(SC_HANDLE (WINAPI *) (LPCTSTR, LPCTSTR, DWORD) ) fnOpenSCManager;
FuncPtrType(SC_HANDLE (WINAPI *) (SC_HANDLE, LPCTSTR, DWORD) ) fnOpenService;
FuncPtrType(BOOL (WINAPI *) (SC_HANDLE, DWORD, LPSERVICE_STATUS) ) fnControlService;
FuncPtrType(BOOL (WINAPI *) (SC_HANDLE, LPSERVICE_STATUS) ) fnQueryServiceStatus;
FuncPtrType(BOOL (WINAPI *) (SC_HANDLE) ) fnCloseServiceHandle;
FuncPtrType(BOOL (WINAPI *) (SC_HANDLE, DWORD, LPCTSTR*) ) fnStartService;
FuncPtrType(SC_LOCK (WINAPI *) (SC_HANDLE) ) fnLockServiceDatabase;
FuncPtrType(BOOL (WINAPI *) (SC_LOCK) ) fnUnlockServiceDatabase;
FuncPtrType(BOOL (WINAPI *) (SC_HANDLE, DWORD, DWORD, DWORD, LPCTSTR, LPCTSTR, LPDWORD, LPCTSTR, LPCTSTR, LPCTSTR, LPCTSTR) ) fnChangeServiceConfig;

#define CreateHook( hService, szName, Function ) \
	_ASSERT( hService == NULL ); \
	hService = HookEvent( szName, Function ); \
	_ASSERT ( hService != NULL ); \
	if ( ! hService ) return 1;

#define DestroyHook( hService ) \
	if ( hService ) { \
		UnhookEvent( hService ); \
		hService = NULL; \
	}

#define CreateServiceFn( hService, szName, Function ) \
	_ASSERT( hService == NULL ); \
	hService = CreateServiceFunction( modname szName, Function ); \
	_ASSERT( hService != NULL ); \
	if ( ! hService ) return 1;

#ifdef _DEBUG
	#define DestroyServiceFn( hService ) \
		if( hService ) { \
			_ASSERT( DestroyServiceFunction( hService ) == 0 ); \
			hService = NULL; \
		}
#else // _DEBUG
	#define DestroyServiceFn( hService ) \
		if( hService ) { \
			DestroyServiceFunction( hService ); \
			hService = NULL; \
		}
#endif // _DEBUG


INT_PTR Explore(WPARAM wParam, LPARAM /* lParam */)
{
	CString sNick = GetNick( (HANDLE)wParam );
	if ( pluginInstalled && ! sNick.IsEmpty() )
	{
		// ����� UNC �����: \\Nick
		CString cmd( _T("\\\\") );
		cmd += sNick;
		ShellExecute( NULL, NULL, cmd, NULL, NULL, SW_SHOWDEFAULT );
	}
	return 0;
}

INT_PTR AddDialog (WPARAM /* wParam */, LPARAM /* lParam */)
{
	AddDialog( NULL );
	return 0;
}

INT_PTR GetCaps (WPARAM flagNum, LPARAM /* lParam */)
{
	INT_PTR nReturn = 0;
	switch ( flagNum )
	{
		case PFLAGNUM_1:
			nReturn = PF1_IM | PF1_BASICSEARCH | PF1_MODEMSG | PF1_PEER2PEER;
			break;

		case PFLAGNUM_2:
		case PFLAGNUM_3:
			nReturn = PF2_ONLINE | PF2_INVISIBLE | PF2_SHORTAWAY | PF2_LONGAWAY |
				PF2_LIGHTDND | PF2_HEAVYDND | PF2_FREECHAT |
				PF2_ONTHEPHONE | PF2_OUTTOLUNCH;
			break;

		case PFLAGNUM_4:
			nReturn = PF4_AVATARS;
			break;

		case PFLAGNUM_5:
			nReturn = 0;
			break;

		case PFLAG_UNIQUEIDTEXT:
			nReturn = (INT_PTR)Translate ("Computer,User,Group");
			break;

		case PFLAG_UNIQUEIDSETTING:
			nReturn = (INT_PTR)"Nick";
			break;

		case PFLAG_MAXLENOFMESSAGE:
			// HANDLE hContact = (HANDLE) lParam;
			nReturn = 0x7fffffff;
			break;
	}
	return nReturn;
}

INT_PTR GetName(WPARAM cchName, LPARAM szName)
{
	lstrcpynA( (char*) szName, modtitle, static_cast <int>( cchName ) );
	return 0;
}

INT_PTR LoadIcon(WPARAM whichIcon, LPARAM /* lParam */)
{
	UINT id;
	switch ( whichIcon & 0xFFFF )
	{
		case PLI_PROTOCOL:
			id = IDI_WINPOPUP;
			break;

		case PLI_ONLINE:
			id = IDI_ONLINE;
			break;

		case PLI_OFFLINE:
			id = IDI_OFFLINE;
			break;

		default:
			return 0;	
	}
	return (INT_PTR)LoadImage (pluginModule, MAKEINTRESOURCE (id), IMAGE_ICON,
		(whichIcon & PLIF_SMALL ? 16 : 32), (whichIcon & PLIF_SMALL ? 16 : 32), 0);
}

INT_PTR SetStatus (WPARAM newStatus, LPARAM /* lParam */)
{
	if ( pluginInstalled && pluginCurrentStatus != (int)newStatus )
	{
		LOG ("SetStatus from \"%s\" to \"%s\"",
			STATUS2TEXT(pluginCurrentStatus), STATUS2TEXT(newStatus));

		pluginRequestedStatus = newStatus;
		switch (newStatus) {
			case ID_STATUS_OFFLINE:
				GotoOffline ();
				break;
			case ID_STATUS_FREECHAT:
			case ID_STATUS_INVISIBLE:
				newStatus = ID_STATUS_ONLINE;
			case ID_STATUS_ONLINE:
			case ID_STATUS_AWAY:
			case ID_STATUS_DND:
			case ID_STATUS_NA:
			case ID_STATUS_OCCUPIED:
			case ID_STATUS_ONTHEPHONE:
			case ID_STATUS_OUTTOLUNCH:
			default:
				GotoOnline ();
				break;
		}
	}
	return 0;
}

INT_PTR SetAwayMsg (WPARAM status_mode, LPARAM szMessage)
{
	pluginStatusMessage[ status_mode ] = ( szMessage ? (LPCSTR) szMessage : "" );
	return 0;
}

INT_PTR GetAwayMsg(WPARAM /* wParam */, LPARAM lParam)
{
	CCSDATA *ccs = (CCSDATA*)lParam;
	if ( pluginInstalled && ccs && ccs->hContact )
	{
		if ( ContactData* data = new ContactData )
		{
			data->hContact = ccs->hContact;
			HANDLE cookie = data->cookie = GenerateCookie();
			mir_forkthread( GetAwayMsgThread, data );
			return (INT_PTR)cookie;
		}
	}
	return 0;
}

INT_PTR GetStatus (WPARAM /* wParam */, LPARAM /* lParam */)
{
	return (INT_PTR)pluginCurrentStatus;
}

INT_PTR GetInfo (WPARAM /* flags */, LPARAM lParam)
{
	CCSDATA* ccs = (CCSDATA*)lParam;
	if ( pluginInstalled && ccs && ccs->hContact )
	{
		mir_forkthread( GetInfoThread, ccs->hContact );
		return 0;
	}
	return 1;
}

INT_PTR RecvMessage (WPARAM /* flags */, LPARAM lParam)
{
	CCSDATA* ccs = (CCSDATA*) lParam;
	if ( pluginInstalled && ccs && ccs->hContact && ccs->lParam)
	{
		PROTORECVEVENT *pre = (PROTORECVEVENT*) ccs->lParam;

		// ���������� ���������
		DBDeleteContactSetting (ccs->hContact, "CList", "Hidden");
		DBEVENTINFO ei = { 0 };
		ei.cbSize = sizeof (DBEVENTINFO);
		ei.szModule = modname;
		ei.timestamp = pre->timestamp;
		ei.flags = (pre->flags & PREF_CREATEREAD) ? DBEF_READ : 0u;
		ei.eventType = EVENTTYPE_MESSAGE;
		ei.cbBlob = (DWORD)( lstrlenA (pre->szMessage) + 1 );
		ei.pBlob = (PBYTE) pre->szMessage;
		CallService (MS_DB_EVENT_ADD, (WPARAM) ccs->hContact, (LPARAM) &ei);
	}
	return 0;
}

INT_PTR SendMsg(WPARAM /* flags */, LPARAM lParam)
{
	CCSDATA* ccs = (CCSDATA*) lParam;
	if ( pluginInstalled && ccs && ccs->hContact && ccs->lParam )
	{
		if ( SendMsgData* data = new SendMsgData )
		{
			data->hContact = ccs->hContact;
			data->text = (LPCSTR)ccs->lParam;
			HANDLE cookie = data->cookie = GenerateCookie();
			mir_forkthread( SendMsgThread, data );
			return (INT_PTR)cookie;
		}
	}
	return 0;
}

INT_PTR BasicSearch(WPARAM /* wParam */, LPARAM szId)
{
	if ( pluginInstalled && szId )
	{
		return pluginSearch.StartSearch( CA2T( (LPCSTR)szId ) );
	}
	return 0;
}

INT_PTR AddToList(WPARAM flags, LPARAM lParam)
{
	PROTOSEARCHRESULT* psr = (PROTOSEARCHRESULT*) lParam;
	if ( psr && psr->cbSize >= sizeof (PROTOSEARCHRESULT) && psr->nick && *psr->nick )
	{
		CString sName( psr->nick );
		CString sNotes( psr->firstName );
		return (INT_PTR)AddToListByName( sName, flags, sNotes, false, false );
	}
	return 0;
}

INT_PTR GetAvatarCaps(WPARAM wParam, LPARAM lParam)
{
	switch ( wParam )
	{
	case AF_MAXSIZE:
		if ( POINT* size = (POINT*)lParam )
		{
			size->x = 300; // -1 - �� �������� ��-�� �������������� avs.dll ���������
			size->y = 300; // -1 - �� �������� ��-�� �������������� avs.dll ���������
		}
		break;

	case AF_PROPORTION:
		return PIP_NONE;

	case AF_FORMATSUPPORTED:
		switch ( lParam )
		{
		case PA_FORMAT_UNKNOWN:
		case PA_FORMAT_PNG:
		case PA_FORMAT_JPEG:
		case PA_FORMAT_ICON:
		case PA_FORMAT_BMP:
		case PA_FORMAT_GIF:
		case PA_FORMAT_SWF:
		case PA_FORMAT_XML:
			return 1;
		}
		break;

	case AF_ENABLED:
		return 1;

	case AF_DONTNEEDDELAYS:
		return 1;

	case AF_MAXFILESIZE:
		return MAX_AVATAR_SIZE;

	case AF_DELAYAFTERFAIL:
		return 10 * 60 * 1000; // 10 �����
	}

	return 0;
}

INT_PTR GetAvatarInfo(WPARAM /*flags*/, LPARAM lParam)
{
	PROTO_AVATAR_INFORMATION* pai = (PROTO_AVATAR_INFORMATION*)lParam;
	if ( pai && pai->cbSize >= sizeof( PROTO_AVATAR_INFORMATION ) )
	{
		if ( ContactData* data = new ContactData )
		{
			data->hContact = pai->hContact;
			/*HANDLE cookie =*/ data->cookie = GenerateCookie();
			mir_forkthread( GetAvatarInfoThread, data );
			return GAIR_WAITFOR;
		}
	}
	return GAIR_NOAVATAR;
}

INT_PTR SetMyAvatar(WPARAM /*wParam*/, LPARAM lParam)
{
	CA2T szFilename( (LPCSTR)lParam );

	TCHAR szPath[ MAX_PATH ];
	GetAvatarCache( szPath );

	if ( szFilename )
	{
		// ���������� �������
		lstrcat( szPath, _T("MyAvatar") );
		lstrcat( szPath, _tcsrchr( szFilename, _T('.') ) );

		if ( lstrcmpi( szPath, szFilename ) == 0 )
		{
			// ��� �� ����� ���� - ������ �� ������
		}
		else
		{
			// ������ ���� - ����������� ������� � ����
			SHFILEOPSTRUCT sfo = {};
			sfo.hwnd = GetDesktopWindow();
			sfo.wFunc = FO_COPY;
			TCHAR szFrom[ MAX_PATH ] = {};
			lstrcpy( szFrom, szFilename );
			sfo.pFrom = szFrom;
			sfo.pTo = szPath;
			sfo.fFlags = FOF_ALLOWUNDO | FOF_FILESONLY | FOF_NORECURSION |
				FOF_NOCONFIRMATION;
			SHFileOperation( &sfo );
		}

		DBWriteContactSettingTString( NULL, modname, "AvatarFile",
			_tcsrchr( szPath, _T('\\') ) + 1 );
	}
	else
	{
		// �������� ������� � �������
		DBVARIANT dbv = {};
		if ( ! DBGetContactSettingTString( NULL, modname, "AvatarFile", &dbv ) )
		{
			lstrcat( szPath, dbv.ptszVal );

			SHFILEOPSTRUCT sfo = {};
			sfo.hwnd = GetDesktopWindow();
			sfo.wFunc = FO_DELETE;
			sfo.pFrom = szPath;
			sfo.fFlags = FOF_ALLOWUNDO | FOF_FILESONLY | FOF_NORECURSION |
				FOF_NOCONFIRMATION;
			SHFileOperation( &sfo );

			DBFreeVariant( &dbv );

			DBDeleteContactSetting( NULL, modname, "AvatarFile" );
		}
	}

	return 0;
}

INT_PTR GetMyAvatar(WPARAM wParam, LPARAM lParam)
{
	LPSTR szFilename = (LPSTR)wParam;
	int nLength = (int)lParam;
	bool ret = false;
	
	if ( szFilename == NULL || nLength < MAX_PATH )
		return -1;

	TCHAR szPath[ MAX_PATH ];
	GetAvatarCache( szPath );

	DBVARIANT dbv = {};
	if ( ! DBGetContactSettingTString( NULL, modname, "AvatarFile", &dbv ) )
	{
		lstrcat( szPath, dbv.ptszVal );

		ret = ( GetFileAttributes( szPath ) != INVALID_FILE_ATTRIBUTES );

		DBFreeVariant( &dbv );
	}

	if ( ! ret )
		return -1;

	lstrcpyA( szFilename, CT2A( szPath ) );

	return 0;
}

static INT_PTR CALLBACK DlgProcCreateAccMgrUI(HWND hwndDlg, UINT Msg,
	WPARAM /*wParam*/, LPARAM /*lParam*/)
{
	switch ( Msg )
	{
	case WM_INITDIALOG:
		{
			TranslateDialogDefault( hwndDlg );
			return TRUE;
		}
	}
	return FALSE;
}

INT_PTR CreateAccMgrUI(WPARAM /*wParam*/, LPARAM lParam)
{
	return (INT_PTR)CreateDialogParam( pluginModule, MAKEINTRESOURCE( IDD_CREATE ),
		(HWND)lParam, DlgProcCreateAccMgrUI, 0 );
}

/////////////////////////////////////////////////////////////////////////////
// ���������� �������

extern "C" __declspec(dllexport) PLUGININFOEX *MirandaPluginInfoEx(DWORD mirandaVersion)
{
	return &pluginInfoEx;
}

extern "C" int __declspec(dllexport) Load(void)
{
	mir_getLP(&pluginInfoEx);
	GetVersionEx( &pluginOS );

	if (pluginNetapi32.LoadLibrary (pluginModule))
	{
		pluginNetapi32.LoadFunc (fnNetWkstaGetInfo, "NetWkstaGetInfo");
		pluginNetapi32.LoadFunc (fnNetApiBufferFree, "NetApiBufferFree");
		pluginNetapi32.LoadFunc (fnNetMessageBufferSend, "NetMessageBufferSend");
	}

	if (pluginAdvapi32.LoadLibrary (pluginModule))
	{
		pluginAdvapi32.LoadFunc (fnOpenSCManager, "OpenSCManagerW");
		pluginAdvapi32.LoadFunc (fnOpenService, "OpenServiceW");
		pluginAdvapi32.LoadFunc (fnStartService, "StartServiceW");
		pluginAdvapi32.LoadFunc (fnChangeServiceConfig, "ChangeServiceConfigW");
		pluginAdvapi32.LoadFunc (fnControlService, "ControlService");
		pluginAdvapi32.LoadFunc (fnQueryServiceStatus, "QueryServiceStatus");
		pluginAdvapi32.LoadFunc (fnCloseServiceHandle, "CloseServiceHandle");
		pluginAdvapi32.LoadFunc (fnLockServiceDatabase, "LockServiceDatabase");
		pluginAdvapi32.LoadFunc (fnUnlockServiceDatabase, "UnlockServiceDatabase");
	}

	_ASSERT( pluginInternalState == NULL );
	pluginInternalState = CreateEvent( NULL, TRUE, TRUE, NULL );
	_ASSERT( pluginInternalState != NULL );

	INT_PTR retCallService = CallService (MS_PROTO_REGISTERMODULE, 0, (LPARAM) &pluginPD);
	_ASSERT (retCallService != CALLSERVICE_NOTFOUND);
	if ( retCallService == CALLSERVICE_NOTFOUND ) return 1;

	CreateServiceFn( plugin_FnGetCaps, PS_GETCAPS, GetCaps );
	CreateServiceFn( plugin_FnGetName, PS_GETNAME, GetName );
	CreateServiceFn( plugin_FnLoadIcon, PS_LOADICON, LoadIcon );
	CreateServiceFn( plugin_FnSetStatus, PS_SETSTATUS, SetStatus );
	CreateServiceFn( plugin_FnGetStatus, PS_GETSTATUS, GetStatus );
	CreateServiceFn( plugin_FnSetAwayMsg, PS_SETAWAYMSG, SetAwayMsg );
	CreateServiceFn( plugin_FnGetAwayMsg, PSS_GETAWAYMSG, GetAwayMsg );
	CreateServiceFn( plugin_FnGetInfo, PSS_GETINFO, GetInfo );
	CreateServiceFn( plugin_FnSendMsg, PSS_MESSAGE, SendMsg );
	CreateServiceFn( plugin_FnRecvMessage, PSR_MESSAGE, RecvMessage );
	CreateServiceFn( plugin_FnBasicSearch, PS_BASICSEARCH, BasicSearch );
	CreateServiceFn( plugin_FnAddToList, PS_ADDTOLIST, AddToList );
	CreateServiceFn( plugin_FnExplore, PS_EXPLORE, Explore );
	CreateServiceFn( plugin_FnAddDialog, PS_ADDDIALOG, AddDialog);
	CreateServiceFn( plugin_FnGetAvatarCaps, PS_GETAVATARCAPS, GetAvatarCaps );
	CreateServiceFn( plugin_FnGetAvatarInfo, PS_GETAVATARINFO, GetAvatarInfo );
	CreateServiceFn( plugin_FnSetMyAvatar, PS_SETMYAVATAR, SetMyAvatar );
	CreateServiceFn( plugin_FnGetMyAvatar, PS_GETMYAVATAR, GetMyAvatar );
	CreateServiceFn( plugin_FnCreateAccMgrUI, PS_CREATEACCMGRUI, CreateAccMgrUI );

	CreateHook( plugin_SYSTEM_MODULESLOADED, ME_SYSTEM_MODULESLOADED, SYSTEM_MODULESLOADED );
	CreateHook( plugin_SYSTEM_PRESHUTDOWN, ME_SYSTEM_PRESHUTDOWN, SYSTEM_PRESHUTDOWN );


	return 0;
}

extern "C" int __declspec(dllexport) Unload()
{
#ifdef CHAT_ENABLED
	ChatUnregister();
#endif // CHAT_ENABLED

	DestroyHook( plugin_USERINFO_INITIALISE );
	DestroyHook( plugin_OPT_INITIALISE );
	DestroyHook( plugin_SYSTEM_PRESHUTDOWN );
	DestroyHook( plugin_SYSTEM_MODULESLOADED );

	DestroyServiceFn( plugin_FnExplore );
	DestroyServiceFn( plugin_FnAddDialog );
	DestroyServiceFn( plugin_FnGetCaps );
	DestroyServiceFn( plugin_FnGetName );
	DestroyServiceFn( plugin_FnLoadIcon );
	DestroyServiceFn( plugin_FnSetStatus );
	DestroyServiceFn( plugin_FnGetStatus );
	DestroyServiceFn( plugin_FnSetAwayMsg );
	DestroyServiceFn( plugin_FnGetAwayMsg );
	DestroyServiceFn( plugin_FnGetInfo );
	DestroyServiceFn( plugin_FnSendMsg );
	DestroyServiceFn( plugin_FnRecvMessage );
	DestroyServiceFn( plugin_FnBasicSearch );
	DestroyServiceFn( plugin_FnAddToList );
	DestroyServiceFn( plugin_FnGetAvatarCaps );
	DestroyServiceFn( plugin_FnGetAvatarInfo );
	DestroyServiceFn( plugin_FnSetMyAvatar );
	DestroyServiceFn( plugin_FnGetMyAvatar );
	DestroyServiceFn( plugin_FnCreateAccMgrUI );

	if ( pluginNetLibUser != NULL )
		Netlib_CloseHandle( pluginNetLibUser );
	pluginNetLibUser = NULL;

	if ( pluginInternalState != NULL )
		CloseHandle( pluginInternalState );
	pluginInternalState = NULL;

	pluginNetapi32.FreeLibrary();
	pluginAdvapi32.FreeLibrary();

	return 0;
}

/////////////////////////////////////////////////////////////////////////////
// ������������

int __cdecl SYSTEM_MODULESLOADED (WPARAM /* wParam */, LPARAM /* lParam */)
{
	_ASSERT (pluginInstalled == true);
	_ASSERT (pluginInitialized == false);

	_ASSERT (pluginNetLibUser == NULL);
	const NETLIBUSER nlu =
	{
		sizeof( NETLIBUSER ),
		modname,
		modname,
		0,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		0		
	};
	pluginNetLibUser = (HANDLE)CallService( MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu );
	_ASSERT (pluginNetLibUser);

	CreateHook( plugin_OPT_INITIALISE, ME_OPT_INITIALISE, OPT_INITIALISE );
	CreateHook( plugin_USERINFO_INITIALISE, ME_USERINFO_INITIALISE, USERINFO_INITIALISE );

	// ��������� ������ ��������
	struct
	{
		int icon_id;
		int status;
	}
	const StatusIcons [] =
	{
		{ IDI_OFFLINE, ID_STATUS_OFFLINE },
		{ IDI_ONLINE, ID_STATUS_ONLINE },
		{ IDI_AWAY, ID_STATUS_AWAY },
		{ IDI_DND, ID_STATUS_DND },
		{ IDI_NA, ID_STATUS_NA },
		{ IDI_OCCUPIED, ID_STATUS_OCCUPIED },
		{ IDI_FREECHAT, ID_STATUS_FREECHAT },
		{ IDI_INVISIBLE, ID_STATUS_INVISIBLE },
		{ IDI_ONTHEPHONE, ID_STATUS_ONTHEPHONE },
		{ IDI_OUTTOLUNCH, ID_STATUS_OUTTOLUNCH },
		{ 0, 0 }
	};
	TCHAR path [ MAX_PATH * 2 ] = { 0 };
	TCHAR icon [ 32 ] = { 0 };
	DWORD len = GetModuleFileName (pluginModule, path, MAX_PATH);
	lstrcpy( path + len, _T(",-") );
	len += 2;
	for (int i = 0; StatusIcons[i].icon_id ; i++)
	{
		wsprintf( path + len, _T("%d"), StatusIcons[i].icon_id );
		wsprintf( icon, modname_t _T("%d"), StatusIcons[i].status );
		DBVARIANT dbv = {};
		if ( ! DBGetContactSetting( NULL, "Icons", CT2A( icon ), &dbv ) )
			DBFreeVariant( &dbv );
		else
			DBWriteContactSettingTString( NULL, "Icons", CT2A( icon ), path );
	}

	// ����������� ����� ����������
	DWORD iMachineNameLength = MAX_COMPUTERNAME_LENGTH + 2;
	GetComputerName(
		pluginMachineName.GetBuffer( (int)iMachineNameLength ), &iMachineNameLength );
	pluginMachineName.ReleaseBuffer();
	SetNick( NULL, pluginMachineName );

	// ����������� ����� ������������
	DWORD iUserNameLength = UNLEN + 2;
	GetUserName(
		pluginUserName.GetBuffer( (int)iUserNameLength ), &iUserNameLength );
	pluginUserName.ReleaseBuffer();
	DBWriteContactSettingTString( NULL, modname, "User", pluginUserName );

	// ����������� ����� ������� ������
	if ( pluginOS.dwPlatformId == VER_PLATFORM_WIN32_NT )
	{
		WKSTA_INFO_100* info = NULL;
		NET_API_STATUS err = fnNetWkstaGetInfo (NULL, 100, (LPBYTE*) &info);
		if (err == NERR_Success && info)
		{
			LPTSTR langroup = mir_u2t( info->wki100_langroup );
			pluginDomainName = langroup;
			fnNetApiBufferFree (info);
			mir_free( langroup );
		}
		// ������������?
		// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
		// DefaultDomainName
	}
	else
	{
		HKEY hKey = NULL;
		if (ERROR_SUCCESS == RegOpenKeyEx (HKEY_LOCAL_MACHINE,
			_T("System\\CurrentControlSet\\Services\\VxD\\VNETSUP"), 0, KEY_READ, &hKey))
		{
			DWORD type, size = MAX_PATH;
			RegQueryValueEx (hKey, _T("Workgroup"), 0, &type,
				(LPBYTE) pluginDomainName.GetBuffer (MAX_PATH + 1), &size);
			pluginDomainName.ReleaseBuffer( (int)size );
			RegCloseKey (hKey);
		}
	}
	DBWriteContactSettingTString (NULL, modname, "Workgroup", pluginDomainName);

	// ����������� � Chat
#ifdef CHAT_ENABLED
	pluginChatEnabled = ChatRegister();
#endif // CHAT_ENABLED

	// ���������� ����
	CLISTMENUITEM miExplore =
	{
		sizeof( CLISTMENUITEM ),
		(LPSTR)TranslateT( "Explore" ),
		CMIF_TCHAR,
		0,
		(HICON)LoadImage( pluginModule, MAKEINTRESOURCE( IDI_EXPLORE ),
			IMAGE_ICON, 16, 16, LR_SHARED ),
		modname PS_EXPLORE,
		NULL,
		0,
		0,
		modname
	};
	Menu_AddContactMenuItem(&miExplore);

	CLISTMENUITEM miAddContact =
	{
		sizeof( CLISTMENUITEM ),
		NULL,
		CMIF_TCHAR,
		500090000,
		(HICON)LoadImage( pluginModule, MAKEINTRESOURCE( IDI_WINPOPUP ),
			IMAGE_ICON, 16, 16, LR_SHARED ),
		modname PS_ADDDIALOG,
		NULL,
		0,
		0,
		NULL
	};
	miAddContact.ptszName = (LPTSTR)TranslateT( "Add contact..." );
	miAddContact.ptszPopupName = (LPTSTR)modtitle_t;

	HANDLE hMenuItem = Menu_AddMainMenuItem(&miAddContact);
	// ������ ������
	miAddContact.flags = CMIM_ICON;
	miAddContact.hIcon = (HICON)LoadImage( pluginModule,
		MAKEINTRESOURCE( IDI_ADD_COMPUTER ), IMAGE_ICON, 16, 16, LR_SHARED ),
	CallService( MS_CLIST_MODIFYMENUITEM, (WPARAM)hMenuItem, (LPARAM)&miAddContact );

	// ���������� �� �������� �������� (��������, ������� � ������)
	pluginInitialized = true;

	// �������������� ������� ������� ��� ���� ��������������
	SetStatus( pluginRequestedStatus, 0 );

	return 0;
}

int __cdecl SYSTEM_PRESHUTDOWN (WPARAM /* wParam */, LPARAM /* lParam */)
{
	// ������ �� �������� �������� (��������, ������� � ������)
	pluginInstalled = false;

	// �������� ������������� ��������, �.�. ������� � ������� ����������� ����������
	// ����� �� ������ �������, ��� ���� ������� ����
	do
	{
		MSG msg;
		while ( PeekMessage( &msg, NULL, NULL, NULL, PM_REMOVE ) )
		{
			if ( IsDialogMessage( msg.hwnd, &msg ) ) continue;
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}
	}
	while ( MsgWaitForMultipleObjects( 1, &pluginInternalState, FALSE,
		INFINITE, QS_ALLINPUT ) == WAIT_OBJECT_0 + 1 );

	return 0;
}