/*

Jabber Protocol Plugin for Miranda NG

Copyright (c) 2002-04  Santithorn Bunchua
Copyright (c) 2005-12  George Hazan
Copyright (c) 2007     Maxim Mluhov
Copyright (c) 2007     Victor Pavlychko
Copyright (C) 2012-19 Miranda NG team

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"

#define JCPF_IN    0x01UL
#define JCPF_OUT   0x02UL
#define JCPF_ERROR 0x04UL

#define JCPF_TCHAR 0x00UL

#define WM_CREATECONSOLE  WM_USER+1000

#ifndef SES_EXTENDBACKCOLOR
#define	SES_EXTENDBACKCOLOR 4
#endif

/* increment buffer with 1K steps */
#define STRINGBUF_INCREMENT		1024

struct StringBuf
{
	char *buf;
	size_t size, offset, streamOffset;
};

static void sttAppendBufRaw(StringBuf *buf, const char *str);
static void sttAppendBufW(StringBuf *buf, const WCHAR *str);
#define sttAppendBufT(a,b)		(sttAppendBufW((a),(b)))
static void sttEmptyBuf(StringBuf *buf);

#define RTF_HEADER	\
			"{\\rtf1\\ansi{\\colortbl;"	\
				"\\red128\\green0\\blue0;"	\
				"\\red0\\green0\\blue128;"	\
				"\\red245\\green255\\blue245;"	\
				"\\red245\\green245\\blue255;"	\
				"\\red128\\green128\\blue128;"	\
				"\\red255\\green235\\blue235;"	\
			"}"
#define RTF_FOOTER			"}"
#define RTF_BEGINTAG		"\\pard "
#define RTF_INDENT_FMT		"\\fi-100\\li%d "
#define RTF_ENDTAG			"\\par"
#define RTF_BEGINTAGNAME	"\\cf1\\b "
#define RTF_ENDTAGNAME		"\\cf0\\b0 "
#define RTF_BEGINATTRNAME	"\\cf2\\b "
#define RTF_ENDATTRNAME		"\\cf0\\b0 "
#define RTF_BEGINATTRVAL	"\\b0 "
#define RTF_ENDATTRVAL		""
#define RTF_BEGINTEXT		"\\pard "
#define RTF_TEXTINDENT_FMT	"\\fi0\\li%d "
#define RTF_ENDTEXT			"\\par"
#define RTF_BEGINPLAINXML	"\\pard\\fi0\\li100\\highlight6\\cf0 "
#define RTF_ENDPLAINXML		"\\par"
#define RTF_SEPARATOR		"\\sl-1\\slmult0\\highlight5\\cf5\\-\\par\\sl0"

static void sttRtfAppendXml(StringBuf *buf, HXML node, DWORD flags, int indent);

void CJabberProto::OnConsoleProcessXml(HXML node, DWORD flags)
{
	if (node && m_pDlgConsole) {
		if (XmlGetName(node)) {
			if (FilterXml(node, flags)) {
				StringBuf buf = {};
				sttAppendBufRaw(&buf, RTF_HEADER);
				sttRtfAppendXml(&buf, node, flags, 1);
				sttAppendBufRaw(&buf, RTF_SEPARATOR);
				sttAppendBufRaw(&buf, RTF_FOOTER);
				SendMessage(m_pDlgConsole->GetHwnd(), WM_JABBER_REFRESH, 0, (LPARAM)&buf);
				sttEmptyBuf(&buf);
			}
		}
		else {
			for (int i=0; i < XmlGetChildCount(node); i++)
				OnConsoleProcessXml(XmlGetChild(node, i), flags);
		}
	}
}

bool CJabberProto::RecursiveCheckFilter(HXML node, DWORD flags)
{
	for (int i = 0; i < XmlGetAttrCount(node); i++)
		if (JabberStrIStr(XmlGetAttr(node, i), m_filterInfo.pattern))
			return true;

	for (int i = 0; i < XmlGetChildCount(node); i++)
		if (RecursiveCheckFilter(XmlGetChild(node, i), flags))
			return true;

	return false;
}

