#include "stdafx.h"
#include "scintilla_conf.h"
#include "scintilla_prop_sets.h"
#include "helpers.h"


const t_style_to_key_table default_style_table[] = 
{
	// Default
	{STYLE_DEFAULT, "style.default"},
	// Line number
	{STYLE_LINENUMBER, "style.linenumber"},
	// Bracelight
	{STYLE_BRACELIGHT, "style.bracelight"},
	// Bracebad
	{STYLE_BRACEBAD, "style.bracebad"},
	{-1, NULL},
};

const t_style_to_key_table js_style_table[] =
{
	// Comments
	{SCE_C_COMMENT, "style.comment"},
	{SCE_C_COMMENTLINE, "style.comment"},
	{SCE_C_COMMENTDOC, "style.comment"},
	{SCE_C_COMMENTLINEDOC, "style.comment"},
	{SCE_C_COMMENTDOCKEYWORD, "style.comment"},
	{SCE_C_COMMENTDOCKEYWORDERROR, "style.comment"},
	// Keywords
	{SCE_C_WORD, "style.keyword"},
	// Indentifier
	{SCE_C_IDENTIFIER, "style.indentifier"},
	// Numbers
	{SCE_C_NUMBER, "style.number"},
	// String/Chars
	{SCE_C_STRING, "style.string"},
	{SCE_C_CHARACTER, "style.string"},
	// Operators
	{SCE_C_OPERATOR, "style.operator"},
	//
	{-1, NULL},
};

const t_style_to_key_table vbs_style_table[] =
{
	// Comments
	{SCE_B_COMMENT, "style.comment"},
	// Keywords
	{SCE_B_KEYWORD, "style.keyword"},
	// Indentifier
	{SCE_B_IDENTIFIER, "style.indentifier"},
	// Numbers
	{SCE_B_NUMBER, "style.number"},
	// String/Chars
	{SCE_B_STRING, "style.string"},
	// Operators
	{SCE_B_OPERATOR, "style.operator"},
	//
	{-1, NULL},
};

DWORD sci_parse_hex(const char * hex)
{
	// len('#000000') = 7
	if (pfc::strlen_max(hex, 8) == 8)
		return 0;

	int r = helpers::int_from_hex_byte(hex + 1);
	int g = helpers::int_from_hex_byte(hex + 3);
	int b = helpers::int_from_hex_byte(hex + 5);

	return RGB(r, g, b);
}

DWORD sci_get_prop_color(HWND edit, const char * key, bool * key_exist)
{
	pfc::array_t<char> buff;
	int len = SendMessage(edit, SCI_GETPROPERTYEXPANDED, (WPARAM)key, 0);  // Get property len

	if (key_exist)
		*key_exist = (len != 0);

	if (len == 0)
		return 0;

	// Allocate buffer
	buff.set_size(len + 1);
	buff[len] = 0;
	// Get property
	SendMessage(edit, SCI_GETPROPERTYEXPANDED, (WPARAM)key, (LPARAM)buff.get_ptr());

	return sci_parse_hex(buff.get_ptr());
}

