local L = LibStub("AceLocale-3.0"):GetLocale("IceHUD", false)
IceClassPowerCounter = IceCore_CreateClass(IceElement)

local IceHUD = _G.IceHUD

IceClassPowerCounter.prototype.runeHeight = 22
IceClassPowerCounter.prototype.runeWidth = 36
IceClassPowerCounter.prototype.numRunes = 3
IceClassPowerCounter.prototype.numConsideredFull = 99
IceClassPowerCounter.prototype.lastNumReady = 0
IceClassPowerCounter.prototype.runeCoords = {}
IceClassPowerCounter.prototype.runeShineFadeSpeed = 0.4
IceClassPowerCounter.prototype.minLevel = 9
IceClassPowerCounter.prototype.DesiredAnimDuration = 0.3
IceClassPowerCounter.prototype.DesiredScaleMod = .4
IceClassPowerCounter.prototype.DesiredAnimPause = 0.5
IceClassPowerCounter.prototype.requiredSpec = nil
IceClassPowerCounter.prototype.shouldShowUnmodified = false
IceClassPowerCounter.prototype.unmodifiedMaxPerRune = 10
IceClassPowerCounter.prototype.unit = "player"
IceClassPowerCounter.prototype.round = ceil
IceClassPowerCounter.prototype.growModes = { width = 1, height = 2 }
IceClassPowerCounter.prototype.currentGrowMode = IceClassPowerCounter.prototype.growModes["height"]
IceClassPowerCounter.prototype.shouldRegisterDisplayPower = true

if not IceHUD.CanAccessSecrets() then
	IceClassPowerCounter.prototype.round = function(var)
		if IceHUD.CanAccessValue(var) then
			return ceil(var)
		end

		return var
	end
end

-- Constructor --
function IceClassPowerCounter.prototype:init(name)
	assert(name ~= nil, "ClassPowerCounter cannot be instantiated directly - supply a name from the child class and pass it up.")
	IceClassPowerCounter.super.prototype.init(self, name)

	self.scalingEnabled = true
end

-- 'Public' methods -----------------------------------------------------------

function IceClassPowerCounter.prototype:GetPower()
	return UnitPower("player", self.unitPower)
end

function IceClassPowerCounter.prototype:GetPowerUnmodified()
	return UnitPower("player", self.unitPower, true)
end

function IceClassPowerCounter.prototype:GetPowerMax()
	return UnitPowerMax(self.unit, self.unitPower)
end