bool CJabberProto::FilterXml(HXML node, DWORD flags)
{
	if (!m_filterInfo.msg && !mir_wstrcmp(XmlGetName(node), L"message")) return false;
	if (!m_filterInfo.presence && !mir_wstrcmp(XmlGetName(node), L"presence")) return false;
	if (!m_filterInfo.iq && !mir_wstrcmp(XmlGetName(node), L"iq")) return false;
	if (m_filterInfo.type == TFilterInfo::T_OFF) return true;

	mir_cslock lck(m_filterInfo.csPatternLock);

	const wchar_t *attrValue;
	switch (m_filterInfo.type) {
	case TFilterInfo::T_JID:
		attrValue = XmlGetAttrValue(node, (flags & JCPF_OUT) ? L"to" : L"from");
		if (attrValue)
			return JabberStrIStr(attrValue, m_filterInfo.pattern) != nullptr;
		break;

	case TFilterInfo::T_XMLNS:
		if (XmlGetChildCount(node)) {
			attrValue = XmlGetAttrValue(XmlGetChild(node, 0), L"xmlns");
			if (attrValue)
				return JabberStrIStr(attrValue, m_filterInfo.pattern) != nullptr;
		}
		break;

	case TFilterInfo::T_ANY:
		return RecursiveCheckFilter(node, flags);
	}

	return false;
}

static void sttAppendBufRaw(StringBuf *buf, const char *str)
{
	if (!str) return;

	size_t length = mir_strlen(str);
	if (buf->size - buf->offset < length + 1) {
		buf->size += (length + STRINGBUF_INCREMENT);
		buf->buf = (char *)mir_realloc(buf->buf, buf->size);
	}
	mir_strcpy(buf->buf + buf->offset, str);
	buf->offset += length;
}

static void sttAppendBufW(StringBuf *buf, const WCHAR *str)
{
	char tmp[32];

	if (!str) return;

	sttAppendBufRaw(buf, "{\\uc1 ");
	for (const WCHAR *p = str; *p; ++p) {
		if ((*p == '\\') || (*p == '{') || (*p == '}')) {
			tmp[0] = '\\';
			tmp[1] = (char)*p;
			tmp[2] = 0;
		}
		else if (*p < 128) {
			tmp[0] = (char)*p;
			tmp[1] = 0;
		}
		else mir_snprintf(tmp, "\\u%d ?", (int)*p);

		sttAppendBufRaw(buf, tmp);
	}
	sttAppendBufRaw(buf, "}");
}

static void sttEmptyBuf(StringBuf *buf)
{
	if (buf->buf) mir_free(buf->buf);
	buf->buf = nullptr;
	buf->size = 0;
	buf->offset = 0;
}

static void sttRtfAppendXml(StringBuf *buf, HXML node, DWORD flags, int indent)
{
	char indentLevel[128];
	mir_snprintf(indentLevel, RTF_INDENT_FMT, (int)(indent*200));

	sttAppendBufRaw(buf, RTF_BEGINTAG);
	sttAppendBufRaw(buf, indentLevel);
	if (flags&JCPF_IN)	sttAppendBufRaw(buf, "\\highlight3 ");
	if (flags&JCPF_OUT)	sttAppendBufRaw(buf, "\\highlight4 ");
	sttAppendBufRaw(buf, "<");
	sttAppendBufRaw(buf, RTF_BEGINTAGNAME);
	sttAppendBufW(buf, (wchar_t*)XmlGetName(node));
	sttAppendBufRaw(buf, RTF_ENDTAGNAME);

	for (int i = 0; i < XmlGetAttrCount(node); i++) {
		wchar_t *attr = (wchar_t*)xmlGetAttrName(node, i);
		sttAppendBufRaw(buf, " ");
		sttAppendBufRaw(buf, RTF_BEGINATTRNAME);
		sttAppendBufW(buf, attr);
		sttAppendBufRaw(buf, RTF_ENDATTRNAME);
		sttAppendBufRaw(buf, "=\"");
		sttAppendBufRaw(buf, RTF_BEGINATTRVAL);
		sttAppendBufT(buf, (wchar_t*)XmlGetAttr(node, i));
		sttAppendBufRaw(buf, "\"");
		sttAppendBufRaw(buf, RTF_ENDATTRVAL);
	}

	if (XmlGetChild(node) || XmlGetText(node)) {
		sttAppendBufRaw(buf, ">");
		if (XmlGetChild(node))
			sttAppendBufRaw(buf, RTF_ENDTAG);
	}

	if (XmlGetText(node)) {
		if (XmlGetChildCount(node)) {
			sttAppendBufRaw(buf, RTF_BEGINTEXT);
			char indentTextLevel[128];
			mir_snprintf(indentTextLevel, RTF_TEXTINDENT_FMT, (int)((indent + 1) * 200));
			sttAppendBufRaw(buf, indentTextLevel);
		}

		sttAppendBufT(buf, XmlGetText(node));
		if (XmlGetChild(node))
			sttAppendBufRaw(buf, RTF_ENDTEXT);
	}

	for (int i = 0; i < XmlGetChildCount(node); i++)
		sttRtfAppendXml(buf, XmlGetChild(node, i), flags & ~(JCPF_IN | JCPF_OUT), indent + 1);

	if (XmlGetChildCount(node) || XmlGetText(node)) {
		sttAppendBufRaw(buf, RTF_BEGINTAG);
		sttAppendBufRaw(buf, indentLevel);
		sttAppendBufRaw(buf, "</");
		sttAppendBufRaw(buf, RTF_BEGINTAGNAME);
		sttAppendBufT(buf, XmlGetName(node));
		sttAppendBufRaw(buf, RTF_ENDTAGNAME);
		sttAppendBufRaw(buf, ">");
	}
	else sttAppendBufRaw(buf, " />");

	sttAppendBufRaw(buf, RTF_ENDTAG);
}

