-- ------------------------------------------------------------------------------ --
--                            TradeSkillMaster_Crafting                           --
--            http://www.curse.com/addons/wow/tradeskillmaster_crafting           --
--                                                                                --
--             A TradeSkillMaster Addon (http://tradeskillmaster.com)             --
--    All Rights Reserved* - Detailed license information included with addon.    --
-- ------------------------------------------------------------------------------ --

-- load the parent file (TSM) into a local variable and register this file as a module
local TSM = select(2, ...)
local Queue = TSM:NewModule("Queue")
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster_Crafting") -- loads the localization table

local MAX_QUEUE_STAGES = 10
local inventoryTotals = {}


local notified = {}
function Queue:ValidateOperation(operation, opName)
	if not operation then return end

	if operation.minRestock > operation.maxRestock then
		-- invalid cause min > max restock quantity (shouldn't happen)
		if not notified[opName] then
			notified[opName] = true
			TSM:Printf(L["'%s' is an invalid operation! Min restock of %d is higher than max restock of %d."], opName, operation.minRestock, operation.maxRestock)
		end
		return
	end
	return true
end

function Queue:CreateRestockQueue(groupInfo)
	TSM:UpdateCraftReverseLookup()
	local numItems = 0

	for _, data in pairs(groupInfo) do
		for _, opName in ipairs(data.operations) do
			TSMAPI:UpdateOperation("Crafting", opName)
			local opSettings = TSM.operations[opName]
			if Queue:ValidateOperation(opSettings, opName) then
				-- it's a valid operation
				for itemString in pairs(data.items) do
					itemString = TSMAPI:GetItemString(itemString)
					local spellID = TSM.craftReverseLookup[itemString] and TSM.craftReverseLookup[itemString][1]
					if spellID and TSM.db.realm.crafts[spellID] then
						local maxQueueCount = max(opSettings.maxRestock - TSM.Inventory:GetTotalQuantity(itemString), 0)
						local numToQueue = 0

						if not opSettings.minProfit then
							-- no minimum, so queue as many as we can
							numToQueue = maxQueueCount
						else
							-- queue it if the profit is at least the min profit
							local cheapestSpellID, cost, _, profit = TSM.Cost:GetLowestCraftPrices(itemString)
							local minProfit = TSM:GetCustomPrice(opSettings.minProfit, itemString)
							if minProfit and profit and profit >= minProfit then
								spellID = cheapestSpellID
								numToQueue = maxQueueCount
							end
						end

						local craft = TSM.db.realm.crafts[spellID]
						craft.queued = floor(numToQueue / craft.numResult)
						craft.queued = craft.queued >= opSettings.minRestock and craft.queued or 0
						if craft.queued > 0 then
							numItems = numItems + 1
						end
					end
				end
			end
		end
	end
	
	if numItems==0 then
		TSM:Printf("There are no items to restock.")
	end
	TSMAPI:FireEvent("CRAFTING:QUEUE:RESTOCKED", numItems)
end

function Queue:HasLoop(itemString, steps, visited)
	steps = steps + 1
	if steps > 10 then return true end
	if visited[itemString] then return true end
	visited[itemString] = true
	local craftCost = TSM:GetCustomPrice("Crafting", itemString)
	local mat = TSM.db.realm.mats[itemString]
	local lowestCost = TSM:GetCustomPrice(mat.customValue or TSM.db.global.defaultMatCostMethod, itemString)
	if craftCost and lowestCost and craftCost <= lowestCost and (not TSM.db.global.neverCraftInks or not TSMAPI.InkConversions[itemString]) then
		local spellID = TSM.Cost:GetLowestCraftPrices(itemString, true)
		if spellID and TSM.db.realm.crafts[spellID] then
			for matItemString in pairs(TSM.db.realm.crafts[spellID].mats) do
				if Queue:HasLoop(matItemString, steps, CopyTable(visited)) then
					return true
				end
			end
		end
	end
end

function Queue:GetIntermediateCrafts(mats, usedItems, usedMats, tempMats)
	local subCrafts = {}
	for profession, data in pairs(mats) do
		tempMats[profession] = tempMats[profession] or {}
		for itemString, quantity in pairs(data) do
			usedItems[itemString] = usedItems[itemString] or 0
			local numHave = (inventoryTotals[itemString] or 0) - usedItems[itemString]
			if TSMAPI.SOULBOUND_MATS[itemString] then
				numHave = numHave + GetItemCount(itemString, true)
			end
			if numHave > 0 then
				if numHave >= quantity then
					usedItems[itemString] = usedItems[itemString] + quantity
					tempMats[profession][itemString] = (tempMats[profession][itemString] or 0) + quantity
					quantity = 0
					mats[profession][itemString] = nil
				else
					usedItems[itemString] = numHave
					quantity = quantity - numHave
					tempMats[profession][itemString] = (tempMats[profession][itemString] or 0) + numHave
					mats[profession][itemString] = mats[profession][itemString] - numHave
				end
			end

			if quantity > 0 and not Queue:HasLoop(itemString, 0, {}) then
				local mat = TSM.db.realm.mats[itemString]
				local craftCost = TSM:GetCustomPrice("Crafting", itemString)
				local lowestCost = TSM:GetCustomPrice(mat.customValue or TSM.db.global.defaultMatCostMethod, itemString)
				if craftCost and lowestCost and craftCost <= lowestCost and (not TSM.db.global.neverCraftInks or not TSMAPI.InkConversions[itemString]) then
					local spellID = TSM.Cost:GetLowestCraftPrices(itemString, true)
					if spellID and TSM.db.realm.crafts[spellID] then
						local numResult = TSM.db.realm.crafts[spellID].numResult
						quantity = ceil(quantity / numResult)
						subCrafts[TSM.db.realm.crafts[spellID].profession] = subCrafts[TSM.db.realm.crafts[spellID].profession] or {}
						subCrafts[TSM.db.realm.crafts[spellID].profession][spellID] = (subCrafts[TSM.db.realm.crafts[spellID].profession][spellID] or 0) + quantity
						TSM.db.realm.crafts[spellID].queued = TSM.db.realm.crafts[spellID].queued + quantity
						TSM.db.realm.crafts[spellID].intermediateQueued = (TSM.db.realm.crafts[spellID].intermediateQueued or 0) + quantity
						mats[profession][itemString] = nil
						usedMats[profession] = usedMats[profession] or {}
						usedMats[profession][itemString] = numHave
					end
				end
			end
		end
	end

	local newSubCrafts
	for profession, data in pairs(subCrafts) do
		for spellID, quantity in pairs(data) do
			newSubCrafts = true
			for itemString, matQuantity in pairs(TSM.db.realm.crafts[spellID].mats) do
				mats[profession] = mats[profession] or {}
				mats[profession][itemString] = (mats[profession][itemString] or 0) + matQuantity * quantity
			end
		end
	end
	return newSubCrafts and subCrafts
end

function Queue:GetQueue()
	local tempCrafts, queuedCrafts, mats = {}, {}, {}
	local totalCost, totalProfit

	-- first queue up all the normally queued stuff
	for spellID, data in pairs(TSM.db.realm.crafts) do
		if data.intermediateQueued then
			data.queued = max(data.queued - data.intermediateQueued, 0)
			data.intermediateQueued = nil
		end
		if data.queued > 0 then
			local cost, buyout, profit = TSM.Cost:GetCraftPrices(spellID)
			if cost then
				totalCost = (totalCost or 0) + cost * data.queued
			end
			if profit then
				totalProfit = (totalProfit or 0) + profit * data.queued
			end
			tempCrafts[data.profession] = tempCrafts[data.profession] or {}
			tempCrafts[data.profession][spellID] = data.queued
			for itemString, quantity in pairs(data.mats) do
				mats[data.profession] = mats[data.profession] or {}
				mats[data.profession][itemString] = (mats[data.profession][itemString] or 0) + quantity * data.queued
			end
		end
	end
	for profession, data in pairs(tempCrafts) do
		queuedCrafts[profession] = queuedCrafts[profession] or {}
		tinsert(queuedCrafts[profession], 1, { crafts = data })
	end

	-- queue intermediate crafts
	local usedItems, usedMats, tempMats = {}, {}, {}
	inventoryTotals = select(4, TSM.Inventory:GetTotals())
	for i = 1, MAX_QUEUE_STAGES - 1 do
		local subCrafts = Queue:GetIntermediateCrafts(mats, usedItems, usedMats, tempMats)
		if not subCrafts then break end

		for profession, data in pairs(subCrafts) do
			queuedCrafts[profession] = queuedCrafts[profession] or {}
			tinsert(queuedCrafts[profession], 1, { crafts = data })
		end
	end

	-- readd usedMats back in
	for profession, data in pairs(tempMats) do
		for itemString, quantity in pairs(data) do
			mats[profession][itemString] = (mats[profession][itemString] or 0) + quantity
		end
	end

	for profession, data in pairs(usedMats) do
		if mats[profession] then
			for itemString, quantity in pairs(data) do
				mats[profession][itemString] = (mats[profession][itemString] or 0) + quantity
			end
		end
	end

	-- give each stage the appropriate name
	for profession, stages in pairs(queuedCrafts) do
		for i, stageInfo in ipairs(stages) do
			stageInfo.name = format(L["Stage %d"], i)
		end
	end

	totalCost = totalCost or "---"
	totalProfit = totalProfit or "---"
	return queuedCrafts, mats, totalCost, totalProfit
end

function Queue:ClearQueue()
	for spellID, data in pairs(TSM.db.realm.crafts) do
		data.queued = 0
		data.intermediateQueued = nil
	end
end

function Queue:addQueue(spellID, quantity)
	local craft = TSM.db.realm.crafts[spellID]
	if not craft then return end
	quantity = quantity or 1
	craft.queued = craft.queued + quantity
	TSM.CraftingGUI:UpdateQueue()
end

function Queue:removeQueue(spellID, quantity)
	local craft = TSM.db.realm.crafts[spellID]
	if not craft then return end
	quantity = quantity or 1
	craft.queued = max(craft.queued - quantity, 0)
	TSM.CraftingGUI:UpdateQueue()
end

function Queue:getQueue(spellID)
	local craft = TSM.db.realm.crafts[spellID]
	return craft.queued
end