void sci_init(HWND edit)
{
	// Reset to default p_style
	SendMessage(edit, SCI_STYLERESETDEFAULT, 0, 0);

	// common
	SendMessage(edit, SCI_SETCODEPAGE, SC_CP_UTF8, 0);
	SendMessage(edit, SCI_SETEOLMODE, SC_EOL_CRLF, 0);
	SendMessage(edit, SCI_SETMODEVENTMASK, SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT | SC_PERFORMED_UNDO | SC_PERFORMED_REDO, 0);
	SendMessage(edit, SCI_USEPOPUP, true, 0);

	// Keys
	SendMessage(edit, SCI_ASSIGNCMDKEY, MAKELONG(SCK_NEXT, SCMOD_CTRL), SCI_PARADOWN);
	SendMessage(edit, SCI_ASSIGNCMDKEY, MAKELONG(SCK_PRIOR, SCMOD_CTRL), SCI_PARAUP);
	SendMessage(edit, SCI_ASSIGNCMDKEY, MAKELONG(SCK_NEXT, (SCMOD_CTRL | SCMOD_SHIFT)), SCI_PARADOWNEXTEND);
	SendMessage(edit, SCI_ASSIGNCMDKEY, MAKELONG(SCK_PRIOR, (SCMOD_CTRL | SCMOD_SHIFT)), SCI_PARAUPEXTEND);
	SendMessage(edit, SCI_ASSIGNCMDKEY, MAKELONG(SCK_HOME, SCMOD_NORM), SCI_VCHOMEWRAP);
	SendMessage(edit, SCI_ASSIGNCMDKEY, MAKELONG(SCK_END, SCMOD_NORM), SCI_LINEENDWRAP);
	SendMessage(edit, SCI_ASSIGNCMDKEY, MAKELONG(SCK_HOME, SCMOD_SHIFT), SCI_VCHOMEWRAPEXTEND);
	SendMessage(edit, SCI_ASSIGNCMDKEY, MAKELONG(SCK_END, SCMOD_SHIFT), SCI_LINEENDWRAPEXTEND);

	// Clear Some CMD KEY
	SendMessage(edit, SCI_CLEARCMDKEY, MAKELONG('F', SCMOD_CTRL), SCI_NULL);
	SendMessage(edit, SCI_CLEARCMDKEY, MAKELONG('G', SCMOD_CTRL), SCI_NULL);
	SendMessage(edit, SCI_CLEARCMDKEY, MAKELONG('S', SCMOD_CTRL), SCI_NULL);
	SendMessage(edit, SCI_CLEARCMDKEY, MAKELONG('H', SCMOD_CTRL), SCI_NULL);

	// Tabs and indentation
	SendMessage(edit, SCI_SETUSETABS, true, 0);
	SendMessage(edit, SCI_SETTABINDENTS, true, 0);
	SendMessage(edit, SCI_SETBACKSPACEUNINDENTS, true, 0);
	SendMessage(edit, SCI_SETTABWIDTH, 4, 0);
	SendMessage(edit, SCI_SETINDENT, 4, 0);

	// Scroll
	SendMessage(edit, SCI_SETENDATLASTLINE, true, 0);
	SendMessage(edit, SCI_SETSCROLLWIDTHTRACKING, true, 0);
	SendMessage(edit, SCI_SETSCROLLWIDTH, 1, 0);

	// Load properties
	sci_set_props(edit, g_sci_prop_sets.val());
}

static void sci_default_style(HWND edit)
{
	// Clear and reset all styles
	SendMessage(edit, SCI_CLEARDOCUMENTSTYLE, 0, 0);
	SendMessage(edit, SCI_STYLERESETDEFAULT, 0, 0);

	// enable line numbering
	SendMessage(edit, SCI_SETMARGINWIDTHN, 1, 0);
	SendMessage(edit, SCI_SETMARGINWIDTHN, 2, 0);
	SendMessage(edit, SCI_SETMARGINWIDTHN, 3, 0);
	SendMessage(edit, SCI_SETMARGINWIDTHN, 4, 0);
	SendMessage(edit, SCI_SETMARGINTYPEN, 0, SC_MARGIN_NUMBER);

	// Default styles
	sci_set_all_style_from_prop_by_table(edit, default_style_table);

	// Additional styles
	// 
	bool sel_fore, sel_back, line_back;
	DWORD color;

	color = sci_get_prop_color(edit, "style.selection.fore", &sel_fore);

	if (sel_fore)
		SendMessage(edit, SCI_SETSELFORE, true, color);
	else
		SendMessage(edit, SCI_SETSELFORE, false, 0);

	color = sci_get_prop_color(edit, "style.selection.back", &sel_back);

	if (sel_back)
	{
		SendMessage(edit, SCI_SETSELBACK, true, color);
	}
	else
	{
		if (sel_fore)
			SendMessage(edit, SCI_SETSELBACK, true, RGB(0xc0, 0xc0, 0xc0));
	}

	SendMessage(edit, SCI_SETSELALPHA, sci_get_prop_int(edit, "style.selection.alpha", SC_ALPHA_NOALPHA), 0);
	SendMessage(edit, SCI_SETCARETFORE, sci_get_prop_color(edit, "style.caret.fore"), 0);
	SendMessage(edit, SCI_SETCARETWIDTH, sci_get_prop_int(edit, "style.caret.width", 1), 0);

	color = sci_get_prop_color(edit, "style.caret.line.back", &line_back);

	if (line_back)
	{
		SendMessage(edit, SCI_SETCARETLINEVISIBLE, true, 0);
		SendMessage(edit, SCI_SETCARETLINEBACK, color, 0);
	}
	else
	{
		SendMessage(edit, SCI_SETCARETLINEVISIBLE, false, 0);
	}
	
	SendMessage(edit, SCI_SETCARETLINEBACKALPHA, sci_get_prop_int(edit, "style.caret.line.back.alpha", SC_ALPHA_NOALPHA), 0);
}