DWORD CALLBACK sttStreamInCallback(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
	StringBuf *buf = (StringBuf *)dwCookie;

	if (buf->streamOffset < buf->offset) {
		size_t cbLen = min(size_t(cb), buf->offset - buf->streamOffset);
		memcpy(pbBuff, buf->buf + buf->streamOffset, cbLen);
		buf->streamOffset += cbLen;
		*pcb = (LONG)cbLen;
	}
	else *pcb = 0;

	return 0;
}

static void sttJabberConsoleRebuildStrings(CJabberProto *ppro, HWND hwndCombo)
{
	JABBER_LIST_ITEM *item = nullptr;

	int len = GetWindowTextLength(hwndCombo) + 1;
	wchar_t *buf = (wchar_t *)_alloca(len * sizeof(wchar_t));
	GetWindowText(hwndCombo, buf, len);

	SendMessage(hwndCombo, CB_RESETCONTENT, 0, 0);

	for (int i = 0; i < g_cJabberFeatCapPairs; i++)
		SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM)g_JabberFeatCapPairs[i].szFeature);
	for (int i = 0; i < g_cJabberFeatCapPairsExt; i++)
		SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM)g_JabberFeatCapPairsExt[i].szFeature);

	LISTFOREACH(i, ppro, LIST_ROSTER)
		if (item = ppro->ListGetItemPtrFromIndex(i))
			SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM)item->jid);
	LISTFOREACH(i, ppro, LIST_CHATROOM)
		if (item = ppro->ListGetItemPtrFromIndex(i))
			SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM)item->jid);

	SetWindowText(hwndCombo, buf);
}

///////////////////////////////////////////////////////////////////////////////
// CJabberDlgConsole class

struct
{
	int type;
	wchar_t *title;
	char *icon;
}
static filter_modes[] =
{
	{ TFilterInfo::T_JID,   L"JID",            "main" },
	{ TFilterInfo::T_XMLNS, L"xmlns",          "xmlconsole" },
	{ TFilterInfo::T_ANY,   L"all attributes", "sd_filter_apply" },
	{ TFilterInfo::T_OFF,   L"disabled",       "sd_filter_reset" },
};

class CJabberDlgConsole: public CJabberDlgBase
{
	typedef CJabberDlgBase CSuper;

public:
	CJabberDlgConsole(CJabberProto *proto);

protected:
	bool OnInitDialog() override;
	bool OnClose() override;
	void OnDestroy() override;
	int Resizer(UTILRESIZECONTROL *urc) override;
	INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) override;

	void OnProtoRefresh(WPARAM wParam, LPARAM lParam);
};

CJabberDlgConsole::CJabberDlgConsole(CJabberProto *proto):
	CJabberDlgBase(proto, IDD_CONSOLE)
{
}

