--[[
Name: LibAuraInfo-1.0
Author(s): Cyprias (cyprias@gmail.com)
Documentation: http://www.wowace.com/addons/libaurainfo-1-0/
SVN: svn://svn.wowace.com/wow/libaurainfo-1-0/mainline/trunk
Description: Database of spellID's duration and debuff type.
Dependencies: LibStub
]]

local _G = _G
local print = print
local UnitAura = UnitAura
local _
local math_floor = math.floor
local UnitIsUnit = UnitIsUnit
local UnitExists = UnitExists
local UnitName = UnitName
local UnitGUID = UnitGUID
local pairs = pairs
local table_insert = table.insert
local table_remove = table.remove
local GetSpellInfo = GetSpellInfo
local UnitIsPlayer = UnitIsPlayer
local GetTime = GetTime
local tostring = tostring
local bit_band = bit.band
local COMBATLOG_OBJECT_TYPE_PLAYER = COMBATLOG_OBJECT_TYPE_PLAYER
local tonumber = tonumber
local strsplit = strsplit

local MAJOR, MINOR = "LibAuraInfo-1.0", 7
if not LibStub then error(MAJOR .. " requires LibStub.") return end

local lib = LibStub:NewLibrary(MAJOR, MINOR)
if not lib then return end

lib.callbacks = lib.callbacks or LibStub("CallbackHandler-1.0"):New(lib)
if not lib.callbacks then	error(MAJOR .. " CallbackHandler-1.0.") return end

lib.confirmedDur = {}

lib.GUIDDurations = {}

local DEBUG = false
--[===[@debug@
DEBUG = true
--@end-debug@]===]


local function debugPrint(...)
	if DEBUG then
		print(...)
	end
end
lib.debugPrint = debugPrint

--Save debuffType as a number, then return as a string when requested.
lib.debuffTypes = {
	Magic = 1,
	Disease = 2,
	Poison = 3,
	Curse = 4,
	[1] = "Magic",
	[2] = "Disease",
	[3] = "Poison",
	[4] = "Curse",
}

lib.trackAuras = false
lib.callbacksUsed = {}

--~ lib.callbacks:Fire("LibAuraInfo-1.0_xxxx", "blaw")
function lib.callbacks:OnUsed(target, eventname)
--~ 	debugPrint("OnUsed", target, eventname)
	
	lib.callbacksUsed[eventname] = lib.callbacksUsed[eventname] or {}
	table_insert(lib.callbacksUsed[eventname], #lib.callbacksUsed[eventname]+1, target)
	lib.trackAuras = true
	lib.frame:RegisterEvent('COMBAT_LOG_EVENT_UNFILTERED')
	
	lib.frame:RegisterEvent('UPDATE_MOUSEOVER_UNIT')
	lib.frame:RegisterEvent('PLAYER_TARGET_CHANGED')
	lib.frame:RegisterEvent('UNIT_TARGET')
	lib.frame:RegisterEvent('UNIT_AURA')
--~ 	debugPrint("OnUsed", eventName)
end

function lib.callbacks:OnUnused(target, eventname)
--~ 	debugPrint("OnUsed", target, eventname)
	if lib.callbacksUsed[eventname] then
		for i= #lib.callbacksUsed[eventname], 1, -1 do 
			if lib.callbacksUsed[eventname][i] == target then
				table_remove(lib.callbacksUsed[eventname], i)
			end
		end
	end
	
	for event, value in pairs(lib.callbacksUsed) do 
		if #value == 0 then
			lib.callbacksUsed[event] = nil
		end
	end
	
	for event in pairs(lib.callbacksUsed) do 
		return
	end
	lib.trackAuras = false
	lib.frame:UnregisterEvent('COMBAT_LOG_EVENT_UNFILTERED')
	
	lib.frame:UnregisterEvent('UPDATE_MOUSEOVER_UNIT')
	lib.frame:UnregisterEvent('PLAYER_TARGET_CHANGED')
	lib.frame:UnregisterEvent('UNIT_TARGET')
	lib.frame:UnregisterEvent('UNIT_AURA')
--~ 	debugPrint("OnUnused", eventName)
end


local LAI_DB
local function Round(num, zeros)
	return math_floor( num * 10 ^ (zeros or 0) + 0.5 ) / 10 ^ (zeros or 0)
end

local function ResetUnitAuras(unitID)
	if lib.trackAuras  == true then
		local dstGUID = UnitGUID(unitID)
		lib:RemoveAllAurasFromGUID(dstGUID)
	end
end

function lib:AddAuraFromUnitID(dstGUID, spellID, srcGUID, isDebuff, debuffType, duration, expirationTime, stackCount)
	local currentTime = GetTime()
	
	self.GUIDAuras[dstGUID] = self.GUIDAuras[dstGUID] or {}
	
	table_insert(self.GUIDAuras[dstGUID], #self.GUIDAuras[dstGUID]+1, {
		spellID = spellID,
		srcGUID = srcGUID,
		duration = duration,
		debuffType = debuffType,
		isDebuff = isDebuff,
		expirationTime = currentTime + duration,
		startTime = currentTime,
		stackCount = stackCount,--This should only be added if stackCount is over 1, else it'll be nil.
	})
	
end

