
-- these constants are from the Templates.xml definitions
KLHTM_TableMaxColumns = 10
KLHTM_TableMaxRows = 15
KLHTM_SavedRaidRowsVisible = 10 -- this is a saved variable


-- This sets the maximum update frequency (5 Hz)
KLHTM_TableUpdateInterval = 0.2 

----------------------------------
--		GUI Table Drawing		--
----------------------------------

-- ggf = get global formatted
local function ggf(String, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
	return getglobal(string.format(String, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9))
end

function KLHTM_NewWindowLayoutData(Name, Functions)
	
	return 
	{
		["Name"] = Name,		-- to use in getglobal
		["IsShown"] = true,		-- whether any part of the form is visible
		["IsMinimised"] = true,	-- whether only the label and buttons are shown
		["IsPinned"] = false,		-- whether the window is stuck in place
		["Label"] = "",			-- the label, when the window is not minimised
		["ShortLabel"] = "",	-- the label, when the window is minimised
		
		["Table"] = 
		{	
			["Name"] = Name .. "Table",	-- to use in getglobal
			["Rows"] = 0,				-- the number of rows that are actually rendered
			["Columns"] = 0,			-- Rows and Columns are updated by the table drawing method
			["Functions"] = Functions,	-- An array of functions that compute the table values
			["LastRenderTime"] = 0,		-- When the table was last drawn
			["RedrawPending"] = false,	-- Whether RedrawTable has been called, but the table has not been drawn
										-- Pitch = size of the fonts
			["HeaderPitch"] = KLHTM_GlobalScale * KLHTM_TableHeaderScale,
			["TextPitch"] = KLHTM_GlobalScale * KLHTM_TableScale,
			["DigitWidth"] = 0,			-- Width of the longest digit. Used to set up the minimum column widths.
			["DataSource"] = nil,		-- Data that is sent to Functions to create the table values
			["SortBy"] = KLHTM_String("gui", "column", "threat"), -- Will find the column named threat to sort it
			["SortDirection"] = 1,		-- Biggest on the bottom
			["Render"] = KLHTM_GetNewTableData(),	-- What the user will see, in a 2D array. {row, row, row}.
			["IgnoreEmptyRows"] = true,	-- Don't draw rows of 0's.
			["RowsVisible"] = KLHTM_TableMaxRows,
			
			-- lukon mod: bar data
			["BarWidths"] = {},			-- The width, between 0 and 100, of each row's bar. Index is the row number, from 1.
			["BarColours"] = {},		-- The colours of each row's bar. eg {1.0, 1.0, 1.0}. Index is the row number, from 1.
		},
	}

end

function KLHTM_GetNewTableData()

	-- inner arrays are for each column
	local out = { }
	local row
	local x
	local y
	
	for x = 1, KLHTM_TableMaxRows do
		row = { }
		
		for y = 1, KLHTM_TableMaxColumns do
			table.insert(row, "nil")
		end
		
		table.insert(out, row)
	end

	return out
end

-- RedrawTable is here to regulate table drawing. Type of <Table> is as from NewTableLayoutData().
function KLHTM_RedrawTable(Window)
	
	if GetTime() < Window.Table.LastRenderTime + KLHTM_TableUpdateInterval then
		Window.Table.RedrawPending = true
		
		KLHTM_PrintTrace("TableRedrawDenial", "Denied a Redraw Request for " .. Window.Name .. ".")
		return false -- don't update
	end
	
	Window.Table.LastRenderTime = GetTime()
	Window.Table.RedrawPending = false
	
	KLHTM_DrawTable(Window)
	return true
end

-- return: non-nil for success
function KLHTM_RenderTable(Table)
	
	-- Check if the table is right to be drawn to
	if Table.Functions == nil then
		KLHTM_PrintTrace("TableRenderFail", string.format("Not drawing %s, because no column functions are defined.", Table.Name))
		return
		
	elseif Table.DataSource == nil then
		KLHTM_PrintTrace("TableRenderFail", string.format("Not drawing %s, because no data source is defined.", Table.Name))
		return
	end
	
	-- Determine how many columns and rows there actually are.
	-- Note that Table.Functions is an array, Table.DataSource is a hashtable.
	Table.Columns = math.min(table.getn(Table.Functions), KLHTM_TableMaxColumns)
	Table.Rows = math.min(KLHTM_GetHashTableLength(Table.DataSource), KLHTM_TableMaxRows, Table.RowsVisible)
	
	-- get sorted row data
	local sortedkeylist
	sortedkeylist, Table.Rows = KLHTM_GetSortedRows(Table)
	
	-- Now write all the values in
	local column;	local row
	local key; 		local value
	
	for column = 1, Table.Columns do
		
		row = 0
		
		-- sortedkeylist = { {"sunder", 10}, {"hs", 20}, {"total", 30} }, for example
		for key, value in sortedkeylist do
			row = row + 1
			
			Table.Render[row][column] = Table.Functions[column].myfunction(value[1], Table.DataSource)
		end
	end	
	
	-- lukon mod: fill in bar data
	local row = 0;
	
	for key, value in sortedkeylist do
		row = row + 1;
		Table.BarWidths[row] = Table.Functions["BarWidths"](value[1], Table.DataSource);
		Table.BarColours[row] = Table.Functions["BarColours"](value[1], Table.DataSource);
	end
	
	-- eliminate rows that are empty (except for "Total")
	if Table.IgnoreEmptyRows == true then

		row = 0
		local isempty;	local i
	
		while true do
			row = row + 1
			if row > Table.Rows then
				break
			end
			
			-- always include the total, even if it is empty
			if Table.Render[row][1] ~= "Total" then
				
				isempty = true
				for column = 2, Table.Columns do
					if Table.Render[row][column] ~= 0 then
						isempty = false
						break
					end
				end
				
				if isempty then
					-- delete this row
					Table.Rows = Table.Rows - 1
					
					-- push all future rows down 1
					for i = row, Table.Rows do
						for column = 1, Table.Columns do
							Table.Render[i][column] = Table.Render[i + 1][column]
						end
						-- lukon mod: reshuffle the bar data down one index
						Table.BarWidths[i] = Table.BarWidths[i + 1];
						Table.BarColours[i] = Table.BarColours[i + 1];
					end
						
					-- set the last row to nil
					for column = 1, Table.Columns do
						Table.Render[Table.Rows + 1][column] = "nil"
					end
					
					-- the next row to be scanned is <row>, not <row + 1>, so we have to prevent the iteration
					row = row - 1
				end
			end
		end
	end
	
	-- set Total to the bottom (force)
	if Table == KLHTM_MainWindow.Table then
		for row = 1, Table.Rows - 1 do
			if Table.Render[row][1] == KLHTM_String("threatsource", "total") then
				-- push the row down
				KLHTM_SwapRows(Table, row, row + 1)
			end
		end
	end
	
	-- done (successfully)
	return true
end

--[[
Here's the lowdown. We have a Table, which includes a data set, which is a set of Key-Value pairs. We can only display
a certain number of values in the table - there's a maximum of, say, 10. We might have more than 10 pairs in the data
set, however. So we want to pick the top 10, the ones with the highest value according to the sort function defined by
the table. So first we make a list of all the keys, and their sort value. Then we sort that list, and return the top
10, or 15, or however many we get. And that's what we return, the completely sorted key list.
]]
function KLHTM_GetSortedRows(Table)
	
	-- 1) Identify the sort function
	local sortfunction
	
	local key
	local value
	
	for key, value in Table.Functions do
		if value.label == Table.SortBy then
			sortfunction = value.myfunction
			break
		end
	end
	
	-- put all the keys with their values in a list
	local rankedkeylist = { }
	local listlength = 0
	
	for key, value in Table.DataSource do
		listlength = listlength + 1
		table.insert(rankedkeylist, { key, sortfunction(key, Table.DataSource) })
	end
	
	-- sort the list (insertion sort). Direction = 1 --> Big down the bottom
	local x
	local thisvalue
	
	for x = 2, listlength do
		-- take the element in position x, and sift him up as far as needed, as high as 1.
		thisvalue = { rankedkeylist[x][1], rankedkeylist[x][2] }
		
		for y = 1, x - 1 do
			if Table.SortDirection * (math.abs(thisvalue[2]) - math.abs(rankedkeylist[x - y][2])) < 0 then
				
				-- sift him up
				rankedkeylist[x - y + 1][1] = rankedkeylist[x - y][1]
				rankedkeylist[x - y + 1][2] = rankedkeylist[x - y][2]
				
				-- check for end condition
				if y == x - 1 then
					rankedkeylist[1][1] = thisvalue[1]
					rankedkeylist[1][2] = thisvalue[2]
				end
			
			else
				-- stop sifting. place him where he is
				rankedkeylist[x - y + 1][1] = thisvalue[1]
				rankedkeylist[x - y + 1][2] = thisvalue[2]
				break
			end
		end
	end
	
	-- the list is now sorted. Now trim the list to the maximum size, if necessary
	local maxrowstodraw = math.min(Table.RowsVisible, KLHTM_TableMaxRows)
	local itemstotrim = listlength - maxrowstodraw
	
	if Table.SortDirection > 0 then
		-- small ones at the start, so trim them
		for x = 1, itemstotrim do
			table.remove(rankedkeylist, 1)
		end
		
	else
		-- small ones at the bottom, trim them
		for x = 1, itemstotrim do
			table.remove(rankedkeylist, listlength - x + 1)
		end
	end
	
	listlength = math.min(listlength, maxrowstodraw)
	
	return rankedkeylist, listlength
	
