local _, ns = ...
local B, C, L, DB = unpack(ns)
local oUF = ns.oUF
local A = B:RegisterModule("Auras")

local _G = getfenv(0)
local format, floor, strmatch, select, unpack, tonumber = format, floor, strmatch, select, unpack, tonumber
local GetTime = GetTime
local GetInventoryItemQuality, GetInventoryItemTexture, GetWeaponEnchantInfo = GetInventoryItemQuality, GetInventoryItemTexture, GetWeaponEnchantInfo
local GetAuraApplicationDisplayCount = C_UnitAuras.GetAuraApplicationDisplayCount
local GetAuraDataByIndex = C_UnitAuras.GetAuraDataByIndex
local GetAuraDuration = C_UnitAuras.GetAuraDuration
local MIN_SPELL_COUNT, MAX_SPELL_COUNT = 2, 999
local FALLBACK_COLOR = {r=0, g=0, b=0}

function A:OnLogin()
	A:HideBlizBuff()
	A:BuildBuffFrame()
	A:Totems()
	A:InitReminder()
end

function A:HideBlizBuff()
	if not C.db["Auras"]["BuffFrame"] and not C.db["Auras"]["HideBlizBuff"] then return end

	B:RegisterEvent("PLAYER_ENTERING_WORLD", function(_, isLogin, isReload)
		if isLogin or isReload then
			B.HideObject(_G.BuffFrame)
			B.HideObject(_G.DebuffFrame)
			BuffFrame.numHideableBuffs = 0 -- fix error when on editmode
		end
	end)
end

function A:BuildBuffFrame()
	if DB.isNewPatch then return end
	if not C.db["Auras"]["BuffFrame"] then return end

	-- Config
	A.settings = {
		Buffs = {
			offset = 12,
			size = C.db["Auras"]["BuffSize"],
			wrapAfter = C.db["Auras"]["BuffsPerRow"],
			maxWraps = 3,
			reverseGrow = C.db["Auras"]["ReverseBuff"],
		},
		Debuffs = {
			offset = 12,
			size = C.db["Auras"]["DebuffSize"],
			wrapAfter = C.db["Auras"]["DebuffsPerRow"],
			maxWraps = 1,
			reverseGrow = C.db["Auras"]["ReverseDebuff"],
		},
	}

	A.DispelColorCurve = C_CurveUtil.CreateColorCurve()
	A.DispelColorCurve:SetType(Enum.LuaCurveType.Step)
	for _, dispelIndex in next, oUF.Enum.DispelType do
		if(oUF.colors.dispel[dispelIndex]) then
			A.DispelColorCurve:AddPoint(dispelIndex, oUF.colors.dispel[dispelIndex])
		end
	end

	-- Movers
	A.BuffFrame = A:CreateAuraHeader("HELPFUL")
	A.BuffFrame.mover = B.Mover(A.BuffFrame, "Buffs", "BuffAnchor", C.Auras.BuffPos)
	A.BuffFrame:ClearAllPoints()
	A.BuffFrame:SetPoint("TOPRIGHT", A.BuffFrame.mover)

	A.DebuffFrame = A:CreateAuraHeader("HARMFUL")
	A.DebuffFrame.mover = B.Mover(A.DebuffFrame, "Debuffs", "DebuffAnchor", {"TOPRIGHT", A.BuffFrame.mover, "BOTTOMRIGHT", 0, -12})
	A.DebuffFrame:ClearAllPoints()
	A.DebuffFrame:SetPoint("TOPRIGHT", A.DebuffFrame.mover)
end

