local MOVE_COST = 100
local FUEL_EFFICIENCY = 1000
tl = {}

local function delay(x)
	return function() return x end
end

local function pointable(stack, node)
	local nodedef = minetest.registered_nodes[node.name]
	local def = minetest.registered_items[stack:get_name()]
	return nodedef and def and (nodedef.pointable or (nodedef.liquidtype ~= "none" and def.liquid_pointable))
end

function turtles.create_turtle_player(turtle_id, dir, only_player)
	local info = turtles.get_turtle_info(turtle_id)
	local inv = turtles.get_turtle_inventory(turtle_id)
	local pitch
	local yaw
	if dir.z > 0 then
		yaw = 0
		pitch = 0
	elseif dir.z < 0 then
		yaw = math.pi
		pitch = 0
	elseif dir.x > 0 then
		yaw = 3*math.pi/2
		pitch = 0
	elseif dir.x < 0 then
		yaw = math.pi/2
		pitch = 0
	elseif dir.y > 0 then
		yaw = 0
		pitch = -math.pi/2
	else
		yaw = 0
		pitch = math.pi/2
	end
	local player = {
		get_inventory_formspec = delay(""), -- TODO
		get_look_dir = delay(vector.new(dir)),
		get_look_pitch = delay(pitch),
		get_look_yaw = delay(yaw),
		get_player_control = delay({jump = false, right = false, left = false, LMB = false, RMB = false, sneak = false, aux1 = false, down = false, up = false}),
		get_player_control_bits = delay(0),
		get_player_name = delay("turtle:" .. tostring(turtle_id)),
		is_player = delay(true),
		is_turtle = true,
		set_inventory_formspec = delay(),
		getpos = function() vector.subtract(info.spos, {x = 0, y = 1.5, z = 0}) end,
		get_hp = delay(20),
		get_inventory = function() return turtles.get_turtle_inventory(turtle_id) end,
		get_wielded_item = function() return turtles.get_turtle_inventory(turtle_id):get_stack("main", info.wield_index or 1) end,
		get_wield_index = function() return info.wield_index or 1 end,
		get_wield_list = delay("main"),
		moveto = delay(), -- TODO
		punch = delay(),
		remove = delay(),
		right_click = delay(), -- TODO
		setpos = delay(), -- TODO
		set_hp = delay(),
		set_properties = delay(),
		set_wielded_item = function(self, item)
			turtles.get_turtle_inventory(turtle_id):set_stack("main", info.wield_index or 1, item)
		end,
		set_animation = delay(),
		set_attach = delay(), -- TODO???
		set_detach = delay(),
		set_bone_position = delay(),
	}
	if only_player then return player end
	local above, under = nil, nil
	local wieldstack = player:get_wielded_item()
	local pos = vector.add(info.spos, dir)
	if pointable(wieldstack, minetest.get_node(pos)) then
		above = vector.new(info.spos)
		under = pos
	elseif pointable(wieldstack, minetest.get_node(vector.add(pos, dir))) then
		above = pos
		under = vector.add(pos, dir)
	else
		for i = 0, 5 do
			local dir2 = directions.side_to_dir(i)
			if vector.dot(dir2, dir) == 0 and pointable(wieldstack, minetest.get_node(vector.add(pos, dir2))) then
				under = vector.add(pos, dir2)
				break
			end
		end
		above = pos
	end
	local pointed_thing = nil
	if under ~= nil then
		pointed_thing = {type = "node", above = above, under = under}
	end
	return player, pointed_thing
end

function tl.select(turtle, cptr, slot)
	if 1 <= slot and slot < turtles.get_turtle_inventory(turtle):get_size("main") then
		turtles.get_turtle_info(turtle).wield_index = slot
	end
end

local function tl_move(turtle, cptr, dir)
	tl.close_form(turtle)
	local info = turtles.get_turtle_info(turtle)
	if info.energy < MOVE_COST then
		cptr.X = 0
		return
	end
	local spos = info.spos
	local npos = vector.add(spos, dir)
	if minetest.get_node(npos).name == "air" then
		minetest.set_node(npos, {name = "turtle:turtle2"})
		info.npos = npos
		info.moving = true
		info.energy = info.energy - MOVE_COST
		cptr.X = u16(-1)
		cptr.paused = true
	else
		cptr.X = 0
	end
end

function tl.forward(turtle, cptr)
	local dir = turtles.get_turtle_info(turtle).dir
	tl_move(turtle, cptr, minetest.facedir_to_dir(dir))
