/*
Copyright (C) 2006-2007 Scott Ellis
Copyright (C) 2007-2011 Jan Holub

This is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This 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
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this file; see the file license.txt.  If
not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  
*/

#include "common.h"
#include "message_pump.h"
#include "popwin.h"
#include "options.h"
#include "str_utils.h"
#include "subst.h"

BOOL (WINAPI *MySetLayeredWindowAttributes)(HWND,COLORREF,BYTE,DWORD) = 0;
BOOL (WINAPI *MyUpdateLayeredWindow)(HWND hwnd, HDC hdcDST, POINT *pptDst, SIZE *psize, HDC hdcSrc, POINT *pptSrc, COLORREF crKey, BLENDFUNCTION *pblend, DWORD dwFlags) = 0;
BOOL (WINAPI *MyAnimateWindow)(HWND hWnd,DWORD dwTime,DWORD dwFlags) = 0;
HMONITOR (WINAPI *MyMonitorFromPoint)(POINT, DWORD);
BOOL (WINAPI *MyGetMonitorInfo)(HMONITOR, LPMONITORINFO);
HRESULT (WINAPI *MyDwmEnableBlurBehindWindow)(HWND hWnd, DWM_BLURBEHIND *pBlurBehind) = 0; 

unsigned int uintMessagePumpThreadId = 0;
POINT pt = {-1};
UINT WaitForContentTimerID = 0;
bool bAvatarReady = false;
bool bStatusMsgReady = false;

__inline bool IsContactTooltip(CLCINFOTIPEX *clc)
{
	return (clc->szProto || clc->swzText) == false;
}

void CALLBACK TimerProcWaitForContent(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) 
{
	KillTimer(0, WaitForContentTimerID);
	WaitForContentTimerID = 0;
	bStatusMsgReady = true;	
	bAvatarReady = true;
	PostMPMessage(MUM_CREATEPOPUP, 0, 0);
}

bool NeedWaitForContent(CLCINFOTIPEX *clcitex)
{
	bool bNeedWait = false;

	if (opt.bWaitForContent && IsContactTooltip(clcitex))
	{
		char *szProto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)clcitex->hItem, 0);
		if (!szProto) return false;

		if (opt.bWaitForStatusMsg && !bStatusMsgReady) 
		{
			DBDeleteContactSetting(clcitex->hItem, MODULE, "TempStatusMsg");
			if (CanRetrieveStatusMsg(clcitex->hItem, szProto) &&
				CallContactService(clcitex->hItem, PSS_GETAWAYMSG, 0, 0)) 
			{
				if (WaitForContentTimerID) 
					KillTimer(0, WaitForContentTimerID);

				WaitForContentTimerID = SetTimer(NULL, 0, WAIT_TIMER_INTERVAL, TimerProcWaitForContent);
				bNeedWait = true;
			}
		}

		if (opt.bWaitForAvatar && !bAvatarReady && 
			CallProtoService(szProto, PS_GETAVATARCAPS, AF_ENABLED, 0)) 
		{
			DBVARIANT dbv;
			if (!DBGetContactSettingString(clcitex->hItem, "ContactPhoto", "File", &dbv))
			{		
				if (!strstr(dbv.pszVal, ".xml")) 
				{
					AVATARCACHEENTRY *ace = (AVATARCACHEENTRY *)CallService(MS_AV_GETAVATARBITMAP, (WPARAM)clcitex->hItem, 0);
					if (!ace) 
					{
						if (WaitForContentTimerID) 
							KillTimer(0, WaitForContentTimerID);

						WaitForContentTimerID = SetTimer(NULL, 0, WAIT_TIMER_INTERVAL, TimerProcWaitForContent);
						bNeedWait = true;
					} 
					else
						bAvatarReady = true;
				} 
				else
					bAvatarReady = true;

				DBFreeVariant(&dbv);
			} 
			else
				bAvatarReady = true;
		}
		
	} 

	return bNeedWait;
}

