local PA, ACL, ACH = unpack(_G.ProjectAzilroka)
local IF = PA:NewModule('iFilger', 'AceEvent-3.0', 'AceTimer-3.0')
local LSM = PA.Libs.LSM
PA.iFilger = IF

IF.Title = ACL['|cFF16C3F2i|r|cFFFFFFFFFilger|r']
IF.Description = ACL['Minimalistic Auras / Buffs / Procs / Cooldowns']
IF.Authors = 'Azilroka    Nils Ruesch    Ildyria'

IF.isEnabled = false

local _G = _G

_G.iFilger = IF

local CreateFrame = CreateFrame
local UIParent = UIParent

local floor = floor
local format = format
local ipairs = ipairs
local next = next
local select = select
local sort = sort
local strmatch = strmatch
local tinsert = tinsert
local tonumber = tonumber
local tostring = tostring
local type = type
local unpack = unpack
local wipe = wipe

local CopyTable = CopyTable
local GetTime = GetTime
local RegisterUnitWatch = RegisterUnitWatch

local GetContainerItemCooldown = C_Container.GetContainerItemCooldown
local GetContainerItemID = C_Container.GetContainerItemID
local GetContainerNumSlots = C_Container.GetContainerNumSlots
local GetItemIcon = C_Item.GetItemIcon
local GetItemInfo = C_Item.GetItemInfo

local GetSpellInfo = PA.GetSpellInfo

local GetFlyoutInfo = GetFlyoutInfo
local GetFlyoutSlotInfo = GetFlyoutSlotInfo
local GetInventoryItemCooldown = GetInventoryItemCooldown
local GetInventoryItemLink = GetInventoryItemLink
local GetItemCooldown = GetItemCooldown
local GetSpellLink = GetSpellLink
local IsSpellKnown = IsSpellKnown

local VISIBLE = 1
local HIDDEN = 0
local selectedSpell = ''
local selectedFilter = nil
local spellList = {}

IF.Cooldowns = {}
IF.ActiveCooldowns = {}
IF.DelayCooldowns = {}
IF.IsChargeCooldown = {}
IF.ItemCooldowns = {}
IF.HasCDDelay = {
	[5384] = true
}

local GLOBAL_COOLDOWN_TIME = 1.5
local COOLDOWN_MIN_DURATION = .1
local AURA_MIN_DURATION = .1

function IF:Spawn(unit, name, db, filter, position)
	local object = CreateFrame('Button', 'iFilger_'..name, PA.PetBattleFrameHider)
	object.db, object.name, object.unit, object.filter, object.Whitelist, object.Blacklist, object.createdIcons, object.anchoredIcons = db, name, unit, filter, db.Whitelist, db.Blacklist, 0, 0
	object:SetSize(100, 20)
	object:SetPoint(unpack(position))
	object:EnableMouse(false)
	IF:CreateMover(object)

	if name ~= 'Cooldowns' and name ~= 'ItemCooldowns' then
		object:SetAttribute('unit', unit)
		object:RegisterEvent('UNIT_AURA')
		object:SetScript('OnEvent', function() IF:UpdateAuras(object, unit) end)
		RegisterUnitWatch(object)

		if not db.Enable then
			IF:DisableUnit(object)
		end
	end

	return object
end

function IF:DisableUnit(button)
	button:Disable()
	button:UnregisterEvent('UNIT_AURA')

	for _, element in ipairs(button) do
		element:Hide()
	end

	if button:GetAttribute('unit') then
		UnregisterUnitWatch(button)
	end

	IF:ToggleMover(button)
end

function IF:EnableUnit(button)
	button:Enable()
	button:RegisterEvent('UNIT_AURA')

	if button:GetAttribute('unit') then
		RegisterUnitWatch(button)
	end

	IF:ToggleMover(button)
end

function IF:UpdateActiveCooldowns()
	local Panel = IF.Panels.Cooldowns

	for i = PA:CountTable(IF.ActiveCooldowns) + 1, #Panel do
		Panel[i]:Hide()
	end

	local Position = 0
	for SpellID in next, IF.ActiveCooldowns do
		local Name, _, Icon = GetSpellInfo(SpellID)

		if Name then
			Position = Position + 1
			local button = Panel[Position] or IF:CreateAuraIcon(Panel)

			local Start, Duration, CurrentDuration, Charges

			if IF.IsChargeCooldown[SpellID] then
				Charges, _, Start, Duration = GetSpellCharges(SpellID)
			else
				Start, Duration = GetSpellCooldown(SpellID)
			end

			CurrentDuration = (Start + Duration - GetTime())

			if Charges and Start == (((2^32)/1000) - Duration) then
				CurrentDuration = 0
			end

			button.duration = Duration
			button.spellID = SpellID
			button.spellName = Name

			button.Icon:SetTexture(Icon)
			button:SetShown(CurrentDuration and CurrentDuration >= COOLDOWN_MIN_DURATION)

			if (CurrentDuration and CurrentDuration >= COOLDOWN_MIN_DURATION) then
				if Panel.db.StatusBar then
					local timervalue, formatid = PA:GetTimeInfo(CurrentDuration, IF.db.Cooldown.threshold)
					local color = PA.TimeColors[formatid]

					button.StatusBar:SetValue(CurrentDuration / Duration)
					button.StatusBar.Time:SetFormattedText(PA.TimeFormats[formatid][1], timervalue)
					button.StatusBar.Time:SetTextColor(unpack(PA.TimeColors[formatid]))
					button.StatusBar.Time:SetTextColor(color.r, color.g, color.b)
					if Panel.db.FollowCooldownText and (formatid == 1 or formatid == 2) then
						button.StatusBar:SetStatusBarColor(color.r, color.g, color.b)
					end

					button.StatusBar.Name:SetText(Name)
				else
					button.Cooldown:SetCooldown(Start, Duration)
				end

				button.Cooldown:SetShown(not Panel.db.StatusBar)
				button.StatusBar:SetShown(Panel.db.StatusBar)
			else
				IF.ActiveCooldowns[SpellID] = nil
				button.CurrentDuration = 0
			end
		end
	end

	IF:SetPosition(Panel)
