Quantcast
local addon = LibStub("AceAddon-3.0"):NewAddon("SpellAlerter","AceEvent-3.0","AceConsole-3.0")
local L, SM = LibStub("AceLocale-3.0"):GetLocale("SpellAlerter", true), LibStub("LibSharedMedia-3.0")
local LDB = LibStub("LibDataBroker-1.1",true)
local LDBIcon = LibStub("LibDBIcon-1.0",true)

_G.SpellAlerter = addon

do
	local function AddSound(soundName,soundFile) SM:Register("Sound",soundName,soundFile) end
	AddSound("Bell Toll Alliance", "Sound\\Doodad\\BellTollAlliance.wav")
	AddSound("Bell Toll Horde", "Sound\\Doodad\\BellTollHorde.wav")
	AddSound("Rubber Ducky", "Sound\\Doodad\\Goblin_Lottery_Open01.wav")
	AddSound("Cartoon FX", "Sound\\Doodad\\Goblin_Lottery_Open03.wav")
	AddSound("Explosion", "Sound\\Doodad\\Hellfire_Raid_FX_Explosion05.wav")
	AddSound("Shing!", "Sound\\Doodad\\PortcullisActive_Closed.wav")
	AddSound("Wham!", "Sound\\Doodad\\PVP_Lordaeron_Door_Open.wav")
	AddSound("Simon Chime", "Sound\\Doodad\\SimonGame_LargeBlueTree.wav")
	AddSound("War Drums", "Sound\\Event Sounds\\Event_wardrum_ogre.wav")
	AddSound("Cheer", "Sound\\Event Sounds\\OgreEventCheerUnique.wav")
	AddSound("Humm", "Sound\\Spells\\SimonGame_Visual_GameStart.wav")
	AddSound("Short Circuit", "Sound\\Spells\\SimonGame_Visual_BadPress.wav")
	AddSound("Fel Portal", "Sound\\Spells\\Sunwell_Fel_PortalStand.wav")
	AddSound("Fel Nova", "Sound\\Spells\\SeepingGaseous_Fel_Nova.wav")
	AddSound("You Will Die!", "Sound\\Creature\\CThun\\CThunYouWillDIe.wav")
end

local alert
local gbl,pfl
local SpellCasts, EnemyBuffs, FriendlyDebuffs, Ignores
local PGUID,PNAME

local SPELLCASTS = "spellcasts"
local ENEMYBUFFS = "enemybuffs"
local FRIENDLYDEBUFFS = "friendlydebuffs"

local ListSelect, ListSelect2

local GetSpellInfo = GetSpellInfo
local UnitExists = UnitExists
local UnitGUID = UnitGUID
local UnitName = UnitName
local UnitGUID = UnitGUID
local UnitClass = UnitClass
local PlaySoundFile = PlaySoundFile
local pairs = pairs
local ipairs = ipairs
local unpack = unpack
local select = select
local match = string.match
local wipe = table.wipe
local UNKNOWN = "UNKNOWN"

local ClassColors = {}
local function UpdateClassHexes()
	for class,color in pairs(CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS) do
		ClassColors[class] = ("|cff%02x%02x%02x"):format(color.r * 255, color.g * 255, color.b * 255)
	end
end
UpdateClassHexes()
if CUSTOM_CLASS_COLORS then CUSTOM_CLASS_COLORS:RegisterCallback(UpdateClassHexes) end

local ColorName = setmetatable({}, {__index =
	function(t, unit)
		local class = select(2,UnitClass(unit))
		if not class then return false end
		local name = UnitName(unit)
		local prev = rawget(t,name)
		if prev then return prev end
		t[name] = ClassColors[class]..name.."|r"
		return t[name]
	end,
})

local Icons = setmetatable({}, {
	__index = function(t,spellid)
		local icon = select(3,GetSpellInfo(spellid))
		t[spellid] = icon
		return icon
	end,
})

local Defaults = {
	global = {
		Enabled = true,
		Lock = true,
		Minimap = {},
	},
	profile = {
		Positions = { SpellAlerterFrameAnchor =  {"CENTER",nil,"CENTER",0,190} },
		CategorySelect = "SpellCasts",
		ShowIcon = true,
		ShowCaster = true,
		SpellNames = false,
		ClassColored = true,
		TargetGraphic = "arrow",
		HoldTime = 1,
		IconSize = 40,
		Font = SM:GetDefault("font"),
		FontSize = 40,
		YouText = "YOU",
		ShowTarget = true,
		TargetOnly = false,
		Filters = {
			["*"] = {
				Players = true,
				NPCs = true,
				TargetIsSelf = false,
			},
		},
	},
}

local CategoryValues = {
	SpellCasts = L["Spell Casts"],
	EnemyBuffs = L["Enemy Buffs"],
	FriendlyDebuffs = L["Friendly Debuffs"]
}

