/*

Jabber Protocol Plugin for Miranda NG

Copyright (c) 2002-04  Santithorn Bunchua
Copyright (c) 2005-12  George Hazan
Copyright (c) 2007     Artem Shpynov
Copyright (C) 2012-19 Miranda NG team

Module implements a search according to XEP-0055: Jabber Search
http://www.xmpp.org/extensions/xep-0055.html

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"
#include <CommCtrl.h>
#include "jabber_iq.h"
#include "jabber_caps.h"

///////////////////////////////////////////////////////////////////////////////
// Subclassing of IDC_FRAME to implement more user-friendly fields scrolling

static int JabberSearchFrameProc(HWND hwnd, int msg, WPARAM wParam, LPARAM lParam)
{
	if (msg == WM_COMMAND && lParam != 0) {
		HWND hwndDlg = GetParent(hwnd);
		JabberSearchData *dat = (JabberSearchData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
		if (dat && lParam) {
			int pos = dat->curPos;
			RECT MineRect;
			RECT FrameRect;
			GetWindowRect(GetDlgItem(hwndDlg, IDC_FRAME), &FrameRect);
			GetWindowRect((HWND)lParam, &MineRect);
			if (MineRect.top - 10 < FrameRect.top) {
				pos = dat->curPos + (MineRect.top - 14 - FrameRect.top);
				if (pos < 0) pos = 0;
			}
			else if (MineRect.bottom > FrameRect.bottom) {
				pos = dat->curPos + (MineRect.bottom - FrameRect.bottom);
				if (dat->frameHeight + pos > dat->CurrentHeight)
					pos = dat->CurrentHeight - dat->frameHeight;
			}
			if (pos != dat->curPos) {
				ScrollWindow(GetDlgItem(hwndDlg, IDC_FRAME), 0, dat->curPos - pos, nullptr, &(dat->frameRect));
				SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, pos, TRUE);
				RECT Invalid = dat->frameRect;
				if (dat->curPos - pos > 0)
					Invalid.bottom = Invalid.top + (dat->curPos - pos);
				else
					Invalid.top = Invalid.bottom + (dat->curPos - pos);

				RedrawWindow(GetDlgItem(hwndDlg, IDC_FRAME), nullptr, nullptr, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
				dat->curPos = pos;
			}
		}
		if (HIWORD(wParam) == EN_SETFOCUS) { //Transmit focus set notification to parent window
			PostMessage(GetParent(hwndDlg), WM_COMMAND, MAKEWPARAM(0, EN_SETFOCUS), (LPARAM)hwndDlg);
		}
	}

	if (msg == WM_PAINT) {
		PAINTSTRUCT ps;
		HDC hdc = BeginPaint(hwnd, &ps);
		FillRect(hdc, &(ps.rcPaint), GetSysColorBrush(COLOR_BTNFACE));
		EndPaint(hwnd, &ps);
	}

	return DefWindowProc(hwnd, msg, wParam, lParam);
}

///////////////////////////////////////////////////////////////////////////////
//  Add Search field to form

static int JabberSearchAddField(HWND hwndDlg, Data* FieldDat)
{
	if (!FieldDat || !FieldDat->Label || !FieldDat->Var)
		return FALSE;

	HFONT hFont = (HFONT)SendMessage(hwndDlg, WM_GETFONT, 0, 0);
	HWND hwndParent = GetDlgItem(hwndDlg, IDC_FRAME);
	LONG_PTR frameExStyle = GetWindowLongPtr(hwndParent, GWL_EXSTYLE);
	frameExStyle |= WS_EX_CONTROLPARENT;
	SetWindowLongPtr(hwndParent, GWL_EXSTYLE, frameExStyle);
	SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_FRAME), GWLP_WNDPROC, (LONG_PTR)JabberSearchFrameProc);

	int CornerX = 1;
	int CornerY = 1;
	RECT rect;
	GetClientRect(hwndParent, &rect);
	int width = rect.right - 5 - CornerX;

	int Order = (FieldDat->bHidden) ? -1 : FieldDat->Order;

	HWND hwndLabel = CreateWindowEx(0, L"STATIC", (const wchar_t *)TranslateW(FieldDat->Label), WS_CHILD, CornerX, CornerY + Order * 40, width, 13, hwndParent, nullptr, g_plugin.getInst(), nullptr);
	HWND hwndVar = CreateWindowEx(0 | WS_EX_CLIENTEDGE, L"EDIT", (const wchar_t *)FieldDat->defValue, WS_CHILD | WS_TABSTOP, CornerX + 5, CornerY + Order * 40 + 14, width, 20, hwndParent, nullptr, g_plugin.getInst(), nullptr);
	SendMessage(hwndLabel, WM_SETFONT, (WPARAM)hFont, 0);
	SendMessage(hwndVar, WM_SETFONT, (WPARAM)hFont, 0);
	if (!FieldDat->bHidden) {
		ShowWindow(hwndLabel, SW_SHOW);
		ShowWindow(hwndVar, SW_SHOW);
		EnableWindow(hwndLabel, !FieldDat->bReadOnly);
		SendMessage(hwndVar, EM_SETREADONLY, (WPARAM)FieldDat->bReadOnly, 0);
	}
	//remade list
	//reallocation
	JabberSearchData *dat = (JabberSearchData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
	if (dat) {
		dat->pJSInf = (JabberSearchFieldsInfo*)realloc(dat->pJSInf, sizeof(JabberSearchFieldsInfo)*(dat->nJSInfCount + 1));
		dat->pJSInf[dat->nJSInfCount].hwndCaptionItem = hwndLabel;
		dat->pJSInf[dat->nJSInfCount].hwndValueItem = hwndVar;
		dat->pJSInf[dat->nJSInfCount].szFieldCaption = wcsdup(FieldDat->Label);
		dat->pJSInf[dat->nJSInfCount].szFieldName = wcsdup(FieldDat->Var);
		dat->nJSInfCount++;
	}
	return CornerY + Order * 40 + 14 + 20;
}

////////////////////////////////////////////////////////////////////////////////
// Available search field request result handler  (XEP-0055. Examples 2, 7)

void CJabberProto::OnIqResultGetSearchFields(HXML iqNode, CJabberIqInfo*)
{
	if (!searchHandleDlg)
		return;

	const wchar_t *type = XmlGetAttrValue(iqNode, L"type");
	if (type == nullptr)
		return;

	if (!mir_wstrcmp(type, L"result")) {
		HXML queryNode = XmlGetNthChild(iqNode, L"query", 1);
		HXML xNode = XmlGetChildByTag(queryNode, "x", "xmlns", JABBER_FEAT_DATA_FORMS);

		ShowWindow(searchHandleDlg, SW_HIDE);
		if (xNode) {
			//1. Form
			PostMessage(searchHandleDlg, WM_USER + 11, (WPARAM)xmlCopyNode(xNode), 0);
			HXML xcNode = XmlGetNthChild(xNode, L"instructions", 1);
			if (xcNode)
				SetDlgItemText(searchHandleDlg, IDC_INSTRUCTIONS, XmlGetText(xcNode));
		}
		else {
			int Order = 0;
			for (int i = 0;; i++) {
				HXML chNode = XmlGetChild(queryNode, i);
				if (!chNode)
					break;

				if (!mir_wstrcmpi(XmlGetName(chNode), L"instructions") && XmlGetText(chNode))
					SetDlgItemText(searchHandleDlg, IDC_INSTRUCTIONS, TranslateW(XmlGetText(chNode)));
				else if (XmlGetName(chNode)) {
					Data *MyData = (Data*)malloc(sizeof(Data));
					memset(MyData, 0, sizeof(Data));

					MyData->Label = mir_wstrdup(XmlGetName(chNode));
					MyData->Var = mir_wstrdup(XmlGetName(chNode));
					MyData->defValue = mir_wstrdup(XmlGetText(chNode));
					MyData->Order = Order;
					if (MyData->defValue) MyData->bReadOnly = TRUE;
					PostMessage(searchHandleDlg, WM_USER + 10, FALSE, (LPARAM)MyData);
					Order++;
				}
			}
		}

		const wchar_t *szFrom = XmlGetAttrValue(iqNode, L"from");
		if (szFrom)
			SearchAddToRecent(szFrom, searchHandleDlg);
		PostMessage(searchHandleDlg, WM_USER + 10, 0, 0);
		ShowWindow(searchHandleDlg, SW_SHOW);
	}
	else if (!mir_wstrcmp(type, L"error")) {
		const wchar_t *code = nullptr;
		const wchar_t *description = nullptr;
		wchar_t buff[255];
		HXML errorNode = XmlGetChild(iqNode, "error");
		if (errorNode) {
			code = XmlGetAttrValue(errorNode, L"code");
			description = XmlGetText(errorNode);
		}
		mir_snwprintf(buff, TranslateT("Error %s %s\r\nPlease select other server"), code ? code : L"", description ? description : L"");
		SetDlgItemText(searchHandleDlg, IDC_INSTRUCTIONS, buff);
	}
	else SetDlgItemText(searchHandleDlg, IDC_INSTRUCTIONS, TranslateT("Error: unknown reply received\r\nPlease select other server"));
}

//////////////////////////////////////////////////////////////////////////////////////////
//  Return results to search dialog
//  The	pmFields is the pointer to map of <field Name, field Label> Not unical but ordered
//	This can help to made result parser routines more simple

static wchar_t *nickfields[] = { L"nick", L"nickname", L"fullname", L"name", L"given", L"first", L"jid", nullptr };

void CJabberProto::SearchReturnResults(HANDLE  id, void * pvUsersInfo, U_TCHAR_MAP * pmAllFields)
{
	LIST<wchar_t> ListOfNonEmptyFields(20, (LIST<wchar_t>::FTSortFunc)TCharKeyCmp);
	LIST<wchar_t> ListOfFields(20);
	LIST<U_TCHAR_MAP> *plUsersInfo = (LIST<U_TCHAR_MAP>*)pvUsersInfo;

	// lets fill the ListOfNonEmptyFields but in users order
	for (auto &pmUserData : *plUsersInfo) {
		int nUserFields = pmUserData->getCount();
		for (int j = 0; j < nUserFields; j++) {
			wchar_t *var = pmUserData->getKeyName(j);
			if (var && ListOfNonEmptyFields.getIndex(var) < 0)
				ListOfNonEmptyFields.insert(var);
		}
	}

	// now fill the ListOfFields but order is from pmAllFields
	int nAllCount = pmAllFields->getCount();
	for (int i = 0; i < nAllCount; i++) {
		wchar_t *var = pmAllFields->getUnOrderedKeyName(i);
		if (var && ListOfNonEmptyFields.getIndex(var) < 0)
			continue;
		ListOfFields.insert(var);
	}

	// now lets transfer field names
	int nFieldCount = ListOfFields.getCount();

	CUSTOMSEARCHRESULTS Results = { 0 };
	Results.nSize = sizeof(Results);
	Results.pszFields = (wchar_t**)mir_alloc(sizeof(wchar_t*)*nFieldCount);
	Results.nFieldCount = nFieldCount;

	/* Sending Columns Titles */
	for (int i = 0; i < nFieldCount; i++) {
		wchar_t *var = ListOfFields[i];
		if (var)
			Results.pszFields[i] = pmAllFields->operator [](var);
	}

	Results.psr.cbSize = 0; // sending column names
	ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SEARCHRESULT, id, (LPARAM)&Results);

	/* Sending Users Data */
	Results.psr.cbSize = sizeof(Results.psr); // sending user data

	for (auto &pmUserData : *plUsersInfo) {
		wchar_t buff[200];
		buff[0] = 0;
		for (int j = 0; j < nFieldCount; j++) {
			wchar_t *var = ListOfFields[j];
			wchar_t *value = pmUserData->operator [](var);
			Results.pszFields[j] = value ? value : (wchar_t *)L" ";
			if (!mir_wstrcmpi(var, L"jid") && value)
				Results.psr.id.w = value;
		}

		wchar_t *nick = nullptr;
		for (int k = 0; k < _countof(nickfields) && !nick; k++)
			nick = pmUserData->operator [](nickfields[k]);

		if (nick) {
			if (mir_wstrcmpi(nick, Results.psr.id.w))
				mir_snwprintf(buff, L"%s (%s)", nick, Results.psr.id.w);
			else
				wcsncpy_s(buff, nick, _TRUNCATE);

			nick = buff;
		}
		Results.psr.nick.w = nick;
		Results.psr.flags = PSR_UNICODE;

		ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SEARCHRESULT, id, (LPARAM)&Results);
		Results.psr.nick.w = nullptr;
	}
	mir_free(Results.pszFields);
}

