
local me = { name = "table"}
local mod = thismod
mod[me.name] = me

--[[
Tables.lua

Handles the threat list and threat per second data.

]]

--! This variable is referenced by these modules: alert, combat, guiself, 
me.mydata = { } -- personal threat table data
--! This variable is referenced by these modules: boss, combat, net, guiraid, 
me.raiddata = { } -- raid's threat table data
--! This variable is referenced by these modules: boss, my, 
me.raidthreatoffset = 0 -- difference between your total threat and your raid threat

--! This variable is referenced by these modules: guiraid, 
me.raidclasses = { } -- list of classes by player name. e.g. { ["Kenco"] = "Warrior", }
me.raidupdatetimes = { } -- list of last update times by player, e.g. { ["Kenco"] = GetTime(), }

------------------------------------
--    Special Core.lua Methods    --
------------------------------------

me.onload = function()
	
	-- initialise <me.mydata>. This refers to static methods from me.string, so goes in our onload().
	me.mydata[mod.string.get("threatsource", "total")] = me.newdatastruct()
	
end

me.myonupdates = 
{
	updatetps = 1.0,
	updateraidlist = 1.0,
}

--[[
---------------------------------------------------------------------------------------------
				Tables Module 
---------------------------------------------------------------------------------------------
]]

me.mynetmessages = { "clear", "t", }

me.onnetmessage = function(author, command, data)
	
	if command == "clear" then
		
		-- check the author has permission
		if mod.unit.isplayerofficer(author) == nil then
			return "permission"
		end
		
		mod.print(string.format(mod.string.get("print", "network", "threatreset"), author))
			
		me.resetraidthreat()
		me.clearraidtable()
		
	elseif command == "t" then
		
		local value = tonumber(data)
		
		if value == nil then
			return "invalid"
		end
				
		me.updateplayerthreat(author, value)
		mod.raidtable.updateview()
	end
	
end


--[[
-------------------------------------------------------------------------------------------------
	Maintaining the Raid Threat List: Reset non-responsive players, remove players not in group
-------------------------------------------------------------------------------------------------

	We have the key-value list <me.raiddata>, key = name of the player, value = threat of the player. 
	We have the list <me.raidupdatetimes>, key = name of the player, value = GetTime() of last update.

There are two maintenance functions:
1) If someone hasn't updated their threat for a long time, set it to 0. This happens for example when someone gets disconnected in pairs(the middle of a fight. After the fight ends you) don't want their threat sitting in the table forever.
2) If a player leaves the raid group / party, remove them from the threat list.

Now, if someone has left the raid group, then their threat will stop updating. Therefore we only need to check for case (2) when a case (1) has occurred.

We don't want me.onupdate running all the time, since the threat list maintenance doesn't need it and would be using excess processor time. Therefore it is run at most every <me.updateinterval> seconds. <me.lastonupdate> is the last time it was run.

<me.idlereset> is the number of seconds a player must be idle for before their threat is wiped. Make sure it is significantly more than <mod.net.idleupdateinterval>, which is how often players will update when their threat is not changing.
]]
me.idlereset = 10.0			-- seconds before we reset someone's threat

-- poll time = 1.0s
me.updateraidlist = function()
	
	local x
	local timenow = GetTime()
	
	for x, _ in pairs(me.raiddata) do
		
		-- if the player doesn't exist in <me.raidupdatetimes>, add them and do nothing.
		if me.raidupdatetimes[x] == nil then
			me.raidupdatetimes[x] = timenow
	
		-- check for player idle / not updating properly
		elseif (timenow > me.raidupdatetimes[x] + me.idlereset) and (x ~= mod.string.get("misc", "aggrogain")) then
			
			-- Player x hasn't updated for an unusually long time. It could be because they are no longer in the raid group - check:
			if  mod.unit.isplayeringroup(x) == nil then
			
				-- yes; this player is no longer in the group. Remove them from the system
				if mod.trace.check("info", me, "idleplayer") then
					mod.trace.print(string.format("Removing %s from the table, since he isn't in the raid group.", x))
				end
				
				me.raiddata[x] = nil
				me.raidupdatetimes[x] = nil
				me.tpsdata[x] = nil
				
				-- might need to update gui
				mod.raidtable.updateview()
			
			elseif me.raiddata[x] ~= 0 then
				-- they are still in the raid group, but they haven't updated for some time, probably becaue they were disconnected. Set their threat to 0. Also reset their UpdateTimes value.
			
				me.raiddata[x] = 0
				me.raidupdatetimes[x] = timenow
				
				if mod.trace.check("info", me, "idleplayer") then
					mod.trace.print(string.format("Resetting %s's threat, since he hasn't updated for a while.", x))
				end
				
				mod.raidtable.updateview()
			end
		end
	end

end


-- to save lots of writing!
--! This variable is referenced by these modules: combat, 
me.newdatastruct = function()
	
	return
	{
		["hits"] = 0,
		["damage"] = 0,
		["threat"] = 0,
	}
	
end


--------------------------------------------------------------------------------

--[[ 
mod.table.getraidthreat()
Returns the value you would post to the raid group as your threat.
]]
--! This variable is referenced by these modules: boss, combat, net, 
me.getraidthreat = function()

	return me.mydata[mod.string.get("threatsource", "total")].threat + me.raidthreatoffset

end

--[[ 
mod.table.resetraidthreat()
Set your threat for the rest of the raid group to 0. Used for a complete threat wipe.
]]
--! This variable is referenced by these modules: boss, combat, my, netin, 
me.resetraidthreat = function()
	
	me.raidthreatoffset = - me.mydata[mod.string.get("threatsource", "total")].threat
	