end

function tl.backward(turtle, cptr)
	local dir = turtles.get_turtle_info(turtle).dir
	tl_move(turtle, cptr, vector.multiply(minetest.facedir_to_dir(dir), -1))
end

function tl.up(turtle, cptr)
	tl_move(turtle, cptr, {x = 0, y = 1, z = 0})
end

function tl.down(turtle, cptr)
	tl_move(turtle, cptr, {x = 0, y = -1, z = 0})
end

function tl.turnleft(turtle, cptr)
	tl.close_form(turtle)
	local info = turtles.get_turtle_info(turtle)
	info.ndir = (info.dir + 3) % 4
	info.rotate = math.pi / 2
	info.moving = true
	cptr.paused = true
end

function tl.turnright(turtle, cptr)
	tl.close_form(turtle)
	local info = turtles.get_turtle_info(turtle)
	info.ndir = (info.dir + 1) % 4
	info.rotate = - math.pi / 2
	info.moving = true
	cptr.paused = true
end

local function write_string_at(cptr, addr, str)
	for i = 1, string.len(str) do
		cptr[u16(addr - 1 + i)] = string.byte(str, i)
	end
	cptr.X = string.len(str)
end

local function turtle_detect(turtle, cptr, dir)
	local info = turtles.get_turtle_info(turtle)
	local pos = vector.add(info.spos, dir)
	local name = minetest.get_node(pos).name
	write_string_at(cptr, cptr.X, name)
end

function tl.detect(turtle, cptr)
	local info = turtles.get_turtle_info(turtle)
	turtle_detect(turtle, cptr, minetest.facedir_to_dir(info.dir))
end

function tl.detectup(turtle, cptr)
	turtle_detect(turtle, cptr, {x = 0, y = 1, z = 0})
end

function tl.detectdown(turtle, cptr)
	turtle_detect(turtle, cptr, {x = 0, y = -1, z = 0})
end

local function turtle_dig(turtle, cptr, dir)
	tl.close_form(turtle)
	local player, pointed_thing = turtles.create_turtle_player(turtle, dir)
	if pointed_thing == nil then return end
	local info = turtles.get_turtle_info(turtle)
	local wieldstack = player:get_wielded_item()
	local on_use = (minetest.registered_items[wieldstack:get_name()] or {}).on_use
	if on_use then
		player:set_wielded_item(on_use(wieldstack, player, pointed_thing) or wieldstack)
	else
		local pos = info.spos
		local node = minetest.get_node(pos)
		local def = ItemStack({name = node.name}):get_definition()
		local toolcaps = wieldstack:get_tool_capabilities()
		local dp = minetest.get_dig_params(def.groups, toolcaps)
		local dp2 = minetest.get_dig_params(def.groups, ItemStack(""):get_tool_capabilities())
		if (dp.diggable or dp2.diggable) and def.diggable and def.pointable and (not def.can_dig or def.can_dig(pos, player)) and
				(not minetest.is_protected(pos, player:get_player_name())) then
			local on_dig = (minetest.registered_nodes[node.name] or {on_dig = minetest.node_dig}).on_dig
			on_dig(pos, node, player)
		end
	end
end

function tl.dig(turtle, cptr)
	local info = turtles.get_turtle_info(turtle)
	turtle_dig(turtle, cptr, minetest.facedir_to_dir(info.dir))
end

function tl.digup(turtle, cptr)
	turtle_dig(turtle, cptr, {x = 0, y = 1, z = 0})
end

function tl.digdown(turtle, cptr)
	turtle_dig(turtle, cptr, {x = 0, y = -1, z = 0})
end

local function turtle_place(turtle, cptr, dir)
	tl.close_form(turtle)
	local player, pointed_thing = turtles.create_turtle_player(turtle, dir)
	if pointed_thing == nil then return end
	local formspec = minetest.get_meta(pointed_thing.under):get_string("formspec")
	if formspec ~= nil then
		local info = turtles.get_turtle_info(turtle)
		info.open_formspec = tl.read_formspec(formspec)
		info.formspec_type = {type = "node", pos = pointed_thing.under}
		info.formspec_fields = {}
		return
	end
	local wieldstack = player:get_wielded_item()
	local on_place = (minetest.registered_items[wieldstack:get_name()] or {on_place = minetest.item_place}).on_place
	player:set_wielded_item(on_place(wieldstack, player, pointed_thing) or wieldstack)