static void sci_set_JScript(HWND edit)
{
	const char js_keywords[] = "abstract boolean break byte case catch char class const continue \
							   debugger default delete do double else enum export extends false final \
							   finally float for function goto if implements import in instanceof int \
							   interface long native new null package private protected public return \
							   short static super switch synchronized this throw throws transient true \
							   try typeof var void while with enum byvalue cast future generic inner \
							   operator outer rest var Array Math RegExp window fb gdi utils";

	sci_default_style(edit);

	// Set lexer
	SendMessage(edit, SCI_SETLEXER, SCLEX_CPP, 0);
	// Set keywords
	SendMessage(edit, SCI_SETKEYWORDS, 0, (LPARAM)js_keywords);
	// Set styles
	sci_set_all_style_from_prop_by_table(edit, js_style_table);
	// Colorise
	SendMessage(edit, SCI_COLOURISE, 0, -1);
}

static void sci_set_VBScript(HWND edit)
{
	const char vbs_keywords[] = "addressof alias and as attribute base begin binary boolean byref byte byval call case cdbl \
								cint clng compare const csng cstr currency date decimal declare defbool defbyte defcur \
								defdate defdbl defdec defint deflng defobj defsng defstr defvar dim do double each else \
								elseif empty end enum eqv erase error event exit explicit false for friend function get \
								global gosub goto if imp implements in input integer is len let lib like load lock long \
								loop lset me mid midb mod new next not nothing null object on option optional or paramarray \
								preserve print private property public raiseevent randomize redim rem resume return rset \
								seek select set single static step stop string sub text then time to true type typeof \
								unload until variant wend while with withevents xor";

	sci_default_style(edit);

	// Set lexer
	SendMessage(edit, SCI_SETLEXER, SCLEX_VBSCRIPT, 0);
	// Set keywords
	SendMessage(edit, SCI_SETKEYWORDS, 0, (LPARAM)vbs_keywords);
	// Set styles
	sci_set_all_style_from_prop_by_table(edit, vbs_style_table);
	// Colorise
	SendMessage(edit, SCI_COLOURISE, 0, -1);
}

void sci_set_lexer(HWND edit, const char * lang)
{
	if (_stricmp(lang, "JScript") == 0)
	{
		sci_set_JScript(edit);
	}
	else if (_stricmp(lang, "VBScript") == 0)
	{
		sci_set_VBScript(edit);
	}
}

inline bool is_brace(char ch)
{
	return ch == '[' || ch == ']' || ch == '(' || ch == ')' || ch == '{' || ch == '}';
}

