function mesecon.move_node(pos, newpos)
	local node = minetest.get_node(pos)
	local meta = minetest.get_meta(pos):to_table()
	minetest.remove_node(pos)
	minetest.set_node(newpos, node)
	minetest.get_meta(pos):from_table(meta)
end

-- An on_rotate callback for mesecons components.
function mesecon.on_rotate(pos, node, _, _, new_param2)
	local new_node = {name = node.name, param1 = node.param1, param2 = new_param2}
	minetest.swap_node(pos, new_node)
	mesecon.on_dignode(pos, node)
	mesecon.on_placenode(pos, new_node)
	minetest.check_for_falling(pos)
	return true
end

-- An on_rotate callback for components which stay horizontal.
-- Deprecated. Use paramtype2 = "4dir" instead.
function mesecon.on_rotate_horiz(pos, node, user, mode, new_param2)
	if not minetest.global_exists("screwdriver") or mode ~= screwdriver.ROTATE_FACE then
		return false
	end
	return mesecon.on_rotate(pos, node, user, mode, new_param2)
end

-- Rules rotation Functions:
function mesecon.rotate_rules_right(rules)
	local nr = {}
	for i, rule in ipairs(rules) do
		table.insert(nr, {
			x = -rule.z,
			y =  rule.y,
			z =  rule.x,
			name = rule.name})
	end
	return nr
end

function mesecon.rotate_rules_left(rules)
	local nr = {}
	for i, rule in ipairs(rules) do
		table.insert(nr, {
			x =  rule.z,
			y =  rule.y,
			z = -rule.x,
			name = rule.name})
	end
	return nr
end

function mesecon.rotate_rules_down(rules)
	local nr = {}
	for i, rule in ipairs(rules) do
		table.insert(nr, {
			x = -rule.y,
			y =  rule.x,
			z =  rule.z,
			name = rule.name})
	end
	return nr
end

function mesecon.rotate_rules_up(rules)
	local nr = {}
	for i, rule in ipairs(rules) do
		table.insert(nr, {
			x =  rule.y,
			y = -rule.x,
			z =  rule.z,
			name = rule.name})
	end
	return nr
end

-- Returns a rules getter function that returns different rules depending on the node's horizontal rotation.
-- If param2 % 4 == 0, then the rules returned by the getter are a copy of base_rules.
function mesecon.horiz_rules_getter(base_rules)
	local rotations = {mesecon.tablecopy(base_rules)}
	for i = 2, 4 do
		local right_rules = rotations[i - 1]
		if not right_rules[1] or right_rules[1].x then
			-- flat rules
			rotations[i] = mesecon.rotate_rules_left(right_rules)
		else
			-- not flat
			rotations[i] = {}
			for j, rules in ipairs(right_rules) do
				rotations[i][j] = mesecon.rotate_rules_left(rules)
			end
		end
	end
	return function(node)
		return rotations[node.param2 % 4 + 1]
	end
end

function mesecon.flattenrules(allrules)
--[[
	{
		{
			{xyz},
			{xyz},
		},
		{
			{xyz},
			{xyz},
		},
	}
--]]
	if allrules[1] and
	   allrules[1].x then
		return allrules
	end

	local shallowrules = {}
	for _, metarule in ipairs( allrules) do
	for _,     rule in ipairs(metarule ) do
		table.insert(shallowrules, rule)
	end
	end
	return shallowrules
--[[
	{
		{xyz},
		{xyz},
		{xyz},
		{xyz},
	}
--]]
end

function mesecon.rule2bit(findrule, allrules)
	--get the bit of the metarule the rule is in, or bit 1
	if (allrules[1] and
	    allrules[1].x) or
	    not findrule then
		return 1
	end
	for m,metarule in ipairs( allrules) do
	for _,    rule in ipairs(metarule ) do
		if vector.equals(findrule, rule) then
			return m
		end
	end
	end
end