local day, hour, minute = 86400, 3600, 60
function A:FormatAuraTime(s)
	if s >= day then
		return format("%d"..DB.MyColor.."d", s/day + .5), s%day
	elseif s >= 2*hour then
		return format("%d"..DB.MyColor.."h", s/hour + .5), s%hour
	elseif s >= 10*minute then
		return format("%d"..DB.MyColor.."m", s/minute + .5), s%minute
	elseif s >= minute then
		return format("%d:%.2d", s/minute, s%minute), s - floor(s)
	elseif s > 10 then
		return format("%d"..DB.MyColor.."s", s + .5), s - floor(s)
	elseif s > 5 then
		return format("|cffffff00%.1f|r", s), s - format("%.1f", s)
	else
		return format("|cffff0000%.1f|r", s), s - format("%.1f", s)
	end
end

function A:UpdateTimer(elapsed)
	local onTooltip = GameTooltip:IsOwned(self)

	if not (self.timeLeft or self.expiration or onTooltip) then
		self:SetScript("OnUpdate", nil)
		return
	end

	if self.timeLeft then
		self.timeLeft = self.timeLeft - elapsed
	end

	if self.nextUpdate > 0 then
		self.nextUpdate = self.nextUpdate - elapsed
		return
	end

	if self.expiration then
		self.timeLeft = self.expiration / 1e3 - (GetTime() - self.oldTime)
	end

	if self.timeLeft and self.timeLeft >= 0 then
		local timer, nextUpdate = A:FormatAuraTime(self.timeLeft)
		self.nextUpdate = nextUpdate
		self.timer:SetText(timer)
	end

	if onTooltip then A:Button_SetTooltip(self) end
end

function A:UpdateAuras(button, index)
	local unit, filter = button.header:GetAttribute("unit"), button.filter
	local auraData = GetAuraDataByIndex(unit, index, filter)
	if not auraData then return end
--[[
	if auraData.duration and auraData.expirationTime then
		local timeLeft = auraData.expirationTime - GetTime()
		if not button.timeLeft then
			button.nextUpdate = -1
			button.timeLeft = timeLeft
			button:SetScript("OnUpdate", A.UpdateTimer)
		else
			button.timeLeft = timeLeft
		end
		button.nextUpdate = -1
		A.UpdateTimer(button, 0)
	else
		button.timeLeft = nil
		button.timer:SetText("|cff00ff00^-^|r")
	end
]]
	local auraDuration = unit and GetAuraDuration(unit, auraData.auraInstanceID)
	if auraDuration then
		button.Cooldown:SetCooldownFromDurationObject(auraDuration)
		button.Cooldown:Show()
	else
		button.Cooldown:Hide()
	end

	local count = auraData.applications
	if B:IsSecretValue(count) then
		button.count:SetText(GetAuraApplicationDisplayCount(unit, auraData.auraInstanceID, MIN_SPELL_COUNT, MAX_SPELL_COUNT))
	else
		local hideCount = not count or (count < MIN_SPELL_COUNT or count > MAX_SPELL_COUNT)
		button.count:SetText(hideCount and "" or count)
	end

	if filter == "HARMFUL" then
		local color = C_UnitAuras.GetAuraDispelTypeColor(unit, auraData.auraInstanceID, A.DispelColorCurve) or FALLBACK_COLOR
		button:SetBackdropBorderColor(color.r, color.g, color.b)
	else
		button:SetBackdropBorderColor(0, 0, 0)
	end

	button.spellID = auraData.spellId
	button.icon:SetTexture(auraData.icon)
	button.expiration = nil
	button.timeLeft = nil
	button.timer:SetText("") --button.timer:SetText("|cff00ff00^-^|r")
end

function A:UpdateTempEnchant(button, index)
	local expirationTime = select(button.enchantOffset, GetWeaponEnchantInfo())
	if expirationTime then
		local quality = GetInventoryItemQuality("player", index)
		local color = DB.QualityColors[quality or 1]
		button:SetBackdropBorderColor(color.r, color.g, color.b)
		button.icon:SetTexture(GetInventoryItemTexture("player", index))

		button.expiration = expirationTime
		button.oldTime = GetTime()
		button:SetScript("OnUpdate", A.UpdateTimer)
		button.nextUpdate = -1
		A.UpdateTimer(button, 0)
	else
		button.expiration = nil
		button.timeLeft = nil
		button.timer:SetText("")
	end
