#include "stdafx.h"
#include "ConsoleDialog.h"
#include "Scintilla.h"
#include "SciLexer.h"
#include "resource.h"
#include "PythonConsole.h"
#include "Notepad_plus_msgs.h"
#include "PluginInterface.h"
#include "Docking.h"
#include "WcharMbcsConverter.h"
#include "MenuManager.h"
#include "PythonScript.h"
#include "ConfigFile.h"


namespace NppPythonScript
{

ConsoleDialog::ConsoleDialog() :
	DockingDlgInterface(IDD_CONSOLE),
	m_data(new tTbData),
	m_scintilla(NULL),
	m_hInput(NULL),
	m_hCombo(NULL),
	m_console(NULL),
	m_currentPrompt(">>> "),
	m_standardPrompt(">>> "),
	m_continuePrompt("... "),
	m_originalInputWndProc(NULL),
	m_hTabIcon(NULL),
	m_runButtonIsRun(true),
	m_hContext(NULL),
	m_nppData{0,0,0},
	m_colorOutput(false),
	m_user_color(-1)
{}


ConsoleDialog::~ConsoleDialog()
{
	if (m_scintilla)
	{
		::SendMessage(_hParent, NPPM_DESTROYSCINTILLAHANDLE_DEPRECATED, 0, reinterpret_cast<LPARAM>(m_scintilla));
		m_scintilla = NULL;
	}

	if (m_data)
	{
		delete m_data;
		m_data = NULL;
	}

	if (m_hTabIcon)
	{
		::DestroyIcon(m_hTabIcon);
		m_hTabIcon = NULL;
	}

	if (m_hContext)
	{
		::DestroyMenu(m_hContext);
		m_hContext = NULL;
	}

	// To please Lint, let's NULL these handles and pointers
	m_hInput = NULL;
	m_hCombo = NULL;
	m_console = NULL;

}

WNDPROC ConsoleDialog::s_originalScintillaWndProc;


void ConsoleDialog::initDialog(HINSTANCE hInst, NppData& nppData, ConsoleInterface* console)
{
	DockingDlgInterface::init(hInst, nppData._nppHandle);

	auto getColorSetting = [](const TCHAR* settingName, int& output) {
		try {
			output = stoi(ConfigFile::getInstance()->getSetting(settingName));
			return true;
		}
		catch (const std::exception&) {
			return false;
		}
		};

	m_colorOutput = getColorSetting(_T("COLORIZEOUTPUT"), m_user_color) && m_user_color > -1;
	m_customizeConsoleErrorColor = getColorSetting(_T("CUSTOMCONSOLEERRORCOLOR"), m_customConsoleErrorColor) && m_customConsoleErrorColor > -1;

	m_standardPrompt = ConfigFile::getInstance()->getSetting(_T("ADDEXTRALINETOOUTPUT")) == _T("1") ? m_standardPrompt.insert(0, "\n") : m_standardPrompt;
	m_currentPrompt = m_standardPrompt;
	//Window::init(hInst, nppData._nppHandle);
	createOutputWindow(nppData._nppHandle);


	m_console = console;
	m_hContext = CreatePopupMenu();
	MENUITEMINFO mi{};
	mi.cbSize = sizeof(mi);
	mi.fMask = MIIM_ID | MIIM_STRING;
	mi.fType = MFT_STRING;
	mi.fState = MFS_ENABLED;

	mi.wID = 1;
	mi.dwTypeData = _T("Select all");
	InsertMenuItem(m_hContext, 0, TRUE, &mi);

	mi.wID = 2;
	mi.dwTypeData = _T("Copy");
	InsertMenuItem(m_hContext, 1, TRUE, &mi);

	mi.wID = 3;
	mi.dwTypeData = _T("Clear");
	InsertMenuItem(m_hContext, 3, TRUE, &mi);

	m_nppData = nppData;
}

INT_PTR CALLBACK ConsoleDialog::run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	switch(message)
	{
		case WM_INITDIALOG:
			{
				SetParent(m_scintilla, _hSelf);
				ShowWindow(m_scintilla, SW_SHOW);
				m_hCombo = ::GetDlgItem(_hSelf, IDC_COMBO1);
				m_hInput = FindWindowEx(m_hCombo, NULL, L"Edit", NULL);
				HFONT hCourier = CreateFont(14,0,0,0,FW_DONTCARE,FALSE,FALSE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS,
					CLIP_DEFAULT_PRECIS,CLEARTYPE_QUALITY, FIXED_PITCH, _T("Courier New"));
				if (hCourier != NULL)
				{
					SendMessage(m_hInput, WM_SETFONT, reinterpret_cast<WPARAM>(hCourier), TRUE);
					SendMessage(::GetDlgItem(_hSelf, IDC_PROMPT), WM_SETFONT, reinterpret_cast<WPARAM>(hCourier), TRUE);
				}
				// Subclass the Input box from the combobox
				::SetWindowLongPtr(m_hInput, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
				m_originalInputWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtr(m_hInput, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(ConsoleDialog::inputWndProc)));
				// Subclass Scintilla
				s_originalScintillaWndProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr(m_scintilla, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&ConsoleDialog::scintillaWndProc)));
				::SetFocus(m_hInput);
				return FALSE;
			}
		case WM_SIZE:
			MoveWindow(m_scintilla, 0, 0, LOWORD(lParam), HIWORD(lParam)-30, TRUE);
			MoveWindow(::GetDlgItem(_hSelf, IDC_PROMPT), 0, HIWORD(lParam)-22, 30, 25, TRUE);
			MoveWindow(m_hCombo, 30, HIWORD(lParam)-25, LOWORD(lParam)-85, 50, TRUE);
			MoveWindow(::GetDlgItem(_hSelf, IDC_RUN), LOWORD(lParam)-50, HIWORD(lParam)-25, 50, 21, TRUE);
			// ::SendMessage(m_scintilla, WM_SIZE, 0, MAKEWORD(LOWORD(lParam) - 10, HIWORD(lParam) - 30));
			return FALSE;

		case WM_CONTEXTMENU:
			{
				MENUITEMINFO mi{};
				mi.cbSize = sizeof(mi);
				mi.fMask = MIIM_STATE;
				if (0 == (callScintilla(SCI_GETSELECTIONSTART) - callScintilla(SCI_GETSELECTIONEND)))
				{
					mi.fState = MFS_DISABLED;
				}
				else
				{
					mi.fState = MFS_ENABLED;
				}

				SetMenuItemInfo(m_hContext, 2, FALSE, &mi);

				// Thanks MS for corrupting the value of BOOL. :-/
				// From the documentation (http://msdn.microsoft.com/en-us/library/ms648002.aspx):
				//
				//     If you specify TPM_RETURNCMD in the uFlags parameter, the return value is the menu-item
				//     identifier of the item that the user selected. If the user cancels the menu without making
				//     a selection, or if an error occurs, then the return value is zero.
				INT cmdID = (INT)TrackPopupMenu(m_hContext, TPM_RETURNCMD, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0, _hSelf, NULL);

				switch(cmdID)
				{
					case 1: // Select All
						callScintilla(SCI_SELECTALL);
						break;

					case 2: // Copy
						callScintilla(SCI_COPY);
						break;

					case 3: // Clear
						clearText();
						break;

					default:
						break;
				}
			}
			break;
		case WM_COMMAND:
			if (LOWORD(wParam) == IDC_RUN)
			{
				if (m_runButtonIsRun)
				{
					runStatement();
				}
				else
				{
					assert(m_console != NULL);
					if (m_console)
					{
						m_console->stopStatement();
					}
				}
				//MessageBox(NULL, _T("Command") , _T("Python Command"), 0);
				return FALSE;
			}
			else if (LOWORD(wParam) == IDCANCEL)
			{
				::SetFocus(getCurrScintilla());
				return FALSE;
			}
			break;

		case WM_SETFOCUS:
			//giveInputFocus();
			OutputDebugString(_T("ConsoleDialog SetFocus\r\n"));
			return FALSE;

		case WM_ACTIVATE:
			if (wParam == WA_ACTIVE)
			{
				OutputDebugString(_T("ConsoleDialog WM_ACTIVATE WA_ACTIVE\r\n"));
				giveInputFocus();
			}
			break;

		case WM_CHILDACTIVATE:
			OutputDebugString(_T("ConsoleDialog WM_CHILDACTIVATE\r\n"));
			giveInputFocus();
			break;

		case WM_NOTIFY:
			{
				LPNMHDR nmhdr = reinterpret_cast<LPNMHDR>(lParam);
				if (m_scintilla == nmhdr->hwndFrom)
				{
					switch(nmhdr->code)
					{
						case SCN_STYLENEEDED:
							onStyleNeeded(reinterpret_cast<SCNotification*>(lParam));
							return FALSE;

						case SCN_HOTSPOTCLICK:
							onHotspotClick(reinterpret_cast<SCNotification*>(lParam));
							return FALSE;

						default:
							break;
					}
				}
				break;
			}
		case WM_SHOWWINDOW:
			{
				MenuManager::getInstance()->checkShowConsole(wParam);
			}
		default:
			break;

	}

	return DockingDlgInterface::run_dlgProc(message, wParam, lParam);
}