function mesecon.rule2metaindex(findrule, allrules)
	--get the metarule the rule is in, or allrules
	if allrules[1].x then
		return nil
	end

	if not(findrule) then
		return mesecon.flattenrules(allrules)
	end

	for m, metarule in ipairs( allrules) do
	for _,     rule in ipairs(metarule ) do
		if vector.equals(findrule, rule) then
			return m
		end
	end
	end
end

function mesecon.rule2meta(findrule, allrules)
	if #allrules == 0 then return {} end

	local index = mesecon.rule2metaindex(findrule, allrules)
	if index == nil then
		if allrules[1].x then
			return allrules
		else
			return {}
		end
	end
	return allrules[index]
end

function mesecon.dec2bin(n)
	local x, y = math.floor(n / 2), n % 2
	if (n > 1) then
		return mesecon.dec2bin(x)..y
	else
		return ""..y
	end
end

function mesecon.getstate(nodename, states)
	for state, name in ipairs(states) do
		if name == nodename then
			return state
		end
	end
	error(nodename.." doesn't mention itself in "..dump(states))
end

function mesecon.getbinstate(nodename, states)
	return mesecon.dec2bin(mesecon.getstate(nodename, states)-1)
end

function mesecon.get_bit(binary,bit)
	bit = bit or 1
	local len = binary:len()
	if bit > len then return false end
	local c = len-(bit-1)
	return binary:sub(c,c) == "1"
end

function mesecon.set_bit(binary,bit,value)
	if value == "1" then
		if not mesecon.get_bit(binary,bit) then
			return mesecon.dec2bin(tonumber(binary,2)+math.pow(2,bit-1))
		end
	elseif value == "0" then
		if mesecon.get_bit(binary,bit) then
			return mesecon.dec2bin(tonumber(binary,2)-math.pow(2,bit-1))
		end
	end
	return binary

end

function mesecon.invertRule(r)
	return vector.multiply(r, -1)
end

function mesecon.tablecopy(obj) -- deep copy
	if type(obj) == "table" then
		return table.copy(obj)
	end
	return obj
end

-- Returns whether two values are equal.
-- In tables, keys are compared for identity but values are compared recursively.
-- There is no protection from infinite recursion.
function mesecon.cmpAny(t1, t2)
	if type(t1) ~= type(t2) then return false end
	if type(t1) ~= "table" then return t1 == t2 end

	-- Check that for each key of `t1` both tables have the same value
	for i, e in pairs(t1) do
		if not mesecon.cmpAny(e, t2[i]) then return false end
	end

	-- Check that all keys of `t2` are also keys of `t1` so were checked in the previous loop
	for i, _ in pairs(t2) do
		if t1[i] == nil then return false end
	end

	return true
end

-- Deprecated. Use `merge_tables` or `merge_rule_sets` as appropriate.
function mesecon.mergetable(source, dest)
	minetest.log("warning", debug.traceback("Deprecated call to mesecon.mergetable"))
	local rval = mesecon.tablecopy(dest)

	for k, v in pairs(source) do
		rval[k] = dest[k] or mesecon.tablecopy(v)
	end
	for i, v in ipairs(source) do
		table.insert(rval, mesecon.tablecopy(v))
	end

	return rval
end

-- Merges several rule sets in one. Order may not be preserved. Nil arguments
-- are ignored.
-- The rule sets must be of the same kind (either all single-level or all two-level).
-- The function may be changed to normalize the resulting set in some way.
function mesecon.merge_rule_sets(...)
	local rval = {}
	for _, t in pairs({...}) do -- ignores nils automatically
		table.insert_all(rval, mesecon.tablecopy(t))
	end
	return rval
end

-- Merges two tables, with entries from `replacements` taking precedence over
-- those from `base`. Returns the new table.
-- Values are deep-copied from either table, keys are referenced.
-- Numerical indices aren’t handled specially.
function mesecon.merge_tables(base, replacements)
	local ret = mesecon.tablecopy(replacements) -- these are never overriden so have to be copied in any case
	for k, v in pairs(base) do
		if ret[k] == nil then -- it could be `false`
			ret[k] = mesecon.tablecopy(v)
		end
	end
	return ret
