/*

Jabber Protocol Plugin for Miranda NG

Copyright (c) 2002-04  Santithorn Bunchua
Copyright (c) 2005-12  George Hazan
Copyright (�) 2012-17 Miranda NG project

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_caps.h"

#define GC_SERVER_LIST_SIZE 5

int JabberGcGetStatus(JABBER_GC_AFFILIATION a, JABBER_GC_ROLE r);
int JabberGcGetStatus(JABBER_RESOURCE_STATUS *r);

struct JabberGcRecentInfo
{
	ptrW m_room, m_server, m_nick, m_password;
	CJabberProto *ppro;

	JabberGcRecentInfo(CJabberProto *proto)
	{
		ppro = proto;
	}
	JabberGcRecentInfo(CJabberProto *proto, const wchar_t *room, const wchar_t *server, const wchar_t *nick = NULL, const wchar_t *password = NULL)
	{
		ppro = proto;
		fillData(room, server, nick, password);
	}
	JabberGcRecentInfo(CJabberProto *proto, const wchar_t *jid)
	{
		ppro = proto;
		fillData(jid);
	}
	JabberGcRecentInfo(CJabberProto *proto, int iRecent)
	{
		ppro = proto;
		loadRecent(iRecent);
	}

	~JabberGcRecentInfo()
	{
	}

	void cleanup()
	{
		m_room = m_server = m_nick = m_password = NULL;
	}

	BOOL equals(const wchar_t *room, const wchar_t *server, const wchar_t *nick = NULL, const wchar_t *password = NULL)
	{
		return
			null_strequals(m_room, room) &&
			null_strequals(m_server, server) &&
			null_strequals(m_nick, nick) &&
			null_strequals(m_password, password);
	}

	BOOL equalsnp(const wchar_t *room, const wchar_t *server, const wchar_t *nick = NULL)
	{
		return
			null_strequals(m_room, room) &&
			null_strequals(m_server, server) &&
			null_strequals(m_nick, nick);
	}

	void fillForm(HWND hwndDlg)
	{
		SetDlgItemText(hwndDlg, IDC_SERVER, m_server ? m_server : L"");
		SetDlgItemText(hwndDlg, IDC_ROOM, m_room ? m_room : L"");
		SetDlgItemText(hwndDlg, IDC_NICK, m_nick ? m_nick : L"");
		SetDlgItemText(hwndDlg, IDC_PASSWORD, m_password ? m_password : L"");
	}

	void fillData(const wchar_t *room, const wchar_t *server, const wchar_t *nick = NULL, const wchar_t *password = NULL)
	{
		m_room = mir_wstrdup(room);
		m_server = mir_wstrdup(server);
		m_nick = mir_wstrdup(nick);
		m_password = mir_wstrdup(password);
	}

	void fillData(const wchar_t *jid)
	{
		wchar_t *room, *server, *nick = NULL;
		room = NEWWSTR_ALLOCA(jid);
		server = wcschr(room, '@');
		if (server) {
			*server++ = 0;
			nick = wcschr(server, '/');
			if (nick) *nick++ = 0;
		}
		else {
			server = room;
			room = NULL;
		}

		fillData(room, server, nick);
	}

	BOOL loadRecent(int iRecent)
	{
		char setting[MAXMODULELABELLENGTH];
		mir_snprintf(setting, "rcMuc_%d_server", iRecent);
		m_server = ppro->getWStringA(setting);

		mir_snprintf(setting, "rcMuc_%d_room", iRecent);
		m_room = ppro->getWStringA(setting);

		mir_snprintf(setting, "rcMuc_%d_nick", iRecent);
		m_nick = ppro->getWStringA(setting);

		mir_snprintf(setting, "password_rcMuc_%d", iRecent);
		m_password = ppro->getWStringA(NULL, setting);

		return m_room || m_server || m_nick || m_password;
	}

	void saveRecent(int iRecent)
	{
		char setting[MAXMODULELABELLENGTH];

		mir_snprintf(setting, "rcMuc_%d_server", iRecent);
		if (m_server)
			ppro->setWString(setting, m_server);
		else
			ppro->delSetting(setting);

		mir_snprintf(setting, "rcMuc_%d_room", iRecent);
		if (m_room)
			ppro->setWString(setting, m_room);
		else
			ppro->delSetting(setting);

		mir_snprintf(setting, "rcMuc_%d_nick", iRecent);
		if (m_nick)
			ppro->setWString(setting, m_nick);
		else
			ppro->delSetting(setting);

		mir_snprintf(setting, "password_rcMuc_%d", iRecent);
		if (m_password)
			ppro->setWString(setting, m_password);
		else
			ppro->delSetting(setting);
	}

private:
	BOOL null_strequals(const wchar_t *str1, const wchar_t *str2)
	{
		if (!str1 && !str2) return TRUE;
		if (!str1 && str2 && !*str2) return TRUE;
		if (!str2 && str1 && !*str1) return TRUE;

		if (!str1 && str2) return FALSE;
		if (!str2 && str1) return FALSE;

		return !mir_wstrcmp(str1, str2);
	}
};

INT_PTR __cdecl CJabberProto::OnMenuHandleJoinGroupchat(WPARAM, LPARAM)
{
	GroupchatJoinRoomByJid(NULL, NULL);
	return 0;
}

INT_PTR __cdecl CJabberProto::OnJoinChat(WPARAM hContact, LPARAM)
{
	ptrW jid(getWStringA(hContact, "ChatRoomID"));
	if (jid == NULL)
		return 0;

	ptrW nick(getWStringA(hContact, "MyNick"));
	if (nick == NULL)
		if ((nick = getWStringA("Nick")) == NULL)
			return 0;

	ptrW password(getWStringA(hContact, "Password"));

	if (getWord(hContact, "Status", 0) != ID_STATUS_ONLINE) {
		wchar_t *p = wcschr(jid, '@');
		if (p != NULL) {
			*p++ = 0;
			GroupchatJoinRoom(p, jid, nick, password);
		}
	}

	return 0;
}

INT_PTR __cdecl CJabberProto::OnLeaveChat(WPARAM hContact, LPARAM)
{
	ptrW jid(getWStringA(hContact, "ChatRoomID"));
	if (jid != NULL) {
		if (getWord(hContact, "Status", 0) != ID_STATUS_OFFLINE) {
			JABBER_LIST_ITEM *item = ListGetItemPtr(LIST_CHATROOM, jid);
			if (item != NULL)
				GcQuit(item, 200, NULL);
		}
	}
	return 0;
}

void CJabberProto::GroupchatJoinRoom(const wchar_t *server, const wchar_t *room, const wchar_t *nick, const wchar_t *password, bool autojoin)
{
	JabberGcRecentInfo info(this);

	bool found = false;
	for (int i = 0; i < 5; i++) {
		if (!info.loadRecent(i))
			continue;

		if (info.equals(room, server, nick, password)) {
			found = true;
			break;
		}
	}

	if (!found) {
		for (int i = 4; i--;) {
			if (info.loadRecent(i))
				info.saveRecent(i + 1);
		}

		info.fillData(room, server, nick, password);
		info.saveRecent(0);
	}

	wchar_t text[JABBER_MAX_JID_LEN + 1];
	mir_snwprintf(text, L"%s@%s/%s", room, server, nick);

	JABBER_LIST_ITEM *item = ListAdd(LIST_CHATROOM, text);
	item->bAutoJoin = autojoin;
	replaceStrW(item->nick, nick);
	replaceStrW(item->password, info.m_password);

	int status = (m_iStatus == ID_STATUS_INVISIBLE) ? ID_STATUS_ONLINE : m_iStatus;
	XmlNode x(L"x"); x << XATTR(L"xmlns", JABBER_FEAT_MUC);
	if (info.m_password && info.m_password[0])
		x << XCHILD(L"password", info.m_password);

	SendPresenceTo(status, text, x);
}

////////////////////////////////////////////////////////////////////////////////
// Join Dialog

static int sttTextLineHeight = 16;

struct RoomInfo
{
	enum Overlay { ROOM_WAIT, ROOM_FAIL, ROOM_BOOKMARK, ROOM_DEFAULT };
	Overlay	overlay;
	wchar_t	*line1, *line2;
};

static int sttRoomListAppend(HWND hwndList, RoomInfo::Overlay overlay, const wchar_t *line1, const wchar_t *line2, const wchar_t *name)
{
	RoomInfo *info = (RoomInfo *)mir_alloc(sizeof(RoomInfo));
	info->overlay = overlay;
	info->line1 = line1 ? mir_wstrdup(line1) : 0;
	info->line2 = line2 ? mir_wstrdup(line2) : 0;

	int id = SendMessage(hwndList, CB_ADDSTRING, 0, (LPARAM)name);
	SendMessage(hwndList, CB_SETITEMDATA, id, (LPARAM)info);
	SendMessage(hwndList, CB_SETITEMHEIGHT, id, sttTextLineHeight * 2);
	return id;
}

void CJabberProto::OnIqResultDiscovery(HXML iqNode, CJabberIqInfo *pInfo)
{
	if (!iqNode || !pInfo)
		return;

	HWND hwndList = (HWND)pInfo->GetUserData();
	SendMessage(hwndList, CB_SHOWDROPDOWN, FALSE, 0);
	SendMessage(hwndList, CB_RESETCONTENT, 0, 0);

	if (pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT) {
		HXML query = XmlGetChild(iqNode, "query");
		if (query == NULL) {
			sttRoomListAppend(hwndList, RoomInfo::ROOM_FAIL,
				TranslateT("Jabber Error"),
				TranslateT("Failed to retrieve room list from server."),
				L"");
		}
		else {
			bool found = false;
			HXML item;
			for (int i = 1; item = XmlGetNthChild(query, L"item", i); i++) {
				const wchar_t *jid = XmlGetAttrValue(item, L"jid");
				wchar_t *name = NEWWSTR_ALLOCA(jid);
				if (name) {
					if (wchar_t *p = wcschr(name, '@'))
						*p = 0;
				}
				else name = L"";

				sttRoomListAppend(hwndList,
					ListGetItemPtr(LIST_BOOKMARK, jid) ? RoomInfo::ROOM_BOOKMARK : RoomInfo::ROOM_DEFAULT,
					XmlGetAttrValue(item, L"name"),
					jid, name);

				found = true;
			}

			if (!found) {
				sttRoomListAppend(hwndList, RoomInfo::ROOM_FAIL,
					TranslateT("Jabber Error"),
					TranslateT("No rooms available on server."),
					L"");
			}
		}
	}
	else if (pInfo->GetIqType() == JABBER_IQ_TYPE_ERROR) {
		HXML errorNode = XmlGetChild(iqNode, "error");
		wchar_t *str = JabberErrorMsg(errorNode);
		sttRoomListAppend(hwndList, RoomInfo::ROOM_FAIL,
			TranslateT("Jabber Error"),
			str,
			L"");
		mir_free(str);
	}
	else
		sttRoomListAppend(hwndList, RoomInfo::ROOM_FAIL,
			TranslateT("Jabber Error"),
			TranslateT("Room list request timed out."),
			L"");

	SendMessage(hwndList, CB_SHOWDROPDOWN, TRUE, 0);
}

static void sttJoinDlgShowRecentItems(HWND hwndDlg, int newCount)
{
	RECT rcTitle, rcLastItem;
	GetWindowRect(GetDlgItem(hwndDlg, IDC_TXT_RECENT), &rcTitle);
	GetWindowRect(GetDlgItem(hwndDlg, IDC_RECENT5), &rcLastItem);

	ShowWindow(GetDlgItem(hwndDlg, IDC_TXT_RECENT), newCount ? SW_SHOW : SW_HIDE);

	int oldCount = 5;
	for (int idc = IDC_RECENT1; idc <= IDC_RECENT5; ++idc)
		ShowWindow(GetDlgItem(hwndDlg, idc), (idc - IDC_RECENT1 < newCount) ? SW_SHOW : SW_HIDE);

	int curRecentHeight = rcLastItem.bottom - rcTitle.top - (5 - oldCount) * (rcLastItem.bottom - rcLastItem.top);
	int newRecentHeight = rcLastItem.bottom - rcTitle.top - (5 - newCount) * (rcLastItem.bottom - rcLastItem.top);
	if (!newCount)
		newRecentHeight = 0;
	int offset = newRecentHeight - curRecentHeight;

	RECT rc;
	int ctrls[] = { IDC_BOOKMARKS, IDOK, IDCANCEL };
	for (int i = 0; i < _countof(ctrls); i++) {
		GetWindowRect(GetDlgItem(hwndDlg, ctrls[i]), &rc);
		MapWindowPoints(NULL, hwndDlg, (LPPOINT)&rc, 2);
		SetWindowPos(GetDlgItem(hwndDlg, ctrls[i]), NULL, rc.left, rc.top + offset, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
	}

	GetWindowRect(hwndDlg, &rc);
	SetWindowPos(hwndDlg, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top + offset, SWP_NOMOVE | SWP_NOZORDER);
}

class CJabberDlgGcJoin : public CJabberDlgBase
{
	typedef CJabberDlgBase CSuper;

	CCtrlButton btnOk;

public:
	CJabberDlgGcJoin(CJabberProto *proto, wchar_t *jid);
	~CJabberDlgGcJoin();

protected:
	wchar_t *m_jid;

	void OnInitDialog();
	void OnDestroy();

	void OnBtnOk(CCtrlButton*);

	INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam);
};

CJabberDlgGcJoin::CJabberDlgGcJoin(CJabberProto *proto, wchar_t *jid) :
	CSuper(proto, IDD_GROUPCHAT_JOIN, NULL),
	btnOk(this, IDOK),
	m_jid(mir_wstrdup(jid))
{
	btnOk.OnClick = Callback(this, &CJabberDlgGcJoin::OnBtnOk);
}

CJabberDlgGcJoin::~CJabberDlgGcJoin()
{
	mir_free(m_jid);
}

void CJabberDlgGcJoin::OnInitDialog()
{
	CSuper::OnInitDialog();

	Window_SetIcon_IcoLib(m_hwnd, g_GetIconHandle(IDI_GROUP));

	JabberGcRecentInfo *pInfo = NULL;
	if (m_jid)
		pInfo = new JabberGcRecentInfo(m_proto, m_jid);
	else if (OpenClipboard(m_hwnd)) {
		HANDLE hData = GetClipboardData(CF_UNICODETEXT);

		if (hData) {
			wchar_t *buf = (wchar_t *)GlobalLock(hData);
			if (buf && wcschr(buf, '@') && !wcschr(buf, ' '))
				pInfo = new JabberGcRecentInfo(m_proto, buf);
			GlobalUnlock(hData);
		}
		CloseClipboard();
	}

	if (pInfo) {
		pInfo->fillForm(m_hwnd);
		delete pInfo;
	}

	ptrW tszNick(m_proto->getWStringA("Nick"));
	if (tszNick == NULL)
		tszNick = JabberNickFromJID(m_proto->m_szJabberJID);
	SetDlgItemText(m_hwnd, IDC_NICK, tszNick);

	TEXTMETRIC tm = { 0 };
	HDC hdc = GetDC(m_hwnd);
	GetTextMetrics(hdc, &tm);
	ReleaseDC(m_hwnd, hdc);
	sttTextLineHeight = tm.tmHeight;
	SendDlgItemMessage(m_hwnd, IDC_ROOM, CB_SETITEMHEIGHT, -1, sttTextLineHeight - 1);

	LOGFONT lf = { 0 };
	HFONT hfnt = (HFONT)SendDlgItemMessage(m_hwnd, IDC_TXT_RECENT, WM_GETFONT, 0, 0);
	GetObject(hfnt, sizeof(lf), &lf);
	lf.lfWeight = FW_BOLD;
	SendDlgItemMessage(m_hwnd, IDC_TXT_RECENT, WM_SETFONT, (WPARAM)CreateFontIndirect(&lf), TRUE);

	SendDlgItemMessage(m_hwnd, IDC_BOOKMARKS, BM_SETIMAGE, IMAGE_ICON, (LPARAM)m_proto->LoadIconEx("bookmarks"));
	SendDlgItemMessage(m_hwnd, IDC_BOOKMARKS, BUTTONSETASFLATBTN, TRUE, 0);
	SendDlgItemMessage(m_hwnd, IDC_BOOKMARKS, BUTTONADDTOOLTIP, (WPARAM)"Bookmarks", 0);
	SendDlgItemMessage(m_hwnd, IDC_BOOKMARKS, BUTTONSETASPUSHBTN, TRUE, 0);

	m_proto->ComboLoadRecentStrings(m_hwnd, IDC_SERVER, "joinWnd_rcSvr");

	int i;
	for (i = 0; i < 5; i++) {
		wchar_t jid[JABBER_MAX_JID_LEN];
		JabberGcRecentInfo info(m_proto);
		if (!info.loadRecent(i))
			break;

		mir_snwprintf(jid, L"%s@%s (%s)", info.m_room, info.m_server, info.m_nick ? info.m_nick : TranslateT("<no nick>"));
		SetDlgItemText(m_hwnd, IDC_RECENT1 + i, jid);
	}
	sttJoinDlgShowRecentItems(m_hwnd, i);
}

void CJabberDlgGcJoin::OnDestroy()
{
	IcoLib_ReleaseIcon((HICON)SendDlgItemMessage(m_hwnd, IDC_BOOKMARKS, BM_SETIMAGE, IMAGE_ICON, 0));
	m_proto->m_pDlgJabberJoinGroupchat = NULL;
	DeleteObject((HFONT)SendDlgItemMessage(m_hwnd, IDC_TXT_RECENT, WM_GETFONT, 0, 0));

	CSuper::OnDestroy();

	mir_free(m_jid); m_jid = NULL;
}

void CJabberDlgGcJoin::OnBtnOk(CCtrlButton*)
{
	wchar_t text[128];
	GetDlgItemText(m_hwnd, IDC_SERVER, text, _countof(text));
	wchar_t *server = NEWWSTR_ALLOCA(text), *room;

	m_proto->ComboAddRecentString(m_hwnd, IDC_SERVER, "joinWnd_rcSvr", server);

	GetDlgItemText(m_hwnd, IDC_ROOM, text, _countof(text));
	room = NEWWSTR_ALLOCA(text);

	GetDlgItemText(m_hwnd, IDC_NICK, text, _countof(text));
	wchar_t *nick = NEWWSTR_ALLOCA(text);

	GetDlgItemText(m_hwnd, IDC_PASSWORD, text, _countof(text));
	wchar_t *password = NEWWSTR_ALLOCA(text);
	m_proto->GroupchatJoinRoom(server, room, nick, password);
}

INT_PTR CJabberDlgGcJoin::DlgProc(UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg) {
	case WM_DELETEITEM:
		{
			LPDELETEITEMSTRUCT lpdis = (LPDELETEITEMSTRUCT)lParam;
			if (lpdis->CtlID != IDC_ROOM)
				break;

			RoomInfo *info = (RoomInfo *)lpdis->itemData;
			mir_free(info->line1);
			mir_free(info->line2);
			mir_free(info);
		}
		break;

	case WM_MEASUREITEM:
		{
			LPMEASUREITEMSTRUCT lpmis = (LPMEASUREITEMSTRUCT)lParam;
			if (lpmis->CtlID != IDC_ROOM)
				break;

			lpmis->itemHeight = 2 * sttTextLineHeight;
			if (lpmis->itemID == -1)
				lpmis->itemHeight = sttTextLineHeight - 1;

		}
		break;

	case WM_DRAWITEM:
		{
			LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;
			if (lpdis->CtlID != IDC_ROOM)
				break;

			RoomInfo *info = (RoomInfo *)SendDlgItemMessage(m_hwnd, IDC_ROOM, CB_GETITEMDATA, lpdis->itemID, 0);
			COLORREF clLine1, clBack;

			if (lpdis->itemState & ODS_SELECTED) {
				FillRect(lpdis->hDC, &lpdis->rcItem, GetSysColorBrush(COLOR_HIGHLIGHT));
				clBack = GetSysColor(COLOR_HIGHLIGHT);
				clLine1 = GetSysColor(COLOR_HIGHLIGHTTEXT);
			}
			else {
				FillRect(lpdis->hDC, &lpdis->rcItem, GetSysColorBrush(COLOR_WINDOW));
				clBack = GetSysColor(COLOR_WINDOW);
				clLine1 = GetSysColor(COLOR_WINDOWTEXT);
			}
			COLORREF clLine2 = RGB(
				GetRValue(clLine1) * 0.66 + GetRValue(clBack) * 0.34,
				GetGValue(clLine1) * 0.66 + GetGValue(clBack) * 0.34,
				GetBValue(clLine1) * 0.66 + GetBValue(clBack) * 0.34);

			SetBkMode(lpdis->hDC, TRANSPARENT);

			RECT rc = lpdis->rcItem;
			rc.bottom -= (rc.bottom - rc.top) / 2;
			rc.left += 20;
			SetTextColor(lpdis->hDC, clLine1);
			DrawText(lpdis->hDC, info->line1, -1, &rc, DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_WORD_ELLIPSIS);

			rc = lpdis->rcItem;
			rc.top += (rc.bottom - rc.top) / 2;
			rc.left += 20;
			SetTextColor(lpdis->hDC, clLine2);
			DrawText(lpdis->hDC, info->line2, -1, &rc, DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_WORD_ELLIPSIS);

			DrawIconEx(lpdis->hDC, lpdis->rcItem.left + 1, lpdis->rcItem.top + 1, m_proto->LoadIconEx("group"), 16, 16, 0, NULL, DI_NORMAL);
			switch (info->overlay) {
			case RoomInfo::ROOM_WAIT:
				DrawIconEx(lpdis->hDC, lpdis->rcItem.left + 1, lpdis->rcItem.top + 1, m_proto->LoadIconEx("disco_progress"), 16, 16, 0, NULL, DI_NORMAL);
				break;
			case RoomInfo::ROOM_FAIL:
				DrawIconEx(lpdis->hDC, lpdis->rcItem.left + 1, lpdis->rcItem.top + 1, m_proto->LoadIconEx("disco_fail"), 16, 16, 0, NULL, DI_NORMAL);
				break;
			case RoomInfo::ROOM_BOOKMARK:
				DrawIconEx(lpdis->hDC, lpdis->rcItem.left + 1, lpdis->rcItem.top + 1, m_proto->LoadIconEx("disco_ok"), 16, 16, 0, NULL, DI_NORMAL);
				break;
			}
		}
		break;

	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDC_SERVER:
			switch (HIWORD(wParam)) {
			case CBN_EDITCHANGE:
			case CBN_SELCHANGE:
				{
					int iqid = GetWindowLongPtr(GetDlgItem(m_hwnd, IDC_ROOM), GWLP_USERDATA);
					if (iqid) {
						m_proto->m_iqManager.ExpireIq(iqid);
						SetWindowLongPtr(GetDlgItem(m_hwnd, IDC_ROOM), GWLP_USERDATA, 0);
					}
					SendDlgItemMessage(m_hwnd, IDC_ROOM, CB_RESETCONTENT, 0, 0);
				}
				break;
			}
			break;

		case IDC_ROOM:
			switch (HIWORD(wParam)) {
			case CBN_DROPDOWN:
				if (!SendDlgItemMessage(m_hwnd, IDC_ROOM, CB_GETCOUNT, 0, 0)) {
					int iqid = GetWindowLongPtr(GetDlgItem(m_hwnd, IDC_ROOM), GWLP_USERDATA);
					if (iqid) {
						m_proto->m_iqManager.ExpireIq(iqid);
						SetWindowLongPtr(GetDlgItem(m_hwnd, IDC_ROOM), GWLP_USERDATA, 0);
					}

					SendDlgItemMessage(m_hwnd, IDC_ROOM, CB_RESETCONTENT, 0, 0);

					int len = GetWindowTextLength(GetDlgItem(m_hwnd, IDC_SERVER)) + 1;
					wchar_t *server = (wchar_t*)_alloca(len * sizeof(wchar_t));
					GetDlgItemText(m_hwnd, IDC_SERVER, server, len);

					if (*server) {
						sttRoomListAppend(GetDlgItem(m_hwnd, IDC_ROOM), RoomInfo::ROOM_WAIT, TranslateT("Loading..."), TranslateT("Please wait for room list to download."), L"");

						CJabberIqInfo *pInfo = m_proto->AddIQ(&CJabberProto::OnIqResultDiscovery, JABBER_IQ_TYPE_GET, server, 0, -1, (void*)GetDlgItem(m_hwnd, IDC_ROOM));
						pInfo->SetTimeout(30000);
						XmlNodeIq iq(pInfo);
						iq << XQUERY(JABBER_FEAT_DISCO_ITEMS);
						m_proto->m_ThreadInfo->send(iq);

						SetWindowLongPtr(GetDlgItem(m_hwnd, IDC_ROOM), GWLP_USERDATA, pInfo->GetIqId());
					}
					else
						sttRoomListAppend(GetDlgItem(m_hwnd, IDC_ROOM), RoomInfo::ROOM_FAIL,
						TranslateT("Jabber Error"),
						TranslateT("Please specify group chat directory first."),
						L"");
				}
				break;
			}
			break;

		case IDC_BOOKMARKS:
			{
				HMENU hMenu = CreatePopupMenu();

				LISTFOREACH(i, m_proto, LIST_BOOKMARK)
				{
					JABBER_LIST_ITEM *item = 0;
					if (item = m_proto->ListGetItemPtrFromIndex(i))
						if (!mir_wstrcmp(item->type, L"conference"))
							AppendMenu(hMenu, MF_STRING, (UINT_PTR)item, item->name);
				}
				AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);
				AppendMenu(hMenu, MF_STRING, (UINT_PTR)-1, TranslateT("Bookmarks..."));
				AppendMenu(hMenu, MF_STRING, (UINT_PTR)0, TranslateT("Cancel"));

				RECT rc; GetWindowRect(GetDlgItem(m_hwnd, IDC_BOOKMARKS), &rc);
				CheckDlgButton(m_hwnd, IDC_BOOKMARKS, BST_CHECKED);
				int res = TrackPopupMenu(hMenu, TPM_RETURNCMD, rc.left, rc.bottom, 0, m_hwnd, NULL);
				CheckDlgButton(m_hwnd, IDC_BOOKMARKS, BST_UNCHECKED);
				DestroyMenu(hMenu);

				if (res == -1)
					m_proto->OnMenuHandleBookmarks(0, 0);
				else if (res) {
					JABBER_LIST_ITEM *item = (JABBER_LIST_ITEM *)res;
					wchar_t *room = NEWWSTR_ALLOCA(item->jid);
					if (room) {
						wchar_t *server = wcschr(room, '@');
						if (server) {
							*server++ = 0;

							SendMessage(m_hwnd, WM_COMMAND, MAKEWPARAM(IDC_SERVER, CBN_EDITCHANGE), (LPARAM)GetDlgItem(m_hwnd, IDC_SERVER));

							SetDlgItemText(m_hwnd, IDC_SERVER, server);
							SetDlgItemText(m_hwnd, IDC_ROOM, room);
							SetDlgItemText(m_hwnd, IDC_NICK, item->nick);
							SetDlgItemText(m_hwnd, IDC_PASSWORD, item->password);
						}
					}
				}
			}
			break;

		case IDC_RECENT1:
		case IDC_RECENT2:
		case IDC_RECENT3:
		case IDC_RECENT4:
		case IDC_RECENT5:
			JabberGcRecentInfo info(m_proto, LOWORD(wParam) - IDC_RECENT1);
			info.fillForm(m_hwnd);
			if (GetAsyncKeyState(VK_CONTROL))
				break;

			OnBtnOk(NULL);
			Close();
		}
		break;

	case WM_JABBER_CHECK_ONLINE:
		if (!m_proto->m_bJabberOnline)
			EndDialog(m_hwnd, 0);
		break;
	}

	return CSuper::DlgProc(msg, wParam, lParam);
}

void CJabberProto::GroupchatJoinRoomByJid(HWND, wchar_t *jid)
{
	if (m_pDlgJabberJoinGroupchat)
		SetForegroundWindow(m_pDlgJabberJoinGroupchat->GetHwnd());
	else {
		m_pDlgJabberJoinGroupchat = new CJabberDlgGcJoin(this, jid);
		m_pDlgJabberJoinGroupchat->Show();
	}
}

/////////////////////////////////////////////////////////////////////////////////////////
// JabberGroupchatProcessPresence - handles the group chat presence packet

struct JabberGroupchatChangeNicknameParam
{
	JabberGroupchatChangeNicknameParam(CJabberProto* ppro_, const wchar_t *jid_) :
		ppro(ppro_),
		jid(mir_wstrdup(jid_))
		{}

	~JabberGroupchatChangeNicknameParam()
	{	mir_free(jid);
	}

	CJabberProto *ppro;
	wchar_t *jid;
};

static VOID CALLBACK JabberGroupchatChangeNickname(void* arg)
{
	JabberGroupchatChangeNicknameParam *param = (JabberGroupchatChangeNicknameParam*)arg;
	if (param == NULL)
		return;

	JABBER_LIST_ITEM *item = param->ppro->ListGetItemPtr(LIST_CHATROOM, param->jid);
	if (item != NULL) {
		CMStringW szBuffer, szTitle;
		szTitle.Format(TranslateT("Change nickname in <%s>"), item->name ? item->name : item->jid);
		if (item->nick)
			szBuffer = item->nick;

		if (param->ppro->EnterString(szBuffer, szTitle, ESF_COMBO, "gcNick_")) {
			replaceStrW(item->nick, szBuffer);
			param->ppro->SendPresenceTo(param->ppro->m_iStatus, CMStringW(FORMAT, L"%s/%s", item->jid, szBuffer), NULL);
		}
	}

	delete param;
}

static int sttGetStatusCode(HXML node)
{
	HXML statusNode = XmlGetChild(node, "status");
	if (statusNode == NULL)
		return -1;

	const wchar_t *statusCode = XmlGetAttrValue(statusNode, L"code");
	if (statusCode == NULL)
		return -1;

	return _wtol(statusCode);
}

void CJabberProto::RenameParticipantNick(JABBER_LIST_ITEM *item, const wchar_t *oldNick, HXML itemNode)
{
	const wchar_t *jid = XmlGetAttrValue(itemNode, L"jid");
	const wchar_t *newNick = XmlGetAttrValue(itemNode, L"nick");
	if (newNick == NULL)
		return;

	pResourceStatus r(item->findResource(oldNick));
	if (r == NULL)
		return;

	r->m_tszResourceName = mir_wstrdup(newNick);

	if (!mir_wstrcmp(item->nick, oldNick)) {
		replaceStrW(item->nick, newNick);

		MCONTACT hContact = HContactFromJID(item->jid);
		if (hContact != NULL)
			setWString(hContact, "MyNick", newNick);
	}

	Chat_ChangeUserId(m_szModuleName, item->jid, oldNick, newNick);

	GCDEST gcd = { m_szModuleName, item->jid, GC_EVENT_NICK };
	GCEVENT gce = { &gcd };
	if (jid != NULL)
		gce.ptszUserInfo = jid;
	gce.time = time(0);
	gce.ptszNick = oldNick;
	gce.ptszUID = newNick;
	gce.ptszText = newNick;
	Chat_Event(&gce);
}

void CJabberProto::GroupchatProcessPresence(HXML node)
{
	const wchar_t *from;

	if (!node || !XmlGetName(node) || mir_wstrcmp(XmlGetName(node), L"presence")) return;
	if ((from = XmlGetAttrValue(node, L"from")) == NULL) return;

	const wchar_t *resource = wcschr(from, '/');
	if (resource == NULL || *++resource == '\0')
		return;

	JABBER_LIST_ITEM *item = ListGetItemPtr(LIST_CHATROOM, from);
	if (item == NULL)
		return;

	pResourceStatus r(item->findResource(resource));

	HXML nNode = XmlGetChildByTag(node, "nick", "xmlns", JABBER_FEAT_NICK);
	const wchar_t *cnick = XmlGetText(nNode);
	const wchar_t *nick = cnick ? cnick : (r && r->m_tszNick ? r->m_tszNick : resource);

	// process custom nick change
	if (cnick && r && r->m_tszNick && mir_wstrcmp(cnick, r->m_tszNick))
		r->m_tszNick = mir_wstrdup(cnick);

	HXML xNode = XmlGetChildByTag(node, "x", "xmlns", JABBER_FEAT_MUC_USER);
	HXML itemNode = XmlGetChild(xNode, "item");

	const wchar_t *type = XmlGetAttrValue(node, L"type");

	// entering room or a usual room presence
	if (type == NULL || !mir_wstrcmp(type, L"available")) {
		if (ptrW(JabberNickFromJID(from)) == NULL)
			return;

		GcInit(item);
		item->iChatState = 0;

		// Update status of room participant
		int status = ID_STATUS_ONLINE;
		LPCTSTR ptszShow = XmlGetText(XmlGetChild(node, "show"));
		if (ptszShow) {
			if (!mir_wstrcmp(ptszShow, L"away")) status = ID_STATUS_AWAY;
			else if (!mir_wstrcmp(ptszShow, L"xa")) status = ID_STATUS_NA;
			else if (!mir_wstrcmp(ptszShow, L"dnd")) status = ID_STATUS_DND;
			else if (!mir_wstrcmp(ptszShow, L"chat")) status = ID_STATUS_FREECHAT;
		}

		LPCTSTR str = XmlGetText(XmlGetChild(node, "status"));

		char priority = 0;
		if (LPCTSTR ptszPriority = XmlGetText(XmlGetChild(node, "priority")))
			priority = (char)_wtoi(ptszPriority);

		bool bStatusChanged = false, bRoomCreated = false, bAffiliationChanged = false, bRoleChanged = false;
		int  newRes = ListAddResource(LIST_CHATROOM, from, status, str, priority, cnick) ? GC_EVENT_JOIN : 0;

		if (pResourceStatus oldRes = ListFindResource(LIST_CHATROOM, from))
			if ((oldRes->m_iStatus != status) || lstrcmp_null(oldRes->m_tszStatusMessage, str))
				bStatusChanged = true;

		// Check additional MUC info for this user
		if (itemNode != NULL) {
			if (r == NULL)
				r = item->findResource(resource);
			if (r != NULL) {
				JABBER_GC_AFFILIATION affiliation = r->m_affiliation;
				JABBER_GC_ROLE role = r->m_role;

				if ((str = XmlGetAttrValue(itemNode, L"affiliation")) != NULL) {
					if (!mir_wstrcmp(str, L"owner"))       affiliation = AFFILIATION_OWNER;
					else if (!mir_wstrcmp(str, L"admin"))       affiliation = AFFILIATION_ADMIN;
					else if (!mir_wstrcmp(str, L"member"))      affiliation = AFFILIATION_MEMBER;
					else if (!mir_wstrcmp(str, L"none"))	     affiliation = AFFILIATION_NONE;
					else if (!mir_wstrcmp(str, L"outcast"))     affiliation = AFFILIATION_OUTCAST;
				}
				if ((str = XmlGetAttrValue(itemNode, L"role")) != NULL) {
					if (!mir_wstrcmp(str, L"moderator"))   role = ROLE_MODERATOR;
					else if (!mir_wstrcmp(str, L"participant")) role = ROLE_PARTICIPANT;
					else if (!mir_wstrcmp(str, L"visitor"))     role = ROLE_VISITOR;
					else                                        role = ROLE_NONE;
				}

				if ((role != ROLE_NONE) && (JabberGcGetStatus(r) != JabberGcGetStatus(affiliation, role))) {
					GcLogUpdateMemberStatus(item, resource, nick, NULL, GC_EVENT_REMOVESTATUS, NULL);
					if (!newRes) newRes = GC_EVENT_ADDSTATUS;
				}

				if (affiliation != r->m_affiliation) {
					r->m_affiliation = affiliation;
					bAffiliationChanged = true;
				}

				if (role != r->m_role) {
					r->m_role = role;
					if (r->m_role != ROLE_NONE)
						bRoleChanged = true;
				}

				if (str = XmlGetAttrValue(itemNode, L"jid"))
					r->m_tszRealJid = mir_wstrdup(str);
			}
		}

		if (sttGetStatusCode(xNode) == 201)
			bRoomCreated = true;

		// show status change if needed
		if (bStatusChanged)
			if (pResourceStatus res = ListFindResource(LIST_CHATROOM, from))
				GcLogShowInformation(item, res, INFO_STATUS);

		// Update groupchat log window
		GcLogUpdateMemberStatus(item, resource, nick, str, newRes, NULL);
		if (r && bAffiliationChanged) GcLogShowInformation(item, r, INFO_AFFILIATION);
		if (r && bRoleChanged) GcLogShowInformation(item, r, INFO_ROLE);

		// update clist status
		MCONTACT hContact = HContactFromJID(from);
		if (hContact != NULL)
			setWord(hContact, "Status", status);

		// Check <created/>
		if (bRoomCreated) {
			HXML n = XmlGetChild(node, "created");
			if (n != NULL && (str = XmlGetAttrValue(n, L"xmlns")) != NULL && !mir_wstrcmp(str, JABBER_FEAT_MUC_OWNER))
				// A new room just created by me
				// Request room config
				m_ThreadInfo->send(
				XmlNodeIq(AddIQ(&CJabberProto::OnIqResultGetMuc, JABBER_IQ_TYPE_GET, item->jid))
					<< XQUERY(JABBER_FEAT_MUC_OWNER));
		}
	}

	// leaving room
	else if (!mir_wstrcmp(type, L"unavailable")) {
		const wchar_t *str = 0;
		if (xNode != NULL && item->nick != NULL) {
			HXML reasonNode = XmlGetChild(itemNode, "reason");
			str = XmlGetAttrValue(itemNode, L"jid");

			int iStatus = sttGetStatusCode(xNode);
			if (iStatus == 301 && r != NULL)
				GcLogShowInformation(item, r, INFO_BAN);

			if (!mir_wstrcmp(resource, item->nick)) {
				switch (iStatus) {
				case 301:
				case 307:
					GcQuit(item, iStatus, reasonNode);
					return;

				case 303:
					RenameParticipantNick(item, resource, itemNode);
					return;
				}
			}
			else {
				switch (iStatus) {
				case 303:
					RenameParticipantNick(item, resource, itemNode);
					return;

				case 301:
				case 307:
				case 322:
					ListRemoveResource(LIST_CHATROOM, from);
					GcLogUpdateMemberStatus(item, resource, nick, str, GC_EVENT_KICK, reasonNode, iStatus);
					return;
				}
			}
		}

		HXML statusNode = XmlGetChild(node, "status");
		GcLogUpdateMemberStatus(item, resource, nick, str, GC_EVENT_PART, statusNode);
		ListRemoveResource(LIST_CHATROOM, from);

		MCONTACT hContact = HContactFromJID(from);
		if (hContact != NULL)
			setWord(hContact, "Status", ID_STATUS_OFFLINE);
	}

	// processing room errors
	else if (!mir_wstrcmp(type, L"error")) {
		int errorCode = 0;
		HXML errorNode = XmlGetChild(node, "error");
		ptrW str(JabberErrorMsg(errorNode, &errorCode));

		if (errorCode == JABBER_ERROR_CONFLICT) {
			ptrW newNick(getWStringA("GcAltNick"));
			if (++item->iChatState == 1 && newNick != NULL && newNick[0] != 0) {
				replaceStrW(item->nick, newNick);
				wchar_t text[1024] = { 0 };
				mir_snwprintf(text, L"%s/%s", item->jid, newNick);
				SendPresenceTo(m_iStatus, text, NULL);
			}
			else {
				CallFunctionAsync(JabberGroupchatChangeNickname, new JabberGroupchatChangeNicknameParam(this, from));
				item->iChatState = 0;
			}
			return;
		}

		MsgPopup(NULL, str, TranslateT("Jabber Error"));

		if (item != NULL && !item->bChatActive)
			ListRemove(LIST_CHATROOM, from);
	}
}

void CJabberProto::GroupchatProcessMessage(HXML node)
{
	HXML n, m;
	const wchar_t *from, *type, *p, *nick, *resource;
	JABBER_LIST_ITEM *item;
	CMStringW imgLink;

	if (!XmlGetName(node) || mir_wstrcmp(XmlGetName(node), L"message")) return;
	if ((from = XmlGetAttrValue(node, L"from")) == NULL) return;
	if ((item = ListGetItemPtr(LIST_CHATROOM, from)) == NULL) return;

	if ((type = XmlGetAttrValue(node, L"type")) == NULL) return;
	if (!mir_wstrcmp(type, L"error"))
		return;

	GCDEST gcd = { m_szModuleName, item->jid, 0 };

	const wchar_t *msgText = NULL;

	resource = wcschr(from, '/');
	if (resource != NULL && *++resource == '\0')
		resource = NULL;

	if ((n = XmlGetChild(node, "subject")) != NULL) {
		msgText = XmlGetText(n);
		if (msgText == NULL || msgText[0] == '\0')
			return;

		gcd.iType = GC_EVENT_TOPIC;

		if (resource == NULL && (m = XmlGetChild(node, "body")) != NULL) {
			const wchar_t *tmpnick = XmlGetText(m);
			if (tmpnick == NULL || *tmpnick == 0)
				return;

			const wchar_t *tmptr = wcsstr(tmpnick, L"has set the subject to:"); //ejabberd
			if (tmptr == NULL)
				tmptr = wcsstr(tmpnick, TranslateT("has set the subject to:")); //ejabberd
			if (tmptr != NULL && *tmptr != 0) {
				*(wchar_t*)(--tmptr) = 0;
				resource = tmpnick;
			}
		}
		item->getTemp()->m_tszStatusMessage = mir_wstrdup(msgText);
	}
	else {
		imgLink = ExtractImage(node);

		if ((n = XmlGetChildByTag(node, "body", "xml:lang", m_tszSelectedLang)) == NULL)
			if ((n = XmlGetChild(node, "body")) == NULL)
				return;

		msgText = XmlGetText(n);
		if (msgText == NULL)
			return;

		if (resource == NULL)
			gcd.iType = GC_EVENT_INFORMATION;
		else if (wcsncmp(msgText, L"/me ", 4) == 0 && mir_wstrlen(msgText) > 4) {
			msgText += 4;
			gcd.iType = GC_EVENT_ACTION;
		}
		else gcd.iType = GC_EVENT_MESSAGE;
	}

	GcInit(item);

	time_t msgTime = 0;
	if (!JabberReadXep203delay(node, msgTime)) {
		HXML xDelay = XmlGetChildByTag(node, "x", "xmlns", L"jabber:x:delay");
		if (xDelay && (p = XmlGetAttrValue(xDelay, L"stamp")) != NULL)
			msgTime = JabberIsoToUnixTime(p);
	}

	bool isHistory = msgTime != 0;
	time_t now = time(NULL);
	if (!msgTime || msgTime > now)
		msgTime = now;

	if (resource != NULL) {
		pResourceStatus r(item->findResource(resource));
		nick = (r && r->m_tszNick) ? r->m_tszNick : resource;
	}
	else nick = NULL;

	CMStringW tszText(msgText);
	tszText.Replace(L"%", L"%%");
	tszText += imgLink;

	GCEVENT gce = { &gcd };
	gce.ptszUID = resource;
	gce.ptszNick = nick;
	gce.time = msgTime;
	gce.ptszText = tszText;
	gce.bIsMe = nick == NULL ? FALSE : (mir_wstrcmp(resource, item->nick) == 0);

	if (!isHistory)
		gce.dwFlags |= GCEF_ADDTOLOG;

	if (m_options.GcLogChatHistory && isHistory)
		gce.dwFlags |= GCEF_NOTNOTIFY;

	Chat_Event(&gce);

	item->bChatActive = 2;

	if (gcd.iType == GC_EVENT_TOPIC)
		Chat_SetStatusbarText(m_szModuleName, item->jid, tszText);
}

/////////////////////////////////////////////////////////////////////////////////////////
// Accepting groupchat invitations

class CGroupchatInviteAcceptDlg : public CJabberDlgBase
{
	typedef CJabberDlgBase CSuper;
	CCtrlButton m_accept;
	CMStringW m_roomJid, m_from, m_reason, m_password;

public:
	CGroupchatInviteAcceptDlg(CJabberProto *ppro, const wchar_t *roomJid, const wchar_t *from, const wchar_t *reason, const wchar_t *password) :
		CSuper(ppro, IDD_GROUPCHAT_INVITE_ACCEPT, NULL),
		m_roomJid(roomJid), m_from(from), m_reason(reason), m_password(password),
		m_accept(this, IDC_ACCEPT)
	{
		m_accept.OnClick = Callback(this, &CGroupchatInviteAcceptDlg::OnCommand_Accept);
	}

	void OnInitDialog()
	{
		CSuper::OnInitDialog();

		wchar_t buf[256];
		mir_snwprintf(buf, TranslateT("Group chat invitation to\n%s"), m_roomJid);
		SetDlgItemText(m_hwnd, IDC_HEADERBAR, buf);

		SetDlgItemText(m_hwnd, IDC_FROM, m_from);
		SetDlgItemText(m_hwnd, IDC_REASON, m_reason);
		SetDlgItemText(m_hwnd, IDC_NICK, ptrW(JabberNickFromJID(m_proto->m_szJabberJID)));

		Window_SetIcon_IcoLib(m_hwnd, g_GetIconHandle(IDI_GROUP));

		SetFocus(GetDlgItem(m_hwnd, IDC_NICK));
	}

	void OnCommand_Accept(CCtrlButton*)
	{
		wchar_t text[128];
		GetDlgItemText(m_hwnd, IDC_NICK, text, _countof(text));
		m_proto->AcceptGroupchatInvite(m_roomJid, text, m_password);
		EndDialog(m_hwnd, 0);
	}
};

static void __stdcall sttShowDialog(void *pArg)
{
	CGroupchatInviteAcceptDlg *pDlg = (CGroupchatInviteAcceptDlg*)pArg;
	pDlg->Show();
}

void CJabberProto::GroupchatProcessInvite(const wchar_t *roomJid, const wchar_t *from, const wchar_t *reason, const wchar_t *password)
{
	if (roomJid == NULL)
		return;

	if (ListGetItemPtr(LIST_CHATROOM, roomJid))
		return;

	if (m_options.AutoAcceptMUC) {
		ptrW nick(getWStringA(HContactFromJID(m_szJabberJID), "MyNick"));
		if (nick == NULL)
			nick = getWStringA("Nick");
		if (nick == NULL)
			nick = JabberNickFromJID(m_szJabberJID);
		AcceptGroupchatInvite(roomJid, nick, password);
	}
	else CallFunctionAsync(sttShowDialog, new CGroupchatInviteAcceptDlg(this, roomJid, from, reason, password));
}

void CJabberProto::AcceptGroupchatInvite(const wchar_t *roomJid, const wchar_t *reason, const wchar_t *password)
{
	wchar_t room[256], *server, *p;
	wcsncpy_s(room, roomJid, _TRUNCATE);
	p = wcstok(room, L"@");
	server = wcstok(NULL, L"@");
	GroupchatJoinRoom(server, p, reason, password);
}