void ConsoleDialog::historyAdd(const TCHAR *line)
{
	if (line && line[0])
	{
		auto i = ::SendMessage(m_hCombo, CB_FINDSTRINGEXACT, static_cast<WPARAM>(-1), reinterpret_cast<LPARAM>(line));
		if (i != CB_ERR) // found
		{
			::SendMessage(m_hCombo, CB_DELETESTRING, i, 0);
		}
		i = ::SendMessage(m_hCombo, CB_INSERTSTRING, 0, reinterpret_cast<LPARAM>(line));
		::SendMessage(m_hCombo, CB_SETCURSEL, i, 0);
	}
}

LRESULT ConsoleDialog::inputWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	ConsoleDialog *dlg = reinterpret_cast<ConsoleDialog*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
	return dlg->run_inputWndProc(hWnd, message, wParam, lParam);
}

LRESULT ConsoleDialog::run_inputWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch(message)
	{
		case WM_KEYUP:
			switch(wParam)
			{
				case VK_RETURN:
					runStatement();
					return FALSE;

				default:
					return CallWindowProc(m_originalInputWndProc, hWnd, message, wParam, lParam);
			}

		case WM_SETFOCUS:
			OutputDebugString(_T("Input SetFocus\r\n"));

		default:
			return CallWindowProc(m_originalInputWndProc, hWnd, message, wParam, lParam);
	}
}

