--[[------------------------------------------------------------------------------------------------------
oUF_AuraWatch by Astromech
Please leave comments, suggestions, and bug reports on this addon's WoWInterface page

To setup, create a table named AuraWatch in your unit frame. There are several options
you can specify, as explained below.

	icons
		Mandatory!
		A table of frames to be used as icons. oUF_Aurawatch does not position
		these frames, so you must do so yourself. Each icon needs a spellID entry,
		which is the spell ID of the aura to watch. Table should be set up
		such that values are icon frames, but the keys can be anything.

		Note each icon can have several options set as well. See below.
	strictMatching
		Default: false
		If true, AuraWatch will only show an icon if the specific aura
		with the specified spell id is on the unit. If false, AuraWatch
		will show the icon if any aura with the same name and icon texture
		is on the unit. Strict matching can be undesireable because most
		ranks of an aura have different spell ids.
	missingAlpha
		Default 0.75
		The alpha value for icons of auras which have faded from the unit.
	presentAlpha
		Default 1
		The alpha value for icons or auras present on the unit.
	onlyShowMissing
		Default false
		If this is true, oUF_AW will hide icons if they are present on the unit.
	onlyShowPresent
		Default false
		If this is true, oUF_AW will hide icons if they have expired from the unit.
	hideCooldown
		Default false
		If this is true, oUF_AW will not create a cooldown frame
	hideCount
		Default false
		If this is true, oUF_AW will not create a count fontstring
	fromUnits
		Default {["player"] = true, ["pet"] = true, ["vehicle"] = true}
		A table of units from which auras can originate. Have the units be the keys
		and "true" be the values.
	anyUnit
		Default false
		Set to true for oUF_AW to to show an aura no matter what unit it
		originates from. This will override any fromUnits setting.
	decimalThreshold
		Default 5
		The threshold before timers go into decimal form. Set to -1 to disable decimals.
	PostCreateIcon
		Default nil
		A function to call when an icon is created to modify it, such as adding
		a border or repositioning the count fontstring. Leave as nil to ignore.
		The arguements are: AuraWatch table, icon, auraSpellID, auraName, unitFrame

Below are options set on a per icon basis. Set these as fields in the icon frames.

The following settings can be overridden from the AuraWatch table on a per-aura basis:
	onlyShowMissing
	onlyShowPresent
	hideCooldown
	hideCount
	fromUnits
	anyUnit
	decimalThreshold

The following settings are unique to icons:

	spellID
		Mandatory!
		The spell id of the aura, as explained above.
	icon
		Default aura texture
		A texture value for this icon.
	overlay
		Default Blizzard aura overlay
		An overlay for the icon. This is not created if a custom icon texture is created.
	count
		Default A fontstring
		An fontstring to show the stack count of an aura.

Here is an example of how to set oUF_AW up:

	local createAuraWatch = function(self, unit)
		local auras = {}

		-- A table of spellIDs to create icons for
		-- To find spellIDs, look up a spell on www.wowhead.com and look at the URL
		-- http://www.wowhead.com/?spell=SPELL_ID
		local spellIDs = { ... }

		auras.presentAlpha = 1
		auras.missingAlpha = .7
		auras.PostCreateIcon = myCustomIconSkinnerFunction
		-- Set any other AuraWatch settings
		auras.icons = {}
		for i, sid in pairs(spellIDs) do
			local icon = CreateFrame("Frame", nil, auras)
			icon.spellID = sid
			-- set the dimensions and positions
			icon:SetWidth(24)
			icon:SetHeight(24)
			icon:SetPoint("BOTTOM", self, "BOTTOM", 0, 28 * i)
			auras.icons[sid] = icon
			-- Set any other AuraWatch icon settings
		end
		self.AuraWatch = auras
	end
-----------------------------------------------------------------------------------------------------------]]

local _, ns = ...
local oUF = oUF or ns.oUF
assert(oUF, "oUF_AuraWatch was unable to locate oUF install")

local next = next
local pairs = pairs

local CreateFrame = CreateFrame
local GetSpellInfo = GetSpellInfo
local GetTime = GetTime
local UnitAura = UnitAura
local UnitGUID = UnitGUID

local GUIDs = {}

local PLAYER_UNITS = {
	player = true,
	vehicle = true,
	pet = true,
}

local setupGUID
do
	local cache = setmetatable({}, {__type = "k"})

	local frame = CreateFrame("Frame")
	frame:SetScript("OnEvent", function(self, event)
		for k, t in pairs(GUIDs) do
			GUIDs[k] = nil
			for a in pairs(t) do
				t[a] = nil
			end
			cache[t] = true
		end
	end)
	frame:RegisterEvent("PLAYER_REGEN_ENABLED")
	frame:RegisterEvent("PLAYER_ENTERING_WORLD")

	function setupGUID(guid)
		local t = next(cache)
		if t then
			cache[t] = nil
		else
			t = {}
		end
		GUIDs[guid] = t
	end
end

local DAY, HOUR, MINUTE = 86400, 3600, 60
local function formatTime(s, threshold)
	if s >= DAY then
		return format("%dd", ceil(s / HOUR))
	elseif s >= HOUR then
		return format("%dh", ceil(s / HOUR))
	elseif s >= MINUTE then
		return format("%dm", ceil(s / MINUTE))
	elseif s >= threshold then
		return floor(s)
	end

	return format("%.1f", s)
end

