﻿--[[ KLH Threatmeter KLHTM_Gui.lua

local version -> to be integrated into kenco files

Lukon Mod 2: The replacement for Gui.lua and Tables.lua. Controls the GUI 
for KLH ThreatMeter, along with KTM_Frame.xml, KTM_RaidGui.lua, 
KTM_SelfGui.lua, KTM_TitleGui.lua.

There is a single main frame, which shows either raid threat data 
(KTM_RaidGui.lua) or personal threat details (KTM_SelfGui.lua). Each of 
these two frames contains column headers, data rows and a bottom bar. 
Additionally there is a title bar frame (KTM_TitleGui). The GUI elements are
contained in a table "KLHTM_Gui", populated by the CreateGuiTable(). This 
also contains some properties not set in the XML file, such as some 
dimensions and colours.

The appearance of the GUI can be customised via the "KLHTM_GuiOptions" table.
This determines the visibility of command buttons and data columns, the way 
data is displayed in the main tables, and other settings. User access to
these settings is via the options gui (KTM_OptionsFrame.xml, 
KTM_OptionsGui.lua).

The visibility and some properties of the frame as a whole are kept in the
"state" table.

NB the indexes in the tables "gui", "options", and elsewhere shouldn't be
changed. They are assumed to match by various methods.
--]]

-- ken
local mod = klhtm
local me = { }
mod.gui = me

-- The minimum period, in seconds, between data table redrawing
local Min_Redraw_Period = 0.2;

-- When the table was last redrawn
local lastRedraw = 0;
KLHTM_LastRedraw = lastRedraw;

-- Set to true when a redraw has been requested but not yet performed
local needsRedraw = false;
KLHTM_NeedsRedraw = needsRedraw;

-- Contains settings describing the way raid and self data should displayed. 
-- Loaded from saved variables or initialised by _CreateDefaultOptions()
local options = {};
KLHTM_GuiOptions = options;

-- Contains window state information. Loaded from saved variables or 
-- initialised by _CreateDefaultState().
local state = {};
KLHTM_GuiState = state;

-- Contains references to the GUI components and some additional properties
-- not specified in the XML file. Initialised by _CreateGuiTable()
local gui = {};
KLHTM_Gui = gui;

-- true if initialisation (SetupGui) has finished, and rendering is possible
local isInitialised = false;
KLHTM_IsLoaded = false;

-- the maximum change in string width allowed by KLHTM_LocaliseStringWidth()
local Max_Localisation_Factor = 2;

-- frame scale constants
KLHTM_Scale = {["min"] = 0.6, ["max"] = 1.3, ["tick"] = 0.02, ["default"] = 1.0};

-- the current dimensions of the frame and subframes. These are filled by the various
-- UpdateXFrame and Redraw methods.
local sizes = {	["raid"] = {},		["self"] = {},	["but"] = {}, 
				["string"] = {},	["title"] = {},	["frame"] = {}, };
KLHTM_GuiSizes = sizes;

-- The heights of some sizes
KLHTM_GuiHeights = {["button"] = 15, ["string"] = 12, ["header"] = 14, ["data"] = 12};

-- The texture gradient colours used by the main frame and options frame's title bar
KLHTM_TitleBarColours = {
	-- purple
	["raid"] = {["minR"] = 0.2, ["minG"] = 0.0, ["minB"] = 0.2, ["minA"] = 0.5,
				["maxR"] = 1.0, ["maxG"] = 0.0, ["maxB"] = 1.0, ["maxA"] = 0.5, },
	-- dark green
	["self"] = {["minR"] = 0.0, ["minG"] = 0.2, ["minB"] = 0.0, ["minA"] = 0.5,
				["maxR"] = 0.0, ["maxG"] = 0.7, ["maxB"] = 0.0, ["maxA"] = 0.5, },
	-- blue
	["gen"] =  {["minR"] = 0.0, ["minG"] = 0.2, ["minB"] = 0.2, ["minA"] = 0.5,
				["maxR"] = 0.0, ["maxG"] = 1.0, ["maxB"] = 1.0, ["maxA"] = 0.5, }, };
	