-- OVERRIDE
function IceClassPowerCounter.prototype:GetOptions()
	local opts = IceClassPowerCounter.super.prototype.GetOptions(self)

	self:AddDragMoveOption(opts, 30.91)

	opts["vpos"] = {
		type = "range",
		name = L["Vertical Position"],
		desc = L["Vertical Position"],
		get = function()
			return IceHUD:MathRound(self.moduleSettings.vpos)
		end,
		set = function(info, v)
			self.moduleSettings.vpos = v
			self:Redraw()
		end,
		min = -300,
		max = 700,
		step = 1,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		order = 31
	}

	opts["hpos"] = {
		type = "range",
		name = L["Horizontal Position"],
		desc = L["Horizontal Position"],
		get = function()
			return IceHUD:MathRound(self.moduleSettings.hpos)
		end,
		set = function(info, v)
			self.moduleSettings.hpos = v
			self:Redraw()
		end,
		min = -500,
		max = 500,
		step = 1,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		order = 31
	}

	opts["hideBlizz"] = {
		type = "toggle",
		name = L["Hide Blizzard Frame"],
		desc = L["Hides Blizzard frame and disables all events related to it.\n\nNOTE: Blizzard attaches this UI to the player's unitframe, so if you have that hidden in PlayerHealth, then this won't do anything."],
		get = function()
			return self.moduleSettings.hideBlizz
		end,
		set = function(info, value)
			self.moduleSettings.hideBlizz = value
			if (value) then
				self:HideBlizz(true)
			else
				self:ShowBlizz(true)
			end
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		order = 32
	}

	opts["displayMode"] = {
		type = 'select',
		name = L["Display mode"],
		desc = L["Choose whether you'd like a graphical or numeric representation of the runes."],
		get = function(info)
			return IceHUD:GetSelectValue(info, self.moduleSettings.runeMode)
		end,
		set = function(info, v)
			self.moduleSettings.runeMode = info.option.values[v]
			self:SetDisplayMode()
			self:UpdateRunePower()
		end,
		values = { "Graphical", "Numeric", "Graphical Bar", "Graphical Circle", "Graphical Clean Circle", "Graphical Glow" },
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		order = 34
	}

	opts["alsoShowNumeric"] = {
		type = 'toggle',
		name = L["Also show numeric"],
		desc = L["If this is set, the numeric value of the current rune count will show on top of the runes display."],
		get = function(info)
			return self.moduleSettings.alsoShowNumeric
		end,
		set = function(info, v)
			self.moduleSettings.alsoShowNumeric = v
			self:SetDisplayMode()
			self:UpdateRunePower()
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		hidden = function()
			return self.moduleSettings.runeMode == "Numeric"
		end,
		order = 34.01,
	}

	opts["numericVerticalOffset"] = {
		type = 'range',
		min = -500,
		max = 500,
		step = 1,
		name = L["Numeric vertical offset"],
		desc = L["How far to offset the numeric display up or down."],
		get = function(info)
			return self.moduleSettings.numericVerticalOffset
		end,
		set = function(info, v)
			self.moduleSettings.numericVerticalOffset = v
			self:Redraw()
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		hidden = function()
			return self.moduleSettings.runeMode == "Numeric" or not self.moduleSettings.alsoShowNumeric
		end,
		order = 34.02,
	}

	opts["numericHorizontalOffset"] = {
		type = 'range',
		min = -500,
		max = 500,
		step = 1,
		name = L["Numeric horizontal offset"],
		desc = L["How far to offset the numeric display left or right."],
		get = function(info)
			return self.moduleSettings.numericHorizontalOffset
		end,
		set = function(info, v)
			self.moduleSettings.numericHorizontalOffset = v
			self:Redraw()
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		hidden = function()
			return self.moduleSettings.runeMode == "Numeric" or not self.moduleSettings.alsoShowNumeric
		end,
		order = 34.03,
	}

	opts["runeGap"] = {
		type = 'range',
		name = L["Rune gap"],
		desc = L["Spacing between each rune (only works for graphical mode)"],
		min = 0,
		max = 100,
		step = 1,
		get = function()
			return self.moduleSettings.runeGap
		end,
		set = function(info, v)
			self.moduleSettings.runeGap = v
			self:Redraw()
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		hidden = function()
			return self.moduleSettings.runeMode == "Numeric"
		end,
		order = 34.1
	}

	opts["runeOrientation"] = {
		type = 'select',
		name = L["Rune orientation"],
		desc = L["Whether the runes should draw side-by-side or on top of one another"],
		get = function(info)
			return IceHUD:GetSelectValue(info, self.moduleSettings.displayMode)
		end,
		set = function(info, v)
			self.moduleSettings.displayMode = info.option.values[v]
			self:Redraw()
		end,
		values = { "Horizontal", "Vertical" },
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		hidden = function()
			return self.moduleSettings.runeMode == "Numeric"
		end,
		order = 35
	}

	opts["reverse"] =
	{
		type = 'toggle',
		name = L["Reverse direction"],
		desc = L["How points should grow. If orientation is Horizontal, reversing will cause them to grow right to left. If orientation is Vertical, reversing will cause them to grow bottom to top."],
		get = function()
			return self.moduleSettings.reverse
		end,
		set = function(info, value)
			self.moduleSettings.reverse = value
			self:Redraw()
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		hidden = function()
			return self.moduleSettings.runeMode == "Numeric"
		end,
		order = 35.1
	}

	opts["inactiveDisplayMode"] = {
		type = 'select',
		name = L["Inactive mode"],
		desc = L["This controls what happens to runes that are inactive. Darkened means they are visible but colored black, Hidden means they are not displayed."],
		get = function(info)
			return IceHUD:GetSelectValue(info, self.moduleSettings.inactiveDisplayMode)
		end,
		set = function(info, v)
			self.moduleSettings.inactiveDisplayMode = info.option.values[v]
			self:SetDisplayMode()
			self:UpdateRunePower()
		end,
		values = { "Darkened", "Hidden" },
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		hidden = function()
			return self.moduleSettings.runeMode == "Numeric"
		end,
		order = 36
	}

	opts["flashWhenReady"] = {
		type = "toggle",
		name = L["Flash when ready"],
		desc = L["Shows a flash behind each holy rune when it becomes available."],
		get = function()
			return self.moduleSettings.flashWhenBecomingReady
		end,
		set = function(info, value)
			self.moduleSettings.flashWhenBecomingReady = value
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		hidden = function()
			return self.moduleSettings.runeMode == "Numeric"
		end,
		order = 37
	}

	opts["customColor"] = {
		type = 'color',
		name = L["Custom color"],
		desc = L["The color for this counter"],
		get = function()
			return self:GetCustomColor()
		end,
		set = function(info, r,g,b)
			self.moduleSettings.customColor.r = r
			self.moduleSettings.customColor.g = g
			self.moduleSettings.customColor.b = b
			self:UpdateRunePower()
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		hidden = function()
			return self.moduleSettings.runeMode == "Numeric" or self.moduleSettings.runeMode == "Graphical"
		end,
		order = 38,
	}

	opts["customMinColor"] = {
		type = 'color',
		name = L["Custom minimum color"],
		desc = L["The minimum color for this counter (only used if Change Color is enabled)"],
		get = function()
			return self:GetCustomMinColor()
		end,
		set = function(info, r,g,b)
			self.moduleSettings.customMinColor.r = r
			self.moduleSettings.customMinColor.g = g
			self.moduleSettings.customMinColor.b = b
			self:UpdateRunePower()
		end,
		disabled = function()
			return not self.moduleSettings.enabled or not self.moduleSettings.gradient
		end,
		hidden = function()
			return self.moduleSettings.runeMode == "Numeric" or self.moduleSettings.runeMode == "Graphical"
		end,
		order = 39,
	}

	opts["gradient"] = {
		type = "toggle",
		name = L["Change color"],
		desc = L["This will fade the graphical representation from the min color specified to the regular color\n\n(e.g. if the min color is yellow, the color is red, and there are 3 total applications, then the first would be yellow, second orange, and third red)"],
		get = function()
			return self.moduleSettings.gradient
		end,
		set = function(info, v)
			self.moduleSettings.gradient = v
			self:UpdateRunePower()
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		hidden = function()
			return self.moduleSettings.runeMode == "Numeric" or self.moduleSettings.runeMode == "Graphical"
		end,
		order = 40,
	}

	opts["pulseWhenFull"] = {
		type = "toggle",
		name = L["Pulse when full"],
		desc = L["If this is checked, then whenever the counter is maxed out it will gently pulsate to let you know it's ready to be used."],
		get = function()
			return self.moduleSettings.pulseWhenFull
		end,
		set = function(info, v)
			self.moduleSettings.pulseWhenFull = v
			if v and self.lastNumReady == self.numRunes then
				self:StartRunesFullAnimation()
			else
				self:StopRunesFullAnimation()
			end
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		order = 41,
	}

	opts["hideFriendly"] = {
		type = "toggle",
		name = L["Friendly OOC alpha"],
		desc = L["If this is checked, then the counter will use your 'out of target' alpha when targeting someone who is friendly."],
		get = function()
			return self.moduleSettings.hideFriendly
		end,
		set = function(info, v)
			self.moduleSettings.hideFriendly = v
			self:Update(self.unit)
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		order = 42,
	}

	opts["overrideAlpha"] = {
		type = "toggle",
		name = L["Override alpha when not full"],
		desc = L["If your class power is not full (or not empty in the case of Holy Power) then the module will always be displayed on your screen using the In Combat alpha setting. Otherwise it will fade to the OOC alpha when you leave combat."],
		width = "double",
		get = function()
			return self.moduleSettings.overrideAlpha
		end,
		set = function(info, v)
			self.moduleSettings.overrideAlpha = v
			self:UpdateAlpha()
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		order = 43,
	}

	opts.powerDragMove = {
		type = "execute",
		name = L["Toggle interactive point placement"],
		desc = L["Toggles the ability to drag individual points to the desired location."],
		width = "full",
		func = function() self:TogglePowerMoveHint() end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		hidden = function()
			return self.moduleSettings.runeMode == "Numeric"
		end,
		order = 44,
	}

	opts.powerPointReset = {
		type = "execute",
		name = L["Reset custom point placement"],
		desc = L["Resets all individual points to their default location."],
		width = "full",
		func = function() self:ResetPowerMoveLocations() end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		hidden = function()
			return self.moduleSettings.runeMode == "Numeric"
		end,
		order = 45,
	}

	return opts