local function CheckUnitAuras(unitID, filterType)
--~ 	name, rank, icon, count, debuffType, duration, expirationTime, unitCaster, isStealable, shouldConsolidate, spellId  = UnitAura("unit", index or "name"[, "rank"[, "filter"]]) 
	local i = 1;
	local name, rank, dispelType, duration, isStealable, spellID, unitCaster, expirationTime, stackCount, shouldConsolidate
	
	local dstGUID = UnitGUID(unitID)
	local srcGUID
	
	--Since we have a unitID, lets clear our aura table and use 100% accurate aura info.
	if lib.trackAuras == true and lib.GUIDAuras[dstGUID] then
		for i=#lib.GUIDAuras[dstGUID], 1, -1 do 
			table_remove(lib.GUIDAuras[dstGUID], i)
		end
	end

	while true do 
		name, rank, _, stackCount, dispelType, duration, expirationTime, unitCaster, isStealable, shouldConsolidate, spellID = UnitAura(unitID, i, filterType)
		if not name then break end

		duration = Round(duration)

		if not lib.auraInfo[spellID] then

			lib.auraInfo[spellID] = (duration or 0) .. ";" .. (dispelType and lib.debuffTypes[dispelType] or 0)--add it temporarily.
			
			--[===[@debug@
			if not LAI_DB.new[spellID] then
				--Save the spell info to a saved variable to add it later. End users won't execute this code.
				LAI_DB.new[spellID] = lib.auraInfo[spellID]    .."  --"..name .. (rank and rank ~= "" and " ("..rank..")" or "") .."*"
				debugPrint("Missing info on", UnitName(unitID), name, rank, spellID, dispelType, duration)
			end
			--@end-debug@]===]
		elseif not lib.auraInfoPvP[spellID] then
			if unitCaster and UnitExists(unitCaster) then
				local GUID = UnitGUID(unitCaster)
				local baseDuration = lib:GetDuration(spellID) --, nil, nil, UnitIsPlayer(unitID)
				if baseDuration and baseDuration ~= duration then
--~ 					local spellName = GetSpellInfo(spellID)
					
					if duration > 0 then-- Sometimes UnitAura says a spell has 0 duration when it realy has more.
						--caster's duration doesn't match our DB, they're probably speced into something. lets remember that.
						lib.GUIDDurations[GUID.."-"..spellID] = duration
					end
					
					--[===[@debug@
					--If we haven't confirmed the duration on WoWHead and the duration doesn't match our DB duration, save to SV file to confirm duration later.
					if not lib.confirmedDur[spellID] and not lib.auraInfoPvP[spellID] then
						--Save the spell info to a saved variable to add it later. End users won't execute this code.
						if not LAI_DB.needConfirm[spellID] then
							if baseDuration == 0 then
								debugPrint("Replace duration on", spellID, dispelType, name, rank, "ourDur:"..baseDuration, "theirDur:"..duration)
							else
								debugPrint("Confirm duration on", spellID, dispelType, name, rank, "ourDur:"..baseDuration, "theirDur:"..duration)
							end
						end
						
						if baseDuration == 0 then
							LAI_DB.replace[spellID] = (duration or 0) .. ";" .. (dispelType and lib.debuffTypes[dispelType] or 0)    .."  --"..name .. (rank and rank ~= "" and " ("..rank..")" or "") .."* Replace Duration"
						else
							LAI_DB.needConfirm[spellID] = (duration or 0) .. ";" .. (dispelType and lib.debuffTypes[dispelType] or 0)    .."  --"..name .. (rank and rank ~= "" and " ("..rank..")" or "") .."* Confirm Duration"
						end
					end
					--@end-debug@]===]
					
				end
			end
		end
		
		if lib.trackAuras == true and unitCaster then
--~ 			srcGUID = UnitGUID(unitCaster)
			lib:AddAuraFromUnitID(
				dstGUID, 
				spellID, 
				srcGUID, 
				filterType == "HARMFUL", 
				dispelType, 
				duration, 
				expirationTime, 
				stackCount and stackCount > 1 and stackCount or nil
			)
		end
		
		i = i + 1
	end
end

lib.frame = lib.frame or CreateFrame("Frame")
local function OnEvent(self, event, ...)
	if self[event] then
		self[event](self, event, ...)
	end
end

function lib.frame:UPDATE_MOUSEOVER_UNIT()
	ResetUnitAuras("mouseover")
	CheckUnitAuras("mouseover", "HELPFUL")
	CheckUnitAuras("mouseover", "HARMFUL")
end

function lib.frame:PLAYER_TARGET_CHANGED()
	ResetUnitAuras("target")
	CheckUnitAuras("target", "HELPFUL")
	CheckUnitAuras("target", "HARMFUL")
end

function lib.frame:UNIT_TARGET(unitID)
	if not UnitIsUnit(unitID, "player") then
		local targetID = unitID.."target"
		ResetUnitAuras(targetID)
		CheckUnitAuras(targetID, "HELPFUL")
		CheckUnitAuras(targetID, "HARMFUL")
	end
end

function lib.frame:UNIT_AURA(unitID)
	ResetUnitAuras(unitID)
	CheckUnitAuras(unitID, "HELPFUL")
	CheckUnitAuras(unitID, "HARMFUL")
end

function lib.frame:PLAYER_LOGOUT(unitID)
	--[===[@debug@
	_G.LAI_DB = LAI_DB
	--@end-debug@]===]
