require "class"
require "util"
local TechTree = require("techtree")

Ingredient = Class(function(self, ingredienttype, amount, atlas, deconstruct, imageoverride)
    --Character ingredient multiples of 5 check only applies to
    --health and sanity cost, not max health or max sanity
    if ingredienttype == CHARACTER_INGREDIENT.HEALTH or
        ingredienttype == CHARACTER_INGREDIENT.SANITY then
        --V2C: string solution due to inconsistent precision errors with math.floor
        --local x = math.floor(amount)
        local x = tostring(amount)
        x = x:sub(x:find("^%-?%d+"))
        x = tonumber(x:sub(x:len()))
        --NOTE: if you changed CHARACTER_INGREDIENT_SEG, then update this assert
        assert(x == 0 or x == 5, "Character ingredients must be multiples of "..tostring(CHARACTER_INGREDIENT_SEG))
    end
    self.type = ingredienttype
    self.amount = amount
    self.atlas = atlas and resolvefilepath(atlas) or nil
    self.image = imageoverride
    self.deconstruct = deconstruct
end)

function Ingredient:GetAtlas()
    if self.atlas == nil then
       self.atlas = resolvefilepath(GetInventoryItemAtlas(self:GetImage()))
    end
    return self.atlas
end

function Ingredient:GetImage()
    if self.image == nil then
        self.image = self.type..".tex"
    end
    return self.image
end

local num = 0
AllRecipes = {}
AllBuilderTaggedRecipes = {}

local is_character_ingredient = nil
function IsCharacterIngredient(ingredienttype)
    if is_character_ingredient == nil then
        is_character_ingredient = {}
        for k, v in pairs(CHARACTER_INGREDIENT) do
            is_character_ingredient[v] = true
        end
    end
    return ingredienttype ~= nil and is_character_ingredient[ingredienttype] == true
end

local is_tech_ingredient = nil
function IsTechIngredient(ingredienttype)
    if is_tech_ingredient == nil then
        is_tech_ingredient = {}
        for k, v in pairs(TECH_INGREDIENT) do
            is_tech_ingredient[v] = true
        end
    end
    return ingredienttype ~= nil and is_tech_ingredient[ingredienttype] == true
end

mod_protect_Recipe = false

