Quantcast
-- SageGearLevelTooltip.lua
--[[
/console reloadui
]]

----------------------------------------------------------------------------------------------------
local Class = sage and sage.class and sage.class.Class or error("Need access to sage.class.Class")
local Item = sage.item and sage.item.Item or error("Need access to sage.item.Item")
local Reforgings = sage.reforge and sage.reforge.Reforgings or error("Need access to sage.item.Item")

local function newClass(arg)
	return Class:NewClass(arg)
end

----------------------------------------------------------------------------------------------------
local CachingEvaluator do
	local class = newClass{name="CachingEvaluator"}

	function class:_Initialize(instance, delegate)	-- self is class
		self.super:_Initialize(instance)
		instance.delegate = delegate
		instance.cache = {}
		instance.cacheHitCount = 0
		instance.cacheMissCount = 0
	end

	function class:Evaluate(itemLink)
		-- TODO: normalize link first?
		local cached = self.cache[itemLink]
		if cached then
			self.cacheHitCount = self.cacheHitCount + 1
			return unpack(cached)
		end

		local left, leftLabel, right, rightLabel = self.delegate:Evaluate(itemLink)
		if left and leftLabel then
			self.cache[itemLink] = { left, leftLabel, right, rightLabel }
			self.cacheMissCount = self.cacheMissCount + 1
			return left, leftLabel, right, rightLabel
		end
	end

	CachingEvaluator = class
end

----------------------------------------------------------------------------------------------------
--[[
		A base class for Evaluators which *might* need to be cached.
		Just adds a :Cache method which wraps self with a CachingEvaluator
		]]
local CachableEvaluator do
	local class = newClass{name="CachableEvaluator"}

	function class:Cache()
		return CachingEvaluator:New(self)
	end

	CachableEvaluator = class
end

----------------------------------------------------------------------------------------------------
local ItemLevelIdCommenter do
	local class = newClass{name="ItemLevelIdCommenter"}

	function class:Comment(itemLink, tooltip)
		if not itemLink then return end
		local _,_,_,level = GetItemInfo(itemLink)
		local itemId = itemLink:match("item:(%d+):")
		itemId = itemId
		    and tonumber(itemId)
		    or ">>" .. itemLink:gsub("|", "!")
		tooltip:AddDoubleLine(
				"iLevel " .. level,
				"id " .. itemId,
				class.r, class.g, class.b,
				1, 1, 1)
--		return level, {label="iLevel", r=class.r, g=class.g, b=class.b}
	end

	ItemLevelIdCommenter = class
end

----------------------------------------------------------------------------------------------------
local ItemIdEvaluator do
	local class = newClass{name="ItemIdEvaluator"}
	local label = {label = "id", r=0.5, g=1, b=0.5}

	function class:Evaluate(itemLink)
		if not itemLink then return end
		local itemId = itemLink:match("item:(%d+):")
		itemId = itemId
		    and tonumber(itemId)
		    or ">>" .. itemLink:gsub("|", "!")
--		local itemId = Item:New(itemLink):ItemString()
		return itemId, label
	end

	ItemIdEvaluator = class
end

----------------------------------------------------------------------------------------------------
local PairItemCommentor do
	local class = newClass{name="PairItemCommenter", super=CachableEvaluator}

--		local left, leftLabel, right, rightLabel = self.evaluator:Evaluate(itemLink)
--		if left and leftLabel and right and rightLabel then
--			tooltip:AddDoubleLine(
--					leftLabel.label .. ": " .. left,
--					rightLabel.label .. ": " .. right,
--					leftLabel.r, leftLabel.g, leftLabel.b,
--					rightLabel.r, rightLabel.g, rightLabel.b)
--		elseif left and leftLabel then
--			tooltip:AddLine(leftLabel.label .. ": " .. left, leftLabel.r, leftLabel.g, leftLabel.b)
--		end


	function class:_Initialize(instance, left, right)		--self is class
		self.super:_Initialize(instance)
		instance.left = left
		instance.right = right
	end

	function class:Evaluate(itemLink)
		if not itemLink then return end
		local left, leftLabel = self.left:Evaluate(itemLink)
		if left and leftLabel then
--			local right, rightLabel = self.right:Evaluate(itemLink)
--			return left, leftLabel, right, rightLabel
			return left, leftLabel, self.right:Evaluate(itemLink)
		end
	end

	PairItemCommentor = class
end