local colors = {
	YELLOW = {1,1,0},
	PINK = {1,0,1},
	GREEN = {0,1,0},
	RED = {1,0,0},
	WHITE = {1,1,1},
	VIOLET = {0.55,0,1},
	TAN = {0.82,0.71,0.55},
	TEAL = {0,0.5,0.5},
	TURQUOISE = {0.19,0.84,0.78},
	PEACH = {1,0.9,0.71},
	INDIGO = {0,0.25,0.71},
	GREY = {0.5,0.5,0.5},
	AQUA = {0,1,1},
	ORANGE = {1,0.65,0},
	BLUE = {0,0,1},
}

local colorHexes = {
	YELLOW =	   "|cffffff00"..L["Yellow"].."|r",
	PINK = 		"|cffff00ff"..L["Pink"].."|r",
	GREEN = 		"|cff00ff00"..L["Green"].."|r",
	RED = 		"|cffff0000"..L["Red"].."|r",
	WHITE = 		"|cffffffff"..L["White"].."|r",
	VIOLET = 	"|cff8b00ff"..L["Violet"].."|r",
	TAN = 		"|cffd2b48c"..L["Tan"].."|r",
	TEAL = 		"|cff008080"..L["Teal"].."|r",
	TURQUOISE = "|cff30d5c8"..L["Turquoise"].."|r",
	PEACH = 	   "|cffffe5b4"..L["Peach"].."|r",
	INDIGO =    "|cff00416a"..L["Indigo"].."|r",
	GREY =      "|cff808080"..L["Grey"].."|r",
	AQUA =      "|cff00ffff"..L["Aqua"].."|r",
	ORANGE =    "|cffffa500"..L["Orange"].."|r",
	BLUE =      "|cff0000ff"..L["Blue"].."|r",
}

local function AddSpellInfo(list,keyiskey)
	local temp = {}
	for spellid,data in pairs(list) do
		local spellName = GetSpellInfo(spellid)
		if spellName then temp[spellName] = keyiskey and spellName or data end
	end
	wipe(list)
	for spellName,data in pairs(temp) do list[spellName] = data end
end