end

function lib.frame:ADDON_LOADED()
	--[===[@debug@
	LAI_DB = _G.LAI_DB or {needConfirm = {}, new = {}}
	self:RegisterEvent('PLAYER_LOGOUT')
	--@end-debug@]===]
end

function lib.frame:PLAYER_LOGIN()
	self:ADDON_LOADED()
end

lib.frame:SetScript("OnEvent", OnEvent)
lib.frame:RegisterEvent('ADDON_LOADED')
lib.frame:RegisterEvent('PLAYER_LOGIN')

------------------------------------------------------------------------------------------------------
--~ Dimminshing Returns
------------------------------------------------------------------------------------------------------

lib.resetDRTime = 18 --Time it tacks for DR to reset.

--[[
	List of spellID's copied from DRData-1.0 by Shadowed.
	http://www.wowace.com/addons/drdata-1-0/
	http://www.wowace.com/profiles/Shadowed/
]]
lib.drSpells = {
	--[[ TAUNT ]]--
	-- Taunt (Warrior)
	[355] = "taunt",
	-- Taunt (Pet)
	[53477] = "taunt",
	-- Mocking Blow
	[694] = "taunt",
	-- Growl (Druid)
	[6795] = "taunt",
	-- Dark Command
	[56222] = "taunt",
	-- Hand of Reckoning
	[62124] = "taunt",
	-- Righteous Defense
	[31790] = "taunt",
	-- Distracting Shot
	[20736] = "taunt",
	-- Challenging Shout
	[1161] = "taunt",
	-- Challenging Roar
	[5209] = "taunt",
	-- Death Grip
	[49560] = "taunt",
	-- Challenging Howl
	[59671] = "taunt",
	-- Angered Earth
	[36213] = "taunt",
	
	--[[ DISORIENTS ]]--
	-- Dragon's Breath
	[31661] = "disorient",
	[33041] = "disorient",
	[33042] = "disorient",
	[33043] = "disorient",
	[42949] = "disorient",
	[42950] = "disorient",
	
	-- Hungering Cold
	[49203] = "disorient",
	
	-- Sap
	[6770] = "disorient",
	[2070] = "disorient",
	[11297] = "disorient",
	[51724] = "disorient",
	
	-- Gouge
	[1776] = "disorient",
		
	-- Hex (Guessing)
	[51514] = "disorient",
	
	-- Shackle
	[9484] = "disorient",
	[9485] = "disorient",
	[10955] = "disorient",
	
	-- Polymorph
	[118] = "disorient",
	[12824] = "disorient",
	[12825] = "disorient",
	[28272] = "disorient",
	[28271] = "disorient",
	[12826] = "disorient",
	[61305] = "disorient",
	[61025] = "disorient",
	[61721] = "disorient",
	[61780] = "disorient",
	
	-- Freezing Trap
	[3355] = "disorient",
	[14308] = "disorient",
	[14309] = "disorient",
	
	-- Freezing Arrow
	[60210] = "disorient",

	-- Wyvern Sting
	[19386] = "disorient",
	[24132] = "disorient",
	[24133] = "disorient",
	[27068] = "disorient",
	[49011] = "disorient",
	[49012] = "disorient",
	
	-- Repentance
	[20066] = "disorient",
		
	--[[ SILENCES ]]--
	-- Nether Shock
	[53588] = "silence",
	[53589] = "silence",
	
	-- Garrote
	[1330] = "silence",
	
	-- Arcane Torrent (Energy version)
	[25046] = "silence",
	
	-- Arcane Torrent (Mana version)
	[28730] = "silence",
	
	-- Arcane Torrent (Runic power version)
	[50613] = "silence",
	
	-- Silence
	[15487] = "silence",

	-- Silencing Shot
	[34490] = "silence",

	-- Improved Kick
	[18425] = "silence",

	-- Improved Counterspell
	[18469] = "silence",
	
	-- Spell Lock
	[19244] = "silence",
	[19647] = "silence",
	
	-- Shield of the Templar
	[63529] = "silence",
	
	-- Strangulate
	[47476] = "silence",
	[49913] = "silence",
	[49914] = "silence",
	[49915] = "silence",
	[49916] = "silence",
	
	-- Gag Order (Warrior talent)
	[18498] = "silence",
	
	--[[ DISARMS ]]--
	-- Snatch
	[53542] = "disarm",
	[53543] = "disarm",
	
	-- Dismantle
	[51722] = "disarm",
	
	-- Disarm
	[676] = "disarm",
	
	-- Chimera Shot - Scorpid
	[53359] = "disarm",
	
	-- Psychic Horror (Disarm effect)
	[64058] = "disarm",
	
	--[[ FEARS ]]--
	-- Blind
	[2094] = "fear",

	-- Fear (Warlock)
	[5782] = "fear",
	[6213] = "fear",
	[6215] = "fear",
	
	-- Seduction (Pet)
	[6358] = "fear",
	
	-- Howl of Terror
	[5484] = "fear",
	[17928] = "fear",

	-- Psychic scream
	[8122] = "fear",
	[8124] = "fear",
	[10888] = "fear",
	[10890] = "fear",
	
	-- Scare Beast
	[1513] = "fear",
	[14326] = "fear",
	[14327] = "fear",
	
	-- Turn Evil
	[10326] = "fear",
	
	-- Intimidating Shout
	[5246] = "fear",
	

	--[[ CONTROL STUNS ]]--
	-- Intercept (Felguard)
	[30153] = "ctrlstun",
	[30195] = "ctrlstun",
	[30197] = "ctrlstun",
	[47995] = "ctrlstun",
	
	-- Ravage
	[50518] = "ctrlstun",
	[53558] = "ctrlstun",
	[53559] = "ctrlstun",
	[53560] = "ctrlstun",
	[53561] = "ctrlstun",
	[53562] = "ctrlstun",
	
	-- Sonic Blast
	[50519] = "ctrlstun",
	[53564] = "ctrlstun",
	[53565] = "ctrlstun",
	[53566] = "ctrlstun",
	[53567] = "ctrlstun",
	[53568] = "ctrlstun",
	
	-- Concussion Blow
	[12809] = "ctrlstun",
	
	-- Shockwave
	[46968] = "ctrlstun",
	
	-- Hammer of Justice
	[853] = "ctrlstun",
	[5588] = "ctrlstun",
	[5589] = "ctrlstun",
	[10308] = "ctrlstun",

	-- Bash
	[5211] = "ctrlstun",
	[6798] = "ctrlstun",
	[8983] = "ctrlstun",
	
	--***********************************************************
	-- Intimidation
	[19577] = "ctrlstun",

	-- Maim
	[22570] = "ctrlstun",
	[49802] = "ctrlstun",

	-- Kidney Shot
	[408] = "ctrlstun",
	[8643] = "ctrlstun",

	-- War Stomp
	[20549] = "ctrlstun",

	-- Intercept
	[20252] = "ctrlstun",
	
	-- Deep Freeze
	[44572] = "ctrlstun",
			
	-- Shadowfury
	[30283] = "ctrlstun", 
	[30413] = "ctrlstun",
	[30414] = "ctrlstun",
	
	-- Holy Wrath
	[2812] = "ctrlstun",
	
	-- Inferno Effect
	[22703] = "ctrlstun",
	
	-- Demon Charge
	[60995] = "ctrlstun",
	
	-- Gnaw (Ghoul)
	[47481] = "ctrlstun",
	
	--[[ RANDOM STUNS ]]--
	-- Impact
	[12355] = "rndstun",

	-- Stoneclaw Stun
	[39796] = "rndstun",
	
	-- Seal of Justice
	[20170] = "rndstun",
	
	-- Revenge Stun
	[12798] = "rndstun",
	
	--[[ CYCLONE ]]--
	-- Cyclone
	[33786] = "cyclone",
	
	--[[ ROOTS ]]--
	-- Freeze (Water Elemental)
	[33395] = "ctrlroot",
	
	-- Pin (Crab)
	[50245] = "ctrlroot",
	[53544] = "ctrlroot",
	[53545] = "ctrlroot",
	[53546] = "ctrlroot",
	[53547] = "ctrlroot",
	[53548] = "ctrlroot",	
	
	-- Frost Nova
	[122] = "ctrlroot",
	[865] = "ctrlroot",
	[6131] = "ctrlroot",
	[10230] = "ctrlroot",
	[27088] = "ctrlroot",
	[42917] = "ctrlroot",
	
	-- Entangling Roots
	[339] = "ctrlroot",
	[1062] = "ctrlroot",
	[5195] = "ctrlroot",
	[5196] = "ctrlroot",
	[9852] = "ctrlroot",
	[9853] = "ctrlroot",
	[26989] = "ctrlroot",
	[53308] = "ctrlroot",
	
	-- Nature's Grasp (Uses different spellIDs than Entangling Roots for the same spell)
	[19970] = "ctrlroot",
	[19971] = "ctrlroot",
	[19972] = "ctrlroot",
	[19973] = "ctrlroot",
	[19974] = "ctrlroot",
	[19975] = "ctrlroot",
	[27010] = "ctrlroot",
	[53313] = "ctrlroot",
	
	-- Earthgrab (Storm, Earth and Fire talent)
	[8377] = "ctrlroot",
	[31983] = "ctrlroot",

	-- Web (Spider)
	[4167] = "ctrlroot",
	
	-- Venom Web Spray (Silithid)
	[54706] = "ctrlroot",
	[55505] = "ctrlroot",
	[55506] = "ctrlroot",
	[55507] = "ctrlroot",
	[55508] = "ctrlroot",
	[55509] = "ctrlroot",
	
	
	--[[ RANDOM ROOTS ]]--
	-- Improved Hamstring
	[23694] = "rndroot",
	
	-- Frostbite
	[12494] = "rndroot",

	-- Shattered Barrier
	[55080] = "rndroot",
	
	--[[ SLEEPS ]]--
	-- Hibernate
	[2637] = "sleep",
	[18657] = "sleep",
	[18658] = "sleep",
		
	--[[ HORROR ]]--
	-- Death Coil
	[6789] = "horror",
	[17925] = "horror",
	[17926] = "horror",
	[27223] = "horror",
	[47859] = "horror",
	[47860] = "horror",
	
	-- Psychic Horror
	[64044] = "horror",
	
	--[[ MISC ]]--
	-- Scatter Shot
	[19503] = "scatters",

	-- Cheap Shot
	[1833] = "cheapshot",

	-- Pounce
	[9005] = "cheapshot",
	[9823] = "cheapshot",
	[9827] = "cheapshot",
	[27006] = "cheapshot",
	[49803] = "cheapshot",

	-- Charge
	[7922] = "charge",
	
	-- Mind Control
	[605] = "mc",

	-- Banish
	[710] = "banish",
	[18647] = "banish",
	
	-- Entrapment
	[64804] = "entrapment",
	[64804] = "entrapment",
	[19185] = "entrapment",
}

