-- alloying furnace
--
-- there are several kinds of alloy furnace, with varying
-- capabilities. the simplest can alloy two simple metals;
-- the most complex can alloy four high-grade metals such
-- as titanium, platinum, iridium, or levitanium.
--
-- furnace recipes follow a pattern: the number of crucibles
-- determines the input slots, the type of crucible determines
-- how hot the furnace can get, and various other components
-- (like a coolant circulator) can be added to allow the
-- creation of exotic metals.
--
-- alloy furnaces produce ingots that can later be melted down
-- again and cast into a mold to produce items of particular
-- shapes.

-- there are four kinds of crucibles: clay, aluminum, platinum,
-- and duranium.  clay crucibles are made by molding clay into
-- the proper shape and then firing it in a furnace. others
-- are made by casting.

local fragments_per_ingot = 4

for _, c in pairs { 'clay', 'aluminum', 'platinum', 'duranium' } do
	minetest.register_craftitem('sorcery:crucible_' .. c, {
		description = sorcery.lib.str.capitalize(c .. ' crucible');
		inventory_image = 'sorcery_crucible_' .. c .. '.png';
		groups = { sorcery_metallurgy = 1; }
	})
end

minetest.register_craftitem('sorcery:crucible_clay_molding', {
	description = sorcery.lib.str.capitalize('Crucible molding');
	inventory_image = 'sorcery_crucible_clay_molding.png';
	groups = { sorcery_metallurgy = 1; }
})

minetest.register_craft {
	recipe = {
		{ 'default:clay_lump', '', 'default:clay_lump'};
		{ 'default:clay_lump', '', 'default:clay_lump'};
		{ 'default:clay_lump', 'default:clay_lump', 'default:clay_lump'};
	};
	output = 'sorcery:crucible_clay_molding';
}
minetest.register_craft {
	type = 'shapeless';
	recipe = { 'sorcery:crucible_clay_molding' };
	output = 'default:clay_lump 7';
}
minetest.register_craft {
	type = 'cooking';
	recipe = 'sorcery:crucible_clay_molding';
	cooktime = 40;
	output = 'sorcery:crucible_clay';
}

