/*
Paste It plugin
Copyright (C) 2011 Krzysztof Kral

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 version 2
of the License.

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, see <http://www.gnu.org/licenses/>.
*/

#include "stdafx.h"
#include "PasteToWeb1.h"
#include "PasteToWeb2.h"
#include "version.h"
#include "resource.h"
#include "Options.h"


PasteToWeb* pasteToWebs[PasteToWeb::pages];
std::map<HANDLE, HWND>* contactWindows;
DWORD gMirandaVersion;

extern HINSTANCE hInst;
HANDLE hModulesLoaded, hTabsrmmButtonPressed;
HANDLE g_hNetlibUser;
HANDLE hPrebuildContactMenu;
HANDLE hServiceContactMenu;
HGENMENU hContactMenu;
HGENMENU hWebPageMenus[PasteToWeb::pages];
HANDLE hOptionsInit;
HANDLE hWindowEvent = NULL;
HINSTANCE hInst;

#define MODULE				"PasteIt"
#define FROM_CLIPBOARD 10
#define FROM_FILE 11
#define DEF_PAGES_START 20
#define MS_PASTEIT_CONTACTMENU	"PasteIt/ContactMenu"

PLUGININFOEX pluginInfo={
	sizeof(PLUGININFOEX),
	__PLUGIN_NAME,
	PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
	__DESCRIPTION,
	__AUTHOR,
	__AUTHOREMAIL,
	__COPYRIGHT,
	__AUTHORWEB,
	UNICODE_AWARE, 
	// {1AAC15E8-DCEC-4050-B66F-2AA0E6120C22}
	{ 0x1aac15e8, 0xdcec, 0x4050, { 0xb6, 0x6f, 0x2a, 0xa0, 0xe6, 0x12, 0xc, 0x22 } }
};

static IconItem icon = { LPGEN("Paste It"), "PasteIt_main", IDI_MENU };

XML_API xi = {0};
int hLangpack = 0;

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
	hInst = hModule;
	return TRUE;
}

extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD mirandaVersion)
{
	gMirandaVersion = mirandaVersion;
	if (mirandaVersion < PLUGIN_MAKE_VERSION(0, 8, 0, 0))
		return NULL;

	return &pluginInfo;
}


std::wstring GetFile()
{
	TCHAR filter[512];
	_tcscpy_s(filter, 512, TranslateT("All Files (*.*)"));
	memcpy(filter + _tcslen(filter), _T("\0*.*\0"), 6 * sizeof(TCHAR));
	TCHAR stzFilePath[1024];
	stzFilePath[0] = 0;
	stzFilePath[1] = 0;
	OPENFILENAME ofn = {0};
	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = 0;
	ofn.lpstrFilter = filter;
	ofn.nFilterIndex = 1;
	ofn.lpstrFile = stzFilePath;
	ofn.lpstrTitle = TranslateT("Paste It - Select file");
	ofn.nMaxFile = 1024;
	ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_EXPLORER | OFN_NOCHANGEDIR;
	if(GetOpenFileName(&ofn))
	{
		return stzFilePath;
	}

	return L"";
}

