/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2008 Miranda ICQ/IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.

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

#define MS_CONSOLE_SHOW_HIDE "Console/Show/Hide"

#define DEFAULT_WRAPLEN 90
#define MIN_WRAPLEN     25
#define MAX_WRAPLEN     255

#define MIN_LIMIT       1000
#define MAX_LIMIT       1000000

#define IMG_EMPTY       0
#define IMG_ARROW       1
#define IMG_IN          2
#define IMG_OUT         3
#define IMG_INFO        4

#define LOGICONX_SIZE  10
#define LOGICONY_SIZE  10

#define ICON_FIRST     3

#define ICON_NOSCROLL  1
#define ICON_PAUSED    2
#define ICON_SCROLL    3
#define ICON_STARTED   4

#define HM_DUMP        (WM_USER+10)
#define HM_ADD         (WM_USER+11)
#define HM_REMOVE      (WM_USER+12)
#define HM_SETFONT     (WM_USER+13)
#define HM_SETCOLOR    (WM_USER+14)
#define HM_PAUSEALL    (WM_USER+15)
#define HM_RESTART     (WM_USER+16)


typedef struct {
	const char* pszHead;
	const char* pszMsg;
} LOGMSG;


typedef struct {
	char szModule[128];
	TCHAR szMsg[1];
} DUMPMSG;


typedef struct {
	HWND hwnd;
	HWND hList;
	char *Module;
	int Scroll;
	int Paused;
	int newline;
} LOGWIN;



static SortedList lModules = {0};

static LOGWIN *pActive = NULL;
static int tabCount = 0;
static RECT rcTabs = {0};
static HWND hTabs = NULL;
static HWND hwndConsole = NULL;

static HIMAGELIST gImg = NULL;
static HFONT hfLogFont = NULL;

static int gIcons = 0;
static int gVisible = 0;
static int gSingleMode = 0;
static int gLimit = 0;
static int gSeparator = 0;

static DWORD gWrapLen = DEFAULT_WRAPLEN;

static DWORD OutMsgs = 0;
static DWORD InMsgs = 0;

static HICON hIcons[15] = {0};
static HGENMENU hMenu = NULL;

static void LoadSettings();
static void ShowConsole(int show);
static INT_PTR ShowHideConsole(WPARAM wParam, LPARAM lParam);
static int Openfile(TCHAR *outputFile, int selection);

////////////////////////////////////////////////////////////////////////////////

static HANDLE hTTBButt = 0;

static IconItem iconList[] = 
{
	{ "Show", "Console_Up",   IDI_BTN_UP },
	{ "Hide", "Console_Down", IDI_BTN_DN },
};

static int OnTTBLoaded(WPARAM wParam,LPARAM lParam)
{
	int state = IsWindowVisible(hwndConsole);

	Icon_Register(hInst, "Console", iconList, SIZEOF(iconList));

	TTBButton ttb = { sizeof(ttb) };
	ttb.hIconHandleUp = iconList[0].hIcolib;
	ttb.hIconHandleDn = iconList[1].hIcolib;
	ttb.dwFlags = (state ? TTBBF_PUSHED : 0) | TTBBF_VISIBLE | TTBBF_SHOWTOOLTIP;
	ttb.pszService = MS_CONSOLE_SHOW_HIDE;
	ttb.name = LPGEN("Show/Hide Console");
	ttb.pszTooltipDn = LPGEN("Hide Console");
	ttb.pszTooltipUp = LPGEN("Show Console");
	hTTBButt = TopToolbar_AddButton(&ttb);
	return 0;
}

////////////////////////////////////////////////////////////////////////////////

void ScrollDown(LOGWIN * dat) {
	if (dat->Scroll)
		ListView_EnsureVisible(dat->hList, ListView_GetItemCount(dat->hList) - 1, FALSE);
}

////////////////////////////////////////////////////////////////////////////////

static void ShowConsole(int show)
{
	HWND hwnd = NULL;

	if (!hwndConsole || !pActive) return;

	gVisible = show;

	if (show) {
		hwnd = GetForegroundWindow();
		if ( InMsgs == OutMsgs )
			ScrollDown( pActive );
	}
	ShowWindow(hwndConsole, (show)?SW_SHOW:SW_HIDE);
	db_set_b(NULL,"Console","Show",(BYTE)((show)?1:0));

	if (hwnd)
		SetForegroundWindow(hwnd);

	if (show)
		RedrawWindow(pActive->hList, NULL, NULL, RDW_INVALIDATE | RDW_FRAME |RDW_UPDATENOW | RDW_ERASE);

	if (hMenu) {
		CLISTMENUITEM mi = { sizeof(mi) };
		mi.ptszName = (show) ? LPGENT("Hide Console") : LPGENT("Show Console");
		mi.flags = CMIM_NAME | CMIF_TCHAR;
		Menu_ModifyItem(hMenu, &mi);
	}

	if (hTTBButt)
		CallService(MS_TTB_SETBUTTONSTATE, (WPARAM)hTTBButt, (show)?TTBST_PUSHED:TTBST_RELEASED);
}

////////////////////////////////////////////////////////////////////////////////

static INT_PTR ShowHideConsole(WPARAM wParam, LPARAM lParam)
{
	if (hwndConsole)
		ShowConsole(!IsWindowVisible(hwndConsole));

	return 0;
}

////////////////////////////////////////////////////////////////////////////////

int LogResize(HWND hwnd,LPARAM lParam,UTILRESIZECONTROL *urc)
{
	switch(urc->wId) {
	case IDC_LIST:
		ListView_SetColumnWidth(GetDlgItem(hwnd, IDC_LIST), 0, urc->dlgNewSize.cx - 25);
		return RD_ANCHORX_WIDTH|RD_ANCHORY_HEIGHT;
	case IDC_STARTALL:
	case IDC_PAUSEALL:
	case IDC_CLOSE:
		return RD_ANCHORX_RIGHT|RD_ANCHORY_TOP;
	default:
		return RD_ANCHORX_LEFT|RD_ANCHORY_TOP;
	}
}