void DestroyKey(wchar_t* key)
{
	mir_free(key);
}

wchar_t* CopyKey(wchar_t* key)
{
	return mir_wstrdup(key);
}

////////////////////////////////////////////////////////////////////////////////
// Search field request result handler  (XEP-0055. Examples 3, 8)

void CJabberProto::OnIqResultAdvancedSearch(HXML iqNode, CJabberIqInfo*)
{
	const wchar_t *type;
	int id;

	U_TCHAR_MAP mColumnsNames(10);
	LIST<void>  SearchResults(2);

	if (((id = JabberGetPacketID(iqNode)) == -1) || ((type = XmlGetAttrValue(iqNode, L"type")) == nullptr)) {
		ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0);
		return;
	}

	if (!mir_wstrcmp(type, L"result")) {
		HXML queryNode = XmlGetNthChild(iqNode, L"query", 1);
		HXML xNode = XmlGetChildByTag(queryNode, "x", "xmlns", JABBER_FEAT_DATA_FORMS);
		if (xNode) {
			//1. Form search results info
			HXML reportNode = XmlGetNthChild(xNode, L"reported", 1);
			if (reportNode) {
				int i = 1;
				while (HXML fieldNode = XmlGetNthChild(reportNode, L"field", i++)) {
					wchar_t *var = (wchar_t*)XmlGetAttrValue(fieldNode, L"var");
					if (var) {
						wchar_t *Label = (wchar_t*)XmlGetAttrValue(fieldNode, L"label");
						mColumnsNames.insert(var, (Label != nullptr) ? Label : var);
					}
				}
			}

			int i = 1;
			HXML itemNode;
			while (itemNode = XmlGetNthChild(xNode, L"item", i++)) {
				U_TCHAR_MAP *pUserColumn = new U_TCHAR_MAP(10);
				int j = 1;
				while (HXML fieldNode = XmlGetNthChild(itemNode, L"field", j++)) {
					if (wchar_t* var = (wchar_t*)XmlGetAttrValue(fieldNode, L"var")) {
						if (wchar_t* Text = (wchar_t*)XmlGetText(XmlGetChild(fieldNode, L"value"))) {
							if (!mColumnsNames[var])
								mColumnsNames.insert(var, var);
							pUserColumn->insert(var, Text);
						}
					}
				}

				SearchResults.insert((void*)pUserColumn);
			}
		}
		else {
			//2. Field list search results info
			int i = 1;
			while (HXML itemNode = XmlGetNthChild(queryNode, L"item", i++)) {
				U_TCHAR_MAP *pUserColumn = new U_TCHAR_MAP(10);

				wchar_t *jid = (wchar_t*)XmlGetAttrValue(itemNode, L"jid");
				wchar_t *keyReturned;
				mColumnsNames.insertCopyKey(L"jid", L"jid", &keyReturned, CopyKey, DestroyKey);
				mColumnsNames.insert(L"jid", keyReturned);
				pUserColumn->insertCopyKey(L"jid", jid, nullptr, CopyKey, DestroyKey);

				for (int j = 0;; j++) {
					HXML child = XmlGetChild(itemNode, j);
					if (!child)
						break;

					const wchar_t *szColumnName = XmlGetName(child);
					if (szColumnName) {
						const wchar_t *ptszChild = XmlGetText(child);
						if (ptszChild && *ptszChild) {
							mColumnsNames.insertCopyKey((wchar_t*)szColumnName, L"", &keyReturned, CopyKey, DestroyKey);
							mColumnsNames.insert((wchar_t*)szColumnName, keyReturned);
							pUserColumn->insertCopyKey((wchar_t*)szColumnName, (wchar_t*)ptszChild, nullptr, CopyKey, DestroyKey);
						}
					}
				}

				SearchResults.insert((void*)pUserColumn);
			}
		}
	}
	else if (!mir_wstrcmp(type, L"error")) {
		const wchar_t *code = nullptr;
		const wchar_t *description = nullptr;
		wchar_t buff[255];
		HXML errorNode = XmlGetChild(iqNode, "error");
		if (errorNode) {
			code = XmlGetAttrValue(errorNode, L"code");
			description = XmlGetText(errorNode);
		}

		mir_snwprintf(buff, TranslateT("Error %s %s\r\nTry to specify more detailed"), code ? code : L"", description ? description : L"");
		ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0);
		if (searchHandleDlg)
			SetDlgItemText(searchHandleDlg, IDC_INSTRUCTIONS, buff);
		else
			MessageBox(nullptr, buff, TranslateT("Search error"), MB_OK | MB_ICONSTOP);
		return;
	}

	SearchReturnResults((HANDLE)id, (void*)&SearchResults, (U_TCHAR_MAP *)&mColumnsNames);

	for (auto &it : SearchResults)
		delete ((U_TCHAR_MAP*)it);

	//send success to finish searching
	ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0);
}