void ConsoleDialog::runStatement()
{
	assert(m_console != NULL);
	if (m_console)
	{
		size_t length = GetWindowTextLength(m_hInput);
		TCHAR *buffer = new TCHAR[length + 1];
		GetWindowText(m_hInput, buffer, (int)length + 1);
		historyAdd(buffer);

		std::shared_ptr<char> charBuffer = WcharMbcsConverter::tchar2char(buffer);
		delete [] buffer;

		writeCmdText(m_currentPrompt.size(), m_currentPrompt.c_str());
		writeCmdText(strlen(charBuffer.get()), charBuffer.get());
		writeCmdText(1, "\n");
		m_console->runStatement(charBuffer.get());
	}
}


void ConsoleDialog::stopStatement()
{
	assert(m_console != NULL);
	if (m_console)
	{
		m_console->stopStatement();
	}
}


void ConsoleDialog::setPrompt(std::string prompt)
{
	m_currentPrompt = prompt;
	::SetWindowTextA(::GetDlgItem(_hSelf, IDC_PROMPT), (prompt.rfind('\n', 0) == 0) ? prompt.substr(1, prompt.size() - 1).c_str() : prompt.c_str());
}

std::string ConsoleDialog::getStandardPrompt(){	return m_standardPrompt;}

std::string ConsoleDialog::getContinuePrompt(){	return m_continuePrompt;}