----------------------------------------------------------------------------------------------------
local ITEM_MOD_NAME_MAP = {
	EMPTY_SOCKET_RED="RedSocket",
	EMPTY_SOCKET_BLUE="BlueSocket",
	EMPTY_SOCKET_YELLOW="YellowSocket",
	EMPTY_SOCKET_PRISMATIC="PrismaticSocket",
	ITEM_MOD_FIRE_RESISTANCE_SHORT="FireResist",
	ITEM_MOD_FROST_RESISTANCE_SHORT="FrostResist",

	ITEM_MOD_CRIT_RATING_SHORT="Crit",
	ITEM_MOD_CRIT_RANGED_RATING_SHORT="CritRanged",
	ITEM_MOD_DODGE_RATING_SHORT="Dodge",
	ITEM_MOD_EXPERTISE_RATING_SHORT="Expertise",
	ITEM_MOD_HASTE_RATING_SHORT="Haste",
	ITEM_MOD_HIT_RATING_SHORT="Hit",
	ITEM_MOD_MASTERY_RATING_SHORT="Mastery",
	ITEM_MOD_PARRY_RATING_SHORT="Parry",
	ITEM_MOD_RESILIENCE_RATING_SHORT="Resilience",

	ITEM_MOD_ATTACK_POWER_SHORT="AP",
	ITEM_MOD_DAMAGE_PER_SECOND_SHORT="DPS",
	ITEM_MOD_AGILITY_SHORT="Agility",
	ITEM_MOD_INTELLECT_SHORT="Intellect",
	ITEM_MOD_SPELL_POWER_SHORT="SP",
	ITEM_MOD_SPIRIT_SHORT="Spirit",
	ITEM_MOD_STAMINA_SHORT="Stamina",
	ITEM_MOD_STRENGTH_SHORT="Strength",

	RESISTANCE0_NAME="Armor",
	RESISTANCE2_NAME="FireResist",
	RESISTANCE4_NAME="FrostResist*",		-- duplicate?
}

----------------------------------------------------------------------------------------------------
local SimplePawnEvaluator do

	local class = newClass{name="SimplePawnEvaluator", super=CachableEvaluator}

	function class:_Initialize(instance, label, coefficients)		--self is class
		self.super:_Initialize(instance)
		instance.coefficients = coefficients
		instance.label = label
	end

	function class:Evaluate(itemLink)
		if not itemLink then return end
		local stats = self:_GetItemStats(itemLink)
		local total = 0
		local missedStat, missedStatLabel = nil, nil
		for k,v in pairs(stats) do
			local stat = ITEM_MOD_NAME_MAP[k]
			if not stat then
				missedStat, missedStatLabel = v, {label=k, r=class.r, g=class.g, b=class.b}
			end

			total = total + v * (self.coefficients[stat] or 0)
		end
		if total > 0 then
			return math.floor(0.5 + total), self.label, missedStat, missedStatLabel
		elseif missedStat then
			return missedStat, missedStatLabel
		end
	end

	function class:_GetItemStats(itemLink)		-- broken out to be overridable
		return GetItemStats(itemLink)
	end

	SimplePawnEvaluator = class
end

----------------------------------------------------------------------------------------------------
local AsReforgedPawnEvaluator do
	local class = newClass{name="AsReforgedPawnEvaluator", super=SimplePawnEvaluator}

	local reforgings = Reforgings:New()

	function class:_GetItemStats(itemLink)
		local reforging = reforgings:Find(itemLink)
		return Item:New(itemLink):GetItemStats()
	end

	AsReforgedPawnEvaluator = class
end