------------------------------------------------------------------------------
-- Prepares the GUI for use. Called after the Variables Loaded event is
-- received (see KLHTM_Frame <OnLoad>, <OnEvent>).
--
-- some patches to official KLHTM: (a) removed setupaftervariablesloaded
-- method, and its reference in ktmMain. (b) events now come from the update
-- frame. The main frame only sends the variables loaded event.
------------------------------------------------------------------------------

me.myevents = { "ADDON_LOADED" }

me.onevent = function()
	
	KLHTM_SetupGui()
	
end

me.onupdate = function()

	KLHTM_Redraw()

end

function KLHTM_SetupGui()
	
	if (isInitialised) then
		-- will this ever happen? who knows...
		return; 
	end
	
	KLHTM_LoadVariables();
	KLHTM_CreateGuiTable();
	KLHTM_SetupGuiComponents();
	
	-- apply state and options
	KLHTM_UpdateSelfFrame();
	KLHTM_UpdateRaidFrame();
	KLHTM_UpdateTitleButtons();
	KLHTM_UpdateTitleStrings();
	
	isInitialised = true;
	
	-- probably going to cause some dumb error...
	KLHTM_Redraw(true);
	
	KLHTM_UpdateFrame();
	KLHTM_SetGuiScale(options.scale);
	
	if (state.closed ~= true) then
		gui.frame:Show();
	end
end


------------------------------------------------------------------------------
-- Loads data from saved variables. If the required data is not found, default
-- settings are used instead.
------------------------------------------------------------------------------
function KLHTM_LoadVariables()
	
	if (KLHTM_SavedVariables == nil) then
		KLHTM_SavedVariables = {};
	end

	if (KLHTM_SavedVariables.gui) then
		
		if mod.out.checktrace("info", me, "savedvariables") then
			mod.out.printtrace("Loading KTM saved variables");
		end
		
		-- nb byval copies to preserve external references to KLHTM_GuiState and
		-- KLHTM_GuiOptions. Todo: more robust format
		for index, value in KLHTM_SavedVariables.gui.state do
			state[index] = value;
		end
		for index, value in KLHTM_SavedVariables.gui.options do
			options[index] = value;
		end
		
		-- todo: better saved variables upgrading
		if (options.buttonVis.min.targ == nil) then
			options.buttonVis.min.targ = false;
			options.buttonVis.max.targ = true;
		end
		if (options.buttonVis.min.clear == nil) then
			options.buttonVis.min.clear = false;
			options.buttonVis.max.clear = false;
		end
	else
		if mod.out.checktrace("info", me, "savedvariables") then
			mod.out.printtrace("Performing fresh install of KTM")
		end
		
		KLHTM_SetDefaultOptions();
		KLHTM_SetDefaultState();
	end
	
	KLHTM_SavedVariables.gui = {};
	KLHTM_SavedVariables.gui.version = mod.build;
	KLHTM_SavedVariables.gui.options = options;
	KLHTM_SavedVariables.gui.state = state;
end


------------------------------------------------------------------------------
-- Sets up the variable "KLHTM_Gui". It contains (a) references to the gui
-- components, (b) visibility data, (c) string widths, (d) some colours.
------------------------------------------------------------------------------
function KLHTM_CreateGuiTable()
	
	gui.frame = KLHTM_Frame;
	gui.topdiv = KLHTM_FrameTopDivider;
	gui.bottomdiv = KLHTM_FrameBottomDivider;
	
	KLHTM_CreateTitleTable();
	KLHTM_CreateRaidTable();
	KLHTM_CreateSelfTable();
	KLHTM_CreateOptionsTable();
end


------------------------------------------------------------------------------
-- Applies Gui component properties that are not specified in the XML.
------------------------------------------------------------------------------
function KLHTM_SetupGuiComponents()
	
	KLHTM_SetupTitleGui();
	KLHTM_SetupRaidGui();
	KLHTM_SetupSelfGui();
	KLHTM_SetupOptionsGui();
	
	-- frame
	gui.frame:RegisterForDrag("LeftButton");
	gui.frame:SetMovable(true);
	gui.frame:SetUserPlaced(true);
	gui.frame:SetBackdropColor(0.1, 0.1, 0.1);
	gui.frame:SetBackdropBorderColor(1, 1, 1);
end