end

function tl.place(turtle, cptr)
	local info = turtles.get_turtle_info(turtle)
	turtle_place(turtle, cptr, minetest.facedir_to_dir(info.dir))
end

function tl.placeup(turtle, cptr)
	turtle_place(turtle, cptr, {x = 0, y = 1, z = 0})
end

function tl.placedown(turtle, cptr)
	turtle_place(turtle, cptr, {x = 0, y = -1, z = 0})
end

local function stack_set_count(stack, count)
	stack = stack:to_table()
	if stack == nil then return nil end
	stack.count = count
	return ItemStack(stack)
end

function tl.refuel(turtle, cptr, slot, nmax)
	local info = turtles.get_turtle_info(turtle)
	local inv = turtles.get_turtle_inventory(turtle)
	local stack = inv:get_stack("main", slot)
	local fuel, afterfuel = minetest.get_craft_result({method = "fuel", width = 1, items = {stack}})
	if fuel.time <= 0 then
		cptr.X = 0
		return
	end
	local count = math.min(stack:get_count(), nmax)
	local fs = stack:to_table()
	fs.count = 1
	local fstack = ItemStack(fs)
	local fuel, afterfuel
	fuel, afterfuel = minetest.get_craft_result({method = "fuel", width = 1, items = {fstack}})
	stack:take_item(count)
	if afterfuel ~= nil then
		afterfuel = afterfuel.items[1]
	end
	if afterfuel ~= nil then
		afterfuel = stack_set_count(afterfuel, afterfuel:get_count()*count)
		local leftover = stack:add_item(ItemStack(afterfuel))
		inv:set_stack("main", slot, stack)
		local leftover2 = inv:add_item("main", leftover)
		minetest.add_item(info.spos, leftover2)
	else
		inv:set_stack("main", slot, stack)
	end
	info.energy = info.energy + FUEL_EFFICIENCY * count * fuel.time
	cptr.X = u16(-1)
end

function tl.get_energy(turtle, cptr)
	local info = turtles.get_turtle_info(turtle)
	cptr.Y = u16(info.energy)
	cptr.X = u16(math.floor(info.energy / 0x10000))
end

--------------
-- Formspec --
--------------
minetest.register_on_player_receive_fields(function(player, formname, fields)
	if player:get_player_name():sub(1, 7) == "turtle:" and formname == "turtle:inventory" then
		return true
	end
end)

local function get_turtle_formspec_player(turtle)
	local info = turtles.get_turtle_info(turtle)
	local dir
	if info.formspec_type and info.formspec_type.type == "node" then
		dir = vector.normalize(vector.sub(info.formspec_type.pos, info.spos))
	else
		dir = minetest.facedir_to_dir(info.dir)
	end
	return turtles.create_turtle_player(turtle, dir, true)
end

local function send_fields(turtle)
	local info = turtles.get_turtle_info(turtle)
	local fields = info.formspec_fields
	info.formspec_fields = {}
	local player = get_turtle_formspec_player(turtle)
	if info.formspec_type.type == "show" then
		for _, func in ipairs(minetest.registered_on_receive_fields) do
			if func(player, info.formspec_type.formname, fields) then
				return
			end
		end
	else
		local pos = info.formspec_type.pos
		local nodedef = minetest.registered_nodes[minetest.get_node(pos).name]
		if nodedef and nodedef.on_receive_fields then
			nodedef.on_receive_fields(vector.new(pos), "", fields, player)
		end
	end
end

function tl.close_form(turtle)
	local info = turtles.get_turtle_info(turtle)
	if info.formspec_fields then
		info.formspec_fields["quit"] = "true"
		send_fields(turtle)
		info.open_formspec = nil
		info.formspec_type = nil
		info.formspec_fields = nil
	end
end