end

function A:OnAttributeChanged(attribute, value)
	if attribute == "index" then
		A:UpdateAuras(self, value)
	elseif attribute == "target-slot" then
		A:UpdateTempEnchant(self, value)
	end
end

function A:UpdateOptions()
	A.settings.Buffs.size = C.db["Auras"]["BuffSize"]
	A.settings.Buffs.wrapAfter = C.db["Auras"]["BuffsPerRow"]
	A.settings.Buffs.reverseGrow = C.db["Auras"]["ReverseBuff"]
	A.settings.Debuffs.size = C.db["Auras"]["DebuffSize"]
	A.settings.Debuffs.wrapAfter = C.db["Auras"]["DebuffsPerRow"]
	A.settings.Debuffs.reverseGrow = C.db["Auras"]["ReverseDebuff"]
end

function A:UpdateHeader(header)
	local cfg = A.settings.Debuffs
	if header.filter == "HELPFUL" then
		cfg = A.settings.Buffs
		header:SetAttribute("consolidateTo", 0)
		header:SetAttribute("weaponTemplate", format("OrzUIAuraTemplate%d", cfg.size))
	end

	header:SetAttribute("separateOwn", 1)
	header:SetAttribute("sortMethod", "INDEX")
	header:SetAttribute("sortDirection", "+")
	header:SetAttribute("wrapAfter", cfg.wrapAfter)
	header:SetAttribute("maxWraps", cfg.maxWraps)
	header:SetAttribute("point", cfg.reverseGrow and "TOPLEFT" or "TOPRIGHT")
	header:SetAttribute("minWidth", (cfg.size + C.margin)*cfg.wrapAfter)
	header:SetAttribute("minHeight", (cfg.size + cfg.offset)*cfg.maxWraps)
	header:SetAttribute("xOffset", (cfg.reverseGrow and 1 or -1) * (cfg.size + C.margin))
	header:SetAttribute("yOffset", 0)
	header:SetAttribute("wrapXOffset", 0)
	header:SetAttribute("wrapYOffset", -(cfg.size + cfg.offset))
	header:SetAttribute("template", format("OrzUIAuraTemplate%d", cfg.size))

	local fontSize = floor(cfg.size/30*12 + .5)
	local index = 1
	local child = select(index, header:GetChildren())
	while child do
		if (floor(child:GetWidth() * 100 + .5) / 100) ~= cfg.size then
			child:SetSize(cfg.size, cfg.size)
		end

		B.SetFontSize(child.count, fontSize)
		B.SetFontSize(child.timer, fontSize)
		B.SetFontSize(child.CooldownText, fontSize)

		--Blizzard bug fix, icons arent being hidden when you reduce the amount of maximum buttons
		if index > (cfg.maxWraps * cfg.wrapAfter) and child:IsShown() then
			child:Hide()
		end

		index = index + 1
		child = select(index, header:GetChildren())
	end
end

function A:CreateAuraHeader(filter)
	local name = "OrzUIPlayerDebuffs"
	if filter == "HELPFUL" then name = "OrzUIPlayerBuffs" end

	local header = CreateFrame("Frame", name, UIParent, "SecureAuraHeaderTemplate")
	header:SetClampedToScreen(true)
	header:UnregisterEvent("UNIT_AURA") -- we only need to watch player and vehicle
	header:RegisterUnitEvent("UNIT_AURA", "player", "vehicle")
	header:SetAttribute("unit", "player")
	header:SetAttribute("filter", filter)
	header.filter = filter
	RegisterAttributeDriver(header, "unit", "[vehicleui] vehicle; player")

	header.visibility = CreateFrame("Frame", nil, UIParent, "SecureHandlerStateTemplate")
	header.visibility:RegisterEvent("WEAPON_ENCHANT_CHANGED")
	SecureHandlerSetFrameRef(header.visibility, "AuraHeader", header)
	RegisterStateDriver(header.visibility, "customVisibility", "[petbattle] 0;1")
	header.visibility:SetAttribute("_onstate-customVisibility", [[
		local header = self:GetFrameRef("AuraHeader")
		local hide, shown = newstate == 0, header:IsShown()
		if hide and shown then header:Hide() elseif not hide and not shown then header:Show() end
	]]) -- use custom script that will only call hide when it needs to, this prevents spam to `SecureAuraHeader_Update`

	if filter == "HELPFUL" then
		header:SetAttribute("consolidateDuration", -1)
		header:SetAttribute("includeWeapons", 1)
	end

	A:UpdateHeader(header)
	header:Show()

	return header
