--===================================================
--
--				GHM_CodeField
--  			GHM_CodeField.lua
--
--	          CodeField object for GHM
--
-- 	  (c)2012 The Gryphonheart Team
--			All rights reserved
--===================================================

---------------------------------------------------------------------------------------------------------
-- Syntax Highlighting
---------------------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------------------
-- Keyword lookup
---------------------------------------------------------------------------------------------------------

local tabLen = 4;
local TAB = "%s\124CFFFFFFFF\124HHGHM_TAB:0\124h%s\124h\124r"

local ClearAllColorsAndTabs = function(str)
--str,count = gsub(str,"|CFF......([.|\"]-)|r","%1"); print(count)
	str = gsub(str, string.format(TAB, "", strrep(" ", tabLen)), strrep(" ", tabLen));
	str = gsub(str, "|CFF......", "");
	str = gsub(str, "|r", "");
	return str;
end

local function GetRawTextIndex(text, index)
	local lines = { string.split("\n", text) }
	local count = 0;
	for i, line in pairs(lines) do
		--print("line",i,"(",line,")","=",count ,"+", string.len(line) ,">=", index)
		if count + string.len(line) >= index then
			local indexOnLine = index - count;
			local cleanTextIndex = string.len(ClearAllColorsAndTabs(string.sub(line, 0, indexOnLine)));
			return i, indexOnLine, cleanTextIndex;
		end
		count = count + string.len(line) + 1;
	end
	return #(lines), string.len(lines[#(lines)]), string.len(lines[#(lines)]);
end

local function GetFormatPosFromCleanPos(text, cleanPos)
	local allCount = 1;
	local cleanCount = 0;
	local tab = string.format(TAB, "", strrep(" ", tabLen));
	while cleanCount < cleanPos do

		if string.find(text, "^" .. tab, allCount) then
			allCount = allCount + string.len(tab);
			cleanCount = cleanCount + 4;
		elseif string.find(text, "^|CFF......", allCount) then
			allCount = allCount + 10;
		elseif string.find(text, "^|r", allCount) then
			allCount = allCount + 2;
		else
			allCount = allCount + 1;
			cleanCount = cleanCount + 1;
		end
	end

	return allCount - 1;
end

assert(GetFormatPosFromCleanPos(string.format(TAB, "abc", strrep(" ", tabLen)), 1) == 1, "test 1");
assert(GetFormatPosFromCleanPos(string.format(TAB, "abc", strrep(" ", tabLen)), 3) == 3, "test 2");
local tabS = string.format(TAB, "", strrep(" ", tabLen));
local v = GetFormatPosFromCleanPos("abc" .. tabS, 4);
assert(v == string.len(tabS) + 3, "test 3", v, string.len(tabS) + 3);

local GetStringWidth = function(self, str, pos)
	if not (self.widthCalcFrame) then
		self.widthCalcFrame = self:CreateFontString();
		self.widthCalcFrame:SetFontObject(self:GetFontObject());
	end

	self.widthCalcFrame:SetText(strsub(str, 0, pos));

	return self.widthCalcFrame:GetWidth();
end