unsigned int CALLBACK MessagePumpThread(void *param) 
{
	HWND hwndTip = 0;
	CLCINFOTIPEX *clcitex = 0;

	MSG hwndMsg = {0};
	while (GetMessage(&hwndMsg, 0, 0, 0) > 0 && !Miranda_Terminated())
	{
		if (!IsDialogMessage(hwndMsg.hwnd, &hwndMsg)) 
		{
			switch (hwndMsg.message) 
			{
				case MUM_CREATEPOPUP:
				{
					if (!clcitex) 
					{
						if (hwndMsg.lParam) clcitex = (CLCINFOTIPEX *)hwndMsg.lParam;
						else break;
					}

					if (!NeedWaitForContent(clcitex)) 
					{
						if (hwndTip) MyDestroyWindow(hwndTip);
						hwndTip = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TOPMOST, POP_WIN_CLASS, NULL, WS_POPUP, 0, 0, 0, 0, 0, 0, hInst, (LPVOID)clcitex);
							
						if (clcitex)
						{
							mir_free(clcitex);
							clcitex = 0;
						}

						bStatusMsgReady = false;
						bAvatarReady = false;
					}
					break;
				}	
				case MUM_DELETEPOPUP:
				{
					if (hwndTip) 
					{
						MyDestroyWindow(hwndTip);
						hwndTip = 0;
					}

					if (clcitex)
					{
						mir_free(clcitex);
						clcitex = 0;
					}

					bStatusMsgReady = false;
					bAvatarReady = false;
					break;
				}
				case MUM_GOTSTATUS:
				{
					HANDLE hContact = (HANDLE)hwndMsg.wParam;
					TCHAR *swzMsg = (TCHAR *)hwndMsg.lParam;

					if (opt.bWaitForContent && 
						bStatusMsgReady == false && 
						clcitex && 
						clcitex->hItem == hContact)
					{
						if (WaitForContentTimerID) 
						{
							KillTimer(0, WaitForContentTimerID);
							WaitForContentTimerID = 0;
						}

						if (swzMsg)
						{
							DBWriteContactSettingTString(clcitex->hItem, MODULE, "TempStatusMsg", swzMsg);	
							mir_free(swzMsg);
						}

						bStatusMsgReady = true;
						PostMPMessage(MUM_CREATEPOPUP, 0, 0);
					} 
					else if (!opt.bWaitForContent && hwndTip) 
						SendMessage(hwndTip, PUM_SETSTATUSTEXT, (WPARAM)hContact, (LPARAM)swzMsg);
					else if (swzMsg) 
						mir_free(swzMsg);

					break;
				}	
				case MUM_GOTXSTATUS:
				{
					if (hwndTip && !opt.bWaitForContent) 
						SendMessage(hwndTip, PUM_SHOWXSTATUS, hwndMsg.wParam, 0);
					break;
				}
				case MUM_GOTAVATAR:
				{
					HANDLE hContact = (HANDLE)hwndMsg.wParam;

					if (opt.bWaitForContent && 
						bAvatarReady == false && 
						clcitex && 
						clcitex->hItem == hContact) 
					{
						if (WaitForContentTimerID)
						{
							KillTimer(0, WaitForContentTimerID);
							WaitForContentTimerID = 0;
						}

						bAvatarReady = true;		
						PostMPMessage(MUM_CREATEPOPUP, 0, 0);
					} 
					else if (!opt.bWaitForContent && hwndTip) 
						SendMessage(hwndTip, PUM_SETAVATAR, hwndMsg.wParam, 0);

					break;
				}
				default:
				{
					TranslateMessage(&hwndMsg);
					DispatchMessage(&hwndMsg);
					break;
				}
			}
		}
	}

	return 0;
}

void PostMPMessage(UINT msg, WPARAM wParam, LPARAM lParam) 
{
	PostThreadMessage(uintMessagePumpThreadId, msg, wParam, lParam);
}

