--[[

	TechAge
	=======

	Copyright (C) 2019 Joachim Stolberg

	AGPL v3
	See LICENSE.txt for more information

	Meltingpot to produce metal and alloy ingots

]]--

local S = techage.S

local SMELTING_TIME = 2

local Tabs = S("Menu,Recipes")

local Recipes = {}     -- registered recipes
local KeyList = {}     -- index to Recipes key translation
local NumRecipes = 0
local Cache = {}       -- store melting pot inventory data

-- formspec images
local function draw(images)
	local tbl = {}
	for y=0,4 do
		for x=0,4 do
			local idx = 1 + x + y * 5
			local img = images[idx]
			if img ~= false then
				tbl[#tbl+1] = "image["..(x*0.8)..","..(y*0.8)..";0.8,0.8;"..img..".png]"
			end
		end
	end
	return table.concat(tbl)
end

local formspec1 =
	"size[8,8]"..
	default.gui_bg..
	default.gui_bg_img..
	default.gui_slots..
	"tabheader[0,0;tab;"..Tabs..";1;;true]"..
	"label[1,0.2;"..S("Menu").."]"..

	"container[1,1]"..
	"list[current_name;src;0,0;2,2;]"..
	"item_image[2.6,0;0.8,0.8;techage:meltingpot]"..
	"image[2.3,0.6;1.6,1;gui_furnace_arrow_bg.png^[transformR270]"..
	"list[current_name;dst;4,0;2,2;]"..
	"container_end[]"..

	"list[current_player;main;0,4;8,4;]"..
	"listring[current_name;dst]"..
	"listring[current_player;main]"..
	"listring[current_name;src]"..
	"listring[current_player;main]"

local function formspec2(idx)
	idx = math.min(idx, #KeyList)
	local key = KeyList[idx]
	local input1 = Recipes[key].input[1] or ""
	local input2 = Recipes[key].input[2] or ""
	local input3 = Recipes[key].input[3] or ""
	local input4 = Recipes[key].input[4] or ""
	local num = Recipes[key].number
	local heat = Recipes[key].heat
	local time = Recipes[key].time
	local output = Recipes[key].output
	if num > 1 then
		output = output.." "..num
	end
	return "size[8,8]"..
	default.gui_bg..
	default.gui_bg_img..
	default.gui_slots..
	"tabheader[0,0;tab;"..Tabs..";2;;true]"..
	"label[1,0.2;"..S("Melting Guide").."]"..

	"container[1,1]"..
	"item_image_button[0,0;1,1;"..input1..";b1;]"..
	"item_image_button[1,0;1,1;"..input2..";b2;]"..
	"item_image_button[0,1;1,1;"..input3..";b3;]"..
	"item_image_button[1,1;1,1;"..input4..";b4;]"..
	"item_image[2.6,0;0.8,0.8;techage:meltingpot]"..
	"image[2.3,0.6;1.6,1;gui_furnace_arrow_bg.png^[transformR270]"..
	"item_image_button[4,0.5;1,1;"..output..";b5;]"..
	"label[2,2.2;"..S("Heat")..": "..heat.."  /  "..S("Time")..": "..time.." s]"..
	"label[2,4;Recipe "..idx.." of "..NumRecipes.."]"..
	"button[2,5.5;1,1;priv;<<]"..
	"button[3,5.5;1,1;next;>>]"..
	"container_end[]"
end

local function on_receive_fields(pos, formname, fields, sender)
	local meta = minetest.get_meta(pos)
	local recipe_idx = meta:get_int("recipe_idx")
	if recipe_idx == 0 then recipe_idx = 1 end
	if fields.tab == "1" then
		meta:set_string("formspec", formspec1)
	elseif fields.tab == "2" then
		meta:set_string("formspec", formspec2(recipe_idx))
	elseif fields.next == ">>" then
		recipe_idx = math.min(recipe_idx + 1, NumRecipes)
		meta:set_int("recipe_idx", recipe_idx)
		meta:set_string("formspec", formspec2(recipe_idx))
	elseif fields.priv == "<<" then
		recipe_idx = math.max(recipe_idx - 1, 1)
		meta:set_int("recipe_idx", recipe_idx)
		meta:set_string("formspec", formspec2(recipe_idx))
	end
end

local function can_dig(pos, player)
	local meta = minetest.get_meta(pos);
	local inv = meta:get_inventory()
	return inv:is_empty("dst") and inv:is_empty("src")
end

local function allow_metadata_inventory_put(pos, listname, index, stack, player)
	if minetest.is_protected(pos, player:get_player_name()) then
		return 0
	end
	if listname == "src" then
		return stack:get_count()
	elseif listname == "dst" then
		return 0
	end
end

local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	local stack = inv:get_stack(from_list, from_index)
	return allow_metadata_inventory_put(pos, to_list, to_index, stack, player)
end

local function allow_metadata_inventory_take(pos, listname, index, stack, player)
	if minetest.is_protected(pos, player:get_player_name()) then
		return 0
	end
	return stack:get_count()
end

-- generate an unique key based on the unsorted and
-- variable number of inventory items
local function recipe_key(items)
	local tbl = {}
	-- remove items which exist more than once
	for _,item in ipairs(items) do
		tbl[item] = true
	end
	local names = {}
	for key,_ in pairs(tbl) do
		names[#names + 1] = key
	end
	-- bring in a sorted order
	table.sort(names)
	return table.concat(names, "-")
end

-- determine recipe based on inventory items
local function get_recipe(inv)
	-- collect items
	local stacks = {}
	local names = {}
	for _,stack in ipairs(inv:get_list("src")) do
		if not stack:is_empty() then
			table.insert(names, stack:get_name())
			table.insert(stacks, stack)
		else
			table.insert(stacks, ItemStack(""))
		end
	end
	local key = recipe_key(names)
	local recipe = Recipes[key]

	if recipe then
		return {
			input = recipe.input,
			stacks = stacks,
			output = ItemStack(recipe.output.." "..recipe.number),
			heat = recipe.heat,
			time = recipe.time,
		}
	end
	return nil
end

-- prepare recipe and store in cache table for faster access
local function store_recipe_in_cache(pos)
	local hash = minetest.hash_node_position(pos)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	local recipe = get_recipe(inv)
	Cache[hash] = recipe
	return recipe
end

-- read value from the node below
local function get_heat(pos)
	local heat = 0
	pos.y = pos.y - 1
	local node = techage.get_node_lvm(pos)
	local meta = minetest.get_meta(pos)
	if minetest.get_item_group(node.name, "techage_flame") == 0 then
		pos.y = pos.y + 1
		return 0
	end

	pos.y = pos.y - 1
	node = techage.get_node_lvm(pos)
	pos.y = pos.y + 2
	if minetest.get_item_group(node.name, "techage_flame") == 0 and
			node.name ~= "techage:charcoal_burn" then
		return 0
	end

	return meta:get_int("heat")
end

-- Start melting if heat is ok AND source items available
function techage.switch_to_active(pos)
	local meta = minetest.get_meta(pos)
	local heat = get_heat(pos)
	local recipe = store_recipe_in_cache(pos)

	if recipe and heat >= recipe.heat then
		minetest.swap_node(pos, {name = "techage:meltingpot_active"})
		minetest.registered_nodes["techage:meltingpot_active"].on_construct(pos)
		meta:set_string("infotext", S("Melting Pot active (heat=")..heat..")")
		minetest.get_node_timer(pos):start(2)
		return true
	end
	meta:set_string("infotext", S("Melting Pot inactive (heat=")..heat..")")
	return false
end

function techage.update_heat(pos)
	local meta = minetest.get_meta(pos)
	local heat = get_heat(pos)
	meta:set_string("infotext", S("Melting Pot inactive (heat=")..heat..")")
end

local function set_inactive(meta, pos, heat)
	minetest.get_node_timer(pos):stop()
	minetest.swap_node(pos, {name = "techage:meltingpot"})
	minetest.registered_nodes["techage:meltingpot"].on_construct(pos)
	meta:set_string("infotext", S("Melting Pot inactive (heat=")..heat..")")
end

-- Stop melting if heat to low OR no source items available
local function switch_to_inactive(pos)
	local meta = minetest.get_meta(pos)
	local heat = get_heat(pos)
	local hash = minetest.hash_node_position(pos)
	local recipe = Cache[hash] or store_recipe_in_cache(pos)

	if not recipe or heat < recipe.heat then
		set_inactive(meta, pos, heat)
		return true
	end
	meta:set_string("infotext", S("Melting Pot active (heat=")..heat..")")
	return false
end


local function index(list, x)
	for idx, v in pairs(list) do
		if v == x then return idx end
	end
	return nil
end

-- move recipe src items to output inventory
local function process(inv, recipe, heat)
	if heat < recipe.heat then
		return false
	end
	local res = false
	if inv:room_for_item("dst", recipe.output) then
		for _,item in ipairs(recipe.input) do
			res = false
			for _, stack in ipairs(recipe.stacks) do
				if stack:get_count() > 0 and stack:get_name() == item then
					stack:take_item(1)
					res = true
					break
				end
			end
			if res == false then
				return false
			end
		end
		inv:add_item("dst", recipe.output)
		inv:set_list("src", recipe.stacks)
		return true
	end
	return false
end

local function smelting(pos, recipe, heat, elapsed)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	elapsed = elapsed + meta:get_int("leftover")

	while elapsed >= recipe.time do
		if process(inv, recipe, heat) == false then
			meta:set_int("leftover", 0)
			set_inactive(meta, pos, heat)
			return false
		end
		elapsed = elapsed - recipe.time
	end
	meta:set_int("leftover", elapsed)
	return true
end

local function pot_node_timer(pos, elapsed)
	if switch_to_inactive(pos) == false then
		local hash = minetest.hash_node_position(pos)
		local heat = get_heat(pos)
		local recipe = Cache[hash] or store_recipe_in_cache(pos)
		if recipe then
			return smelting(pos, recipe, heat, elapsed)
		end
	end
	return false
end

minetest.register_node("techage:meltingpot_active", {
	description = S("TA1 Melting Pot"),
	tiles = {
		{
			name = "techage_meltingpot_top_active.png",
			backface_culling = false,
			animation = {
				type = "vertical_frames",
				aspect_w = 16,
				aspect_h = 16,
				length = 1,
			},
		},
		"default_cobble.png^techage_meltingpot.png",
	},
	drawtype = "nodebox",
	node_box = {
		type = "fixed",
		fixed = {
			{-10/16, -8/16, -10/16,  10/16, 9/16,  -6/16},
			{-10/16, -8/16,   6/16,  10/16, 9/16,  10/16},
			{-10/16, -8/16, -10/16,  -6/16, 9/16,  10/16},
			{  6/16, -8/16, -10/16,  10/16, 9/16,  10/16},
			{ -6/16, -8/16,  -6/16,   6/16, 5/16,   6/16},
		},
	},
	selection_box = {
		type = "fixed",
		fixed = {-10/16, -8/16, -10/16,  10/16, 9/16,  10/16},
	},

	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("formspec", formspec1)
		local inv = meta:get_inventory()
		inv:set_size('src', 4)
		inv:set_size('dst', 4)
	end,

	on_timer = function(pos, elapsed)
		return pot_node_timer(pos, elapsed)
	end,

	on_receive_fields = function(pos, formname, fields, sender)
		on_receive_fields(pos, formname, fields, sender)
	end,

	on_metadata_inventory_move = function(pos)
		store_recipe_in_cache(pos)
		switch_to_inactive(pos)
	end,

	on_metadata_inventory_put = function(pos)
		store_recipe_in_cache(pos)
		switch_to_inactive(pos)
	end,

	on_metadata_inventory_take = function(pos)
		store_recipe_in_cache(pos)
		switch_to_inactive(pos)
	end,

	can_dig = can_dig,

	paramtype = "light",
	use_texture_alpha = techage.CLIP,
	drop = "techage:meltingpot",
	is_ground_content = false,
	groups = {cracky = 3, not_in_creative_inventory=1},
	sounds = default.node_sound_metal_defaults(),

	allow_metadata_inventory_put = allow_metadata_inventory_put,
	allow_metadata_inventory_move = allow_metadata_inventory_move,
	allow_metadata_inventory_take = allow_metadata_inventory_take,
})

minetest.register_node("techage:meltingpot", {
	description = S("TA1 Melting Pot"),
	tiles = {
		"default_cobble.png",
		"default_cobble.png^techage_meltingpot.png",
	},
	drawtype = "nodebox",
	node_box = {
		type = "fixed",
		fixed = {
			{-10/16, -8/16, -10/16, 10/16,  9/16, -6/16},
			{-10/16, -8/16,   6/16, 10/16,  9/16, 10/16},
			{-10/16, -8/16, -10/16, -6/16,  9/16, 10/16},
			{  6/16, -8/16, -10/16, 10/16,  9/16, 10/16},
			{ -6/16, -8/16,  -6/16,  6/16, -4/16,  6/16},
		},
	},
	selection_box = {
		type = "fixed",
		fixed = {-10/16, -8/16, -10/16, 10/16, 9/16, 10/16},
	},

	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("formspec", formspec1)
		meta:set_string("infotext", S("Melting Pot inactive (heat=0)"))
		local inv = meta:get_inventory()
		inv:set_size('src', 4)
		inv:set_size('dst', 4)
	end,

	on_metadata_inventory_move = function(pos)
		store_recipe_in_cache(pos)
		techage.switch_to_active(pos)
	end,

	on_metadata_inventory_put = function(pos)
		store_recipe_in_cache(pos)
		techage.switch_to_active(pos)
	end,

	on_metadata_inventory_take = function(pos)
		store_recipe_in_cache(pos)
		techage.switch_to_active(pos)
	end,

	on_receive_fields = function(pos, formname, fields, sender)
		on_receive_fields(pos, formname, fields, sender)
	end,

	can_dig = can_dig,

	paramtype = "light",
	use_texture_alpha = techage.CLIP,
	is_ground_content = false,
	groups = {cracky = 3},
	sounds = default.node_sound_metal_defaults(),

	allow_metadata_inventory_put = allow_metadata_inventory_put,
	allow_metadata_inventory_move = allow_metadata_inventory_move,
	allow_metadata_inventory_take = allow_metadata_inventory_take,
})

minetest.register_craft({
	output = "techage:meltingpot",
	recipe = {
		{"default:cobble", "default:copper_ingot", "default:cobble"},
		{"default:cobble", "",                     "default:cobble"},
		{"default:cobble", "default:cobble",       "default:cobble"},
	},
})

techage.recipes.register_craft_type("melting", {
	description = S("TA1 Melting"),
	icon = "default_cobble.png^techage_meltingpot.png",
	width = 2,
	height = 2,
})
techage.recipes.register_craft_type("burning", {
	description = S("TA1 Burning"),
	icon = "techage_smoke.png",
	width = 1,
	height = 1,
})
techage.recipes.register_craft({
	output = "techage:charcoal",
	items = {"group:wood"},
	type = "burning",
})

function techage.ironage_register_recipe(recipe)
	local key = recipe_key(recipe.recipe)
	local output = string.split(recipe.output, " ")
	local number = tonumber(output[2] or 1)
	table.insert(KeyList, key)
	Recipes[key] = {
		input = recipe.recipe,
		output = output[1],
		number = number,
		heat = math.max(recipe.heat or 3, 2),
		time = math.max(recipe.time or 2, 2*number),
	}
	NumRecipes = NumRecipes + 1

	recipe.items = recipe.recipe
	recipe.type = "melting"
	techage.recipes.register_craft(recipe)
end