end


-- OVERRIDE
function IceClassPowerCounter.prototype:GetDefaultSettings()
	local defaults =  IceClassPowerCounter.super.prototype.GetDefaultSettings(self)

	defaults["vpos"] = 0
	defaults["hpos"] = 0
	defaults["runeFontSize"] = 20
	defaults["runeMode"] = "Graphical"
	defaults["usesDogTagStrings"] = false
	defaults["hideBlizz"] = false
	defaults["alwaysFullAlpha"] = false
	defaults["displayMode"] = "Horizontal"
	defaults["runeGap"] = 0
	defaults["flashWhenBecomingReady"] = true
	defaults["inactiveDisplayMode"] = "Darkened"
	defaults["gradient"] = true
	defaults["customMinColor"] = {r=1, g=1, b=0, a=1}
	defaults["customColor"] = {r=1, g=0, b=0, a=1}
	defaults["hideFriendly"] = false
	defaults["pulseWhenFull"] = true
	defaults["overrideAlpha"] = true
	defaults["numericVerticalOffset"] = -25
	defaults["numericHorizontalOffset"] = 0
	defaults["alwaysShowNumeric"] = false
	defaults["reverse"] = false

	return defaults
end


-- OVERRIDE
function IceClassPowerCounter.prototype:Redraw()
	IceClassPowerCounter.super.prototype.Redraw(self)

	self:CreateFrame()
	self:UpdateRunePower()
	self:CheckValidLevel(nil, UnitLevel("player"))
end

function IceClassPowerCounter.prototype:GetCustomColor()
	return self.moduleSettings.customColor.r, self.moduleSettings.customColor.g, self.moduleSettings.customColor.b, self.alpha
end

function IceClassPowerCounter.prototype:GetCustomMinColor()
	return self.moduleSettings.customMinColor.r, self.moduleSettings.customMinColor.g, self.moduleSettings.customMinColor.b, self.alpha
end


-- OVERRIDE
function IceClassPowerCounter.prototype:Enable(core)
	IceClassPowerCounter.super.prototype.Enable(self, core)

	if IceHUD.WowVer >= 70000 then
		self.numRunes = self:GetPowerMax()
	end
	self:CreateFrame()

	self:CheckValidLevel(nil, UnitLevel("player"))
end

function IceClassPowerCounter.prototype:IsInConfigMode()
	return IceClassPowerCounter.super.prototype.IsInConfigMode(self) or self.inPowerMoveMode
end

function IceClassPowerCounter.prototype:CheckValidLevel(event, level)
	if self:IsInConfigMode() then
		self:Show(true)
		return
	end

	if not level then
		if event == "PLAYER_TALENT_UPDATE" then
			level = UnitLevel("player")
		else
			return
		end
	end

	if self.minLevel and level < self.minLevel then
		self:RegisterEvent("PLAYER_LEVEL_UP", "CheckValidLevel")
		self:Show(false)
	else
		self:CheckValidSpec()
	end
end

function IceClassPowerCounter.prototype:CheckValidSpec()
	if self:IsInConfigMode() then
		self:Show(true)
		return
	end

	if self.requiredSpec == nil then
		self:DisplayCounter()
		self:Show(true)
		return
	end

	self:RegisterEvent("PLAYER_TALENT_UPDATE", "CheckValidLevel")

	local spec = IceHUD.GetSpecialization()
	if spec == self.requiredSpec then
		self:DisplayCounter()
		self:Show(true)
	else
		self:Show(false)
	end
end

function IceClassPowerCounter.prototype:GetPowerEvent()
	return IceHUD.UnitPowerEvent
end

function IceClassPowerCounter.prototype:DisplayCounter()
	self:UnregisterEvent("PLAYER_LEVEL_UP")

	self:RegisterEvent(self:GetPowerEvent(), "UpdateRunePower")
	if self.shouldRegisterDisplayPower then
		self:RegisterEvent("UNIT_DISPLAYPOWER", "UpdateRunePower")
	end
	self:RegisterEvent("PLAYER_ENTERING_WORLD", "EnteringWorld")
	if IceHUD.EventExistsUnitMaxPower then
		self:RegisterEvent("UNIT_MAXPOWER", "UpdateRunePower")
	end

	if (self.moduleSettings.hideBlizz) then
		self:HideBlizz()
	end

	self:UpdateRunePower()