----------------------------------------------------------------------------------------------------
local BestReforgedEvaluator do
	local class = newClass{name="BestReforgedEvaluator", super=CachableEvaluator}

	local reforgings = Reforgings:New()
	local reforgingLabel = {label = "", r=0.6, g=0.6, b=0.6}

	function class:_Initialize(instance, label, coefficients)		--self is class
		self.super:_Initialize(instance)
		instance.coefficients = coefficients
		instance.label = label
	end

	function class:_GetItemStats(itemLink)
		local reforging = reforgings:Find(itemLink)
		return Item:New(itemLink):GetItemStats()
	end

	function class:Evaluate(itemLink)
		if not itemLink then return end
		itemLink = Item:New(itemLink):WithoutMods():ItemString()
		local stats = GetItemStats(itemLink)	-- baseline w/o reforging
		local rightValue, rightLabel = nil, nil

		local reforging = self:_FindOptimalReforging(stats)
		if reforging then
			reforging:AdjustStats(stats)
			rightValue, rightLabel = reforging:Describe(), reforgingLabel
		end

		local evaluation = self:_EvalStats(stats)
		return evaluation, self.label, rightValue, rightLabel
	end

	function class:_FindOptimalReforging(stats)
		local maxDelta, maxReforging = 0, nil
		for index,reforging in pairs(reforgings:All()) do
			local from, to = reforging:GetShortNames()
			local c0 = self.coefficients[ITEM_MOD_NAME_MAP[from]] or 0
			local c1 = self.coefficients[ITEM_MOD_NAME_MAP[to]] or 0
			if stats[from] and (not stats[to]) and (c1 > c0) then
				local delta = stats[from] * (c1 - c0)		--don't bother to scale by 40% here
				if delta > maxDelta then
					maxDelta, maxReforging = delta, reforging
				end
			end
		end
		return maxReforging
	end

	function class:_EvalStats(stats)
		local total = 0
		for k,v in pairs(stats) do
			local stat = ITEM_MOD_NAME_MAP[k]
			total = total + v * (self.coefficients[stat] or 0)
		end
		return math.floor(0.5 + total)
	end

	function class:_GetItemStats(itemLink)		-- broken out to be overridable
		return GetItemStats(itemLink)
	end

	BestReforgedEvaluator = class
end

----------------------------------------------------------------------------------------------------
local EvaluatorToLineAdderAdaptor do
	local class = newClass{name="EvaluatorToLineAdderAdaptor"}

	function class:_Initialize(instance, evaluator)
		instance.evaluator = evaluator
	end

	-- Adds lines for the given item
	-- @param itemLink a link for the item
	-- @param tooltip a callback supporting :AddLine and :AddDoubleLine with same args as a GameTooltip
	--        NOTE: tooltip is not guaranteed to be an actual tooltip, or support other methods
	function class:Comment(itemLink, tooltip)
		local left, leftLabel, right, rightLabel = self.evaluator:Evaluate(itemLink)
		if left and leftLabel and right and rightLabel then
			tooltip:AddDoubleLine(
					leftLabel.label .. ": " .. left,
					rightLabel.label .. ": " .. right,
					leftLabel.r, leftLabel.g, leftLabel.b,
					rightLabel.r, rightLabel.g, rightLabel.b)
		elseif left and leftLabel then
			tooltip:AddLine(leftLabel.label .. ": " .. left, leftLabel.r, leftLabel.g, leftLabel.b)
		end
	end

	EvaluatorToLineAdderAdaptor = class
end

----------------------------------------------------------------------------------------------------
local SageGearLevelTooltipUpdater do
	local class = newClass{name="TooltipUpdater"}

	-- class function
	function class:_Initialize(instance, methodsToHook)
		-- no call to super needed
		instance.evals = {}
		instance.commenters = {}
		return instance:_HookFrameMethods(methodsToHook)
	end

	-- Updates the tooltip with all the comments from the item commenters
	-- @param tooltip A "tooltip" support :AddLine and :AddDoubleLine *only*
	function class:UpdateTooltip(tooltip)
		local _,itemLink = tooltip:GetItem()
		for _,commenter in ipairs(self.commenters) do
			commenter:Comment(itemLink, tooltip)
		end
	end

	function class:_HookFrameMethods()
--		for tooltipName, methods in pairs(methodsToHook) do
--			self:HookTooltip(tooltipName, methods)
--		end
		self:Print("SageGearLevelTooltip loaded.")
		return self
	end

	function class:HookTooltip(tooltipName, methods)
		local tooltip = _G[tooltipName]
		local update =
				function(tooltip, ...)
						self:UpdateTooltip(tooltip)
				end
		for _, method in ipairs(methods) do
