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

local IceHUD = _G.IceHUD

local NetherbladeItemIdList = {29044, 29045, 29046, 29047, 29048}
local NineTailedItemIdList = {96679, 96680, 96681, 96682, 96683, 95305, 95306, 95307, 95308, 95309, 95935, 95936, 95937, 95938, 95939}
-- Parnic - bah, have to abandon the more robust string representation of each slot because of loc issues...
local NetherbladeEquipLocList = {1, 3, 5, 7, 10} --"HeadSlot", "ShoulderSlot", "ChestSlot", "LegsSlot", "HandsSlot"}

local GlyphSpellId = 56810

local baseTime = 9
local gapPerComboPoint = 3
local netherbladeBonus = 3
local glyphBonusSec = 6
local impSndTalentPage = 2
local impSndTalentIdx = 4
local impSndBonusPerRank = 0.25
local maxComboPoints = 5
local sndEndTime = 0
local sndDuration = 0
local sixComboPointsTalentID = 19240

local CurrMaxSnDDuration = 0
local PotentialSnDDuration = 0

local sndBuffName = 132306
if IceHUD.WowMain and IceHUD.WowVer < 80000 then
	sndBuffName = "Ability_Rogue_SliceDice"
end

if IceHUD.WowVer >= 50000 then
	baseTime = 12
	gapPerComboPoint = 6
end
if IceHUD.WowClassic then
	impSndBonusPerRank = 0.15
	impSndTalentPage = 1
	impSndTalentIdx = 6
elseif IceHUD.WowClassicBC then
	impSndBonusPerRank = 0.15
	impSndTalentPage = 2
	impSndTalentIdx = 4
elseif IceHUD.WowClassicWrath then
	impSndBonusPerRank = 0.25
	impSndTalentPage = 2
	impSndTalentIdx = 23
end

local SPELL_POWER_COMBO_POINTS = SPELL_POWER_COMBO_POINTS
if Enum and Enum.PowerType then
	SPELL_POWER_COMBO_POINTS = Enum.PowerType.ComboPoints
end

local UnitBuff = UnitBuff
if not UnitBuff and C_UnitAuras and AuraUtil then
  UnitBuff = function(unitToken, index, filter)
    local auraData = C_UnitAuras.GetBuffDataByIndex(unitToken, index, filter)
    if not auraData then
      return nil
    end

    return AuraUtil.UnpackAuraData(auraData)
  end
end

-- Constructor --
function SliceAndDice.prototype:init()
	SliceAndDice.super.prototype.init(self, "SliceAndDice", "player")

	self.moduleSettings = {}
	self.moduleSettings.desiredLerpTime = 0
	self.moduleSettings.shouldAnimate = false

	self:SetDefaultColor("SliceAndDice", 0.75, 1, 0.2)
	self:SetDefaultColor("SliceAndDicePotential", 1, 1, 1)

	self.bTreatEmptyAsFull = true
end

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

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

	self:RegisterEvent("UNIT_AURA", "UpdateSliceAndDice")
	if IceHUD.EventExistsUnitComboPoints then
		self:RegisterEvent("UNIT_COMBO_POINTS", "ComboPointsChanged")
	else
		self:RegisterEvent(IceHUD.UnitPowerEvent, "ComboPointsChanged")
	end

	if IceHUD.WowVer >= 70000 then
		self:RegisterEvent("PLAYER_TALENT_UPDATE", "CheckMaxComboPoints")
		self:CheckMaxComboPoints()
	end

	if IceHUD.EventExistsUnitMaxPower then
		self:RegisterEvent("UNIT_MAXPOWER", "CheckMaxComboPoints")
	end

	if not self.moduleSettings.alwaysFullAlpha then
		self:Show(false)
	else
		self:UpdateSliceAndDice()
	end

	self:SetBottomText1("")
end

function SliceAndDice.prototype:CheckMaxComboPoints()
	if UnitPowerMax then
		maxComboPoints = UnitPowerMax(self.unit, SPELL_POWER_COMBO_POINTS)
	else
		local talentID, name, texture, selected, available, spellID, unknown, row, column, known, grantedByAura = GetTalentInfoByID(sixComboPointsTalentID, GetActiveSpecGroup())
		maxComboPoints = selected and 6 or 5
	end
end

function SliceAndDice.prototype:Disable(core)
	SliceAndDice.super.prototype.Disable(self, core)
end

function SliceAndDice.prototype:ComboPointsChanged(...)
	if select('#', ...) >= 3 and select(1, ...) == IceHUD.UnitPowerEvent and select(3, ...) ~= "COMBO_POINTS" then
		return
	end

	self:TargetChanged()
	self:UpdateDurationBar()