end

function IceClassPowerCounter.prototype:Disable(core)
	IceClassPowerCounter.super.prototype.Disable(self, core)

	if self.moduleSettings.hideBlizz then
		self:ShowBlizz()
	end
end

function IceClassPowerCounter.prototype:EnteringWorld()
	self:TargetChanged()
	self:UpdateRunePower()
end

function IceClassPowerCounter.prototype:UpdateRunePower(event, arg1, arg2)
	if event and (event == self:GetPowerEvent() or event == "UNIT_POWER_FREQUENT") and arg1 ~= "player" and arg1 ~= "vehicle" then
		return
	end

	if IceHUD.WowVer >= 70000 then
		local numMax = self:GetPowerMax()
		-- todo:midnight: no idea how to handle numRunes changing at runtime in midnight...
		-- will probably be left up to the individual child classes since we can't
		-- realistically handle it in a cross-compatible way with secrets in the mix
		if IceHUD.CanAccessValue(numMax) and numMax ~= self.numRunes then
			local oldMax = self.numRunes
			self.numRunes = numMax
			self:CreateFrame()
			self:SetDisplayMode()

			for i=self.numRunes+1, oldMax do
				if self.frame.graphical[i] then
					self.frame.graphical[i]:Hide()
				end
			end
			for i=oldMax+1, self.numRunes do
				if self:GetRuneMode() ~= "Numeric" then
					self.frame.graphical[i]:Show()
				end
				self:HideRune(i)
			end
		end
	end

	local numReady = self:GetPower()
	local percentReady = self.shouldShowUnmodified and (self:GetPowerUnmodified() / self.unmodifiedMaxPerRune) or numReady

	if self:GetRuneMode() == "Numeric" or self.moduleSettings.alsoShowNumeric then
		local displayPercent = percentReady
		if self.partialReadyPercent then
			displayPercent = percentReady + self.partialReadyPercent
		end

		if self.numericFormat then
			self.frame.numeric:SetText(format(self.numericFormat, displayPercent))
		else
			self.frame.numeric:SetText(tostring(displayPercent))
		end
		self.frame.numeric:SetTextColor(self:GetColor(self.numericColor))
	end

	if self:GetRuneMode() ~= "Numeric" then
		for i=1, self.numRunes do
			local effectivei = self.moduleSettings.reverse and self.numRunes - i + 1 or i
			-- todo:midnight: this is stupid for secret vals and effectively disables this whole thing
			if IceHUD.CanAccessValue(percentReady) and i <= self.round(percentReady) then
				if self:GetRuneMode() == "Graphical" then
					self:SetRuneGraphicalTexture(effectivei)
				else
					self:SetCustomColor(i, effectivei)
				end

				if self.moduleSettings.inactiveDisplayMode == "Hidden" then
					self.frame.graphical[effectivei]:Show()
				end

				if i > numReady or self.numRunes == 1 then
					local currPercent = percentReady - numReady
					if self.numRunes == 1 then
						currPercent = numReady / self:GetPowerMax()
					end

					self:SetRuneCoords(effectivei, currPercent)
				elseif IceHUD.CanAccessValue(self.lastNumReady) and i > self.lastNumReady then
					self:SetRuneCoords(effectivei, 1)

					if self.moduleSettings.flashWhenBecomingReady then
						local fadeInfo={
							mode = "IN",
							timeToFade = self.runeShineFadeSpeed,
							finishedFunc = function() self:ShineFinished(effectivei) end,
							finishedArg1 = effectivei
						}
						UIFrameFade(self.frame.graphical[effectivei].shine, fadeInfo);
					end
				end
			else
				if self.partialReady then
					if i == self.partialReady then
						self:SetRuneCoords(effectivei, self.partialReadyPercent)
						if self:GetRuneMode() == "Graphical" then
							self:SetRuneGraphicalTexture(effectivei, true)
						end
					else
						self:SetRuneCoords(effectivei, 0)
					end
				end

				self:HideRune(effectivei)
			end
		end
	end

	local canAccess = IceHUD.CanAccessValue(self.lastNumReady) and IceHUD.CanAccessValue(numReady)
	if self.moduleSettings.pulseWhenFull then
		if canAccess and numReady > self.lastNumReady and (numReady == self.numRunes or numReady >= self.numConsideredFull) then
			self:StartRunesFullAnimation()
		elseif not canAccess or (numReady < self.numRunes and numReady < self.numConsideredFull) then
			self:StopRunesFullAnimation()
		end
	end

	self.lastNumReady = numReady
	self:UpdateAlpha()

	if (self.moduleSettings.hideBlizz) then
		self:HideBlizz()
	end
end

function IceClassPowerCounter.prototype:SetRuneCoords(rune, percent)
	if self.runeCoords == nil or #self.runeCoords < rune then
		return
	end

	local left, right, top, bottom = 0, 1, 0, 1
	if self:GetRuneMode() == "Graphical" then
		local coords = self.runeCoords[rune]
		if coords then
			left, right, top, bottom = unpack(coords)
		end
	end

	if self.currentGrowMode == self.growModes["height"] then
		top = bottom - (percent * (bottom - top))
		self.frame.graphical[rune].rune:SetHeight(percent * self.runeHeight)
	elseif self.currentGrowMode == self.growModes["width"] then
		right = left + (percent * (right - left))
		self.frame.graphical[rune].rune:SetWidth(percent * self.runeWidth)
	end

	self.frame.graphical[rune].rune:SetTexCoord(left, right, top, bottom)