bool CJabberDlgConsole::OnInitDialog()
{
	CSuper::OnInitDialog();

	Window_SetIcon_IcoLib(m_hwnd, g_GetIconHandle(IDI_CONSOLE));
	SendDlgItemMessage(m_hwnd, IDC_CONSOLE, EM_SETEDITSTYLE, SES_EXTENDBACKCOLOR, SES_EXTENDBACKCOLOR);
	SendDlgItemMessage(m_hwnd, IDC_CONSOLE, EM_EXLIMITTEXT, 0, 0x80000000);

	m_proto->m_filterInfo.msg = m_proto->getByte("consoleWnd_msg", TRUE);
	m_proto->m_filterInfo.presence = m_proto->getByte("consoleWnd_presence", TRUE);
	m_proto->m_filterInfo.iq = m_proto->getByte("consoleWnd_iq", TRUE);
	m_proto->m_filterInfo.type = (TFilterInfo::Type)m_proto->getByte("consoleWnd_ftype", TFilterInfo::T_OFF);

	*m_proto->m_filterInfo.pattern = 0;
	ptrW tszPattern( m_proto->getWStringA("consoleWnd_fpattern"));
	if (tszPattern != nullptr)
		mir_wstrncpy(m_proto->m_filterInfo.pattern, tszPattern, _countof(m_proto->m_filterInfo.pattern));

	sttJabberConsoleRebuildStrings(m_proto, GetDlgItem(m_hwnd, IDC_CB_FILTER));
	SetDlgItemText(m_hwnd, IDC_CB_FILTER, m_proto->m_filterInfo.pattern);

	struct
	{
		int idc;
		char *title;
		char *icon;
		bool push;
		BOOL pushed;
	}
	static buttons[] =
	{
		{ IDC_BTN_MSG,            "Messages",     "pl_msg_allow",    true,  m_proto->m_filterInfo.msg},
		{ IDC_BTN_PRESENCE,       "Presences",    "pl_prin_allow",   true,  m_proto->m_filterInfo.presence},
		{ IDC_BTN_IQ,             "Queries",      "pl_iq_allow",     true,  m_proto->m_filterInfo.iq},
		{ IDC_BTN_FILTER,         "Filter mode",  "sd_filter_apply", true,  FALSE},
		{ IDC_BTN_FILTER_REFRESH, "Refresh list", "sd_nav_refresh",  false, FALSE},
	};

	for (auto &it : buttons) {
		SendDlgItemMessage(m_hwnd, it.idc, BM_SETIMAGE, IMAGE_ICON, (LPARAM)m_proto->LoadIconEx(it.icon));
		SendDlgItemMessage(m_hwnd, it.idc, BUTTONSETASFLATBTN, TRUE, 0);
		SendDlgItemMessage(m_hwnd, it.idc, BUTTONADDTOOLTIP, (WPARAM)it.title, 0);
		if (it.push)
			SendDlgItemMessage(m_hwnd, it.idc, BUTTONSETASPUSHBTN, TRUE, 0);
		if (it.pushed)
			CheckDlgButton(m_hwnd, it.idc, BST_CHECKED);
	}

	for (auto &it : filter_modes)
		if (it.type == m_proto->m_filterInfo.type) {
			IcoLib_ReleaseIcon((HICON)SendDlgItemMessage(m_hwnd, IDC_BTN_FILTER, BM_SETIMAGE, IMAGE_ICON, (LPARAM)m_proto->LoadIconEx(it.icon)));
			SendDlgItemMessage(m_hwnd, IDC_BTN_FILTER, BM_SETIMAGE, IMAGE_ICON, (LPARAM)m_proto->LoadIconEx(it.icon));
			break;
		}

	EnableWindow(GetDlgItem(m_hwnd, IDC_CB_FILTER), (m_proto->m_filterInfo.type == TFilterInfo::T_OFF) ? FALSE : TRUE);
	EnableWindow(GetDlgItem(m_hwnd, IDC_BTN_FILTER_REFRESH), (m_proto->m_filterInfo.type == TFilterInfo::T_OFF) ? FALSE : TRUE);

	Utils_RestoreWindowPosition(m_hwnd, 0, m_proto->m_szModuleName, "consoleWnd_");
	return true;
}

bool CJabberDlgConsole::OnClose()
{
	m_proto->setByte("consoleWnd_msg", m_proto->m_filterInfo.msg);
	m_proto->setByte("consoleWnd_presence", m_proto->m_filterInfo.presence);
	m_proto->setByte("consoleWnd_iq", m_proto->m_filterInfo.iq);
	m_proto->setByte("consoleWnd_ftype", m_proto->m_filterInfo.type);
	m_proto->setWString("consoleWnd_fpattern", m_proto->m_filterInfo.pattern);

	Utils_SaveWindowPosition(m_hwnd, 0, m_proto->m_szModuleName, "consoleWnd_");
	DestroyWindow(m_hwnd);
	return CSuper::OnClose();
}