function addon:AddDefaultSpells()
	if not pfl.Ignores then
		pfl.Ignores =  {
		-- Heals
			-------------------------------------
				[26983] = true, -- Tranquility

		-- CC
			-------------------------------------
				-- Hunter
				[14311] = true, -- Freezing Trap
				[13809] = true, -- Frost Trap
				[34600] = true, -- Snake Trap
				-- Warlock
				[17928] = true, -- Howl of Terror
				-- Warrior
				[23920] = true, -- Spell Reflection

		-- Destroyable
			-------------------------------------
				[16190] = true, -- Mana Tide Totem
				[8177] = true,  -- Grounding Totem
				[61657] = true, -- Fire Nova Totem

		-- Damage
			-------------------------------------

		-- Ressurects/Summons
			-------------------------------------
			-- Hunter
				[982] = true,   -- Revive Pet
			-- Warlock
				[712] = true,   -- Summon Succubus
				[30146] = true, -- Summon Felguard
				[691] = true,   -- Summon Felhunter
				[697] = true,   -- Summon Voidwalker
		}
		AddSpellInfo(pfl.Ignores,true)
	end

	if not pfl.SpellCasts then
		pfl.SpellCasts =  {
			-- Heals
			-------------------------------------
				-- Priests
				[25235] = {Color = "YELLOW"}, -- Flash Heal
				[25213] = {Color = "YELLOW"}, -- Greater Heal
				[32546] = {Color = "YELLOW"}, -- Binding Heal
				-- Druid
				[26979] = {Color = "YELLOW"}, -- Healing Touch
				[26980] = {Color = "YELLOW"}, -- Regrowth
				[26983] = {Color = "YELLOW"}, -- Tranquility
				-- Shaman
				[25396] = {Color = "YELLOW"}, -- Healing Wave
				[25420] = {Color = "YELLOW"}, -- Lesser Healing Wave
				-- Paladin
				[27137] = {Color = "YELLOW"}, -- Flash of Light
				[27136] = {Color = "YELLOW"}, -- Holy Light
			-- CC
			-------------------------------------
				-- Priest
				[605] =   {Color = "PINK"},   -- Mind Control
				-- Druid
				[26989] = {Color = "PINK"},   -- Entangling Roots
				[33786] = {Color = "PINK"},   -- Cyclone
				[18658] = {Color = "PINK"}, 	-- Hibernate
				-- Hunter
				[14311] = {Color = "PINK"},   -- Freezing Trap
				[13809] = {Color = "PINK"},   -- Frost Trap
				[34600] = {Color = "PINK"},   -- Snake Trap
				-- Mage
				[12826] = {Color = "PINK"},   -- Polymorph
				-- Warlock
				[6215] =  {Color = "PINK"},   -- Fear
				[17928] = {Color = "PINK"},   -- Howl of Terror
				-- Warrior
				[23920] = {Color = "PINK"},   -- Spell Reflection
				-- Shaman
				[51514] = {Color = "PINK"},   -- Hex

			-- Destroyable
			-------------------------------------
				[16190] = {Color = "GREEN"},  -- Mana Tide Totem
				[8177] =  {Color = "GREEN"},  -- Grounding Totem
				[61657] = {Color = "GREEN"},  -- Fire Nova Totem

			-- Damage
			-------------------------------------
				-- Priest
				[8129]  = {Color = "RED"}, 	-- Mana Burn
				[48123] = {Color = "RED"},    -- Smite
				[48135] = {Color = "RED"},    -- Holy Fire
				[48127] = {Color = "RED"},    -- Mind Blast
				[48156] = {Color = "RED"},    -- Mind Flay
				[48160] = {Color = "RED"},    -- Vampiric Touch
				-- Mage
				[38697] = {Color = "RED"},    -- Frostbolt
				[38692] = {Color = "RED"},    -- Fireball
				[33938] = {Color = "RED"},    -- Pyroblast
				-- Shaman
				[25449] = {Color = "RED"},    -- Lightning Bolt
				[25442] = {Color = "RED"},    -- Chain Lightning
				[60043] = {Color = "RED"},    -- Lava Burst
				-- Warlock
				[47810] = {Color = "RED"},    -- Immolate
				[27209] = {Color = "RED"},    -- Shadow Bolt
				[30545] = {Color = "RED"},    -- Soul Fire
				[59164] = {Color = "RED"},    -- Haunt
				[47843] = {Color = "RED"},    -- Unstable Affliction
				[59172] = {Color = "RED"},    -- Chaos Bolt
		-- Ressurects/Summons
		-------------------------------------
			-- Druid
				[50763] = {Color = "GREY"},   -- Revive
			-- Paladin
				[48950] = {Color = "GREY"},   -- Redemption
			-- Priest
				[48171] = {Color = "GREY"},   -- Resurrect
			-- Hunter
				[982] =   {Color = "GREY"},   -- Revive Pet
			-- Warlock
				[712] =   {Color = "GREY"},   -- Summon Succubus
				[30146] = {Color = "GREY"},   -- Summon Felguard
				[691] =   {Color = "GREY"},   -- Summon Felhunter
				[697] =   {Color = "GREY"},   -- Summon Voidwalker
			-- Shaman
				[49277] = {Color = "GREY"},   -- Ancestral Spirit
		}
		AddSpellInfo(pfl.SpellCasts)
	end
	if not pfl.EnemyBuffs then
		pfl.EnemyBuffs = {
			-- Clearable
			-------------------------------------
				-- Druid
				[29166] = {Color = "PEACH"},  -- Innervate
				[17116] = {Color = "PEACH"},  -- Nature's Swiftness
				-- Shaman
				[32594] = {Color = "PEACH"},  -- Earth Shield
				-- Paladin
				[1044] =  {Color = "VIOLET"}, -- Hand of Freedom
				[10278] = {Color = "VIOLET"}, -- Hand of Protection
				[6940] =  {Color = "VIOLET"}, -- Hand of Sacrifice
				-- Priest
				[10060] = {Color = "PEACH"},  -- Power Infusion
				-- Mage
				[12051] = {Color = "PEACH"},  -- Evocation
				-- Warlock
				[18708] = {Color = "PEACH"},  -- Fel Domination
				-- Paladin
				[53654] = {Color = "PEACH"},  -- Beacon of Light
		}
		AddSpellInfo(pfl.EnemyBuffs)
	end
	if not pfl.FriendlyDebuffs then
		pfl.FriendlyDebuffs = {
			-- Clearable
			-------------------------------------
				[3034] = {Color = "AQUA"},    -- Viper Sting
		}
		AddSpellInfo(pfl.FriendlyDebuffs)
	end
end

function addon:ProfileChanged()
	pfl = self.db.profile
	self:AddDefaultSpells()
	ListSelect,ListSelect2 = nil,nil
	SpellCasts = pfl.SpellCasts
	FriendlyDebuffs = pfl.FriendlyDebuffs
	EnemyBuffs = pfl.EnemyBuffs
	Ignores = pfl.Ignores
end

function addon:OpenConfig()
	if not addon.options then
		addon.options = addon:GetOptions()
		addon.options.args.profile = LibStub("AceDBOptions-3.0"):GetOptionsTable(addon.db)
		addon.options.args.profile.order = 500
		LibStub("AceConfig-3.0"):RegisterOptionsTable("SpellAlerter", addon.options)
		addon.GetOptions = nil
	end
	LibStub("AceConfigDialog-3.0"):Open("SpellAlerter")
end

