/*
	ClientChangeNotify - Plugin for Miranda IM
	Copyright (c) 2006-2008 Chervov Dmitry

    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"

CLIST_INTERFACE *pcli;
HINSTANCE g_hInstance;
HANDLE    g_hMainThread;
HGENMENU  g_hTogglePopupsMenuItem;
int       hLangpack;

COptPage *g_PreviewOptPage; // we need to show popup even for the NULL contact if g_PreviewOptPage is not NULL (used for popup preview)
BOOL bPopupExists = FALSE, bFingerprintExists = FALSE, bVariablesExists = FALSE;

PLUGININFOEX pluginInfo = {
	sizeof(PLUGININFOEX),
	__PLUGIN_NAME,
	PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
	__DESCRIPTION,
	__AUTHOR,
	__COPYRIGHT,
	__AUTHORWEB,
	UNICODE_AWARE,
	// {B68A8906-748B-435D-930E-21CC6E8F3B3F}
	{0xb68a8906, 0x748b, 0x435d, {0x93, 0xe, 0x21, 0xcc, 0x6e, 0x8f, 0x3b, 0x3f}}
};

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD, LPVOID)
{
	g_hInstance = hinstDLL;
	return TRUE;
}

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

static int CALLBACK MenuWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {
	case WM_MEASUREITEM:
		return Menu_MeasureItem(lParam);

	case WM_DRAWITEM:
		return Menu_DrawItem(lParam);
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}


static VOID NTAPI ShowContactMenu(ULONG_PTR wParam)
// wParam = hContact
{
	POINT pt;
	HWND hMenuWnd = CreateWindowEx(WS_EX_TOOLWINDOW, L"static", _A2W(MOD_NAME) L"_MenuWindow", 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, nullptr, g_hInstance, nullptr);
	SetWindowLongPtr(hMenuWnd, GWLP_WNDPROC, (LONG_PTR)MenuWndProc);
	HMENU hMenu = Menu_BuildContactMenu(wParam);
	GetCursorPos(&pt);
	SetForegroundWindow(hMenuWnd);
	Clist_MenuProcessCommand(TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, pt.x, pt.y, 0, hMenuWnd, nullptr), MPCF_CONTACTMENU, wParam);
	PostMessage(hMenuWnd, WM_NULL, 0, 0);
	DestroyMenu(hMenu);
	DestroyWindow(hMenuWnd);
}


void Popup_DoAction(HWND hWnd, BYTE Action, PLUGIN_DATA*)
{
	MCONTACT hContact = (MCONTACT)CallService(MS_POPUP_GETCONTACT, (WPARAM)hWnd, 0);
	switch (Action) {
	case PCA_OPENMESSAGEWND: // open message window
		if (hContact && hContact != INVALID_CONTACT_ID)
			CallServiceSync(MS_MSG_SENDMESSAGE, hContact, 0);
		break;

	case PCA_OPENMENU: // open contact menu
		if (hContact && hContact != INVALID_CONTACT_ID)
			QueueUserAPC(ShowContactMenu, g_hMainThread, (ULONG_PTR)hContact);
		break;

	case PCA_OPENDETAILS: // open contact details window
		if (hContact != INVALID_CONTACT_ID)
			CallServiceSync(MS_USERINFO_SHOWDIALOG, hContact, 0);
		break;

	case PCA_OPENHISTORY: // open contact history
		if (hContact != INVALID_CONTACT_ID)
			CallServiceSync(MS_HISTORY_SHOWCONTACTHISTORY, hContact, 0);
		break;

	case PCA_CLOSEPOPUP: // close popup
		PUDeletePopup(hWnd);
		break;

	case PCA_DONOTHING: // do nothing
		break;
	}
}


static LRESULT CALLBACK PopupWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PLUGIN_DATA *pdata = (PLUGIN_DATA*)CallService(MS_POPUP_GETPLUGINDATA, (WPARAM)hWnd, 0);
	if (pdata) {
		switch (message) {
		case WM_COMMAND:
			if (HIWORD(wParam) == STN_CLICKED) { // left mouse button
				Popup_DoAction(hWnd, pdata->PopupLClickAction, pdata);
				return true;
			}
			break;

		case WM_CONTEXTMENU:	// right mouse button
			Popup_DoAction(hWnd, pdata->PopupRClickAction, pdata);
			return true;

		case UM_FREEPLUGINDATA:
			if (pdata->hIcon)
				DestroyIcon(pdata->hIcon);

			free(pdata);
			return false;
			break;
		}
	}
	return DefWindowProc(hWnd, message, wParam, lParam);
}


void ShowPopup(SHOWPOPUP_DATA *sd)
{
	TCString PopupText;
	if (sd->PopupOptPage->GetValue(IDC_POPUPOPTDLG_SHOWPREVCLIENT)) {
		mir_snwprintf(PopupText.GetBuffer(MAX_MSG_LEN), MAX_MSG_LEN, TranslateT("changed client to %s (was %s)"), (const wchar_t*)sd->MirVer, (const wchar_t*)sd->OldMirVer);
		PopupText.ReleaseBuffer();
	}
	else {
		mir_snwprintf(PopupText.GetBuffer(MAX_MSG_LEN), MAX_MSG_LEN, TranslateT("changed client to %s"), (const wchar_t*)sd->MirVer);
		PopupText.ReleaseBuffer();
	}

	PLUGIN_DATA *pdata = (PLUGIN_DATA*)calloc(1, sizeof(PLUGIN_DATA));
	POPUPDATAT ppd = {0};
	ppd.lchContact = sd->hContact;
	char *szProto = GetContactProto(sd->hContact);
	pdata->hIcon = ppd.lchIcon = Finger_GetClientIcon(sd->MirVer, false);
	_ASSERT(ppd.lchIcon);
	if (!ppd.lchIcon || (INT_PTR)ppd.lchIcon == CALLSERVICE_NOTFOUND) {
		// if we didn't succeed retrieving client icon, show the usual status icon instead
		ppd.lchIcon = Skin_LoadProtoIcon(szProto, db_get_w(sd->hContact, szProto, "Status", ID_STATUS_OFFLINE));
		pdata->hIcon = nullptr;
	}
	wcsncpy(ppd.lptzContactName, (wchar_t*)pcli->pfnGetContactDisplayName(sd->hContact, 0), _countof(ppd.lptzContactName) - 1);
	wcsncpy(ppd.lptzText, PopupText, _countof(ppd.lptzText) - 1);
	ppd.colorBack = (sd->PopupOptPage->GetValue(IDC_POPUPOPTDLG_DEFBGCOLOUR) ? 0 : sd->PopupOptPage->GetValue(IDC_POPUPOPTDLG_BGCOLOUR));
	ppd.colorText = (sd->PopupOptPage->GetValue(IDC_POPUPOPTDLG_DEFTEXTCOLOUR) ? 0 : sd->PopupOptPage->GetValue(IDC_POPUPOPTDLG_TEXTCOLOUR));
	ppd.PluginWindowProc = PopupWndProc;
	pdata->PopupLClickAction = sd->PopupOptPage->GetValue(IDC_POPUPOPTDLG_LCLICK_ACTION);
	pdata->PopupRClickAction = sd->PopupOptPage->GetValue(IDC_POPUPOPTDLG_RCLICK_ACTION);
	ppd.iSeconds = sd->PopupOptPage->GetValue(IDC_POPUPOPTDLG_POPUPDELAY);
	ppd.PluginData = pdata;
	PUAddPopupT(&ppd);
}

int ContactSettingChanged(WPARAM hContact, LPARAM lParam)
{
	DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING*)lParam;
	if (strcmp(cws->szSetting, DB_MIRVER))
		return 0;

	SHOWPOPUP_DATA sd = {0};
	char *szProto = GetContactProto(hContact);
	if (g_PreviewOptPage)
		sd.MirVer = L"Miranda NG ICQ 0.93.5.3007";
	else {
		if (!hContact) // exit if hContact == NULL and it's not a popup preview
			return 0;

		_ASSERT(szProto);
		if (!strcmp(szProto, META_PROTO)) // workaround for metacontacts
			return 0;

		sd.MirVer = db_get_s(hContact, szProto, DB_MIRVER, L"");
		if (sd.MirVer.IsEmpty())
			return 0;
	}
	sd.OldMirVer = db_get_s(hContact, MOD_NAME, DB_OLDMIRVER, L"");
	db_set_ws(hContact, MOD_NAME, DB_OLDMIRVER, sd.MirVer); // we have to write it here, because we modify sd.OldMirVer and sd.MirVer to conform our settings later
	if (sd.OldMirVer.IsEmpty())  // looks like it's the right way to do
		return 0;

	COptPage PopupOptPage;
	if (g_PreviewOptPage)
		PopupOptPage = *g_PreviewOptPage;
	else {
		PopupOptPage = g_PopupOptPage;
		PopupOptPage.DBToMem();
	}

	MCONTACT hContactOrMeta = (hContact) ? db_mc_getMeta(hContact) : 0;
	if (!hContactOrMeta)
		hContactOrMeta = hContact;

	if (hContact && db_get_b(hContactOrMeta, "CList", "Hidden", 0))
		return 0;

	int PerContactSetting = hContact ? db_get_b(hContact, MOD_NAME, DB_CCN_NOTIFY, NOTIFY_USEGLOBAL) : NOTIFY_ALWAYS; // NOTIFY_ALWAYS for preview
	if (PerContactSetting == NOTIFY_USEGLOBAL && hContactOrMeta != hContact) // subcontact setting has a priority over a metacontact setting
		PerContactSetting = db_get_b(hContactOrMeta, MOD_NAME, DB_CCN_NOTIFY, NOTIFY_USEGLOBAL);

	if (PerContactSetting && (PerContactSetting == NOTIFY_ALMOST_ALWAYS || PerContactSetting == NOTIFY_ALWAYS || !PopupOptPage.GetValue(IDC_POPUPOPTDLG_USESTATUSNOTIFYFLAG) || !(db_get_dw(hContactOrMeta, "Ignore", "Mask1", 0) & 0x8))) { // check if we need to notify at all
		sd.hContact = hContact;
		sd.PopupOptPage = &PopupOptPage;
		if (!PopupOptPage.GetValue(IDC_POPUPOPTDLG_VERCHGNOTIFY) || !PopupOptPage.GetValue(IDC_POPUPOPTDLG_SHOWVER)) {
			if (bFingerprintExists) {
				LPCTSTR ptszOldClient = Finger_GetClientDescr(sd.OldMirVer); 
				LPCTSTR ptszClient = Finger_GetClientDescr(sd.MirVer);
				if (ptszOldClient && ptszClient) {
					if (PerContactSetting != NOTIFY_ALMOST_ALWAYS && PerContactSetting != NOTIFY_ALWAYS && !PopupOptPage.GetValue(IDC_POPUPOPTDLG_VERCHGNOTIFY) && !wcscmp(ptszClient, ptszOldClient))
						return 0;

					if (!PopupOptPage.GetValue(IDC_POPUPOPTDLG_SHOWVER)) {
						sd.MirVer = ptszClient;
						sd.OldMirVer = ptszOldClient;
					}
				}
			}
		}
		if (sd.MirVer == (const wchar_t*)sd.OldMirVer) {
			_ASSERT(hContact);
			return 0;
		}
		if (PerContactSetting == NOTIFY_ALWAYS || (PopupOptPage.GetValue(IDC_POPUPOPTDLG_POPUPNOTIFY) && (g_PreviewOptPage || PerContactSetting == NOTIFY_ALMOST_ALWAYS || -1 == PcreCheck(sd.MirVer)))) {
			ShowPopup(&sd);
			Skin_PlaySound(CLIENTCHANGED_SOUND);
		}
	}

	if (hContact) {
		TCString ClientName;
		if (PopupOptPage.GetValue(IDC_POPUPOPTDLG_SHOWPREVCLIENT) && sd.OldMirVer.GetLen()) {
			mir_snwprintf(ClientName.GetBuffer(MAX_MSG_LEN), MAX_MSG_LEN, TranslateT("%s (was %s)"), (const wchar_t*)sd.MirVer, (const wchar_t*)sd.OldMirVer);
			ClientName.ReleaseBuffer();
		}
		else ClientName = sd.MirVer;
	}
	_ASSERT(sd.MirVer.GetLen()); // save the last known MirVer value even if the new one is empty
	return 0;
}

static INT_PTR srvTogglePopups(WPARAM, LPARAM)
{
	g_PopupOptPage.SetDBValueCopy(IDC_POPUPOPTDLG_POPUPNOTIFY, !g_PopupOptPage.GetDBValueCopy(IDC_POPUPOPTDLG_POPUPNOTIFY));
	return 0;
}

static int PrebuildMainMenu(WPARAM, LPARAM)
{
	// we have to use ME_CLIST_PREBUILDMAINMENU instead of updating menu items only on settings change, because "popup_enabled" and "popup_disabled" icons are not always available yet in ModulesLoaded
	if (bPopupExists) {
		if (g_PopupOptPage.GetDBValueCopy(IDC_POPUPOPTDLG_POPUPNOTIFY))
			Menu_ModifyItem(g_hTogglePopupsMenuItem, LPGENW("Disable c&lient change notification"), IcoLib_GetIcon("popup_enabled"));
		else
			Menu_ModifyItem(g_hTogglePopupsMenuItem, LPGENW("Enable c&lient change notification"), IcoLib_GetIcon("popup_disabled"));
	}
	return 0;
}

INT_PTR CALLBACK CCNErrorDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM)
{
	switch (uMsg) {
	case WM_INITDIALOG:
		TranslateDialogDefault(hwndDlg);
		return true;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
			DestroyWindow(hwndDlg);
		break;

	case WM_DESTROY:
		if (IsDlgButtonChecked(hwndDlg, IDC_DONTREMIND))
			db_set_b(NULL, MOD_NAME, DB_NO_FINGERPRINT_ERROR, 1);
		break;
	}
	return 0;
}

static int ModuleLoad(WPARAM, LPARAM)
{
	bPopupExists = ServiceExists(MS_POPUP_ADDPOPUPT);
	bFingerprintExists = ServiceExists(MS_FP_SAMECLIENTST) && ServiceExists(MS_FP_GETCLIENTICONT);
	bVariablesExists = ServiceExists(MS_VARS_FORMATSTRING);
	return 0;
}

int MirandaLoaded(WPARAM, LPARAM)
{
	ModuleLoad(0, 0);
	COptPage PopupOptPage(g_PopupOptPage);
	PopupOptPage.DBToMem();
	RecompileRegexps(*(TCString*)PopupOptPage.GetValue(IDC_POPUPOPTDLG_IGNORESTRINGS));
	HookEvent(ME_OPT_INITIALISE, OptionsDlgInit);
	HookEvent(ME_SYSTEM_MODULELOAD, ModuleLoad);
	HookEvent(ME_SYSTEM_MODULEUNLOAD, ModuleLoad);
	HookEvent(ME_DB_CONTACT_SETTINGCHANGED, ContactSettingChanged);

	Skin_AddSound(CLIENTCHANGED_SOUND, nullptr, LPGENW("ClientChangeNotify: Client changed"));

	if (bPopupExists) {
		CreateServiceFunction(MS_CCN_TOGGLEPOPUPS, srvTogglePopups);
		HookEvent(ME_CLIST_PREBUILDMAINMENU, PrebuildMainMenu);
	
		CMenuItem mi;
		SET_UID(mi, 0xfabb9181, 0xdb92, 0x43f4, 0x86, 0x40, 0xca, 0xb6, 0x4c, 0x93, 0x34, 0x27);
		mi.root = Menu_CreateRoot(MO_MAIN, LPGENW("Popups"), 0);
		mi.flags = CMIF_UNICODE;
		if (g_PopupOptPage.GetDBValueCopy(IDC_POPUPOPTDLG_POPUPNOTIFY))
			mi.name.w = LPGENW("Disable c&lient change notification");
		else
			mi.name.w = LPGENW("Enable c&lient change notification");

		mi.pszService = MS_CCN_TOGGLEPOPUPS;
		g_hTogglePopupsMenuItem = Menu_AddMainMenuItem(&mi);
	}

	// seems that Fingerprint is not installed
	if (!bFingerprintExists && !db_get_b(NULL, MOD_NAME, DB_NO_FINGERPRINT_ERROR, 0))
		CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_CCN_ERROR), nullptr, CCNErrorDlgProc);

	return 0;
}

extern "C" int __declspec(dllexport) Load(void)
{
	mir_getLP(&pluginInfo);
	pcli = Clist_GetInterface();

	HookEvent(ME_SYSTEM_MODULESLOADED, MirandaLoaded);
	DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &g_hMainThread, THREAD_SET_CONTEXT, false, 0);
	InitOptions();

	if (db_get_b(NULL, MOD_NAME, DB_SETTINGSVER, 0) < 1) {
		TCString Str;
		Str = db_get_s(NULL, MOD_NAME, DB_IGNORESUBSTRINGS, L"");
		if (Str.GetLen()) // fix incorrect regexp from v0.1.1.0
			db_set_ws(NULL, MOD_NAME, DB_IGNORESUBSTRINGS, Str.Replace(L"/Miranda[0-9A-F]{8}/", L"/[0-9A-F]{8}(\\W|$)/"));

		db_set_b(NULL, MOD_NAME, DB_SETTINGSVER, 1);
	}
	return 0;
}

extern "C" int __declspec(dllexport) Unload()
{
	CloseHandle(g_hMainThread);
	return 0;
}