--[[
	DPSPLUS - Personal, Party, and Raid DPS Tracking
	TODO:	Make Party DPS spots part of DPSPLUS.xml instead of PartyFrame.xml to keep shit seperate.
			Make it work w/o cosmos.
			Factor in Damage shield.
			DPS Tanked and Heals per second tracking.
			Raid Processing.
			Checking Config Options should enable the correct filters automatically.
			
	NOTE:	I'm new to Lua and it makes me crazy,  I'm sure this stuff can be optimized quite a bit, so feel free to make changes.
	AUTHOR:	Sudo/Suds [Conquest] PvP Server
	
	-- VERSION 1.0 --
	* Changed to OnEvent Handler for better screen display
	* Optimized most of the code
	* Made it much more user friendly/configurable than the previous version THAT WAS SUPPOSE TO BE A INTERNAL GUILD TEST VERSION, NOT IN COSMOS!  ;)
	* Itzlegend total damage tracking can be accessed with /dpsreport.  Totals can be cleared with /dpsreport clear
	* Pet damage now added to owner damage for player display only.  I cannot add pet to owner for party or raid.
	* Announce DPS on mob death in for debugging purposes.
	
	-- VERSION .9 --
	* Internal Guild Test that somehow got into Cosmos
  ]]

-- Config Values
DPSPLUS_Player_OnOff = 0;
DPSPLUS_Party_OnOff = 0;
DPSPLUS_Raid_OnOff = 0;
DPSPLUS_Player_Eat = 0;
DPSPLUS_Party_Eat = 0;
DPSPLUS_Raid_Eat = 0;

DPSPLUS_Announce = 0;
DPSPLUS_UpdateFreq = 0;  -- Update the on screen DPS every x seconds
DPSPLUS_Window = 0;    -- Calc DPS over the last x seconds



-- Globals
DPSPLUS_LastUpdate = 0;
timestamps = {};
players = {};
dmg = {};
dpstotals = {};
itztotals = {};
itzgrandtotal = 0;
itztime = 0;

-- for announce debug
dpsplus_calcdps = 0;


-- Register w/ Cosmos Config and set up a chat watch
function DPSPLUS_RegisterCosmos()
	Cosmos_RegisterConfiguration(
			"COS_DPSPLUS",
			"SECTION",
			DPSPLUS_CONFIG_HEADER,
			DPSPLUS_CONFIG_HEADER_INFO
			);
	Cosmos_RegisterConfiguration(
			"COS_DPSPLUS_HEADER",
			"SEPARATOR",
			DPSPLUS_CONFIG_HEADER,
			DPSPLUS_CONFIG_HEADER_INFO
			);
	Cosmos_RegisterConfiguration(
			"COS_DPSPLUS_PLAYER_ONOFF", 
			"CHECKBOX",
			DPSPLUS_CONFIG_PLAYER_ONOFF,
			DPSPLUS_CONFIG_PLAYER_ONOFF_INFO,
			DPSPLUS_Watch_Player_OnOff,
			0
			);
	Cosmos_RegisterConfiguration(
			"COS_DPSPLUS_PLAYER_EAT", 
			"CHECKBOX",
			DPSPLUS_CONFIG_PLAYER_EAT,
			DPSPLUS_CONFIG_PLAYER_EAT_INFO,
			DPSPLUS_Watch_Player_Eat,
			1
			);
	Cosmos_RegisterConfiguration(
			"COS_DPSPLUS_PARTY_ONOFF", 
			"CHECKBOX",
			DPSPLUS_CONFIG_PARTY_ONOFF,
			DPSPLUS_CONFIG_PARTY_ONOFF_INFO,
			DPSPLUS_Watch_Party_OnOff,
			0
			);
	Cosmos_RegisterConfiguration(
			"COS_DPSPLUS_PARTY_EAT", 
			"CHECKBOX",
			DPSPLUS_CONFIG_PARTY_EAT,
			DPSPLUS_CONFIG_PARTY_EAT_INFO,
			DPSPLUS_Watch_Party_Eat,
			1
			);
	Cosmos_RegisterConfiguration(
			"COS_DPSPLUS_RAID_ONOFF", 
			"CHECKBOX",
			DPSPLUS_CONFIG_RAID_ONOFF,
			DPSPLUS_CONFIG_RAID_ONOFF_INFO,
			DPSPLUS_Watch_Raid_OnOff,
			0
			);
	Cosmos_RegisterConfiguration(
			"COS_DPSPLUS_RAID_EAT", 
			"CHECKBOX",
			DPSPLUS_CONFIG_RAID_EAT,
			DPSPLUS_CONFIG_RAID_EAT_INFO,
			DPSPLUS_Watch_Raid_Eat,
			1
			);
	Cosmos_RegisterConfiguration(
			"COS_DPSPLUS_ANNOUNCE", 
			"CHECKBOX",
			DPSPLUS_CONFIG_ANNOUNCE,
			DPSPLUS_CONFIG_ANNOUNCE_INFO,
			DPSPLUS_Watch_Announce,
			0
			);
	Cosmos_RegisterConfiguration(
			"COS_DPSPLUS_UPDATEFREQ",
			"SLIDER",
			DPSPLUS_CONFIG_UPDATEFREQ,
			DPSPLUS_CONFIG_UPDATEFREQ_INFO,
			DPSPLUS_Watch_UpdateFreq,
			0,
			2, -- default
			1,
			10,
			"",
			1,
			1,
			" seconds",
			1
			);
	Cosmos_RegisterConfiguration(
			"COS_DPSPLUS_CALCWINDOW",
			"SLIDER",
			DPSPLUS_CONFIG_CALCWINDOW,
			DPSPLUS_CONFIG_CALCWINDOW_INFO,
			DPSPLUS_Watch_CalcWindow,
			0,
			25, -- default
			10,
			60,
			"",
			5,
			1,
			" seconds",
			1
			);

	Cosmos_RegisterChatCommand (
			"DPSPLUS_COMMANDS", -- Some Unique Group ID
			{"/dpsreport"}, -- The Commands
			DPSPLUS_ChatCommandHandler,
			DPSPLUS_CHAT_COMMAND_INFO 
			);