void ConsoleDialog::createOutputWindow(HWND hParentWindow)
{
	m_scintilla = (HWND)::SendMessage(_hParent, NPPM_CREATESCINTILLAHANDLE, 0, reinterpret_cast<LPARAM>(hParentWindow));

	LONG_PTR currentStyle = GetWindowLongPtr(m_scintilla, GWL_STYLE);
	SetWindowLongPtr(m_scintilla, GWL_STYLE, currentStyle | WS_TABSTOP);

	LONG_PTR exstyles = GetWindowLongPtr(m_scintilla, GWL_EXSTYLE);
	if ((exstyles & WS_EX_LAYOUTRTL) == WS_EX_LAYOUTRTL)
	{
		SetWindowLongPtr(m_scintilla, GWL_EXSTYLE, exstyles & (~WS_EX_LAYOUTRTL));
	}

	callScintilla(SCI_SETREADONLY, 1, 0);

	/*  Style bits
	 *  LSB  0 - stderr = 1
	 *       1 - hotspot
	 *       2 - warning
	 *       ... to be continued
	 */

	// Set the codepage to UTF-8
	callScintilla(SCI_SETCODEPAGE, 65001);

	// 0 is stdout, black text
	callScintilla(SCI_STYLESETSIZE, 0 /* = style number */, 8 /* = size in points */);

	// 1 is stderr, red text
	callScintilla(SCI_STYLESETSIZE, 1 /* = style number */, 8 /* = size in points */);
	COLORREF consoleErrorColor = m_customizeConsoleErrorColor ? m_customConsoleErrorColor : RGB(250, 0, 0);
	callScintilla(SCI_STYLESETFORE, 1, consoleErrorColor);

	// 2 is stdout, black text, underline hotspot
	callScintilla(SCI_STYLESETSIZE, 2 /* = style number */, 8 /* = size in points */);
	callScintilla(SCI_STYLESETUNDERLINE, 2 /* = style number */, 1 /* = underline */);
	callScintilla(SCI_STYLESETHOTSPOT, 2, 1);

	// 3 is stderr, red text, underline hotspot
	callScintilla(SCI_STYLESETSIZE, 3 /* = style number */, 8 /* = size in points */);
	callScintilla(SCI_STYLESETFORE, 3, RGB(250, 0, 0));
	callScintilla(SCI_STYLESETUNDERLINE, 3 /* = style number */, 1 /* = underline */);
	callScintilla(SCI_STYLESETHOTSPOT, 3, 1);

	// 4 stdout warning without hotspot
	callScintilla(SCI_STYLESETSIZE, 4 /* = style number */, 8 /* = size in points */);
	callScintilla(SCI_STYLESETFORE, 4, RGB(199, 175, 7));  // mucky yellow

	// 5 stderr warning without hotspot
	callScintilla(SCI_STYLESETSIZE, 5 /* = style number */, 8 /* = size in points */);
	callScintilla(SCI_STYLESETFORE, 5, RGB(255, 128, 64));  // orange

	// 6 is hotspot, stdout, warning
	callScintilla(SCI_STYLESETSIZE, 6 /* = style number */, 8 /* = size in points */);
	callScintilla(SCI_STYLESETFORE, 6, RGB(199, 175, 7));  // mucky yellow
	callScintilla(SCI_STYLESETUNDERLINE, 6 /* = style number */, 1 /* = underline */);
	callScintilla(SCI_STYLESETHOTSPOT, 6, 1);

	// 7 is hotspot, stderr, warning
	callScintilla(SCI_STYLESETSIZE, 7 /* = style number */, 8 /* = size in points */);
	callScintilla(SCI_STYLESETFORE, 7, RGB(255, 128, 64));  // orange
	callScintilla(SCI_STYLESETUNDERLINE, 7 /* = style number */, 1 /* = underline */);
	callScintilla(SCI_STYLESETHOTSPOT, 7, 1);

	// 8 is colored stdout indicator
	intptr_t defaultColor = callScintilla(SCI_STYLEGETFORE, 0, 0);
	callScintilla(SCI_INDICSETSTYLE, 8 /* = indicator number */, INDIC_TEXTFORE);
	callScintilla(SCI_INDICSETFORE, 8, m_colorOutput ? m_user_color : defaultColor); // green

	callScintilla(SCI_USEPOPUP, 0);
	//set container lexer mode for ilexer5*, before scintilla5 SCLEX_CONTAINER was used
	callScintilla(SCI_SETILEXER, 0, 0);
}

LRESULT ConsoleDialog::scintillaWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch(message)
	{
		case WM_GETDLGCODE:
			return DLGC_WANTARROWS | DLGC_WANTCHARS;

		case WM_SETFOCUS:
			OutputDebugString(_T("Scintilla SetFocus\r\n"));
			break;

		default:
			break;
	}

	return CallWindowProc(s_originalScintillaWndProc, hWnd, message, wParam, lParam);
}

void ConsoleDialog::writeCmdText(size_t length, const char *text)
{
	::SendMessage(m_scintilla, SCI_SETREADONLY, 0, 0);
	for (idx_t i = 0; i < length; ++i)
	{
		if (text[i] == '\r')
		{
			::SendMessage(m_scintilla, SCI_APPENDTEXT, i, reinterpret_cast<LPARAM>(text));
			text += i + 1;
			length -= i + 1;
			i = 0;
		}
	}

	if (length > 0)
	{
		::SendMessage(m_scintilla, SCI_APPENDTEXT, length, reinterpret_cast<LPARAM>(text));
	}

	::SendMessage(m_scintilla, SCI_SETREADONLY, 1, 0);

	::SendMessage(m_scintilla, SCI_GOTOPOS, ::SendMessage(m_scintilla, SCI_GETLENGTH, 0, 0), 0);

}

void ConsoleDialog::writeText(size_t length, const char *text)
{
	::SendMessage(m_scintilla, SCI_SETREADONLY, 0, 0);
	for (idx_t i = 0; i < length; ++i)
	{
		if (text[i] == '\r')
		{
			::SendMessage(m_scintilla, SCI_APPENDTEXT, i, reinterpret_cast<LPARAM>(text));
			text += i + 1;
			length -= i + 1;
			i = 0;
		}
	}

	if (length > 0)
	{
		::SendMessage(m_scintilla, SCI_APPENDTEXT, length, reinterpret_cast<LPARAM>(text));
	}

	::SendMessage(m_scintilla, SCI_SETREADONLY, 1, 0);

	::SendMessage(m_scintilla, SCI_GOTOPOS, ::SendMessage(m_scintilla, SCI_GETLENGTH, 0, 0), 0);

}