end

function KLHTM_SwapRows(Table, Row1, Row2)
	
	local temp
	local column
	
	for column = 1, Table.Columns do
		temp = Table.Render[Row1][column]
		Table.Render[Row1][column] = Table.Render[Row2][column]
		Table.Render[Row2][column] = temp
	end
end


function KLHTM_DrawTable(Window)

	-- Check the table is visible
	if (Window.IsMinimised == true) or (Window.IsShown == false) then
		-- no need for a printout, since this is a standard condition.
		return
	end

	-- preliminary: update aggro gain if this is the raid window
	if Window == KLHTM_RaidWindow then
		KLHTM_RecalculateAggroGain()
	end

	-- Render the Table
	if KLHTM_RenderTable(Window.Table) == nil then
		-- render failed, take no action.
		return
	end
	
	-- Render worked, now draw it all
	local column;	local fontstring;	local columnwidth
	local Table = Window.Table;		local datatype
	
	-- Draw the visible columns
	for column = 1, Table.Columns do
		y = 0
		
		-- 1) update the label for this column.
		fontstring = ggf("%sRowLabelText%d", Table.Name, column)
		fontstring:SetText(Table.Functions[column].label)
		KLHTM_SizeFontStringToText(fontstring, KLHTM_GlobalScale * KLHTM_TableHeaderScale)
		
		-- 2) work out the width for this column
		columnwidth = fontstring:GetWidth()
		columnwidth = math.max(columnwidth, Table.DigitWidth * Table.Functions[column].minwidth)
		
		for row = 1, Table.Rows do
			
			datatype = type(Table.Render[row][column])
			if datatype == "string" then
				columnwidth = math.max(columnwidth, KLHTM_GetRealStringWidth(Table.Render[row][column], KLHTM_GlobalScale * KLHTM_TableScale))
			
			elseif datatype == "number" then
				-- +2 is a bit of a fudge factor. it was found that strings put 1 blank pixel on either end or something
				columnwidth = math.max(columnwidth, 2 + Table.DigitWidth * string.len(Table.Render[row][column]))
			
			else
				KLHRS_PrintTrace("TableRenderFail", "Unknown datatype " .. datatype .. " to render.")
			end
			
		end
		
		-- now set the width for the label
		fontstring:SetWidth(columnwidth)
		
		-- 3) draw the rest of the column
		for row = 1, Table.Rows do
			fontstring = ggf("%sTableRow%dText%d", Window.Name, row, column)
			fontstring:SetText(Table.Render[row][column])
			fontstring:Show()
			fontstring:SetWidth(columnwidth)
		end
	end
	
	-- lukon mod: apply the bar data
	for row = 1, Table.Rows do
		local bar = ggf("%sTableRow%dBar", Window.Name, row);
		bar:SetValue(Table.BarWidths[row]);
		local colours = Table.BarColours[row];
		bar:SetStatusBarColor(colours.r, colours.g, colours.b, colours.a);
		bar:Show();
	end
	
	-- Now hide the invisible columns
	
	-- 1) Cut off the bottom
	for row = Table.Rows + 1, KLHTM_TableMaxRows do
		for column = 1, Table.Columns do
			fontstring = ggf("%sTableRow%dText%d", Window.Name, row, column)
			fontstring:Hide()
		end
			
			-- lukon mod: hide unused bars
			bar = ggf("%sTableRow%dBar", Window.Name, row);
			bar:Hide();
	end
	
	-- 2) Cut off the right
	for column = Table.Columns + 1, KLHTM_TableMaxColumns do 
		fontstring = ggf("%sTableRowLabelText%d", Window.Name, column)
		fontstring:Hide()
		
		for row = 1, KLHTM_TableMaxRows do 
			fontstring = ggf("%sTableRow%dText%d", Window.Name, row, column)
			fontstring:Hide()
		end
	end

	-- update frame
	KLHTM_UpdateFrameBounds(Window)