////////////////////////////////////////////////////////////////////////////////

typedef struct
{
	UINT control;
	UINT icon;
	int  type;
	TCHAR *tooltip;
} controlinfo;


static controlinfo ctrls[] =
{
	// IDC_SCROLL & IDC_PAUSE must be first
	{IDC_SCROLL, IDI_SCROLL, BUTTONSETASFLATBTN, LPGENT("Scrolling (Ctrl+Q)")},
	{IDC_PAUSE, IDI_STARTED, BUTTONSETASFLATBTN, LPGENT("Pause logging(Ctrl+P)")},
	{IDC_SAVE, IDI_SAVE, BUTTONSETASFLATBTN, LPGENT("Save log to file (Ctrl+S)")},
	{IDC_COPY, IDI_COPY, BUTTONSETASFLATBTN, LPGENT("Copy selected log (Ctrl+C)")},
	{IDC_DELETE, IDI_DELETE, BUTTONSETASFLATBTN, LPGENT("Delete selected (Del)")},
	{IDC_OPTIONS, IDI_OPTIONS, BUTTONSETASFLATBTN, LPGENT("Log options (Ctrl+O)")},
	{IDC_STARTALL, IDI_START, BUTTONSETASFLATBTN, LPGENT("Start logging in all tabs")},
	{IDC_PAUSEALL, IDI_PAUSE, BUTTONSETASFLATBTN, LPGENT("Pause logging in all tabs")},
	{IDC_CLOSE, IDI_CLOSE, BUTTONSETASFLATBTN, LPGENT("Close tab (Ctrl+W)")},
};

////////////////////////////////////////////////////////////////////////////////

static LRESULT CALLBACK SubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
	int ctrl;

	switch (msg) {
	case WM_KEYDOWN:
		ctrl = GetKeyState(VK_CONTROL) & 0x8000;
		if ( wParam == VK_DELETE && !ctrl) {
			SendMessage(GetParent(hwnd), WM_COMMAND, IDC_DELETE, 0);
			break;
		}

		if (wParam == VK_LEFT && ctrl)
		{
			NMHDR nmhdr = {0};
			int tab = TabCtrl_GetCurSel(hTabs);

			if (tab == 0)
				tab = TabCtrl_GetItemCount(hTabs)-1;
			else
				tab--;

			TabCtrl_SetCurSel(hTabs, tab);
			nmhdr.code = TCN_SELCHANGE;
			SendMessage(hwndConsole, WM_NOTIFY, IDC_TABS, (LPARAM)&nmhdr);
			break;
		}

		if (wParam == VK_RIGHT && ctrl)
		{
			NMHDR nmhdr = {0};
			int tab = TabCtrl_GetCurSel(hTabs);
			int count =  TabCtrl_GetItemCount(hTabs);
			tab = (tab + 1) % count;

			TabCtrl_SetCurSel(hTabs, tab);
			nmhdr.code = TCN_SELCHANGE;
			SendMessage(hwndConsole, WM_NOTIFY, IDC_TABS, (LPARAM)&nmhdr);
			break;
		}

		break;

	case WM_CHAR:
		// CTRL
		if ( !(GetKeyState(VK_CONTROL)&0x8000))
			break;

		switch(wParam) {

		case 1:	// Ctrl+A
			if ( ListView_GetSelectedCount(hwnd) != (UINT)ListView_GetItemCount(hwnd))
				ListView_SetItemState(hwnd, -1, LVIS_SELECTED, LVIS_SELECTED);
			return 0;

		case 3: // Ctrl+D
			SendMessage(GetParent(hwnd), WM_COMMAND, IDC_COPY, 0);
			return 0;

		case 15: // Ctrl+O
			SendMessage(GetParent(hwnd), WM_COMMAND, IDC_OPTIONS, 0);
			return 0;

		case 16: // Ctrl+P
			SendMessage(GetParent(hwnd), WM_COMMAND, IDC_PAUSE, 0);
			return 0;

		case 17: // Ctrl+Q
			SendMessage(GetParent(hwnd), WM_COMMAND, IDC_SCROLL, 0);
			return 0;

		case 19: // Ctrl+S
			SendMessage(GetParent(hwnd), WM_COMMAND, IDC_SAVE, 0);
			return 0;

		case 23: // Ctrl+W
			SendMessage(GetParent(hwnd), WM_COMMAND, IDC_CLOSE, 0);
			return 0;
		}
		break;
	}       
	return mir_callNextSubclass(hwnd, SubclassProc, msg, wParam, lParam);
}

////////////////////////////////////////////////////////////////////////////////