void ConsoleDialog::writeColoredText(size_t length, const char *text)
{
	size_t docLength = (size_t)callScintilla(SCI_GETLENGTH);
	callScintilla(SCI_SETREADONLY, 0);

	if (length > 0)
	{
		callScintilla(SCI_APPENDTEXT, length, reinterpret_cast<LPARAM>(text));
	}

	callScintilla(SCI_SETREADONLY, 1);

	callScintilla(SCI_SETINDICATORCURRENT, 8);
	callScintilla(SCI_INDICATORFILLRANGE, docLength, length);
	callScintilla(SCI_GOTOPOS, docLength+length);
}

void ConsoleDialog::writeError(size_t length, const char *text)
{
	size_t docLength = (size_t)callScintilla(SCI_GETLENGTH);
	size_t realLength = length;
	callScintilla(SCI_SETREADONLY, 0);
	for (idx_t i = 0; i < length; ++i)
	{
		if (text[i] == '\r')
		{
			if (i)
			{
				callScintilla(SCI_APPENDTEXT, i, reinterpret_cast<LPARAM>(text));
			}
			text += i + 1;
			length -= i + 1;
			realLength--;
			i = 0;
		}
	}

	if (length > 0)
	{
		callScintilla(SCI_APPENDTEXT, length, reinterpret_cast<LPARAM>(text));
	}

	callScintilla(SCI_SETREADONLY, 1);
	callScintilla(SCI_STARTSTYLING, docLength, 0x01);
	callScintilla(SCI_SETSTYLING, realLength, 1);


	callScintilla(SCI_COLOURISE, docLength, -1);
	callScintilla(SCI_GOTOPOS, docLength + realLength);
}


void ConsoleDialog::doDialog()
{
	if (!isCreated())
	{
		create(m_data);

		assert(m_data);
		if (m_data)
		{
			// define the default docking behaviour
			m_data->uMask			= DWS_DF_CONT_BOTTOM | DWS_ICONTAB;
			m_data->pszName = _T("Python");

			RECT rc{};
			rc.bottom = 0;
			rc.top = 0;
			rc.left = 0;
			rc.right = 0;
			m_hTabIcon = (HICON)::LoadImage(_hInst, MAKEINTRESOURCE(IDI_PYTHON8), IMAGE_ICON, 16, 16, LR_LOADMAP3DCOLORS | LR_LOADTRANSPARENT);
			m_data->hIconTab			= m_hTabIcon;
			m_data->pszModuleName	= PLUGIN_MODULE_NAME; // the plugin filename
			m_data->dlgID			= 1; // zero based index of the plugin's published funcs array command (here the "Show Console" in the getGeneratedFuncItemArray exported func)
			m_data->pszAddInfo	    = NULL; //_pExProp->szCurrentPath;
			m_data->iPrevCont		= -1;
			m_data->hClient			= _hSelf;
			m_data->rcFloat			= rc;


			::SendMessage(_hParent, NPPM_DMMREGASDCKDLG, 0, reinterpret_cast<LPARAM>(m_data));

			// Parse the whole doc, in case we've had errors that haven't been parsed yet
			callScintilla(SCI_COLOURISE, 0, -1);
		}
	}
	display(true);
}

void ConsoleDialog::hide()
{
	display(false);
	HWND current_HWND = ::GetFocus();
	if (m_hInput == current_HWND || m_scintilla == current_HWND)
	{
		intptr_t currentView = MAIN_VIEW;
		::SendMessage(m_nppData._nppHandle, NPPM_GETCURRENTSCINTILLA, 0, (LPARAM)&currentView);
		HWND sci = (currentView == MAIN_VIEW) ? m_nppData._scintillaMainHandle : m_nppData._scintillaSecondHandle;

		DWORD currentThreadId = GetCurrentThreadId();
		DWORD otherThreadId = GetWindowThreadProcessId(sci, NULL);

		AttachThreadInput(currentThreadId, otherThreadId, TRUE);
		SetFocus(sci);
		AttachThreadInput(currentThreadId, otherThreadId, FALSE);
	}
}

