local Srendarr		= _G['Srendarr'] -- grab addon table from global
local L				= Srendarr:GetLocale()

-- CONSTS --
local ABILITY_TYPE_CHANGEAPPEARANCE	= ABILITY_TYPE_CHANGEAPPEARANCE
local ABILITY_TYPE_REGISTERTRIGGER	= ABILITY_TYPE_REGISTERTRIGGER
local BUFF_EFFECT_TYPE_DEBUFF		= BUFF_EFFECT_TYPE_DEBUFF

local GROUP_PLAYER_SHORT			= Srendarr.GROUP_PLAYER_SHORT
local GROUP_PLAYER_LONG				= Srendarr.GROUP_PLAYER_LONG
local GROUP_PLAYER_TOGGLED			= Srendarr.GROUP_PLAYER_TOGGLED
local GROUP_PLAYER_PASSIVE			= Srendarr.GROUP_PLAYER_PASSIVE
local GROUP_PLAYER_DEBUFF			= Srendarr.GROUP_PLAYER_DEBUFF
local GROUP_PLAYER_GROUND			= Srendarr.GROUP_PLAYER_GROUND
local GROUP_PLAYER_MAJOR			= Srendarr.GROUP_PLAYER_MAJOR
local GROUP_PLAYER_MINOR			= Srendarr.GROUP_PLAYER_MINOR
local GROUP_TARGET_BUFF				= Srendarr.GROUP_TARGET_BUFF
local GROUP_TARGET_DEBUFF			= Srendarr.GROUP_TARGET_DEBUFF
local GROUP_PROMINENT				= Srendarr.GROUP_PROMINENT

local AURA_TYPE_TIMED				= Srendarr.AURA_TYPE_TIMED
local AURA_TYPE_TOGGLED				= Srendarr.AURA_TYPE_TOGGLED
local AURA_TYPE_PASSIVE				= Srendarr.AURA_TYPE_PASSIVE

-- UPVALUES --
local GetGameTimeMillis				= GetGameTimeMilliseconds
local IsToggledAura					= Srendarr.IsToggledAura
local IsMajorEffect					= Srendarr.IsMajorEffect	-- technically only used for major|minor buffs on the player, major|minor debuffs
local IsMinorEffect					= Srendarr.IsMinorEffect	-- are filtered to the debuff grouping before being checked for

local auraLookup					= Srendarr.auraLookup
local filteredAuras					= Srendarr.filteredAuras
local prominentAuras				= {}
local displayFrameRef				= {}
local shortBuffThreshold, filterDisguisesOnPlayer, filterDisguisesOnTarget

local displayFrameFake = {
	['AddAuraToDisplay'] = function()
 		-- do nothing : used to make the AuraHandler code more manageable, redirects unwanted auras to nil
	end,
}