void InitMessagePump() 
{
	WNDCLASSEX wcl = {0};
	wcl.cbSize = sizeof(wcl);
	wcl.lpfnWndProc = PopupWindowProc;
	wcl.hInstance = hInst;
	wcl.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcl.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
	wcl.lpszClassName = POP_WIN_CLASS;
	RegisterClassEx(&wcl);

	HMODULE hUserDll = LoadLibrary(_T("user32.dll"));
	if (hUserDll) 
	{
		MySetLayeredWindowAttributes = (BOOL (WINAPI *)(HWND,COLORREF,BYTE,DWORD))GetProcAddress(hUserDll, "SetLayeredWindowAttributes");
		MyUpdateLayeredWindow = (BOOL (WINAPI *)(HWND hwnd, HDC hdcDST, POINT *pptDst, SIZE *psize, HDC hdcSrc, POINT *pptSrc, COLORREF crKey, BLENDFUNCTION *pblend, DWORD dwFlags))GetProcAddress(hUserDll, "UpdateLayeredWindow");
		MyAnimateWindow =(BOOL (WINAPI*)(HWND,DWORD,DWORD))GetProcAddress(hUserDll, "AnimateWindow");
		MyMonitorFromPoint = (HMONITOR (WINAPI*)(POINT, DWORD))GetProcAddress(hUserDll, "MonitorFromPoint");
#ifdef _UNICODE
		MyGetMonitorInfo = (BOOL (WINAPI*)(HMONITOR, LPMONITORINFO))GetProcAddress(hUserDll, "GetMonitorInfoW");
#else
		MyGetMonitorInfo = (BOOL (WINAPI*)(HMONITOR, LPMONITORINFO))GetProcAddress(hUserDll, "GetMonitorInfoA");
#endif
	}

	HMODULE hDwmapiDll = LoadLibrary(_T("dwmapi.dll"));
	if (hDwmapiDll)
		MyDwmEnableBlurBehindWindow = (HRESULT (WINAPI *)(HWND, DWM_BLURBEHIND *))GetProcAddress(hDwmapiDll, "DwmEnableBlurBehindWindow");

	CloseHandle(mir_forkthreadex(MessagePumpThread, NULL, 0, &uintMessagePumpThreadId));
}

void DeinitMessagePump() 
{
	PostMPMessage(WM_QUIT, 0, 0);
	UnregisterClass(POP_WIN_CLASS, hInst);
}

INT_PTR ShowTip(WPARAM wParam, LPARAM lParam) 
{
	CLCINFOTIP *clcit = (CLCINFOTIP *)lParam;
	HWND clist = (HWND)CallService(MS_CLUI_GETHWNDTREE, 0, 0);

	if (clcit->isGroup) return 0; // no group tips (since they're pretty useless)
	if (clcit->isTreeFocused == 0 && opt.bShowNoFocus == false && clist == WindowFromPoint(clcit->ptCursor)) return 0;
	if (clcit->ptCursor.x == pt.x && clcit->ptCursor.y == pt.y) return 0;
	pt.x = pt.y = 0;

	CLCINFOTIPEX *clcit2 = (CLCINFOTIPEX *)mir_alloc(sizeof(CLCINFOTIPEX));
	memcpy(clcit2, clcit, sizeof(CLCINFOTIP));
	clcit2->cbSize = sizeof(CLCINFOTIPEX);
	clcit2->szProto = NULL;
	clcit2->swzText = NULL;

	if (wParam) // wParam is char pointer containing text - e.g. status bar tooltip
	{ 
		clcit2->swzText = a2t((char *)wParam);
		GetCursorPos(&clcit2->ptCursor);
	}

	PostMPMessage(MUM_CREATEPOPUP, 0, (LPARAM)clcit2);
	return 1;
}

int ShowTipHook(WPARAM wParam, LPARAM lParam) 
{
    ShowTip(wParam, lParam);
    return 0;
}