void CJabberDlgConsole::OnDestroy()
{
	IcoLib_ReleaseIcon((HICON)SendDlgItemMessage(m_hwnd, IDC_BTN_MSG, BM_SETIMAGE, IMAGE_ICON, 0));
	IcoLib_ReleaseIcon((HICON)SendDlgItemMessage(m_hwnd, IDC_BTN_PRESENCE, BM_SETIMAGE, IMAGE_ICON, 0));
	IcoLib_ReleaseIcon((HICON)SendDlgItemMessage(m_hwnd, IDC_BTN_IQ, BM_SETIMAGE, IMAGE_ICON, 0));
	IcoLib_ReleaseIcon((HICON)SendDlgItemMessage(m_hwnd, IDC_BTN_FILTER, BM_SETIMAGE, IMAGE_ICON, 0));
	IcoLib_ReleaseIcon((HICON)SendDlgItemMessage(m_hwnd, IDC_BTN_FILTER_REFRESH, BM_SETIMAGE, IMAGE_ICON, 0));

	m_proto->m_pDlgConsole = nullptr;
	CSuper::OnDestroy();
}

void CJabberDlgConsole::OnProtoRefresh(WPARAM, LPARAM lParam)
{
	SendDlgItemMessage(m_hwnd, IDC_CONSOLE, WM_SETREDRAW, FALSE, 0);

	StringBuf *buf = (StringBuf *)lParam;
	buf->streamOffset = 0;

	EDITSTREAM es = {0};
	es.dwCookie = (DWORD_PTR)buf;
	es.pfnCallback = sttStreamInCallback;

	SCROLLINFO si = {0};
	si.cbSize = sizeof(si);
	si.fMask = SIF_ALL;
	GetScrollInfo(GetDlgItem(m_hwnd, IDC_CONSOLE), SB_VERT, &si);

	CHARRANGE oldSel, sel;
	POINT ptScroll;
	SendDlgItemMessage(m_hwnd, IDC_CONSOLE, EM_GETSCROLLPOS, 0, (LPARAM)&ptScroll);
	SendDlgItemMessage(m_hwnd, IDC_CONSOLE, EM_EXGETSEL, 0, (LPARAM)&oldSel);
	sel.cpMin = sel.cpMax = GetWindowTextLength(GetDlgItem(m_hwnd, IDC_CONSOLE));
	SendDlgItemMessage(m_hwnd, IDC_CONSOLE, EM_EXSETSEL, 0, (LPARAM)&sel);
	SendDlgItemMessage(m_hwnd, IDC_CONSOLE, EM_STREAMIN, SF_RTF|SFF_SELECTION, (LPARAM)&es);
	SendDlgItemMessage(m_hwnd, IDC_CONSOLE, EM_EXSETSEL, 0, (LPARAM)&oldSel);

	// magic expression from tabsrmm :)
	if ((UINT)si.nPos >= (UINT)si.nMax - si.nPage - 5 || si.nMax - si.nMin - si.nPage < 50) {
		SendDlgItemMessage(m_hwnd, IDC_CONSOLE, WM_VSCROLL, MAKEWPARAM(SB_BOTTOM, 0), 0);
		sel.cpMin = sel.cpMax = GetWindowTextLength(GetDlgItem(m_hwnd, IDC_CONSOLE));
		SendDlgItemMessage(m_hwnd, IDC_CONSOLE, EM_EXSETSEL, 0, (LPARAM)&sel);
	}
	else SendDlgItemMessage(m_hwnd, IDC_CONSOLE, EM_SETSCROLLPOS, 0, (LPARAM)&ptScroll);

	SendDlgItemMessage(m_hwnd, IDC_CONSOLE, WM_SETREDRAW, TRUE, 0);
	InvalidateRect(GetDlgItem(m_hwnd, IDC_CONSOLE), nullptr, FALSE);
}