static INT_PTR CALLBACK LogDlgProc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam)
{
	LOGWIN *dat = (LOGWIN*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);

	switch(message) {
	case WM_INITDIALOG:
		{
			dat = (LOGWIN*)lParam;

			dat->hwnd = hwndDlg;
			dat->Scroll = 1;
			dat->Paused = 0;
			dat->hList = GetDlgItem(hwndDlg, IDC_LIST);

			SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG)dat);
			mir_subclassWindow(dat->hList, SubclassProc);

			// init buttons
			for(int i = 0; i < SIZEOF(ctrls); i++) {
				HWND hwnd = GetDlgItem(hwndDlg,ctrls[i].control);
				SendMessage(hwnd, ctrls[i].type, 0, 0);
				SendMessage(hwnd, BM_SETIMAGE,IMAGE_ICON,(LPARAM)hIcons[i+ICON_FIRST]);
				SendMessage(hwnd, BUTTONADDTOOLTIP,(WPARAM)TranslateTS(ctrls[i].tooltip), BATF_TCHAR);
			}

			CheckDlgButton(hwndDlg,IDC_SCROLL,dat->Scroll?BST_CHECKED:BST_UNCHECKED);
			SendDlgItemMessage(hwndDlg,IDC_SCROLL,BM_SETIMAGE,IMAGE_ICON,(LPARAM)hIcons[(dat->Scroll?ICON_SCROLL:ICON_NOSCROLL)]);

			if (gSingleMode) {
				ShowWindow(GetDlgItem(hwndDlg, IDC_PAUSEALL), SW_HIDE);
				ShowWindow(GetDlgItem(hwndDlg, IDC_STARTALL), SW_HIDE);
				ShowWindow(GetDlgItem(hwndDlg, IDC_CLOSE), SW_HIDE);
			}

			// init listview
			LVITEM lvi = {0};
			LVCOLUMN sLC;
			//ListView_SetUnicodeFormat(dat->hList, TRUE);
			ListView_SetImageList(dat->hList, gImg, LVSIL_SMALL);
			sLC.mask = LVCF_FMT | LVCF_WIDTH;
			sLC.fmt = LVCFMT_LEFT;
			sLC.cx = 630;
			ListView_InsertColumn(dat->hList, 0, &sLC);
			ListView_SetExtendedListViewStyle(dat->hList, LVS_EX_FULLROWSELECT);

			lvi.mask = LVIF_TEXT;
			if (gIcons) {
				lvi.mask |= LVIF_IMAGE;
				lvi.iImage = IMG_INFO;
			}

			lvi.pszText = TranslateT("*** Console started ***");
			ListView_InsertItem(dat->hList, &lvi);

			SendMessage(hwndDlg, WM_SIZE, 0, 0);
		}
		break;

	case HM_DUMP:
		if (!lParam) break;
		if (dat && !dat->Paused) {
			LVITEM lvi = {0};
			int last = 0x7fffffff;
			TCHAR szBreak;
			DWORD len, tmplen;
			DWORD wraplen = gWrapLen;
			TCHAR *str = ((DUMPMSG*)lParam)->szMsg;

			lvi.iItem = 0x7fffffff;

			str = _tcstok(str, _T("\n"));

			if (gIcons && str != NULL) {
				lvi.mask = LVIF_TEXT | LVIF_IMAGE;

				if (_tcsstr(str, _T("Data received"))) {
					if (gSeparator) ListView_InsertItem(dat->hList, &lvi);
					lvi.iImage = IMG_IN;
				}
				else if (_tcsstr(str, _T("Data sent"))) {
					if (gSeparator) ListView_InsertItem(dat->hList, &lvi);
					lvi.iImage = IMG_OUT;
				}
				else {
					if (gSeparator && dat->newline) {
						ListView_InsertItem(dat->hList, &lvi);
						dat->newline = 0;
					}
					lvi.iImage = IMG_INFO;
				}
			}
			else lvi.mask = LVIF_TEXT;

			while (str != NULL) {
				lvi.pszText = &str[0];
				tmplen = len = (DWORD)_tcslen(lvi.pszText);

				while(len > wraplen) {
					szBreak = lvi.pszText[wraplen];
					lvi.pszText[wraplen] = 0;
					last = ListView_InsertItem(dat->hList, &lvi);
					lvi.pszText[wraplen] = szBreak;
					len -= wraplen;
					lvi.pszText = &str[0] + tmplen - len;

					dat->newline = 1;
					lvi.iImage = IMG_EMPTY;
				}

				if (len && lvi.pszText[len-1] == '\r')
					lvi.pszText[len-1] = 0;

				last = ListView_InsertItem(dat->hList, &lvi);

				str = _tcstok(NULL, _T("\n"));

				if (str) dat->newline = 1;
				lvi.iImage = IMG_EMPTY;
			}

			if ( gVisible && dat == pActive && dat->Scroll == 1 )
				ListView_EnsureVisible(dat->hList, last, FALSE);

			if (last > gLimit) {
				int idx = last - gLimit + gLimit/4; // leave only 75% of LIMIT

				while (idx >= 0) {
					ListView_DeleteItem(dat->hList, idx);
					idx--;
				}
			}
		}

		mir_free((DUMPMSG*)lParam);
		return TRUE;

	case WM_SIZE:
		{
			UTILRESIZEDIALOG urd = { sizeof(urd) };
			urd.hInstance = hInst;
			urd.hwndDlg = hwndDlg;
			urd.lpTemplate = MAKEINTRESOURCEA(IDD_LOG);
			urd.pfnResizer = LogResize;
			SetWindowPos(hwndDlg, HWND_TOP, rcTabs.left, rcTabs.top, rcTabs.right - rcTabs.left, rcTabs.bottom - rcTabs.top, SWP_SHOWWINDOW);
			CallService(MS_UTILS_RESIZEDIALOG, 0, (LPARAM)&urd);
		}
		break;

	case WM_COMMAND:
		if (!dat)
			break;

		switch(LOWORD(wParam)) {
		case IDC_PAUSE:
			{
				LVITEM lvi = {0};
				dat->Paused = !(dat->Paused);
				lvi.mask = LVIF_TEXT | LVIF_IMAGE;
				lvi.iImage = IMG_INFO;
				lvi.iItem = 0x7FFFFFFF;
				lvi.pszText = (dat->Paused) ? TranslateT("*** Console paused ***") : TranslateT("*** Console resumed ***");
				ListView_InsertItem(dat->hList, &lvi);
				CheckDlgButton(hwndDlg,IDC_PAUSE,(dat->Paused)?BST_CHECKED:BST_UNCHECKED);
				SendDlgItemMessage(hwndDlg,IDC_PAUSE,BM_SETIMAGE,IMAGE_ICON,(LPARAM)hIcons[((dat->Paused)?ICON_PAUSED:ICON_STARTED)]);
				break;
			}
		case IDC_SCROLL:
			dat->Scroll = !(dat->Scroll);
			CheckDlgButton(hwndDlg,IDC_SCROLL,(dat->Scroll)?BST_CHECKED:BST_UNCHECKED);
			SendDlgItemMessage(hwndDlg,IDC_SCROLL,BM_SETIMAGE,IMAGE_ICON,(LPARAM)hIcons[((dat->Scroll)?ICON_SCROLL:ICON_NOSCROLL)]);
			break;

		case IDC_COPY:
			{
				int idx = 0;
				TCHAR szText[128];
				TCHAR *src, *dst, *buf;
				int flags = LVNI_BELOW;
				int count = ListView_GetSelectedCount(dat->hList);

				if (count)
					flags |= LVNI_SELECTED;
				else
					count = ListView_GetItemCount(dat->hList);

				dst = buf = (TCHAR*)malloc((count*(sizeof(szText)+1)+1)*sizeof(TCHAR));
				if (!buf) break;

				while ((idx = ListView_GetNextItem(dat->hList, idx, flags)) > 0)
				{
					ListView_GetItemText(dat->hList, idx, 0, szText, sizeof(szText)-1);
					src = szText;
					while (*dst++ = *src++);
					dst--;
					*dst++ = '\r';
					*dst++ = '\n';
					*dst = 0;
				}

				if (dst - buf > 0 && OpenClipboard(hwndDlg)) {
					EmptyClipboard();
					HGLOBAL hClipboardData = GlobalAlloc(GMEM_DDESHARE, (dst-buf+1)*sizeof(TCHAR));
					if (hClipboardData) {
						TCHAR *pchData = (TCHAR*)GlobalLock(hClipboardData);
						_tcscpy(pchData, buf);
						GlobalUnlock(hClipboardData);
						SetClipboardData(CF_UNICODETEXT,hClipboardData);
					}
					CloseClipboard();
				}
				free(buf);
				break;
			}
		case IDC_DELETE:
			{
				int idx = 0;
				int count = ListView_GetSelectedCount(dat->hList);

				if ( !count ) break;

				if ( count == ListView_GetItemCount(dat->hList)) {
					LVITEM lvi = {0};
					ListView_DeleteAllItems(dat->hList);
					lvi.mask = LVIF_TEXT | LVIF_IMAGE;
					lvi.iImage = IMG_INFO;
					lvi.pszText = TranslateT("*** Console cleared ***");
					ListView_InsertItem(dat->hList, &lvi);
					dat->newline = 0;
					break;
				}

				while ((idx = ListView_GetNextItem(dat->hList, idx, LVNI_BELOW|LVNI_SELECTED)) > 0)
				{
					ListView_DeleteItem(dat->hList, idx);
					idx--;
				}
			}
			break;

		case IDC_SAVE:
			{
				TCHAR szFile[MAX_PATH];

				if (!Openfile(szFile, ListView_GetSelectedCount(dat->hList))) break;

				FILE *fp = _tfopen(szFile, _T("wt"));
				if (fp) {
					int idx = 0;
					TCHAR szText[128];
					int flags = LVNI_BELOW;
					if (ListView_GetSelectedCount(dat->hList))
						flags |= LVNI_SELECTED;

					while ((idx = ListView_GetNextItem(dat->hList, idx, flags)) > 0)
					{
						ListView_GetItemText(dat->hList, idx, 0, szText, SIZEOF(szText)-1);
						_ftprintf(fp, _T("%s\n"), szText);
					}
					fclose(fp);
				}
				break;
			}
		case IDC_OPTIONS:
			CallServiceSync(MS_NETLIB_LOGWIN,0,0);
			break;
		case IDC_STARTALL:
			SendMessage(hwndConsole, HM_PAUSEALL, 0, 0);
			break;
		case IDC_PAUSEALL:
			SendMessage(hwndConsole, HM_PAUSEALL, 0, 1);
			break;
		case IDC_CLOSE:
			if (tabCount > 1)
				SendMessage(hwndDlg, WM_CLOSE, 0, 0);
			break;
		}
		break;

	case WM_CLOSE:
		DestroyWindow(hwndDlg);
		break;

	case WM_DESTROY:
		SendMessage(hwndConsole, HM_REMOVE, 0, (LPARAM)dat);
		break;
	}

	return FALSE;
}


