--===================================================
--
--				GHM_CodeField_SyntaxHighlight
--  			GHM_CodeField_SyntaxHighlight.lua
--
--	          (description)
--
-- 	  (c)2011 The Gryphonheart Team
--			All rights reserved
--===================================================


local keywords = { "and", "break", "do", "else", "elseif", "end", "for", "function", "if", "in", "local", "not", "or", "return", "then", "while" };
local booleans = { "true", "false", "nil" };

local DEFAULT_SYNTAX_COLORS = {
	keyword = { 0.6, 0.6, 1.0 },
	comment = { 1.0, 0.6, 0.6 },
	string = { 0.6, 1.0, 0.6 },
	boolean = { 0.5, 0.9, 1.0 },
	number = { 0.8, 0.2, 0.8 },
};
local syntaxColors = {};
for i, v in pairs(DEFAULT_SYNTAX_COLORS) do
	local t = {};
	for i2, v2 in pairs(v) do
		t[i2] = v2;
	end
	syntaxColors[i] = t;
end


local time = 0;
local Start = function()
	time = GetTime();
end
local Check = function()
	if GetTime() - time > 2 then
		error("GHI. Syntax highlight caught in loop!")
	end
end
local lastText = "";

local ColorString = function(s, r, g, b)
	if not (s) or s == "" then return ""; end;
	if not (r) or not (g) or not (b) then return ""; end;
	return "|CFF" .. string.format("%.2x", r * 255) .. string.format("%.2x", g * 255) .. string.format("%.2x", b * 255) .. s .. "|r";
end

function GHM_GetSyntaxCatagories()
	local t = {};
	for i, _ in pairs(syntaxColors) do
		table.insert(t, i);
	end
	return t;
end

function GHM_GetSyntaxColor(catagory, default)
	local t = syntaxColors[catagory];
	if not (t) or default then
		t = DEFAULT_SYNTAX_COLORS[catagory];
	end
	if t then
		return t[1] or 0, t[2] or 0, t[3] or 0;
	end
	return 0, 0, 0;
end

function GHM_SetSyntaxColor(catagory, r, g, b)
	syntaxColors[catagory] = syntaxColors[catagory] or {};
	syntaxColors[catagory][1] = r;
	syntaxColors[catagory][2] = g;
	syntaxColors[catagory][3] = b;

	GHI_MiscData.SyntaxColor = syntaxColors;
	GHM_CodeField_UpdateColors()
end

function GHM_DisableSyntaxColor(catagory, disabled)
	syntaxColors[catagory] = syntaxColors[catagory] or {};
	syntaxColors[catagory].disabled = disabled;
	lastText = "";
	GHM_OptionsData.SyntaxColor = syntaxColors;
	GHM_CodeField_UpdateColors()
end

function GHM_IsSyntaxColorDisabled(catagory)
	return (syntaxColors[catagory] or {}).disabled;
end

local f = CreateFrame("Frame");
f:SetScript("OnEvent", function(...)
	if GHM_OptionsData and type(GHM_OptionsData.SyntaxColor) == "table" then
		for i, v in pairs(GHM_OptionsData.SyntaxColor) do
			syntaxColors[i] = v;
		end
	end
end)
f:RegisterEvent("VARIABLES_LOADED")


GHM_ColorSyntax = function(s, catagory)
	if GHM_IsSyntaxColorDisabled(catagory) then
		return s;
	end
	return ColorString(s, GHM_GetSyntaxColor(catagory));
end


local ValidateKeyword = function(word)
	if tContains(keywords,word) then
		return true, "keyword";
	elseif tContains(booleans, word) then
		return true, "boolean";
	end
	return false;
end

local toF1 = '[a-zA-Z_]';