void PasteIt(HANDLE hContact, int mode)
{
	PasteToWeb* pasteToWeb = pasteToWebs[Options::instance->defWeb];
	if(mode == FROM_CLIPBOARD)
	{
		pasteToWeb->FromClipboard();
	}
	else if(mode == FROM_FILE)
	{
		std::wstring file = GetFile();
		if(file.length() > 0)
		{
			pasteToWeb->FromFile(file);
		}
		else return;
	}
	else return;

	if(pasteToWeb->szFileLink[0] == 0 && pasteToWeb->error != NULL)
	{
		MessageBox(NULL, pasteToWeb->error, TranslateT("Error"), MB_OK | MB_ICONERROR);
	}
	else if(hContact != NULL && pasteToWeb->szFileLink[0] != 0)
	{
		char *szProto = GetContactProto(hContact);
		if (szProto && (INT_PTR)szProto != CALLSERVICE_NOTFOUND)
		{
			BOOL isChat = DBGetContactSettingByte(hContact, szProto, "ChatRoom", 0); 
			if(Options::instance->autoSend)
			{
				if(!isChat)
				{
					DBEVENTINFO dbei = {0};
					dbei.cbSize = sizeof(dbei);
					dbei.eventType = EVENTTYPE_MESSAGE;
					dbei.flags = DBEF_SENT;
					dbei.szModule = szProto;
					dbei.timestamp = (DWORD)time(NULL);
					dbei.cbBlob = (DWORD)strlen(pasteToWeb->szFileLink) + 1;
					dbei.pBlob = (PBYTE)pasteToWeb->szFileLink;
					CallService(MS_DB_EVENT_ADD, (WPARAM)hContact, (LPARAM)&dbei);
					CallContactService(hContact, PSS_MESSAGE, 0, (LPARAM)pasteToWeb->szFileLink);
				}
				else
				{
					// PSS_MESSAGE is not compatible with chat rooms
					// there are no simple method to send text to all users
					// in chat room. 
					// First I check if protocol is unicode or ascii.
					BOOL isUnicodePlugin = TRUE;
					PROTOACCOUNT* protoAc = ProtoGetAccount(szProto);
					if(protoAc != NULL)
					{
						// protoAc->ppro is abstract class, that contains
						// methods implemented in protocol ddl`s segment.
						// Method address in vptr table must be converted
						// to hInstance of protocol dll.
						PROTO_INTERFACE* protoInt = protoAc->ppro;
						MEMORY_BASIC_INFORMATION mb;
						INT_PTR *vptr = *(INT_PTR**)&protoAc->ppro;  
						INT_PTR *vtable = (INT_PTR *)*vptr;   
						if(VirtualQuery((void*)vtable[0], &mb, sizeof(MEMORY_BASIC_INFORMATION)))
						{
							typedef PLUGININFOEX * (__cdecl * Miranda_Plugin_InfoEx) ( DWORD mirandaVersion );
							HINSTANCE hInst = (HINSTANCE)mb.AllocationBase;
							// Now I can get PLUGININFOEX from protocol
							Miranda_Plugin_InfoEx infoEx = (Miranda_Plugin_InfoEx) GetProcAddress(hInst, "MirandaPluginInfoEx");
							PLUGININFOEX* pi = NULL;
							if(infoEx != NULL)
							{
								pi = infoEx(gMirandaVersion);
							}
							// If PLUGININFOEX flags contains UNICODE_AWARE,
							// this mean that protocol is unicode.
							if(pi != NULL && pi->cbSize == sizeof(PLUGININFOEX))
							{
								isUnicodePlugin = pi->flags & UNICODE_AWARE;
							}
						}
					}

					// Next step is to get all protocol sessions and find
					// one with correct hContact 
					GC_INFO gci = {0};    
					GCDEST  gcd = {0};    
					GCEVENT gce = {0};
					int cnt = (int)CallService(MS_GC_GETSESSIONCOUNT, 0, (LPARAM)szProto);  
					for (int i = 0; i < cnt ; i++ ) 
					{
						gci.iItem = i;
						gci.pszModule = szProto;
						gci.Flags = BYINDEX | HCONTACT | ID;
						CallService(MS_GC_GETINFO, 0, (LPARAM)(GC_INFO *) &gci);
						if (gci.hContact == hContact) 
						{
							// In this place session was finded, gci.pszID contains
							// session ID, but it is in unicode or ascii format,
							// depends on protocol wersion
							gcd.pszModule = szProto;
							gcd.iType = GC_EVENT_SENDMESSAGE;
							gcd.ptszID = gci.pszID;
							gce.cbSize = sizeof(GCEVENT);                     
							gce.pDest = &gcd;                 
							gce.bIsMe = TRUE;             
							gce.dwFlags = isUnicodePlugin ? (GCEF_ADDTOLOG | GC_UNICODE) : GCEF_ADDTOLOG;  
							wchar_t* s = NULL;
							if(isUnicodePlugin)
							{
								// If session ID is in unicode, text must be too in unicode
								s = mir_a2u_cp(pasteToWeb->szFileLink, CP_ACP);
								gce.ptszText = s;             
							}
							else
							{
								// If session ID is in ascii, text must be too in ascii
								gce.pszText = pasteToWeb->szFileLink;            
							}
							gce.time = time(NULL);                                            
							CallService(MS_GC_EVENT, 0, (LPARAM)(GCEVENT *) &gce); 
							if(s != NULL)
								mir_free(s);                       
							break;  
						}
					}
				}
				
				// Send message to focus window
				CallServiceSync(MS_MSG_SENDMESSAGE, (WPARAM)hContact, 0);
			}
			else
			{
				if(isChat)
				{
					// MS_MSG_SENDMESSAGE in incompatible with chat rooms,
					// because it sends text to IDC_MESSAGE window,
					// but in chat rooms is only IDC_CHAT_MESSAGE window.
					// contactWindows map contains all opened hContact
					// with assaigned to them chat windows. 
					// This map is prepared in ME_MSG_WINDOWEVENT event. 
					std::map<HANDLE, HWND>::iterator it = contactWindows->find(hContact);
					if(it != contactWindows->end())
					{
						// it->second is imput window, so now I can send to them 
						// new text. Afterr all is sended MS_MSG_SENDMESSAGE 
						// to focus window.
						SendMessage(it->second, EM_SETSEL, -1, SendMessage(it->second, WM_GETTEXTLENGTH, 0, 0));
						SendMessageA(it->second, EM_REPLACESEL, FALSE, (LPARAM)pasteToWeb->szFileLink);
						CallServiceSync(MS_MSG_SENDMESSAGE, (WPARAM)hContact, NULL);
					}
					else
					{
						// If window do not exist, maybe it is not chat
						CallServiceSync(MS_MSG_SENDMESSAGE, (WPARAM)hContact, (LPARAM)pasteToWeb->szFileLink);
					}
				}
				else
				{
					CallServiceSync(MS_MSG_SENDMESSAGE, (WPARAM)hContact, (LPARAM)pasteToWeb->szFileLink);
				}
			}
		}
	}
}