lib.pveDR = {
	["ctrlstun"] = true,
	["rndstun"] = true,
	["taunt"] = true,
	["cyclone"] = true,
}

--~ lib.guidDREffects = {}

--
lib.GUIDDrEffects_reset = {}
lib.GUIDDrEffects_diminished = {}

function lib:GUIDGainedDRAura(dstGUID, spellID, dstIsPlayer)
	local drType = self.drSpells[spellID]
	
	if dstIsPlayer or self.pveDR[drType] then
		local key = dstGUID..drType
		local reset = self.GUIDDrEffects_reset[key]
		if reset and reset <= GetTime() then
			self.GUIDDrEffects_diminished[key] = 1
		end
		
--~ 		debugPrint("GUIDGainedDRAura", dstGUID, drType, self.GUIDDrEffects_diminished[key])
	end
end

local function NextDR(diminished)
	if( diminished == 1 ) then
		return 0.50
	elseif( diminished == 0.50 ) then
		return 0.25
	end
	
	return 0
end


function lib:GUIDRemovedDRAura(dstGUID, spellID, dstIsPlayer)
	local drType = self.drSpells[spellID]
	if dstIsPlayer or self.pveDR[drType] then
		local key = dstGUID..drType
		self.GUIDDrEffects_reset[key] = GetTime() + self.resetDRTime
		self.GUIDDrEffects_diminished[key] = NextDR( self.GUIDDrEffects_diminished[key] or 1.0 )
	end