void ConsoleDialog::runEnabled(bool enabled)
{
	//EnableWindow(GetDlgItem(_hSelf, IDC_RUN), enabled);
	::SetWindowText(GetDlgItem(_hSelf, IDC_RUN), enabled ? _T("Run") : _T("Stop"));
	m_runButtonIsRun = enabled;

	if (enabled)
	{
		::SetForegroundWindow(_hSelf);
		//::SetActiveWindow(_hSelf);
		::SetFocus(m_hInput);
	}
}

void ConsoleDialog::clearText()
{
	::SendMessage(m_scintilla, SCI_SETREADONLY, 0, 0);
	::SendMessage(m_scintilla, SCI_CLEARALL, 0, 0);
	::SendMessage(m_scintilla, SCI_SETREADONLY, 1, 0);
}


void ConsoleDialog::onStyleNeeded(SCNotification* notification)
{
	idx_t startPos = (idx_t)callScintilla(SCI_GETENDSTYLED);
	idx_t startLine = (idx_t)callScintilla(SCI_LINEFROMPOSITION, startPos);
	idx_t endPos = (idx_t)notification->position;
	idx_t endLine = (idx_t)callScintilla(SCI_LINEFROMPOSITION, endPos);


	LineDetails lineDetails{};
	for(idx_t lineNumber = startLine; lineNumber <= endLine; ++lineNumber)
	{
		lineDetails.lineLength = (size_t)callScintilla(SCI_GETLINE, lineNumber);

		if (lineDetails.lineLength > 0)
		{
			lineDetails.line = new char[lineDetails.lineLength + 1];
			callScintilla(SCI_GETLINE, lineNumber, reinterpret_cast<LPARAM>(lineDetails.line));
			lineDetails.line[lineDetails.lineLength] = '\0';
			lineDetails.errorLevel = EL_UNSET;


			if (parseLine(&lineDetails))
			{
				startPos = (idx_t)callScintilla(SCI_POSITIONFROMLINE, lineNumber);

				// Check that it's not just a file called '<console>'
				if (strncmp(lineDetails.line + lineDetails.filenameStart, "<console>", lineDetails.filenameEnd - lineDetails.filenameStart))
				{
					int mask, style;
					switch(lineDetails.errorLevel)
					{
						case EL_WARNING:
							mask = 0x04;
							style = 0x04;
							break;

						case EL_ERROR:
							mask = 0x01;
							style = 0x01;
							break;

						case EL_UNSET:
						case EL_INFO:
						default:
							mask = 0x00;
							style = 0x00;
							break;
					}

					if (lineDetails.filenameStart > 0)
					{
						callScintilla(SCI_STARTSTYLING, startPos, mask);
						callScintilla(SCI_SETSTYLING, lineDetails.filenameStart, style);
					}


					callScintilla(SCI_STARTSTYLING, startPos + lineDetails.filenameStart, mask | 0x02);
					callScintilla(SCI_SETSTYLING, lineDetails.filenameEnd - lineDetails.filenameStart, style | 0x02);

					if (lineDetails.lineLength > lineDetails.filenameEnd)
					{
						callScintilla(SCI_STARTSTYLING, startPos + lineDetails.filenameEnd, mask);
						callScintilla(SCI_SETSTYLING, lineDetails.lineLength - lineDetails.filenameEnd, style);
					}


				}
			}

			delete[] lineDetails.line;

		}
	}

	// ensure that everything is set as styled (just move the endStyled variable on to the requested position)
	callScintilla(SCI_STARTSTYLING, static_cast<WPARAM>(notification->position), 0x0);

}

bool ConsoleDialog::parseLine(LineDetails *lineDetails)
{
	// Eg.
	//   File "C:\Users\Dave\AppData\Roaming\Notepad++\plugins\Config\PythonScript\scripts\fourth.py", line 2, in <module>
	if (0 == strncmp(lineDetails->line, "  File \"", 8)
		&& parsePythonErrorLine(lineDetails))
	{
		return true;
	}

	// Eg.
	// e:\work\pythonscript\pythonscript\src\consoledialog.cpp(523): error C2065: 'ee' : undeclared identifier
	// Potentially with spaces in front if MSBUILD used
	// Line number can contain "," for column  (523,5)
	if (parseVSErrorLine(lineDetails))
	{
		return true;
	}

	// Eg.
	// C:/PalmDev/sdk-4/include/Core/System/NetMgr.h:550: warning: ignoring pragma: ;
	if (parseGCCErrorLine(lineDetails))
	{
		return true;
	}

	return false;
}