local states = {
	none = {
		['-'] = {
			next = 'c1',
		},
		[toF1] = {
			next = 'f1',
		},
		['['] = {
			next = 's3',
		},
		["'"] = {
			next = 's2',
			highlight = {
				offset = 0,
				type = "start",
				category = "string",
			},
		},
		['"'] = {
			next = 's1',
			highlight = {
				offset = 0,
				type = "start",
				category = "string",
			},
		},
		['[0-9]'] = {
			next = 'n1',
			highlight = {
				offset = 0,
				type = "start",
				category = "number",
			},
		},
		['else'] = {
			next = 'none',
		},
	},

	-- Comment
	c1 = {
		['-'] = {
			next = 'c2',
			highlight = {
				offset = -1,
				type = "start",
				category = "comment",
			},
		},
		[toF1] = {
			next = 'f1',
		},
		['['] = {
			next = 's3',
		},
		["'"] = {
			next = 's2',
			highlight = {
				offset = 0,
				type = "start",
				category = "string",
			},
		},
		['"'] = {
			next = 's1',
			highlight = {
				offset = 0,
				type = "start",
				category = "string",
			},
		},
		['[0-9]'] = {
			next = 'n1',
			highlight = {
				offset = 0,
				type = "start",
				category = "number",
			},
		},
		['else'] = {
			next = 'none',
		},
	},
	c2 = {
		['['] = {
			next = 'c4',
		},
		['else'] = {
			next = 'c3',
		},
	},
	c3 = {
		['\n'] = {
			next = 'none',
			highlight = {
				offset = 0,
				type = "end",
				category = "comment",
			},
		},
		['else'] = {
			next = 'c3',
		},
	},
	c4 = {
		['['] = {
			next = 'c5',
		},
		['else'] = {
			next = 'c3',
		},
	},
	c5 = {
		[']'] = {
			next = 'c6',
		},
		['else'] = {
			next = 'c5',
		},
	},
	c6 = {
		[']'] = {
			next = 'none',
			highlight = {
				offset = 1,
				type = "end",
				category = "comment",
			},
		},
		['else'] = {
			next = 'c5',
		},
	},

	-- String
	s1 = {
		['\n'] = {
			next = 'none',
			highlight = {
				offset = 1,
				type = "end",
				category = "string",
			},
		},
		['"'] = {
			next = 'none',
			highlight = {
				offset = 1,
				type = "end",
				category = "string",
			},
		},
		['else'] = {
			next = 's1',
		},
	},
	s2 = {
		['\n'] = {
			next = 'n1',
			highlight = {
				offset = 1,
				type = "end",
				category = "string",
			},
		},
		["'"] = {
			next = 'none',
			highlight = {
				offset = 1,
				type = "end",
				category = "string",
			},
		},
		['else'] = {
			next = 's2',
		},
	},
	s3 = {
		[toF1] = {
			next = 'f1',
		},
		['['] = {
			next = 's4',
			highlight = {
				offset = -1,
				type = "start",
				category = "string",
			},
		},
		["'"] = {
			next = 's2',
			highlight = {
				offset = 0,
				type = "start",
				category = "string",
			},
		},
		['"'] = {
			next = 's1',
			highlight = {
				offset = 0,
				type = "start",
				category = "string",
			},
		},
		['-'] = {
			next = 'c1',
		},
		['[0-9]'] = {
			next = 'n1',
			highlight = {
				offset = 0,
				type = "start",
				category = "number",
			},
		},
		['else'] = {
			next = 'none',
		},
	},
	s4 = {
		[']'] = {
			next = 's5',
		},
		['else'] = {
			next = 's4',
		},
	},
	s5 = {
		[']'] = {
			next = 'none',
			highlight = {
				offset = 1,
				type = "end",
				category = "string",
			},
		},
		['else'] = {
			next = 's4',
		},
	},

	-- Number
	n1 = {
		['[0-9\.]'] = {
			next = 'n1',
		},
		['-'] = {
			next = 'c1',
			highlight = {
				offset = 0,
				type = "end",
				category = "number",
			},
		},
		['['] = {
			next = 's3',
			highlight = {
				offset = 0,
				type = "end",
				category = "number",
			},
		},
		['else'] = {
			next = 'none',
			highlight = {
				offset = 0,
				type = "end",
				category = "number",
			},
		},
	},
	f1 = {
		['-'] = {
			next = 'c1',
		},
		['[A-Za-z]'] = {
			next = 'f2',
			highlight = {
				offset = -1,
				type = "start",
				category = "keyword",
			},
		},

		["'"] = {
			next = 's2',
			highlight = {
				offset = 0,
				type = "start",
				category = "string",
			},
		},
		['"'] = {
			next = 's1',
			highlight = {
				offset = 0,
				type = "start",
				category = "string",
			},
		},
		['['] = {
			next = 's3',
		},
		['else'] = {
			next = 'none',
		},
	},
	f2 = {

		['[A-Za-z0-9]'] = {
			next = 'f2',
		},
		['-'] = {
			next = 'c1',
			highlight = {
				offset = 0,
				type = "end",
				category = "keyword",
				validate = ValidateKeyword,
			},
		},
		['else'] = {
			next = 'none',
			highlight = {
				offset = 0,
				type = "end",
				category = "keyword",
				validate = ValidateKeyword,
			},
		},
	},
};