end

function lib:GetDRDuration(dstGUID, spellID, duration)
	local duration = duration or self:GetDuration(spellID, nil, dstGUID)
	local drType = self.drSpells[spellID]
	if drType then
		local key = dstGUID..drType
		local reset = self.GUIDDrEffects_reset[key]
		if reset and GetTime() < reset then
			return duration * (self.GUIDDrEffects_diminished[key] or 1)
		end
	end
	
	return duration
end


-------------------------------------------------------------------------------------------------------
--~ Combatlog
-------------------------------------------------------------------------------------------------------
lib.GUIDAuras = lib.GUIDAuras or {}

lib.GUIDData_name = {}
lib.GUIDData_flags = {}
local function SaveGUIDInfo(guid, name, flags)
	lib.GUIDData_name[guid] = name
	lib.GUIDData_flags[guid] = flags
end


function lib.frame:COMBAT_LOG_EVENT_UNFILTERED(event, ...)
	--timestamp, eventType, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags
	local timestamp, eventType, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags  = ...-- ***

	if srcGUID and not lib.GUIDData_name[srcGUID] then
		SaveGUIDInfo(srcGUID, srcName, srcFlags)
	end
	if dstGUID and not lib.GUIDData_name[dstGUID] then
		SaveGUIDInfo(dstGUID, dstName, dstFlags)
	end
	
	if self[eventType] then
		self[eventType](self, eventType, ...)
	end
end

----------------------------------------------
function lib:RemoveExpiredAuras(dstGUID)	--
-- Remove expired auras from a GUID. 		--
----------------------------------------------
	if self.GUIDAuras[dstGUID] then
		local data
		local getTime = GetTime()
		for i = #self.GUIDAuras[dstGUID], 1, -1 do 
			data = self.GUIDAuras[dstGUID][i]
			if getTime > data.expirationTime then
				table_remove(self.GUIDAuras[dstGUID], i)
			end
		end
	end
end

----------------------------------------------------------------------
function lib:checkIfAuraAlreadyOnGUID(dstGUID, spellID, srcGUID)	--
-- Used for debuging and learning the combatlog. 					--
----------------------------------------------------------------------

	local data
	local spellName = GetSpellInfo(spellID)
	for i= #self.GUIDAuras[dstGUID], 1, -1  do 
		data = self.GUIDAuras[dstGUID][i]
		if data.spellID == spellID  then --and srcGUID ~= data.srcGUID
			if srcGUID ~= data.srcGUID then
				local newName = self:GetGUIDInfo(srcGUID)
				local oldName = self:GetGUIDInfo(data.srcGUID)
				if newName == oldName then
					debugPrint("checkIfAuraAlreadyOnGUID", spellID, spellName, "new:"..tostring(srcGUID), "old:"..tostring(data.srcGUID))
				else
					debugPrint("checkIfAuraAlreadyOnGUID", spellID, spellName, "new:"..tostring(newName), "old:"..tostring(oldName))
				end
			end
		end
	
	end
end