int CJabberDlgConsole::Resizer(UTILRESIZECONTROL *urc)
{
	switch (urc->wId) {
	case IDC_CONSOLE:
		return RD_ANCHORX_WIDTH | RD_ANCHORY_HEIGHT;
	case IDC_CONSOLEIN:
		return RD_ANCHORX_WIDTH | RD_ANCHORY_BOTTOM;

	case IDC_BTN_MSG:
	case IDC_BTN_PRESENCE:
	case IDC_BTN_IQ:
	case IDC_BTN_FILTER:
		return RD_ANCHORX_LEFT | RD_ANCHORY_BOTTOM;

	case IDC_RESET:
	case IDOK:
	case IDC_BTN_FILTER_REFRESH:
		return RD_ANCHORX_RIGHT | RD_ANCHORY_BOTTOM;

	case IDC_CB_FILTER:
		RECT rc;
		GetWindowRect(GetDlgItem(m_hwnd, urc->wId), &rc);
		urc->rcItem.right += (urc->dlgNewSize.cx - urc->dlgOriginalSize.cx);
		urc->rcItem.top += (urc->dlgNewSize.cy - urc->dlgOriginalSize.cy);
		urc->rcItem.bottom = urc->rcItem.top + rc.bottom - rc.top;
		return 0;
	}
	return CSuper::Resizer(urc);
}

INT_PTR CJabberDlgConsole::DlgProc(UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg) {
	case WM_GETMINMAXINFO:
		{
			LPMINMAXINFO lpmmi = (LPMINMAXINFO)lParam;
			lpmmi->ptMinTrackSize.x = 300;
			lpmmi->ptMinTrackSize.y = 400;
			return 0;
		}

	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDOK:
			if (!m_proto->m_bJabberOnline)
				MessageBox(m_hwnd, TranslateT("Can't send data while you are offline."), TranslateT("Jabber Error"), MB_ICONSTOP | MB_OK);
			else {
				int length = GetWindowTextLength(GetDlgItem(m_hwnd, IDC_CONSOLEIN)) + 1;
				wchar_t *textToSend = (wchar_t *)mir_alloc(length * sizeof(wchar_t));
				GetDlgItemText(m_hwnd, IDC_CONSOLEIN, textToSend, length);

				int bytesProcessed = 0;
				XmlNode xmlTmp(textToSend, &bytesProcessed, nullptr);
				if (xmlTmp)
					m_proto->m_ThreadInfo->send(xmlTmp);
				else {
					StringBuf buf = {};
					sttAppendBufRaw(&buf, RTF_HEADER);
					sttAppendBufRaw(&buf, RTF_BEGINPLAINXML);
					sttAppendBufT(&buf, TranslateT("Outgoing XML parsing error"));
					sttAppendBufRaw(&buf, RTF_ENDPLAINXML);
					sttAppendBufRaw(&buf, RTF_SEPARATOR);
					sttAppendBufRaw(&buf, RTF_FOOTER);
					SendMessage(m_hwnd, WM_JABBER_REFRESH, 0, (LPARAM)&buf);
					sttEmptyBuf(&buf);
				}

				mir_free(textToSend);

				SetDlgItemText(m_hwnd, IDC_CONSOLEIN, L"");
			}
			return TRUE;

		case IDC_RESET:
			SetDlgItemText(m_hwnd, IDC_CONSOLE, L"");
			break;

		case IDC_BTN_MSG:
		case IDC_BTN_PRESENCE:
		case IDC_BTN_IQ:
			m_proto->m_filterInfo.msg = IsDlgButtonChecked(m_hwnd, IDC_BTN_MSG);
			m_proto->m_filterInfo.presence = IsDlgButtonChecked(m_hwnd, IDC_BTN_PRESENCE);
			m_proto->m_filterInfo.iq = IsDlgButtonChecked(m_hwnd, IDC_BTN_IQ);
			break;

		case IDC_BTN_FILTER_REFRESH:
			sttJabberConsoleRebuildStrings(m_proto, GetDlgItem(m_hwnd, IDC_CB_FILTER));
			break;

		case IDC_CB_FILTER:
			if (HIWORD(wParam) == CBN_SELCHANGE) {
				int idx = SendDlgItemMessage(m_hwnd, IDC_CB_FILTER, CB_GETCURSEL, 0, 0);
				int len = SendDlgItemMessage(m_hwnd, IDC_CB_FILTER, CB_GETLBTEXTLEN, idx, 0) + 1;

				mir_cslock lck(m_proto->m_filterInfo.csPatternLock);

				if (len > _countof(m_proto->m_filterInfo.pattern)) {
					wchar_t *buf = (wchar_t *)_alloca(len * sizeof(wchar_t));
					SendDlgItemMessage(m_hwnd, IDC_CB_FILTER, CB_GETLBTEXT, idx, (LPARAM)buf);
					mir_wstrncpy(m_proto->m_filterInfo.pattern, buf, _countof(m_proto->m_filterInfo.pattern));
				}
				else SendDlgItemMessage(m_hwnd, IDC_CB_FILTER, CB_GETLBTEXT, idx, (LPARAM)m_proto->m_filterInfo.pattern);
			}
			else if (HIWORD(wParam) == CBN_EDITCHANGE) {
				mir_cslock lck(m_proto->m_filterInfo.csPatternLock);
				GetDlgItemText(m_hwnd, IDC_CB_FILTER, m_proto->m_filterInfo.pattern, _countof(m_proto->m_filterInfo.pattern));
			}
			break;

		case IDC_BTN_FILTER:
			HMENU hMenu = CreatePopupMenu();
			for (auto &it : filter_modes)
				AppendMenu(hMenu, MF_STRING | ((it.type == m_proto->m_filterInfo.type) ? MF_CHECKED : 0), it.type + 1, TranslateW(it.title));

			RECT rc; GetWindowRect(GetDlgItem(m_hwnd, IDC_BTN_FILTER), &rc);
			CheckDlgButton(m_hwnd, IDC_BTN_FILTER, BST_CHECKED);
			int res = TrackPopupMenu(hMenu, TPM_RETURNCMD | TPM_BOTTOMALIGN, rc.left, rc.top, 0, m_hwnd, nullptr);
			CheckDlgButton(m_hwnd, IDC_BTN_FILTER, BST_UNCHECKED);
			DestroyMenu(hMenu);

			if (res) {
				m_proto->m_filterInfo.type = (TFilterInfo::Type)(res - 1);
				for (auto &it : filter_modes) {
					if (it.type == m_proto->m_filterInfo.type) {
						IcoLib_ReleaseIcon((HICON)SendDlgItemMessage(m_hwnd, IDC_BTN_FILTER, BM_SETIMAGE, IMAGE_ICON, (LPARAM)m_proto->LoadIconEx(it.icon)));
						break;
					}
				}
				EnableWindow(GetDlgItem(m_hwnd, IDC_CB_FILTER), (m_proto->m_filterInfo.type == TFilterInfo::T_OFF) ? FALSE : TRUE);
				EnableWindow(GetDlgItem(m_hwnd, IDC_BTN_FILTER_REFRESH), (m_proto->m_filterInfo.type == TFilterInfo::T_OFF) ? FALSE : TRUE);
			}
		}
		break;
	}

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