end

function A:RemoveSpellFromIgnoreList()
	if IsAltKeyDown() and IsControlKeyDown() and self.spellID and C.db["AuraWatchList"]["IgnoreSpells"][self.spellID] then
		C.db["AuraWatchList"]["IgnoreSpells"][self.spellID] = nil
		print(format(L["RemoveFromIgnoreList"], DB.OrzUIString, self.spellID))
	end
end

function A:Button_SetTooltip(button)
	if button:GetAttribute("index") then
		GameTooltip:SetUnitAura(button.header:GetAttribute("unit"), button:GetID(), button.filter)
	elseif button:GetAttribute("target-slot") then
		GameTooltip:SetInventoryItem("player", button:GetID())
	end
end

function A:Button_OnEnter()
	GameTooltip:SetOwner(self, "ANCHOR_BOTTOMLEFT", -5, -5)
	-- Update tooltip
	self.nextUpdate = -1
	self:SetScript("OnUpdate", A.UpdateTimer)
end

local indexToOffset = {2, 6, 10}

function A:CreateAuraIcon(button)
	button.header = button:GetParent()
	button.filter = button.header.filter
	button.name = button:GetName()
	local enchantIndex = tonumber(strmatch(button.name, "TempEnchant(%d)$"))
	button.enchantOffset = indexToOffset[enchantIndex]

	local cfg = A.settings.Debuffs
	if button.filter == "HELPFUL" then
		cfg = A.settings.Buffs
	end
	local fontSize = floor(cfg.size/30*14 + .5)

	button.icon = button:CreateTexture(nil, "BORDER")
	button.icon:SetInside()
	button.icon:SetTexCoord(unpack(DB.TexCoord))

	button.count = button:CreateFontString(nil, "ARTWORK")
	button.count:SetPoint("TOPRIGHT", -1, -3)
	B.SetFontSize(button.count, fontSize)

	button.timer = button:CreateFontString(nil, "ARTWORK")
	button.timer:SetPoint("TOP", button, "BOTTOM", 1, 2)
	B.SetFontSize(button.timer, fontSize)

	button.highlight = button:CreateTexture(nil, "HIGHLIGHT")
	button.highlight:SetColorTexture(1, 1, 1, .25)
	button.highlight:SetInside()

	local cd = CreateFrame("Cooldown", "$parentCooldown", button, "CooldownFrameTemplate")
	cd:SetReverse(true)
	cd:SetEdgeTexture(DB.bgTex)
	cd:SetDrawSwipe(C.db["Auras"]["CDAnimation"])
	cd:SetDrawBling(false)
	button.Cooldown = cd

	local text = cd:GetRegions()
	B.SetFontSize(text, fontSize)
	text:ClearAllPoints()
	text:SetPoint("TOP", button, "BOTTOM", 1, 2)
	button.CooldownText = text

	B.CreateBD(button, .25)
	B.CreateSD(button)

	--button:RegisterForClicks("RightButtonUp", "RightButtonDown")
	button:SetScript("OnAttributeChanged", A.OnAttributeChanged)
	--button:HookScript("OnMouseDown", A.RemoveSpellFromIgnoreList)
	button:SetScript("OnEnter", A.Button_OnEnter)
	button:SetScript("OnLeave", B.HideTooltip)
end