local HighlighKeywords;
local highlightBuffer = {};
function GHM_HighlightKeywords(text)
	-- check the buffer for fitting text
	local bufferText = text;
	local bufferTextOffset = 0;
	local coloredText = "";

	local lastBufferFitting = 0;
	local lastOffsetFitting = 0;

	for i,buffer in pairs(highlightBuffer) do
		if (buffer.raw == bufferText:sub(bufferTextOffset, bufferTextOffset + buffer.raw:len())) then
			coloredText = coloredText..buffer.colored;
			lastBufferFitting = i;
			bufferTextOffset = bufferTextOffset + buffer.raw:len();
			lastOffsetFitting = bufferTextOffset;
		else
			break;
		end
	end

	if bufferTextOffset == bufferText:len() then -- All is matching
		return coloredText;
	end

	bufferTextOffset = bufferText:len();
	local firstBufferFitting = #(highlightBuffer);
	bufferTextOffset = bufferText:len()
	local firstOffsetFitting = bufferTextOffset;
	local coloredTextEnd = "";

	for i=#(highlightBuffer),lastBufferFitting+1,-1 do -- Buffer after
		local buffer = highlightBuffer[i];
		if (buffer.raw == bufferText:sub(bufferTextOffset - buffer.raw:len(), bufferTextOffset)) then
			coloredTextEnd = buffer.colored..coloredTextEnd;
			firstBufferFitting = i;
			bufferTextOffset = bufferTextOffset - buffer.raw:len();
			firstOffsetFitting = bufferTextOffset;
		else
			break;
		end
	end

	local remainingText = bufferText:sub(lastOffsetFitting,firstOffsetFitting);
	local coloredTextMiddle,middleBuffer = HighlighKeywords(remainingText);
	local colored = coloredText..coloredTextMiddle..coloredTextEnd;
	for i=lastBufferFitting,firstBufferFitting do -- remove invalid buffers
		highlightBuffer[i] = nil;
	end
	for i=1,#(middleBuffer) do -- insert new buffers
		table.insert(highlightBuffer,lastBufferFitting+i,middleBuffer[i]);
	end
	local t = {};
	for i,v in pairs(highlightBuffer) do
		table.insert(t,v);
	end
	highlightBuffer = t;
	return colored;
end


HighlighKeywords = function(text)
	local highlights = {};
	local currentHighlight;
	local state = 'none';

	local chars = {};
	for i=1,string.len(text) do
		chars[i] = string.sub(text,i,i);
	end
	--print(text);
	for i=1,#(chars) do
		local char = chars[i];
		--print(state);
		local info = (states[state][char]);

		if not(info) then
			for pattern,pInfo in pairs(states[state]) do
				if string.len(pattern) > 1 and not(pattern == "else") then
					if string.match(char,pattern) then
						info = pInfo;   --print(char,"matches",pattern)
					else
						--print(char,"no match",pattern)
					end
				end
			end
		end

		if not(info) then
			info = states[state]["else"];
		end

		if (info.highlight) then
			local hl = info.highlight;
			assert((currentHighlight and hl.type == "end" and hl.category == currentHighlight.category) or hl.type == "start","Incorrect type match",state,char)

			if hl.type == "start" then
				currentHighlight = {
					hstart = i+hl.offset,
					category = hl.category,
				}
			elseif hl.type == "end" then
				currentHighlight.hend = i+hl.offset;
				currentHighlight.validate = hl.validate;
				table.insert(highlights,currentHighlight);
				currentHighlight = nil;
			end
		end

		state = info.next;
	end

	--print(state,currentHighlight and currentHighlight.category);
	if currentHighlight then
		currentHighlight.hend = #(chars)+1;
		if state == "f2" then
			currentHighlight.validate = ValidateKeyword;
		end
		table.insert(highlights,currentHighlight);
	end

	local coloredText = "";
	local chunks = {};
	local currentChunk = "";
	local currentChunkRaw = "";
	local prevIndex = 0;

	for _,highlight in pairs(highlights) do
		currentChunk = currentChunk..strsubutf8(text,prevIndex,highlight.hstart-1);
		currentChunkRaw = currentChunkRaw..strsubutf8(text,prevIndex,highlight.hstart-1);
		if highlight.validate then
			local text2 = strsubutf8(text,highlight.hstart,highlight.hend-1);
			local isHighlight,category = highlight.validate(text2)
			if isHighlight then
				currentChunk = currentChunk..GHM_ColorSyntax(text2,category);
				currentChunkRaw = currentChunkRaw..text2;
			else
				currentChunk = currentChunk..text2;
				currentChunkRaw = currentChunkRaw..text2
			end
			prevIndex = highlight.hend;
		else

			currentChunk = currentChunk..GHM_ColorSyntax(strsubutf8(text,highlight.hstart,highlight.hend-1),highlight.category);
			currentChunkRaw = currentChunkRaw..strsubutf8(text,highlight.hstart,highlight.hend-1);
			prevIndex = highlight.hend;
		end

		-- Save the chunk
		if (currentChunkRaw:len() > 128) then
			table.insert(chunks,{
				colored = currentChunk,
				raw = currentChunkRaw,
			});
			coloredText = coloredText..currentChunk;
			currentChunk = "";
			currentChunkRaw = "";
		end

	end

	currentChunk = currentChunk..strsubutf8(text,prevIndex);
	currentChunkRaw = currentChunkRaw..strsubutf8(text,prevIndex);
	coloredText = coloredText..currentChunk;
	table.insert(chunks,{
		colored = currentChunk,
		raw = currentChunkRaw,
	});

	return coloredText,chunks;
end