-- SPELL_DAMAGESHIELDS_ON_SELF
	Cosmos_RegisterChatWatch(
			"DPSPLUS_WATCHES",
			{"COMBAT_SELF_HITS", "SPELL_SELF_DAMAGE",
			 "COMBAT_PET_HITS", "SPELL_PET_DAMAGE",
			 "SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE", "SPELL_PERIODIC_CREATURE_DAMAGE",
			 "COMBAT_PARTY_HITS", "SPELL_PARTY_DAMAGE"},
			DPSPLUS_Watch
			);			

	Cosmos_RegisterChatWatch(
			"DPSPLUS_ANNOUNCE_WATCHES",
			{"COMBAT_HOSTILE_DEATH"},
			DPSPLUS_Announce_Watch
			);			
			
end



-- Called from XML
function DPSPLUS_OnLoad()
	if ( Cosmos_RegisterConfiguration ) then 
		DPSPLUS_RegisterCosmos();
	else
		-- Standalone (chatwatch)
	end
	
	itztime = GetTime();
end


function dpsplus_addentry(p, d)
--	print2("Adding Entry[" .. p .. "] = " .. d);	-- debug
	table.insert(timestamps, GetTime());
	table.insert(players, p);
	table.insert(dmg, d);

	-- Keep running totals on group members to shorten calculation times
	-- We'll subtract off the expired entries during OnUpdate				
	if (dpstotals[p] == nil) then
		dpstotals[p] = d;
	else
		dpstotals[p] = dpstotals[p] + d;
	end

	if (itztotals[p] == nil) then
		itztotals[p] = d;
	else
		itztotals[p] = itztotals[p] + d;
	end	
	
	itzgrandtotal = itzgrandtotal + d;
end


-- DPSPLUS Chat Event Watchers
--  
function DPSPLUS_Announce_Watch(type, info, arg1, arg2, arg3, arg4)
	if (DPSPLUS_Announce == 1) then
		-- say or party your current dps
		SendChatMessage("Current DPS: " .. dpsplus_calcdps,"SAY",DEFAULT_CHAT_FRAME.language,"");
	end
end