-- ------------------------
-- AURA HANDLER
-- ------------------------
local function AuraHandler(flagBurst, auraName, unitTag, start, finish, icon, effectType, abilityType, abilityID)
	-- d('AuraHandler ' .. auraName .. ' [' .. abilityID ..'] (' .. unitTag .. '), effectType: ' .. effectType .. ', abilityType: ' .. abilityType .. string.format(', %.3f | %.3f (%.3f)', start, finish, (finish - start)))

	if (start ~= finish and (finish - start) < 2.25) then return end -- abort showing any timed auras with a duration of < 2.25s

	if (filteredAuras[unitTag][abilityID]) then return end -- abort immediately if this is an ability we've filtered

	if (auraLookup[unitTag][abilityID]) then -- aura exists, update its data (assume would not exist unless passed filters earlier)
		auraLookup[unitTag][abilityID]:Update(start, finish)
		return
	end

	if (prominentAuras[abilityID] and unitTag ~= 'reticleover') then -- special case, this is a prominent aura on player or gtaoe
		displayFrameRef[GROUP_PROMINENT]:AddAuraToDisplay(flagBurst, GROUP_PROMINENT, AURA_TYPE_TIMED, auraName, unitTag, start, finish, icon, effectType, abilityType, abilityID)
		return
	end

	if (unitTag == 'reticleover') then -- new aura on target
		if (effectType == BUFF_EFFECT_TYPE_DEBUFF) then
			-- debuff on target, check for it being a passive (not sure they can be, but just to be sure as things break with a 'timed' passive)
			displayFrameRef[GROUP_TARGET_DEBUFF]:AddAuraToDisplay(flagBurst, GROUP_TARGET_DEBUFF, (start == finish) and AURA_TYPE_PASSIVE or AURA_TYPE_TIMED, auraName, unitTag, start, finish, icon, effectType, abilityType, abilityID)
		else
			-- buff on target, sort as passive, toggle or timed and add
			if (filterDisguisesOnTarget and abilityType == ABILITY_TYPE_CHANGEAPPEARANCE) then return end -- is a disguise and they are filtered

			if (start == finish) then -- toggled or passive
				displayFrameRef[GROUP_TARGET_BUFF]:AddAuraToDisplay(flagBurst, GROUP_TARGET_BUFF, (IsToggledAura(abilityID)) and AURA_TYPE_TOGGLED or AURA_TYPE_PASSIVE, auraName, unitTag, start, finish, icon, effectType, abilityType, abilityID)
			else -- timed buff
				displayFrameRef[GROUP_TARGET_BUFF]:AddAuraToDisplay(flagBurst, GROUP_TARGET_BUFF, AURA_TYPE_TIMED, auraName, unitTag, start, finish, icon, effectType, abilityType, abilityID)
			end
		end
	elseif (unitTag == 'player') then -- new aura on player
		if (effectType == BUFF_EFFECT_TYPE_DEBUFF) then
			-- debuff on player, check for it being a passive (not sure they can be, but just to be sure as things break with a 'timed' passive)
			displayFrameRef[GROUP_PLAYER_DEBUFF]:AddAuraToDisplay(flagBurst, GROUP_PLAYER_DEBUFF, (start == finish) and AURA_TYPE_PASSIVE or AURA_TYPE_TIMED, auraName, unitTag, start, finish, icon, effectType, abilityType, abilityID)
		else
			-- buff on player, sort as passive, toggled or timed and add
			if (filterDisguisesOnPlayer and abilityType == ABILITY_TYPE_CHANGEAPPEARANCE) then return end -- is a disguise and they are filtered

			if (start == finish) then -- toggled or passive
				if (IsToggledAura(abilityID)) then -- toggled
					displayFrameRef[GROUP_PLAYER_TOGGLED]:AddAuraToDisplay(flagBurst, GROUP_PLAYER_TOGGLED, AURA_TYPE_TOGGLED, auraName, unitTag, start, finish, icon, effectType, abilityType, abilityID)
				else -- passive
					displayFrameRef[GROUP_PLAYER_PASSIVE]:AddAuraToDisplay(flagBurst, GROUP_PLAYER_PASSIVE, AURA_TYPE_PASSIVE, auraName, unitTag, start, finish, icon, effectType, abilityType, abilityID)
				end
			else -- timed buff
				if (IsMajorEffect(abilityID)) then -- major buff on player
					displayFrameRef[GROUP_PLAYER_MAJOR]:AddAuraToDisplay(flagBurst, GROUP_PLAYER_MAJOR, AURA_TYPE_TIMED, auraName, unitTag, start, finish, icon, effectType, abilityType, abilityID)
				elseif (IsMinorEffect(abilityID)) then -- minor buff on player
					displayFrameRef[GROUP_PLAYER_MINOR]:AddAuraToDisplay(flagBurst, GROUP_PLAYER_MINOR, AURA_TYPE_TIMED, auraName, unitTag, start, finish, icon, effectType, abilityType, abilityID)
				elseif ((finish - start) > shortBuffThreshold) then -- is considered a long duration buff
					displayFrameRef[GROUP_PLAYER_LONG]:AddAuraToDisplay(flagBurst, GROUP_PLAYER_LONG, AURA_TYPE_TIMED, auraName, unitTag, start, finish, icon, effectType, abilityType, abilityID)
				else
					displayFrameRef[GROUP_PLAYER_SHORT]:AddAuraToDisplay(flagBurst, GROUP_PLAYER_SHORT, AURA_TYPE_TIMED, auraName, unitTag, start, finish, icon, effectType, abilityType, abilityID)
				end
			end
		end
	else -- unitTag == 'groundaoe' -- new ground aoe cast by player (assume always timed)
		displayFrameRef[GROUP_PLAYER_GROUND]:AddAuraToDisplay(flagBurst, GROUP_PLAYER_GROUND, AURA_TYPE_TIMED, auraName, unitTag, start, finish, icon, effectType, abilityType, abilityID)
	end
