-- LUALOCALS < ---------------------------------------------------------
local math, minetest, nodecore, pairs, type
    = math, minetest, nodecore, pairs, type
local math_random
    = math.random
-- LUALOCALS > ---------------------------------------------------------

--[[
Nodes with the "falling_repose" group will not only fall if unsupported
from below, but if there is a sufficient drop off the sides, so simulate
an "angle of repose."
--]]

function nodecore.falling_repose_drop(posfrom, posto, node)
	minetest.spawn_falling_node(posto, node, minetest.get_meta(posfrom))
	minetest.remove_node(posfrom)
	posfrom.y = posfrom.y + 1
	return minetest.check_for_falling(posfrom)
end

nodecore.register_on_register_item(function(name, def)
		if def.type ~= "node" then return end
		
		def.groups = def.groups or {}

		if def.groups.falling_repose then def.groups.falling_node = 1 end

		def.repose_drop = def.repose_drop or nodecore.falling_repose_drop
	end)

function minetest.spawn_falling_node(pos, node, meta)
	node = node or minetest.get_node(pos)
	if node.name == "air" or node.name == "ignore" then
		return false
	end
	local obj = minetest.add_entity(pos, "__builtin:falling_node")
	if obj then
		obj:get_luaentity():set_node(node, meta or minetest.get_meta(pos):to_table())
		minetest.remove_node(pos)
		return true
	end
	return false
end

local function check_empty(pos, dx, dy, dz)
	for ndy = dy, 1 do
		local p = {x = pos.x + dx, y = pos.y + ndy, z = pos.z + dz}
		if not nodecore.buildable_to(p) then return end
	end
	return {x = pos.x + dx, y = pos.y, z = pos.z + dz}
end
function nodecore.falling_repose_check(pos)
	if minetest.check_single_for_falling(pos) then return end
	local node = minetest.get_node(pos)
	local def = minetest.registered_items[node.name] or {}
	local repose = def.groups and def.groups.falling_repose
	if not repose then return end

	-- Reposing nodes can always sit comfortably atop
	-- a non-moving node; it's only when stacked on other
	-- falling nodes that they can slip off.
	local sitdef = minetest.registered_items[minetest.get_node(
			{x = pos.x, y = pos.y - 1, z = pos.z}).name]
	if not (sitdef and sitdef.groups and sitdef.groups.falling_node)
	then return end

	local open = {}
	local ok = check_empty(pos, 1, -repose, 0)
	if ok then open[1] = ok end
	ok = check_empty(pos, -1, -repose, 0)
	if ok then open[#open + 1] = ok end
	ok = check_empty(pos, 0, -repose, 1)
	if ok then open[#open + 1] = ok end
	ok = check_empty(pos, 0, -repose, -1)
	if ok then open[#open + 1] = ok end
	if #open < 1 then return end
	return def.repose_drop(pos, open[math_random(1, #open)], node)
end

local reposeq
local qqty
local qmax = 100
local function reposeall()
	for _, v in pairs(reposeq) do v() end
	reposeq = nil
	qqty = nil
end
nodecore.register_limited_abm({
		label = "Falling Repose",
		nodenames = {"group:falling_repose"},
		neighbors = {"air"},
		interval = 2,
		chance = 5,
		action = function(pos, node)
			if not reposeq then
				reposeq = {}
				qqty = 0
				minetest.after(0, reposeall)
			end
			local f = function() nodecore.falling_repose_check(pos) end
			if #reposeq > qmax then
				local i = math_random(1, qqty)
				if i < qmax then reposeq[i] = f end
			else
				reposeq[#reposeq + 1] = f
			end
			qqty = qqty + 1
		end
	})