static BOOL CALLBACK DeleteChildWindowsProc(HWND hwnd, LPARAM)
{
	DestroyWindow(hwnd);
	return TRUE;
}

static void JabberSearchFreeData(HWND hwndDlg, JabberSearchData * dat)
{
	if (!dat->fSearchRequestIsXForm && dat->nJSInfCount && dat->pJSInf) {
		for (int i = 0; i < dat->nJSInfCount; i++) {
			if (dat->pJSInf[i].hwndValueItem)
				DestroyWindow(dat->pJSInf[i].hwndValueItem);
			if (dat->pJSInf[i].hwndCaptionItem)
				DestroyWindow(dat->pJSInf[i].hwndCaptionItem);
			if (dat->pJSInf[i].szFieldCaption)
				free(dat->pJSInf[i].szFieldCaption);
			if (dat->pJSInf[i].szFieldName)
				free(dat->pJSInf[i].szFieldName);
		}
		free(dat->pJSInf);
		dat->pJSInf = nullptr;
	}
	else EnumChildWindows(GetDlgItem(hwndDlg, IDC_FRAME), DeleteChildWindowsProc, 0);

	if (dat->xNode)
		xmlDestroyNode(dat->xNode);

	SendDlgItemMessage(hwndDlg, IDC_FRAME, WM_SETFONT, (WPARAM)SendMessage(hwndDlg, WM_GETFONT, 0, 0), 0);
	dat->nJSInfCount = 0;
	ShowWindow(GetDlgItem(hwndDlg, IDC_VSCROLL), SW_HIDE);
	SetDlgItemText(hwndDlg, IDC_INSTRUCTIONS, TranslateT("Select/type search service URL above and press <Go>"));
}