local function split_str(str, delim)
	local parsed = {}
	local i = 1
	local s = ""
	while i <= string.len(str) do
		if str:sub(i, i) == "\\" then
			s = s .. str:sub(i, i + 1)
			i = i + 2
		elseif str:sub(i, i) == delim then
			parsed[#parsed + 1] = s
			s = ""
			i = i + 1
		else
			s = s .. str:sub(i, i)
			i = i + 1
		end
	end
	parsed[#parsed + 1] = s
	return parsed
end

local function parse_list(lstdef)
	local parsed = split_str(lstdef, ";")
	local psize = split_str(parsed[4], ",")
	if parsed[1] == "current_name" then parsed[1] = "context" end
	return {location = parsed[1], listname = parsed[2],
		size = tonumber(psize[1]) * tonumber(psize[2]),
		start_index = ((parsed[5] and tonumber(parsed[5])) or 0) + 1}
end

local function merge_adjacent_lists(lsts)
	local function merge_at(t, ind)
		if t.starts[ind] and t.ends[ind] then
			t.starts[t.ends[ind]] = t.starts[ind]
			t.ends[t.starts[ind]] = t.ends[ind]
			t.starts[ind] = nil
			t.ends[ind] = nil
		end
	end
	local locs = {}
	for _, lst in ipairs(lsts) do
		local loc = lst.location .. ";" .. lst.listname
		if locs[loc] == nil then
			locs[loc] = {location = lst.location, listname = lst.listname, starts = {}, ends = {}}
		end
		local starti, endi = lst.start_index, lst.start_index + lst.size
		locs[loc].ends[endi] = starti
		locs[loc].starts[starti] = endi
		merge_at(locs[loc], endi)
		merge_at(locs[loc], starti)
	end
	local new = {}
	for _, lst in pairs(locs) do
		for starti, endi in pairs(lst.starts) do
			new[#new + 1] = {location = lst.location, listname = lst.listname, size = endi - starti, start_index = starti}
		end
	end
	return new
end

function tl.read_formspec(formspec)
	local parsed = split_str(formspec, "]")
	local lsts = {}
	for _, item in ipairs(parsed) do
		if item:sub(1, 5) == "list[" then
			lsts[#lsts + 1] = parse_list(item:sub(6, -1))
		end
	end
	return {lists = merge_adjacent_lists(lsts)}
end

local old_show_formspec = minetest.show_formspec
function minetest.show_formspec(playername, formname, formspec)
	if playername:sub(1, 7) == "turtle:" then
		local id = tonumber(playername:sub(8, -1))
		local info = turtles.get_turtle_info(id)
		info.open_formspec = tl.read_formspec(formspec)
		info.formspec_type = {type = "show", formname = formname}
		info.formspec_fields = {}
		return
	end
	old_show_formspec(playername, formname, formspec)
end

function tl.open_inv(turtle, cptr)
	tl.close_form(turtle)
	local info = turtles.get_turtle_info(turtle)
	info.open_formspec = tl.read_formspec(
		"list[current_player;main;0,4.25;8,4;]"..
		"list[current_player;craft;1.75,0.5;3,3;]"..
		"list[current_player;craftpreview;5.75,1.5;1,1;]")
	info.formspec_type = {type = "show", formname = "turtle:inventory"}
end

-- Formspec memory layout
-- +-----+-----+-----+-----+----
-- |     |  Pointer  |     |
-- | Tag |  to next  | ID  | Data
-- |     |  element  |     |
-- +-----+-----+-----+-----+----
-- 
-- For last element (TAG_END), only tag is present

local function push(cptr, addr, value)
	cptr[addr] = bit32.band(value, 0xff)
	cptr[u16(addr + 1)] = bit32.band(math.floor(value/256), 0xff)
	return u16(addr + 2)
end

local function pushC(cptr, addr, value)
	cptr[addr] = bit32.band(value, 0xff)
	return u16(addr + 1)
end

local function push_string(cptr, addr, str)
	for i = 1, string.len(str) do
		cptr[u16(addr - 1 + i)] = string.byte(str, i)
	end
	return u16(addr + string.len(str))
end

local function push_string_counted(cptr, addr, str)
	-- String length (2 bytes)
	-- String contents
	return push_string(cptr, push(cptr, addr, string.len(str)), str)
end

local function push_stack(cptr, addr, stack)
	-- Count (2 bytes)
	-- Wear (2 bytes)
	-- Item name
	return push_string_counted(cptr,
		push(cptr,
		push(cptr, addr,
			stack:get_count()),
			stack:get_wear()),
			stack:get_name())
end

local TAG_END = 0
local TAG_LIST = 1
function tl.get_formspec(turtle, cptr, addr)
	local info = turtles.get_turtle_info(turtle)
	if not info.open_formspec then
		pushC(cptr, addr, TAG_END)
		return
	end
	local i = 0
	for _, lst in ipairs(info.open_formspec.lists) do
		addr = pushC(cptr, addr, TAG_LIST)
		local old_addr = addr
		addr = u16(addr + 2)
		addr = pushC(cptr, addr, i)
		i = i + 1
		addr = push_string_counted(cptr, addr, lst.location)
		addr = push_string_counted(cptr, addr, lst.listname)
		addr = push(cptr, addr, lst.size)
		addr = push(cptr, addr, lst.start_index)
		push(cptr, old_addr, addr) -- Pointer to next element
	end
	pushC(cptr, addr, TAG_END)
end

local function get_element_by_id(formspec, elem_id)
	return formspec.lists[elem_id + 1]
end

---------------
-- Inventory --
---------------

local function get_inventory_from_location(turtle, location)
	if location == "current_player" then
		return turtles.get_turtle_inventory(turtle)
	elseif location == "context" then
		local info = turtles.get_turtle_info(turtle)
		local formspec = info.formspec_type
		if formspec and formspec.type == "node" then
			return minetest.get_meta(formspec.pos):get_inventory()
		end
		print("WARNING: tried to access context without open node formspec")
	elseif location:sub(1, 8) == "nodemeta" then
		local p = split_str(location, ":")
		local spos = split_str(p[2], ",")
		local pos = {x = tonumber(spos[1]), y = tonumber(spos[2]), z = tonumber(spos[3])}
		if pos.x and pos.y and pos.z then
			return minetest.get_meta(pos):get_inventory()
		end
		print("WARNING: incorrect nodemeta element: " .. location)
	else
		print("WARNING: unimplemented location type: " .. location)
	end
end

function tl.get_stack(turtle, cptr, elem_id, slot, addr)
	local info = turtles.get_turtle_info(turtle)
	local stack = ItemStack("")
	if info.open_formspec then
		local formspec = info.open_formspec
		local elem = get_element_by_id(formspec, elem_id)
		if elem and elem.location and
				elem.start_index <= slot and slot < elem.start_index + elem.size then
			local inv = get_inventory_from_location(turtle, elem.location)
			if inv then
				stack = inv:get_stack(elem.listname, slot)
			end
		end
	end
	push_stack(cptr, addr, stack)
end

local function same_location(location1, location2)
	if location1.type ~= location2.type then
		return false
	end
	if location1.type == "node" then
		return vector.equals(location1.pos, location2.pos)
	else
		return location1.name == location2.name
	end
end

local function get_callbacks(location)
	if location.type == "node" then
		local node = minetest.get_node(location.pos)
		local nodedef = minetest.registered_nodes[node.name] or {}
		return {allow_move = function(list1, index1, list2, index2, count, player)
				return nodedef.allow_metadata_inventory_move and
					nodedef.allow_metadata_inventory_move(location.pos, list1, index1, list2, index2, count, player) or
					count
			end,
			allow_take = function(list, index, stack, player)
				return nodedef.allow_metadata_inventory_take and
					nodedef.allow_metadata_inventory_take(location.pos, list, index, stack, player) or
					stack:get_count()
			end,
			allow_put = function(list, index, stack, player)
				return nodedef.allow_metadata_inventory_put and
					nodedef.allow_metadata_inventory_put(location.pos, list, index, stack, player) or
					stack:get_count()
			end,
			on_move = function(list1, index1, list2, index2, count, player)
				if nodedef.on_metadata_inventory_move then
					nodedef.on_metadata_inventory_move(location.pos, list1, index1, list2, index2, count, player)
				end
			end,
			on_take = function(list, index, stack, player)
				if nodedef.on_metadata_inventory_take then
					nodedef.on_metadata_inventory_take(location.pos, list, index, stack, player)
				end
			end,
			on_put = function(list, index, stack, player)
				if nodedef.on_metadata_inventory_put then
					nodedef.on_metadata_inventory_put(location.pos, list, index, stack, player)
				end
			end,
		}
	elseif location.type == "detached" then
		print("WARNING: not yet implemented")
		return {allow_move = function(list1, index1, list2, index2, count, player)
				return count
			end,
			allow_take = function(list, index, stack, player)
				return stack:get_count()
			end,
			allow_put = function(list, index, stack, player)
				return stack:get_count()
			end,
			on_move = function() end,
			on_take = function() end,
			on_put = function() end,
		}
	else
		return {allow_move = function(list1, index1, list2, index2, count, player)
				return count
			end,
			allow_take = function(list, index, stack, player)
				return stack:get_count()
			end,
			allow_put = function(list, index, stack, player)
				return stack:get_count()
			end,
			on_move = function() end,
			on_take = function() end,
			on_put = function() end,
		}
	end
end

local function room_for_item(stack, stack2)
	return stack:is_empty() or stack2:is_empty() or
		(stack:get_name() == stack2:get_name() and stack:get_free_space() >= stack2:get_count())
end

local function move(inv1, list1, index1, inv2, list2, index2, player, count)
	local location1 = inv1:get_location()
	local location2 = inv2:get_location()
	local callbacks1 = get_callbacks(location1)
	local callbacks2 = get_callbacks(location2)
	local stack = inv1:get_stack(list1, index1)
	local stack_to = inv2:get_stack(list2, index2)
	if stack_to:get_name() ~= stack:get_name() and stack_to:get_count() > 0 then -- Disallow move to different type of item
		return
	end
	if list2 == "craftpreview" then return end -- Disallow moving items into craftpreview
	if list1 ~= "craftpreview" then
		count = math.min(stack:get_count(), count)
		stack:set_count(count)	
		count = math.min(count, stack_to:get_free_space())
	else
		local c = 0
		local crafted_stack = ItemStack("")
		while c < count do
			local st = minetest.craft_predict(
				minetest.get_craft_result(
					{method = "normal",
					items = inv1:get_list("craft"),
					width = inv1:get_width("craft")}).item,
				player,
				inv1:get_list("craft"),
				inv1)
			--if not stack_to:room_for_item(st) then
			if not room_for_item(stack_to, st) then
				break
			end
			local old_grid = inv1:get_list("craft")
			local out, decr_input = minetest.get_craft_result(
							{method = "normal",
							items = inv1:get_list("craft"),
							width = inv1:get_width("craft")})
			local item = out.item
			inv1:set_list("craft", decr_input.items)
			local crafted = minetest.on_craft(item, player, old_grid, inv1)
			crafted_stack:add_item(crafted)
			stack_to:add_item(crafted)
			c = c + 1
		end
		stack = crafted_stack
		count = stack:get_count()
	end
	if count <= 0 then return end
	local count1, count2
	if same_location(location1, location2) then
		count1 = callbacks1.allow_move(list1, index1, list2, index2, count, player)
		count2 = count1
	else
		count1 = callbacks1.allow_take(list1, index1, stack, player)
		count2 = callbacks2.allow_put(list2, index2, stack, player)
	end
	if count1 >= 0 then
		count = math.min(count, count1)
	end
	if count2 >= 0 then
		count = math.min(count, count2)
	end
	if count == 0 then return end
	stack:set_count(count)
	if count1 >= 0 then
		local s = inv1:get_stack(list1, index1)
		s:take_item(count)
		inv1:set_stack(list1, index1, s)
	end
	if count2 >= 0 then
		local s = inv2:get_stack(list2, index2)
		s:add_item(stack)
		inv2:set_stack(list2, index2, s)
	end
	if same_location(location1, location2) then
		callbacks1.on_move(list1, index1, list2, index2, count, player)
	else
		callbacks1.on_take(list1, index1, stack, player)
		callbacks2.on_put(list2, index2, stack, player)
	end
end

local function readC(cptr, addr)
	return cptr[addr]
end
local function read(cptr, addr)
	return cptr[addr] + 256 * cptr[u16(addr + 1)]
end

function tl.move_item(turtle, cptr, addr)
	local from_id = readC(cptr, addr)
	local to_id = readC(cptr, u16(addr + 1))
	local from_index = read(cptr, u16(addr + 2))
	local to_index = read(cptr, u16(addr + 4))
	local count = read(cptr, u16(addr + 6))
	local info = turtles.get_turtle_info(turtle)
	local player = get_turtle_formspec_player(turtle)
	if info.open_formspec then
		local formspec = info.open_formspec
		local from_elem = get_element_by_id(formspec, from_id)
		local to_elem = get_element_by_id(formspec, to_id)
		if from_elem and from_elem.location and to_elem and to_elem.location and
				from_elem.start_index <= from_index and from_index < from_elem.start_index + from_elem.size and
				to_elem.start_index <= to_index and to_index < to_elem.start_index + to_elem.size then
			local from_inv = get_inventory_from_location(turtle, from_elem.location)
			local to_inv = get_inventory_from_location(turtle, to_elem.location)
			if from_inv and to_inv then
				move(from_inv, from_elem.listname, from_index, to_inv, to_elem.listname, to_index, player, count)
			end
		end
	end
end