------------------------------------------------------------------------------
-- Initialises the local variable "options". It contains settings describing
-- how the self and data windows and the title bar should be displayed.
------------------------------------------------------------------------------
function KLHTM_SetDefaultOptions()
	
	options.scale = KLHTM_Scale.default;
	
	KLHTM_SetDefaultRaidOptions();
	KLHTM_SetDefaultSelfOptions();
	KLHTM_SetDefaultTitleOptions();
end


------------------------------------------------------------------------------
-- Initialises the local variable "state", containing GUI display properties
------------------------------------------------------------------------------
function KLHTM_SetDefaultState()
	
	state.min = false;
	state.max = true;
	state.minmax = "max";
	
	state.raid = true;
	state.self = false;
	state.view = "raid";
	
	state.pinned = false;
	state.closed = false;
end


------------------------------------------------------------------------------
-- Changes the raid \ self view.
--
-- [newView] - either "self" or "raid"
------------------------------------------------------------------------------
function KLHTM_SetView(newView)
	
	if ((newView ~= "self") and (newView ~= "raid")) then
		if mod.out.checktrace("warning", me, "invalidargument") then
			mod.out.printtrace(string.format("The argument '%s' to SetView is not recognised.", tostring(newView)))
		end
		return;
	end
	
	state.view = newView;
	state.raid = not state.raid;
	state.self = not state.self;
	
	KLHTM_Redraw(true);
	
	local col = KLHTM_TitleBarColours[state.view];	
	gui.title.back:SetGradientAlpha("VERTICAL", col.minR, col.minG, col.minB, col.minA, col.maxR, col.maxG, col.maxB, col.maxA);
	
	KLHTM_UpdateTitleButtons();
	KLHTM_UpdateTitleStrings();
	KLHTM_UpdateFrame();
end


------------------------------------------------------------------------------
-- Changes the minimised \ maximised state.
--
-- [newMinMax] - either "min" or "max"
------------------------------------------------------------------------------
function KLHTM_SetMinMax(newMinMax)
	
	if ((newMinMax ~= "min") and (newMinMax ~= "max")) then
		if mod.out.checktrace("warning", me, "invalidargument") then
			mod.out.printtrace(string.format("The argument '%s' to SetMinMax is not recognised.", tostring(newMinMax)))
		end
		return;
	end
	
	state.minmax = newMinMax;
	state.min = not state.min;
	state.max = not state.max;
	
	KLHTM_Redraw(true);
	
	KLHTM_UpdateTitleButtons();
	KLHTM_UpdateTitleStrings();
	KLHTM_UpdateFrame();
end


------------------------------------------------------------------------------
-- Sets the frame visibility. Should this method even exist?
--
-- [newVisible] - set to true to show the frame
------------------------------------------------------------------------------
function KLHTM_SetVisible(newVisible)
	
	if ((newVisible ~= false) and (newVisible ~= true)) then
		if mod.out.checktrace("warning", me, "invalidargument") then
			mod.out.printtrace(string.format("The argument to '%s' to SetVisible is not recognised.", tostring(newVisible)))
		end
	end
	
	state.closed = not newVisible;
	
	if (newVisible) then
		KLHTM_Redraw(true);
		gui.frame:Show();
	else
		gui.frame:Hide();
	end
end


------------------------------------------------------------------------------
-- Changes the global scale of the main frame.
--
-- [newScale] - a value between 0.5 and 1.2
------------------------------------------------------------------------------
function KLHTM_SetGuiScale(newScale)
	
	local argument = newScale
	newScale = tonumber(newScale);
	
	if (newScale == nil) then
		if mod.out.checktrace("warning", me, "invalidargument") then
			mod.out.printtrace(string.format("The argument '%s' to SetGuiScale is not a number.", tostring(argument)))
		end
		return;
	end
	
	if ((newScale < KLHTM_Scale.min) or (newScale > KLHTM_Scale.max)) then
		if mod.out.checktrace("warning", me, "invalidargument") then
			mod.out.printtrace(string.format("The argument '%s' to SetGuiScale is outside the valid bounds.", newScale))
		end
		return;
	end
	
	-- maintain the top-right corner when resizing
	local right = gui.frame:GetRight();
	local top = gui.frame:GetTop();
	
	gui.frame:SetScale(newScale);
	
	if ((top ~= nil) and (right ~= nil)) then
		top = top * options.scale / newScale;
		right = right * options.scale / newScale;
		gui.frame:ClearAllPoints();
		gui.frame:SetPoint("TOPRIGHT", UIParent, "BOTTOMLEFT", right, top);
	else
		-- should occur the first time the mod is loaded
		gui.frame:ClearAllPoints();
		gui.frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0);
	end
	
	options.scale = newScale;