static void JabberSearchRefreshFrameScroll(HWND hwndDlg, JabberSearchData *dat)
{
	HWND hFrame = GetDlgItem(hwndDlg, IDC_FRAME);
	HWND hwndScroll = GetDlgItem(hwndDlg, IDC_VSCROLL);
	RECT rc;
	GetClientRect(hFrame, &rc);
	GetClientRect(hFrame, &dat->frameRect);
	dat->frameHeight = rc.bottom - rc.top;
	if (dat->frameHeight < dat->CurrentHeight) {
		ShowWindow(hwndScroll, SW_SHOW);
		EnableWindow(hwndScroll, TRUE);
	}
	else ShowWindow(hwndScroll, SW_HIDE);

	SetScrollRange(hwndScroll, SB_CTL, 0, dat->CurrentHeight - dat->frameHeight, FALSE);
}

int CJabberProto::SearchRenewFields(HWND hwndDlg, JabberSearchData *dat)
{
	wchar_t szServerName[100];
	EnableWindow(GetDlgItem(hwndDlg, IDC_GO), FALSE);
	GetDlgItemText(hwndDlg, IDC_SERVER, szServerName, _countof(szServerName));
	dat->CurrentHeight = 0;
	dat->curPos = 0;
	SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, 0, FALSE);

	JabberSearchFreeData(hwndDlg, dat);
	JabberSearchRefreshFrameScroll(hwndDlg, dat);

	SetDlgItemText(hwndDlg, IDC_INSTRUCTIONS, m_bJabberOnline ? TranslateT("Please wait...\r\nConnecting search server...") : TranslateT("You have to be connected to server"));

	if (!m_bJabberOnline)
		return 0;

	searchHandleDlg = hwndDlg;

	CJabberIqInfo *pInfo = AddIQ(&CJabberProto::OnIqResultGetSearchFields, JABBER_IQ_TYPE_GET, szServerName);
	m_ThreadInfo->send(XmlNodeIq(pInfo) << XQUERY(L"jabber:iq:search"));
	return pInfo->GetIqId();
}