--			self:Debug((tooltip and tooltipName or "whoops") .. "/" .. type(tooltip) .. "+" .. method .. "/" .. type(method))
			hooksecurefunc(tooltip, method, update)
		end
		return self;
	end

	function class:AddEvaluator(e)
		return self:AddItemCommenter(EvaluatorToLineAdderAdaptor:New(e))
	end

	-- Adds an item commenter to this tooltip annotator
	-- @param commenter an ItemCommenter to give comment to this tooltip
	function class:AddItemCommenter(commenter)
		local commenters = self.commenters
		commenters[#commenters + 1] = commenter
		return self
	end

	SageGearLevelTooltipUpdater = class
end

----------------------------------------------------------------------------------------------------
--local methodsToHook = {
--	GameTooltip = {"SetBagItem", "SetInventoryItem", "SetHyperlink", "SetAuctionItem",
--				"SetQuestItem", "SetQuestLogItem"},
--	ItemRefTooltip = {"SetHyperlink", "Show", "Hide"},
--	ShoppingTooltip1 = {"SetHyperlinkCompareItem"},
--	ShoppingTooltip2 = {"SetHyperlinkCompareItem"}}

SageGearLevelTooltipUpdater:New(methodsToHook)
		:HookTooltip("GameTooltip", {"SetBagItem", "SetInventoryItem", "SetHyperlink", "SetAuctionItem",
					"SetQuestItem", "SetQuestLogItem"})
		:HookTooltip("ItemRefTooltip", {"SetHyperlink", "Show", "Hide"})
		:HookTooltip("ShoppingTooltip1", {"SetHyperlinkCompareItem"})
		:HookTooltip("ShoppingTooltip2", {"SetHyperlinkCompareItem"})


		:AddItemCommenter(ItemLevelIdCommenter:New())

		--Warlock
--		:AddEvaluator(BestReforgedEvaluator:New({label = "Wk.Aff", r=1.0, g=0.2, b=1.0},
--				{Hit=100, Master=100, SP=72, Haste=61, Crit=38, Spirit=34, Intellect=15})
--				:Cache())
--		:AddEvaluator(BestReforgedEvaluator:New({label = "Wk.Dem", r=0.8, g=0.2, b=1.0},
--				{Hit=100, Master=100, Haste=50, SP=45, Crit=31, Spirit=29, Intellect=13})
--				:Cache())

		-- Hunter
--		:AddEvaluator(BestReforgedEvaluator:New({label = "Hr.Su", r=1.0, g=0.7, b=0.3},
--				{RangedDPS=181, Mastery=100, Hit=100, Agility=76, Crit=42, Intellect=35, Haste=31})
--				:Cache())

		-- Worlock
--		:AddEvaluator(BestReforgedEvaluator:New({label = "Wk.Aff", r=0.4, g=1.0, b=0.2},
--				{Hit=100, Mastery=100, SP=72, Haste=61, Crit=38, Spirit=34, Intellect=15})
--				:Cache())
--		:AddEvaluator(BestReforgedEvaluator:New({label = "Wk.Demo", r=0.8, g=0.2, b=1.0},
--				{Hit=100, Mastery=100, Haste=50, SP=45, Crit=31, Spirit=29, Intellect=13})
--				:Cache())

		-- Warrior
		:AddEvaluator(BestReforgedEvaluator:New({label = "W.Best", r=0.8, g=0.8, b=1.0},
				{Strength=48, Agility=67, Stamina=100, Hit=10, Crit=7, Haste=1, AP=1, Expertise=19, Armor=6, Dodge=90, Parry=67})
				:Cache())

--		:AddEvaluator(SimplePawnEvaluator:New({label = "W.Simple", r=0.8, g=0.8, b=1.0},
--				{Strength=48, Agility=67, Stamina=100, Hit=10, Crit=7, Haste=1, AP=1, Expertise=19, Armor=6, Dodge=90, Parry=67})
--				:Cache())
----		:AddEvaluator(SimplePawnEvaluator:New({label = "M.Simple", r=0.8, g=1, b=0.6},
----				{Mastery=1})
----				:Cache())
--		:AddEvaluator(AsReforgedPawnEvaluator:New({label = "W.Reforged", r=0.8, g=0.8, b=1.0},
--				{Strength=48, Agility=67, Stamina=100, Hit=10, Crit=7, Haste=1, AP=1, Expertise=19, Armor=6, Dodge=90, Parry=67})
--				:Cache())
----		:AddEvaluator(AsReforgedPawnEvaluator:New({label = "M.Reforged", r=0.8, g=1, b=0.6},
----				{Mastery=1})
----				:Cache())
--		:AddEvaluator(BestReforgedEvaluator:New({label = "W.Best", r=0.8, g=0.8, b=1.0},
--				{Strength=48, Agility=67, Stamina=100, Hit=10, Crit=7, Haste=1, AP=1, Expertise=19, Armor=6, Dodge=90, Parry=67})
--				:Cache())
----		:AddEvaluator(BestReforgedEvaluator:New({label = "D.Best", r=0.8, g=1, b=0.6},
----				{Dodge=1})
----				:Cache())
----		:AddEvaluator(BestReforgedEvaluator:New({label = "Hunter.SV", r=1.0, g=0.8, b=0.4},
----				{RangedDPS=181, Hit=100, Master=100, Agility=76, Crit=42, Intellect=35, Haste=31})
----				:Cache())