------------------------------------------------------------------
function lib:AddAuraToGUID(dstGUID, spellID, srcGUID, isDebuff)	--
-- Add a auraID to our GUID list.								--
------------------------------------------------------------------

	local duration = self:GetDuration(spellID, srcGUID, dstGUID)
	local debuffType = self:GetDebuffType(spellID)
	local currentTime = GetTime()

	self.GUIDAuras[dstGUID] = self.GUIDAuras[dstGUID] or {}

	--[===[@debug@
--~ 	self:checkIfAuraAlreadyOnGUID(dstGUID, spellID, srcGUID)
	--@end-debug@]===]
	
	--[[
		I didn't want to use tables this way when I started the project but due to multiple instnces of a spellID being on a GUID I couldn't do a hash table lookup.
		I wanted do something like
		GUIDAuras_Duration[dstGUID..spellID..srcGUID] = 30
		but because UnitAura() sometimes doesn't return a unitID to get srcGUID, I had to do a index table. meh
		
	]]

	table_insert(self.GUIDAuras[dstGUID], #self.GUIDAuras[dstGUID]+1, {
		spellID = spellID,
		srcGUID = srcGUID,
		duration = duration,
		debuffType = debuffType,
		isDebuff = isDebuff,
		expirationTime = currentTime + duration,
		startTime = currentTime,
	})
	
	
--~ 	table.sort(self.GUIDAuras[dstGUID], function(a,b) 
--~ 		return a.expirationTime < b.expirationTime 
--~ 	end)
end

------------------------------------------------------
function lib:AddAuraDose(dstGUID, spellID, srcGUID)	--
-- Increase stack count of a aura.					--
------------------------------------------------------

	if self.GUIDAuras[dstGUID] then
		local data
		if srcGUID then
			for i=1, #self.GUIDAuras[dstGUID] do 
				data = self.GUIDAuras[dstGUID][i]
				if data.spellID == spellID and data.srcGUID == srcGUID then
					
					data.stackCount = (data.stackCount or 1) + 1
					
					data.startTime = GetTime()
					data.expirationTime = data.duration + data.startTime
					
					return true, data.stackCount, data.expirationTime
				end
			end
		end
		
		for i=1, #self.GUIDAuras[dstGUID] do 
			data = self.GUIDAuras[dstGUID][i]
			if data.spellID == spellID then
				
				data.stackCount = (data.stackCount or 1) + 1
				
				data.startTime = GetTime()
				data.expirationTime = data.duration + data.startTime
				
				return true, data.stackCount, data.expirationTime
			end
		end

	end
	return false
end

----------------------------------------------------------
function lib:RemoveAuraDose(dstGUID, spellID, srcGUID)	--
-- Remove 1 stack from a aura.							--
----------------------------------------------------------
	if self.GUIDAuras[dstGUID] then
		local data
		if srcGUID then
			for i=1, #self.GUIDAuras[dstGUID] do 
				data = self.GUIDAuras[dstGUID][i]
				if data.spellID == spellID and data.srcGUID == srcGUID then
					data.stackCount = (data.stackCount or 1) - 1
--~ 					data.startTime = GetTime()
--~ 					data.expirationTime = data.duration + data.startTime
					return true, data.stackCount, data.expirationTime
				end
			end
		end
		
		for i=1, #self.GUIDAuras[dstGUID] do 
			data = self.GUIDAuras[dstGUID][i]
			if data.spellID == spellID then
				data.stackCount = (data.stackCount or 1) - 1
--~ 				data.startTime = GetTime()
--~ 				data.expirationTime = data.duration + data.startTime
				return true, data.stackCount, data.expirationTime
			end
		end

	end
	return false
end

------------------------------------------------------
function lib:RefreshAura(dstGUID, spellID, srcGUID)	--
-- Refresh the start and expiration time of a aura.	--
------------------------------------------------------
	if self.GUIDAuras[dstGUID] then
		local data
		if srcGUID then
			for i=1, #self.GUIDAuras[dstGUID] do 
				data = self.GUIDAuras[dstGUID][i]
				if data.spellID == spellID and data.srcGUID == srcGUID then
					data.startTime = GetTime()
					data.expirationTime = data.duration + data.startTime
					return true, data.expirationTime
				end
			end
		end
		
		for i=1, #self.GUIDAuras[dstGUID] do 
			data = self.GUIDAuras[dstGUID][i]
			if data.spellID == spellID then
				data.startTime = GetTime()
				data.expirationTime = data.duration + data.startTime
				return true, data.expirationTime
			end
		end

	end
	return false
end

------------------------------------------------------------------
local function RemoveAuraFromGUID(dstGUID, spellID, srcGUID)	--
-- Remove a aura from a GUID.									--
------------------------------------------------------------------

	if lib.GUIDAuras[dstGUID] then
		local data
		local spellName  = GetSpellInfo(spellID)
		
		if srcGUID then
			for i= #lib.GUIDAuras[dstGUID],1, -1 do 
				data = lib.GUIDAuras[dstGUID][i]
				if data.spellID == spellID and srcGUID == data.srcGUID then