INT_PTR ShowTipW(WPARAM wParam, LPARAM lParam) 
{
	CLCINFOTIP *clcit = (CLCINFOTIP *)lParam;
	HWND clist = (HWND)CallService(MS_CLUI_GETHWNDTREE, 0, 0);

	if (clcit->isGroup) return 0; // no group tips (since they're pretty useless)
	if (clcit->isTreeFocused == 0 && opt.bShowNoFocus == false && clist == WindowFromPoint(clcit->ptCursor)) return 0;
	if (clcit->ptCursor.x == pt.x && clcit->ptCursor.y == pt.y) return 0;
	pt.x = pt.y = -1;

	CLCINFOTIPEX *clcit2 = (CLCINFOTIPEX *)mir_alloc(sizeof(CLCINFOTIPEX));
	memcpy(clcit2, clcit, sizeof(CLCINFOTIP));
	clcit2->cbSize = sizeof(CLCINFOTIPEX);
	clcit2->szProto = NULL;
	clcit2->swzText = NULL;

	if (wParam) // wParam is char pointer containing text - e.g. status bar tooltip
	{ 
		clcit2->swzText = mir_tstrdup((TCHAR *)wParam);
		GetCursorPos(&clcit2->ptCursor);
	}

	PostMPMessage(MUM_CREATEPOPUP, 0, (LPARAM)clcit2);
	return 1;
}

INT_PTR HideTip(WPARAM wParam, LPARAM lParam) 
{
	//CLCINFOTIP *clcit = (CLCINFOTIP *)lParam;
	if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
		return 0;

	GetCursorPos(&pt);
	PostMPMessage(MUM_DELETEPOPUP, 0, 0);
	return 1;
}

int HideTipHook(WPARAM wParam, LPARAM lParam) 
{
    HideTip(wParam, lParam);
    return 0;
}

int ProtoAck(WPARAM wParam, LPARAM lParam) 
{
	DBVARIANT dbv;
	ACKDATA *ack = (ACKDATA *)lParam;
	if (ack->result != ACKRESULT_SUCCESS)
		return 0;

	if (ack->type == ACKTYPE_AWAYMSG) 
	{
		if (!DBGetContactSettingTString(ack->hContact, "CList", "StatusMsg", &dbv))
		{
			if (dbv.ptszVal[0]) 
				PostMPMessage(MUM_GOTSTATUS, (WPARAM)ack->hContact, (LPARAM)mir_tstrdup(dbv.ptszVal));

			DBFreeVariant(&dbv);
		}
	} 
	else if (ack->type == ICQACKTYPE_XSTATUS_RESPONSE)
	{
		PostMPMessage(MUM_GOTXSTATUS, (WPARAM)ack->hContact, 0);
	}

	return 0;
}

int AvatarChanged(WPARAM wParam, LPARAM lParam) 
{
	HANDLE hContact = (HANDLE)wParam;
	PostMPMessage(MUM_GOTAVATAR, (WPARAM)hContact, 0);
	return 0;
}

int FramesShowSBTip(WPARAM wParam, LPARAM lParam) 
{
	if (opt.bStatusBarTips)
	{
		char *szProto = (char *)wParam;

		CLCINFOTIPEX *clcit2 = (CLCINFOTIPEX *)mir_alloc(sizeof(CLCINFOTIPEX));
		memset(clcit2, 0, sizeof(CLCINFOTIPEX));
		clcit2->cbSize = sizeof(CLCINFOTIPEX);
		clcit2->szProto= szProto; // assume static string
		GetCursorPos(&clcit2->ptCursor);

		PostMPMessage(MUM_CREATEPOPUP, 0, (LPARAM)clcit2);
		return 1;
	}
	return 0;
}

int FramesHideSBTip(WPARAM wParam, LPARAM lParam) 
{
	if (opt.bStatusBarTips) 
	{
		PostMPMessage(MUM_DELETEPOPUP, 0, 0);
		return 1;
	}
	return 0;
}

BOOL MyDestroyWindow(HWND hwnd) 
{
	SendMessage(hwnd, PUM_FADEOUTWINDOW, 0, 0);
	return DestroyWindow(hwnd);
}