bool ConsoleDialog::parseVSErrorLine(LineDetails *lineDetails)
{
	enum StyleState
	{
		SS_BEGIN,
		SS_FILENAME,
		SS_LINENUMBER,
		SS_ERRORTYPE,
		SS_EXIT
	} styleState;

	bool retVal = false;
	styleState = SS_BEGIN;

	idx_t pos = 0;
	lineDetails->errorLineNo = IDX_MAX;

	while (styleState != SS_EXIT)
	{
		switch(styleState)
		{
			case SS_BEGIN:
				while(lineDetails->line[pos] == ' ')
				{
					++pos;
				}
				lineDetails->filenameStart = pos;
				styleState = SS_FILENAME;
				break;

			case SS_FILENAME:

				while(lineDetails->line[pos] != '(' && pos < lineDetails->lineLength)
				{
					if (pos - lineDetails->filenameStart == 1)
					{
						if (lineDetails->line[pos] != ':')
						{
							styleState = SS_EXIT;
							break;
						}
					}
					else if (!isValidFilenameChar(lineDetails->line[pos]))
					{
						styleState = SS_EXIT;
						break;
					}

					++pos;
				}

				if (lineDetails->line[pos] == '(') // Found the opening bracket for line no
				{
					++pos;
					styleState = SS_LINENUMBER;
				}
				else
				{
					styleState = SS_EXIT;
				}
				break;

			case SS_LINENUMBER:
				{
					idx_t startLineNoPos = pos;
					idx_t endLineNoPos;
					while(lineDetails->line[pos] >= '0' && lineDetails->line[pos] <= '9' && pos < lineDetails->lineLength)
					{
						++pos;
					}
					endLineNoPos = pos;

					if (pos < (lineDetails->lineLength + 1)
						&& lineDetails->line[pos] == ',')
					{
						++pos;
						while(lineDetails->line[pos] >= '0' && lineDetails->line[pos] <= '9' && pos < lineDetails->lineLength)
						{
							++pos;
						}
					}


					if (pos < (lineDetails->lineLength + 1)
						&& lineDetails->line[pos] == ')'
						&& lineDetails->line[pos+1] == ':')
					{
						// If no line number, jump out
						if (endLineNoPos == startLineNoPos)
						{
							styleState = SS_EXIT;
							break;
						}

						char *lineNumber = new char[(endLineNoPos - startLineNoPos) + 2];
						strncpy_s(lineNumber, (endLineNoPos - startLineNoPos) + 2, lineDetails->line + startLineNoPos, endLineNoPos - startLineNoPos);
						lineDetails->errorLineNo = strtoul(lineNumber, NULL, 0) - 1;
						delete[] lineNumber;
						lineDetails->filenameEnd = startLineNoPos - 1;
						retVal = true;
						pos += 3; // jump past "): " to either error or warning
						styleState = SS_ERRORTYPE;
					}
					else
					{
						pos = startLineNoPos + 1;
						styleState = SS_FILENAME;
					}
					break;
				}

			case SS_ERRORTYPE:
				if (0 == strncmp(lineDetails->line + pos, "error", 5))
				{
					lineDetails->errorLevel = EL_ERROR;
				}
				else if (0 == strncmp(lineDetails->line + pos, "warning", 7))
				{
					lineDetails->errorLevel = EL_WARNING;
				}
				styleState = SS_EXIT;
				break;

			case SS_EXIT:
				break;

			default:
				styleState = SS_EXIT;
				break;
		}
	}
	return retVal;
}