--~ 					debugPrint("RemoveAuraFromGUID", spellName, srcGUID, data.srcGUID, "match:"..tostring(srcGUID == data.srcGUID))
					table_remove(lib.GUIDAuras[dstGUID], i)
					return
				end
			end
		end
		
		for i= #lib.GUIDAuras[dstGUID],1, -1 do 
			data = lib.GUIDAuras[dstGUID][i]
			if data.spellID == spellID  then
				table_remove(lib.GUIDAuras[dstGUID], i)
				
				--[===[@debug@
				local newName = lib:GetGUIDInfo(srcGUID)
				local oldName = lib:GetGUIDInfo(data.srcGUID)
				if newName == oldName then
					debugPrint("RemoveAuraFromGUID", spellName, "new:"..tostring(newName), "old:"..tostring(data.srcGUID))
--~ 				else
--~ 					debugPrint("RemoveAuraFromGUID", spellName, "new:"..tostring(newName), "old:"..tostring(oldName))
				end
				--@end-debug@]===]
				return
			end
		end
		
	end
end

------------------------------------------------------
function lib:RemoveAllAurasFromGUID(dstGUID)		--
-- Remove all auras on a GUID. They must have died.	--
------------------------------------------------------
	if self.GUIDAuras[dstGUID] then
		for i=#self.GUIDAuras[dstGUID], 1, -1 do 
			table_remove(self.GUIDAuras[dstGUID], i)
		end
	end
end

--[[
local function AuraAlreadyOnGUID(dstGUID, spellID, srcGUID)
	if lib.GUIDAuras[dstGUID] then
		local data
		if srcGUID then
			for i=1, lib.GUIDAuras[dstGUID] do 
				data = lib.GUIDAuras[dstGUID][i]
				if data.spellID == spellID and data.srcGUID == srcGUID then
					return true
				end
			end
		end
		
		for i=1, lib.GUIDAuras[dstGUID] do 
			data = lib.GUIDAuras[dstGUID][i]
			if data.spellID == spellID then
				return true
			end
		end
	end
	return false
end
]]

function lib.frame:SPELL_AURA_REMOVED(event, timestamp, eventType, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)	--
	local spellID, spellName, spellSchool, auraType  = ...
	RemoveAuraFromGUID(dstGUID, spellID, srcGUID)
	
	
	if lib.drSpells[spellID] then
		lib:GUIDRemovedDRAura(dstGUID, spellID, lib:FlagIsPlayer(dstFlags))
	end
end

function lib.frame:SPELL_AURA_APPLIED(event, timestamp, eventType, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)	--
	local spellID, spellName, spellSchool, auraType, amount  = ...
	

	
	if lib.drSpells[spellID] then
		lib:GUIDGainedDRAura(dstGUID, spellID, lib:FlagIsPlayer(dstFlags))
	end
	
	if lib.auraInfo[spellID] then
		lib:RemoveExpiredAuras(dstGUID)
		
		lib:AddAuraToGUID(dstGUID, spellID, srcGUID, auraType == "DEBUFF")
		lib.callbacks:Fire("LibAuraInfo_AURA_APPLIED", dstGUID, spellID, srcGUID, spellSchool, auraType)
	end
end

function lib.frame:SPELL_AURA_REFRESH(event, timestamp, eventType, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)
	local spellID, spellName, spellSchool, auraType  = ...
	
	if lib.drSpells[spellID] then
		lib:GUIDRemovedDRAura(dstGUID, spellID, lib:FlagIsPlayer(dstFlags))
		lib:GUIDGainedDRAura(dstGUID, spellID, lib:FlagIsPlayer(dstFlags))
	end

	local refreshed, expirationTime = lib:RefreshAura(dstGUID, spellID, srcGUID)

	if refreshed then
		lib.callbacks:Fire("LibAuraInfo_AURA_REFRESH", dstGUID, spellID, srcGUID, spellSchool, auraType, expirationTime)
		return
	end

	self:SPELL_AURA_APPLIED(event, timestamp, eventType, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)
end

--DOSE = spell stacking
function lib.frame:SPELL_AURA_APPLIED_DOSE(event, timestamp, eventType, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)
	local spellID, spellName, spellSchool, auraType  = ...

	local dosed, stackCount, expirationTime = lib:AddAuraDose(dstGUID, spellID, srcGUID)
	
	if dosed then
		lib.callbacks:Fire("LibAuraInfo_AURA_APPLIED_DOSE", dstGUID, spellID, srcGUID, spellSchool, auraType, stackCount, expirationTime)
		return
	end

	--Spell isn't in our list, let's add it.
	--Note this event could have fired on the 5th stack but our spell frame will only show it applied once. 
	self:SPELL_AURA_APPLIED(event, timestamp, eventType, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)
end


function lib.frame:SPELL_AURA_APPLIED_REMOVED_DOSE(event, timestamp, eventType, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)
	local spellID, spellName, spellSchool, auraType  = ...

	local dosed, stackCount, expirationTime = lib:RemoveAuraDose(dstGUID, spellID, srcGUID)
	
	if dosed then
		lib.callbacks:Fire("LibAuraInfo_AURA_APPLIED_DOSE", dstGUID, spellID, srcGUID, spellSchool, auraType, stackCount, expirationTime)
		return
	end
	
end


function lib.frame:SPELL_AURA_BROKEN_SPELL(...)
--~ 	local spellID, spellName, spellSchool, auraType  = ...
	self:SPELL_AURA_REMOVED(...)
end

function lib.frame:SPELL_AURA_BROKEN(...)
--~ 	local spellID, spellName, spellSchool, auraType  = ...
	self:SPELL_AURA_REMOVED(...)
end