end

function IF:UpdateItemCooldowns()
	local Panel = IF.Panels.ItemCooldowns

	for i = PA:CountTable(IF.ItemCooldowns) + 1, #Panel do
		Panel[i]:Hide()
	end

	local Position = 0
	for itemID in next, IF.ItemCooldowns do
		local Name = GetItemInfo(itemID)

		if Name then
			Position = Position + 1
			local button = Panel[Position] or IF:CreateAuraIcon(Panel)

			local Start, Duration, CurrentDuration
			Start, Duration = GetItemCooldown(itemID)
			CurrentDuration = (Start + Duration - GetTime())

			button.duration = Duration
			button.itemID = itemID
			button.itemName = Name
			button.expiration = Start + Duration

			button.Icon:SetTexture(GetItemIcon(itemID))
			button:SetShown(CurrentDuration and CurrentDuration >= COOLDOWN_MIN_DURATION)

			if (CurrentDuration and CurrentDuration >= COOLDOWN_MIN_DURATION) then
				if Panel.db.StatusBar then
					local timervalue, formatid = PA:GetTimeInfo(CurrentDuration, IF.db.Cooldown.threshold)
					local color = PA.TimeColors[formatid]

					button.StatusBar:SetValue(CurrentDuration / Duration)
					button.StatusBar.Time:SetFormattedText(PA.TimeFormats[formatid][1], timervalue)
					button.StatusBar.Time:SetTextColor(unpack(PA.TimeColors[formatid]))
					button.StatusBar.Time:SetTextColor(color.r, color.g, color.b)
					if Panel.db.FollowCooldownText and (formatid == 1 or formatid == 2) then
						button.StatusBar:SetStatusBarColor(color.r, color.g, color.b)
					end

					button.StatusBar.Name:SetText(Name)
				else
					button.Cooldown:SetCooldown(Start, Duration)
				end

				button.Cooldown:SetShown(not Panel.db.StatusBar)
				button.StatusBar:SetShown(Panel.db.StatusBar)
			else
				IF.ItemCooldowns[itemID] = nil
				button.CurrentDuration = 0
			end
		end
	end

	IF:SetPosition(Panel)
end

function IF:UpdateDelayedCooldowns()
	for SpellID in next, IF.DelayCooldowns do
		local spellData, Start, Duration = PA.SpellBook.Complete[SpellID]

		do
			local cooldownInfo, chargeInfo = GetSpellCooldown(SpellID), GetSpellCharges(SpellID)

			if chargeInfo and (chargeInfo.currentCharges and chargeInfo.maxCharges > 1 and chargeInfo.currentCharges < chargeInfo.maxCharges) then
				Start, Duration = chargeInfo.cooldownStartTime, chargeInfo.cooldownDuration
			else
				Start, Duration = cooldownInfo.startTime, cooldownInfo.duration
			end
		end

		local CurrentDuration = (Start + Duration - GetTime())

		if CurrentDuration then
			if (CurrentDuration < IF.db.SuppressDuration) and (CurrentDuration > GLOBAL_COOLDOWN_TIME) then
				IF.DelayCooldowns[SpellID] = nil
				IF.ActiveCooldowns[SpellID] = Duration
			end
		else
			IF.DelayCooldowns[SpellID] = nil
		end
	end
end

function IF:CreateMover(frame)
	if PA.ElvUI then
		_G.ElvUI[1]:CreateMover(frame, frame:GetName()..'Mover', frame:GetName(), nil, nil, nil, 'ALL,iFilger', nil, 'ProjectAzilroka,iFilger,'..frame.name)
	elseif PA.Tukui then
		_G.Tukui[1]['Movers']:RegisterFrame(frame)
	end
end

function IF:ToggleMover(frame)
	if PA.ElvUI then
		if frame.db.Enable then
			_G.ElvUI[1]:EnableMover(frame.mover:GetName())
		else
			_G.ElvUI[1]:DisableMover(frame.mover:GetName())
		end
	end
end