bool ConsoleDialog::parseGCCErrorLine(LineDetails *lineDetails)
{
	enum StyleState
	{
		SS_FILENAME,
		SS_LINENUMBER,
		SS_ERRORTYPE,
		SS_EXIT
	} styleState;

	bool retVal = false;
	styleState = SS_FILENAME;

	idx_t pos = 0;
	lineDetails->filenameStart = 0;
	lineDetails->errorLineNo = IDX_MAX;

	while (styleState != SS_EXIT)
	{
		switch(styleState)
		{
			case SS_FILENAME:
				{
					bool isEscaped = false;
					while((lineDetails->line[pos] != ':' || pos == 1) && pos < lineDetails->lineLength)
					{
						// Unescaped space, so leave - this isn't a gcc error
						if (lineDetails->line[pos] == ' ' && !isEscaped)
						{
							styleState = SS_EXIT;
							break;
						}

						// Unescaped invalid char, so it's not a gcc error
						if (!(isEscaped || lineDetails->line[pos] == '/' || isValidFilenameChar(lineDetails->line[pos]) || (lineDetails->line[pos] == ':' && pos == 1)))
						{
							styleState = SS_EXIT;
							break;
						}

						if (isEscaped)
							isEscaped = false;

						if (lineDetails->line[pos] == '\\')
							isEscaped = true;

						++pos;


					}

					if (lineDetails->line[pos] == ':') // Found the colon for line no
					{
						++pos;
						styleState = SS_LINENUMBER;
					}
					else
					{
						styleState = SS_EXIT;
					}
				}
				break;

			case SS_LINENUMBER:
				{
					idx_t startLineNoPos = pos;
					while(lineDetails->line[pos] >= '0' && lineDetails->line[pos] <= '9' && pos < lineDetails->lineLength)
					{
						++pos;
					}
					if (pos < (lineDetails->lineLength + 1)
						&& lineDetails->line[pos] == ':')
					{
						lineDetails->errorLineNo = strtoul(lineDetails->line + startLineNoPos, NULL, 0) - 1;

						// If the line number came out as 0, ie. there wasn't any,
						// then the line is not a gcc error
						if (lineDetails->errorLineNo == IDX_MAX)
						{
							styleState = SS_EXIT;
						}
						else
						{
							lineDetails->filenameEnd = startLineNoPos - 1;
							retVal = true;
							pos += 2;
							styleState = SS_ERRORTYPE;
						}
					}
					else
					{
						// If we've found the end of the number, and it isn't followed with a colon:
						// then it's not a gcc error.
						styleState = SS_EXIT;
					}
					break;
				}
			case SS_ERRORTYPE:
				if (0 == strncmp(lineDetails->line + pos, "error", 5))
				{
					lineDetails->errorLevel = EL_ERROR;
				}
				else if (0 == strncmp(lineDetails->line + pos, "warning", 7))
				{
					lineDetails->errorLevel = EL_WARNING;
				}
				styleState = SS_EXIT;
				break;

			case SS_EXIT:
				break;

			default:
				styleState = SS_EXIT;
				break;
		}
	}
	return retVal;
}

bool ConsoleDialog::parsePythonErrorLine(LineDetails *lineDetails)
{
	enum StyleState
	{
		SS_BEGIN,
		SS_FILENAME,
		SS_EXPECTLINE,
		SS_LINENUMBER,
		SS_EXIT
	} styleState;

	bool retVal = false;
	styleState = SS_BEGIN;
	lineDetails->errorLineNo = IDX_MAX;
	idx_t pos = 0;
	while(styleState != SS_EXIT)
	{
		//lint -e{788} enum constant 'StyleState::SS_EXIT' not used within defaulted switch
		// That's normal since SS_EXIT is strictly used to exit the loop.
		switch(styleState)
		{
			case SS_BEGIN:
				if (strncmp(lineDetails->line, "  File \"", 8) == 0)
				{
					pos = 8;
					lineDetails->filenameStart = 8;
					styleState = SS_FILENAME;
				}
				else
				{
					styleState = SS_EXIT;
				}
				break;

			case SS_FILENAME:
				while(lineDetails->line[pos] != '\"' && pos < lineDetails->lineLength)
				{
					++pos;
				}

				if (pos >= lineDetails->lineLength) // Not found, so revert to default style
				{
					styleState = SS_EXIT;
				}
				else
				{
					lineDetails->filenameEnd = pos;
					retVal = true;
					styleState = SS_EXPECTLINE;
				}
				break;

			case SS_EXPECTLINE:
				if (strncmp(lineDetails->line + pos, "\", line ", 8) == 0)
				{
					pos += 8;
					styleState = SS_LINENUMBER;
				}
				else
				{
					styleState = SS_EXIT;
				}
				break;

			case SS_LINENUMBER:
				lineDetails->errorLineNo = strtoul(lineDetails->line + pos, NULL, 0) - 1;
				styleState = SS_EXIT;
				break;

			default:
				styleState = SS_EXIT;
				break;
		}
	}

	return retVal;
}

void ConsoleDialog::onHotspotClick(SCNotification* notification)
{
	assert(m_console != NULL);
	if (m_console)
	{
		idx_t lineNumber = callScintilla(SCI_LINEFROMPOSITION, static_cast<WPARAM>(notification->position));
		LineDetails lineDetails{};
		lineDetails.lineLength = (size_t)callScintilla(SCI_GETLINE, lineNumber);

		if (lineDetails.lineLength != SIZE_MAX)
		{
			lineDetails.line = new char[lineDetails.lineLength + 1];
			callScintilla(SCI_GETLINE, lineNumber, reinterpret_cast<LPARAM>(lineDetails.line));
			lineDetails.line[lineDetails.lineLength] = '\0';
			if (parseLine(&lineDetails))
			{
				lineDetails.line[lineDetails.filenameEnd] = '\0';
				m_console->openFile(lineDetails.line + lineDetails.filenameStart, lineDetails.errorLineNo);
			}
		}
	}
}


}