end

function KLHTM_GetDigitLength(FontHeight)
	
	local text, x, y
	local longestdigit
	local longestlength = 0
	
	KLHTM_StringTesterText:SetTextHeight(FontHeight)
	KLHTM_StringTester:SetScale(1.01)
	KLHTM_StringTester:SetScale(1)
	
	for x = 0, 9 do
		text = ""
		
		for y = 1, 10 do
			text = text .. x
		end
		
		KLHTM_StringTesterText:SetText(text)
		
		if KLHTM_StringTesterText:GetStringWidth() > longestlength then
			longestdigit = x
			longestlength = KLHTM_StringTesterText:GetStringWidth()
		end
	end
	
	return longestlength / 10
		
end

-----------------------------------
--    Columns for Main Window    --
-----------------------------------


--[[ A few words on the .minwidth property. The values for any column could be changing fairly rapidly. Also, we want
to trim the column width so that it just fits each time. But then if one value is changing enough, it might cause the
columns to rapidly vary in width, which would be really annoying to the user. So we have a (generous) minimum digit
width for each column. The commented values are the smallest values that would be bigger than the minimum width. ]]
function KLHTM_GetSimpleColumns()
	return 	
	{	
		{
			["label"] = KLHTM_String("gui", "column", "name"),
			["minwidth"] = 5, -- not important, cause the names don't change
			["myfunction"] = KLHTM_Column_Name
		},
		{
			["label"] = KLHTM_String("gui", "column", "hits"),
			["minwidth"] = 3,	-- 1000
			["myfunction"] = KLHTM_Column_Hits
		},
		{
			["label"] = KLHTM_String("power", "rage"),
			["minwidth"] = 6,	-- 10,000
			["myfunction"] = KLHTM_Column_Rage
		},
		{
			["label"] = KLHTM_String("gui", "column", "damage"),
			["minwidth"] = 6,	-- 1 million
			["myfunction"] = KLHTM_Column_Damage
		},
		{
			["label"] = KLHTM_String("gui", "column", "threat"),
			["minwidth"] = 6,	-- 10 million
			["myfunction"] = KLHTM_Column_Threat
		},
		{
			["label"] = "%" .. KLHTM_String("gui", "column", "threat"),
			["minwidth"] = 3,	-- 1000
			["myfunction"] = KLHTM_Column_PercentThreat
		},
		-- lukon mod. Bars are disabled for the main window
		["BarWidths"] = function() return 0; end,
		["BarColours"] = function() return {["r"] = 1.0, ["g"] = 1.0, ["b"] = 1.0}; end,
	}