end

function IceClassPowerCounter.prototype:HideRune(i)
	if self:GetRuneMode() == "Numeric" then
		self.frame.graphical[i]:Hide()
		return
	end

	if self.moduleSettings.inactiveDisplayMode == "Darkened" then
		self.frame.graphical[i].rune:SetVertexColor(0, 0, 0)
	elseif self.moduleSettings.inactiveDisplayMode == "Hidden" then
		self.frame.graphical[i]:Hide()
	end
end

function IceClassPowerCounter.prototype:StartRunesFullAnimation()
	if not self.AnimUpdate then
		self.AnimUpdate = function() self:UpdateRuneAnimation() end
	end

	self.AnimStartTime = GetTime()
	self.AnimDirection = 1
	self.frame:SetScript("OnUpdate", self.AnimUpdate)
end

function IceClassPowerCounter.prototype:UpdateRuneAnimation(frame, elapsed)
	local scale = self.frame:GetScale()
	local now = GetTime()
	local perc = IceHUD:Clamp((now - self.AnimStartTime) / self.DesiredAnimDuration, 0, 1)

	if self.AnimDirection > 0 then
		scale = 1 + (self.DesiredScaleMod * perc)

		if perc >= 1 then
			self.AnimDirection = -1
			self.AnimStartTime = now
		end
	elseif self.AnimDirection < 0 then
		scale = (1 + self.DesiredScaleMod) - (self.DesiredScaleMod * perc)

		if perc >= 1 then
			self.AnimDirection = 0
			self.AnimStartTime = now
		end
	else
		if now - self.AnimStartTime >= self.DesiredAnimPause then
			self.AnimDirection = 1
			self.AnimStartTime = now
		end
	end

	for i=1, #self.frame.graphical do
		local scaleFrame = self:HasAnyCustomPointPlacement() and self.frame.graphical[i].rune or self.frame.graphical[i]
		scaleFrame:SetScale(scale)
	end
	self.frame.numericParent:SetScale(scale)
end

function IceClassPowerCounter.prototype:StopRunesFullAnimation()
	self.frame:SetScript("OnUpdate", nil)
	for i=1, #self.frame.graphical do
		local scaleFrame = self:HasAnyCustomPointPlacement() and self.frame.graphical[i].rune or self.frame.graphical[i]
		scaleFrame:SetScale(1)
	end
	self.frame.numericParent:SetScale(1)
end

function IceClassPowerCounter.prototype:ShineFinished(rune)
	UIFrameFadeOut(self.frame.graphical[rune].shine, self.runeShineFadeSpeed, 1, 0);
end

function IceClassPowerCounter.prototype:GetRuneTexture(rune)
	return nil
end

function IceClassPowerCounter.prototype:GetRuneAtlas(rune)
	return nil
end

function IceClassPowerCounter.prototype:UseAtlasSize(rune)
	return false
end

function IceClassPowerCounter.prototype:GetShineAtlas(rune)
	return nil
end

function IceClassPowerCounter.prototype:GetFrameAtlas(rune)
	return nil
end

function IceClassPowerCounter.prototype:GetBackgroundAtlas(rune)
	return nil
end

function IceClassPowerCounter.prototype:GetPartialRuneAtlas(rune)
	return nil
end

function IceClassPowerCounter.prototype:CreateFrame()
	IceClassPowerCounter.super.prototype.CreateFrame(self)

	self:CreateMoveHintFrame()

	self.frame:SetFrameStrata(IceHUD.IceCore:DetermineStrata(IceElement.defaultStrata))
	self.frame:SetHeight(self.runeHeight)
	self.frame:ClearAllPoints()
	self:SetFramePosition()

	self:CreateRuneFrame()

	self:SetDisplayMode()
end

function IceClassPowerCounter.prototype:GetRuneMode()
	return self.moduleSettings.runeMode
end

function IceClassPowerCounter.prototype:SetDisplayMode()
	if self:GetRuneMode() == "Numeric" or self.moduleSettings.alsoShowNumeric then
		self.frame.numeric:Show()
		self.frame.numeric:SetPoint(
			"CENTER",
			self.frame.numericParent,
			"CENTER",
			self.moduleSettings.runeMode == "Numeric" and 0 or self.moduleSettings.numericHorizontalOffset,
			self.moduleSettings.runeMode == "Numeric" and 0 or self.moduleSettings.numericVerticalOffset
		)
		for i=1, self.numRunes do
			self.frame.graphical[i]:Hide()
		end
	else
		self.frame.numeric:Hide()
	end

	if self:GetRuneMode() ~= "Numeric" then
		for i=1, self.numRunes do
			self:SetupRuneTexture(i)
			self.frame.graphical[i]:Show()

			if self.frame.graphical[i].frame then
				if self:GetRuneMode() == "Graphical" then
					self.frame.graphical[i].frame:Show()
				else
					self.frame.graphical[i].frame:Hide()
				end
			end
			if self.frame.graphical[i].bg then
				if self:GetRuneMode() == "Graphical" then
					self.frame.graphical[i].bg:Show()
				else
					self.frame.graphical[i].bg:Hide()
				end
			end
		end
	end
end

function IceClassPowerCounter.prototype:CreateRuneFrame()
	-- create numeric runes
	if self.frame.numericParent == nil then
		self.frame.numericParent = CreateFrame("Frame", nil, self.frame)
	end
	self.frame.numericParent:SetAllPoints(self.frame)
	self.frame.numeric = self:FontFactory(self.moduleSettings.runeFontSize, self.frame.numericParent, self.frame.numeric)

	self.frame.numeric:SetJustifyH("CENTER")

	self.frame.numeric:Hide()

	if (not self.frame.graphical) then
		self.frame.graphical = {}
	end

	for i=1, self.numRunes do
		self:CreateRune(i)
	end
	for i=self.numRunes+1, #self.frame.graphical do
		self.frame.graphical[i]:Hide()
	end