////////////////////////////////////////////////////////////////////////////////

int ConsoleResize(HWND hwnd,LPARAM lParam,UTILRESIZECONTROL *urc)
{
	switch(urc->wId) {
	case IDC_TABS:
		return RD_ANCHORX_WIDTH|RD_ANCHORY_HEIGHT;
	default:
		return RD_ANCHORX_LEFT|RD_ANCHORY_TOP;
	}
}


static INT_PTR CALLBACK ConsoleDlgProc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam)
{
	switch(message) {
	case WM_INITDIALOG:
	{
		TCHAR title[MAX_PATH];
		TCHAR name[MAX_PATH] = {0};
		TCHAR path[MAX_PATH] = {0};
		
		hTabs = GetDlgItem(hwndDlg, IDC_TABS);

		//TabCtrl_SetMinTabWidth(hTabs, 100);

		// restore position
		Utils_RestoreWindowPositionEx(hwndDlg,RWPF_HIDDEN,NULL,"Console","Console");

		CallService(MS_DB_GETPROFILENAMET,(WPARAM)MAX_PATH,(LPARAM)name);

		CallService(MS_DB_GETPROFILEPATHT,(WPARAM)MAX_PATH,(LPARAM)path);

		mir_sntprintf(title, MAX_PATH, _T("%s - %s\\%s"), TranslateT("Miranda Console"), path, name);

		SetWindowText(hwndDlg, title);
		SendMessage(hwndDlg,WM_SETICON,ICON_BIG,(LPARAM)hIcons[0]);

		hwndConsole = hwndDlg;
		SendMessage(hwndDlg, HM_ADD, 0, 0);
		PostMessage(hwndDlg, WM_SIZE, 0, 0);
		break;
	}
	case HM_DUMP:
	{
		// lParam = DUMPMSG
		int idx;
		LOGWIN *lw;
		LOGWIN lw2;
		DUMPMSG *dumpMsg = (DUMPMSG*)lParam;

		if (!pActive) {
			mir_free(dumpMsg);
			break;
		}

		if (!gSingleMode)
		{
			lw2.Module = dumpMsg->szModule;
			if (!List_GetIndex(&lModules, &lw2, &idx))
				SendMessage(hwndDlg, HM_ADD, (WPARAM)idx, (LPARAM)dumpMsg->szModule);

			lw = (LOGWIN*)lModules.items[idx];
		}
		else
			lw = pActive;

		if (lw->hwnd)
			SendMessage(lw->hwnd, HM_DUMP, wParam, lParam);
		else
			PostMessage(hwndDlg, HM_DUMP, wParam, lParam); // loop msg until window will be ready

		return TRUE;
	}
	case HM_ADD:
	{
		// wParam = index, lParam = module name
		LOGWIN *lw;
		COLORREF col;
		TCITEM tci = {0};
		int idx = (int)wParam;
		char *str = (char*)lParam;

		if (!str) str = ""; // startup window

		lw = (LOGWIN*)mir_calloc( sizeof(LOGWIN));
		lw->Module = (char*)mir_strdup(str);
		List_Insert(&lModules, lw, idx);

		if (!gSingleMode && lParam)
		{
			tci.mask = TCIF_PARAM | TCIF_TEXT;
			tci.lParam = (LPARAM)lw;

			tci.pszText = mir_a2u(lw->Module);
			idx = TabCtrl_InsertItem(hTabs, tabCount, &tci);
			mir_free(tci.pszText);

			tabCount++;
		}

		GetClientRect(hTabs, &rcTabs);
		TabCtrl_AdjustRect(hTabs, FALSE, &rcTabs);

		CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_LOG), hwndDlg, LogDlgProc, (LPARAM)lw);
		ShowWindow(lw->hwnd, (tabCount > 1)?SW_HIDE:SW_SHOWNOACTIVATE);

		if (pActive)
		{
			col = ListView_GetBkColor(pActive->hList);
			ListView_SetBkColor(lw->hList, col);
			ListView_SetTextBkColor(lw->hList, col);

			col = ListView_GetTextColor(pActive->hList);
			ListView_SetTextColor(lw->hList, col);

			if (hfLogFont)
				SendMessage(lw->hList, WM_SETFONT, (WPARAM)hfLogFont, (LPARAM)TRUE);
		}

		// hide startup window
		if (tabCount == 1)
		{
			ShowWindow(pActive->hwnd, SW_HIDE);
			PostMessage(pActive->hwnd, WM_CLOSE, 0, 0);
			pActive = lw;
		}

		if (!pActive)
			pActive = lw;

		return TRUE;
	}
	case HM_REMOVE:
	{
		// lParam = LOGWIN
		LOGWIN *lw = (LOGWIN*)lParam;

		if (!lw) break;

		if (lw == pActive)
		{
			int tab = TabCtrl_GetCurSel(hTabs);
			if (tab >= 0)
			{
				TCITEM tci={0};

				TabCtrl_DeleteItem(hTabs, tab);
				tabCount--;
				if (tabCount)
				{
					tab--;
					if (tab < 0 ) tab = 0;
					TabCtrl_SetCurSel(hTabs, tab);

					tci.mask = TCIF_PARAM;
					TabCtrl_GetItem(hTabs, tab, &tci);
					pActive = (LOGWIN*)tci.lParam;
					SendMessage(pActive->hwnd, WM_SIZE, 0, 0);
					ScrollDown(pActive);
					ShowWindow(pActive->hwnd, SW_SHOWNOACTIVATE);
					SetFocus(pActive->hList);
				}
				else
					pActive = NULL;
			}
		}

		List_RemovePtr(&lModules, lw);
		mir_free(lw->Module);
		mir_free(lw);
		return TRUE;
	}
	case HM_SETFONT:
	{
		// wParam = font, lParam = font color
		int i;
		LOGWIN *lw;
		for ( i = 0; i < lModules.realCount; i++ )
		{
			lw = (LOGWIN*)lModules.items[i];
			ListView_SetTextColor(lw->hList, (COLORREF)lParam);
			if (wParam)
				SendMessage(lw->hList, WM_SETFONT, wParam, (LPARAM)TRUE);
		}
		return TRUE;
	}
	case HM_SETCOLOR:
	{
		// wParam = font, lParam = background color
		int i;
		LOGWIN *lw;
		for ( i = 0; i < lModules.realCount; i++ )
		{
			lw = (LOGWIN*)lModules.items[i];
			ListView_SetBkColor(lw->hList, (COLORREF)lParam);
			ListView_SetTextBkColor(lw->hList, (COLORREF)lParam);
			if (wParam)
				SendMessage(lw->hList, WM_SETFONT, wParam, (LPARAM)TRUE);
		}
		return TRUE;
	}
	case HM_PAUSEALL:
	{
		// lParam = 1 to pause, 0 to start
		int i;
		LOGWIN *lw;
		for ( i = 0; i < lModules.realCount; i++ )
		{
			lw = (LOGWIN*)lModules.items[i];
			if (lw->Paused != (int)lParam)
				SendMessage(lw->hwnd, WM_COMMAND, IDC_PAUSE, 0);
		}
		return TRUE;
	}
	case HM_RESTART:
	{
		if (pActive)
		{
			pActive = NULL;
			PostMessage(hwndDlg, HM_RESTART, 0, 0);
			return TRUE;
		}
		// close all tabs
		if (!lParam)
		{
			LOGWIN *lw;
			TabCtrl_DeleteAllItems(hTabs);
			while ( lModules.realCount )
			{
				lw = (LOGWIN*)lModules.items[0];
				SendMessage(lw->hwnd, WM_CLOSE, 0, 0);
			}
			tabCount = 0;
			PostMessage(hwndDlg, HM_RESTART, 0, 1);
			return TRUE;
		}

		LoadSettings();
		SendMessage(hwndDlg, HM_ADD, 0, 0);
		PostMessage(hwndDlg, WM_SIZE, 0, 0);
		return TRUE;
	}
 	case WM_SETFOCUS:
		if (pActive) {
			SetFocus(pActive->hList);
		}
		return TRUE;
	case WM_NOTIFY:
		switch(wParam) {
		case IDC_TABS:
			{
				LPNMHDR lpnmhdr = (LPNMHDR)lParam;
				if (lpnmhdr->code == TCN_SELCHANGE)
				{
					int newTab = TabCtrl_GetCurSel(hTabs);
					if (newTab >= 0 )
					{
						TCITEM tci={0};
						HWND hOld = pActive->hwnd;

						tci.mask = TCIF_PARAM;

						if (!TabCtrl_GetItem(hTabs, newTab, &tci)) break;

						pActive = (LOGWIN*)tci.lParam;

						SendMessage(pActive->hwnd, WM_SIZE, 0, 0);
						ScrollDown(pActive);
						ShowWindow(hOld, SW_HIDE);
						ShowWindow(pActive->hwnd, SW_SHOWNOACTIVATE);
						SetFocus(pActive->hList);
					} else
						SendMessage(pActive->hwnd, WM_SIZE, 0, 0);
				}
				break;
			}
		}
		break;
	case WM_SIZE:
	{
		UTILRESIZEDIALOG urd={0};
		urd.cbSize=sizeof(urd);
		urd.hInstance=hInst;
		urd.hwndDlg=hwndDlg;
		urd.lpTemplate=MAKEINTRESOURCEA(IDD_CONSOLE);
		urd.pfnResizer=ConsoleResize;
		CallService(MS_UTILS_RESIZEDIALOG,0,(LPARAM)&urd);

		GetClientRect(hTabs, &rcTabs);
		TabCtrl_AdjustRect(hTabs, FALSE, &rcTabs);

		if ( pActive )
			SendMessage(pActive->hwnd, WM_SIZE, 0, 0);
		break;
	}
	case WM_GETMINMAXINFO:
	{
		MINMAXINFO *mmi=(MINMAXINFO*)lParam;
		mmi->ptMinTrackSize.x=400;
		mmi->ptMinTrackSize.y=200;
		break;
	}
	case WM_CLOSE:
		if ( lParam != 1 ) {
			Utils_SaveWindowPosition(hwndDlg,NULL,"Console","Console");
			ShowConsole(0);
			return TRUE;
		} else
			DestroyWindow(hwndDlg);
		break;
	case WM_DESTROY:
		pActive = NULL;
		if (hfLogFont) DeleteObject(hfLogFont);
		PostQuitMessage(0);
		break;
	}

	return FALSE;
}