end

-- OVERRIDE
function SliceAndDice.prototype:GetDefaultSettings()
	local settings = SliceAndDice.super.prototype.GetDefaultSettings(self)

	settings["enabled"] = false
	settings["shouldAnimate"] = false
	settings["desiredLerpTime"] = nil
	settings["lowThreshold"] = 0
	settings["side"] = IceCore.Side.Right
	settings["offset"] = 6
	settings["upperText"]="SnD:"
	settings["showAsPercentOfMax"] = true
	settings["durationAlpha"] = 0.6
	settings["usesDogTagStrings"] = false
	settings["lockLowerFontAlpha"] = false
	settings["lowerTextString"] = ""
	settings["lowerTextVisible"] = false
	settings["hideAnimationSettings"] = true
	settings["bAllowExpand"] = true
	settings["bShowWithNoTarget"] = true

	return settings
end

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

	opts["textSettings"].args["upperTextString"]["desc"] = "The text to display under this bar. # will be replaced with the number of Slice and Dice seconds remaining."
	opts["textSettings"].args["upperTextString"].hidden = false

	opts["showAsPercentOfMax"] =
	{
		type = 'toggle',
		name = L["Show bar as % of maximum"],
		desc = L["If this is checked, then the SnD buff time shows as a percent of the maximum attainable (taking set bonuses and talents into account). Otherwise, the bar always goes from full to empty when applying SnD no matter the duration."],
		get = function()
			return self.moduleSettings.showAsPercentOfMax
		end,
		set = function(info, v)
			self.moduleSettings.showAsPercentOfMax = v
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end
	}

	opts["durationAlpha"] =
	{
		type = "range",
		name = L["Potential SnD time bar alpha"],
		desc = L["What alpha value to use for the bar that displays how long your SnD will last if you activate it. (This gets multiplied by the bar's current alpha to stay in line with the bar on top of it)"],
		min = 0,
		max = 100,
		step = 5,
		get = function()
			return self.moduleSettings.durationAlpha * 100
		end,
		set = function(info, v)
			self.moduleSettings.durationAlpha = v / 100.0
			self:Redraw()
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end
	}

	opts["bShowWithNoTarget"] =
	{
		type = 'toggle',
		name = L["Show with no target"],
		desc = L["Whether or not to display when you have no target selected but have combo points available"],
		get = function()
			return self.moduleSettings.bShowWithNoTarget
		end,
		set = function(info, v)
			self.moduleSettings.bShowWithNoTarget = v
			self:ComboPointsChanged()
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
	}

	return opts
end

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

	self:CreateDurationBar()
end

function SliceAndDice.prototype:CreateDurationBar()
	self.durationFrame = self:BarFactory(self.durationFrame, "BACKGROUND", "ARTWORK", "Duration")

	-- Rokiyo: Do we need to call this here?
	self.CurrScale = 0

	self.durationFrame.bar:SetVertexColor(self:GetColor("SliceAndDicePotential", self.moduleSettings.durationAlpha))
	self.durationFrame.bar:SetHeight(0)

	self:UpdateBar(1, "undef")

	-- force update the bar...if we're in here, then either the UI was just loaded or the player is jacking with the options.
	-- either way, make sure the duration bar matches accordingly
	self:UpdateDurationBar()
end

function SliceAndDice.prototype:RotateHorizontal()
	SliceAndDice.super.prototype.RotateHorizontal(self)

	self:RotateFrame(self.durationFrame)
end

function SliceAndDice.prototype:ResetRotation()
	SliceAndDice.super.prototype.ResetRotation(self)

	if self.durationFrame and self.durationFrame.anim then
		self.durationFrame.anim:Stop()
	end
end

-- 'Protected' methods --------------------------------------------------------

function SliceAndDice.prototype:GetBuffDuration(unitName, buffName)
	local i = 1
	local buff, _, texture, duration, endTime, remaining
	if IceHUD.SpellFunctionsReturnRank then
		buff, _, texture, _, _, duration, endTime = UnitBuff(unitName, i)
	else
		buff, texture, _, _, duration, endTime = UnitBuff(unitName, i)
	end

	while buff do
		if (texture and (type(buffName) == 'string' and string.match(texture, buffName) or texture == buffName)) then
			if endTime and not remaining then
				remaining = endTime - GetTime()
			end
			return duration, remaining
		end

		i = i + 1;

		if IceHUD.SpellFunctionsReturnRank then
			buff, _, texture, _, _, duration, endTime = UnitBuff(unitName, i)
		else
			buff, texture, _, _, duration, endTime = UnitBuff(unitName, i)
		end
	end

	return nil, nil
