local S = minetest.get_translator("crafting_bench")
local F = minetest.formspec_escape
local C = minetest.colorize

minetest.register_alias("castle:workbench", "crafting_bench:workbench")

local has_mcl = minetest.get_modpath("mcl_formspec")
local has_default = minetest.get_modpath("default")

local usage_help = S("The inventory on the left is for raw materials, the inventory on the right holds finished products. The crafting grid in the center defines what recipe this workbench will make use of; place raw materials into it in the crafting pattern corresponding to what you want to build.")

if ( minetest.get_modpath("hopper") and hopper ~= nil and hopper.add_container ~= nil ) or has_mcl then
	usage_help = usage_help .. "\n\n" .. S("This workbench is compatible with hoppers. Hoppers will insert into the raw material inventory and remove items from the finished goods inventory.")
end


local crafting_rate = tonumber(minetest.settings:get("crafting_bench_crafting_rate")) or 5

if not has_default and not has_mcl then
	error("The crafting bench mod needs either the default mod (minetest game) or mineclonia/mineclone2 to work")
end

local invsize_src = 2 * 4
local invsize_dst = 1 * 4

if has_mcl then
	invsize_src = 2 * 3
	invsize_dst = 1 * 3
end

local groups, mcl_hardness, mcl_blast_res, sounds
local steel_ingot, wood, tree

local formspec
if has_default then
	formspec = 'size[10,10;]' ..
		default.gui_bg ..
		default.gui_bg_img ..
		default.gui_slots ..
		'label[1,0;'..S('Source Material')..']' ..
		'list[context;src;1,1;2,4;]' ..
		'label[4,0;'..S('Recipe to Use')..']' ..
		'list[context;rec;4,1;3,3;]' ..
		'label[7.5,0;'..S('Craft Output')..']' ..
		'list[context;dst;8,1;1,4;]' ..
		'list[current_player;main;1,6;8,4;]' ..
		'listring[current_name;dst]'..
		'listring[current_player;main]'..
		'listring[current_name;src]'..
		'listring[current_player;main]'
	groups = {choppy=2, oddly_breakable_by_hand=2, flammable=2}
	sounds = default.node_sound_wood_defaults()
elseif has_mcl then
	formspec ='formspec_version[4]'..
		'size[11.75,10.425]'..

		mcl_formspec.get_itemslot_bg_v4(1, 0.75, 2, 3)..
		mcl_formspec.get_itemslot_bg_v4(5, 0.75, 3, 3)..
		mcl_formspec.get_itemslot_bg_v4(10, 0.75, 1, 3)..
		'label[1,0.375;'..S('Source Material')..']' ..
		'list[context;src;1,0.75;2,4;]' ..
		'label[5,0.375;'..S('Recipe to Use')..']' ..
		'list[context;rec;5,0.75;3,3;]' ..
		'label[9.5,0.375;'..S('Craft Output')..']' ..
		'list[context;dst;10,0.75;1,4;]' ..

		'label[0.375,4.7;' .. F(C(mcl_formspec.label_color, S('Inventory'))) .. ']'..
		mcl_formspec.get_itemslot_bg_v4(0.375, 5.1, 9, 3)..
		'list[current_player;main;0.375,5.1;9,3;9]'..
		mcl_formspec.get_itemslot_bg_v4(0.375, 9.05, 9, 1)..
		'list[current_player;main;0.375,9.05;9,1;]'..

		'listring[current_name;dst]'..
		'listring[current_player;main]'..
		'listring[current_name;src]'..
		'listring[current_player;main]'
	groups = {axey=2, handy=1, flammable=-1, container = 4}
	sounds = mcl_sounds.node_sound_wood_defaults()
	mcl_hardness = 3
	mcl_blast_res = 3
end