////////////////////////////////////////////////////////////////////////////////


void __cdecl ConsoleThread(void* arg)
{
	MSG msg;
	HWND hwnd;

	hwnd = CreateDialog(hInst,MAKEINTRESOURCE(IDD_CONSOLE),NULL,ConsoleDlgProc);

	if (!hwnd) return;

	while( GetMessage(&msg, NULL, 0, 0) > 0 )
	{
		switch(msg.message) {
		case HM_DUMP:
			OutMsgs++;
			break;
		}

		if ( IsDialogMessage(hwnd, &msg))
			continue;

		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	hwndConsole = NULL;
}

///////////////////////////////////////////////////////////////////////////////

static int OnFastDump(WPARAM wParam,LPARAM lParam)
{
	if ( pActive )
	{
		LOGMSG *logMsg = (LOGMSG*)lParam;
		DWORD headlen = (DWORD)strlen(logMsg->pszHead);
		DWORD msglen = (DWORD)strlen(logMsg->pszMsg);
		DWORD len = (headlen + msglen + 1) * sizeof(TCHAR) + sizeof(DUMPMSG);
		DUMPMSG *dumpMsg = (DUMPMSG*)mir_alloc( len );
		TCHAR *str = dumpMsg->szMsg;

		strncpy(dumpMsg->szModule, ((NETLIBUSER*)wParam)->szDescriptiveName, sizeof(dumpMsg->szModule))[ sizeof(dumpMsg->szModule)-1 ] = 0;

		{
			wchar_t *ucs2;

			ucs2 = mir_a2u( logMsg->pszHead );
			wcscpy(str, ucs2);
			mir_free(ucs2);

			// try to detect utf8
			ucs2 = mir_utf8decodeW( logMsg->pszMsg );
			if ( !ucs2 )
				ucs2 = mir_a2u( logMsg->pszMsg );

			wcscat( str, ucs2 );
			mir_free( ucs2 );
		}

		InMsgs++;
		PostMessage(hwndConsole, HM_DUMP, 0, (LPARAM)dumpMsg);
	}
	return 0;
}

///////////////////////////////////////////////////////////////////////////////

static void LoadSettings()
{
	gIcons = db_get_b(NULL, "Console", "ShowIcons", 1);
	gSeparator = db_get_b(NULL, "Console", "Separator", 1);
	gSingleMode = db_get_b(NULL, "Console", "SingleMode", 0);

	gWrapLen = db_get_b(NULL, "Console", "Wrap", DEFAULT_WRAPLEN);
	if ( gWrapLen < MIN_WRAPLEN ) gWrapLen = DEFAULT_WRAPLEN;

	gLimit = db_get_dw(NULL, "Console", "Limit", MAX_LIMIT);
	if (gLimit > MAX_LIMIT) gLimit = MAX_LIMIT;
	if (gLimit < MIN_LIMIT) gLimit = MIN_LIMIT;
}


static void SaveSettings(HWND hwndDlg)
{
	int len = GetDlgItemInt(hwndDlg, IDC_WRAP, NULL, FALSE);
	if (len < MIN_WRAPLEN )
		len = MIN_WRAPLEN;
	else
	if (len > MAX_WRAPLEN)
		len = MAX_WRAPLEN;

	gWrapLen = len;
	SetDlgItemInt(hwndDlg, IDC_WRAP, gWrapLen, FALSE);
	db_set_b(NULL, "Console", "Wrap", (BYTE)len);

	len = GetDlgItemInt(hwndDlg, IDC_LIMIT, NULL, FALSE);
	if (len < MIN_LIMIT )
		len = MIN_LIMIT;
	else
	if (len > MAX_LIMIT)
		len = MAX_LIMIT;

	gLimit = len;
	SetDlgItemInt(hwndDlg, IDC_LIMIT, gLimit, FALSE);
	db_set_dw(NULL, "Console", "Limit", len);

	db_set_b(NULL, "Console", "SingleMode", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_SINGLE));
	db_set_b(NULL, "Console", "Separator", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_SEPARATOR));
	db_set_b(NULL, "Console", "ShowIcons", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_SHOWICONS));

	db_set_b(NULL, "Console", "ShowAtStart", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_START));
}