function addon:OnInitialize()
	self.frame = CreateFrame("Frame","SpellAlerterFrame",UIParent)
	self.db = LibStub("AceDB-3.0"):New("SpellAlerterDB",Defaults,"Default")
	gbl = self.db.global
	self:ProfileChanged()

	self:RegisterChatCommand("sa",self.OpenConfig)
	self:RegisterChatCommand("spellalerter",self.OpenConfig)

	self.db.RegisterCallback(self, "OnProfileChanged", "ProfileChanged")
	self.db.RegisterCallback(self, "OnProfileCopied", "ProfileChanged")
	self.db.RegisterCallback(self, "OnProfileReset", "ProfileChanged")

	if LDB then
		self.Launcher = LDB:NewDataObject("SpellAlerter",
		{
			type = "launcher",
			icon = "Interface\\Icons\\Spell_Fire_Flare",
			OnClick = function(_, button)
				addon.OpenConfig()
			end,
			OnTooltipShow = function(tooltip)
				tooltip:AddLine("Spell Alerter")
				tooltip:AddLine(L["|cff99ff33Click|r to open the config"])
			end,
		})
		if LDBIcon then LDBIcon:Register("SpellAlerter",self.Launcher,self.db.global.Minimap) end
	end

	-- ChatFrame1:AddMessage("Spell Alerter by Kollektiv. Type /sa or /spellalerter to open options",0,1,0)
	self:SetEnabledState(self.db.global.Enabled)
end

function addon:OnEnable()
	self.frame:Show()
	alert = alert or self:CreateAlert()
	self:ApplySettings()
	self:ApplyLock()
	self:LoadPositions()
	self:UpdateFilters()
	self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
	PGUID = PGUID or UnitGUID("player")
	PNAME = UnitName("player")
end

function addon:OnDisable()
	if LDBIcon then LDBIcon:Hide("SpellAlerter") end
	self.frame:Hide()
end

function addon:SavePosition(anchor)
	pfl.Positions[anchor:GetName()] = {anchor:GetPoint()}
end

function addon:LoadPositions()
	for name,pos in pairs(pfl.Positions) do
		local f = _G[name]
		if f then f:ClearAllPoints() f:SetPoint(unpack(pos)) end
	end
end

function addon:GetAnchor()
	local anchor = CreateFrame("Frame",self.frame:GetName().."Anchor",self.frame)
	anchor:SetWidth(100)
	anchor:SetHeight(15)
	anchor.bg = anchor:CreateTexture(nil,"BACKGROUND")
	anchor.bg:SetTexture(SM:Fetch("background","Solid"))
	anchor.bg:SetAllPoints(true)
	anchor.bg:SetVertexColor(0,0,0,0.33)
	anchor.text = anchor:CreateFontString(nil,"ARTWORK")
	anchor.text:SetFont(SM:Fetch("font"),8)
	anchor.text:SetText("Spell Alerter Anchor")
	anchor.text:SetPoint("CENTER")
	anchor:SetMovable(true)
	anchor:EnableMouse(true)
	anchor:SetScript("OnMouseDown",function(self) self:StartMoving() end)
	anchor:SetScript("OnMouseUp",function(self) self:StopMovingOrSizing() addon:SavePosition(self) end)
	return anchor
end

function addon:CreateAlert()
	alert = CreateFrame("Frame","SpellAlerterFrameAlert",self.frame)
	alert.anchor = self:GetAnchor()
	alert:SetWidth(1); alert:SetHeight(1)
	alert:SetPoint("CENTER",alert.anchor,"CENTER")
	alert:SetFrameLevel(alert.anchor:GetFrameLevel()+1)
	alert.text = alert:CreateFontString(nil,"ARTWORK")
	alert.text2 = alert:CreateFontString(nil,"ARTWORK")

    -- DSKNRPTCH1
    alert.text:SetShadowColor(0, 0, 0)
    alert.text2:SetShadowColor(0, 0, 0)
    alert.text:SetShadowOffset(1, -1)
    alert.text2:SetShadowOffset(1, -1)

	alert.icon = alert:CreateTexture(nil,"ARTWORK")
	alert.icon:SetTexCoord(0.07,0.93,0.07,0.93)
    -- DSKNRPTCH3
	alert.icon:SetPoint("RIGHT",alert.text,"LEFT", -8, 1)
    alert.icon:SetAlpha(0.8)
	alert.arrow = alert:CreateTexture(nil,"ARTWORK")
	alert.arrow:SetTexture("Interface\\Addons\\SpellAlerter\\Arrow")
	alert.arrow:SetHeight(42)
	alert.arrow:SetWidth(48)
	alert:Hide()
	alert:SetScript("OnUpdate",function(self,elapsed)
		self.elapsed = self.elapsed + elapsed
		if self.elapsed <= 0.1 then
			self:SetAlpha(1-((0.1-self.elapsed)/0.1))
		elseif self.elapsed <= 0.1 + pfl.HoldTime then
			self:SetAlpha(1)
		elseif self.elapsed <= 0.2 + pfl.HoldTime then
			self:SetAlpha((pfl.HoldTime + 0.2 - self.elapsed)/0.1)
		else
			self:SetAlpha(0)
			self:Hide()
		end
	end)
	return alert
end

function addon:ApplyLock()
	if self.db.global.Lock then
		alert.anchor:Hide()
	else
		alert.anchor:Show()
	end
end

function addon:ApplyMinimap()
	if LDBIcon then LDBIcon[self.db.global.Minimap.hide and "Hide" or "Show"](LDBIcon, "SpellAlerter") end
end