end

function KLHTM_Column_Name(Name, AllData)
	return Name
end

function KLHTM_Column_Hits(Name, AllData)
	return math.ceil(AllData[Name].hits)
end

function KLHTM_Column_Rage(Name, AllData)
	return math.ceil(AllData[Name].rage)
end

function KLHTM_Column_Damage(Name, AllData)
	return math.ceil(AllData[Name].damage)
end

function KLHTM_Column_Threat(Name, AllData)
	return math.ceil(AllData[Name].threat)
end

function KLHTM_Column_PercentThreat(Name, AllData)
	
	local total = AllData[KLHTM_String("threatsource", "total")].threat - AllData[KLHTM_String("threatsource", "threatwipe")].threat
	local mine = AllData[Name].threat
	
	if total == 0 then
		return 0
	else
		return math.floor(0.5 + (mine * 100 / total))
	end
end

-----------------------------------
--    Columns for Raid Window    --
-----------------------------------

function KLHTM_RaidColumn_Threat(Name, AllData)

	return math.ceil(AllData[Name])

end

function KLHTM_RaidColuumn_PercentMaxThreat(Name, AllData)
	
	-- 1) get max threat
	local maxthreat = 0
	local key;	local value
	
	for key, value in AllData do	
		if (key ~= KLHTM_String("misc", "aggrogain")) and (value > maxthreat) then
		 	maxthreat = value
		end
	end
	
	if maxthreat == 0 then
		return 0
	else
		return math.floor(0.5 + (AllData[Name] * 100 / maxthreat))
	end