end

function IceClassPowerCounter.prototype:CreateRune(i)
	-- create runes
	if (not self.frame.graphical[i]) then
		self.frame.graphical[i] = CreateFrame("Frame", nil, self.frame)
		self.frame.graphical[i]:SetFrameStrata(IceHUD.IceCore:DetermineStrata(IceElement.defaultStrata))

		self.frame.graphical[i].rune = self.frame.graphical[i]:CreateTexture(nil, "BORDER")
		self.frame.graphical[i].rune:SetVertexColor(0, 0, 0)
		self:SetupRuneTexture(i)

		self.frame.graphical[i].shine = self.frame.graphical[i]:CreateTexture(nil, "OVERLAY")
		if self:GetShineAtlas(i) then
			self.frame.graphical[i].shine:SetAtlas(self:GetShineAtlas(i))
		else
			self.frame.graphical[i].shine:SetTexture("Interface\\ComboFrame\\ComboPoint")
			self.frame.graphical[i].shine:SetTexCoord(0.5625, 1, 0, 1)
		end
		self.frame.graphical[i].shine:SetBlendMode("ADD")
		self.frame.graphical[i].shine:ClearAllPoints()
		self.frame.graphical[i].shine:SetPoint("CENTER", self.frame.graphical[i], "CENTER")

		self.frame.graphical[i].shine:SetWidth(self.runeWidth + 25)
		self.frame.graphical[i].shine:SetHeight(self.runeHeight + 10)
		self.frame.graphical[i].shine:Hide()

		local frameAtlas = self:GetFrameAtlas(i)
		if frameAtlas then
			self.frame.graphical[i].frame = self.frame.graphical[i]:CreateTexture(nil, "ARTWORK")
			self.frame.graphical[i].frame:SetAtlas(frameAtlas)
			self.frame.graphical[i].frame:ClearAllPoints()
			self.frame.graphical[i].frame:SetPoint("CENTER", self.frame.graphical[i], "CENTER")
		end

		local bgAtlas = self:GetBackgroundAtlas(i)
		if bgAtlas then
			self.frame.graphical[i].bg = self.frame.graphical[i]:CreateTexture(nil, "BACKGROUND")
			self.frame.graphical[i].bg:SetAtlas(bgAtlas)
			self.frame.graphical[i].bg:ClearAllPoints()
			self.frame.graphical[i].bg:SetAllPoints(self.frame.graphical[i])
		end

		self.frame.graphical[i]:Hide()
	end

	self.frame.graphical[i]:SetWidth(self.runeWidth)
	self.frame.graphical[i]:SetHeight(self.runeHeight)
	self.frame.graphical[i].rune:SetWidth(self.runeWidth)
	self.frame.graphical[i].rune:SetHeight(self.runeHeight)
	if self.frame.graphical[i].frame then
		self.frame.graphical[i].frame:SetWidth(self.runeWidth + 10)
		self.frame.graphical[i].frame:SetHeight(self.runeHeight + 10)
	end
	if self.currentGrowMode == self.growModes["width"] then
		self.frame.graphical[i].rune:SetPoint("LEFT", self.frame.graphical[i], "LEFT")
	else
		self.frame.graphical[i].rune:SetPoint("BOTTOM", self.frame.graphical[i], "BOTTOM")
	end
end

function IceClassPowerCounter.prototype:SetupNewRune(rune)
end

function IceClassPowerCounter.prototype:SetupRuneTexture(rune)
	if rune > #self.runeCoords then
		self:SetupNewRune(rune)
	end
	if not rune or rune < 1 or rune > #self.runeCoords then
		return
	end

	local width = self.runeHeight
	local a,b,c,d = 0, 1, 0, 1
	if self:GetRuneMode() == "Graphical" then
		width = self.runeWidth
		local coords = self.runeCoords[rune]
		if coords then
			a,b,c,d = unpack(coords)
		end
	end

	local offx, offy = 0, 0
	if self.moduleSettings.powerMoveOffsets and self.moduleSettings.powerMoveOffsets[rune] then
		offx = self.moduleSettings.powerMoveOffsets[rune].x or 0
		offy = self.moduleSettings.powerMoveOffsets[rune].y or 0
	end

	-- make sure any texture aside from the special one is square and has the proper coordinates
	self.frame.graphical[rune].rune:SetTexCoord(a, b, c, d)
	self.frame.graphical[rune]:SetWidth(width)
	self.frame:SetWidth(width*self.numRunes)
	local runeAdjust = rune - (self.numRunes / 2) - 0.5
	if self.moduleSettings.displayMode == "Horizontal" then
		self.frame.graphical[rune]:SetPoint("CENTER", runeAdjust * (width-5) + runeAdjust + (runeAdjust * self.moduleSettings.runeGap) + offx, offy)
	else
		self.frame.graphical[rune]:SetPoint("CENTER", offx, -1 * (runeAdjust * (self.runeHeight-5) + runeAdjust + (runeAdjust * self.moduleSettings.runeGap) + offy))
	end

	if self:GetRuneMode() == "Graphical" then
		self:SetRuneGraphicalTexture(rune)
	elseif self:GetRuneMode() == "Graphical Bar" then
		self.frame.graphical[rune].rune:SetTexture(IceElement.TexturePath .. "Combo")
	elseif self:GetRuneMode() == "Graphical Circle" then
		self.frame.graphical[rune].rune:SetTexture(IceElement.TexturePath .. "ComboRound")
	elseif self:GetRuneMode() == "Graphical Glow" then
		self.frame.graphical[rune].rune:SetTexture(IceElement.TexturePath .. "ComboGlow")
	elseif self:GetRuneMode() == "Graphical Clean Circle" then
		self.frame.graphical[rune].rune:SetTexture(IceElement.TexturePath .. "ComboCleanCurves")
	end