static void JabberSearchAddUrlToRecentCombo(HWND hwndDlg, const wchar_t *szAddr)
{
	int lResult = SendDlgItemMessage(hwndDlg, IDC_SERVER, (UINT)CB_FINDSTRING, 0, (LPARAM)szAddr);
	if (lResult == -1)
		SendDlgItemMessage(hwndDlg, IDC_SERVER, CB_ADDSTRING, 0, (LPARAM)szAddr);
}

void CJabberProto::SearchDeleteFromRecent(const wchar_t *szAddr, bool deleteLastFromDB)
{
	// search in recent
	for (int i = 0; i < 10; i++) {
		char key[30];
		mir_snprintf(key, "RecentlySearched_%d", i);
		ptrW szValue(getWStringA(key));
		if (szValue == nullptr || mir_wstrcmpi(szAddr, szValue))
			continue;

		for (int j = i; j < 10; j++) {
			mir_snprintf(key, "RecentlySearched_%d", j + 1);
			szValue = getWStringA(key);
			if (szValue != nullptr) {
				mir_snprintf(key, "RecentlySearched_%d", j);
				setWString(0, key, szValue);
			}
			else {
				if (deleteLastFromDB) {
					mir_snprintf(key, "RecentlySearched_%d", j);
					delSetting(0, key);
				}
				break;
			}
		}
		break;
	}
}