end

function Srendarr:ConfigureAuraHandler()
	for group, frameNum in pairs(self.db.auraGroups) do
		-- if a group is set to hidden, auras will be sent to a fake frame that does nothing (simplifies things)
		displayFrameRef[group] = (frameNum > 0) and self.displayFrames[frameNum] or displayFrameFake
	end

	shortBuffThreshold		= self.db.shortBuffThreshold
	filterDisguisesOnPlayer	= self.db.filtersPlayer.disguise
	filterDisguisesOnTarget	= self.db.filtersTarget.disguise

	for id in pairs(prominentAuras) do
		prominentAuras[id] = nil -- clean out prominent auras list
	end

	for _, abilityIDs in pairs(self.db.prominentWhitelist) do
		for id in pairs(abilityIDs) do
			prominentAuras[id] = true -- populate promience list from saved database
		end
	end
end


-- ------------------------
-- EVENT: EVENT_PLAYER_ACTIVATED, EVENT_PLAYER_ALIVE
do ------------------------
    local GetNumBuffs       	= GetNumBuffs
    local GetUnitBuffInfo   	= GetUnitBuffInfo
    local NUM_DISPLAY_FRAMES	= Srendarr.NUM_DISPLAY_FRAMES

	local auraLookup				= Srendarr.auraLookup
    local numAuras, auraName, start, finish, stack, icon, effectType, abilityType, abilityID

	Srendarr.OnPlayerActivatedAlive = function()
		for _, auras in pairs(auraLookup) do -- iterate all aura lookups
			for _, aura in pairs(auras) do -- iterate all auras for each lookup
				aura:Release(true)
			end
		end

		numAuras = GetNumBuffs('player')

		if (numAuras > 0) then -- player has auras, scan and send to handle
			for i = 1, numAuras do
				auraName, start, finish, _, _, icon, _, effectType, abilityType, _, abilityID = GetUnitBuffInfo('player', i)

				AuraHandler(true, auraName, 'player', start, finish, icon, effectType, abilityType, abilityID)
			end
		end

		for x = 1, NUM_DISPLAY_FRAMES do
			Srendarr.displayFrames[x]:UpdateDisplay() -- update the display for all frames
		end
	end
end

-- ------------------------
-- EVENT: EVENT_PLAYER_DEAD
do ------------------------
    local NUM_DISPLAY_FRAMES	= Srendarr.NUM_DISPLAY_FRAMES

    local auraLookup			= Srendarr.auraLookup

	Srendarr.OnPlayerDead = function()
		for _, auras in pairs(auraLookup) do -- iterate all aura lookups
			for _, aura in pairs(auras) do -- iterate all auras for each lookup
				aura:Release(true)
			end
		end

		for x = 1, NUM_DISPLAY_FRAMES do
			Srendarr.displayFrames[x]:UpdateDisplay() -- update the display for all frames
		end
	end
end

-- ------------------------
-- EVENT: EVENT_PLAYER_COMBAT_STATE
do -----------------------
    local NUM_DISPLAY_FRAMES	= Srendarr.NUM_DISPLAY_FRAMES

    local displayFramesScene	= Srendarr.displayFramesScene

	OnCombatState = function(e, inCombat)
		if (inCombat) then
			if (Srendarr.db.combatDisplayOnly) then
				for x = 1, NUM_DISPLAY_FRAMES do
					displayFramesScene[x]:SetHiddenForReason('combatstate', false)
				end
			end
		else
			if (Srendarr.db.combatDisplayOnly) then
				for x = 1, NUM_DISPLAY_FRAMES do
					displayFramesScene[x]:SetHiddenForReason('combatstate', true)
				end
			end
		end
	end

	function Srendarr:ConfigureOnCombatState()
		if (self.db.combatDisplayOnly) then
			EVENT_MANAGER:RegisterForEvent(self.name, EVENT_PLAYER_COMBAT_STATE, OnCombatState)

			OnCombatState(nil, IsUnitInCombat('player')) -- force an update
		else
			EVENT_MANAGER:UnregisterForEvent(self.name, EVENT_PLAYER_COMBAT_STATE)

			if (self.displayFramesScene[1]:IsHiddenForReason('combatstate')) then -- if currently hidden due to setting, show
				for x = 1, NUM_DISPLAY_FRAMES do
					self.displayFramesScene[x]:SetHiddenForReason('combatstate', false)
				end
			end
		end
	end

	Srendarr.OnCombatState = OnCombatState