int TabsrmmButtonPressed(WPARAM wParam, LPARAM lParam) 
{
	CustomButtonClickData *cbc = (CustomButtonClickData *)lParam;
	HANDLE hContact = (HANDLE)wParam;

	if (!strcmp(cbc->pszModule, MODULE) && cbc->dwButtonId == 1 && hContact) 
	{
		if (cbc->flags == BBCF_ARROWCLICKED) 
		{
			HMENU hMenu = CreatePopupMenu();
			if (hMenu) 
			{
				AppendMenu(hMenu, MF_STRING, FROM_CLIPBOARD, TranslateT("Paste from clipboard"));
				AppendMenu(hMenu, MF_STRING, FROM_FILE, TranslateT("Paste from file"));
				HMENU hDefMenu = CreatePopupMenu();
				for(int i = 0 ; i < PasteToWeb::pages; ++i)
				{
					UINT flags = MF_STRING;
					if(Options::instance->defWeb == i)
						flags |= MF_CHECKED;

					AppendMenu(hDefMenu, flags, DEF_PAGES_START + i, pasteToWebs[i]->GetName());
				}

				AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hDefMenu, TranslateT("Default web page"));

				POINT pt;
				GetCursorPos(&pt);
				HWND hwndBtn = WindowFromPoint(pt);
				if (hwndBtn) 
				{
					RECT rc;
					GetWindowRect(hwndBtn, &rc);
					SetForegroundWindow(cbc->hwndFrom);
					int selected = TrackPopupMenu(hMenu, TPM_RETURNCMD, rc.left, rc.bottom, 0, cbc->hwndFrom, 0);
					if (selected != 0)
					{
						if(selected >= DEF_PAGES_START)
						{
							Options::instance->SetDefWeb(selected - DEF_PAGES_START);
						}
						else
						{
							PasteIt(hContact, selected);
						}
					}
				}
				
				DestroyMenu(hDefMenu);
				DestroyMenu(hMenu);
			}
		} 
		else
		{
			PasteIt(hContact, FROM_CLIPBOARD);
		}
	}

	return 0;
}