end


------------------------------------------------------------------------------
-- Add an black outline to the specified FontString. This increases its 
-- legibility when viewed on a light background, eg the self view bars. It is
-- normally able to be specified via the XML, but this is not possible due to 
-- compatibility reasons with some localisations. The default minimim ouline is
-- very dark and overpowering, so the shadow's alpha is reduced (to 0, but there
-- seems to be a minimum value).
--
-- [fontstring] - a GUI FontString object
------------------------------------------------------------------------------
function KLHTM_AddOutline(fontstring)
	
	local path, height;
	path, height = fontstring:GetFont();
	
	fontstring:SetFont(path, height, "OUTLINE");
	fontstring:SetShadowColor(0,0,0,0.3);
end


------------------------------------------------------------------------------
-- Increases the space allocated to the specified string to match the localised
-- string width.
--
-- [stringData] - a table containing a [width] element and a [text] element.
-- the latter should be a FontString whose text has been set.
------------------------------------------------------------------------------
function KLHTM_LocaliseStringWidth(stringData)
	
	local width = math.ceil(stringData.text:GetStringWidth());
	
	if (width > stringData.width) then
		
		local newValue = math.min(width, stringData.width * Max_Localisation_Factor);
		
		if mod.out.checktrace("info", me, "resizing") then
			mod.out.printtrace(string.format("Extending the width of %s from %s to %s.", stringData.frame:GetName(), stringData.width, newValue))
		end
		
		stringData.width = newValue;
	end
end

------------------------------------------------------------------------------
-- Repositions the frame in the center of the screen and shows it.
------------------------------------------------------------------------------
function KLHTM_ResetFrame()
	
	local scale = options.scale;
	
	gui.frame:SetScale(1);
	gui.frame:ClearAllPoints();
	gui.frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0);
	
	KLHTM_SetGuiScale(scale);
	KLHTM_SetVisible(true);
end

------------------------------------------------------------------------------
-- Updates the visibility of the data tables, and recalculates the frame bounds.
-- Should be called whenever the sizes of the subframes changes. ie:
--
-- a) view changes
-- b) minmax changes
-- c) column visibility changes
-- d) command button visibility changes
--
-- This method uses the widths calculated by the other methods such as 
-- Update...Table(), UpdateTitleButtons() and UpdateTitleStrings() which
-- should be called prior to this method when the affected gui components are 
-- changed.
--
-- When the frame is maximised, its width is determined by the visible data frame.
-- If it is smaller than the title bar's preferred width, then overlap can
-- occur between the title bar strings and buttons. In this case the default
-- string is replaced by the short version.
------------------------------------------------------------------------------
function KLHTM_UpdateFrame()
	
	-- raid frame
	if (state.max and state.raid) then
		sizes.frame.x = sizes.raid.x;
		gui.raid.frame:Show();
		-- height is set by the redraw method
	else
		gui.raid.frame:Hide();
	end
	
	-- self frame
	if (state.max and state.self) then
		sizes.frame.x = sizes.self.x;
		gui.self.frame:Show();
		-- height is set by the redraw method
	else
		gui.self.frame:Hide();
	end
	
	-- title frame
	sizes.title.x = sizes.string.x + sizes.but.x;
	sizes.title.y = math.max(sizes.string.y, sizes.but.y);
	gui.title.frame:SetHeight(sizes.title.y);
	
	if (state.min) then
		sizes.frame.y = sizes.title.y;
		sizes.frame.x = sizes.title.x;
		gui.frame:SetHeight(sizes.frame.y + 10); -- 10?
	end
	
	-- maintain the top-right corner when resizing
	local right = gui.frame:GetRight();
	local top = gui.frame:GetTop();
	
	gui.frame:SetWidth(sizes.frame.x + 12); -- 12: 5 inset + 1 gap * 2 for each side.
	
	if ((top ~= nil) and (right ~= nil)) then
		gui.frame:ClearAllPoints();
		gui.frame:SetPoint("TOPRIGHT", UIParent, "BOTTOMLEFT", right, top);
	else
		-- harmless, occurs once at startup
	end
	
	-- check for lack of horizontal space in the title bar
	if (state.max) then
		if (sizes.title.x > sizes.frame.x) then
			gui.title.string.short.frame:SetWidth(gui.title.string.short.width);
			gui.title.string.short.frame:Show();
			gui.title.string.long.frame:SetWidth(0.1);
			gui.title.string.long.frame:Hide();
		else
			gui.title.string.short.frame:SetWidth(0.1);
			gui.title.string.short.frame:Hide();
			gui.title.string.long.frame:SetWidth(gui.title.string.long.width);
			gui.title.string.long.frame:Show();
		end
		-- sometimes the anchors aren't reapplied. Not sure if this is needed now
		-- that the raid and self titles have been replaced
		gui.title.string.short.frame:GetLeft();
		gui.title.string.long.frame:GetLeft();
	end
	