bool find_brace_match_pos(HWND edit, int &braceAtCaret, int &braceOpposite)
{
	bool isInside = false;
	int mask = (1 << SendMessage(edit, SCI_GETSTYLEBITS, 0, 0)) - 1;
	int caretPos = SendMessage(edit, SCI_GETCURRENTPOS, 0, 0);

	braceAtCaret = -1;
	braceOpposite = -1;

	char charBefore = 0;
	char styleBefore = 0;
	int lengthDoc = SendMessage(edit, SCI_GETLENGTH, 0, 0);

	if ((lengthDoc > 0) && (caretPos > 0))
	{
		// Check to ensure not matching brace that is part of a multibyte character
		int posBefore = SendMessage(edit, SCI_POSITIONBEFORE, caretPos, 0);

		if (posBefore == (caretPos - 1))
		{
			charBefore = static_cast<char>(SendMessage(edit, SCI_GETCHARAT, posBefore, 0));
			styleBefore = static_cast<char>(SendMessage(edit, SCI_GETSTYLEAT, posBefore, 0)) & mask;
		}
	}

	// Priority goes to character before caret
	if (charBefore && is_brace(charBefore))
	{
		braceAtCaret = caretPos - 1;
	}

	bool colonMode = false;
	long lexLanguage = SendMessage(edit, SCI_GETLEXER, 0, 0);

	bool isAfter = true;

	if (lengthDoc > 0 && (braceAtCaret < 0) && (caretPos < lengthDoc))
	{
		// No brace found so check other side
		// Check to ensure not matching brace that is part of a multibyte character
		char charAfter = static_cast<char>(SendMessage(edit, SCI_GETCHARAT, caretPos, 0));
		char styleAfter = static_cast<char>(SendMessage(edit, SCI_GETSTYLEAT, caretPos, 0)) & mask;

		if (charAfter && is_brace(charAfter))
		{
			braceAtCaret = caretPos;
			isAfter = false;
		}
	}

	if (braceAtCaret >= 0)
	{
		if (colonMode)
		{
			int lineStart = SendMessage(edit, SCI_LINEFROMPOSITION, braceAtCaret, 0);
			int lineMaxSubord = SendMessage(edit, SCI_GETLASTCHILD, lineStart, -1);

			braceOpposite = SendMessage(edit, SCI_GETLINEENDPOSITION, lineMaxSubord, 0);
		}
		else
		{
			braceOpposite = SendMessage(edit, SCI_BRACEMATCH, braceAtCaret, 0);
		}

		if (braceOpposite > braceAtCaret)
			isInside = isAfter;
		else
			isInside = !isAfter;
	}

	return isInside;
}

void sci_match_brace(HWND edit)
{
	int braceAtCaret = -1;
	int braceOpposite = -1;

	find_brace_match_pos(edit, braceAtCaret, braceOpposite);

	if (braceAtCaret != -1 && braceOpposite == -1)
	{
		SendMessage(edit, SCI_BRACEBADLIGHT, braceAtCaret, 0);
		SendMessage(edit, SCI_SETHIGHLIGHTGUIDE, 0, 0);
	}
	else
	{
		char chBrace = static_cast<char>(SendMessage(edit, SCI_GETCHARAT, braceAtCaret, 0));

		SendMessage(edit, SCI_BRACEHIGHLIGHT, braceAtCaret, braceOpposite);

		int columnAtCaret = SendMessage(edit, SCI_GETCOLUMN, braceAtCaret, 0);
		int columnOpposite = SendMessage(edit, SCI_GETCOLUMN, braceOpposite, 0);

		SendMessage(edit, SCI_SETHIGHLIGHTGUIDE, min(columnAtCaret, columnOpposite), 0);
	}
}

void sci_auto_indent(HWND edit, char ch)
{
	pfc::array_t<char> linebuf;
	int curPos = SendMessage(edit, SCI_GETCURRENTPOS, 0, 0);
	int curLine = SendMessage(edit, SCI_LINEFROMPOSITION, curPos, 0);
	int eolMode = SendMessage(edit, SCI_GETEOLMODE, 0, 0);

	if (((eolMode == SC_EOL_CRLF || eolMode == SC_EOL_LF) && ch == '\n') || (eolMode == SC_EOL_CR && ch == '\r'))
	{
		if (curLine > 0)
		{
			int prevLineLength = SendMessage(edit, SCI_LINELENGTH, curLine - 1, 0);
			int slen = 0;

			linebuf.set_size(prevLineLength + 2);
			SendMessage(edit, SCI_GETLINE, curLine - 1, (LPARAM)linebuf.get_ptr());
			linebuf[prevLineLength] = 0;
			slen = strlen(linebuf.get_ptr());

			// Get indent
			for (int pos = 0; linebuf[pos]; ++pos)
			{
				if (linebuf[pos] != ' ' && linebuf[pos] != '\t')
				{
					linebuf[pos] = 0;
					break;
				}
			}

			// Smart indent?
			bool found_brace = false;

			for (int pos = slen - 1; pos >= 0 && linebuf[pos]; --pos)
			{
				char c = linebuf[pos];

				if (c == '\t' || c == ' ' || c == '\r' || c == '\n')
				{
					continue;
				}
				else if (c == '{' || c == '[' || c == '(')
				{
					found_brace = true;
				}

				break;
			}

			if (found_brace)
			{
				int len = strlen(linebuf.get_ptr());

				linebuf[len] = '\t';
				linebuf[len + 1] = 0;
			}

			SendMessage(edit, SCI_REPLACESEL, 0, (LPARAM)linebuf.get_ptr());
		}
	}
}

