--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| 	Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]


PLUGIN.name = "Test"
PLUGIN.author = "Gr4Ss"
PLUGIN.description = ""

PLUGIN.ingIndex = {}
PLUGIN.ingredients = {}
PLUGIN.itemRecipes = {}
PLUGIN.uncraftables = {}
PLUGIN.baseResourceCost = {}
PLUGIN.recipeTier = {}
PLUGIN.smugglingWorth = {}
PLUGIN.makeFromBaseCost = {}
PLUGIN.recipeVariantPreference = {
	["comp_unrefinedchemical"] = "break_artwhiskey_chemical",
	["comp_adhesive"] = "rec_comp_adhesive",
	["comp_strong_adhesive"] = "rec_comp_strong_adhesive",
}

PLUGIN.costOverride = {
	["drink_proc_whiskey"] = 12,
	["drink_breen_water"] = 5
}

PLUGIN.bIncludeCooking = false

function PLUGIN:InitializedPlugins()
	self:CalculateCraftingMatrix()
end

function PLUGIN:CalculateCraftingMatrix()
	self:ParseRecipes()
	self:ManualCraftingRecipe("comp_bloodsyringe", 1, {["comp_syringe"] = 1})
	if (self.bIncludeCooking) then
		self:ManualCraftingRecipe("baking_bread_slice", 8, {["baking_bread"] = 1}, {"tool_knife"})
	end

	for k, v in pairs(ix.plugin.list.smuggler.itemList) do
		self.smugglingWorth[k] = v.buy / (v.stackSize or 1)
		self.makeFromBaseCost[k] = v.sell / (v.stackSize or 1)
	end

	for k, v in pairs(self.costOverride) do
		self.makeFromBaseCost[k] = v
	end

	self:AddToolsAsIngredients()

	self:CalculateUncraftables()

	self:CreateCraftingMatrixCSV()

	--[[
	for k, v in pairs(ix.recipe.stored) do
		if (!self:IsValidRecipe(v)) then continue end
		for result, amount in pairs(v.result) do
			if (!ingIndex[result]) then
				ingIndex[result] = #ingredients + 1
				ingredients[#ingredients + 1] = {
					sources = {v.name},
					craftable = true
				}
			else
				ingredients[ ingIndex[result] ].craftable = true
				table.insert(ingredients[ ingIndex[result] ].sources, v.name)
			end
		end
	end
	--]]

	self:CalculateResultCosts()
	table.SortByMember(self.uncraftables, "count")

	local changed = true
	local loop = 0
	while (changed and loop < 20) do
		changed = false
		loop = loop + 1
		for k, v in pairs(self.baseResourceCost) do
			local cost = 0
			for ing, amount in pairs(v) do
				if (!self.makeFromBaseCost[ing]) then
					--print("missing sell price", ing)
					continue
				end
				cost = cost + amount * self.makeFromBaseCost[ing]
			end

			local craftingRecipes = self.itemRecipes[k]
			if (craftingRecipes) then
				for _, data in pairs(craftingRecipes) do
					if (#craftingRecipes > 1 and data.id != self.recipeVariantPreference[k]) then continue end
					local cost2 = 0
					for ing, amount in pairs(data.ingredients) do
						if (!self.makeFromBaseCost[ing]) then
							--print("missing sell price", ing)
							continue
						end
						cost2 = cost2 + amount * self.makeFromBaseCost[ing]
					end

					cost = math.min(cost2, cost)
				end
			end
			local newCost = math.min(self.makeFromBaseCost[k] or math.huge, cost)
			if (!self.makeFromBaseCost[k] or self.makeFromBaseCost[k] != newCost) then
				changed = true
			end
			self.makeFromBaseCost[k] = math.min(self.makeFromBaseCost[k] or math.huge, cost)
		end
	end
	self:CreateResultCostCSV()

	--PrintTable(self.uncraftables)
end

local whitelist = {["crafting"] = true, ["medicine"] = true}
if (PLUGIN.bIncludeCooking) then whitelist["cooking"] = true end
function PLUGIN:IsValidRecipe(recipe)
	if (!whitelist[recipe.skill]) then return false end
	if (recipe.category == "Matériaux") then return false end
	if (!recipe.ingredients or !recipe.result) then return false end

	return true
end

function PLUGIN:AddNewIngredient(ingredient)
	if (!self.ingIndex[ingredient]) then
		self.ingIndex[ingredient] = #self.ingredients + 1
		self.ingredients[#self.ingredients + 1] = {
			id = ingredient,
			sources = {},
			craftable = self.itemRecipes[ingredient] != nil
		}
	end
end

function PLUGIN:SetIngredientCraftable(ingredient, name)
	if (self.ingIndex[ingredient]) then
		self.ingredients[self.ingIndex[ingredient]].craftable = true
		table.insert(self.ingredients[self.ingIndex[ingredient]].sources, name)
	end
end

function PLUGIN:AddToolsAsIngredients()
	for result, variants in pairs(self.itemRecipes) do
		for variant, data in ipairs(variants) do
			for _, tool in ipairs(data.tools) do
				self:AddNewIngredient(tool)
			end
		end
	end
end

function PLUGIN:ManualCraftingRecipe(result, amount, ingredients, tools, station, skill, level)
	for ingredient in pairs(ingredients) do
		self:AddNewIngredient(ingredient)
	end

	self.itemRecipes[result] = self.itemRecipes[result] or {}
	table.insert(self.itemRecipes[result], {
		amount = amount,
		ingredients = ingredients,
		tools = tools or {},
		station = station,
		recipe = "Manual Crafting",
		skill = skill or "None",
		level = level or 0
	})
	self.ingredients[self.ingIndex[result]].craftable = true
	table.insert(self.ingredients[self.ingIndex[result]].sources, "Manual Crafting")
end

function PLUGIN:ParseRecipes()
	for _, recipe in pairs(ix.recipe.stored) do
		if (!self:IsValidRecipe(recipe)) then continue end
		for ingredient, amount in pairs(recipe.ingredients) do
			self:AddNewIngredient(ingredient)
		end

		for result, amount in pairs(recipe.result) do
			self:SetIngredientCraftable(result, recipe.name)

			self.itemRecipes[result] = self.itemRecipes[result] or {}
			table.insert(self.itemRecipes[result], {
				id = recipe.uniqueID,
				amount = amount,
				ingredients = recipe.ingredients,
				tools = recipe.tools,
				station = recipe.station,
				recipe = recipe.name,
				skill = recipe.skill,
				level = recipe.level
			})

			if (!recipe.tools) then
				self.itemRecipes[result][#self.itemRecipes[result]].tools = {recipe.tool}
			end
		end
	end
end

function PLUGIN:CalculateUncraftables()
	for k, v in ipairs(self.ingredients) do
		if (!v.craftable) then
			self.uncraftables[#self.uncraftables + 1] = {id = v.id, count = 0}
		end
	end
end

function PLUGIN:IsUncraftable(item)
	for k, v in ipairs(self.uncraftables) do
		if (v.id == item) then
			return true
		end
	end
end

function PLUGIN:SelectSingleVariant(result, variants)
	if (#variants > 1) then
		if (self.recipeVariantPreference[result]) then
			for _, data in pairs(variants) do
				if (data.id ==self.recipeVariantPreference[result]) then
					return data.ingredients, data.amount
				end
			end
		else
			table.SortByMember(variants, "level", true)
			self.recipeVariantPreference[result] = variants[1].id
			return variants[1].ingredients, variants[1].amount
		end
	else
		return variants[1].ingredients, variants[1].amount
	end
end

function PLUGIN:CalculateBaseResourceCost(ingredients, resAmount)
	local inputs = {}
	for ing, amount in pairs(ingredients) do
		if (self:IsUncraftable(ing)) then
			inputs[ing] = (inputs[ing] or 0) + amount / resAmount
		elseif (self.baseResourceCost[ing]) then
			for resIng, resAm in pairs(self.baseResourceCost[ing]) do
				inputs[resIng] = (inputs[resIng] or 0) + resAm * amount / resAmount
			end
		else
			return false
		end
	end

	return inputs
end

function PLUGIN:CalculateResultCosts()
	local bNewFound = false
	local iters = 1
	repeat
		-- reset loop condition
		local tempResult = {}
		bNewFound = false
		for result, variants in pairs(self.itemRecipes) do
			-- prevent doing the same recipe again and again
			if (self.baseResourceCost[result]) then continue end

			local ingredients, amount = self:SelectSingleVariant(result, variants)
			if (!ingredients) then print("PANIC", result) end

			local inputs = self:CalculateBaseResourceCost(ingredients, amount)
			if (inputs) then
				bNewFound = true
				self.recipeTier[result] = iters
				tempResult[result] = inputs
				for ing in pairs(inputs) do
					for k, tbl in ipairs(self.uncraftables) do
						if (tbl.id == ing) then
							self.uncraftables[k].count = (self.uncraftables[k].count or 0) + 1
							break
						end
					end
				end
			end
		end

		for k, v in pairs(tempResult) do
			self.baseResourceCost[k] = v
		end

		iters = iters + 1
	until bNewFound == false
end

function PLUGIN:CreateCraftingMatrixCSV()
	if (file.Exists("craftingMatrix.csv", "DATA")) then
		file.Delete("craftingMatrix.csv")
	end

	local outputString = ""
	local ingOffset = 5
	for i = 1, ingOffset do
		outputString = outputString..";"
	end
	for k, v in ipairs(self.ingredients) do
		outputString =  outputString..v.id..";"
	end
	file.Append("craftingMatrix.csv", outputString.."\n")

	for result, variants in pairs(self.itemRecipes) do
		table.SortByMember(variants, "level", true)
		for variant, data in ipairs(variants) do
			local outputRow = {result, data.recipe, data.skill, data.level, data.amount}
			for ingredient, amount in pairs(data.ingredients) do
				outputRow[self.ingIndex[ingredient] + ingOffset] = amount
			end

			for _, tool in ipairs(data.tools) do
				outputRow[self.ingIndex[tool] + ingOffset] = "T"
			end

			outputString = ""
			for i = 1, #self.ingredients + ingOffset do
				outputString = outputString..string.gsub(outputRow[i] or "", "%.", ",")..";"
			end
			file.Append("craftingMatrix.csv", outputString.."\n")
		end
	end
end

function PLUGIN:CreateResultCostCSV()
	if (file.Exists("craftingBaseResourceMatrix.csv", "DATA")) then
		file.Delete("craftingBaseResourceMatrix.csv")
	end

	local outputString = ""
	local ingOffset = 5
	for i = 1, ingOffset do
		outputString = outputString..";"
	end
	for k, v in ipairs(self.uncraftables) do
		outputString =  outputString..v.id..";"
	end
	file.Append("craftingBaseResourceMatrix.csv", outputString.."\n")

	for result, costs in pairs(self.baseResourceCost) do
		local outputRow = {result, self.recipeTier[result], self.smugglingWorth[result] or "N/A", self.makeFromBaseCost[result], math.max((self.smugglingWorth[result] or 0) - self.makeFromBaseCost[result])}
		for ingredient, amount in pairs(costs) do
			for k, uncraft in ipairs(self.uncraftables) do
				if (uncraft.id == ingredient) then
					outputRow[k + ingOffset] = amount
				end
			end
		end

		outputString = ""
		for i = 1, #self.uncraftables + ingOffset do
			outputString = outputString..string.gsub(outputRow[i] or "", "%.", ",")..";"
		end
		file.Append("craftingBaseResourceMatrix.csv", string.gsub(outputString, "%.", ",").."\n")
	end
end