local function FixPoints(self,dstName,same)
	alert.text:ClearAllPoints()
	alert.text2:ClearAllPoints()
	alert.arrow:ClearAllPoints()
	if same then
		alert.text:SetPoint("CENTER",alert.anchor,"CENTER")
		alert.arrow:SetPoint("LEFT",alert.text,"RIGHT")
		alert.text2:Hide()
		alert.arrow:SetTexCoord(0.01,(512/9.1)/512,((512/12)*2.9)/512,((512/12)*3.9)/512)
		alert.arrow:Show()
	elseif dstName then
		alert.arrow:SetPoint("CENTER",alert.anchor,"CENTER")
		alert.text:SetPoint("RIGHT",alert.arrow,"LEFT", -6)
		alert.text2:SetPoint("LEFT",alert.arrow,"RIGHT", 6)
		alert.arrow:SetTexCoord(0,0.109375,0.73828125,0.8203125)
		alert.arrow:Show()
		alert.text2:Show()
	else
		alert.text:SetPoint("CENTER",alert.anchor,"CENTER")
		alert.arrow:Hide()
		alert.text2:Hide()
	end
end


function addon:ApplySettings()
    -- DSKNRPTCH2
	--alert.text:SetFont(SM:Fetch("font",pfl.Font),pfl.FontSize,"THICKOUTLINE")
	--alert.text2:SetFont(SM:Fetch("font",pfl.Font),pfl.FontSize,"THICKOUTLINE")
	alert.text:SetFont(SM:Fetch("font",pfl.Font),pfl.FontSize,"OUTLINE")
	alert.text2:SetFont(SM:Fetch("font",pfl.Font),pfl.FontSize,"OUTLINE")
	alert.icon:SetWidth(pfl.IconSize)
	alert.icon:SetHeight(pfl.IconSize)

	alert.text:ClearAllPoints()
	alert.text:SetPoint("CENTER",alert.anchor,"CENTER")
	alert.text2:Hide()
	alert.arrow:Hide()
	if pfl.ShowIcon then alert.icon:Show() else alert.icon:Hide() end
	if pfl.ShowTarget and pfl.TargetGraphic == "arrow" then
		alert.FixPoints = FixPoints
	else
		alert.FixPoints = nil
	end
end