end

-- ------------------------
-- EVENT: EVENT_EFFECT_CHANGED
do ------------------------
	local EFFECT_RESULT_FADED			= EFFECT_RESULT_FADED
	local ABILITY_TYPE_AREAEFFECT		= ABILITY_TYPE_AREAEFFECT
--	local ABILITY_TYPE_REGISTERTRIGGER	= ABILITY_TYPE_REGISTERTRIGGER
	local AURA_TYPE_TIMED				= Srendarr.AURA_TYPE_TIMED
	local GROUP_PLAYER_GROUND			= Srendarr.GROUP_PLAYER_GROUND

	local GetAbilityDescription			= GetAbilityDescription
	local crystalFragmentsPassive		= Srendarr.crystalFragmentsPassive -- special case for tracking fragments proc

	local auraLookup					= Srendarr.auraLookup
	local fadedAura

	Srendarr.OnEffectChanged = function(e, change, slot, auraName, unitTag, start, finish, stack, icon, buffType, effectType, abilityType, statusType, unitName, unitID, abilityID)
		-- check the aura is on either the player, the target or is a ground aoe -- the description check filters a lot of extra auras attached to many ground effects
		unitTag = (unitTag == 'player' or unitTag == 'reticleover') and unitTag or (abilityType == ABILITY_TYPE_AREAEFFECT and GetAbilityDescription(abilityID) ~= '') and 'groundaoe' or nil

		if (not unitTag) then return end -- don't care about this unit and isn't a ground aoe, abort

		if (change == EFFECT_RESULT_FADED) then -- aura has faded
			fadedAura = auraLookup[unitTag][abilityID]

			if (fadedAura) then -- aura exists, tell it to expire if timed, or release otherwise
				if (fadedAura.auraType == AURA_TYPE_TIMED) then
					if (fadedAura.abilityType == ABILITY_TYPE_AREAEFFECT) then return end -- gtaoes expire internally (repeated casting, only one timer)

					fadedAura:SetExpired()
				else
					fadedAura:Release()
				end
			end

