-- ------------------------------------------------------------------------------ --
--                                TradeSkillMaster                                --
--                          https://tradeskillmaster.com                          --
--    All Rights Reserved - Detailed license information included with addon.     --
-- ------------------------------------------------------------------------------ --

local _, TSM = ...
local Conversions = TSM.Init("Service.Conversions")
local DisenchantInfo = TSM.Include("Data.DisenchantInfo")
local Mill = TSM.Include("Data.Mill")
local Prospect = TSM.Include("Data.Prospect")
local Transform = TSM.Include("Data.Transform")
local VendorTrade = TSM.Include("Data.VendorTrade")
local TempTable = TSM.Include("Util.TempTable")
local ItemString = TSM.Include("Util.ItemString")
local Table = TSM.Include("Util.Table")
local ItemInfo = TSM.Include("Service.ItemInfo")
Conversions.METHOD = {
	DISENCHANT = newproxy(),
	MILL = newproxy(),
	PROSPECT = newproxy(),
	TRANSFORM = newproxy(),
	VENDOR_TRADE = newproxy(),
	CRAFT = newproxy(),
}
local private = {
	data = {},
	sourceItemCache = {},
	skippedConversions = {},
}
local MAX_CONVERSION_DEPTH = 3
local EMPTY_CONVERSION = newproxy()



-- ============================================================================
-- Module Loading
-- ============================================================================

Conversions:OnModuleLoad(function()
	Table.SetReadOnly(Conversions.METHOD)
	for itemString in DisenchantInfo.TargetItemIterator() do
		ItemInfo.FetchInfo(itemString)
	end
	for targetItemString in Mill.TargetItemIterator() do
		for sourceItemString in Mill.SourceItemIterator(targetItemString) do
			local rate = Mill.GetRate(targetItemString, sourceItemString)
			private.Add(targetItemString, sourceItemString, Conversions.METHOD.MILL, rate)
		end
	end
	for targetItemString in Prospect.TargetItemIterator() do
		for sourceItemString in Prospect.SourceItemIterator(targetItemString) do
			local rate, amount, minAmount, maxAmount = Prospect.GetRate(targetItemString, sourceItemString)
			private.Add(targetItemString, sourceItemString, Conversions.METHOD.PROSPECT, rate, amount, minAmount, maxAmount)
		end
	end
	for targetItemString in Transform.TargetItemIterator() do
		for sourceItemString in Transform.SourceItemIterator(targetItemString) do
			local rate = Transform.GetRate(targetItemString, sourceItemString)
			private.Add(targetItemString, sourceItemString, Conversions.METHOD.TRANSFORM, rate)
		end
	end
	for targetItemString in VendorTrade.TargetItemIterator() do
		for sourceItemString in VendorTrade.SourceItemIterator(targetItemString) do
			local rate = VendorTrade.GetRate(targetItemString, sourceItemString)
			private.Add(targetItemString, sourceItemString, Conversions.METHOD.VENDOR_TRADE, rate)
		end
	end
end)



-- ============================================================================
-- Module Functions
-- ============================================================================

function Conversions.AddCraft(targetItemString, sourceItemString, rate)
	private.Add(targetItemString, sourceItemString, Conversions.METHOD.CRAFT, rate)
end

function Conversions.TargetItemsByMethodIterator(sourceItemString, method)
	local context = TempTable.Acquire()
	context.sourceItemString = sourceItemString
	context.method = method
	return private.TargetItemsByMethodIteratorHelper, context, nil
end

function Conversions.GetTargetItemByName(targetItemName)
	targetItemName = strlower(targetItemName)
	for targetItemString in pairs(private.data) do
		local name = ItemInfo.GetName(targetItemString)
		if name and strlower(name) == targetItemName then
			return targetItemString
		end
	end
	for targetItemString in DisenchantInfo.TargetItemIterator() do
		local name = ItemInfo.GetName(targetItemString)
		if name and strlower(name) == targetItemName then
			return targetItemString
		end
	end
end

function Conversions.GetSourceItems(targetItemString)
	if not targetItemString or not private.data[targetItemString] or private.sourceItemCache[targetItemString] == EMPTY_CONVERSION then
		return
	end
	if not private.sourceItemCache[targetItemString] then
		local depthLookup = TempTable.Acquire()
		depthLookup[targetItemString] = -1 -- set this so we don't loop back through the target item
		private.sourceItemCache[targetItemString] = {}
		private.GetSourceItemsHelper(targetItemString, private.sourceItemCache[targetItemString], depthLookup, 0, 1)
		TempTable.Release(depthLookup)
		if not next(private.sourceItemCache[targetItemString]) then
			private.sourceItemCache[targetItemString] = EMPTY_CONVERSION
			return
		end
	end
	return private.sourceItemCache[targetItemString]
end



-- ============================================================================
-- Private Helper Functions
-- ============================================================================

function private.Add(targetItemString, sourceItemString, method, rate, amount, minAmount, maxAmount)
	targetItemString = ItemString.GetBase(targetItemString)
	sourceItemString = ItemString.GetBase(sourceItemString)
	assert(targetItemString and sourceItemString)

	private.data[targetItemString] = private.data[targetItemString] or {}
	if private.data[targetItemString][sourceItemString] then
		-- if there is more than one way to go from source to target, then just skip all conversions between these items
		private.skippedConversions[targetItemString..sourceItemString] = true
		private.data[targetItemString][sourceItemString] = nil
	end
	if private.skippedConversions[targetItemString..sourceItemString] then
		return
	end

	private.data[targetItemString][sourceItemString] = {
		method = method,
		rate = rate,
		amount = amount,
		minAmount = minAmount,
		maxAmount = maxAmount,
	}
	ItemInfo.FetchInfo(targetItemString)
	ItemInfo.FetchInfo(sourceItemString)
	wipe(private.sourceItemCache)
end

function private.GetSourceItemsHelper(targetItemString, result, depthLookup, currentDepth, currentRate)
	if currentDepth >= MAX_CONVERSION_DEPTH or not private.data[targetItemString] then
		return
	end
	for sourceItemString, info in pairs(private.data[targetItemString]) do
		if not result[sourceItemString] or depthLookup[sourceItemString] > currentDepth then
			local rate = info.rate * currentRate
			result[sourceItemString] = rate
			depthLookup[sourceItemString] = currentDepth
			private.GetSourceItemsHelper(sourceItemString, result, depthLookup, currentDepth + 1, rate)
		end
	end
end

function private.TargetItemsByMethodIteratorHelper(context, index)
	while true do
		index = next(private.data, index)
		local items = private.data[index]
		if not items then
			TempTable.Release(context)
			return
		end
		local info = items[context.sourceItemString]
		if info and ((not context.method and info.method ~= Conversions.METHOD.CRAFT) or info.method == context.method) then
			return index, info.rate, info.amount, info.minAmount, info.maxAmount
		end
	end
end