do
	local unitlist = {}
	local unit_to_unittarget = {}
	local levels = 3
	local END = ""

	local function addids(uid,lower,upper)
		if lower and upper then
			for i=lower,upper do
				unitlist[#unitlist+1] = uid..i
			end
		else
			unitlist[#unitlist+1] = uid
		end
	end

	addids("target")
	addids("arena",1,5)
	addids("focus")
	addids("party",1,4)
	addids("pettarget")
	addids("partypet",1,4)
	addids("raid",1,40)

	local function reset()
		wipe(unit_to_unittarget)
		for k,v in ipairs(unitlist) do unit_to_unittarget[k] = v end
	end

	reset()

	local targetof = setmetatable({},{
		__index = function(t,k)
			if type(k) ~= "string" then return end
			t[k] = k.."target"
			return t[k]
		end
	})

	function addon:FindTargetInfo(srcGUID)
		for i=1,levels do
			for k,id in ipairs(unit_to_unittarget) do
				if id ~= END and UnitExists(id) then
					local nextid = targetof[id]
					if UnitGUID(id) == srcGUID then
						reset()
						return id,nextid,UnitExists(nextid)
					else
						unit_to_unittarget[k] = nextid
					end
				else
					unit_to_unittarget[k] = END
				end
			end
		end
		reset()
	end
end

function addon:FormatInfo(srcName,srcGUID,spellName,icon,sound,cat,color,forcedDst)
	local srcUnit,dstUnit,dstExists = self:FindTargetInfo(srcGUID)
	local valid = cat == SPELLCASTS and not Ignores[spellName] and dstExists
	local dstName
	if valid then dstName = PGUID == UnitGUID(dstUnit) and pfl.YouText or UnitName(dstUnit) end
	if forcedDst then dstName = forcedDst end


	local targetself = pfl.Filters[cat].TargetIsSelf
	if cat == SPELLCASTS and targetself and dstName ~= PNAME then return
	elseif cat == FRIENDLYDEBUFFS and targetself and srcName ~= PNAME then return end

	if sound then PlaySoundFile(SM:Fetch("sound",sound)) end
	local r,g,b = unpack(color)

	local same = srcName == dstName

	if pfl.ClassColored then
		if srcUnit then srcName = ColorName[srcUnit] or srcName end
		if valid then dstName = ColorName[dstUnit] or dstName end
	end

	if not pfl.ShowCaster and cat == SPELLCASTS then
		srcName = ""
	end

	if pfl.SpellNames then
		srcName = srcName ~= "" and srcName .. " - "..spellName or spellName
	end

	if pfl.ShowTarget and pfl.TargetGraphic == "text" then
		if same then
			alert.text:SetText(srcName.." <")
		else
			alert.text:SetText((dstExists and dstName) and srcName.." > "..dstName or srcName)
		end
	else
		alert.text:SetText(srcName)
	end

	alert.text2:SetText(dstName)
	alert.icon:SetTexture(icon)
	alert.text:SetVertexColor(r,g,b)
	alert.text2:SetVertexColor(r,g,b)
	alert.arrow:SetVertexColor(r,g,b)
	if alert.FixPoints then alert:FixPoints(dstName,same) end
	alert.elapsed = 0
	alert:Show()
end

local COMBATLOG_TARGET	= COMBATLOG_OBJECT_TARGET
local COMBATLOG_FRIENDLY = COMBATLOG_OBJECT_REACTION_FRIENDLY
local COMBATLOG_HOSTILE = COMBATLOG_OBJECT_REACTION_HOSTILE
local COMBATLOG_PLAYER = COMBATLOG_OBJECT_TYPE_PLAYER

local SpellCastEvents = {
	SPELL_CAST_START = 1,
	SPELL_CAST_SUCCESS = 1,
	SPELL_CREATE = 1,
}

local StrippedName = setmetatable({},{
	__index = function(t,k)
		if not k then return end
		local stripped = match(k,"[^-]*")
		t[k] = stripped
		return t[k]
	end,
})

local SPELLCASTS_FILTER
local ENEMYBUFFS_FILTER
local FRIENDLYDEBUFFS_FILTER
local bor = bit.bor
function addon:UpdateFilters()
	local Filters = pfl.Filters
	SPELLCASTS_FILTER = bor(
		Filters.SpellCasts.Players and COMBATLOG_OBJECT_TYPE_PLAYER or 0,
		Filters.SpellCasts.NPCs and COMBATLOG_OBJECT_TYPE_NPC or 0
	)
	ENEMYBUFFS_FILTER = bor(
		Filters.EnemyBuffs.Players and COMBATLOG_OBJECT_TYPE_PLAYER or 0,
		Filters.EnemyBuffs.NPCs and COMBATLOG_OBJECT_TYPE_NPC or 0
	)
	FRIENDLYDEBUFFS_FILTER = bor(
		Filters.FriendlyDebuffs.Players and COMBATLOG_OBJECT_TYPE_PLAYER or 0,
		Filters.FriendlyDebuffs.NPCs and COMBATLOG_OBJECT_TYPE_NPC or 0
	)
end

local band = bit.band
function addon:COMBAT_LOG_EVENT_UNFILTERED(_, _, eventtype, _, srcGUID, srcName, srcFlags, dstGUID, dstName, dstFlags, spellID, spellName, _, auraType)
	if not spellID then return end
	if SpellCastEvents[eventtype] and band(srcFlags, COMBATLOG_HOSTILE) == COMBATLOG_HOSTILE and SpellCasts[spellName] then
		if pfl.TargetOnly and band(srcFlags) ~= COMBATLOG_TARGET then return end
		if band(SPELLCASTS_FILTER,srcFlags) == 0 then return end
		self:FormatInfo(StrippedName[srcName],srcGUID,spellName,Icons[spellID],SpellCasts[spellName].Sound,SPELLCASTS,colors[SpellCasts[spellName].Color])
	elseif eventtype == "SPELL_AURA_APPLIED" then
		if pfl.TargetOnly and band(dstFlags) ~= COMBATLOG_TARGET then return end
		if auraType == "BUFF" and band(dstFlags,COMBATLOG_HOSTILE) == COMBATLOG_HOSTILE and EnemyBuffs[spellName] and band(ENEMYBUFFS_FILTER,dstFlags) > 0 then
			self:FormatInfo(StrippedName[dstName],dstGUID,spellName,Icons[spellID],EnemyBuffs[spellName].Sound,ENEMYBUFFS,colors[EnemyBuffs[spellName].Color])
		elseif auraType == "DEBUFF" and band(dstFlags,COMBATLOG_FRIENDLY) == COMBATLOG_FRIENDLY and FriendlyDebuffs[spellName] and band(FRIENDLYDEBUFFS_FILTER) > 0 then
			self:FormatInfo(StrippedName[dstName],dstGUID,spellName,Icons[spellID],FriendlyDebuffs[spellName].Sound,FRIENDLYDEBUFFS,colors[FriendlyDebuffs[spellName].Color])
		end
	end
end

local function IsDisabled()
	return not addon.db.global.Enabled
end

function addon:GetOptions()
	return {
		type = "group",
		name = L["Spell Alerter"],
		order = 100,
		args = {
			About = {
				type = "group",
				name = L["About"],
				order = -1,
				args = {
					author = {
						type = "description",
						name = "Author: |cffffd200Kollektiv|r\
\
Website: |cffffd200http://wow.curse.com/downloads/wow-addons/details/spell-alerter.aspx|r",
						order = 100,
					},
				},
			},
			Enabled = {
				type = "toggle",
				name = L["Enabled"],
				order = 25,
				get = function() return self.db.global.Enabled end,
				set = function(info,value) self.db.global.Enabled = value; self:SetEnabledState(value); if value then self:Enable() else self:Disable() end end,
				width = "half",
			},
			Lock = {
				type = "toggle",
				name = L["Lock"],
				set = function(info,value) self.db.global.Lock = value; self:ApplyLock() end,
				get = function(info) return self.db.global.Lock end,
				order = 55,
				disabled = IsDisabled,
				width = "half",
			},
			ShowMinimap = {
				type = "toggle",
				name = L["Minimap"],
				set = function(info,value) self.db.global.Minimap.hide = not value; self:ApplyMinimap() end,
				get = function(info) return not self.db.global.Minimap.hide end,
				order = 56,
				disabled = IsDisabled,
				width = "half",
			},
			Test = {
				type = "execute",
				name = L["Test"],
				order = 60,
				func = function() self:FormatInfo(UnitName("player"),UnitGUID("player"),"Earth Shield","Interface\\Icons\\Spell_Nature_SkinofEarth","Bell Toll Alliance",SPELLCASTS,colors.YELLOW,"Kollektor") end,
				width = "half",
				disabled = IsDisabled,
			},
			Version = {
				type = "description",
				name = "|cff00ff00"..L["Version"].."|r: "..GetAddOnMetadata("SpellAlerter","Version"),
				order = 80,
			},

			Settings_Group = {
				type = "group",
				name = L["Settings"],
				order = 100,
				get = function(info) return pfl[info[#info]] end,
				set = function(info,value) pfl[info[#info]] = value; self:ApplySettings() end,
				disabled = IsDisabled,
				args = {
					Toggles_Group = {
						type = "group",
						name = L["Toggles"],
						order = 100,
						inline = true,
						args = {
							General_Group = {
								type = "group",
								name = L["General"],
								order = 100,
								args = {
									ShowIcon = {
										type = "toggle",
										name = L["Show icon"],
										order = 200,
									},
									ShowCaster = {
										type = "toggle",
										name = L["Show caster"],
										order = 250,
									},
									SpellNames = {
										type = "toggle",
										name = L["Show spell names"],
										order = 300,
									},
									TargetOnly = {
										type = "toggle",
										name = L["Targeted unit only"],
										desc = L["Show alerts only on your targeted unit"],
										order = 350,
									},
									ClassColored = {
										type = "toggle",
										name = L["Class colored"],
										desc = L["Is not 100% reliable outside of arena. Overrides colors in Spell List"],
										order = 400,
									},
								},
							},
							Filters_Group = {
								type = "group",
								name = L["Filters"],
								order = 150,
								inline = true,
								get = function(info) return pfl.Filters[info[#info-1]][info[#info]] end,
								set = function(info,v) pfl.Filters[info[#info-1]][info[#info]] = v; addon:UpdateFilters() end,
								args = {
									SpellCasts = {
										type = "group",
										name = L["Spell Casts"],
										order = 100,
										args = {
											Players = {
												type = "toggle",
												name = L["Players"],
												order = 100,
											},
											NPCs = {
												type = "toggle",
												name = L["NPCs"],
												order = 200,
											},
											TargetIsSelf = {
												type = "toggle",
												name = L["Target is YOU"],
												desc = L["Show alerts only if the spell is going to affect you. Not 100% reliable outside of arena"],
												order = 300,
											},
										},
									},
									EnemyBuffs = {
										type = "group",
										name = L["Enemy Buffs"],
										order = 200,
										args = {
											Players = {
												type = "toggle",
												name = L["Players"],
												order = 100,
											},
											NPCs = {
												type = "toggle",
												name = L["NPCs"],
												order = 200,
											},
										},
									},
									FriendlyDebuffs = {
										type = "group",
										name = L["Friendly Debuffs"],
										order = 300,
										args = {
											Players = {
												type = "toggle",
												name = L["Players"],
												order = 100,
											},
											NPCs = {
												type = "toggle",
												name = L["NPCs"],
												order = 200,
											},
											TargetIsSelf = {
												type = "toggle",
												name = L["Target is YOU"],
												desc = L["Show alerts only if the spell is going to affect you. Not 100% reliable outside of arena"],
												order = 300,
											},
										},
									},
								},
							},
						},
					},

					Sliders_Group = {
						type = "group",
						name = L["Sliders"],
						order = 200,
						inline = true,
						args = {
							HoldTime = {
								type = "range",
								name = L["Hold time"],
								order = 400,
								min = 0.5,
								max = 5,
								step = 0.1,
							},
							IconSize = {
								type = "range",
								name = L["Icon size"],
								order = 500,
								min = 10,
								max = 100,
								step = 1,
							},
							FontSize = {
								type = "range",
								name = L["Font size"],
								order = 600,
								min = 10,
								max = 40,
								step = 1,
							},
						},
					},
					LookAndFeel_Group = {
						type = "group",
						name = L["Look and feel"],
						order = 300,
						inline = true,
						args = {
							Font = {
								type = "select",
								name = L["Font"],
								order = 100,
								values = function()
									self.fonts = self.fonts or {}
									wipe(self.fonts)
									for _, name in pairs(SM:List("font")) do self.fonts[name] = name end
									return self.fonts
								end,
							},
							YouText = {
								type = "input",
								name = L["Replace player name with..."],
								order = 200,
							},
						},
					},
					ShowTarget = {
						type = "toggle",
						name = L["Show target"],
						order = 350,
					},
					Target_Group = {
						type = "group",
						name = L["Targets"],
						order = 400,
						disabled = function() return not pfl.ShowTarget or IsDisabled() end,
						inline = true,
						args = {
							TargetGraphic = {
								type = "select",
								name = L["Target graphic"],
								order = 900,
								values = {
									arrow = L["Arrow"],
									text = L["Text"],
								},
							},
						},
					},
				},
			},
			Spell_List_Group = {
				type = "group",
				name = L["Spell List"],
				order = 200,
				disabled = IsDisabled,
				args = {
					CategorySelect = {
						type = "select",
						name = L["Category"],
						order = 50,
						set = function(info,value) ListSelect = nil; pfl.CategorySelect = value end,
						get = function() return pfl.CategorySelect end,
						values = CategoryValues,
					},
					Category_Header = {
						type = "header",
						name = "",
						order = 60,
					},
					Add_Desc = {
						type = "description",
						name = L["Enter a spell name and select one. IMPORTANT: It is case sensitive."],
						order = 70,
					},
					Add_Editbox = {
						type = "input",
						name = L["Add"],
						get = function() return "" end,
						set = function(info,value)
							pfl[pfl.CategorySelect][value] = {Color = "RED"}
							ListSelect = value
						end,
						dialogControl = "Spell_EditBox",
						order = 120,
					},
					Blank1 = {
						type = "description",
						name = "",
						order = 130,
					},
					List_Select_Group = {
						type = "group",
						name = function() return CategoryValues[pfl.CategorySelect].." "..L["List"] end,
						order = 140,
						inline = true,
						args = {
							List_Select = {
								type = "select",
								name = "",
								order = 140,
								get = function() return ListSelect end,
								set = function(info,value) ListSelect = value end,
								values = function()
									self.listTemp = self.listTemp or {}
									wipe(self.listTemp)
									for k,v in pairs(pfl[pfl.CategorySelect]) do
										self.listTemp[k] = colorHexes[v.Color]:sub(1,10)..k.."|r"
									end
									return self.listTemp
								end,
								dialogControl = "SA_FauxScrollFrame",
							},
							Delete = {
								type = "execute",
								name = L["Delete"],
								order = 145,
								func = function()
									pfl[pfl.CategorySelect][ListSelect] = nil
									ListSelect = nil
								end,
								disabled = function() return not ListSelect or IsDisabled() end,
								width = "half",
							},
							Attributes_Group = {
								type = "group",
								name = function()
									if not ListSelect then
										return L["Attributes"]
									else
										return L["Attributes"] .." of "..ListSelect
									end
								end,
								order = 250,
								inline = true,
								set = function(info,value)
									if not ListSelect then return end
									pfl[pfl.CategorySelect][ListSelect][info[#info]] = value
								end,
								get = function(info)
									if not ListSelect then return end
									return pfl[pfl.CategorySelect][ListSelect][info[#info]]
								end,
								disabled = function() return not ListSelect or IsDisabled() end,
								args = {
									Color = {
										type = "select",
										name = L["Color"],
										order = 250,
										values = colorHexes,
									},
									Sound = {
										type = "select",
										name = L["Sound"],
										order = 300,
										values = function()
											self.sounds = self.sounds or {}
											wipe(self.sounds)
											for _, name in pairs(SM:List("sound")) do self.sounds[name] = name end
											return self.sounds
										end,
										dialogControl = "LSM30_Sound",
									},
								},
							},
						},
					},
				},
			},
			Target_Ignore_Group = {
				type = "group",
				name = L["Ignores"],
				order = 300,
				disabled = IsDisabled,
				args = {
					desc1 = {
						type = "description",
						name = L["These are Spell Casts that won't show targets.\n"],
						order = 100,
					},
					Add_Desc = {
						type = "description",
						name = L["Enter a spell name and select one. IMPORTANT: It is case sensitive."],
						order = 150,
					},
					Add_Editbox = {
						type = "input",
						name = L["Add"],
						get = function() return "" end,
						set = function(info,value)
							Ignores[value] = value
							ListSelect2 = value
						end,
						dialogControl = "Spell_EditBox",
						order = 175,
					},
					blank = { type = "description", name = "", order = 190},
					List_Select2 = {
						type = "select",
						name = "",
						order = 200,
						get = function() return ListSelect2 end,
						set = function(info,value) ListSelect2 = value end,
						values = function() return Ignores end,
						dialogControl = "SA_FauxScrollFrame",
					},
					Delete = {
						type = "execute",
						name = L["Delete"],
						order = 250,
						func = function()
							pfl.Ignores[ListSelect2] = nil
							ListSelect2 = nil
						end,
						disabled = function() return not ListSelect2 or IsDisabled() end,
						width = "half",
					},
				},
			},
		},
	}
end