end

function mesecon.register_node(name, spec_common, spec_off, spec_on)
	spec_common.drop = spec_common.drop or name .. "_off"
	spec_common.on_blast = spec_common.on_blast or mesecon.on_blastnode
	spec_common.__mesecon_basename = name
	spec_on.__mesecon_state = "on"
	spec_off.__mesecon_state = "off"

	spec_on = mesecon.merge_tables(spec_common, spec_on);
	spec_off = mesecon.merge_tables(spec_common, spec_off);

	minetest.register_node(name .. "_on", spec_on)
	minetest.register_node(name .. "_off", spec_off)
end

-- swap onstate and offstate nodes, returns new state
function mesecon.flipstate(pos, node)
	local nodedef = minetest.registered_nodes[node.name]
	local newstate
	if (nodedef.__mesecon_state == "on") then newstate = "off" end
	if (nodedef.__mesecon_state == "off") then newstate = "on" end

	minetest.swap_node(pos, {name = nodedef.__mesecon_basename .. "_" .. newstate,
		param2 = node.param2})

	return newstate
end

-- File writing / reading utilities
local wpath = minetest.get_worldpath()
function mesecon.file2table(filename)
	local f = io.open(wpath.."/"..filename, "r")
	if f == nil then return {} end
	local t = f:read("*all")
	f:close()
	if t == "" or t == nil then return {} end
	return minetest.deserialize(t)
end

function mesecon.table2file(filename, table)
	local f = io.open(wpath.."/"..filename, "w")
	f:write(minetest.serialize(table))
	f:close()
end

-- Block position "hashing" (convert to integer) functions for voxelmanip cache
local BLOCKSIZE = 16

-- convert node position --> block hash
local function hash_blockpos(pos)
	return minetest.hash_node_position({
		x = math.floor(pos.x/BLOCKSIZE),
		y = math.floor(pos.y/BLOCKSIZE),
		z = math.floor(pos.z/BLOCKSIZE)
	})
end

-- Maps from a hashed mapblock position (as returned by hash_blockpos) to a
-- table.
--
-- Contents of the table are:
-- “vm” → the VoxelManipulator
-- “dirty” → true if data has been modified
--
-- Nil if no VM-based transaction is in progress.
local vm_cache = nil

-- Cache from node position hashes to nodes (represented as tables).
local vm_node_cache = nil

-- Whether the current transaction will need a light update afterward.
local vm_update_light = false

-- Starts a VoxelManipulator-based transaction.
--
-- During a VM transaction, calls to vm_get_node and vm_swap_node operate on a
-- cached copy of the world loaded via VoxelManipulators. That cache can later
-- be committed to the real map by means of vm_commit or discarded by means of
-- vm_abort.
function mesecon.vm_begin()
	vm_cache = {}
	vm_node_cache = {}
	vm_update_light = false
end

-- Finishes a VoxelManipulator-based transaction, freeing the VMs and map data
-- and writing back any modified areas.
function mesecon.vm_commit()
	for hash, tbl in pairs(vm_cache) do
		if tbl.dirty then
			local vm = tbl.vm
			vm:write_to_map(vm_update_light)
			vm:update_map()
		end
	end
	vm_cache = nil
	vm_node_cache = nil
end

-- Finishes a VoxelManipulator-based transaction, freeing the VMs and throwing
-- away any modified areas.
function mesecon.vm_abort()
	vm_cache = nil
	vm_node_cache = nil
end

-- Gets the cache entry covering a position, populating it if necessary.
local function vm_get_or_create_entry(pos)
	local hash = hash_blockpos(pos)
	local tbl = vm_cache[hash]
	if not tbl then
		tbl = {vm = minetest.get_voxel_manip(pos, pos), dirty = false}
		vm_cache[hash] = tbl
	end
	return tbl
end