end


------------------------------------------------------------------------------
-- Notifies the GUI that the frame should be updated. The frame will be redrawn
-- within Min_Redraw_Period seconds. Should be called by data-receiving modules,
-- eg networking and combat. 
--
-- [view] - set to "raid" or "self" to prevent redraw if it does not match
-- 			[state.view]. Ignored if nil.
------------------------------------------------------------------------------
function KLHTM_RequestRedraw(view)
	
	if ((view ~= nil) and (view ~= state.view)) then
		return;
	end
	if (state.closed) then
		needsRedraw = false;
		return;
	end
	
	needsRedraw = true;
end


KLHTM_RedrawDebug = {0,0,0,0,0,0,0,0,0,0};
_redrawindex = 1;
------------------------------------------------------------------------------
-- Redraws the data table contents, if needed. The redraw will only occur if:
--
-- a) [needsRedraw] is true. This is set by data-receiving modules via
-- KLHTM_RequestRedraw()
-- b) The last update was at least Min_Redraw_Period seconds ago
-- c) the frame is visible and initialised
--
-- This method is automatically called via KLHTM_GuiOnUpdate in order to track
-- processor usage. It should not be called externally; use KLHTM_RequestRedraw()
-- instead.
--
-- [forceRedraw] - if true, bypasses conditions (a) and (b) above. Use this 
-- before updating GUI element visibility to prevent flickering.
------------------------------------------------------------------------------
function KLHTM_Redraw(forceRedraw)
	
	if (not isInitialised) then
		return;
	end
	
	if (forceRedraw ~= true) then
		if ((GetTime() - lastRedraw) < Min_Redraw_Period) then
			return;
		end
		if (needsRedraw == false) then
			return;
		end
	end
	
	-- some debugging to check frequency of redraws
	KLHTM_RedrawDebug[_redrawindex] = GetTime() - lastRedraw;
	_redrawindex = _redrawindex + 1;
	if (_redrawindex == 11) then
		_redrawindex = 1;
	end
	
	lastRedraw = GetTime();
	needsRedraw = false;
	
	-- draw stuff... ? NO!
	if (state.raid) then
		KLHTM_DrawRaidFrame();
	else
		KLHTM_DrawSelfFrame();
	end
end

------------------------------------------------------------------------------
-- Abbreviates large number with the "k" suffix. Works on positive and negative
-- numbers. Non-numbers are returned as is.
------------------------------------------------------------------------------
function KLHTM_Abbreviate(input)
	
	if (type(input) ~= "number") then
		return input;
	end
	
	local isNegative = false;
	if (input < 0) then
		isNegative = true;
		input = -1 * input;
	end
	
	local answer;
	if (input < 10000) then
		answer = input;
	elseif (input < 100000) then
		answer = math.floor(input / 100 + 0.5) / 10;
		if (math.mod(answer, 1) == 0) then
			answer = answer .. ".0";
		end
		answer = answer .. "k";
	else
		answer = math.floor(input / 1000 + 0.5) .. "k";
	end
	
	if (isNegative) then
		answer = "-" .. answer;
	end
	return answer;
end


------------------------------------------------------------------------------
-- Frame dragging
------------------------------------------------------------------------------
function KLHTM_Frame_OnDragStart()
	if (gui.frame:IsMovable() and (state.pinned == false)) then 
		gui.frame:StartMoving();
	end
end
function KLHTM_Frame_OnDragStop()
	gui.frame:StopMovingOrSizing();
end