minetest.register_node("crafting_bench:workbench",{
	description = S("Workbench"),
	_doc_items_longdesc = S("A workbench that does work for you. Set a crafting recipe and provide raw materials and items will magically craft themselves once every @1 seconds.", crafting_rate),
	_doc_items_usagehelp = usage_help,
	tiles = {
		"crafting_bench_workbench_top.png",
		"crafting_bench_workbench_bottom.png",
		"crafting_bench_workbench_side.png",
		"crafting_bench_workbench_side.png",
		"crafting_bench_workbench_back.png",
		"crafting_bench_workbench_front.png"
		},
	paramtype2 = "facedir",
	paramtype = "light",
	groups = groups,
	sounds = sounds,
	drawtype = "normal",
	_mcl_hardness = mcl_hardness,
	_mcl_blast_resistance = mcl_blast_res,
	on_construct = function ( pos )
		local meta = minetest.get_meta( pos )
		meta:set_string( 'formspec', formspec)
		meta:set_string( 'infotext', S('Workbench'))
		local inv = meta:get_inventory()
		inv:set_size( 'src', invsize_src )
		inv:set_size( 'rec', 3 * 3 )
		inv:set_size( 'dst', invsize_dst )
	end,
	can_dig = function(pos,player)
		local meta = minetest.get_meta(pos);
		local inv = meta:get_inventory()
		return inv:is_empty("src") and inv:is_empty("rec") and inv:is_empty("dst")
	end,
	on_blast = function(pos) end,
	on_metadata_inventory_put = function(pos, listname, index, stack, player)
		minetest.log("action", player:get_player_name().." put "..stack:to_string().." in workbench at "..minetest.pos_to_string(pos))
	end,
	on_metadata_inventory_take = function(pos, listname, index, stack, player)
		minetest.log("action", player:get_player_name().." takes "..stack:to_string().." from workbench at "..minetest.pos_to_string(pos))
	end,
	allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
		if not minetest.is_player(player) or minetest.is_protected(pos, player:get_player_name()) then
			return 0
		end

		if to_list == "dst" then
			-- Only allow to take from the output
			return 0
		elseif to_list == "rec" then
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			local stack = inv:get_stack(from_list, from_index)
			stack:set_count(1)
			inv:set_stack(to_list, to_index, stack)

			if from_list == "rec" then
				-- For convenience: emulate movement instead of duplication
				inv:set_stack(from_list, from_index, "")
			end
			return 0
		elseif from_list == "rec" then
			-- Remove recipe stack
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			inv:set_stack(from_list, from_index, "")
			return 0
		end

		return count
	end,
	allow_metadata_inventory_put = function(pos, listname, index, stack, player)
		if not minetest.is_player(player) or minetest.is_protected(pos, player:get_player_name()) then
			return 0
		end

		if listname == "rec" then
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			stack:set_count(1)
			inv:set_stack("rec", index, stack)
			return 0
		elseif listname == "dst" then
			return 0
		end

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

		if listname == "rec" then
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			inv:set_stack("rec", index, "")
			return 0
		end

		return stack:get_count()
	end,
})
local get_recipe = function ( inv )
	local output, needed, decremented_input
	needed = inv:get_list( 'rec' )

	output, decremented_input = minetest.get_craft_result( {
		method = 'normal',
		width = 3,
		items = needed
	})

	local totalneed = {}

	if output.item:is_empty() then
		output = nil
	else
		output = output.item
		for _, item in ipairs( needed ) do
			if item ~= nil and not item:is_empty() and not inv:contains_item( 'src', item ) then
				output = nil
				break
			end
			if item ~= nil and not item:is_empty() then
				if totalneed[item:get_name()] == nil then
					totalneed[item:get_name()] = 1
				else
					totalneed[item:get_name()] = totalneed[item:get_name()] + 1
				end
			end
		end
		for name, number in pairs( totalneed ) do
			local totallist = inv:get_list( 'src' )
			for i, srcitem in pairs( totallist ) do
				if srcitem:get_name() == name then
					local taken = srcitem:take_item( number )
					number = number - taken:get_count()
					totallist[i] = srcitem
				end
				if number <= 0 then
					break
				end
			end
			if number > 0 then
				output = nil
				break
			end
		end
	end

	return needed, decremented_input, output
end

minetest.register_abm( {
	nodenames = { 'crafting_bench:workbench' },
	interval = crafting_rate,
	chance = 1,
	action = function ( pos, node )
		local meta = minetest.get_meta( pos )
		local inv = meta:get_inventory()
		local output, decremented_input, needed
		if not inv:is_empty( 'src' ) then
			-- Check for a valid recipe and sufficient resources to craft it
			needed, decremented_input, output = get_recipe( inv )
			if output ~= nil and inv:room_for_item( 'dst', output) then
				inv:add_item( 'dst', output)
				for _, item in pairs( needed ) do
					if item ~= nil and item ~= '' then
						inv:remove_item( 'src', ItemStack( item ) )
					end
				end
				for i = 1, 9 do
					inv:add_item( 'dst', decremented_input.items[i] )
				end
			end
		end
	end
} )

-- Make sure that all stacks in the 'recipe' inv count 1 item.
-- Bigger stacks result in item duplication, see issue#14
minetest.register_lbm({
	name = "crafting_bench:cleanup_rec_inv",
	label = "remove multiple-item-stacks from recipe inv",
	nodenames = {"crafting_bench:workbench"},
	run_at_every_load = false,
	action = function(pos, node)
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		-- `get_list` may return `nil` when the node was placed without calling `on_construct`
		for i, item in ipairs(inv:get_list("rec") or {}) do
			-- Limit to stack size 1 (or keep empty)
			inv:set_stack("rec", i, item:peek_item())
			local leftover = inv:add_item("dst", item)
			if not leftover:is_empty() then
				minetest.log("action", "[crafting_bench] deleting leftover " ..
					item:to_string() .. " from recipe inv at " .. minetest.pos_to_string(pos))
			end
		end
	end
})

-- Crafting recipe compatibility.
if has_default then
	steel_ingot = "default:steel_ingot"
	wood = "default:wood"
	tree = "default:tree"
elseif has_mcl then
	steel_ingot = "mcl_core:iron_ingot"
	wood = "mcl_core:wood"
	tree = "mcl_core:tree"
end

minetest.register_craft({
	output = "crafting_bench:workbench",
	recipe = {
		{steel_ingot, steel_ingot, steel_ingot},
		{wood, wood, steel_ingot},
		{tree, tree, steel_ingot},
	}
})

-- Hopper compatibility
if minetest.get_modpath("hopper") and hopper ~= nil and hopper.add_container ~= nil then
	hopper:add_container({
		{"top", "crafting_bench:workbench", "dst"},
		{"side", "crafting_bench:workbench", "src"},
		{"bottom", "crafting_bench:workbench", "src"},
	})
end