-- Gets the node at a given position during a VoxelManipulator-based
-- transaction.
function mesecon.vm_get_node(pos)
	local hash = minetest.hash_node_position(pos)
	local node = vm_node_cache[hash]
	if not node then
		node = vm_get_or_create_entry(pos).vm:get_node_at(pos)
		vm_node_cache[hash] = node
	end
	return node.name ~= "ignore" and {name = node.name, param1 = node.param1, param2 = node.param2} or nil
end

-- Sets a node’s name during a VoxelManipulator-based transaction.
--
-- Existing param1, param2, and metadata are left alone.
--
-- The swap will necessitate a light update unless update_light equals false.
function mesecon.vm_swap_node(pos, name, update_light)
	-- If one node needs a light update, all VMs should use light updates to
	-- prevent newly calculated light from being overwritten by other VMs.
	vm_update_light = vm_update_light or update_light ~= false

	local tbl = vm_get_or_create_entry(pos)
	local hash = minetest.hash_node_position(pos)
	local node = vm_node_cache[hash]
	if not node then
		node = tbl.vm:get_node_at(pos)
		vm_node_cache[hash] = node
	end
	node.name = name
	tbl.vm:set_node_at(pos, node)
	tbl.dirty = true
end

-- Gets the node at a given position, regardless of whether it is loaded or
-- not, respecting a transaction if one is in progress.
--
-- Outside a VM transaction, if the mapblock is not loaded, it is pulled into
-- the server’s main map data cache and then accessed from there.
--
-- Inside a VM transaction, the transaction’s VM cache is used.
function mesecon.get_node_force(pos)
	if vm_cache then
		return mesecon.vm_get_node(pos)
	else
		local node = minetest.get_node_or_nil(pos)
		if node == nil then
			-- Node is not currently loaded; use a VoxelManipulator to prime
			-- the mapblock cache and try again.
			minetest.get_voxel_manip(pos, pos)
			node = minetest.get_node_or_nil(pos)
		end
		return node
	end
end

-- Swaps the node at a given position, regardless of whether it is loaded or
-- not, respecting a transaction if one is in progress.
--
-- Outside a VM transaction, if the mapblock is not loaded, it is pulled into
-- the server’s main map data cache and then accessed from there.
--
-- Inside a VM transaction, the transaction’s VM cache is used.
--
-- This function can only be used to change the node’s name, not its parameters
-- or metadata.
--
-- The swap will necessitate a light update unless update_light equals false.
function mesecon.swap_node_force(pos, name, update_light)
	if vm_cache then
		return mesecon.vm_swap_node(pos, name, update_light)
	else
		-- This serves to both ensure the mapblock is loaded and also hand us
		-- the old node table so we can preserve param2.
		local node = mesecon.get_node_force(pos)
		node.name = name
		minetest.swap_node(pos, node)
	end
end

-- Autoconnect Hooks
-- Nodes like conductors may change their appearance and their connection rules
-- right after being placed or after being dug, e.g. the default wires use this
-- to automatically connect to linking nodes after placement.
-- After placement, the update function will be executed immediately so that the
-- possibly changed rules can be taken into account when recalculating the circuit.
-- After digging, the update function will be queued and executed after
-- recalculating the circuit. The update function must take care of updating the
-- node at the given position itself, but also all of the other nodes the given
-- position may have (had) a linking connection to.
mesecon.autoconnect_hooks = {}

-- name: A unique name for the hook, e.g. "foowire". Used to name the actionqueue function.
-- fct: The update function with parameters function(pos, node)
function mesecon.register_autoconnect_hook(name, fct)
	mesecon.autoconnect_hooks[name] = fct
	mesecon.queue:add_function("autoconnect_hook_"..name, fct)
end

function mesecon.execute_autoconnect_hooks_now(pos, node)
	for _, fct in pairs(mesecon.autoconnect_hooks) do
		fct(pos, node)
	end
end

function mesecon.execute_autoconnect_hooks_queue(pos, node)
	for name in pairs(mesecon.autoconnect_hooks) do
		mesecon.queue:add_action(pos, "autoconnect_hook_"..name, {node})
	end
end