-- returns false to eat messages
function DPSPLUS_Watch(type, info, arg1, arg2, arg3, arg4)
	local p, d;

	-- Player Processing
	if (DPSPLUS_Player_OnOff == 1) then
		if ( (type == "COMBAT_SELF_HITS") or (type == "SPELL_SELF_DAMAGE") or (type == "COMBAT_PET_HITS") or (type == "SPELL_PET_DAMAGE") ) then 
			_, _, d = string.find(arg1, ".* for (%d+).*");
		elseif ( (type == "SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE") or (type == "SPELL_PERIODIC_CREATURE_DAMAGE") ) then 
			_, _, d = string.find(arg1, ".* (%d+) .* from your .*");
		end
		
		-- Matched, Record this Entry
		if ( d ~= nil ) then
			dpsplus_addentry("Your", d);
			return DPSPLUS_Player_Eat;
		end
	end

	-- Party Processing
	if (DPSPLUS_Party_OnOff == 1) then
		if ( (type == "COMBAT_PARTY_HITS") or (type == "SPELL_PARTY_DAMAGE") ) then 
			_, _, p, d = string.find(arg1, "(%w+).*it.* for (%d+).*");
		elseif ( (type == "SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE") or (type == "SPELL_PERIODIC_CREATURE_DAMAGE") ) then 
			-- Because of the stupid way blizz made the periodic filters, this will find ANYONE (friendly / enemy / mob) that is hurting a mob or enemy
			_, _, d, p = string.find(arg1, ".* (%d+) .* from (%w+).*");
		end

		-- Matched, Record this Entry
		if ( (p ~= nil) and (d ~= nil) ) then
			-- p may be a pet name or other player w/ periodic, see msg above
			dpsplus_addentry(p, d);
			return DPSPLUS_Party_Eat;
		end
	end

	-- Raid Processing
	--[[ Currently Off
	if (DPSPLUS_Raid_OnOff == 1) then
		if ( (type == "COMBAT_PARTY_HITS") or (type == "SPELL_PARTY_DAMAGE") ) then 
			_, _, p, d = string.find(arg1, "(%w+).*it.* for (%d+).*");
		elseif ( (type == "SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE") or (type == "SPELL_PERIODIC_CREATURE_DAMAGE") ) then 
			_, _, d, p = string.find(arg1, ".* (%d+) .* from (%w+).*");
		end

		-- Matched, Record this Entry
		if ( (p ~= nil) and (d ~= nil) ) then
			dpsplus_addentry(p, d);
			return DPSPLUS_Party_Eat;
		end
	end
	]]

end

-- Determines whether the DPS window should be visible or not
function DPSPLUS_DecideWindowVisibility()
	if ( ( DPSPLUS_Player_OnOff == 1 ) or ( DPSPLUS_Party_OnOff == 1) or ( DPSPLUS_Raid_OnOff == 1 ) )  then 
		--ShowUIPanel(DPSPLUS_PlayerFrame);
		if ( not DPSPLUS_PlayerFrame:IsVisible() ) then
			DPSPLUS_PlayerFrame:Show();
		end
	else
		--HideUIPanel(DPSPLUS_PlayerFrame);
		if ( DPSPLUS_PlayerFrame:IsVisible() ) then
			DPSPLUS_PlayerFrame:Hide();
		end
	end
end

--
--	Cosmos Config Handlers
--
function DPSPLUS_Watch_Player_OnOff(toggle) 
	DPSPLUS_Player_OnOff = toggle;

	DPSPLUS_DecideWindowVisibility();
end

function DPSPLUS_Watch_Player_Eat(toggle) 
	DPSPLUS_Player_Eat = toggle;
end

function DPSPLUS_Watch_Party_OnOff(toggle) 
	DPSPLUS_Party_OnOff = toggle;

	DPSPLUS_DecideWindowVisibility();
end

function DPSPLUS_Watch_Party_Eat(toggle) 
	DPSPLUS_Party_Eat = toggle;
end

function DPSPLUS_Watch_Raid_OnOff(toggle) 
	DPSPLUS_Raid_OnOff = toggle;

	DPSPLUS_DecideWindowVisibility();
end

function DPSPLUS_Watch_Raid_Eat(toggle) 
	DPSPLUS_Raid_Eat = toggle;