function lib.frame:UNIT_DIED(event, timestamp, eventType, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, ...)
	if lib.GUIDAuras[dstGUID] then
		lib:RemoveAllAurasFromGUID(dstGUID)
		lib.callbacks:Fire("LibAuraInfo_AURA_CLEAR", dstGUID)
	end
end

function lib.frame:UNIT_DESTROYED(...)
	self:UNIT_DIED(...)
end

function lib.frame:UNIT_DISSIPATES(...)
	self:UNIT_DIED(...)
end

function lib.frame:PARTY_KILL(...)
	self:UNIT_DIED(...)
end
function lib:FlagIsPlayer(flags)
	if bit_band(flags, COMBATLOG_OBJECT_TYPE_PLAYER) == COMBATLOG_OBJECT_TYPE_PLAYER then
		return true
	end
	return nil
end

--------------------------------------------------------------
--~ API
--------------------------------------------------------------
local function GUIDIsPlayer(guid)
	local B = tonumber(guid:sub(5,5), 16);
	local maskedB = B % 8; -- x % 8 has the same effect as x & 0x7 on numbers <= 0xf
--~ 	local knownTypes = {[0]="player", [3]="NPC", [4]="pet", [5]="vehicle"};
--~ 	print("Your target is a " .. (knownTypes[maskedB] or " unknown entity!"));
	if maskedB == 0 then
		return true
	end
	return false
end

--Return the duration of a spell.
function lib:GetDuration(spellID, srcGUID, dstGUID, dstIsPlayer)
	local dstIsPlayer = dstIsPlayer or dstGUID and GUIDIsPlayer(dstGUID) or false
	local spellStr = ""
	if dstIsPlayer and lib.auraInfoPvP[spellID] then
		--Receiver is a player and the spell has a PvP duration. Return the pvp duration.
		local duration = lib.auraInfoPvP[spellID]
		
		if dstGUID and duration then
			--Check if there's dimminshing returns on the spell.
			duration = self:GetDRDuration(dstGUID, spellID, duration)
		end
				
		return tonumber(duration or 0)
	elseif self.auraInfo[spellID] then
		--Check caster GUID was given.
		if srcGUID then
			--Check if we've seen that caster cast a spell with a duration that doesn't match our own (spec/glphed into something?)
			if self.GUIDDurations[srcGUID.."-"..spellID] then
				local dur = self.GUIDDurations[srcGUID.."-"..spellID]
				
				--Check if receiver GUID was given.
				if dstGUID then
					--Check if there's dimminshing returns on the spell.
					dur = self:GetDRDuration(dstGUID, spellID, dur)
				end
				return dur
			end
		end
	
		local duration, debuffType = strsplit(";", self.auraInfo[spellID])
		return tonumber(duration or 0)
	end
end

--Return the debuff type of a spell.
function lib:GetDebuffType(spellID)
	if self.auraInfo[spellID] then
		local duration, debuffType = strsplit(";", self.auraInfo[spellID])
		if debuffType then
			debuffType = tonumber(debuffType)
			if debuffType == 0 then
				return "none" --Lowercase because DebuffTypeColor["none"] is lowercase.
			else
				return self.debuffTypes[debuffType] or "unknown"
			end
		end
		return "none"--Lowercase because DebuffTypeColor["none"] is lowercase.
	end
	return nil
end

function lib:GetNumGUIDAuras(dstGUID)
	self:RemoveExpiredAuras(dstGUID)
	
	if self.GUIDAuras[dstGUID] then
		return #self.GUIDAuras[dstGUID]
	end

	return 0
end

function lib:GUIDAura(dstGUID, i)
	if self.GUIDAuras[dstGUID] and self.GUIDAuras[dstGUID][i] then
		local data = self.GUIDAuras[dstGUID][i]
		return true, data.count or 0, data.debuffType, data.duration, data.expirationTime, data.isDebuff, data.srcGUID
	end
	return false
end


function lib:GUIDAuraID(dstGUID, spellID, srcGUID)
	if self.GUIDAuras[dstGUID] then
		local data
		if srcGUID then
			for i=1, #self.GUIDAuras[dstGUID] do 
				data = self.GUIDAuras[dstGUID][i]
				if data.spellID == spellID and data.srcGUID == srcGUID then
					return true, data.count or 0, data.debuffType, data.duration, data.expirationTime, data.isDebuff, data.srcGUID
				end
			end
		end
		for i=1, #self.GUIDAuras[dstGUID] do 
			data = self.GUIDAuras[dstGUID][i]
			if data.spellID == spellID then
				return true, data.count or 0, data.debuffType, data.duration, data.expirationTime, data.isDebuff, data.srcGUID
			end
		end
	end
	return false
end
--[[
old function no longer used.
function lib:GetGUIDAuras(GUID)
	if self.GUIDAuras[GUID] then
		return unpack(self.GUIDAuras[GUID])
	end
	return nil
end
]]

function lib:HasDREffect(dstGUID, spellID)
	local drType = self.drSpells[spellID]
	if drType then
		local key = dstGUID..drType
		local reset = self.GUIDDrEffects_reset[key]
		if reset and GetTime() < reset then
			return true, (self.GUIDDrEffects_diminished[key] or 1)
		end
	end
	return false
end

function lib:GetGUIDInfo(GUID)
	return self.GUIDData_name[GUID], self.GUIDData_flags[GUID]
end