end

function SliceAndDice.prototype:MyOnUpdate()
	SliceAndDice.super.prototype.MyOnUpdate(self)
	if self.bUpdateSnd then
		self:UpdateSliceAndDice("internal", self.unit)
	end
	if self.target or self.moduleSettings.bShowWithNoTarget then
		self:UpdateDurationBar()
	end
end

local function SNDGetComboPoints(unit)
	if IceHUD.PerTargetComboPoints then
		return GetComboPoints(unit, "target")
	elseif IceHUD.WowVer >= 60000 then
		return UnitPower(unit, SPELL_POWER_COMBO_POINTS)
	else
		return GetComboPoints()
	end
end

local function ShouldHide()
	if IceHUD.WowVer >= 90000 or IceHUD.WowClassicBC or IceHUD.WowClassicWrath or IceHUD.WowClassicMists then
		return false
	end

	return --[[(IceHUD.WowVer < 70000 or not IsSpellKnown(193316)) and]] not IsPlayerSpell(5171) -- IsSpellKnown returns incorrect info for SnD in 7.0
	-- commented code is here in case we decide we'd like to use this module for Roll the Bones.
	-- if we do, though, the "active" check gets way more complicated since it can activate any number of 6 different abilities
	-- with different durations
end

function SliceAndDice.prototype:UpdateSliceAndDice(event, unit)
	if unit and unit ~= self.unit then
		return
	end

	local now = GetTime()
	local remaining = nil
	local fromUpdate = event == "internal"

	if not fromUpdate or IceHUD.WowVer < 30000 then
		sndDuration, remaining = self:GetBuffDuration(self.unit, sndBuffName)

		if not remaining then
			sndEndTime = 0
		else
			sndEndTime = remaining + now
		end
	end

	if sndEndTime and sndEndTime >= now then
		if not fromUpdate then
			self.bUpdateSnd = true
		end

		self:Show(true)
		if not remaining then
			remaining = sndEndTime - now
		end
		local denominator = (self.moduleSettings.showAsPercentOfMax and CurrMaxSnDDuration or sndDuration)
		self:UpdateBar(denominator ~= 0 and remaining / denominator or 0, "SliceAndDice")
	else
		self:UpdateBar(0, "SliceAndDice")

		if SNDGetComboPoints(self.unit) == 0 or (not UnitExists("target") and not self.moduleSettings.bShowWithNoTarget) or ShouldHide() then
			if self.bIsVisible then
				self.bUpdateSnd = nil
			end

			if not self.moduleSettings.alwaysFullAlpha or ShouldHide() then
				self:Show(false)
			end
		end
	end

	-- somewhat redundant, but we also need to check potential remaining time
	if (remaining ~= nil) or PotentialSnDDuration > 0 then
		local potText = " (" .. PotentialSnDDuration .. ")"
		self:SetBottomText1(self.moduleSettings.upperText .. tostring(floor(remaining or 0)) .. (self.moduleSettings.durationAlpha ~= 0 and potText or ""))
	end
end

function SliceAndDice.prototype:TargetChanged()
	if self.moduleSettings.bShowWithNoTarget and SNDGetComboPoints(self.unit) > 0 then
		self.target = true
	else
		self.target = UnitExists("target")
	end
	self:Update(self.unit)

	self:UpdateDurationBar()
	self:UpdateSliceAndDice()
end

function SliceAndDice.prototype:UpdateDurationBar(event, unit)
	if unit and unit ~= self.unit then
		return
	end

	local points = SNDGetComboPoints(self.unit)

	-- first, set the cached upper limit of SnD duration
	CurrMaxSnDDuration = self:GetMaxBuffTime(maxComboPoints)

	if event then
		self:UpdateSliceAndDice()
	end

	-- player doesn't want to show the percent of max or the alpha is zeroed out, so don't bother with the duration bar
	if not self.moduleSettings.showAsPercentOfMax or self.moduleSettings.durationAlpha == 0 or (points == 0 and not self:IsVisible())
		or ShouldHide() then
		self.durationFrame:Hide()
		return
	end
	self.durationFrame:Show()

	-- if we have combo points and a target selected, go ahead and show the bar so the duration bar can be seen
	if points > 0 and (UnitExists("target") or self.moduleSettings.bShowWithNoTarget) then
		self:Show(true)
	end

	if self.moduleSettings.durationAlpha > 0 then
		PotentialSnDDuration = self:GetMaxBuffTime(points)

		-- compute the scale from the current number of combo points
		local scale = IceHUD:Clamp(PotentialSnDDuration / CurrMaxSnDDuration, 0, 1)

		-- sadly, animation uses bar-local variables so we can't use the animation for 2 bar textures on the same bar element
		if (self.moduleSettings.reverse) then
			scale = 1 - scale
		end

		self.durationFrame.bar:SetVertexColor(self:GetColor("SliceAndDicePotential", self.moduleSettings.durationAlpha))
		self:SetBarCoord(self.durationFrame, scale)
	end

	if sndEndTime < GetTime() then
		local potText = " (" .. PotentialSnDDuration .. ")"
		self:SetBottomText1(self.moduleSettings.upperText .. "0" .. (self.moduleSettings.durationAlpha > 0 and potText or ""))
	end