static INT_PTR CALLBACK OptDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg) {
		case WM_INITDIALOG:
			TranslateDialogDefault(hwndDlg);
			CheckDlgButton(hwndDlg, IDC_START, db_get_b(NULL, "Console", "ShowAtStart", 0));
			CheckDlgButton(hwndDlg, IDC_SINGLE, gSingleMode);
			CheckDlgButton(hwndDlg, IDC_SHOWICONS, gIcons);
			CheckDlgButton(hwndDlg, IDC_SEPARATOR, gSeparator);
			SetDlgItemInt(hwndDlg, IDC_WRAP, gWrapLen, FALSE);
			SetDlgItemInt(hwndDlg, IDC_LIMIT, gLimit, FALSE);
			break;
		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDC_RESTART:
				{
					if (!pActive) break;
					SaveSettings(hwndDlg);
					PostMessage(hwndConsole, HM_RESTART, 0, 0);
					break;
				}
				case IDC_LIMIT:
				case IDC_WRAP:
					if (HIWORD(wParam) != EN_CHANGE || (HWND) lParam != GetFocus())
						return FALSE;
					SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
					break;
				case IDC_START:
				case IDC_SEPARATOR:
				case IDC_SHOWICONS:
				case IDC_SINGLE:
					SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
					break;
			}
			break;
		case WM_NOTIFY:
			switch(((LPNMHDR)lParam)->idFrom)
			{
				case 0:
					switch (((LPNMHDR)lParam)->code)
					{
						case PSN_APPLY:
						{
							SaveSettings(hwndDlg);
							break;
						}
					}
				break;
			}
			break;
	}
	return FALSE;
}