void CJabberProto::SearchAddToRecent(const wchar_t *szAddr, HWND hwndDialog)
{
	char key[30];
	SearchDeleteFromRecent(szAddr, true);

	for (int j = 9; j > 0; j--) {
		mir_snprintf(key, "RecentlySearched_%d", j - 1);
		ptrW szValue(getWStringA(key));
		if (szValue != nullptr) {
			mir_snprintf(key, "RecentlySearched_%d", j);
			setWString(0, key, szValue);
		}
	}

	mir_snprintf(key, "RecentlySearched_%d", 0);
	setWString(key, szAddr);
	if (hwndDialog)
		JabberSearchAddUrlToRecentCombo(hwndDialog, szAddr);
}

static INT_PTR CALLBACK JabberSearchAdvancedDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	JabberSearchData* dat = (JabberSearchData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
	switch (msg) {
	case WM_INITDIALOG:
		TranslateDialogDefault(hwndDlg);
		{
			dat = (JabberSearchData *)mir_calloc(sizeof(JabberSearchData));
			dat->ppro = (CJabberProto*)lParam;
			SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat);

			/* Server Combo box */
			ptrA jud(dat->ppro->getStringA("Jud"));
			char *szServerName = (jud == nullptr) ? "users.jabber.org" : jud;
			SetDlgItemTextA(hwndDlg, IDC_SERVER, szServerName);
			SendDlgItemMessageA(hwndDlg, IDC_SERVER, CB_ADDSTRING, 0, (LPARAM)szServerName);
			//TO DO: Add Transports here
			for (auto &it : dat->ppro->m_lstTransports)
				if (it != nullptr)
					JabberSearchAddUrlToRecentCombo(hwndDlg, it);

			for (int i = 0; i < 10; i++) {
				char key[30];
				mir_snprintf(key, "RecentlySearched_%d", i);
				ptrW szValue(dat->ppro->getWStringA(key));
				if (szValue != nullptr)
					JabberSearchAddUrlToRecentCombo(hwndDlg, szValue);
			}

			//TO DO: Add 4 recently used
			dat->lastRequestIq = dat->ppro->SearchRenewFields(hwndDlg, dat);
		}
		return TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDC_SERVER) {
			switch (HIWORD(wParam)) {
			case CBN_SETFOCUS:
				PostMessage(GetParent(hwndDlg), WM_COMMAND, MAKEWPARAM(0, EN_SETFOCUS), (LPARAM)hwndDlg);
				return TRUE;

			case CBN_EDITCHANGE:
				EnableWindow(GetDlgItem(hwndDlg, IDC_GO), TRUE);
				return TRUE;

			case CBN_EDITUPDATE:
				JabberSearchFreeData(hwndDlg, dat);
				EnableWindow(GetDlgItem(hwndDlg, IDC_GO), TRUE);
				return TRUE;

			case CBN_SELENDOK:
				EnableWindow(GetDlgItem(hwndDlg, IDC_GO), TRUE);
				PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_GO, BN_CLICKED), 0);
				return TRUE;
			}
		}
		else if (LOWORD(wParam) == IDC_GO && HIWORD(wParam) == BN_CLICKED) {
			dat->ppro->SearchRenewFields(hwndDlg, dat);
			return TRUE;
		}
		break;

	case WM_SIZE:
		{
			//Resize IDC_FRAME to take full size
			RECT rcForm;
			GetWindowRect(hwndDlg, &rcForm);
			RECT rcFrame;
			GetWindowRect(GetDlgItem(hwndDlg, IDC_FRAME), &rcFrame);
			rcFrame.bottom = rcForm.bottom;
			SetWindowPos(GetDlgItem(hwndDlg, IDC_FRAME), nullptr, 0, 0, rcFrame.right - rcFrame.left, rcFrame.bottom - rcFrame.top, SWP_NOZORDER | SWP_NOMOVE);
			GetWindowRect(GetDlgItem(hwndDlg, IDC_VSCROLL), &rcForm);
			SetWindowPos(GetDlgItem(hwndDlg, IDC_VSCROLL), nullptr, 0, 0, rcForm.right - rcForm.left, rcFrame.bottom - rcFrame.top, SWP_NOZORDER | SWP_NOMOVE);
			JabberSearchRefreshFrameScroll(hwndDlg, dat);
		}
		return TRUE;

	case WM_USER + 11:
		{
			dat->fSearchRequestIsXForm = TRUE;
			dat->xNode = (HXML)wParam;
			JabberFormCreateUI(GetDlgItem(hwndDlg, IDC_FRAME), dat->xNode, &dat->CurrentHeight, TRUE);
			ShowWindow(GetDlgItem(hwndDlg, IDC_FRAME), SW_SHOW);
			dat->nJSInfCount = 1;
		}
		return TRUE;

	case WM_USER + 10:
		{
			Data *MyDat = (Data*)lParam;
			if (MyDat) {
				dat->fSearchRequestIsXForm = (BOOL)wParam;
				dat->CurrentHeight = JabberSearchAddField(hwndDlg, MyDat);
				mir_free(MyDat->Label);
				mir_free(MyDat->Var);
				mir_free(MyDat->defValue);
				free(MyDat);
			}
			else {
				JabberSearchRefreshFrameScroll(hwndDlg, dat);
				ScrollWindow(GetDlgItem(hwndDlg, IDC_FRAME), 0, dat->curPos - 0, nullptr, &(dat->frameRect));
				SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, 0, FALSE);
				dat->curPos = 0;
			}
		}
		return TRUE;

	case WM_MOUSEWHEEL:
		{
			short zDelta = GET_WHEEL_DELTA_WPARAM(wParam);
			if (zDelta) {
				int nScrollLines = 0;
				SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, (void*)&nScrollLines, 0);
				for (int i = 0; i < (nScrollLines + 1) / 2; i++)
					SendMessage(hwndDlg, WM_VSCROLL, (zDelta < 0) ? SB_LINEDOWN : SB_LINEUP, 0);
			}
		}
		return TRUE;

	case WM_VSCROLL:
		{
			int pos;
			if (dat != nullptr) {
				pos = dat->curPos;
				switch (LOWORD(wParam)) {
				case SB_LINEDOWN:
					pos += 10;
					break;
				case SB_LINEUP:
					pos -= 10;
					break;
				case SB_PAGEDOWN:
					pos += (dat->CurrentHeight - 10);
					break;
				case SB_PAGEUP:
					pos -= (dat->CurrentHeight - 10);
					break;
				case SB_THUMBTRACK:
					pos = HIWORD(wParam);
					break;
				}
				if (pos > (dat->CurrentHeight - dat->frameHeight))
					pos = dat->CurrentHeight - dat->frameHeight;
				if (pos < 0)
					pos = 0;
				if (dat->curPos != pos) {
					ScrollWindow(GetDlgItem(hwndDlg, IDC_FRAME), 0, dat->curPos - pos, nullptr, &(dat->frameRect));
					SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, pos, TRUE);
					RECT Invalid = dat->frameRect;
					if (dat->curPos - pos >0)
						Invalid.bottom = Invalid.top + (dat->curPos - pos);
					else
						Invalid.top = Invalid.bottom + (dat->curPos - pos);

					RedrawWindow(GetDlgItem(hwndDlg, IDC_FRAME), nullptr, nullptr, RDW_UPDATENOW | RDW_ALLCHILDREN);
					dat->curPos = pos;
				}
			}
		}
		return TRUE;

	case WM_DESTROY:
		JabberSearchFreeData(hwndDlg, dat);
		JabberFormDestroyUI(GetDlgItem(hwndDlg, IDC_FRAME));
		mir_free(dat);
		SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0);
		return TRUE;
	}
	return FALSE;
}

