local ofs = {
	neighbors = {
		{x =  1, y =  0, z =  0};
		{x = -1, y =  0, z =  0};
		{x =  0, y =  1, z =  0};
		{x =  0, y = -1, z =  0};
		{x =  0, y =  0, z =  1};
		{x =  0, y =  0, z = -1};
	};
	planecorners = {
		{x =  1, y =  0, z =  1};
		{x = -1, y =  0, z =  1};
		{x = -1, y =  0, z = -1};
		{x =  1, y =  0, z = -1};

		{x =  1, y =  1, z =  0};
		{x = -1, y =  1, z =  0};
		{x = -1, y = -1, z =  0};
		{x =  1, y = -1, z =  0};
	};
	cubecorners = {
		{x =  1, y =  1, z =  1};
		{x = -1, y =  1, z =  1};
		{x = -1, y = -1, z =  1};
		{x = -1, y = -1, z = -1};
		{x =  1, y = -1, z = -1};
		{x =  1, y =  1, z = -1};
		{x =  1, y = -1, z =  1};
		{x = -1, y =  1, z = -1};
	};
	nextto = {
		{x =  1, y =  0, z =  0};
		{x = -1, y =  0, z =  0};
		{x =  0, y =  0, z =  1};
		{x =  0, y =  0, z = -1};
	};
}

ofs.adjoining = sorcery.lib.tbl.append(sorcery.lib.tbl.append(
	ofs.neighbors,ofs.planecorners),ofs.cubecorners)

local purge_container = function(only, pos,node,meta,user)
	local offset = function(pos,range)
		local r = function(min,max)
			return (math.random() * (max - min)) + min
		end
		return {
			x = pos.x + r(0 - range, range);
			y = pos.y;
			z = pos.z + r(0 - range, range);
		}
	end
	for name, inv in pairs(meta.inventory) do
		if only and not sorcery.lib.tbl.has(only,name) then goto skip end
		for _, item in pairs(inv) do
			if not item:is_empty() then
				minetest.add_item(offset(pos,0.4), item)
			end
		end
	::skip::end
end;
return {
	offsets = ofs;
	purge_container = function(...) return purge_container(nil, ...) end;
	purge_only = function(lst)
		return function(...)
			return purge_container(lst, ...)
		end
	end; 

	is_air = function(pos)
		local n = sorcery.lib.node.force(pos)
		if n.name == 'air' then return true end
		local d = minetest.registered_nodes[n.name]
		if not d then return false end
		return not d.walkable
	end;

	get_arrival_point = function(pos)
		local air = sorcery.lib.node.is_air
		if air(pos) then
			local n = {x=0,y=1,z=0}
			if air(vector.add(pos,n)) then return pos end
			local down = vector.subtract(pos,n)
			if air(down) then return down end
		else return nil end
	end;

	amass = function(startpoint,names,directions)
		if not directions then directions = ofs.neighbors end
		local nodes, positions, checked = {},{},{}
		local checkedp = function(pos)
			for _,v in pairs(checked) do
				if vector.equals(pos,v) then return true end
			end
			return false
		end
		local i,stack = 1,{startpoint} repeat
			local pos = stack[i]
			local n = sorcery.lib.node.force(pos).name
			if sorcery.lib.tbl.has(names, n, function(check,against)
				return sorcery.lib.item.groupmatch(against,check)
			end) then -- match found
				-- record the find
				nodes[pos] = n
				if positions[n] then positions[n][#positions[n]] = pos
				else positions[n] = {pos} end

				-- check selected neighbors to see if any need scanning
				for _,d in pairs(directions) do
					local sum = vector.add(pos, d)
					if not checkedp(sum) then
						stack[#stack + 1] = sum
					end
				end
			end
			checked[#checked+1] = pos
			i = i + 1
		until i > #stack
		return nodes, positions
	end;

	forneighbor = function(pos, n, fn)
		for _,p in pairs(n) do
			local sum = vector.add(pos, p)
			local n = minetest.get_node(sum)
			if n.name == 'ignore' then
				minetest.load_area(sum)
				n = minetest.get_node(sum)
			end
			fn(sum, n)
		end
	end;
	
	force = function(pos,preload_for)
		local n = minetest.get_node_or_nil(pos)
		if preload_for then sorcery.lib.node.preload(pos,preload_for) end
		if n then return n end

		minetest.load_area(pos)
		return minetest.get_node(pos)
	end;

	-- when items have already been removed; notify cannot be relied on
	-- to reach the entire network; this function accounts for the gap
	notifyneighbors = function(pos)
		sorcery.lib.node.forneighbor(pos, sorcery.ley.txofs, function(sum,node)
			if minetest.get_item_group(node.name,'sorcery_ley_device') ~= 0 then
				sorcery.ley.notify(sum)
			end
		end)
	end;

	blockpos = function(pos)
		return {
			x = math.floor(pos.x / 16);
			y = math.floor(pos.y / 16);
			z = math.floor(pos.z / 16);
		}
	end;

	preload = function(pos, user)
		minetest.load_area(pos)
		user:send_mapblock(sorcery.lib.node.blockpos(pos))
	end;

	discharger = function(pos)
		local below = sorcery.lib.node.force(vector.subtract(pos,{x=0,y=1,z=0}))
		if below.name == 'hopper:hopper'
		or below.name == 'hopper:hopper_side' then
			local hopper = minetest.get_meta(below):get_inventory()
			return function(i)
				if hopper:room_for_item('main',i) then
					return hopper:add_item('main',i), true
				end
				return i, false
			end
		else
			return function(i) return i, false end
		end
	end;
}
