/*

Jabber Protocol Plugin for Miranda IM
Copyright ( C ) 2002-04  Santithorn Bunchua
Copyright ( C ) 2005-12  George Hazan
Copyright ( C ) 2007     Artem Shpynov

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 "jabber.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, NULL,  &( 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 ), NULL, NULL, 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 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,_T("STATIC"),(LPCTSTR)TranslateTS(FieldDat->Label),WS_CHILD, CornerX, CornerY + Order*40, width, 13,hwndParent,NULL,hInst,0);
	HWND hwndVar=CreateWindowEx(0|WS_EX_CLIENTEDGE,_T("EDIT"),(LPCTSTR)FieldDat->defValue,WS_CHILD|WS_TABSTOP, CornerX+5, CornerY + Order*40+14, width ,20,hwndParent,NULL,hInst,0);
	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=_tcsdup(FieldDat->Label);
		dat->pJSInf[dat->nJSInfCount].szFieldName=_tcsdup(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 )
{
	if  ( !searchHandleDlg )
		return;

	LPCTSTR type = xmlGetAttrValue( iqNode, _T("type"));
	if ( !type )
		return;

	if ( !lstrcmp( type, _T("result"))) {
		HXML queryNode = xmlGetNthChild( iqNode, _T("query"), 1 );
		HXML xNode = xmlGetChildByTag( queryNode, "x", "xmlns", _T(JABBER_FEAT_DATA_FORMS));

		ShowWindow(searchHandleDlg,SW_HIDE);
		if ( xNode ) {
			//1. Form
			PostMessage( searchHandleDlg, WM_USER+11, ( WPARAM )xi.copyNode( xNode ), ( LPARAM )0 );
			HXML xcNode = xmlGetNthChild( xNode, _T("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 ( !_tcsicmp( xmlGetName( chNode ), _T("instructions")) && xmlGetText( chNode ))
					SetDlgItemText(searchHandleDlg,IDC_INSTRUCTIONS,TranslateTS(xmlGetText( chNode )));
				else if ( xmlGetName( chNode )) {
					Data *MyData=(Data*)malloc(sizeof(Data));
					memset(MyData,0,sizeof(Data));

					MyData->Label = mir_tstrdup( xmlGetName( chNode ));
					MyData->Var = mir_tstrdup( xmlGetName( chNode ));
					MyData->defValue = mir_tstrdup(xmlGetText( chNode ));
					MyData->Order = Order;
					if (MyData->defValue) MyData->bReadOnly = TRUE;
					PostMessage(searchHandleDlg,WM_USER+10,(WPARAM)FALSE,(LPARAM)MyData);
					Order++;
		}	}	}
		const TCHAR* szFrom = xmlGetAttrValue( iqNode, _T("from"));
		if (szFrom)
			SearchAddToRecent(szFrom,searchHandleDlg);
		PostMessage(searchHandleDlg,WM_USER+10,(WPARAM)0,(LPARAM)0);
		ShowWindow(searchHandleDlg,SW_SHOW);
	}
	else if ( !lstrcmp( type, _T("error"))) {
		const TCHAR* code=NULL;
		const TCHAR* description=NULL;
		TCHAR buff[255];
		HXML errorNode = xmlGetChild( iqNode, "error");
		if ( errorNode ) {
			code = xmlGetAttrValue( errorNode, _T("code"));
			description=xmlGetText( errorNode );
		}
		_sntprintf(buff,SIZEOF(buff),TranslateT("Error %s %s\r\nPlease select other server"),code ? code : _T(""),description?description:_T(""));
		SetDlgItemText(searchHandleDlg,IDC_INSTRUCTIONS,buff);
	}
	else SetDlgItemText( searchHandleDlg, IDC_INSTRUCTIONS, TranslateT( "Error Unknown reply recieved\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

void CJabberProto::SearchReturnResults( HANDLE  id, void * pvUsersInfo, U_TCHAR_MAP * pmAllFields )
{
	LIST<TCHAR> ListOfNonEmptyFields(20,(LIST<TCHAR>::FTSortFunc)TCharKeyCmp);
	LIST<TCHAR> ListOfFields(20);
	LIST<void>* plUsersInfo = ( LIST<void>* )pvUsersInfo;
	int i, nUsersFound = plUsersInfo->getCount();

	// lets fill the ListOfNonEmptyFields but in users order
	for ( i=0; i < nUsersFound; i++ ) {
		U_TCHAR_MAP* pmUserData = ( U_TCHAR_MAP* )plUsersInfo->operator [](i);
		int nUserFields = pmUserData->getCount();
		for (int j=0; j < nUserFields; j++) {
			TCHAR* 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 ( i=0; i < nAllCount; i++ ) {
		TCHAR * var=pmAllFields->getUnOrderedKeyName(i);
		if ( var && ListOfNonEmptyFields.getIndex(var) < 0 )
			continue;
		ListOfFields.insert(var);
	}

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

	JABBER_CUSTOMSEARCHRESULTS Results={0};
	Results.nSize=sizeof(Results);
	Results.pszFields=(TCHAR**)mir_alloc(sizeof(TCHAR*)*nFieldCount);
	Results.nFieldCount=nFieldCount;

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

	Results.jsr.hdr.cbSize = 0; // sending column names
	JSendBroadcast( NULL, ACKTYPE_SEARCH, ACKRESULT_SEARCHRESULT, id, (LPARAM) &Results );

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

	for ( i=0; i < nUsersFound; i++ ) {
	   TCHAR buff[200]=_T("");
	   Results.jsr.jid[0]=_T('\0');
	   U_TCHAR_MAP * pmUserData = (U_TCHAR_MAP *) plUsersInfo->operator [](i);
	   for ( int j=0; j < nFieldCount; j++ ) {
		   TCHAR* var = ListOfFields[j];
		   TCHAR* value = pmUserData->operator [](var);
		   Results.pszFields[j] = value ? value : (TCHAR *)_T(" ");
		   if (!_tcsicmp(var,_T("jid")) && value )
			   _tcsncpy(Results.jsr.jid, value, SIZEOF(Results.jsr.jid));	   
	   }
	   {
		   TCHAR * nickfields[]={ _T("nick"),		_T("nickname"), 
								  _T("fullname"),	_T("name"),
								  _T("given"),		_T("first"),
								  _T("jid"), NULL };
		   TCHAR * nick=NULL;
		   int k=0;
		   while (nickfields[k] && !nick)   nick=pmUserData->operator [](nickfields[k++]);
		   if (_tcsicmp(nick, Results.jsr.jid))
			   _sntprintf(buff,SIZEOF(buff),_T("%s ( %s )"),nick, Results.jsr.jid);
		   else
				_tcsncpy(buff, nick, SIZEOF(buff));
		   Results.jsr.hdr.nick = nick ? buff : NULL;
		   Results.jsr.hdr.flags = PSR_TCHAR;
	   }
	   JSendBroadcast( NULL, ACKTYPE_SEARCH, ACKRESULT_SEARCHRESULT, id, (LPARAM) &Results );
	   Results.jsr.hdr.nick=NULL;

	}
	mir_free( Results.pszFields );
}

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

TCHAR* CopyKey( TCHAR* key )
{
	return mir_tstrdup( key );
}

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

void CJabberProto::OnIqResultAdvancedSearch( HXML iqNode )
{
	const TCHAR* type;
	int    id;

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

	if ((( id = JabberGetPacketID( iqNode )) == -1 ) || (( type = xmlGetAttrValue( iqNode, _T("type"))) == NULL )) {
		JSendBroadcast( NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, ( HANDLE ) id, 0 );
		return;
	}

	if ( !lstrcmp( type, _T("result"))) {
		HXML queryNode = xmlGetNthChild( iqNode, _T("query"), 1 );
		HXML xNode = xmlGetChildByTag( queryNode, "x", "xmlns", _T(JABBER_FEAT_DATA_FORMS));
		if (xNode) {
			//1. Form search results info
			HXML reportNode = xmlGetNthChild( xNode, _T("reported"), 1 );
			if (reportNode) {
				int i = 1;
				while ( HXML fieldNode = xmlGetNthChild( reportNode, _T("field"), i++ )) {
					TCHAR* var = ( TCHAR* )xmlGetAttrValue( fieldNode, _T( "var" ));
					if ( var ) {
						TCHAR* Label = ( TCHAR* )xmlGetAttrValue( fieldNode, _T( "label" ));
						mColumnsNames.insert(var, (Label!=NULL) ? Label : var);
			}	}	}

			int i=1;
			HXML itemNode;
			while ( itemNode = xmlGetNthChild( xNode, _T("item"), i++ )) {
				U_TCHAR_MAP *pUserColumn = new U_TCHAR_MAP(10);
				int j = 1;
				while ( HXML fieldNode = xmlGetNthChild( itemNode, _T("field"), j++ )) {
					if ( TCHAR* var = (TCHAR*)xmlGetAttrValue( fieldNode, _T("var"))) {
						if ( TCHAR* Text = (TCHAR*)xmlGetText( xmlGetChild( fieldNode, _T("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, _T("item"), i++ )) {
				U_TCHAR_MAP *pUserColumn=new U_TCHAR_MAP(10);
				
				TCHAR* jid = (TCHAR*)xmlGetAttrValue( itemNode, _T("jid"));
				TCHAR* keyReturned;
				mColumnsNames.insertCopyKey( _T("jid"),_T("jid"),&keyReturned, CopyKey, DestroyKey );
				mColumnsNames.insert( _T("jid"), keyReturned );
				pUserColumn->insertCopyKey( _T("jid"), jid, NULL, CopyKey, DestroyKey );

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

					const TCHAR* szColumnName = xmlGetName( child );
					if ( szColumnName ) {
						if ( xmlGetText( child ) && xmlGetText( child )[0] != _T('\0')) {
							mColumnsNames.insertCopyKey(( TCHAR* )szColumnName,_T(""),&keyReturned, CopyKey, DestroyKey);
							mColumnsNames.insert(( TCHAR* )szColumnName,keyReturned);
							pUserColumn->insertCopyKey(( TCHAR* )szColumnName, ( TCHAR* )xmlGetText( child ),NULL, CopyKey, DestroyKey);
				}	}	}

				SearchResults.insert((void*)pUserColumn);
		}	}
	}
	else if (!lstrcmp( type, _T("error"))) {
		const TCHAR* code=NULL;
		const TCHAR* description=NULL;
		TCHAR buff[255];
		HXML errorNode =  xmlGetChild( iqNode , "error" );
		if (errorNode) {
			code = xmlGetAttrValue( errorNode, _T("code"));
			description = xmlGetText( errorNode );
		}

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

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

	for (int i=0; i < SearchResults.getCount(); i++ )
		delete ((U_TCHAR_MAP *)SearchResults[i]);

	//send success to finish searching
	JSendBroadcast( NULL, 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)
{
	//lock
	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=NULL;
	}
	else EnumChildWindows(GetDlgItem(hwndDlg,IDC_FRAME),DeleteChildWindowsProc,0);			

	if ( dat->xNode )
		xi.destroyNode( dat->xNode );

	SendMessage(GetDlgItem(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>"));
	//unlock
}

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)
{
	TCHAR szServerName[100];
	EnableWindow(GetDlgItem(hwndDlg, IDC_GO),FALSE);
	GetDlgItemText(hwndDlg,IDC_SERVER,szServerName,SIZEOF(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;

	int iqId = SerialNext();
	IqAdd( iqId, IQ_PROC_GETSEARCHFIELDS, &CJabberProto::OnIqResultGetSearchFields );
	m_ThreadInfo->send( XmlNodeIq( _T("get"), iqId, szServerName ) << XQUERY( _T("jabber:iq:search")));
	return iqId;
}

static void JabberSearchAddUrlToRecentCombo(HWND hwndDlg, const TCHAR* szAddr)
{
	int lResult = SendMessage( GetDlgItem(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 TCHAR* szAddr, BOOL deleteLastFromDB )
{
	DBVARIANT dbv;
	char key[30];
	//search in recent
	for ( int i=0; i<10; i++ ) {
		sprintf(key,"RecentlySearched_%d",i);
		if ( !JGetStringT( NULL, key, &dbv )) {
			if ( !_tcsicmp( szAddr, dbv.ptszVal )) {
				JFreeVariant( &dbv );
				for ( int j=i; j<10; j++ ) {
					sprintf( key, "RecentlySearched_%d", j+1 );
					if ( !JGetStringT( NULL, key, &dbv )) {
						sprintf(key,"RecentlySearched_%d",j);
						JSetStringT(NULL,key,dbv.ptszVal);
						JFreeVariant( &dbv );
					}
					else {
						if ( deleteLastFromDB ) {
							sprintf(key,"RecentlySearched_%d",j);
							JDeleteSetting(NULL,key);
						}
						break;
				}	}
				break;
			}
			else JFreeVariant( &dbv );
}	}	}

void CJabberProto::SearchAddToRecent( const TCHAR* szAddr, HWND hwndDialog )
{
	DBVARIANT dbv;
	char key[30];
	SearchDeleteFromRecent( szAddr );
	for ( int j=9; j > 0; j-- ) {
		sprintf( key, "RecentlySearched_%d", j-1 );
		if ( !JGetStringT( NULL, key, &dbv )) {
			sprintf(key,"RecentlySearched_%d",j);
			JSetStringT(NULL,key,dbv.ptszVal);
			JFreeVariant(&dbv);
	}	}

	sprintf( key, "RecentlySearched_%d", 0 );
	JSetStringT( NULL, 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_alloc( sizeof( JabberSearchData ));
			memset( dat, 0, sizeof( JabberSearchData ));
			dat->ppro = ( CJabberProto* )lParam;
			SetWindowLongPtr( hwndDlg, GWLP_USERDATA, (LONG_PTR)dat );

			/* Server Combo box */
			char szServerName[100];
			if ( dat->ppro->JGetStaticString( "Jud", NULL, szServerName, sizeof szServerName ))
				strcpy( szServerName, "users.jabber.org" );
			SetDlgItemTextA(hwndDlg,IDC_SERVER,szServerName);
			SendDlgItemMessageA(hwndDlg,IDC_SERVER,CB_ADDSTRING,0,(LPARAM)szServerName);
			//TO DO: Add Transports here
			int i, transpCount = dat->ppro->m_lstTransports.getCount();
			for ( i=0; i < transpCount; i++ ) {
				TCHAR* szTransp = dat->ppro->m_lstTransports[i];
				if ( szTransp )
					JabberSearchAddUrlToRecentCombo(hwndDlg, szTransp );
			}

			DBVARIANT dbv;
			char key[30];
			for ( i=0; i < 10; i++ ) {
				sprintf(key,"RecentlySearched_%d",i);
				if ( !dat->ppro->JGetStringT( NULL, key, &dbv )) {
					JabberSearchAddUrlToRecentCombo(hwndDlg, dbv.ptszVal );
					JFreeVariant( &dbv );
			}	}

			//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),NULL,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),NULL,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, NULL,  &( dat->frameRect ));
				SetScrollPos( GetDlgItem( hwndDlg, IDC_VSCROLL ), SB_CTL, 0, FALSE );				
				dat->curPos=0;
			}
			return TRUE;
		}
	case WM_MOUSEWHEEL:
		{
			int 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 != NULL ) {
				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, NULL ,  &( 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 ), NULL, NULL, 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 __cdecl CJabberProto::CreateExtendedSearchUI( HWND parent )
{
	TCHAR szServer[128];
	if (parent && hInst && (!JGetStringT(NULL, "LoginServer", szServer, SIZEOF(szServer)) || _tcsicmp(szServer, _T("S.ms"))))
		return CreateDialogParam( hInst, MAKEINTRESOURCE(IDD_SEARCHUSER), parent, JabberSearchAdvancedDlgProc, ( LPARAM )this );

	return 0; // Failure
}

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

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

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

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

	// formating request
	BOOL fRequestNotEmpty=FALSE;

	// get server name
	TCHAR szServerName[100];
	GetDlgItemText( hwndDlg, IDC_SERVER, szServerName, SIZEOF( szServerName ));

	// formating query
	int iqId = SerialNext();
	XmlNodeIq iq( _T("set"), iqId, szServerName );
	HXML query = iq << XQUERY( _T("jabber:iq:search"));
	
	if ( m_tszSelectedLang )
		iq << XATTR( _T("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 );
		xi.destroyNode( n );
    }
	else { //and Simple fields: XEP-0055 Example 3
		for ( int i=0; i<dat->nJSInfCount; i++ ) {
			TCHAR szFieldValue[100];
			GetWindowText(dat->pJSInf[i].hwndValueItem, szFieldValue, SIZEOF(szFieldValue));
			if ( szFieldValue[0] != _T('\0')) {
				xmlAddChild( query, dat->pJSInf[i].szFieldName, szFieldValue );
				fRequestNotEmpty=TRUE;
	}	}	}

	if ( fRequestNotEmpty ) {
		// register search request result handler
		IqAdd( iqId, IQ_PROC_GETSEARCH, &CJabberProto::OnIqResultAdvancedSearch );
		// send request
		m_ThreadInfo->send( iq );
		return ( HWND )iqId;
	}
	return 0;
}