end

function IceClassPowerCounter.prototype:SetRuneGraphicalTexture(rune, partial)
	self.frame.graphical[rune].rune:SetVertexColor(1, 1, 1)

	local tex = self:GetRuneTexture(rune)
	if tex then
		self.frame.graphical[rune].rune:SetTexture(tex)
	else
		local partialAtlas = self:GetPartialRuneAtlas(rune)
		local atlas = (partial and partialAtlas) and partialAtlas or self:GetRuneAtlas(rune)
		self.frame.graphical[rune].rune:SetAtlas(atlas, self:UseAtlasSize(rune))
	end
end

function IceClassPowerCounter.prototype:GetAlphaAdd()
	return 0.15
end

function IceClassPowerCounter.prototype:SetCustomColor(i, effectivei)
	local r, g, b = self:GetCustomColor()
	if (self.moduleSettings.gradient) then
		r,g,b = self:GetGradientColor(i)
	end
	self.frame.graphical[effectivei].rune:SetVertexColor(r, g, b)
end

function IceClassPowerCounter.prototype:GetGradientColor(curr)
	local r, g, b = self:GetCustomColor()
	local mr, mg, mb = self:GetCustomMinColor()
	local scale = self.numRunes == 1 and 0 or ((curr-1)/(self.numRunes-1))

	if r < mr then
		r = ((r-mr)*scale) + mr
	else
		r = ((mr-r)*scale) + r
	end

	if g < mg then
		g = ((g-mg)*scale) + mg
	else
		g = ((mg-g)*scale) + g
	end

	if b < mb then
		b = ((b-mb)*scale) + mb
	else
		b = ((mb-b)*scale) + b
	end

	return r, g, b
end