void sci_auto_margin_width(HWND edit)
{
	int linenumwidth = 1;
	int marginwidth, oldmarginwidth;
	int linecount;

	linecount = SendMessage(edit, SCI_GETLINECOUNT, 0, 0);

	while (linecount >= 10)
	{
		linecount /= 10;
		++linenumwidth;
	}

	oldmarginwidth = SendMessage(edit, SCI_GETMARGINWIDTHN, 0, 0);
	marginwidth = 4 + linenumwidth * (SendMessage(edit, SCI_TEXTWIDTH, STYLE_LINENUMBER, (LPARAM)"9"));

	if (oldmarginwidth != marginwidth)
		SendMessage(edit, SCI_SETMARGINWIDTHN, 0, marginwidth);
}

void sci_set_text(HWND edit, const char * text, bool clear_undo_buffer)
{
	SendMessage(edit, SCI_SETTEXT, 0, (LPARAM)text);
	SendMessage(edit, SCI_CONVERTEOLS, SC_EOL_CRLF, 0);

	if (clear_undo_buffer)
		SendMessage(edit, SCI_EMPTYUNDOBUFFER, 0, 0);

	SendMessage(edit, SCI_COLOURISE, 0, -1);
	SendMessage(edit, SCI_GRABFOCUS, 0, 0);
	sci_track_width(edit);
}

void sci_set_props(HWND edit, const t_sci_prop_set_list & data)
{
	for (t_size i = 0; i < data.get_count(); ++i)
	{
		SendMessage(edit, SCI_SETPROPERTY, (WPARAM)data[i].key.get_ptr(), (LPARAM)data[i].val.get_ptr());
	}
}

// Taken from SciTE source
bool sci_parse_style(const char * p_definition, t_sci_editor_style & p_style)
{
	if (p_definition == 0 || !*p_definition)
		return false;

	char * val = _strdup(p_definition);
	char * opt = val;

	while (opt)
	{
		// Find attribute separator
		char * cpComma = strchr(opt, ',');

		if (cpComma) 
		{
			// If found, we terminate the current attribute (opt) string
			*cpComma = 0;
		}

		// Find attribute name/value separator
		char * colon = strchr(opt, ':');

		if (colon)
		{
			// If found, we terminate the current attribute name and point on the value
			*colon++ = '\0';
		}

		if (0 == strcmp(opt, "italics"))
		{
			p_style.flags |= ESF_ITALICS;
			p_style.italics = true;
		}
		else if (0 == strcmp(opt, "notitalics"))
		{
			p_style.flags |= ESF_ITALICS;
			p_style.italics = false;
		}
		else if (0 == strcmp(opt, "bold")) 
		{
			p_style.flags |= ESF_BOLD;
			p_style.bold = true;
		}
		else if (0 == strcmp(opt, "notbold"))
		{
			p_style.flags |= ESF_BOLD;
			p_style.bold = false;
		}
		else if (0 == strcmp(opt, "font"))
		{
			p_style.flags |= ESF_FONT;
			p_style.font = colon;
		}
		else if (0 == strcmp(opt, "fore")) 
		{
			p_style.flags |= ESF_FORE;
			p_style.fore = sci_parse_hex(colon);
		}
		else if (0 == strcmp(opt, "back")) 
		{
			p_style.flags |= ESF_BACK;
			p_style.back = sci_parse_hex(colon);
		}
		else if (0 == strcmp(opt, "size"))
		{
			p_style.flags |= ESF_SIZE;
			p_style.size = atoi(colon);
		}
		else if (0 == strcmp(opt, "underlined"))
		{
			p_style.flags |= ESF_UNDERLINED;
			p_style.underlined = true;
		}
		else if (0 == strcmp(opt, "notunderlined"))
		{
			p_style.flags |= ESF_UNDERLINED;
			p_style.underlined = false;
		}
		else if (0 == strcmp(opt, "case"))
		{
			p_style.flags |= ESF_CASEFORCE;
			p_style.case_force = SC_CASE_MIXED;

			if (colon)
			{
				if (*colon == 'u')
					p_style.case_force = SC_CASE_UPPER;
				else if (*colon == 'l')
					p_style.case_force = SC_CASE_LOWER;
			}
		}

		if (cpComma)
			opt = cpComma + 1;
		else
			opt = 0;
	}

	delete [] val;
	return true;
}