local function updateText(self, elapsed)
	if self.timeLeft then
		self.elapsed = self.elapsed + elapsed

		if self.elapsed >= 0.1 then
			if not self.first then
				self.timeLeft = self.timeLeft - self.elapsed
			else
				self.timeLeft = self.timeLeft - GetTime()
				self.first = false
			end

			if self.timeLeft > 0 then
				if self.timeLeft <= self.textThreshold or self.textThreshold == -1 then
					self.text:SetText(formatTime(self.timeLeft, self.decimalThreshold or 5))
				else
					self.text:SetText("")
				end
			else
				self.text:SetText("")
				self:SetScript("OnUpdate", nil)
			end

			self.elapsed = 0
		end
	end
end

local function resetIcon(icon, frame, count, duration, remaining)
	if icon.onlyShowMissing then
		icon:Hide()
	else
		if icon.cd then
			if duration and duration > 0 and icon.style ~= "NONE" then
				icon.cd:SetCooldown(remaining - duration, duration)
				icon.cd:Show()
			else
				icon.cd:Hide()
			end
		end

		if icon.displayText then
			icon.timeLeft = remaining
			icon.first = true
			icon.elapsed = 0
			icon:SetScript("OnUpdate", updateText)
		end

		if icon.count then
			icon.count:SetText(count > 1 and count)
		end

		if icon.overlay then
			icon.overlay:Hide()
		end

		icon:SetAlpha(icon.presentAlpha)
		icon:Show()
	end
end

local function expireIcon(icon, frame)
	if icon.onlyShowPresent then
		icon:Hide()
	else
		if icon.cd then
			icon.cd:Hide()
		end

		if icon.count then
			icon.count:SetText()
		end

		if icon.overlay then
			icon.overlay:Show()
		end

		icon:SetAlpha(icon.missingAlpha)
		icon:Show()
	end
end

local found = {}
local function Update(self, event, unit)
	if not unit or self.unit ~= unit then return end

	local guid = UnitGUID(unit)
	if not guid then return end

	if not GUIDs[guid] then
		setupGUID(guid)
	end

	local element = self.AuraWatch
	local icons = element.watched

	for _, icon in pairs(icons) do
		if not icon.onlyShowMissing then
			icon:Hide()
		else
			icon:Show()
		end
	end

	local filter, index = "HELPFUL", 1
	local _, name, texture, count, duration, remaining, caster, spellID
	local key, icon

	while true do
		name, _, texture, count, _, duration, remaining, caster, _, _, spellID = UnitAura(unit, index, filter)

		if not name then
			if filter == "HELPFUL" then
				filter = "HARMFUL"
				index = 1
			else
				break
			end
		else
			if element.strictMatching then
				key = spellID
			else
				key = name..texture
			end

			icon = icons[key]

			if icon and (icon.anyUnit or (caster and icon.fromUnits and icon.fromUnits[caster])) then
				resetIcon(icon, element, count, duration, remaining)
				GUIDs[guid][key] = true
				found[key] = true
			end

			index = index + 1
		end
	end

	for icon in pairs(GUIDs[guid]) do
		if icons[icon] and not found[icon] then
			expireIcon(icons[icon], element)
		end
	end

	for k in pairs(found) do
		found[k] = nil
	end
end

local function setupIcons(self)
	local element = self.AuraWatch
	local icons = element.icons

	element.watched = {}

	for _, icon in pairs(icons) do
		local name, _, image = GetSpellInfo(icon.spellID)

		if name then
			icon.name = name

			if not icon.cd and not (element.hideCooldown or icon.hideCooldown) then
				local cd = CreateFrame("Cooldown", nil, icon, "CooldownFrameTemplate")
				cd:SetAllPoints(icon)
				icon.cd = cd
			end

			if not icon.icon then
				local tex = icon:CreateTexture(nil, "BACKGROUND")
				tex:SetAllPoints(icon)
				tex:SetTexture(image)
				icon.icon = tex

				if not icon.overlay then
					local overlay = icon:CreateTexture(nil, "OVERLAY")
					overlay:SetTexture("Interface\\Buttons\\UI-Debuff-Overlays")
					overlay:SetAllPoints(icon)
					overlay:SetTexCoord(.296875, .5703125, 0, .515625)
					overlay:SetVertexColor(1, 0, 0)
					icon.overlay = overlay
				end
			end

			if not icon.count and not (element.hideCount or icon.hideCount) then
				local count = icon:CreateFontString(nil, "OVERLAY")
				count:SetFontObject(NumberFontNormal)
				count:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", -1, 0)
				icon.count = count
			end

			if icon.onlyShowMissing == nil then
				icon.onlyShowMissing = element.onlyShowMissing
			end
			if icon.onlyShowPresent == nil then
				icon.onlyShowPresent = element.onlyShowPresent
			end
			if icon.presentAlpha == nil then
				icon.presentAlpha = element.presentAlpha
			end
			if icon.missingAlpha == nil then
				icon.missingAlpha = element.missingAlpha
			end
			if icon.fromUnits == nil then
				icon.fromUnits = element.fromUnits or PLAYER_UNITS
			end
			if icon.anyUnit == nil then
				icon.anyUnit = element.anyUnit
			end

			if element.strictMatching then
				element.watched[icon.spellID] = icon
			else
				element.watched[name..image] = icon
			end

			if element.PostCreateIcon then
				element:PostCreateIcon(icon, icon.spellID, name, self)
			end
		else
			print("oUF_AuraWatch error: no spell with "..tostring(icon.spellID).." spell ID exists")
		end
	end
end

local function Enable(self)
	local element = self.AuraWatch

	if element then
		element.Update = setupIcons
		self:RegisterEvent("UNIT_AURA", Update)
		setupIcons(self)

		return true
	end
end

local function Disable(self)
	local element = self.AuraWatch

	if element then
		self:UnregisterEvent("UNIT_AURA", Update)

		for _, icon in pairs(element.icons) do
			icon:Hide()
		end
	end
end

oUF:AddElement("AuraWatch", Update, Enable, Disable)