end

-- lukon mod: gets the bar width for a row in the raid table
function KLHTM_GetRaidBarWidth(Name, AllData)
	
	local width = KLHTM_RaidColuumn_PercentMaxThreat(Name, AllData);
	
	if (width < 0) then
		width = 0;
	end
	
	if (width > 100) then
		width = 100;
	end
	
	return width;
end


function KLHTM_GetRaidColumns()
	
	return
	{	
		{
			["label"] = KLHTM_String("gui", "column", "name"),
			["minwidth"] = 10, -- not important, cause the names don't change
			["myfunction"] = KLHTM_Column_Name,
		},
		{
			["label"] = KLHTM_String("gui", "column", "threat"),
			["minwidth"] = 5,	-- 1000
			["myfunction"] = KLHTM_RaidColumn_Threat,
		},
		{
			["label"] = KLHTM_String("gui", "column", "%max"),
			["minwidth"] = 3,	
			["myfunction"] = KLHTM_RaidColuumn_PercentMaxThreat,
		},
		-- lukon mod. Functions for calculating bar properties.
		-- Since the keys are not the default array values, they wont be interpreted as columns.
		["BarWidths"] = KLHTM_GetRaidBarWidth,
		["BarColours"] = KLHTM_GetRaidBarColour,
	}
	
end

-- lukon mod: gets the colour of a bar in the raid table
-- Uses the default class colours, white for the player and gray for unknown.
function KLHTM_GetRaidBarColour(playerName)
	
	local className;
	
	-- apply pre-defined colours
	if (playerName == UnitName("player")) then
		return {["r"] = 1.0, ["g"] = 0, ["b"] = 0};
	end
	
	if (playerName == KLHTM_String("misc", "aggrogain")) then
		return {["r"] = 0, ["g"] = 0, ["b"] = 1.0};
	end
	
	className = KLHTM_PlayerClasses[playerName];
	
	if (className == nil) then
		-- the player's class isnt stored in our table, rebuild it.
		KLHTM_PrintTrace("RaidColours", "Updating class entry for " .. playerName);
		KLHTM_UpdateRaidClasses();
		className = KLHTM_PlayerClasses[playerName];
	end
	
	if (className == nil) then
		-- class name could not be found.
		-- probably some stupid row like "aggro gain"
		KLHTM_PrintTrace("RaidColours", "Adding dummy entry for " .. playerName);
		KLHTM_PlayerClasses[playerName] = "";
		className = "";
	end
	
	className = KLHTM_UnlocaliseClass(className)
	
	if (className == "") then
		return {["r"] = 0.5, ["g"] = 0.5, ["b"] = 0.5};
	else
		return RAID_CLASS_COLORS[className];
	end
end

-- class: a localised class name
-- returns: "WARRIOR", "DRUID", etc.
function KLHTM_UnlocaliseClass(class)

	local classes = {"warrior", "druid", "priest", "shaman", "mage", "warlock", "rogue", "hunter", "paladin"}
	local key
	
	for _, key in classes do
		if KLHTM_String("class", key) == class then
			return string.upper(key)
		end
	end
	
	return ""
end