function IceClassPowerCounter.prototype:TargetChanged()
	IceClassPowerCounter.super.prototype.TargetChanged(self)
	-- sort of a hack fix...if "ooc" alpha is set to 0, then the runes frame is all jacked up when the user spawns in
	-- need to re-run CreateFrame in order to setup the frame properly. not sure why :(
	self:Redraw()
end

function IceClassPowerCounter.prototype:InCombat()
	IceClassPowerCounter.super.prototype.InCombat(self)
	self:Redraw()
end

function IceClassPowerCounter.prototype:OutCombat()
	IceClassPowerCounter.super.prototype.OutCombat(self)
	self:Redraw()
end

function IceClassPowerCounter.prototype:CheckCombat()
	IceClassPowerCounter.super.prototype.CheckCombat(self)
	self:Redraw()
end

function IceClassPowerCounter.prototype:AlphaPassThroughTarget()
	return self.moduleSettings.hideFriendly and UnitIsFriend("player", "target")
end

function IceClassPowerCounter.prototype:ShowBlizz(fromConfig)
	assert(false, "Must override ShowBlizz in child classes.")
end

function IceClassPowerCounter.prototype:HideBlizz(fromConfig)
	assert(false, "Must override HideBlizz in child classes.")
end

function IceClassPowerCounter.prototype:UseTargetAlpha()
	if not IceHUD.CanAccessValue(self.lastNumReady) then
		return true
	end

	if not self.moduleSettings.overrideAlpha then
		return false
	end

	if self.bTreatEmptyAsFull then
		return self.lastNumReady > 0
	else
		return self.lastNumReady < self.numRunes
	end
end

function IceClassPowerCounter.prototype:CreatePowerMoveHintFrames()
	if not self.powerMoveHint then
		self.powerMoveHint = {}
	end

	for i=1,self.numRunes do
		self.frame.graphical[i]:SetClampedToScreen(true)

		if not self.powerMoveHint[i] then
			self.powerMoveHint[i] = CreateFrame("Frame", "IceHUD_"..self.elementName.."_rune_" .. i .. "_move", self.frame.graphical[i])

			self.powerMoveHint[i].texture = self.powerMoveHint[i]:CreateTexture(nil, "ARTWORK")
			self.powerMoveHint[i].texture:SetTexture("Interface/Tooltips/UI-Tooltip-Background")
			self.powerMoveHint[i].texture:SetBlendMode("ADD")
			self.powerMoveHint[i].texture:SetVertexColor(0.2, 0.8, 0.2, 0.75)
			self.powerMoveHint[i].texture:SetAllPoints(self.powerMoveHint[i])

			self.powerMoveHint[i]:SetScript("OnMouseDown", function() self:PowerMoveHintMouseDown(i) end)
			self.powerMoveHint[i]:SetScript("OnMouseUp", function() self:PowerMoveHintMouseUp(i) end)
			self.powerMoveHint[i]:SetScript("OnEnter", function() self:PowerMoveHintEnter(i) end)
			self.powerMoveHint[i]:SetScript("OnLeave", function() self:PowerMoveHintLeave(i) end)
			self.powerMoveHint[i]:Hide()
		end

		self.powerMoveHint[i]:SetAllPoints(self.frame.graphical[i])
	end
end

function IceClassPowerCounter.prototype:PowerMoveHintMouseDown(i)
	if IsMouseButtonDown("RightButton") then
		self:TogglePowerMoveHint()
		self:PowerMoveHintMouseUp(i)
		return
	elseif IsMouseButtonDown("MiddleButton") then
		self:PowerMoveHintMoveTo(i, 0, 0)

		self:SetupRuneTexture(i)
		return
	elseif not IsMouseButtonDown("LeftButton") then
		return
	end

	self:SetPowerMoveHintLast(i, true)
	self.powerMoveHintScale = UIParent:GetEffectiveScale()
	self.powerMoveHint[i]:SetScript("OnUpdate", function() self:PowerMoveHintUpdate(i) end)
end

function IceClassPowerCounter.prototype:PowerMoveHintMouseUp(i)
	self.powerMoveHint[i]:SetScript("OnUpdate", nil)
end

function IceClassPowerCounter.prototype:PowerMoveHintEnter(i)
	if not self.powerMoveHintTooltip then
		self.powerMoveHintTooltip = {}
	end
	if not self.powerMoveHintTooltip[i] then
		self.powerMoveHintTooltip[i] = "|cffffffff"..self.elementName.."|r point |cffffffff"..i.."|r\n"..L["|cff9999ffLeft click|r and drag to move. Hold |cff9999ffShift|r to lock vertical position, hold |cff9999ffControl|r to lock horizontal position.\n\n|cff9999ffMiddle click|r to reset to previous position.\n\n|cff9999ffRight click|r to exit placement mode."]
	end

	GameTooltip:SetOwner(self.powerMoveHint[i], "ANCHOR_TOPLEFT")
	GameTooltip:SetText(self.powerMoveHintTooltip[i])
end

function IceClassPowerCounter.prototype:PowerMoveHintLeave(i)
	GameTooltip:Hide()
end

function IceClassPowerCounter.prototype:TogglePowerMoveHint()
	self:CreatePowerMoveHintFrames()

	local wasInPowerMoveMode = self.inPowerMoveMode
	if wasInPowerMoveMode then
		self.inPowerMoveMode = false
		for i=1,self.numRunes do
			self.powerMoveHint[i]:Hide()
		end
		self:Redraw()
	else
		self.inPowerMoveMode = true
		for i=1,self.numRunes do
			self.powerMoveHint[i]:Show()
		end
		self:Redraw()
	end

	return not wasInPowerMoveMode
end

function IceClassPowerCounter.prototype:ResetPowerMoveLocations()
	if not self.moduleSettings.powerMoveOffsets then
		self.moduleSettings.powerMoveOffsets = {}
	end

	for i=1,self.numRunes do
		if not self.moduleSettings.powerMoveOffsets[i] then
			break
		end

		self.moduleSettings.powerMoveOffsets[i].x = 0
		self.moduleSettings.powerMoveOffsets[i].y = 0
	end

	self:Redraw()
end

function IceClassPowerCounter.prototype:PowerMoveHintUpdate(i)
	local currLeft, currTop = self.frame.graphical[i]:GetLeft(), self.frame.graphical[i]:GetTop()
	local lastX, lastY = self.powerMoveHintLastX, self.powerMoveHintLastY
	local currX, currY = GetCursorPosition()
	local dx, dy = (currX - lastX) / self.powerMoveHintScale / IceHUD.IceCore:GetScale() / self.moduleSettings.scale, (currY - lastY) / self.powerMoveHintScale / IceHUD.IceCore:GetScale() / self.moduleSettings.scale
	if IsShiftKeyDown() then
		dy = 0
	end
	if IsControlKeyDown() then
		dx = 0
	end

	local moveConsumed = self:PowerMoveHintMoveBy(i, dx, dy)

	self:SetupRuneTexture(i)
	self:SetPowerMoveHintLast(i, moveConsumed)

	-- if the frame clamping caused the frame to not move in a given direction, un-apply our delta to respect the clamp
	if not IsShiftKeyDown() and currTop == self.powerMoveHintLastTop then
		self:PowerMoveHintMoveBy(i, 0, -dy)
	end
	if not IsControlKeyDown() and currLeft == self.powerMoveHintLastLeft then
		self:PowerMoveHintMoveBy(i, -dx, 0)
	end
end

function IceClassPowerCounter.prototype:PowerMoveHintMoveBy(i, dx, dy)
	local prevx, prevy = 0, 0
	if self.moduleSettings.powerMoveOffsets and self.moduleSettings.powerMoveOffsets[i] then
		prevx = self.moduleSettings.powerMoveOffsets[i].x or 0
		prevy = self.moduleSettings.powerMoveOffsets[i].y or 0
	end

	self:PowerMoveHintMoveTo(i, prevx + dx, prevy + dy)
	return true
end

function IceClassPowerCounter.prototype:PowerMoveHintMoveTo(i, x, y)
	if not self.moduleSettings.powerMoveOffsets then
		self.moduleSettings.powerMoveOffsets = {}
	end
	if not self.moduleSettings.powerMoveOffsets[i] then
		self.moduleSettings.powerMoveOffsets[i] = {}
	end

	self.moduleSettings.powerMoveOffsets[i].x = x
	self.moduleSettings.powerMoveOffsets[i].y = y
end

function IceClassPowerCounter.prototype:SetPowerMoveHintLast(i, updateCursor)
	if updateCursor then
		self.powerMoveHintLastX, self.powerMoveHintLastY = GetCursorPosition()
	end
	self.powerMoveHintLastLeft, self.powerMoveHintLastTop = self.frame.graphical[i]:GetLeft(), self.frame.graphical[i]:GetTop()
end

function IceClassPowerCounter.prototype:HasAnyCustomPointPlacement()
	if not self.moduleSettings.powerMoveOffsets then
		return false
	end

	for i=1,self.numRunes do
		if not self.moduleSettings.powerMoveOffsets[i] then
			return false
		end

		if self.moduleSettings.powerMoveOffsets[i].x ~= 0 or self.moduleSettings.powerMoveOffsets[i].y ~= 0 then
			return true
		end
	end

	return false
end