static int OptInit(WPARAM wParam,LPARAM lParam)
{
	OPTIONSDIALOGPAGE odp={0};
	odp.cbSize = sizeof(odp);
	odp.hInstance = hInst;
	odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS);
	odp.pszGroup = LPGEN("Services");
	odp.pszTitle = LPGEN("Console");
	odp.pfnDlgProc = OptDlgProc;
	odp.flags = ODPF_BOLDGROUPS;
	Options_AddPage(wParam, &odp);
	return 0;
}

///////////////////////////////////////////////////////////////////////////////

static int OnColourChange(WPARAM wParam,LPARAM lParam)
{
	if (hwndConsole)
	{
	   	ColourID cid = {0};
	   	COLORREF col;

	   	cid.cbSize=sizeof(cid);
		strcpy(cid.group,"Console");
		strcpy(cid.name,"Background");
		strcpy(cid.dbSettingsGroup,"Console");
		strcpy(cid.setting,"BgColor");

		col = (COLORREF)CallService(MS_COLOUR_GET,(WPARAM)&cid,0);
		if (col != -1)
			SendMessage(hwndConsole, HM_SETCOLOR, (WPARAM)hfLogFont, (LPARAM)col);
	}
	return 0;
}


static int OnFontChange(WPARAM wParam,LPARAM lParam)
{
	if (hwndConsole)
	{
	   	COLORREF col;
		HFONT hf = NULL;
		LOGFONT LogFont={0};
		FontIDT fid={0};
		fid.cbSize=sizeof(fid);

		_tcscpy(fid.group,_T("Console"));
		_tcscpy(fid.name,TranslateT("Text"));

		col = (COLORREF)CallService(MS_FONT_GETT,(WPARAM)&fid,(LPARAM)&LogFont);

		if (LogFont.lfHeight != 0)
		{
			hf=CreateFontIndirect(&LogFont);

			SendMessage(hwndConsole, HM_SETFONT, (WPARAM)hf, (LPARAM)col);

			if (hfLogFont)
				DeleteObject(hfLogFont);
			hfLogFont = hf;
		}
	}
	return 0;
}