end

function DPSPLUS_Watch_Announce(toggle) 
	DPSPLUS_Announce = toggle;
end

function DPSPLUS_Watch_UpdateFreq(checked, value) 
	DPSPLUS_UpdateFreq = value;
end

function DPSPLUS_Watch_CalcWindow(checked, value) 
	DPSPLUS_Window = value;
end


--
-- Command Handler
--
function DPSPLUS_ChatCommandHandler(msg)
	if ( string.lower(msg) == "clear" ) then
		print2("** Itzlegend Totals Cleared **");
		itztotals = {};
		itzgrandtotal = 0;
		itztime = GetTime();
	else
		print2("** Itzlegend Total DPS Report **");
		local secs = math.floor(GetTime() - itztime);
		print2("** " .. secs .. " seconds **");
		for player,dmg in pairs(itztotals) do
			print2(player .. ": " .. dmg .. " ( ".. math.ceil((dmg / itzgrandtotal) * 100) .. "% ) - " .. math.ceil(dmg / secs) .. " dps");
		end
		print2("************************");
		print2("Grand Total: |c00880022" .. itzgrandtotal .. "|r");
	end
end




--
-- Update Handler
--
function DPSPLUS_OnUpdate()
	local curtime = GetTime();
	local oldesttime = 0;
	local deleteindex = 0;

	-- Check to see if its time to update the on screen DPS and only update if there are dps entries to save CPU
	if ( ((curtime - DPSPLUS_LastUpdate) > DPSPLUS_UpdateFreq) and (table.getn(timestamps) > 0) ) then
	
		-- TODO: Change this to a high to low loop so we can do our subtracts and deletes in the same run
		
		for k,v in pairs(timestamps) do
		
			-- Get rid of old dps entries and adjust the totals				
			if ( (curtime - DPSPLUS_Window) > v ) then

				-- Subtract this entry from the totals
				dpstotals[players[k]] = dpstotals[players[k]] - dmg[k];
				
				-- Mark for later removal *We can't do the remove here cause table.remove reindexes the list and bones our loop
				deleteindex = k;
			else
				-- were into the good stuff now, stop
				oldesttime = v;
				break;
			end
		end

		-- Remove entries from deleteindex down so we don't miss em
		while (deleteindex > 0) do
			table.remove(timestamps, deleteindex);
			table.remove(players, deleteindex);
			table.remove(dmg, deleteindex);
			deleteindex = deleteindex -1;
		end
		
		-- NOTE: Everyone calcs off the same oldest time, not their own oldest time.
		--       This should give more accurate dps on a per group fight basis.
		
		-- Update player DPS
		if ( dpstotals["Your"] ~= nil ) then
			dpsplus_calcdps = math.ceil( (dpstotals["Your"] / (curtime - oldesttime +1)) );

			if ( dpsplus_calcdps > 0 ) then
				SelfDPS:SetText("DPS: " .. dpsplus_calcdps );  -- +1 for infinity fix
			else
				SelfDPS:SetText("");
			end
			
			-- DPS Tanked crap here
--					SelfDPSTanked:SetText("DTPS: NYI");
		end


		-- Update party DPS
		for i = 1, MAX_PARTY_MEMBERS, 1 do
			local unit = UnitName("party"..i);
			if (unit ~= nil) then
				if ( (dpstotals[unit] == nil) or (dpstotals[unit] == 0) ) then
					getglobal("PartyMemberFrame"..i.."DPS"):SetText("");
				else
					getglobal("PartyMemberFrame"..i.."DPS"):SetText(math.ceil((dpstotals[unit] / (curtime - oldesttime +1))) ); -- infinity fix
				end
			end
		end

--[[ Old Party Loop
		for player,dmg in pairs(dpstotals) do
			for i = 1, MAX_PARTY_MEMBERS, 1 do
				if (UnitName("party"..i) == player) then
					getglobal("PartyMemberFrame"..i.."DPS"):SetText(math.ceil((dmg / (curtime - oldesttime +1))) ); -- infinity fix
				end
			end
		end
  ]]
		
		-- Set the last update time
		DPSPLUS_LastUpdate = curtime;
	end
end
