/*

Jabber Protocol Plugin for Miranda NG

Copyright (c) 2002-04  Santithorn Bunchua
Copyright (c) 2005-12  George Hazan
Copyright (c) 2007     Maxim Mluhov
Copyright (�) 2012-16 Miranda NG project

XEP-0146 support for Miranda IM

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

CJabberAdhocSession::CJabberAdhocSession(CJabberProto* global)
{
	m_pNext = NULL;
	m_pUserData = NULL;
	m_bAutofreeUserData = FALSE;
	m_dwStage = 0;
	ppro = global;
	m_szSessionId.Format(L"%u%u", ppro->SerialNext(), GetTickCount());
	m_dwStartTime = GetTickCount();
}

BOOL CJabberProto::IsRcRequestAllowedByACL(CJabberIqInfo *pInfo)
{
	if (!pInfo || !pInfo->GetFrom())
		return FALSE;

	return IsMyOwnJID(pInfo->GetFrom());
}

BOOL CJabberProto::HandleAdhocCommandRequest(HXML iqNode, CJabberIqInfo *pInfo)
{
	if (!pInfo->GetChildNode())
		return TRUE;

	if (!m_options.EnableRemoteControl || !IsRcRequestAllowedByACL(pInfo)) {
		// FIXME: send error and return
		return TRUE;
	}

	const TCHAR *szNode = XmlGetAttrValue(pInfo->GetChildNode(), L"node");
	if (!szNode)
		return TRUE;

	m_adhocManager.HandleCommandRequest(iqNode, pInfo, (TCHAR*)szNode);
	return TRUE;
}

BOOL CJabberAdhocManager::HandleItemsRequest(HXML, CJabberIqInfo *pInfo, const TCHAR *szNode)
{
	if (!szNode || !m_pProto->m_options.EnableRemoteControl || !m_pProto->IsRcRequestAllowedByACL(pInfo))
		return FALSE;

	if (!mir_tstrcmp(szNode, JABBER_FEAT_COMMANDS)) {
		XmlNodeIq iq(L"result", pInfo);
		HXML resultQuery = iq << XQUERY(JABBER_FEAT_DISCO_ITEMS) << XATTR(L"node", JABBER_FEAT_COMMANDS);
		{
			mir_cslock lck(m_cs);

			CJabberAdhocNode* pNode = GetFirstNode();
			while (pNode) {
				TCHAR *szJid = pNode->GetJid();
				if (!szJid)
					szJid = m_pProto->m_ThreadInfo->fullJID;

				resultQuery << XCHILD(L"item") << XATTR(L"jid", szJid)
					<< XATTR(L"node", pNode->GetNode()) << XATTR(L"name", pNode->GetName());

				pNode = pNode->GetNext();
			}
		}

		m_pProto->m_ThreadInfo->send(iq);
		return TRUE;
	}
	return FALSE;
}

BOOL CJabberAdhocManager::HandleInfoRequest(HXML, CJabberIqInfo *pInfo, const TCHAR *szNode)
{
	if (!szNode || !m_pProto->m_options.EnableRemoteControl || !m_pProto->IsRcRequestAllowedByACL(pInfo))
		return FALSE;

	// FIXME: same code twice
	if (!mir_tstrcmp(szNode, JABBER_FEAT_COMMANDS)) {
		XmlNodeIq iq(L"result", pInfo);
		HXML resultQuery = iq << XQUERY(JABBER_FEAT_DISCO_INFO) << XATTR(L"node", JABBER_FEAT_COMMANDS);
		resultQuery << XCHILD(L"identity") << XATTR(L"name", L"Ad-hoc commands")
			<< XATTR(L"category", L"automation") << XATTR(L"type", L"command-node");

		resultQuery << XCHILD(L"feature") << XATTR(L"var", JABBER_FEAT_COMMANDS);
		resultQuery << XCHILD(L"feature") << XATTR(L"var", JABBER_FEAT_DATA_FORMS);
		resultQuery << XCHILD(L"feature") << XATTR(L"var", JABBER_FEAT_DISCO_INFO);
		resultQuery << XCHILD(L"feature") << XATTR(L"var", JABBER_FEAT_DISCO_ITEMS);

		m_pProto->m_ThreadInfo->send(iq);
		return TRUE;
	}

	mir_cslockfull lck(m_cs);
	CJabberAdhocNode *pNode = FindNode(szNode);
	if (pNode == NULL)
		return FALSE;

	XmlNodeIq iq(L"result", pInfo);
	HXML resultQuery = iq << XQUERY(JABBER_FEAT_DISCO_INFO) << XATTR(L"node", JABBER_FEAT_DISCO_INFO);
	resultQuery << XCHILD(L"identity") << XATTR(L"name", pNode->GetName())
		<< XATTR(L"category", L"automation") << XATTR(L"type", L"command-node");

	resultQuery << XCHILD(L"feature") << XATTR(L"var", JABBER_FEAT_COMMANDS);
	resultQuery << XCHILD(L"feature") << XATTR(L"var", JABBER_FEAT_DATA_FORMS);
	resultQuery << XCHILD(L"feature") << XATTR(L"var", JABBER_FEAT_DISCO_INFO);
	lck.unlock();
	m_pProto->m_ThreadInfo->send(iq);
	return TRUE;
}

BOOL CJabberAdhocManager::HandleCommandRequest(HXML iqNode, CJabberIqInfo *pInfo, const TCHAR *szNode)
{
	// ATTN: ACL and db settings checked in calling function

	HXML commandNode = pInfo->GetChildNode();

	mir_cslockfull lck(m_cs);
	CJabberAdhocNode* pNode = FindNode(szNode);
	if (!pNode) {
		lck.unlock();

		m_pProto->m_ThreadInfo->send(
			XmlNodeIq(L"error", pInfo)
				<< XCHILD(L"error") << XATTR(L"type", L"cancel")
					<< XCHILDNS(L"item-not-found", L"urn:ietf:params:xml:ns:xmpp-stanzas"));

		return FALSE;
	}

	const TCHAR *szSessionId = XmlGetAttrValue(commandNode, L"sessionid");

	CJabberAdhocSession* pSession = NULL;
	if (szSessionId) {
		pSession = FindSession(szSessionId);
		if (!pSession) {
			lck.unlock();

			XmlNodeIq iq(L"error", pInfo);
			HXML errorNode = iq << XCHILD(L"error") << XATTR(L"type", L"modify");
			errorNode << XCHILDNS(L"bad-request", L"urn:ietf:params:xml:ns:xmpp-stanzas");
			errorNode << XCHILDNS(L"bad-sessionid", JABBER_FEAT_COMMANDS);
			m_pProto->m_ThreadInfo->send(iq);
			return FALSE;
		}
	}
	else
		pSession = AddNewSession();

	if (!pSession) {
		lck.unlock();

		m_pProto->m_ThreadInfo->send(
			XmlNodeIq(L"error", pInfo)
				<< XCHILD(L"error") << XATTR(L"type", L"cancel")
					<< XCHILDNS(L"forbidden", L"urn:ietf:params:xml:ns:xmpp-stanzas"));

		return FALSE;
	}

	// session id and node exits here, call handler

	int nResultCode = pNode->CallHandler(iqNode, pInfo, pSession);

	if (nResultCode == JABBER_ADHOC_HANDLER_STATUS_COMPLETED) {
		m_pProto->m_ThreadInfo->send(
			XmlNodeIq(L"result", pInfo)
				<< XCHILDNS(L"command", JABBER_FEAT_COMMANDS) << XATTR(L"node", szNode)
					<< XATTR(L"sessionid", pSession->GetSessionId()) << XATTR(L"status", L"completed")
				<< XCHILD(L"note", TranslateT("Command completed successfully")) << XATTR(L"type", L"info"));

		RemoveSession(pSession);
		pSession = NULL;
	}
	else if (nResultCode == JABBER_ADHOC_HANDLER_STATUS_CANCEL) {
		m_pProto->m_ThreadInfo->send(
			XmlNodeIq(L"result", pInfo)
				<< XCHILDNS(L"command", JABBER_FEAT_COMMANDS) << XATTR(L"node", szNode)
					<< XATTR(L"sessionid", pSession->GetSessionId()) << XATTR(L"status", L"canceled")
				<< XCHILD(L"note", TranslateT("Error occurred during processing command")) << XATTR(L"type", L"error"));

		RemoveSession(pSession);
		pSession = NULL;
	}
	else if (nResultCode == JABBER_ADHOC_HANDLER_STATUS_REMOVE_SESSION) {
		RemoveSession(pSession);
		pSession = NULL;
	}

	return TRUE;
}

BOOL CJabberAdhocManager::FillDefaultNodes()
{
	AddNode(NULL, JABBER_FEAT_RC_SET_STATUS,       TranslateT("Set status"),              &CJabberProto::AdhocSetStatusHandler);
	AddNode(NULL, JABBER_FEAT_RC_SET_OPTIONS,      TranslateT("Set options"),             &CJabberProto::AdhocOptionsHandler);
	AddNode(NULL, JABBER_FEAT_RC_FORWARD,          TranslateT("Forward unread messages"), &CJabberProto::AdhocForwardHandler);
	AddNode(NULL, JABBER_FEAT_RC_LEAVE_GROUPCHATS, TranslateT("Leave group chats"),        &CJabberProto::AdhocLeaveGroupchatsHandler);
	AddNode(NULL, JABBER_FEAT_RC_WS_LOCK,          TranslateT("Lock workstation"),        &CJabberProto::AdhocLockWSHandler);
	AddNode(NULL, JABBER_FEAT_RC_QUIT_MIRANDA,     TranslateT("Quit Miranda NG"),         &CJabberProto::AdhocQuitMirandaHandler);
	return TRUE;
}


static char *StatusModeToDbSetting(int status,const char *suffix)
{
	char *prefix;
	static char str[64];

	switch(status) {
		case ID_STATUS_AWAY:       prefix="Away";	    break;
		case ID_STATUS_NA:         prefix="Na";	    break;
		case ID_STATUS_DND:        prefix="Dnd";      break;
		case ID_STATUS_OCCUPIED:   prefix="Occupied"; break;
		case ID_STATUS_FREECHAT:   prefix="FreeChat"; break;
		case ID_STATUS_ONLINE:     prefix="On";       break;
		case ID_STATUS_OFFLINE:    prefix="Off";      break;
		case ID_STATUS_INVISIBLE:  prefix="Inv";      break;
		case ID_STATUS_ONTHEPHONE: prefix="Otp";      break;
		case ID_STATUS_OUTTOLUNCH: prefix="Otl";      break;
		case ID_STATUS_IDLE:       prefix="Idl";      break;
		default: return NULL;
	}
	mir_strcpy(str,prefix); mir_strcat(str,suffix);
	return str;
}

int CJabberProto::AdhocSetStatusHandler(HXML, CJabberIqInfo *pInfo, CJabberAdhocSession* pSession)
{
	if (pSession->GetStage() == 0) {
		// first form
		pSession->SetStage(1);

		XmlNodeIq iq(L"result", pInfo);
		HXML xNode = iq
			<< XCHILDNS(L"command", JABBER_FEAT_COMMANDS) << XATTR(L"node", JABBER_FEAT_RC_SET_STATUS)
				<< XATTR(L"sessionid", pSession->GetSessionId()) << XATTR(L"status", L"executing")
			<< XCHILDNS(L"x", JABBER_FEAT_DATA_FORMS) << XATTR(L"type", L"form");

		xNode << XCHILD(L"title", TranslateT("Change Status"));
		xNode << XCHILD(L"instructions", TranslateT("Choose the status and status message"));

		xNode << XCHILD(L"field") << XATTR(L"type", L"hidden") << XATTR(L"var", L"FORM_TYPE")
			<< XATTR(L"value", JABBER_FEAT_RC);

		HXML fieldNode = xNode << XCHILD(L"field") << XATTR(L"label", TranslateT("Status"))
			<< XATTR(L"type", L"list-single") << XATTR(L"var", L"status");

		fieldNode << XCHILD(L"required");

		int status = CallService(MS_CLIST_GETSTATUSMODE, 0, 0);
		switch (status) {
		case ID_STATUS_INVISIBLE:
			fieldNode << XCHILD(L"value", L"invisible");
			break;
		case ID_STATUS_AWAY:
		case ID_STATUS_ONTHEPHONE:
		case ID_STATUS_OUTTOLUNCH:
			fieldNode << XCHILD(L"value", L"away");
			break;
		case ID_STATUS_NA:
			fieldNode << XCHILD(L"value", L"xa");
			break;
		case ID_STATUS_DND:
		case ID_STATUS_OCCUPIED:
			fieldNode << XCHILD(L"value", L"dnd");
			break;
		case ID_STATUS_FREECHAT:
			fieldNode << XCHILD(L"value", L"chat");
			break;
		case ID_STATUS_ONLINE:
		default:
			fieldNode << XCHILD(L"value", L"online");
			break;
		}

		fieldNode << XCHILD(L"option") << XATTR(L"label", TranslateT("Free for chat")) << XCHILD(L"value", L"chat");
		fieldNode << XCHILD(L"option") << XATTR(L"label", TranslateT("Online")) << XCHILD(L"value", L"online");
		fieldNode << XCHILD(L"option") << XATTR(L"label", TranslateT("Away")) << XCHILD(L"value", L"away");
		fieldNode << XCHILD(L"option") << XATTR(L"label", TranslateT("Extended away (Not available)")) << XCHILD(L"value", L"xa");
		fieldNode << XCHILD(L"option") << XATTR(L"label", TranslateT("Do not disturb")) << XCHILD(L"value", L"dnd");
		fieldNode << XCHILD(L"option") << XATTR(L"label", TranslateT("Invisible")) << XCHILD(L"value", L"invisible");
		fieldNode << XCHILD(L"option") << XATTR(L"label", TranslateT("Offline")) << XCHILD(L"value", L"offline");

		// priority
		TCHAR szPriority[ 256 ];
		mir_sntprintf(szPriority, L"%d", (int)getDword("Priority", 5));
		xNode << XCHILD(L"field") << XATTR(L"label", TranslateT("Priority")) << XATTR(L"type", L"text-single")
			<< XATTR(L"var", L"status-priority") << XCHILD(L"value", szPriority);

		// status message text
		xNode << XCHILD(L"field") << XATTR(L"label", TranslateT("Status message"))
			<< XATTR(L"type", L"text-multi") << XATTR(L"var", L"status-message");

		// global status
		fieldNode = xNode << XCHILD(L"field") << XATTR(L"label", TranslateT("Change global status"))
			<< XATTR(L"type", L"boolean") << XATTR(L"var", L"status-global");

		ptrT tszStatusMsg((TCHAR*)CallService(MS_AWAYMSG_GETSTATUSMSGT, status, 0));
		if (tszStatusMsg)
			fieldNode << XCHILD(L"value", tszStatusMsg);

		m_ThreadInfo->send(iq);
		return JABBER_ADHOC_HANDLER_STATUS_EXECUTING;
	}
	
	if (pSession->GetStage() == 1) {
		// result form here
		HXML commandNode = pInfo->GetChildNode();
		HXML xNode = XmlGetChildByTag(commandNode, "x", "xmlns", JABBER_FEAT_DATA_FORMS);
		if (!xNode)
			return JABBER_ADHOC_HANDLER_STATUS_CANCEL;

		HXML fieldNode = XmlGetChildByTag(xNode, "field", "var", L"status"), valueNode;
		if (!fieldNode)
			return JABBER_ADHOC_HANDLER_STATUS_CANCEL;

		LPCTSTR ptszValue = XmlGetText( XmlGetChild(fieldNode , "value"));
		if (ptszValue == NULL)
			return JABBER_ADHOC_HANDLER_STATUS_CANCEL;

		int status;
		if (!mir_tstrcmp(ptszValue, L"away")) status = ID_STATUS_AWAY;
		else if (!mir_tstrcmp(ptszValue, L"xa")) status = ID_STATUS_NA;
		else if (!mir_tstrcmp(ptszValue, L"dnd")) status = ID_STATUS_DND;
		else if (!mir_tstrcmp(ptszValue, L"chat")) status = ID_STATUS_FREECHAT;
		else if (!mir_tstrcmp(ptszValue, L"online")) status = ID_STATUS_ONLINE;
		else if (!mir_tstrcmp(ptszValue, L"invisible")) status = ID_STATUS_INVISIBLE;
		else if (!mir_tstrcmp(ptszValue, L"offline")) status = ID_STATUS_OFFLINE;
		else
			return JABBER_ADHOC_HANDLER_STATUS_CANCEL;

		int priority = -9999;

		fieldNode = XmlGetChildByTag(xNode, "field", "var", L"status-priority");
		if (fieldNode && (valueNode = XmlGetChild(fieldNode , "value")))
			if (ptszValue = XmlGetText(valueNode))
				priority = _ttoi(ptszValue);

		if (priority >= -128 && priority <= 127)
			setDword("Priority", priority);

		const TCHAR *szStatusMessage = NULL;
		fieldNode = XmlGetChildByTag(xNode, "field", "var", L"status-message");
		if (fieldNode && (valueNode = XmlGetChild(fieldNode , "value")))
			szStatusMessage = XmlGetText(valueNode);

		// skip f...ng away dialog
		int nNoDlg = db_get_b(NULL, "SRAway", StatusModeToDbSetting(status, "NoDlg"), 0);
		db_set_b(NULL, "SRAway", StatusModeToDbSetting(status, "NoDlg"), 1);

		db_set_ts(NULL, "SRAway", StatusModeToDbSetting(status, "Msg"), szStatusMessage ? szStatusMessage : L"");

		fieldNode = XmlGetChildByTag(xNode, "field", "var", L"status-global");
		if (fieldNode && (valueNode = XmlGetChild(fieldNode , "value"))) {
			if ((ptszValue = XmlGetText(valueNode)) != NULL && _ttoi(ptszValue))
				CallService(MS_CLIST_SETSTATUSMODE, status, NULL);
			else
				CallProtoService(m_szModuleName, PS_SETSTATUS, status, NULL);
		}
		SetAwayMsg(status, szStatusMessage);

		// return NoDlg setting
		db_set_b(NULL, "SRAway", StatusModeToDbSetting(status, "NoDlg"), (BYTE)nNoDlg);

		return JABBER_ADHOC_HANDLER_STATUS_COMPLETED;
	}
	return JABBER_ADHOC_HANDLER_STATUS_CANCEL;
}

int CJabberProto::AdhocOptionsHandler(HXML, CJabberIqInfo *pInfo, CJabberAdhocSession* pSession)
{
	if (pSession->GetStage() == 0) {
		// first form
		pSession->SetStage(1);

		XmlNodeIq iq(L"result", pInfo);
		HXML xNode = iq
			<< XCHILDNS(L"command", JABBER_FEAT_COMMANDS) << XATTR(L"node", JABBER_FEAT_RC_SET_OPTIONS)
				<< XATTR(L"sessionid", pSession->GetSessionId()) << XATTR(L"status", L"executing")
			<< XCHILDNS(L"x", JABBER_FEAT_DATA_FORMS) << XATTR(L"type", L"form");

		xNode << XCHILD(L"title", TranslateT("Set Options"));
		xNode << XCHILD(L"instructions", TranslateT("Set the desired options"));

		xNode << XCHILD(L"field") << XATTR(L"type", L"hidden") << XATTR(L"var", L"FORM_TYPE")
			<< XATTR(L"value", JABBER_FEAT_RC);

		// Automatically Accept File Transfers
		TCHAR szTmpBuff[ 1024 ];
		mir_sntprintf(szTmpBuff, L"%d", db_get_b(NULL, "SRFile", "AutoAccept", 0));
		xNode << XCHILD(L"field") << XATTR(L"label", TranslateT("Automatically Accept File Transfers"))
			<< XATTR(L"type", L"boolean") << XATTR(L"var", L"auto-files") << XCHILD(L"value", szTmpBuff);

		// Use sounds
		mir_sntprintf(szTmpBuff, L"%d", db_get_b(NULL, "Skin", "UseSound", 0));
		xNode << XCHILD(L"field") << XATTR(L"label", TranslateT("Play sounds"))
			<< XATTR(L"type", L"boolean") << XATTR(L"var", L"sounds") << XCHILD(L"value", szTmpBuff);

		// Disable remote controlling
		xNode << XCHILD(L"field") << XATTR(L"label", TranslateT("Disable remote controlling (check twice what you are doing)"))
			<< XATTR(L"type", L"boolean") << XATTR(L"var", L"enable-rc") << XCHILD(L"value", L"0");

		m_ThreadInfo->send(iq);
		return JABBER_ADHOC_HANDLER_STATUS_EXECUTING;
	}

	if (pSession->GetStage() == 1) {
		// result form here
		HXML commandNode = pInfo->GetChildNode();
		HXML xNode = XmlGetChildByTag(commandNode, "x", "xmlns", JABBER_FEAT_DATA_FORMS);
		if (!xNode)
			return JABBER_ADHOC_HANDLER_STATUS_CANCEL;

		// Automatically Accept File Transfers
		HXML fieldNode = XmlGetChildByTag(xNode, "field", "var", L"auto-files"), valueNode;
		if (fieldNode && (valueNode = XmlGetChild(fieldNode , "value")))
			if (XmlGetText(valueNode))
				db_set_b(NULL, "SRFile", "AutoAccept", (BYTE)_ttoi(XmlGetText(valueNode)));

		// Use sounds
		fieldNode = XmlGetChildByTag(xNode, "field", "var", L"sounds");
		if (fieldNode && (valueNode = XmlGetChild(fieldNode , "value")))
			if (XmlGetText(valueNode))
				db_set_b(NULL, "Skin", "UseSound", (BYTE)_ttoi(XmlGetText(valueNode)));

		// Disable remote controlling
		fieldNode = XmlGetChildByTag(xNode, "field", "var", L"enable-rc");
		if (fieldNode && (valueNode = XmlGetChild(fieldNode , "value")))
			if (XmlGetText(valueNode) && _ttoi(XmlGetText(valueNode)))
				m_options.EnableRemoteControl = 0;

		return JABBER_ADHOC_HANDLER_STATUS_COMPLETED;
	}
	return JABBER_ADHOC_HANDLER_STATUS_CANCEL;
}

int CJabberProto::RcGetUnreadEventsCount()
{
	int nEventsSent = 0;
	for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) {
		ptrT jid( getTStringA(hContact, "jid"));
		if (jid == NULL) continue;

		for (MEVENT hDbEvent = db_event_firstUnread(hContact); hDbEvent; hDbEvent = db_event_next(hContact, hDbEvent)) {
			DBEVENTINFO dbei = { sizeof(dbei) };
			dbei.cbBlob = db_event_getBlobSize(hDbEvent);
			if (dbei.cbBlob == -1)
				continue;

			dbei.pBlob = (PBYTE)mir_alloc(dbei.cbBlob + 1);
			int nGetTextResult = db_event_get(hDbEvent, &dbei);
			if (!nGetTextResult && dbei.eventType == EVENTTYPE_MESSAGE && !(dbei.flags & DBEF_READ) && !(dbei.flags & DBEF_SENT)) {
				TCHAR *szEventText = DbGetEventTextT(&dbei, CP_ACP);
				if (szEventText) {
					nEventsSent++;
					mir_free(szEventText);
				}
			}
			mir_free(dbei.pBlob);
		}
	}
	return nEventsSent;
}

int CJabberProto::AdhocForwardHandler(HXML, CJabberIqInfo *pInfo, CJabberAdhocSession* pSession)
{
	TCHAR szMsg[ 1024 ];
	if (pSession->GetStage() == 0) {
		int nUnreadEvents = RcGetUnreadEventsCount();
		if (!nUnreadEvents) {
			mir_sntprintf(szMsg, TranslateT("There is no messages to forward"));

			m_ThreadInfo->send(
				XmlNodeIq(L"result", pInfo)
					<< XCHILDNS(L"command", JABBER_FEAT_COMMANDS) << XATTR(L"node", JABBER_FEAT_RC_FORWARD)
						<< XATTR(L"sessionid", pSession->GetSessionId()) << XATTR(L"status", L"completed")
					<< XCHILD(L"note", szMsg) << XATTR(L"type", L"info"));

			return JABBER_ADHOC_HANDLER_STATUS_REMOVE_SESSION;
		}

		// first form
		pSession->SetStage(1);

		XmlNodeIq iq(L"result", pInfo);
		HXML xNode = iq
			<< XCHILDNS(L"command", JABBER_FEAT_COMMANDS) << XATTR(L"node", JABBER_FEAT_RC_FORWARD)
				<< XATTR(L"sessionid", pSession->GetSessionId()) << XATTR(L"status", L"executing")
			<< XCHILDNS(L"x", JABBER_FEAT_DATA_FORMS) << XATTR(L"type", L"form");

		xNode << XCHILD(L"title", TranslateT("Forward options"));

		mir_sntprintf(szMsg, TranslateT("%d message(s) to be forwarded"), nUnreadEvents);
		xNode << XCHILD(L"instructions", szMsg);

		xNode << XCHILD(L"field") << XATTR(L"type", L"hidden") << XATTR(L"var", L"FORM_TYPE")
			<< XCHILD(L"value", JABBER_FEAT_RC);

		// remove clist events
		xNode << XCHILD(L"field") << XATTR(L"label", TranslateT("Mark messages as read")) << XATTR(L"type", L"boolean")
			<< XATTR(L"var", L"remove-clist-events") << XCHILD(L"value",
			m_options.RcMarkMessagesAsRead == 1 ? L"1" : L"0");

		m_ThreadInfo->send(iq);
		return JABBER_ADHOC_HANDLER_STATUS_EXECUTING;
	}

	if (pSession->GetStage() == 1) {
		// result form here
		HXML commandNode = pInfo->GetChildNode();
		HXML xNode = XmlGetChildByTag(commandNode, "x", "xmlns", JABBER_FEAT_DATA_FORMS);
		if (!xNode)
			return JABBER_ADHOC_HANDLER_STATUS_CANCEL;

		BOOL bRemoveCListEvents = TRUE;

		// remove clist events
		HXML fieldNode = XmlGetChildByTag(xNode,"field", "var", L"remove-clist-events"), valueNode;
		if (fieldNode && (valueNode = XmlGetChild(fieldNode , "value")))
			if (XmlGetText(valueNode) && !_ttoi(XmlGetText(valueNode)))
				bRemoveCListEvents = FALSE;

		m_options.RcMarkMessagesAsRead = bRemoveCListEvents ? 1 : 0;

		int nEventsSent = 0;
		for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) {
			ptrT tszJid( getTStringA(hContact, "jid"));
			if (tszJid == NULL)
				continue;
				
			for (MEVENT hDbEvent = db_event_firstUnread(hContact); hDbEvent; hDbEvent = db_event_next(hContact, hDbEvent)) {
				DBEVENTINFO dbei = { sizeof(dbei) };
				dbei.cbBlob = db_event_getBlobSize(hDbEvent);
				if (dbei.cbBlob == -1)
					continue;

				mir_ptr<BYTE> pEventBuf((PBYTE)mir_alloc(dbei.cbBlob + 1));
				dbei.pBlob = pEventBuf;
				if (db_event_get(hDbEvent, &dbei))
					continue;

				if (dbei.eventType != EVENTTYPE_MESSAGE || (dbei.flags & (DBEF_READ | DBEF_SENT)))
					continue;

				ptrT szEventText( DbGetEventTextT(&dbei, CP_ACP));
				if (szEventText == NULL)
					continue;

				XmlNode msg(L"message");
				msg << XATTR(L"to", pInfo->GetFrom()) << XATTRID(SerialNext())
					<< XCHILD(L"body", szEventText);

				HXML addressesNode = msg << XCHILDNS(L"addresses", JABBER_FEAT_EXT_ADDRESSING);
				TCHAR szOFrom[JABBER_MAX_JID_LEN];

				size_t cbBlob = mir_strlen((LPSTR)dbei.pBlob)+1;
				if (cbBlob < dbei.cbBlob) { // rest of message contains a sender's resource
					ptrT szOResource( mir_utf8decodeT((LPSTR)dbei.pBlob + cbBlob+1));
					mir_sntprintf(szOFrom, L"%s/%s", tszJid, szOResource);
				} else
					_tcsncpy_s(szOFrom, tszJid, _TRUNCATE);

				addressesNode << XCHILD(L"address") << XATTR(L"type", L"ofrom") << XATTR(L"jid", szOFrom);
				addressesNode << XCHILD(L"address") << XATTR(L"type", L"oto") << XATTR(L"jid", m_ThreadInfo->fullJID);

				time_t ltime = (time_t)dbei.timestamp;
				struct tm *gmt = gmtime(&ltime);
				TCHAR stime[512];
				mir_sntprintf(stime, L"%.4i-%.2i-%.2iT%.2i:%.2i:%.2iZ", gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday,
					gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
				msg << XCHILDNS(L"delay", L"urn:xmpp:delay") << XATTR(L"stamp", stime);

				m_ThreadInfo->send(msg);

				nEventsSent++;

				db_event_markRead(hContact, hDbEvent);
				if (bRemoveCListEvents)
					pcli->pfnRemoveEvent(hContact, hDbEvent);
			}
		}

		mir_sntprintf(szMsg, TranslateT("%d message(s) forwarded"), nEventsSent);

		m_ThreadInfo->send(
			XmlNodeIq(L"result", pInfo)
				<< XCHILDNS(L"command", JABBER_FEAT_COMMANDS) << XATTR(L"node", JABBER_FEAT_RC_FORWARD)
					<< XATTR(L"sessionid", pSession->GetSessionId()) << XATTR(L"status", L"completed")
				<< XCHILD(L"note", szMsg) << XATTR(L"type", L"info"));

		return JABBER_ADHOC_HANDLER_STATUS_REMOVE_SESSION;
	}

	return JABBER_ADHOC_HANDLER_STATUS_CANCEL;
}

int CJabberProto::AdhocLockWSHandler(HXML, CJabberIqInfo *pInfo, CJabberAdhocSession* pSession)
{
	BOOL bOk = LockWorkStation();

	TCHAR szMsg[ 1024 ];
	if (bOk)
		mir_sntprintf(szMsg, TranslateT("Workstation successfully locked"));
	else
		mir_sntprintf(szMsg, TranslateT("Error %d occurred during workstation lock"), GetLastError());

	m_ThreadInfo->send(
		XmlNodeIq(L"result", pInfo)
			<< XCHILDNS(L"command", JABBER_FEAT_COMMANDS) << XATTR(L"node", JABBER_FEAT_RC_WS_LOCK)
				<< XATTR(L"sessionid", pSession->GetSessionId()) << XATTR(L"status", L"completed")
			<< XCHILD(L"note", szMsg) << XATTR(L"type", bOk ? L"info" : L"error"));

	return JABBER_ADHOC_HANDLER_STATUS_REMOVE_SESSION;
}

static void __stdcall JabberQuitMirandaIMThread(void*)
{
	CallService("CloseAction", 0, 0);
}

int CJabberProto::AdhocQuitMirandaHandler(HXML, CJabberIqInfo *pInfo, CJabberAdhocSession* pSession)
{
	if (pSession->GetStage() == 0) {
		// first form
		pSession->SetStage(1);

		XmlNodeIq iq(L"result", pInfo);
		HXML xNode = iq
			<< XCHILDNS(L"command", JABBER_FEAT_COMMANDS) << XATTR(L"node", JABBER_FEAT_RC_QUIT_MIRANDA)
				<< XATTR(L"sessionid", pSession->GetSessionId()) << XATTR(L"status", L"executing")
			<< XCHILDNS(L"x", JABBER_FEAT_DATA_FORMS) << XATTR(L"type", L"form");

		xNode << XCHILD(L"title", TranslateT("Confirmation needed"));
		xNode << XCHILD(L"instructions", TranslateT("Please confirm Miranda NG shutdown"));

		xNode << XCHILD(L"field") << XATTR(L"type", L"hidden") << XATTR(L"var", L"FORM_TYPE")
			<< XCHILD(L"value", JABBER_FEAT_RC);

		// I Agree checkbox
		xNode << XCHILD(L"field") << XATTR(L"label", L"I agree") << XATTR(L"type", L"boolean")
			<< XATTR(L"var", L"allow-shutdown") << XCHILD(L"value", L"0");

		m_ThreadInfo->send(iq);
		return JABBER_ADHOC_HANDLER_STATUS_EXECUTING;
	}

	if (pSession->GetStage() == 1) {
		// result form here
		HXML commandNode = pInfo->GetChildNode();
		HXML xNode = XmlGetChildByTag(commandNode, "x", "xmlns", JABBER_FEAT_DATA_FORMS);
		if (!xNode)
			return JABBER_ADHOC_HANDLER_STATUS_CANCEL;

		HXML fieldNode, valueNode;

		// I Agree checkbox
		fieldNode = XmlGetChildByTag(xNode,"field", "var", L"allow-shutdown");
		if (fieldNode && (valueNode = XmlGetChild(fieldNode , "value")))
			if (XmlGetText(valueNode) && _ttoi(XmlGetText(valueNode)))
				CallFunctionAsync(JabberQuitMirandaIMThread, 0);

		return JABBER_ADHOC_HANDLER_STATUS_COMPLETED;
	}
	return JABBER_ADHOC_HANDLER_STATUS_CANCEL;
}

int CJabberProto::AdhocLeaveGroupchatsHandler(HXML, CJabberIqInfo *pInfo, CJabberAdhocSession* pSession)
{
	int i = 0;
	if (pSession->GetStage() == 0) {
		// first form
		int nChatsCount = 0;
		{
			mir_cslock lck(m_csLists);
			LISTFOREACH_NODEF(i, this, LIST_CHATROOM)
			{
				JABBER_LIST_ITEM *item = ListGetItemPtrFromIndex(i);
				if (item != NULL)
					nChatsCount++;
			}
		}

		if (!nChatsCount) {
			TCHAR szMsg[ 1024 ];
			mir_sntprintf(szMsg, TranslateT("There is no group chats to leave"));

			m_ThreadInfo->send(
				XmlNodeIq(L"result", pInfo)
					<< XCHILDNS(L"command", JABBER_FEAT_COMMANDS) << XATTR(L"node", JABBER_FEAT_RC_LEAVE_GROUPCHATS)
						<< XATTR(L"sessionid", pSession->GetSessionId()) << XATTR(L"status", L"completed")
					<< XCHILD(L"note", szMsg) << XATTR(L"type", L"info"));

			return JABBER_ADHOC_HANDLER_STATUS_REMOVE_SESSION;
		}

		pSession->SetStage(1);

		XmlNodeIq iq(L"result", pInfo);
		HXML xNode = iq
			<<	XCHILDNS(L"command", JABBER_FEAT_COMMANDS) << XATTR(L"node", JABBER_FEAT_RC_LEAVE_GROUPCHATS)
				<< XATTR(L"sessionid", pSession->GetSessionId()) << XATTR(L"status", L"executing")
			<< XCHILDNS(L"x", JABBER_FEAT_DATA_FORMS) << XATTR(L"type", L"form");

		xNode << XCHILD(L"title", TranslateT("Leave group chats"));
		xNode << XCHILD(L"instructions", TranslateT("Choose the group chats you want to leave"));

		xNode << XCHILD(L"field") << XATTR(L"type", L"hidden") << XATTR(L"var", L"FORM_TYPE")
			<< XATTR(L"value", JABBER_FEAT_RC);

		// Groupchats
		HXML fieldNode = xNode << XCHILD(L"field") << XATTR(L"label", NULL) << XATTR(L"type", L"list-multi") << XATTR(L"var", L"groupchats");
		fieldNode << XCHILD(L"required");
		{
			mir_cslock lck(m_csLists);
			LISTFOREACH_NODEF(i, this, LIST_CHATROOM)
			{
				JABBER_LIST_ITEM *item = ListGetItemPtrFromIndex(i);
				if (item != NULL)
					fieldNode << XCHILD(L"option") << XATTR(L"label", item->jid) << XCHILD(L"value", item->jid);
			}
		}

		m_ThreadInfo->send(iq);
		return JABBER_ADHOC_HANDLER_STATUS_EXECUTING;
	}

	if (pSession->GetStage() == 1) {
		// result form here
		HXML commandNode = pInfo->GetChildNode();
		HXML xNode = XmlGetChildByTag(commandNode, "x", "xmlns", JABBER_FEAT_DATA_FORMS);
		if (!xNode)
			return JABBER_ADHOC_HANDLER_STATUS_CANCEL;

		// Groupchat list here:
		HXML fieldNode = XmlGetChildByTag(xNode,"field", "var", L"groupchats");
		if (fieldNode) {
			for (i=0; i < XmlGetChildCount(fieldNode); i++) {
				HXML valueNode = XmlGetChild(fieldNode, i);
				if (valueNode && XmlGetName(valueNode) && XmlGetText(valueNode) && !mir_tstrcmp(XmlGetName(valueNode), L"value")) {
					JABBER_LIST_ITEM *item = ListGetItemPtr(LIST_CHATROOM, XmlGetText(valueNode));
					if (item)
						GcQuit(item, 0, NULL);
				}
			}
		}

		return JABBER_ADHOC_HANDLER_STATUS_COMPLETED;
	}
	return JABBER_ADHOC_HANDLER_STATUS_CANCEL;
}