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 ItemLevelEvaluator do
	local class = newClass{name="ItemLevelEvaluator"}

	function class:Evaluate(itemLink)
		if not itemLink then return end
		local _,_,_,level = GetItemInfo(itemLink)
		return level, {label="iLevel", r=class.r, g=class.g, b=class.b}
	end

	ItemLevelEvaluator = 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 PairEvaluator do
	local class = newClass{name="PairEvaluator", super=CachableEvaluator}

	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

	PairEvaluator = 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:AddLines(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"}

	function class:UpdateTooltip(tooltip)
		local _,itemLink = tooltip:GetItem()
		for _,adder in ipairs(self.adders) do
			adder:AddLines(itemLink, tooltip)
		end
	end

	function class:_HookFrameMethods(methodsToHook)
		for tooltipName, methods in pairs(methodsToHook) do
			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
		end
		self:Print("SageGearLevelTooltip loaded.")
		return self
	end

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

	function class:AddLineAdder(adder)
		local adders = self.adders
		adders[#adders + 1] = adder
		return self
	end

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

	SageGearLevelTooltipUpdater = class
end

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

local foo = SageGearLevelTooltipUpdater:New(methodsToHook)
		:AddEvaluator(PairEvaluator:New(ItemLevelEvaluator:New(), ItemIdEvaluator:New()):Cache())

		--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())