local GetCursorCoordinates = function(self)
	local pos = self:GetCursorPosition();
	local _, height = self:GetFont();
	local t = { strsplit("\n", self:GetText()) };
	local i = 1;
	local y = 0;
	while (pos > 0 and i <= #(t)) do
		local str = t[i];
		local len = strlen(str);
		if pos > len then
			pos = pos - len;
			y = y + height;
		else
			return GetStringWidth(self, str, pos), y;
		end

		i = i + 1;
	end
	return 0, y;
end


local function GetTextFormatIndex(text, lineNum, pos)
	local lines = { string.split("\n", text) }
	local count = 0;
	for i, line in pairs(lines) do
		if lineNum == i then
			return count + GetFormatPosFromCleanPos(line, pos);
		end
		count = count + string.len(line) + 1;
	end
end

local function pairsByKeys(t, f)
	local a = {}
	for n in pairs(t) do table.insert(a, n) end
	table.sort(a, f)
	local i = 0 -- iterator variable
	local iter = function() -- iterator function
		i = i + 1
		if a[i] == nil then return nil
		else return a[i], t[a[i]]
		end
	end
	return iter
end

local UpdateLookupVariables = function(self)
	local vars = self.lookupVariables;

	if not (self.varList) then
		local f = CreateFrame("Frame", nil, self);
		f:SetWidth(50);
		f:SetHeight(50);
		f:SetBackdrop({
			bgFile = "Interface/DialogFrame/UI-DialogBox-Background",
			edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
			tile = false,
			tileSize = 16,
			edgeSize = 16,
			insets = { left = 4, right = 4, top = 4, bottom = 4 }
		});

		local CreateButton = function()
			local b = CreateFrame("Button", nil, f);
			local fs = b:CreateFontString();
			fs:SetFont("Fonts\\FRIZQT__.TTF", 11);
			b:SetWidth(f:GetWidth());
			b:SetHeight(11);
			fs:SetPoint("LEFT", 0, 0);
			b.SetText = function(b, str)
				fs:SetText(str);
				b:SetWidth(fs:GetWidth())
			end

			return b;
		end

		f.buttons = {};


		f.SetTexts = function(s, texts)

			local i = 1;
			local y = 10;
			local x = 40;
			while (i <= #(texts)) do
				if not (f.buttons[i]) then
					f.buttons[i] = CreateButton();
					f.buttons[i]:SetPoint("TOPLEFT", 10, -y);
				end
				f.buttons[i]:Show();
				f.buttons[i]:SetText(texts[i]);

				y = y + f.buttons[i]:GetHeight();
				x = max(f.buttons[i]:GetWidth(), x);
				i = i + 1;
			end

			while (f.buttons[i]) do
				f.buttons[i]:Hide();
				i = i + 1;
			end

			f:SetWidth(x + 20);
			f:SetHeight(y + 10);
		end

		self.varList = f;
	end

	if vars then
		self.varList:ClearAllPoints();
		local x, y = GetCursorCoordinates(self);
		self.varList:SetPoint("TOPLEFT", x, -y or 0);

		local text = {};

		for varName, var in pairsByKeys(vars) do
			table.insert(text, varName .. " : " .. type(var));
		end

		if #(text) > 0 then
			self.varList:SetTexts(text);
			self.varList:Show();
		else
			self.varList:Hide();
		end
	else
		self.varList:Hide();
	end
end

local LookupVariables = function(self)
	if not (self.executionEnv) then return; end;

	local text = self:GetText();
	local pos = self:GetCursorPosition();
	local pretext = strsub(text, 0, pos);

	local searchWord;
	gsub(pretext, "(%a[%a%d]*)$", function(w) searchWord = w; end);

	if searchWord then
		self.lookupVariables = self.executionEnv.GetAllValues(searchWord);
	else
		self.lookupVariables = nil;
	end
	UpdateLookupVariables(self);
end


---------------------------------------------------------------------------------------------------------
-- The GHM Object
---------------------------------------------------------------------------------------------------------
local DEFAULT_HEIGHT = 80;
local DEFAULT_WIDTH = 170;

local codeFields = {};

local count = 1;
function GHM_CodeField(parent, main, profile)
	local frame = CreateFrame("Frame", "GHM_CodeField" .. count, parent, "GHM_CodeField_Template");
	count = count + 1;

	local textLabel =_G[frame:GetName().."TextLabel"];
	textLabel:SetText(profile.text)
	_G[frame:GetName() .. "Text"].tooltip = profile.tooltip;
	local area = _G[frame:GetName().."Area"];
	local textFrame = _G[frame:GetName().."AreaScrollText"];
	frame.field = textFrame;

	frame.GetCleanText = function(self)
		local t = textFrame:GetText();
		return ClearAllColorsAndTabs(t);
	end

	local varAttFrame;
	frame.GetValue = function(self)
		if (varAttFrame and not (varAttFrame:IsStaticTabShown())) then
			return varAttFrame:GetValue();
		else
			return frame.GetCleanText();
		end
	end

	local Force1 = function(inputValue)
		textFrame:SetText(inputValue)
	end

	local Force2 = function(inputType, inputValue)
		if (inputType == "attribute" or inputType == "variable") and varAttFrame then
			textFrame:SetText("");
			varAttFrame:SetValue(inputType, inputValue);

		else -- static
			varAttFrame:Clear();
			textFrame:SetText(inputValue);
		end
	end


	frame.Force = function(self, ...)
		if self ~= frame then return frame.Force(frame, self, ...); end
		local numInput = #({ ... });

		if numInput == 1 then
			Force1(...);
		elseif numInput == 2 then
			Force2(...);
		end
	end

	frame.EnableVariableAttributeInput = function(self, scriptingEnv, item)
		if not (varAttFrame) then
			varAttFrame = GHM_VarAttInput(frame, area, area:GetWidth());
		end
		varAttFrame:EnableVariableAttributeInput(scriptingEnv, item, profile.outputOnly)
	end

	-- events
	local ignore;
	textFrame:SetScript("OnTextChanged",function(self)
		if ignore == true then
			return
		end
		--Start();

		local startText = self:GetText();
		if (startText == lastText) then
			return
		end

		local pos = self:GetCursorPosition();
		local cleanLine, _, cleanPos = GetRawTextIndex(startText, pos);
		local cleanText= ClearAllColorsAndTabs(startText);
		local text = GHM_HighlightKeywords(cleanText);

		if text ~= startText then
			ignore = true;
			lastText = text;
			self:SetText(text or "");
			local newPos = GetTextFormatIndex(text or "", cleanLine, cleanPos);
			self:SetCursorPosition(newPos)



			ignore = false;
		end


		-- lookup variables
		LookupVariables(self)
	end);
	textFrame:SetScript("OnTabPressed",function(self)
		local pos = self:GetCursorPosition()
		local before = strsub(self:GetText(), 0, pos);
		local after = strsub(self:GetText(), pos + 1);
		self:SetText(string.join("", before, strrep(" ", tabLen), after));
		self:SetCursorPosition(pos + tabLen)
	end)

	table.insert(codeFields, frame.field);


	-- positioning
	local width = profile.width or DEFAULT_WIDTH;
	frame:SetWidth(width);
	local height = profile.height or DEFAULT_HEIGHT;
	frame:SetHeight(height);

	local extraX = profile.xOff or 0;
	local extraY = profile.yOff or 0;

	if profile.align == "c" then
		frame:SetPoint("CENTER", parent, "CENTER", extraX, extraY);
	elseif profile.align == "r" then
		frame:SetPoint("RIGHT", parent.lastRight or parent, "RIGHT", extraX, extraY);
		parent.lastRight = frame;
	else
		if parent.lastLeft then frame:SetPoint("LEFT", parent.lastLeft, "RIGHT", extraX, extraY); else frame:SetPoint("LEFT", parent, "LEFT", extraX, extraY); end
		parent.lastLeft = frame;
	end

	if type(profile.OnLoad) == "function" then
		profile.OnLoad(frame);
	end

	frame:Show();

	return frame;
end


---------------------------------------------------------------------------------------------------------
-- Global update triggers
---------------------------------------------------------------------------------------------------------

local updateColors = false;
function GHM_CodeField_UpdateColors()
	updateColors = true;
end

local f = CreateFrame("Frame");
f:SetScript("OnUpdate", function(...)
	if updateColors == true then
		updateColors = false;
		for _, frame in pairs(codeFields) do
			frame:GetScript("OnTextChanged")(frame);
		end
	end
end);