end


--[[ 
mod.table.redoraidclasses()
Checks the raid / party and finds the class of every player.
]]
--! This variable is referenced by these modules: guiraid, 
me.redoraidclasses = function()
	
	local numraiders = GetNumRaidMembers()
	local class
	
	if (numraiders == 0) then
		-- we're not in a raid. check party
		
		local numparty = GetNumPartyMembers()
		
		for x = 1, numparty do
			_, class = UnitClass("party" .. x)
			
			if class then
				me.raidclasses[UnitName("party" .. x)] = string.lower(class)
			end
		end
		
		-- also do yourself
		me.raidclasses[UnitName("player")] = mod.my.class
		
	else -- we're in a raid
		
		local class
		local name
		
		for x = 1, numraiders do
			_, class = UnitClass("raid" .. x)
			
			-- 1.12: found class could be nil sometimes, maybe just when someone joins the raid group. rare, but still.
			if class then
				me.raidclasses[UnitName("raid" .. x)] = string.lower(class)
			end
		end
	end
end

--[[
mod.table.resetmytable()
Clears all the data in your personal threat table.
]]
--! This variable is referenced by these modules: guiself, 
me.resetmytable = function()
	
	-- reset raid offset
	me.raidthreatoffset = me.getraidthreat()
	
	-- clear table
	local key
	local value
	local key2
	
	for key, value in pairs(me.mydata) do
		for key2 in pairs(value) do
			value[key2] = 0
		end
	end
	
end

--[[ 
mod.table.resetraidtable()
Clears all the data in the raid table.
]]
--! This variable is referenced by these modules: netin, 
me.clearraidtable = function()

	local key
	
	for key, _ in pairs(me.raiddata) do
		me.raiddata[key] = nil;
	end
	
	me.raiddata[UnitName("player")] = 0

end


--[[
----------------------------------------------------------------------------
					Calculating TPS (Threat Per Second) for a player
----------------------------------------------------------------------------

All the data we keep is in <me.tpsdata>. 
e.g. (most recent data is last in history list. .activehistory = count of highest "enabled = true" index.

me.tpsdata = 
{
	["Kenco"] = 
		["history"] = 
		{
			[1] = 
			{	
				time = 0.0,
				threat = 100,
				enabled = true,
			},
			[2] = 
			{	
				time = 2.0,
				threat = 500,
				enabled = false,
			},
		},
		value = 100,
		activehistory = 1,
}]]
me.tpsdata = { } 

me.maxlength = 20.0 	-- oldest event, in seconds ago
me.minlength = 3.0	-- minimum distance between earliest and latest events
me.reset = 4.0			-- go to 0 tps after this long with no report.

--[[ 
mod.table.updateplayerthreat(player, threat)
Updates a raid threat entry.
<player> is the name of the player whose threat is updated
<threat> is the new amount
TODO: add code to change aggrogain here, if player is master target???
]]
--! This variable is referenced by these modules: netin, 
me.updateplayerthreat = function(player, threat)
	
	-- dodgy pet hack
	if player == UnitName("player") and (mod.pet.petthreat > me.getraidthreat()) then
		threat = me.getraidthreat()
	end

	me.raiddata[player] = threat
	me.raidupdatetimes[player] = GetTime()
	
	-- check the player's history exists
	if me.tpsdata[player] == nil then
		me.tpsdata[player] = 
		{
			history = { },
			value = 0,
			activehistory = 0,
		}
	end
	
	local data = me.tpsdata[player]
	
	-- check there is room at the end for a new item
	if #data.history == data.activehistory then
		data.history[#data.history + 1] = 
		{
			time = 0,
			threat = 0,
			enabled = true,
		}
	end
	
	-- append
	data.activehistory = data.activehistory + 1
	data.history[data.activehistory].time = GetTime()
	data.history[data.activehistory].threat = threat
	data.history[data.activehistory].enabled = true	
	
	-- now recalculate their tps
	me.redotps(data)
	
end

-- data is e.g. <me.tpsdata.Kenco>
me.redotps = function(data)
	
	local timenow = GetTime()
	
	-- 1) purge old data
	for x = 1, #data.history do
		
		-- exit condition for end of data stream
		if data.history[x].enabled == false then
			break
		end
		
		-- data too old?
		if data.history[x].time + me.maxlength < timenow then
			
			-- decremenet active length
			data.activehistory = data.activehistory - 1
			
			-- shuffle all the data below up one
			for y = x, #data.history do
				
				-- check if the next one exists
				if data.history[y + 1] and (data.history[y + 1].enabled == true) then
					data.history[y].time = data.history[y + 1].time
					data.history[y].threat = data.history[y + 1].threat
				
				else
					data.history[y].enabled = false
				end
			end
		end
	end
	
	-- 2) Check whether the data has reset, if the most recent value is too old
	if (data.activehistory == 0) or (data.history[data.activehistory].time + me.reset < timenow) then
		data.value = 0
		
		-- optionally: remove all history
		for x = 1, data.activehistory do
			data.history[x].enabled = false
		end
		
		data.activehistory = 0
		
		return
	end
	
	-- 3) Check whether the total length is long enough.
	if data.history[data.activehistory].time - data.history[1].time < me.minlength then
		
		-- not enough data yet
		data.value = 0
		
	else
		-- decent value ready	
		data.value = (data.history[data.activehistory].threat - data.history[1].threat) / (data.history[data.activehistory].time - data.history[1].time)
		
	end
	
end

-- polled every 1s
me.updatetps = function()
	
	for player, data in pairs(me.tpsdata) do
		me.redotps(data)
	end
	
end