local burn_layout = function(v)
	local layouts = {
		[1] = {w = 1, h = 1}; [2] = {w = 2, h = 1}; [3] = {w = 3, h = 1};
		[4] = {w = 2, h = 2}; [5] = {w = 3, h = 2}; [6] = {w = 3, h = 2};
		[7] = {w = 4, h = 2}; [8] = {w = 4, h = 2}; [9] = {w = 3, h = 3};
	}
	local inpos  = { x = 2.8, y = 1 }
	local insize = layouts[v.inp]
	local outpos = { x = 5.2, y = 2.4 }
	local outsize = layouts[v.out]
	local fuelpos = { x = 2.8, y = 3.4 }
	local fuelsize = layouts[v.fuel]
	return string.format([[
		list[context;input;%f,%f;%f,%f;]
		list[context;output;%f,%f;%f,%f;]
		list[context;fuel;%f,%f;%f,%f;]
		
		image[2.3,1.9;1,1;default_furnace_fire_bg.png^[lowpart:%u%%:default_furnace_fire_fg.png]
		image[3.2,1.9;1,1;gui_furnace_arrow_bg.png^[lowpart:%u%%:gui_furnace_arrow_fg.png^[transformR270]

		listring[context;output] listring[current_player;main]
		listring[context;input]  listring[current_player;main]
		listring[context;fuel]   listring[current_player;main]
	]], 

	--input
		inpos.x - insize.w/2, -- pos
		inpos.y - insize.h/2, 
		insize.w, insize.h, -- size
	--output
		outpos.x - outsize.w/2, -- pos
		outpos.y - outsize.h/2, 
		outsize.w, outsize.h, -- size
	--fuel
		fuelpos.x - fuelsize.w/2, -- pos
		fuelpos.y - fuelsize.h/2, 
		fuelsize.w, fuelsize.h, -- size

		v.burn, v.prog
	)
end

local kiln_formspec = function(kind, fuel_progress, cook_progress)
	return [[
		size[8,8]
		list[current_player;main;0,4.2;8,4]
		list[context;wax;0,2;1,1]
	]] .. burn_layout{
		inp = kind.size^2;
		out = kind.outsize;
		fuel = kind.fuelsize;
		burn = fuel_progress;
		prog = cook_progress;
	}
end

local smelter_formspec = function(kind, fuel_progress, smelt_progress)
	return [[
		size[8,8]
		list[current_player;main;0,4.2;8,4]
	]] .. burn_layout {
		inp  = kind.size;
		out = kind.outsize;
		fuel = kind.fuelsize;
		burn = fuel_progress;
		prog = smelt_progress;
	}
end

local find_recipe = function(inv)
	local mix = {}
	local count = 0
	for i=1,inv:get_size('input') do
		local m = inv:get_stack('input',i)
		if m:is_empty() then goto skip end
		local l = sorcery.data.metallookup[m:get_name()]
		if not l then return false end
		mix[l.id] = (mix[l.id] or 0) + l.value
		count = count + l.value
	::skip::end
	-- everything is metal, we've finished summing it up.
	-- let's see if the assembled items match the ratio
	-- specified in any of the smelting recipes.
	local matches = 0
	for _,rec in pairs(sorcery.data.alloys) do
		local fac = nil
		local meltpoint = 1
		if rec.metals == nil then goto skip_recipe end
		for metal, ratio in pairs(rec.metals) do
			if mix[metal] and mix[metal] % ratio == 0 then
				if fac then
					if mix[metal] / ratio ~= fac then goto skip_recipe end
				else fac = math.floor(mix[metal] / ratio) end
				local m = sorcery.data.metals[metal]
				if m.meltpoint then
					meltpoint = math.max(meltpoint, m.meltpoint) end
			else goto skip_recipe end
		end
		do return rec, count, fac, meltpoint end
	::skip_recipe::end
	return false
end

local update_smelter = function(pos)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	local proto = minetest.registered_nodes[minetest.get_node(pos).name]._proto
	local recipe, count, factor, meltpoint = find_recipe(inv)
	if recipe and proto.temp >= meltpoint then
		minetest.get_node_timer(pos):start(1)
	else
		meta:set_float('burntime',0)
	end
end

local smelter_step = function(kind,active,pos,delta)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	local recipe, count, factor = find_recipe(inv)
	local cooktime
	local elapsed = meta:get_float('burntime') + delta
	meta:set_float('burnleft',meta:get_float('burnleft') - delta)
	if (not active) and (not recipe) then return false end
	if meta:get_float('burnleft') <= 0 or not active then
		if recipe then
			local burn, frep = minetest.get_craft_result {
				method = 'fuel', width = 1;
				items = { inv:get_stack('fuel',1) };
			}
			if burn.time == 0 then goto nofuel end
			inv:set_stack('fuel', 1, frep.items[1])
			meta:set_float('burnleft',burn.time)
			meta:set_float('burnmax',burn.time)
			if not active then
				minetest.swap_node(pos,
					sorcery.lib.tbl.merge(minetest.get_node(pos), {
						name = kind.id .. '_active'
					}))
				active = true
			end
		else goto nofuel end
	end

	if not recipe then goto update end
	
	cooktime = ((recipe.cooktime / fragments_per_ingot) * count) / factor
	if elapsed >= cooktime then
		elapsed = 0
		-- remove used items
		for i=1,inv:get_size('input') do
			local s = inv:get_stack('input',i)
			if s:is_empty() then goto skip end
			s:take_item(1) inv:set_stack('input',i,s)
		::skip::end

		local outstack
		if count % fragments_per_ingot == 0 then
			outstack = ItemStack {
				name = sorcery.data.metals[recipe.output].ingot or 'sorcery:' .. recipe.output .. '_ingot';
				count = count / fragments_per_ingot;
			}
		else
			outstack = ItemStack {
				name = 'sorcery:fragment_' .. recipe.output;
				count = count;
			}
		end

		local leftover = inv:add_item('output',outstack)
		if not leftover:is_empty() then
			minetest.add_item(pos, leftover)
		end
	end

	::update::
		meta:set_float('burntime',elapsed)
		meta:set_string('formspec', smelter_formspec(kind,
			math.min(1, meta:get_float('burnleft') /
						meta:get_float('burnmax')
					) * 100, -- fuel
			(cooktime and math.min(1, elapsed / cooktime) * 100) or 0 -- smelt
		))
		do return active end

	::nofuel::
		if active then
			minetest.swap_node(pos,
				sorcery.lib.tbl.merge(minetest.get_node(pos), { name = kind.id }))
		end
		meta:set_float('burnleft',0) -- just in case
	::noburn::
		meta:set_float('burntime',0) -- just in case
		meta:set_string('formspec', smelter_formspec(kind, 0, 0))
		return false
end
local register_kiln = function(kind)
	local box = {
		open = {
			type = 'fixed';
			fixed = {
				-0.5,-0.5,-0.5,
				 0.5, 1.6, 0.5
			};
		};
		closed = {
			type = 'fixed';
			fixed = {
				-0.5,-0.5,-0.5,
				 0.5, 0.7, 0.5
			};
		};
	}
	local id = 'sorcery:kiln_' .. kind.material
	local metal = sorcery.data.metals[kind.material]
	-- use some iffy heuristics to guess the texture
	local metaltex
	if kind.material == 'clay' then
		metaltex = 'default_brick.png';
	else
		metaltex = (metal.img and metal.img.block) or
			(metal.ingot and 'default_' .. kind.material .. '_block.png') or
			sorcery.lib.image('default_steel_block.png'):multiply(sorcery.lib.color(metal.tone)):render();
	end
	local tex = {
		open = {
			'default_furnace_front.png';
			metaltex;
			'default_copper_block.png';
			'default_stone.png';
		};
		closed = {
			'default_furnace_front_active.png';
			'default_copper_block.png';
			metaltex;
			'default_stone.png';
		};
	};
	for _,state in pairs{'open','closed'} do
		local id_closed = id .. '_closed'
		local id_current = (state == 'closed' and id_closed) or id
		local desc = (kind.temp_name and sorcery.lib.str.capitalize(kind.temp_name) .. ' kiln') or 'Kiln'
		minetest.register_node(id_current, {
			description = desc;
			drawtype = "mesh";
			after_dig_node = sorcery.lib.node.purge_container;
			mesh = 'sorcery-kiln-' .. state .. '.obj';
			drop = id;
			groups = {
				cracky = (state == 'open' and 2) or nil;
				sorcery_metallurgy = 1;
			};
			sunlight_propagates = true;
			paramtype1 = 'light';
			paramtype2 = 'facedir';
			selection_box = box[state];
			collision_box = box[state];
			tiles = tex[state];
			light_source = (state == 'closed' and 7) or 0;
			on_construct = function(pos)
				local meta = minetest.get_meta(pos)
				local inv = meta:get_inventory()
				inv:set_size('input', kind.size^2)
				inv:set_size('output', kind.outsize)
				inv:set_size('fuel', kind.fuelsize)
				inv:set_size('wax', 1)

				meta:set_float('burnleft',0)
				meta:set_float('burnmax',0)
				meta:set_float('burntime',0)

				meta:set_string('infotext',desc)
				meta:set_string('formspec',kiln_formspec(kind,0,0))
			end;
			on_timer = function(pos,delta)
			end;

			on_metadata_inventory_put = function(pos)
				minetest.get_node_timer(pos):start(1) end;
			on_metadata_inventory_move = function(pos)
				minetest.get_node_timer(pos):start(1) end;
			on_metadata_inventory_take = function(pos)
				minetest.get_node_timer(pos):start(1) end;
		})
	end
	local ingot
	if kind.material == 'clay' then
		ingot = 'default:clay_brick';
	else
		ingot = metal.ingot or 'sorcery:' .. kind.material .. '_ingot'
	end
	minetest.register_craft {
		output = id;
		recipe = {
			{'default:copper_ingot', 'default:copper_ingot', 'default:copper_ingot'};
			{ingot,'',ingot};
			{ingot,'default:furnace',ingot};
		};
	}
end

local register_smelter = function(kind)
	local recipe = {{},{};
		{'default:stone','default:furnace','default:stone'};
	} do
		local on = kind.crucible
		local ti = 'default:tin_ingot'
		local cu = 'default:copper_ingot'
		local crucmap = {
			[2] = { {cu,cu,cu}, {on,ti,on} };
			[3] = { {cu,on,cu}, {on,ti,on} };
			[4] = { {on,cu,on}, {on,ti,on} };
			[5] = { {on,cu,on}, {on,on,on} };
			[6] = { {on,on,on}, {on,on,on} };
		};
		for y=1,2 do recipe[y] = crucmap[kind.size][y] end
	end
	
	local desc = 'smelter';
	if kind.temp_name then desc = kind.temp_name .. ' ' .. desc end
	if kind.size_name then desc = kind.size_name .. ' ' .. desc end
	desc = sorcery.lib.str.capitalize(desc);
	local id = 'sorcery:smelter_' .. kind.material .. kind.size_name
	kind.id = id
	for _, active in pairs {false, true} do
		minetest.register_node((active and id .. '_active') or id, {
			_proto = kind;
			description = desc;
			drop = id;
			after_dig_node = sorcery.lib.node.purge_container;
			groups = {
				cracky = (active and 2) or nil;
				sorcery_metallurgy = 1;
			};
			paramtype2 = 'facedir';
			light_source = (active and 9) or 0;
			on_construct = function(pos)
				local meta = minetest.get_meta(pos)
				local inv = meta:get_inventory()
				inv:set_size('input',kind.size)
				inv:set_size('output',kind.outsize)
				inv:set_size('fuel',kind.fuelsize)
				meta:set_string('infotext', desc)
				meta:set_string('formspec', smelter_formspec(kind, 0, 0))
				meta:set_float('burnleft',0)
				meta:set_float('burnmax',0)
				meta:set_float('burntime',0)
			end;
			on_metadata_inventory_put = update_smelter;
			on_metadata_inventory_move = update_smelter;
			on_metadata_inventory_take = update_smelter;
			on_timer = function(pos,delta) return smelter_step(kind, active, pos, delta) end;
			-- allow_metadata_inventory_put = function(pos, listname, index, stack, player)
			-- end;
			tiles = {
				'sorcery_smelter_top_' .. tostring(kind.size) .. '.png';
				'sorcery_smelter_bottom.png';
				'sorcery_smelter_side.png';
				'sorcery_smelter_side.png';
				'sorcery_smelter_side.png';
				'sorcery_smelter_front' .. ((active and '_hot') or '') .. '.png';
			};
		})
	end
	minetest.register_craft {
		recipe = recipe;
		output = id;
	}
end

for _, t in pairs {
	{1, nil, 'clay'};
	{2, 'hot','aluminum'};
	{3, 'volcanic','platinum'};
	{4, 'stellar','duridium'};
	{5, 'nova','impervium'};
} do register_kiln {
		temp = t[1], temp_name = t[2];
		material = t[3];
		size = 3, outsize = 4, fuelsize = 1; -- future-proofing
	}

	for _, s in pairs {
		{2, 'small'};
		{3, 'large'};
		{4, 'grand'};
	} do register_smelter {
		size = s[1], size_name = s[2];
		temp = t[1], temp_name = t[2];
		material = t[3];
		crucible = 'sorcery:crucible_' .. t[3];
		outsize = 4, fuelsize = 1; -- future-proofing
	} end
end