end

function SliceAndDice.prototype:GetMaxBuffTime(numComboPoints)
	if numComboPoints == 0 then
		return 0
	end

	local maxduration = baseTime + ((numComboPoints - 1) * gapPerComboPoint)

	if self:HasNetherbladeBonus() then
		maxduration = maxduration + netherbladeBonus
	end

	if self:HasNineTailedBonus() then
		maxduration = maxduration + gapPerComboPoint
	end

	if IceHUD.WowVer < 50000 then
		if self:HasGlyphBonus() then
			maxduration = maxduration + glyphBonusSec
		end

		local rank = 0
		if GetTalentInfo then
			local _
			_, _, _, _, rank = GetTalentInfo(impSndTalentPage, impSndTalentIdx)
		end

		maxduration = maxduration * (1 + (rank * impSndBonusPerRank))
	end

	return maxduration
end

function SliceAndDice.prototype:HasNetherbladeBonus()
	local numPieces
	local linkStr, itemId

	numPieces = 0

	-- run through all the possible equip locations of a netherblade piece
	for i=1,#NetherbladeEquipLocList do
		-- pull the link string for the item in this equip loc
		linkStr = GetInventoryItemLink(self.unit, NetherbladeEquipLocList[i])
		-- get the item id out of that link string
		itemId = self:GetItemIdFromItemLink(linkStr)

		-- check if the item id in that slot is part of the netherblade item id list
		if self:IsItemIdInList(itemId, NetherbladeItemIdList) then
			-- increment the fact that we have this piece of netherblade
			numPieces = numPieces + 1

			-- check if we've met the set bonus for slice and dice
			if numPieces >= 2 then
				return true
			end
		end
	end
end

function SliceAndDice.prototype:HasNineTailedBonus()
	local numPieces
	local linkStr, itemId

	numPieces = 0

	-- run through all the possible equip locations of a nine-tailed piece
	for i=1,#NetherbladeEquipLocList do
		-- pull the link string for the item in this equip loc
		linkStr = GetInventoryItemLink(self.unit, NetherbladeEquipLocList[i])
		-- get the item id out of that link string
		itemId = self:GetItemIdFromItemLink(linkStr)

		-- check if the item id in that slot is part of the nine-tailed item id list
		if self:IsItemIdInList(itemId, NineTailedItemIdList) then
			-- increment the fact that we have this piece of nine-tailed
			numPieces = numPieces + 1

			-- check if we've met the set bonus for slice and dice
			if numPieces >= 2 then
				return true
			end
		end
	end
end

function SliceAndDice.prototype:HasGlyphBonus()
	if not GetNumGlyphSockets then
		return false
	end
	for i=1,GetNumGlyphSockets() do
		local enabled, _, _, spell = GetGlyphSocketInfo(i)

		if enabled and spell == GlyphSpellId then
			return true
		end
	end

	return false
end

function SliceAndDice.prototype:GetItemIdFromItemLink(linkStr)
	local itemId
	local _

	if linkStr then
		_, itemId, itemId2, _, _, _, _, _, _ = strsplit(":", linkStr)
	end

	if not tonumber(itemId) then
		itemId = itemId2
	end

	return itemId or 0
end

function SliceAndDice.prototype:IsItemIdInList(itemId, list)
	for i=1,#list do
		if string.match(itemId, list[i]) then
			return true
		end
	end

	return false
end

function SliceAndDice.prototype:OutCombat()
	SliceAndDice.super.prototype.OutCombat(self)

	self:UpdateSliceAndDice()
end

local _, unitClass = UnitClass("player")
-- Load us up
if unitClass == "ROGUE" then
	IceHUD.SliceAndDice = SliceAndDice:new()
end