int PrebuildContactMenu(WPARAM wParam, LPARAM lParam)
{
	bool bIsContact = false;
	
	char *szProto = GetContactProto((HANDLE)wParam);
	if (szProto && (INT_PTR)szProto != CALLSERVICE_NOTFOUND)
		bIsContact = (CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_IM) ? true : false;
	
	CLISTMENUITEM mi = { sizeof(mi) };
	mi.flags = CMIM_FLAGS;

	if (!bIsContact) mi.flags |= CMIF_HIDDEN;
	else mi.flags &= ~CMIF_HIDDEN;
	CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hContactMenu, (LPARAM)&mi);

	return 0;
}

INT_PTR ContactMenuService(WPARAM wParam, LPARAM lParam) 
{
	if(lParam >= DEF_PAGES_START)
	{
		Options::instance->SetDefWeb(lParam - DEF_PAGES_START);
	}
	else
	{
		HANDLE hContact = (HANDLE)wParam;
		PasteIt(hContact, lParam);
	}
	return 0;
}

void InitMenuItems()
{
	CLISTMENUITEM mi = { sizeof(mi) };
	mi.flags = CMIF_ROOTPOPUP | CMIF_ICONFROMICOLIB | CMIF_TCHAR;
	mi.icolibItem = icon.hIcolib;
	mi.position = 3000090005;
	mi.ptszName = LPGENT("Paste It");

	hContactMenu = Menu_AddContactMenuItem(&mi);

	memset(&mi, 0, sizeof(mi));
	mi.cbSize = sizeof(mi);
	mi.flags = CMIF_CHILDPOPUP | CMIF_ROOTHANDLE | CMIF_TCHAR;
	mi.pszService = MS_PASTEIT_CONTACTMENU;
	mi.hParentMenu = hContactMenu;
	mi.popupPosition = FROM_CLIPBOARD;	
	mi.ptszName = LPGENT("Paste from clipboard");
	Menu_AddContactMenuItem(&mi);

	mi.popupPosition = FROM_FILE;
	mi.ptszName = LPGENT("Paste from file");
	Menu_AddContactMenuItem(&mi);

	mi.popupPosition = DEF_PAGES_START - 1;
	mi.ptszName = LPGENT("Default web page");
	HGENMENU hDefWebMenu = Menu_AddContactMenuItem(&mi);
	
	CLISTMENUITEM mi2 = { sizeof(mi2) };
	mi2.pszService = MS_PASTEIT_CONTACTMENU;
	mi2.hParentMenu = hDefWebMenu;
	for(int i = 0 ; i < PasteToWeb::pages; ++i)
	{
		mi2.flags =  CMIF_CHILDPOPUP | CMIF_ROOTHANDLE | CMIF_TCHAR;
		if(Options::instance->defWeb == i)
			mi2.flags |= CMIF_CHECKED;
		mi2.ptszName = pasteToWebs[i]->GetName();
		mi2.popupPosition = mi2.position = DEF_PAGES_START + i;
		hWebPageMenus[i] = Menu_AddContactMenuItem(&mi2);
	}

	hPrebuildContactMenu = HookEvent(ME_CLIST_PREBUILDCONTACTMENU, PrebuildContactMenu);
}

void DefWebPageChanged()
{
	CLISTMENUITEM mi = { sizeof(mi) };
	for (int i = 0; i < PasteToWeb::pages; i++) {
		mi.flags = CMIM_FLAGS;
		if (Options::instance->defWeb == i) 
			mi.flags |= CMIF_CHECKED;

		CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hWebPageMenus[i], (LPARAM)&mi);
	}
}