void sci_set_style(HWND edit, int style_num, const t_sci_editor_style & style)
{	
	if (style.flags & ESF_FONT)
		SendMessage(edit, SCI_STYLESETFONT, style_num, (LPARAM)style.font.get_ptr());

	if (style.flags & ESF_SIZE)
		SendMessage(edit, SCI_STYLESETSIZE, style_num, style.size);

	if (style.flags & ESF_FORE)
		SendMessage(edit, SCI_STYLESETFORE, style_num, style.fore);

	if (style.flags & ESF_BACK)
		SendMessage(edit, SCI_STYLESETBACK, style_num, style.back);

	if (style.flags & ESF_ITALICS)
		SendMessage(edit, SCI_STYLESETITALIC, style_num, style.italics);

	if (style.flags & ESF_BOLD)
		SendMessage(edit, SCI_STYLESETBOLD, style_num, style.bold);

	if (style.flags & ESF_UNDERLINED)
		SendMessage(edit, SCI_STYLESETUNDERLINE, style_num, style.underlined);

	if (style.flags & ESF_CASEFORCE)
		SendMessage(edit, SCI_STYLESETCASE, style_num, style.case_force);
}

void sci_set_all_style_from_prop_by_table(HWND edit, const t_style_to_key_table table[])
{
	const char * key = NULL;

	for (int i = 0; table[i].style_num != -1; ++i)
	{
		if ((key = table[i].key) != NULL)
		{
			int style_num;
			pfc::array_t<char> definition;
			t_sci_editor_style style;
			int len;

			style_num = table[i].style_num;

			// Get property len
			len = SendMessage(edit, SCI_GETPROPERTYEXPANDED, (WPARAM)key, 0);

			if (len != 0)
			{
				// Alloc buffer
				definition.set_size(len + 1);
				definition[len] = 0;
				// Get property
				SendMessage(edit, SCI_GETPROPERTYEXPANDED, (WPARAM)key, (LPARAM)definition.get_ptr());

				if (!sci_parse_style(definition.get_ptr(), style))
					continue;

				sci_set_style(edit, style_num, style);
			}

			if (style_num == STYLE_DEFAULT)
				SendMessage(edit, SCI_STYLECLEARALL, 0, 0);
		}
	}
}

void sci_reload_style(HWND edit)
{
	int lexer = SendMessage(edit, SCI_GETLEXER, 0, 0);

	sci_set_props(edit, g_sci_prop_sets.val());

	if (lexer == SCLEX_CPP)
		sci_set_JScript(edit);
	else if (lexer == SCLEX_VBSCRIPT)
		sci_set_VBScript(edit);
}

void sci_track_width(HWND edit)
{
	int max_width = 1;

	for (int i = 0; i < SendMessage(edit, SCI_GETLINECOUNT, 0, 0); ++i)
	{
		// Get max width
		int pos = SendMessage(edit, SCI_GETLINEENDPOSITION, i, 0);
		int width = SendMessage(edit, SCI_POINTXFROMPOSITION, 0, pos);

		if (width > max_width)
			max_width = width;
	}

	SendMessage(edit, SCI_SETSCROLLWIDTH, max_width, 0);
}