function IF:CustomFilter(element, unit, button, name, auraData)
	if duration == 0 then
		return false
	end
	if element.db.FilterByList == 'Blacklist' then
		return not element.Blacklist[spellID]
	elseif element.db.FilterByList == 'Whitelist' then
		return element.Whitelist[spellID]
	elseif element.db.FilterByList == 'None' then
		if element.name == 'Procs' then
			if (caster == 'player' or caster == 'pet') then
				return not PA.SpellBook.Complete[spellID]
			end
		else
			local isPlayer = (caster == 'player' or caster == 'vehicle' or caster == 'pet')
			if (isPlayer or casterIsPlayer) and (duration ~= 0) then
				return true
			else
				return false
			end
		end
	end
end

function IF:UpdateAuraIcon(element, unit, index, offset, filter, isDebuff, visible)
	local auraData = PA:GetAuraData(unit, index, filter)

	if auraData then
		local position = visible + offset + 1
		local button = element[position] or IF:CreateAuraIcon(element, position)
		local show = IF:CustomFilter(element, unit, button, auraData)

		button.caster, button.filter, button.isDebuff, button.expiration, button.duration, button.spellID, button.isPlayer = auraData.sourceUnit, filter, auraData.isHarmful, auraData.expirationTime, auraData.duration, auraData.spellId, auraData.isFromPlayerOrPlayerPet

		button:SetShown(show)

		if show then
			if not element.db.StatusBar then
				if (auraData.duration and auraData.duration >= AURA_MIN_DURATION) then
					button.Cooldown:SetCooldown(auraData.expirationTime - auraData.duration, auraData.duration)
				end
				button.Cooldown:SetShown(auraData.duration and auraData.duration >= AURA_MIN_DURATION)
			end

			button:SetID(index)
			button.Icon:SetTexture(auraData.icon)
			button.Count:SetText(auraData.applications > 1 and auraData.applications or '')
			button.StatusBar:SetStatusBarColor(unpack(element.db.StatusBarTextureColor))
			button.StatusBar.Name:SetText(auraData.name)

			if isDebuff then
				button.backdrop:SetBackdropBorderColor(1, 0, 0)
			else
				button.backdrop:SetBackdropBorderColor(0, 0, 0)
			end

			return VISIBLE
		else
			return HIDDEN
		end
	end
end

function IF:SetPosition(element)
	local sizex = (element.db.Size + element.db.Spacing + 2) + (element.db.StatusBar and element.db.StatusBarWidth or 0)
	local sizey = element.db.Size + element.db.Spacing + 2
	local anchor = element.initialAnchor or 'BOTTOMLEFT'
	local growthx = not element.db.StatusBar and (element.db.Direction == 'LEFT' and -1) or 1
	local growthy = ((element.db.StatusBar and element.db.StatusBarDirection == 'DOWN' or element.db.Direction == 'DOWN') and -1) or 1
	local cols = element.db.StatusBar and 1 or element.db.NumPerRow

	local col, row
	for i, button in ipairs(element) do
		if (not button) then break end
		col = (i - 1) % cols
		row = floor((i - 1) / cols)

		button:ClearAllPoints()
		button:SetPoint(anchor, element, anchor, col * sizex * growthx, row * sizey * growthy)
	end
end

function IF:FilterAuraIcons(element, unit, filter, limit, isDebuff, offset, dontHide)
	if (not offset) then offset = 0 end
	local index, visible, hidden = 1, 0, 0

	while (visible < limit) do
		local result = IF:UpdateAuraIcon(element, unit, index, offset, filter, isDebuff, visible)
		if (not result) then
			break
		elseif (result == VISIBLE) then
			visible = visible + 1
		elseif (result == HIDDEN) then
			hidden = hidden + 1
		end

		index = index + 1
	end

	if (not dontHide) then
		for i = visible + offset + 1, #element do
			element[i]:Hide()
		end
	end

	return visible, hidden
end

function IF:UpdateAuras(element, unit)
	if(element.unit ~= unit) then return end
	IF:FilterAuraIcons(element, unit, element.filter, element.numAuras or 32, nil, 0)
	IF:SetPosition(element)
end

function IF:PLAYER_ENTERING_WORLD()
	for SpellID in next, IF.db.Cooldowns.SpellCDs do
		local cooldownInfo = GetSpellCooldown(SpellID)
		local CurrentDuration = (Start + Duration - GetTime()) or 0

		if Enable == 1 and (CurrentDuration > .1) and (CurrentDuration < IF.db.Cooldowns.IgnoreDuration) then
			if (CurrentDuration >= IF.db.Cooldowns.SuppressDuration) then
				IF.DelayCooldowns[SpellID] = true
			elseif (CurrentDuration > GLOBAL_COOLDOWN_TIME) then
				IF.ActiveCooldowns[SpellID] = true
			end
		end
	end

	if IF.db.SortByDuration then
		sort(IF.ActiveCooldowns)
	end

	IF:UnregisterEvent('PLAYER_ENTERING_WORLD')
end

function IF:UNIT_SPELLCAST_SUCCEEDED(_, unit, _, SpellID)
	if (unit == 'player' or unit == 'pet') and IF.db.Cooldowns.SpellCDs[SpellID] then
		IF.Cooldowns[SpellID] = true
	end