void __cdecl CJabberProto::ConsoleThread(void*)
{
	Thread_SetName("Jabber: ConsoleThread");

	MSG msg;
	while (GetMessage(&msg, nullptr, 0, 0)) {
		if (msg.message == WM_CREATECONSOLE) {
			m_pDlgConsole = new CJabberDlgConsole(this);
			m_pDlgConsole->Show();
			continue;
		}

		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	m_dwConsoleThreadId = 0;
}

void CJabberProto::ConsoleInit()
{
	m_hThreadConsole = ForkThreadEx(&CJabberProto::ConsoleThread, nullptr, &m_dwConsoleThreadId);
}

void CJabberProto::ConsoleUninit()
{
	if (m_hThreadConsole) {
		PostThreadMessage(m_dwConsoleThreadId, WM_QUIT, 0, 0);
		if (WaitForSingleObject(m_hThreadConsole, 5000) == WAIT_TIMEOUT) {
			TerminateThread(m_hThreadConsole, 0);
		}
		CloseHandle(m_hThreadConsole);
		m_hThreadConsole = nullptr;
	}

	m_filterInfo.iq = m_filterInfo.msg = m_filterInfo.presence = FALSE;
	m_filterInfo.type = TFilterInfo::T_OFF;
}

INT_PTR __cdecl CJabberProto::OnMenuHandleConsole(WPARAM, LPARAM)
{
	if (m_pDlgConsole)
		SetForegroundWindow(m_pDlgConsole->GetHwnd());
	else if (m_hThreadConsole)
		PostThreadMessage(m_dwConsoleThreadId, WM_CREATECONSOLE, 0, 0);
	return 0;
}