HWND CJabberProto::CreateExtendedSearchUI(HWND parent)
{
	if (parent && g_plugin.getInst()) {
		ptrW szServer(getWStringA("LoginServer"));
		if (szServer == nullptr || mir_wstrcmpi(szServer, L"S.ms"))
			return CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_SEARCHUSER), parent, JabberSearchAdvancedDlgProc, (LPARAM)this);
	}

	return nullptr; // Failure
}

//////////////////////////////////////////////////////////////////////////
// The function formats request to server

HWND CJabberProto::SearchAdvanced(HWND hwndDlg)
{
	if (!m_bJabberOnline || !hwndDlg)
		return nullptr;	//error

	JabberSearchData * dat = (JabberSearchData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
	if (!dat)
		return nullptr; //error

	// check if server connected (at least one field exists)
	if (dat->nJSInfCount == 0)
		return nullptr;

	// formating request
	BOOL fRequestNotEmpty = FALSE;

	// get server name
	wchar_t szServerName[100];
	GetDlgItemText(hwndDlg, IDC_SERVER, szServerName, _countof(szServerName));

	// formating query
	CJabberIqInfo *pInfo = AddIQ(&CJabberProto::OnIqResultAdvancedSearch, JABBER_IQ_TYPE_SET, szServerName);
	XmlNodeIq iq(pInfo);
	HXML query = iq << XQUERY(L"jabber:iq:search");

	if (m_tszSelectedLang)
		iq << XATTR(L"xml:lang", m_tszSelectedLang); // i'm sure :)

	// next can be 2 cases:
	// Forms: XEP-0055 Example 7
	if (dat->fSearchRequestIsXForm) {
		fRequestNotEmpty = TRUE;
		HXML n = JabberFormGetData(GetDlgItem(hwndDlg, IDC_FRAME), dat->xNode);
		XmlAddChild(query, n);
		xmlDestroyNode(n);
	}
	else { //and Simple fields: XEP-0055 Example 3
		for (int i = 0; i < dat->nJSInfCount; i++) {
			wchar_t szFieldValue[100];
			GetWindowText(dat->pJSInf[i].hwndValueItem, szFieldValue, _countof(szFieldValue));
			if (szFieldValue[0] != 0) {
				XmlAddChild(query, dat->pJSInf[i].szFieldName, szFieldValue);
				fRequestNotEmpty = TRUE;
			}
		}
	}

	if (fRequestNotEmpty) {
		m_ThreadInfo->send(iq);
		return (HWND)pInfo->GetIqId();
	}
	return nullptr;
}