--			if (abilityType == ABILITY_TYPE_REGISTERTRIGGER and
			if (auraName == crystalFragmentsPassive) then -- special case for tracking fragments proc
				Srendarr:OnCrystalFragmentsProc(false)
			end
		else -- aura has been gained or changed, dispatch to handler
			AuraHandler(false, auraName, unitTag, start, finish, icon, effectType, abilityType, abilityID)

--			if (abilityType == ABILITY_TYPE_REGISTERTRIGGER and
			if (auraName == crystalFragmentsPassive) then -- special case for tracking fragments proc
				Srendarr:OnCrystalFragmentsProc(true)
			end
		end
	end
end

-- ------------------------
-- EVENT: EVENT_RETICLE_TARGET_CHANGED
do ------------------------
    local GetNumBuffs      			= GetNumBuffs
    local GetUnitBuffInfo  			= GetUnitBuffInfo
    local DoesUnitExist    			= DoesUnitExist

	local auraLookupReticle			= Srendarr.auraLookup['reticleover'] -- local ref for speed, this functions expensive
	local targetDisplayFrame1		= false -- local refs to frames displaying target auras (if any)
	local targetDisplayFrame2		= false -- local refs to frames displaying target auras (if any)
	local numAuras, auraName, start, finish, stack, icon, effectType, abilityType, abilityID

	local function OnTargetChanged()
		for _, aura in pairs(auraLookupReticle) do
			aura:Release(true) -- old auras cleaned out
		end

		if (DoesUnitExist('reticleover')) then -- have a target, scan for auras
			numAuras = GetNumBuffs('reticleover')

            if (numAuras > 0) then -- target has auras, scan and send to handler
            	for i = 1, numAuras do
					auraName, start, finish, _, stack, icon, _, effectType, abilityType, _, abilityID = GetUnitBuffInfo('reticleover', i)

					AuraHandler(true, auraName, 'reticleover', start, finish, icon, effectType, abilityType, abilityID)
				end
			end
		end

		-- no matter, update the display of the 1-2 frames displaying targets auras
		if (targetDisplayFrame1) then targetDisplayFrame1:UpdateDisplay() end
		if (targetDisplayFrame2) then targetDisplayFrame2:UpdateDisplay() end
	end

	function Srendarr:ConfigureOnTargetChanged()
		-- figure out which frames currently display target auras
		local targetBuff	= self.db.auraGroups[Srendarr.GROUP_TARGET_BUFF]
		local targetDebuff	= self.db.auraGroups[Srendarr.GROUP_TARGET_DEBUFF]

		targetDisplayFrame1 = (targetBuff ~= 0) and self.displayFrames[targetBuff] or false
		targetDisplayFrame2 = (targetDebuff ~= 0) and self.displayFrames[targetDebuff] or false

		if (targetDisplayFrame1 or targetDisplayFrame2) then -- event configured and needed, start tracking
			EVENT_MANAGER:RegisterForEvent(self.name, EVENT_RETICLE_TARGET_CHANGED,	OnTargetChanged)
		else -- not needed (not displaying any target auras)
			EVENT_MANAGER:UnregisterForEvent(self.name, EVENT_RETICLE_TARGET_CHANGED)
		end
	end

	Srendarr.OnTargetChanged = OnTargetChanged
end

-- ------------------------
-- EVENT: EVENT_ACTION_SLOT_ABILITY_USED
do ------------------------
	local ABILITY_TYPE_NONE 	= ABILITY_TYPE_NONE		-- no fakes have any specifc ability type
	local BUFF_EFFECT_TYPE_BUFF = BUFF_EFFECT_TYPE_BUFF -- all fakes are buffs or gtaoe

	local GetGameTimeMillis		= GetGameTimeMilliseconds
	local GetLatency			= GetLatency

	local slotData				= Srendarr.slotData
	local fakeAuras				= Srendarr.fakeAuras
	local slotAbilityName, currentTime

	Srendarr.OnActionSlotAbilityUsed = function(e, slotID)
		if (slotID < 3 or slotID > 8) then return end -- abort if not a main ability (or ultimate)

		slotAbilityName = slotData[slotID].abilityName

		if (not fakeAuras[slotAbilityName]) then return end -- no fake aura needed for this ability (majority case)

  		currentTime = GetGameTimeMillis() / 1000

		AuraHandler(
			false,
			slotAbilityName,
			fakeAuras[slotAbilityName].unitTag,
			currentTime,
			currentTime + fakeAuras[slotAbilityName].duration + (GetLatency() / 1000), -- + cooldown? GetSlotCooldownInfo(slotID)
			slotData[slotID].abilityIcon,
			BUFF_EFFECT_TYPE_BUFF,
			ABILITY_TYPE_NONE,
			fakeAuras[slotAbilityName].abilityID
		)
	end

	function Srendarr:ConfigureOnActionSlotAbilityUsed()
		if (self.db.auraFakeEnabled) then
			EVENT_MANAGER:RegisterForEvent(self.name, EVENT_ACTION_SLOT_ABILITY_USED,	Srendarr.OnActionSlotAbilityUsed)
		else
			EVENT_MANAGER:UnregisterForEvent(self.name, EVENT_ACTION_SLOT_ABILITY_USED)
		end
	end
end


function Srendarr:InitializeAuraControl()
	-- setup event handlers
	EVENT_MANAGER:RegisterForEvent(self.name, EVENT_PLAYER_ACTIVATED,			Srendarr.OnPlayerActivatedAlive) -- same action for both events
	EVENT_MANAGER:RegisterForEvent(self.name, EVENT_PLAYER_ALIVE,				Srendarr.OnPlayerActivatedAlive) -- same action for both events
	EVENT_MANAGER:RegisterForEvent(self.name, EVENT_PLAYER_DEAD,				Srendarr.OnPlayerDead)
	EVENT_MANAGER:RegisterForEvent(self.name, EVENT_EFFECT_CHANGED,				Srendarr.OnEffectChanged)

	self:ConfigureOnCombatState()			-- EVENT_PLAYER_COMBAT_STATE
	self:ConfigureOnTargetChanged()			-- EVENT_RETICLE_TARGET_CHANGED
	self:ConfigureOnActionSlotAbilityUsed()	-- EVENT_ACTION_SLOT_ABILITY_USED

	self:ConfigureAuraHandler()
end