void InitTabsrmmButton()
{
	if (ServiceExists(MS_BB_ADDBUTTON)) 
	{
		BBButton btn = {0};
		btn.cbSize = sizeof(btn);
		btn.dwButtonID = 1;
		btn.pszModuleName = MODULE;
		btn.dwDefPos = 110;
		btn.hIcon = icon.hIcolib;
		btn.bbbFlags = BBBF_ISARROWBUTTON | BBBF_ISIMBUTTON | BBBF_ISLSIDEBUTTON | BBBF_CANBEHIDDEN | BBBF_ISCHATBUTTON;
		btn.ptszTooltip = TranslateT("Paste It");
		CallService(MS_BB_ADDBUTTON, 0, (LPARAM)&btn);

		if(hTabsrmmButtonPressed != NULL)
			UnhookEvent(hTabsrmmButtonPressed);

		hTabsrmmButtonPressed = HookEvent(ME_MSG_BUTTONPRESSED, TabsrmmButtonPressed);
	}
}

int WindowEvent(WPARAM wParam, MessageWindowEventData* lParam)
{
	if(lParam->uType == MSG_WINDOW_EVT_OPEN)
	{
		char *szProto = GetContactProto(lParam->hContact);
		if (szProto && (INT_PTR)szProto != CALLSERVICE_NOTFOUND)
		{
			if(DBGetContactSettingByte(lParam->hContact, szProto, "ChatRoom", 0))
			{
				(*contactWindows)[lParam->hContact] = lParam->hwndInput;
			}
		}
	}
	else if(lParam->uType == MSG_WINDOW_EVT_CLOSE)
	{
		std::map<HANDLE, HWND>::iterator it = contactWindows->find(lParam->hContact);
		if(it != contactWindows->end())
		{
			contactWindows->erase(it);
		}
	}

	return 0;
}

int ModulesLoaded(WPARAM wParam, LPARAM lParam)
{
	InitMenuItems();
	InitTabsrmmButton();
	hWindowEvent = HookEvent(ME_MSG_WINDOWEVENT, (MIRANDAHOOK)WindowEvent);

	return 0;
}

extern "C" int __declspec(dllexport) Load(void)
{
	mir_getXI(&xi);
	mir_getLP(&pluginInfo);

	Icon_Register(hInst, LPGEN("Paste It"), &icon, 1);

	NETLIBUSER nlu = {0};
	nlu.cbSize = sizeof(nlu);
  	nlu.flags = NUF_TCHAR | NUF_OUTGOING | NUF_HTTPCONNS;
	nlu.szSettingsModule = MODULE;
	nlu.ptszDescriptiveName = TranslateT("Paste It HTTP connections");
	g_hNetlibUser = ( HANDLE )CallService( MS_NETLIB_REGISTERUSER, 0, ( LPARAM )&nlu );
	
	pasteToWebs[0] = new PasteToWeb1();
	pasteToWebs[0]->pageIndex = 0;
	pasteToWebs[1] = new PasteToWeb2();
	pasteToWebs[1]->pageIndex = 1;
	Options::instance = new Options();
	pasteToWebs[0]->ConfigureSettings();
	pasteToWebs[1]->ConfigureSettings();
	Options::instance->Load();
	hModulesLoaded = HookEvent(ME_SYSTEM_MODULESLOADED, ModulesLoaded);
	hOptionsInit = HookEvent(ME_OPT_INITIALISE, Options::InitOptions);
	hTabsrmmButtonPressed = NULL;
	hServiceContactMenu = CreateServiceFunction(MS_PASTEIT_CONTACTMENU, ContactMenuService);
	contactWindows = new std::map<HANDLE, HWND>();
	return 0;
}

extern "C" int __declspec(dllexport) Unload(void) 
{	
	UnhookEvent(hModulesLoaded);
	UnhookEvent(hPrebuildContactMenu);
	UnhookEvent(hOptionsInit);
	if(hWindowEvent != NULL)
		UnhookEvent(hWindowEvent);

	DestroyServiceFunction(hServiceContactMenu);
	Netlib_CloseHandle(g_hNetlibUser);
	if(hTabsrmmButtonPressed != NULL)
		UnhookEvent(hTabsrmmButtonPressed);

	for(int i=0; i < PasteToWeb::pages; ++i)
		if(pasteToWebs[i] != NULL) {
			delete pasteToWebs[i];
			pasteToWebs[i] = NULL;
		}

	if(Options::instance != NULL) {
		delete Options::instance;
		Options::instance = NULL;
	}

	delete contactWindows;
	return 0;
}