end

function IF:SPELL_UPDATE_COOLDOWN()
	local Start, Duration, Enable, Charges, _, ChargeStart, ChargeDuration, CurrentDuration

	for SpellID in next, IF.Cooldowns do
		Start, Duration, Enable = GetSpellCooldown(SpellID)

		if IF.IsChargeCooldown[SpellID] ~= false then
			Charges, _, ChargeStart, ChargeDuration = GetSpellCharges(SpellID)

			if IF.IsChargeCooldown[SpellID] == nil then
				IF.IsChargeCooldown[SpellID] = Charges and true or false
			end

			if Charges then
				Start, Duration = ChargeStart, ChargeDuration
			end
		end

		CurrentDuration = (Start + Duration - GetTime())

		if Enable == 1 and CurrentDuration and (CurrentDuration < IF.db.Cooldowns.IgnoreDuration) then
			if (CurrentDuration >= IF.db.Cooldowns.SuppressDuration) or IF.HasCDDelay[SpellID] then
				IF.DelayCooldowns[SpellID] = true
			elseif (CurrentDuration > GLOBAL_COOLDOWN_TIME) then
				IF.ActiveCooldowns[SpellID] = true
			end
		end

		IF.Cooldowns[SpellID] = nil
	end

	if IF.db.SortByDuration then
		sort(IF.ActiveCooldowns)
	end
end

function IF:BAG_UPDATE_COOLDOWN()
	for bagID = 0, 4 do
		for slotID = 1, GetContainerNumSlots(bagID) do
			local itemID = GetContainerItemID(bagID, slotID)
			if itemID then
				local start, duration, enable = GetContainerItemCooldown(bagID, slotID)
				if duration and duration > GLOBAL_COOLDOWN_TIME and enable == 1 then
					IF.ItemCooldowns[itemID] = true
				end
			end
		end
	end
end

function IF:CreateAuraIcon(element, position)
	local Frame = element[position]
	if not Frame then
		Frame = CreateFrame('Button', nil, element, 'PA_AuraTemplate')
		Frame:EnableMouse(false)
		Frame:SetSize(element.db.Size, element.db.Size)

		Frame.Cooldown:SetDrawEdge(false)
		Frame.Cooldown.CooldownOverride = 'iFilger'
		PA:RegisterCooldown(Frame.Cooldown)

		Frame.Icon:SetTexCoord(PA:TexCoords())

		Frame.Count:SetFont(LSM:Fetch('font', element.db.StackCountFont), element.db.StackCountFontSize, element.db.StackCountFontFlag)
		Frame.Count:SetPoint('BOTTOMRIGHT', Frame, 'BOTTOMRIGHT', 0, 2)

		Frame.StatusBar:SetSize(element.db.StatusBarWidth, element.db.StatusBarHeight)
		Frame.StatusBar:SetStatusBarTexture(LSM:Fetch('statusbar', element.db.StatusBarTexture))
		Frame.StatusBar:SetStatusBarColor(unpack(element.db.StatusBarTextureColor))
		Frame.StatusBar:SetPoint('BOTTOMLEFT', Frame, 'BOTTOMRIGHT', 2, 0)
		Frame.StatusBar:SetMinMaxValues(0, 1)
		Frame.StatusBar:SetValue(0)

		if element.name ~= 'Cooldowns' and element.name ~= 'ItemCooldowns' then
			Frame.Cooldown:SetReverse(true)
			Frame.StatusBar:SetScript('OnUpdate', function(s, elapsed)
				s.elapsed = (s.elapsed or 0) + elapsed
				if (s.elapsed > COOLDOWN_MIN_DURATION) then
					local expiration = Frame.expiration - GetTime()
					local timervalue, formatid = PA:GetTimeInfo(expiration, IF.db.Cooldown.threshold)
					local color = PA.TimeColors[formatid]
					if timervalue then
						local Normalized = expiration / Frame.duration
						s:SetValue(Normalized)
						s.Time:SetFormattedText(PA.TimeFormats[formatid][1], timervalue)
						s.Time:SetTextColor(color.r, color.g, color.b)
						if element.db.FollowCooldownText and (formatid == 1 or formatid == 2) then
							s:SetStatusBarColor(color.r, color.g, color.b)
						end
					end
					s.elapsed = 0
				end
			end)
		end

		Frame.StatusBar.Name:SetFont(LSM:Fetch('font', element.db.StatusBarFont), element.db.StatusBarFontSize, element.db.StatusBarFontFlag)
		Frame.StatusBar.Name:SetPoint('BOTTOMLEFT', Frame.StatusBar, element.db.StatusBarNameX, element.db.StatusBarNameY)

		Frame.StatusBar.Time:SetFont(LSM:Fetch('font', element.db.StatusBarFont), element.db.StatusBarFontSize, element.db.StatusBarFontFlag)
		Frame.StatusBar.Time:SetPoint('BOTTOMRIGHT', Frame.StatusBar, element.db.StatusBarTimeX, element.db.StatusBarTimeY)

		PA:CreateBackdrop(Frame)
		PA:CreateShadow(Frame.backdrop)
		PA:CreateBackdrop(Frame.StatusBar, 'Default')
		PA:CreateShadow(Frame.StatusBar.backdrop)

		Frame.Count:SetShown(true)
		Frame.StatusBar:SetShown(element.db.StatusBar)
		Frame.StatusBar.Name:SetShown(element.db.StatusBarNameEnabled)
		Frame.StatusBar.Time:SetShown(element.db.StatusBarTimeEnabled)

		tinsert(element, Frame)
		element.createdIcons = element.createdIcons + 1
	end

	return Frame
