local infuser_formspec = function(percent)
	return string.format([[
		size[8,7]
		list[context;infusion;3.5,0;1,1;]
		list[context;potions;2.5,1.7;1,1;0]
			list[context;potions;3.5,2;1,1;1]
			list[context;potions;4.5,1.7;1,1;2]
			image[2.5,1.7;1,1;vessels_shelf_slot.png]
			image[3.5,2;1,1;vessels_shelf_slot.png]
			image[4.5,1.7;1,1;vessels_shelf_slot.png]
		image[3.5,1;1,1;gui_furnace_arrow_bg.png^[lowpart:%d:gui_furnace_arrow_fg.png^[transformR180]
		list[current_player;main;0,3.3;8,4;]
		listring[context;infusion]
		listring[current_player;main]
		listring[context;potions]
		listring[current_player;main]
		listring[context;infusion]
	]], percent)
end

local infuser_stop = function(pos)
	local meta = minetest.get_meta(pos)
	meta:set_float('runtime', 0)
	meta:set_string('formspec', infuser_formspec(0))
	meta:set_string('infotext', 'Infuser')
	minetest.get_node_timer(pos):stop()
end

local elixir_can_apply = function(elixir, potion)
	-- accepts an elixir def and potion def
	if elixir        == nil or
	   elixir._proto == nil or
	   potion        == nil then return false end

	if elixir._proto.apply and potion.on_use then
		-- the ingredient is an elixir and at least one potion
		-- is a fully enchanted, usable potion
		if elixir._proto.flag and potion._proto and
		   potion._proto['no_' .. elixir._proto.flag] == true then
			-- does the elixir have a property used to denote
			-- compatibility? if so, check the potion to see if it's
			-- marked as incompatible
			return false
		else
			return true
		end
	end
	
	return false
end

local effects_table = function(potion)
	local meta = potion:get_meta()
	local tbl = {}
	for k,v in pairs(sorcery.data.elixirs) do
		if not v.flag then goto skip end
		local val = meta:get_int(v.flag)
		if val > 0 then
			local aff, title, desc = v.describe(potion)
			if val > 3 then title = title .. ' x' .. val
			elseif val == 3 then title = 'thrice-' .. title
			elseif val == 2 then title = 'twice-' .. title
			end
			tbl[#tbl + 1] = {
				title = sorcery.lib.str.capitalize(title);
				desc = desc;
				affinity = aff;
			}
		end
	::skip::end
	return tbl
end

local infuser_timer = function(pos, elapsed)
	local meta = minetest.get_meta(pos)

	local inv = meta:get_inventory()
	local infusion = inv:get_list('infusion')
	local potions = inv:get_list('potions')
	local elixir = infusion[1]:get_definition()
	local probe = sorcery.spell.probe(pos)
	if probe.disjunction then return true end

	local potionct = 0

	do
		local ingredient -- *eyeroll*
		if infusion[1]:is_empty() then goto cancel end
		ingredient = infusion[1]:get_name()
		for i = 1,#potions do
			if potions[i]:is_empty() then goto skip end
			potionct = potionct + 1
			local base = potions[i]:get_name()
			local potion = potions[i]:get_definition()
			if elixir_can_apply(elixir,potion) then
				-- at least one combination makes a valid potion;
				-- we can start the infuser
				goto start
			end
			for _,v in pairs(sorcery.register.infusions.db) do
				if v.infuse == ingredient and v.into == base then
					-- at least one combination makes a valid
					-- potion; we can start the infuser
					goto start
				end
			end
		::skip:: end

		::cancel:: do
			infuser_stop(pos)
			return false
		end

		::start::
	end

	local time = meta:get_float("runtime") or 0
	local newtime = time + elapsed
	local infusion_time = potionct * (15 * 60) -- 15 minutes per potion
		-- FIXME make dependent on recipe
	local percent = math.min(100, math.floor(100 * (newtime / infusion_time)))
	local spawn = function(particle, scale, amt)
		minetest.add_particlespawner {
			amount = amt;
			time = 15;
			minpos = pos;
			maxpos = pos;
			minvel = {x = -0.1, y = 0.05, z = -0.1};
			maxvel = {x = 0.1, y = 0.1, z = 0.1};
			minacc = {x = 0, y = 0.1, z = 0};
			maxacc = {x = 0, y = 0.4, z = 0};
			minexptime = 2;
			maxexptime = 4;
			minsize = 0.5 * scale;
			maxsize = 3 * scale;
			glow = 14;
			texture = particle;
			animation = {
				type = "vertical_frames";
				aspect_h = 16;
				aspect_w = 16;
				length = 4.1;
			};
		}
	end
	-- for i=0,4 do
		spawn('sorcery_spark.png^[multiply:#FF8FDD', 1, 32 * 4)
	-- end
	-- for i=0,4 do
		spawn('sorcery_spark.png^[multiply:#FFB1F6', 0.5, 64 * 4)
	-- end
	
	local discharge = sorcery.lib.node.discharger(pos)
	
	if newtime >= infusion_time then
		-- finished
		local ingredient = infusion[1]:get_name()
		for i = 1,#potions do
			if potions[i]:is_empty() then goto skip end
			local base = potions[i]:get_name()
			local potion = potions[i]:get_definition()
			if elixir_can_apply(elixir, potion) then
				local newstack = inv:get_stack('potions',i)
				elixir._proto.apply(newstack, potion._proto)
				newstack:get_meta():set_string('description', sorcery.lib.ui.tooltip {
					title = potion._proto.name .. ' Draught';
					desc = potion._proto.desc;
					color = sorcery.lib.color(potion._proto.color):readable();
					props = effects_table(newstack);
				});
				inv:set_stack('potions',i,discharge(newstack))
			else
				for _,v in pairs(sorcery.register.infusions.db) do
					if v.infuse == ingredient and v.into == base then
						-- transform the base into the infusion
						inv:set_stack('potions',i,discharge(ItemStack(v.output)))
					end
				end
			end
		::skip:: end

		inv:set_stack('infusion',1,ItemStack(sorcery.register.residue.db[ingredient]))

		infuser_stop(pos)
		return false
	else
		meta:set_float('runtime', newtime)
		meta:set_string('formspec', infuser_formspec(percent))
		meta:set_string('infotext', 'Infuser (active)')
		return true
	end
end

local infuser_start = function(pos)
	local meta = minetest.get_meta(pos)
	infuser_stop(pos)
	infuser_timer(pos,0)
	minetest.get_node_timer(pos):start(15)
end

minetest.register_node("sorcery:infuser", {
	description = "Infuser";
	drawtype = "mesh";
	mesh = "sorcery-infuser.obj";
	paramtype2 = "facedir";
	after_dig_node = sorcery.lib.node.purge_container;
	tiles = { -- FIXME
		"default_stone.png",
		"default_copper_block.png",
		"default_steel_block.png",
		"default_bronze_block.png",
		"default_tin_block.png",
	};
	paramtype2 = 'facedir';
	groups = {
		cracky = 2, oddly_breakable_by_hand = 1, heavy = 1;
		sorcery_alchemy = 1, sorcery_magitech = 1;
	};
	_sorcery = {
		recipe = {
			note = 'Infuse special ingredients into liquids to create and alter powerful potions';
		};
	};
	selection_box = {
		type = 'fixed';
		fixed = {
			-0.37, -0.5, -0.37,
			 0.37,  0.5,  0.37
		};
	};
	collision_box = {
		type = 'fixed';
		fixed = {
			-0.37, -0.5, -0.37,
			 0.37,  0.5,  0.37
		};
	};

	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		inv:set_size('infusion', 1)
		inv:set_size('potions', 3)
		meta:set_string('infotext','Infuser')
		infuser_timer(pos,0)
	end;

	on_timer = infuser_timer;

	on_metadata_inventory_move = infuser_start;
	on_metadata_inventory_put = infuser_start;
	allow_metadata_inventory_put = function(pos, listname, index, stack, player)
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		if not inv:get_stack(listname,index):is_empty() then
			return 0
		end

		if listname == 'infusion' then
			return 1
		elseif listname == 'potions' then
			if minetest.get_item_group(stack:get_name(), "vessel") >= 1 then
				return 1
			end
		end
		return 0
	end;
	allow_metadata_inventory_move = function(pos, from_list, from_idx, to_list, to_idx, count, player)
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		if inv:get_stack(to_list, to_idx):is_empty() then
			if to_list == 'potions' and to_list ~= from_list then
				if minetest.get_item_group(
					--[[name]] inv:get_stack(from_list, from_idx):get_name(),
					--[[group]] "vessel") >= 1 then
					return 1
				else
					return 0
				end
			else
				return 1
			end
		else
			return 0
		end
	end;
})