Recipe = Class(function(self, name, ingredients, tab, level, placer_or_more_data, min_spacing, nounlock, numtogive, builder_tag, atlas, image, testfn, product, build_mode, build_distance) -- do not add more params here, add them to "placer_or_more_data"
    if mod_protect_Recipe then
        print("Warning: Calling Recipe from a mod is now deprecated. Please call AddRecipe from your modmain.lua file.")
    end

	local placer = nil
	local more_data = nil
	if type(placer_or_more_data) == "table" then
		placer = placer_or_more_data.placer
		more_data = placer_or_more_data
	else
		placer = placer_or_more_data
        more_data = {}
	end

    self.name          = name

    self.ingredients   = {}
    self.character_ingredients = {}
    self.tech_ingredients = {}
	self.filter = more_data.filter

    for k,v in pairs(ingredients) do
        table.insert(
            (IsCharacterIngredient(v.type) and self.character_ingredients) or
            (IsTechIngredient(v.type) and self.tech_ingredients) or
            self.ingredients,
            v
        )
    end

    self.product       = product or name
    self.tab           = tab					-- DEPRECATED

    self.nameoverride  = more_data.nameoverride -- Override the name string in the crafting menu.
	self.description   = more_data.description -- override the description string in the crafting menu

    self.imagefn       = type(image) == "function" and image or nil
    self.image         = self.imagefn == nil and image or (self.product .. ".tex")
    self.atlas         = (atlas and resolvefilepath(atlas))-- or resolvefilepath(GetInventoryItemAtlas(self.image))
	self.fxover        = more_data.fxover

    --self.lockedatlas   = (lockedatlas and resolvefilepath(lockedatlas)) or (atlas == nil and resolvefilepath("images/inventoryimages_inverse.xml")) or nil
    --self.lockedimage   = lockedimage or (self.product ..".tex")

    self.sortkey       = num
    self.rpc_id        = num --mods will set the rpc_id in SetModRPCID when called by AddRecipe()
    self.level         = TechTree.Create(level)
    self.placer        = placer
    self.min_spacing   = min_spacing or 3.2

    
    self.testfn        = testfn					-- custom placer test function if default test isn't enough
	self.canbuild      = more_data.canbuild		-- custom test function to see if we should be allowed to craft this recipe, return a build action fail message if false

    self.nounlock      = nounlock or false

    self.numtogive     = numtogive or 1

    self.builder_tag   = builder_tag or nil
    self.builder_skill = more_data.builder_skill or nil
	self.sg_state      = more_data.sg_state or more_data.buildingstate or nil -- overrides the SG state to use when crafting the item (buildingstate is the old variable name)

    self.build_mode    = build_mode or BUILDMODE.LAND
    self.build_distance= build_distance or 1

    self.no_deconstruction = more_data.no_deconstruction -- function or bool
    self.require_special_event = more_data.require_special_event

	self.dropitem      = more_data.dropitem

	self.actionstr     = more_data.actionstr
	self.hint_msg      = more_data.hint_msg

	self.manufactured = more_data.manufactured -- if true, then it is up to the crafting station to handle creating the item, not the builder component
    self.station_tag  = more_data.station_tag -- If set to a tag this will only show up in CRAFTING_FILTERS.CRAFTING_STATION when the prototyper machine has this tag.

	self.is_deconstruction_recipe = tab == nil

    self.source_recipename = more_data.source_recipename -- NOTES(JBK): What the basis recipename is used to create this object useful for things like Winona scanner.

    num                = num + 1
    AllRecipes[name]   = self
    if (self.builder_tag ~= nil or self.builder_skill ~= nil) and more_data.allowautopick == nil then -- NOTES(JBK): "donotautopick" filtered items should set allowautopick in the recipe if they are to be picked up by things for the player.
        AllBuilderTaggedRecipes[name] = self.builder_tag or self.builder_skill
    end

    if ModManager then
        for k,recipepostinit in pairs(ModManager:GetPostInitFns("RecipePostInit")) do
            recipepostinit(self)
        end

        for k,recipepostinitany in pairs(ModManager:GetPostInitFns("RecipePostInitAny")) do
            recipepostinitany(self)
        end
    end
end)

function Recipe:GetAtlas()
	self.atlas = self.atlas or resolvefilepath(GetInventoryItemAtlas(self.image))
	return self.atlas
end

function Recipe:SetModRPCID()
    local rpc_id = smallhash(self.name)

    for _,v in pairs(AllRecipes) do
        if v.rpc_id == rpc_id then
            print("ERROR:hash collision between recipe names ", self.name, " and ", v.name )
        end
    end
    self.rpc_id = rpc_id
end

function GetValidRecipe(recname)
    if TheNet == nil or not IsRecipeValidInGameMode(TheNet:GetServerGameMode(), recname) then
        return
    end
    local rec = AllRecipes[recname]
    return rec ~= nil and not rec.is_deconstruction_recipe and (rec.require_special_event == nil or IsSpecialEventActive(rec.require_special_event)) and rec or nil
end

function IsRecipeValid(recname)
    return GetValidRecipe(recname) ~= nil
end

function RemoveAllRecipes()
    AllRecipes = {}
    AllBuilderTaggedRecipes = {}
    num = 0
end

Recipe2 = Class(Recipe, function(self, name, ingredients, tech, config) -- add new optional params to config
	if config ~= nil then
		Recipe._ctor(self, name, ingredients, nil, tech, config, config.min_spacing, config.nounlock, config.numtogive, config.builder_tag, config.atlas, config.image, config.testfn, config.product, config.build_mode, config.build_distance)
	else
		Recipe._ctor(self, name, ingredients, nil, tech)
	end

	self.is_deconstruction_recipe = false
end)

DeconstructRecipe = Class(Recipe, function(self, name, return_ingredients, config)
	Recipe._ctor(self, name, return_ingredients, nil, TECH.NONE, config)
	self.is_deconstruction_recipe = true
	self.nounlock = true
end)