end

function IF:UpdateAll()
	for FrameName, Frame in next, IF.Panels do
		Frame.db = IF.db[FrameName]

		if Frame.db.Enable then
			IF:EnableUnit(Frame)
		else
			IF:DisableUnit(Frame)
		end

		Frame:SetWidth((Frame.db.StatusBar and 1 or Frame.db.NumPerRow) * (Frame.db.StatusBar and Frame.db.StatusBarWidth or Frame.db.Size))

		for _, Button in ipairs(Frame) do
			Button:SetSize(Frame.db.Size, Frame.db.Size)
			Button.Count:SetFont(LSM:Fetch('font', Frame.db.StackCountFont), Frame.db.StackCountFontSize, Frame.db.StackCountFontFlag)
			Button.StatusBar:SetStatusBarTexture(LSM:Fetch('statusbar', Frame.db.StatusBarTexture))
			Button.StatusBar:SetStatusBarColor(unpack(Frame.db.StatusBarTextureColor))
			Button.StatusBar:SetSize(Frame.db.StatusBarWidth, Frame.db.StatusBarHeight)
			Button.StatusBar.Name:SetFont(LSM:Fetch('font', Frame.db.StatusBarFont), Frame.db.StatusBarFontSize, Frame.db.StatusBarFontFlag)
			Button.StatusBar.Time:SetFont(LSM:Fetch('font', Frame.db.StatusBarFont), Frame.db.StatusBarFontSize, Frame.db.StatusBarFontFlag)
			Button.StatusBar.Name:SetPoint('BOTTOMLEFT', Button.StatusBar, Frame.db.StatusBarNameX, Frame.db.StatusBarNameY)
			Button.StatusBar.Time:SetPoint('BOTTOMRIGHT', Button.StatusBar, Frame.db.StatusBarTimeX, Frame.db.StatusBarTimeY)
			Button.StatusBar:SetShown(Frame.db.StatusBar)
			Button.StatusBar.Name:SetShown(Frame.db.StatusBarNameEnabled)
			Button.StatusBar.Time:SetShown(Frame.db.StatusBarTimeEnabled)
		end
	end

	IF:CancelAllTimers()

	if IF.db.Cooldowns.Enable then
		IF:ScheduleRepeatingTimer('UpdateActiveCooldowns', IF.db.Cooldowns.UpdateSpeed)
		IF:ScheduleRepeatingTimer('UpdateDelayedCooldowns', .5)
	end

	if IF.db.ItemCooldowns.Enable then
		IF:ScheduleRepeatingTimer('UpdateItemCooldowns', IF.db.ItemCooldowns.UpdateSpeed)
	end
end

function IF:SPELLS_CHANGED()
	PA:AddKeysToTable(IF.db.Cooldowns.SpellCDs, PA.SpellBook.Spells)
	PA.Options.args.iFilger.args.Cooldowns.args.Spells.args = PA:GenerateSpellOptions(IF.db.Cooldowns.SpellCDs)
end

local function GetSelectedSpell()
	if selectedSpell and selectedSpell ~= '' then
		local spell = strmatch(selectedSpell, " %((%d+)%)$") or selectedSpell
		if spell then
			return tonumber(spell) or spell
		end
	end
end

function IF:BuildProfile()
	PA.Defaults.profile.iFilger = {
		Enable = false,
		Cooldown = CopyTable(PA.Defaults.profile.Cooldown),
	}

	for _, Name in next, {'Cooldowns', 'ItemCooldowns', 'Buffs', 'Procs', 'Enhancements', 'RaidDebuffs', 'TargetDebuffs', 'FocusBuffs', 'FocusDebuffs'} do
		PA.Defaults.profile.iFilger[Name] = {
			Direction = 'RIGHT',
			Enable = true,
			FollowCooldownText = false,
			Size = 28,
			NumPerRow = 12,
			SuppressDuration = 60,
			IgnoreDuration = 300,
			UpdateSpeed = .1,
			Spacing = 4,
			StackCountFont = PA.ElvUI and 'Homespun' or 'Arial Narrow',
			StackCountFontFlag = PA.ElvUI and 'MONOCHROMEOUTLINE' or 'OUTLINE',
			StackCountFontSize = PA.ElvUI and 10 or 12,
			StatusBar = false,
			StatusBarDirection = 'UP',
			StatusBarFont = PA.ElvUI and 'Homespun' or 'Arial Narrow',
			StatusBarFontFlag = PA.ElvUI and 'MONOCHROMEOUTLINE' or 'OUTLINE',
			StatusBarFontSize = PA.ElvUI and 10 or 12,
			StatusBarHeight = 5,
			StatusBarNameEnabled = true,
			StatusBarNameX = 0,
			StatusBarNameY = 8,
			StatusBarTexture = PA.ElvUI and 'ElvUI Norm' or 'Blizzard Raid Bar',
			StatusBarTextureColor = { .24, .54, .78 },
			StatusBarTimeEnabled = true,
			StatusBarTimeX = 0,
			StatusBarTimeY = 8,
			StatusBarWidth = 148,
		}

		if Name ~= 'Cooldowns' then
			PA.Defaults.profile.iFilger[Name].FilterByList = 'None'
			PA.Defaults.profile.iFilger[Name].Whitelist = {}
			PA.Defaults.profile.iFilger[Name].Blacklist = {}
		end
	end

	PA.Defaults.profile.iFilger.Cooldowns.SpellCDs = PA.SpellBook.Spells