static int OnSystemModulesLoaded(WPARAM wParam,LPARAM lParam)
{
	CreateServiceFunction(MS_CONSOLE_SHOW_HIDE, ShowHideConsole);

	FontIDT fid = {0};
	fid.cbSize = sizeof(fid);
	_tcscpy(fid.group,_T("Console"));
	_tcscpy(fid.name,TranslateT("Text"));
	strcpy(fid.dbSettingsGroup,"Console");
	strcpy(fid.prefix,"ConsoleFont");
	_tcscpy(fid.backgroundGroup,_T("Console"));
	_tcscpy(fid.backgroundName,_T("Background"));
	fid.flags = FIDF_DEFAULTVALID;
	fid.deffontsettings.charset = DEFAULT_CHARSET;
	fid.deffontsettings.colour = RGB(0, 0, 0);
	fid.deffontsettings.size = 10;
	fid.deffontsettings.style = 0;
	_tcsncpy(fid.deffontsettings.szFace, _T("Courier"), LF_FACESIZE);
	FontRegisterT(&fid);

	HookEvent(ME_FONT_RELOAD,OnFontChange);

	ColourIDT cid = {0};
	cid.cbSize=sizeof(cid);
	_tcscpy(cid.group,_T("Console"));
	_tcscpy(cid.name,_T("Background"));
	strcpy(cid.dbSettingsGroup,"Console");
	strcpy(cid.setting,"BgColor");
	cid.defcolour = RGB(255,255,255);
	ColourRegisterT(&cid);

	HookEvent(ME_COLOUR_RELOAD, OnColourChange);

	HOTKEYDESC hkd = {0};
	hkd.cbSize = sizeof(hkd);
	hkd.pszName = "Console_Show_Hide";
	hkd.pszDescription = LPGEN("Show/Hide Console");
	hkd.pszSection = "Main";
	hkd.pszService = MS_CONSOLE_SHOW_HIDE;
	hkd.DefHotKey = HOTKEYCODE(HOTKEYF_EXT, 'C');
	Hotkey_Register(&hkd);

	if (hwndConsole && IsWindow(hwndConsole))
	{
		HookEvent(ME_TTB_MODULELOADED, OnTTBLoaded);

		CLISTMENUITEM mi = { sizeof(mi) };
		mi.flags = CMIF_TCHAR;
		mi.hIcon = hIcons[0];
		mi.ptszPopupName = LPGENT("&Help");
		mi.popupPosition = 2000090000;
		mi.position = 1000000000;
		mi.ptszName = (IsWindowVisible(hwndConsole)) ? LPGENT("Hide Console") : LPGENT("Show Console");
		mi.pszService = MS_CONSOLE_SHOW_HIDE;
		hMenu = Menu_AddMainMenuItem(&mi);

		OnFontChange(0,0);
		OnColourChange(0,0);

		if (db_get_b(NULL,"Console","ShowAtStart",0) || db_get_b(NULL,"Console","Show",1))
			ShowConsole(1);
		else
			ShowConsole(0);
	}

	return 0;
}

static int PreshutdownConsole(WPARAM wParam,LPARAM lParam)
{
	if (hwndConsole)
		PostMessage(hwndConsole, WM_CLOSE, 0, 1 );

	return 0;
}

///////////////////////////////////////////////////////////////////////////////

static int stringCompare( LOGWIN *lw1, LOGWIN *lw2 )
{
	return strcmp( lw1->Module, lw2->Module );
}

///////////////////////////////////////////////////////////////////////////////

static UINT logicons[] = {IDI_EMPTY, IDI_ARROW, IDI_IN, IDI_OUT, IDI_INFO};


void InitConsole()
{
	int i;
	HICON hi;

	lModules.sortFunc = (FSortFunc)stringCompare;
	lModules.increment = 5;

	hIcons[0] = LoadIcon(hInst, MAKEINTRESOURCE(IDI_CONSOLE));
	hIcons[1] = (HICON)LoadImage(hInst,MAKEINTRESOURCE(IDI_NOSCROLL),IMAGE_ICON,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),0);
	hIcons[2] = (HICON)LoadImage(hInst,MAKEINTRESOURCE(IDI_PAUSED),IMAGE_ICON,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),0);

	for(i = 0; i < SIZEOF(ctrls); i++) {
		hIcons[i+ICON_FIRST] = (HICON)LoadImage(hInst,MAKEINTRESOURCE(ctrls[i].icon),IMAGE_ICON,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),0);
	}

   	gImg = ImageList_Create(LOGICONX_SIZE, LOGICONY_SIZE, ILC_COLOR24 | ILC_MASK, SIZEOF(logicons), 0);

    for(i = 0; i < SIZEOF(logicons); i++)
    {
       	hi = (HICON)LoadImage(hInst,MAKEINTRESOURCE(logicons[i]),IMAGE_ICON,LOGICONX_SIZE,LOGICONY_SIZE,0);
		if (hi)
		{
			ImageList_AddIcon(gImg, hi);
			DestroyIcon(hi);
		}
	}

	LoadSettings();

	mir_forkthread(ConsoleThread, 0);

	HookEvent(ME_SYSTEM_PRESHUTDOWN, PreshutdownConsole);
	HookEvent(ME_SYSTEM_MODULESLOADED, OnSystemModulesLoaded);
	HookEvent(ME_OPT_INITIALISE, OptInit);
	HookEvent(ME_NETLIB_FASTDUMP, OnFastDump);
}

void ShutdownConsole(void)
{
	int i;

	List_Destroy(&lModules);

	if (gImg) ImageList_Destroy(gImg);

	for(i = 0; i < SIZEOF(hIcons); i++) {
		if (hIcons[i]) DestroyIcon(hIcons[i]);
	}
}

////////////////////////////////////////////////////////////////////////////////

TCHAR *addstring(TCHAR *str, TCHAR *add) {
	_tcscpy(str,add);
	return str + _tcslen(add) + 1;
}


static int Openfile(TCHAR *outputFile, int selection)
{
	TCHAR filename[MAX_PATH+2] = _T("");
	TCHAR *title;

	TCHAR *filter, *tmp, *tmp1, *tmp2;
	tmp1 = TranslateT("Text Files (*.txt)");
	tmp2 = TranslateT("All Files");
	filter = tmp = (TCHAR*)_alloca((_tcslen(tmp1)+_tcslen(tmp2)+11)*sizeof(TCHAR));
	tmp = addstring(tmp, tmp1);
	tmp = addstring(tmp, _T("*.TXT"));
	tmp = addstring(tmp, tmp2);
	tmp = addstring(tmp, _T("*"));
	*tmp = 0;

	if (selection)
		title = TranslateT("Save selection to file");
	else
		title = TranslateT("Save log to file");

	OPENFILENAME ofn = {0};
	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = hwndConsole;
	ofn.lpstrFile = filename;
	ofn.lpstrFilter = filter;
	ofn.Flags = OFN_HIDEREADONLY | OFN_SHAREAWARE | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;
	ofn.lpstrTitle = title;
	ofn.nMaxFile = MAX_PATH;
	ofn.lpstrDefExt = _T("txt");

	if (!GetSaveFileName(&ofn))
		return 0;
	_tcscpy(outputFile, filename);
	return 1;
}

////////////////////////////////////////////////////////////////////////////////