end

function IF:GetOptions()
	local iFilger = ACH:Group(IF.Title, IF.Description, nil, 'tab')
	PA.Options.args.iFilger = iFilger

	iFilger.args.Description = ACH:Description(IF.Description, 0)
	iFilger.args.Enable = ACH:Toggle(ACL['Enable'], nil, 1, nil, nil, nil, function(info) return IF.db[info[#info]] end, function(info, value) IF.db[info[#info]] = value if (not IF.isEnabled) then IF:Initialize() else _G.StaticPopup_Show('PROJECTAZILROKA_RL') end end)

	iFilger.args.AuthorHeader = ACH:Header(ACL['Authors:'], -2)
	iFilger.args.Authors = ACH:Description(IF.Authors, -1, 'large')

	for _, Name in next, {'Cooldowns','ItemCooldowns','Buffs','Procs','Enhancements','RaidDebuffs','TargetDebuffs','FocusBuffs','FocusDebuffs'} do
		iFilger.args[Name] = ACH:Group(Name, nil, nil, nil, function(info) return IF.db[Name][info[#info]] end, function(info, value) IF.db[Name][info[#info]] = value IF:UpdateAll() end)

		iFilger.args[Name].args.Enable = ACH:Toggle(ACL['Enable'], nil, 0)
		iFilger.args[Name].args.Size = ACH:Range(ACL['Icon Size'], nil, 1, { min = 16, max = 64, step = 1 })
		iFilger.args[Name].args.Spacing = ACH:Range(ACL['Spacing'], nil, 2, { min = 0, max = 18, step = 1 })
		iFilger.args[Name].args.NumPerRow = ACH:Range(ACL['Number Per Row'], nil, 3, { min = 1, max = 24, step = 1 }, nil, nil, nil, nil, function() return IF.db[Name].StatusBar end)
		iFilger.args[Name].args.Direction = ACH:Select(ACL['Growth Direction'], nil, 4, { LEFT = 'Left', RIGHT = 'Right' }, nil, nil, nil, nil, nil, function() return IF.db[Name].StatusBar end)
		iFilger.args[Name].args.FilterByList = ACH:Select(ACL['Filter by List'], nil, 5, { None = 'None', Whitelist = 'Whitelist', Blacklist = 'Blacklist' }, nil, nil, nil, nil, nil, Name == 'Cooldowns')

		iFilger.args[Name].args.IconStack = ACH:Group(ACL['Stack Count'], nil, 10)
		iFilger.args[Name].args.IconStack.inline = true
		iFilger.args[Name].args.IconStack.args.StackCountFont = ACH:SharedMediaFont(ACL['Font'], nil, 1)
		iFilger.args[Name].args.IconStack.args.StackCountFontSize = ACH:Range(ACL['Font Size'], nil, 2, { min = 8, max = 18, step = 1 })
		iFilger.args[Name].args.IconStack.args.StackCountFontFlag = ACH:FontFlags(ACL['Font Flag'], nil, 3)

		iFilger.args[Name].args.StatusBarGroup = ACH:Group(ACL['StatusBar'], nil, 11, nil, nil, nil, function() return not IF.db[Name].StatusBar end)
		iFilger.args[Name].args.StatusBarGroup.inline = true
		iFilger.args[Name].args.StatusBarGroup.args.StatusBar = ACH:Toggle(ACL['Enable'], nil, 0, nil, nil, nil, nil, nil, false)
		iFilger.args[Name].args.StatusBarGroup.args.FollowCooldownText = ACH:Toggle(ACL['Follow Cooldown Text Color'], ACL['Follow Cooldown Text Colors (Expiring / Seconds)'], 1)
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarWidth = ACH:Range(ACL['Width'], nil, 3, { min = 1, max = 256, step = 1 })
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarHeight = ACH:Range(ACL['Height'], nil, 4, { min = 1, max = 64, step = 1 })
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarTexture = ACH:SharedMediaStatusbar(ACL['Texture'], nil, 5)
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarTextureColor = ACH:Color(ACL['Texture Color'], nil, 6, nil, nil, function(info) return unpack(IF.db[Name][info[#info]]) end, function(info, r, g, b, a) IF.db[Name][info[#info]] = { r, g, b, a} end)
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarFont = ACH:SharedMediaFont(ACL['Font'], nil, 7)
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarFontSize = ACH:Range(ACL['Font Size'], nil, 8, { min = 8, max = 18, step = 1 })
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarFontFlag = ACH:FontFlags(ACL['Font Flag'], nil, 9)
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarDirection = ACH:Select(ACL['Growth Direction'], nil, 10, { UP = 'Up', DOWN = 'Down' })

		iFilger.args[Name].args.StatusBarGroup.args.StatusBarName = ACH:Group(ACL['Name'], nil, 11)
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarName.inline = true
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarName.args.StatusBarNameEnabled = ACH:Toggle(ACL['Enable'], nil, 0)
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarName.args.StatusBarNameX = ACH:Range(ACL['X Offset'], nil, 1, { min = -256, max = 256, step = 1 })
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarName.args.StatusBarNameY = ACH:Range(ACL['Y Offset'], nil, 2, { min = -64, max = 64, step = 1 })

		iFilger.args[Name].args.StatusBarGroup.args.StatusBarTime = ACH:Group(ACL['Name'], nil, 12)
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarTime.inline = true
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarTime.args.StatusBarTimeEnabled = ACH:Toggle(ACL['Enable'], nil, 0)
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarTime.args.StatusBarTimeX = ACH:Range(ACL['X Offset'], nil, 1, { min = -256, max = 256, step = 1 })
		iFilger.args[Name].args.StatusBarGroup.args.StatusBarTime.args.StatusBarTimeY = ACH:Range(ACL['Y Offset'], nil, 2, { min = -64, max = 64, step = 1 })

		iFilger.args[Name].args.filterGroup = ACH:Group(ACL['Filters'], nil, 12, nil, nil, nil, nil, Name == 'Cooldowns')
		iFilger.args[Name].args.filterGroup.inline = true
		iFilger.args[Name].args.filterGroup.args.selectFilter = ACH:Select(ACL['Select Filter'], nil, 1, { Whitelist = 'Whitelist', Blacklist = 'Blacklist' }, nil, nil, function() return selectedFilter end, function(_, value) selectedFilter, selectedSpell = nil, nil if value ~= '' then selectedFilter = value end end)
		iFilger.args[Name].args.filterGroup.args.resetFilter = ACH:Execute(ACL["Reset Filter"], ACL["This will reset the contents of this filter back to default. Any spell you have added to this filter will be removed."], 2, function() wipe(IF.db[Name][selectedFilter]) selectedSpell = nil end, nil, true)

		iFilger.args[Name].args.filterGroup.args.filterGroup = ACH:Group(function() return selectedFilter end, nil, 10, nil, nil, nil, nil, function() return not selectedFilter end)
		iFilger.args[Name].args.filterGroup.args.filterGroup.inline = true
		iFilger.args[Name].args.filterGroup.args.filterGroup.args.addSpell = ACH:Input(ACL['Add SpellID'], ACL['Add a spell to the filter.'], 1, nil, nil, function() return '' end, function(_, value) value = tonumber(value) if not value then return end local spellName = GetSpellInfo(value) selectedSpell = (spellName and value) or nil if not selectedSpell then return end IF.db[Name][selectedFilter][value] = true end)
		iFilger.args[Name].args.filterGroup.args.filterGroup.args.removeSpell = ACH:Execute(ACL["Remove Spell"], ACL["Remove a spell from the filter. Use the spell ID if you see the ID as part of the spell name in the filter."], 2, function() local value = GetSelectedSpell() if not value then return end selectedSpell = nil IF.db[Name][selectedFilter][value] = nil end, nil, true)
		iFilger.args[Name].args.filterGroup.args.filterGroup.args.selectSpell = ACH:Select(ACL["Select Spell"], nil, 10, function() local list = IF.db[Name][selectedFilter] if not list then return end wipe(spellList) for filter in next, list do local spellName = tonumber(filter) and GetSpellInfo(filter) local name = (spellName and format("%s |cFF888888(%s)|r", spellName, filter)) or tostring(filter) spellList[filter] = name end if not next(spellList) then spellList[''] = ACL["None"] end return spellList end, nil, 'double', function() if not IF.db[Name][selectedFilter][selectedSpell] then selectedSpell = nil end return selectedSpell or '' end, function(_, value) selectedSpell = (value ~= '' and value) or nil end)
		iFilger.args[Name].args.filterGroup.args.spellGroup = ACH:Group(function() local spell = GetSelectedSpell() local spellName = spell and GetSpellInfo(spell) return (spellName and spellName..' |cFF888888('..spell..')|r') or spell or ' ' end, nil, -15, nil, nil, nil, nil, function() return not GetSelectedSpell() end)
		iFilger.args[Name].args.filterGroup.args.spellGroup.inline = true
		iFilger.args[Name].args.filterGroup.args.spellGroup.args.enabled = ACH:Toggle(ACL['Enable'], nil, 0, nil, nil, nil, function() local spell = GetSelectedSpell() if not spell then return end return IF.db[Name][selectedFilter][spell] end, function(_, value) local spell = GetSelectedSpell() if not spell then return end IF.db[Name][selectedFilter][spell] = value end)
	end

	iFilger.args.Cooldowns.args.UpdateSpeed = ACH:Range(ACL['Update Speed'], nil, 5, { min = .1, max = .5, step = .1 })
	iFilger.args.ItemCooldowns.args.UpdateSpeed = ACH:Range(ACL['Update Speed'], nil, 5, { min = .1, max = .5, step = .1 })

	iFilger.args.Cooldowns.args.SuppressDuration = ACH:Range(ACL['Suppress Duration Threshold'], ACL['Duration in Seconds'], 6, { min = 2, max = 600, step = 1 })
	iFilger.args.Cooldowns.args.IgnoreDuration = ACH:Range(ACL['Ignore Duration Threshold'], ACL['Duration in Seconds'], 7, { min = 2, max = 600, step = 1 })

	iFilger.args.Cooldowns.args.Spells = ACH:Group(_G.SPELLS, nil, 12, nil, function(info) return IF.db.Cooldowns.SpellCDs[tonumber(info[#info])] end, function(info, value) IF.db.Cooldowns.SpellCDs[tonumber(info[#info])] = value end)
	iFilger.args.Cooldowns.args.Spells.inline = true
	iFilger.args.Cooldowns.args.Spells.args = PA:GenerateSpellOptions(IF.db.Cooldowns.SpellCDs)
end

function IF:UpdateSettings()
	IF.db = PA.db.iFilger
end

function IF:Initialize()
	if IF.db.Enable ~= true then
		return
	end

	IF.isEnabled = true

	if PA.ElvUI then
		ElvUI[1]:ConfigMode_AddGroup('iFilger')
	end

	IF.Panels = {
		Buffs = IF:Spawn('player', 'Buffs', IF.db.Buffs, 'HELPFUL', { 'TOPLEFT', UIParent, 'CENTER', -351, -203 }),
		RaidDebuffs = IF:Spawn('player', 'RaidDebuffs', IF.db.RaidDebuffs, 'HARMFUL', { 'TOPLEFT', UIParent, 'CENTER', -351, -203 }),
		Procs = IF:Spawn('player', 'Procs', IF.db.Procs, 'HELPFUL', { 'BOTTOMLEFT', UIParent, 'CENTER', -57, -52 }),
		Enhancements = IF:Spawn('player', 'Enhancements', IF.db.Enhancements, 'HELPFUL', { 'BOTTOMRIGHT', UIParent, 'CENTER', -351, 161 }),
		TargetDebuffs = IF:Spawn('target', 'TargetDebuffs', IF.db.TargetDebuffs, 'HARMFUL|PLAYER', { 'TOPLEFT', UIParent, 'CENTER', 283, -207 }),
		Cooldowns = IF:Spawn('player', 'Cooldowns', IF.db.Cooldowns, nil, { 'BOTTOMRIGHT', UIParent, 'CENTER', -71, -109 }),
		ItemCooldowns = IF:Spawn('player', 'ItemCooldowns', IF.db.ItemCooldowns, nil, { 'BOTTOMRIGHT', UIParent, 'CENTER', -71, -109 }),
	}

	IF:RegisterEvent('UNIT_SPELLCAST_SUCCEEDED')	-- For Cooldown Queue
	IF:RegisterEvent('SPELL_UPDATE_COOLDOWN')		-- Process Cooldown Queue
	IF:RegisterEvent('BAG_UPDATE_COOLDOWN')
	IF:RegisterEvent('PLAYER_TARGET_CHANGED', function() if IF.db.TargetDebuffs.Enable then IF:UpdateAuras(IF.Panels.TargetDebuffs, 'target') end end)

	if not PA.Classic then
		IF.Panels.FocusBuffs = IF:Spawn('focus', 'FocusBuffs', IF.db.FocusBuffs, 'HELPFUL', { 'TOPRIGHT', UIParent, 'CENTER', -53, 53 })
		IF.Panels.FocusDebuffs = IF:Spawn('focus', 'FocusDebuffs', IF.db.FocusDebuffs, 'HARMFUL', { 'TOPRIGHT', UIParent, 'CENTER', -53, 53 })
		IF:RegisterEvent('PLAYER_FOCUS_CHANGED', function() IF:UpdateAuras(IF.Panels.FocusBuffs, 'focus') IF:UpdateAuras(IF.Panels.FocusDebuffs, 'focus') end)
	end

	if IF.db.Cooldowns.Enable then
		IF:ScheduleRepeatingTimer('UpdateActiveCooldowns', IF.db.Cooldowns.UpdateSpeed)
		IF:ScheduleRepeatingTimer('UpdateDelayedCooldowns', .5)
	end

	if IF.db.ItemCooldowns.Enable then
		IF:ScheduleRepeatingTimer('UpdateItemCooldowns', IF.db.ItemCooldowns.UpdateSpeed)
	end
end
