-- abort if the function already exists
if minetest.delay_function then
	minetest.log("error", "[function_delayer] minetest.delay_function already exists.")
	return
end

local clock = minetest.get_us_time
local load_time_start = clock()

local maxdelay = 1 * 1000000
local skipstep = 5
local lastmod_effect = 2


local tasks = {}

-- used for the table.sort function
local previous_modname
local function sort_times(a, b)
	a, b = tasks[a], tasks[b]
	local a_first = b.time - a.time
	if a.mod_origin ~= b.mod_origin then
		if a.mod_origin == previous_modname then
			a_first = a_first - lastmod_effect
		elseif b.mod_origin == previous_modname then
			a_first = a_first + lastmod_effect
		end
	end
	a_first = a_first > 0
	previous_modname = a_first and a.mod_origin or b.mod_origin
	return a_first
end

local needs_sort, toadd, supramod
local todo = {}
function minetest.delay_function(task, func, ...)
	if type(task) == "number" then
		task = {time = task}
	end
	if toadd then
		task.time = task.time + toadd
	end
	local id = #tasks+1
	todo[#todo+1] = id
	task.mod_origin = supramod or minetest.get_last_run_mod()
	if func then
		task.func = func
		task.params = {...}
	end
	tasks[id] = task

	needs_sort = true
end

local stepnum = 0
local col_dtime = 0
minetest.register_globalstep(function(dtime)
	local count = #todo

	-- abort if nothing is todo
	if count == 0 then
		return
	end

	-- abort if it's not the skipstepths step
	stepnum = (stepnum+1)%skipstep
	col_dtime = col_dtime+dtime
	if stepnum ~= 0 then
		return
	end
	dtime = col_dtime
	col_dtime = 0

	-- get the start time
	local ts = clock() - dtime * 1000000

	if needs_sort then
		-- execute the functions with lower delays earlier
		table.sort(todo, sort_times)
		needs_sort = false
	end

	-- execute expired functions
	toadd = dtime
	local n = 1
	while true do
		local id = todo[n]
		if not id then
			break
		end
		local task = tasks[id]
		local time = task.time - dtime
		if time < 0 then
			local params = task.params or {}
			params[#params+1] = time
			local func = task.func
			supramod = task.mod_origin
			table.remove(todo, n)
			tasks[id] = nil
			func(unpack(params))
		else
			task.time = time
			n = n+1
		end
		--print("expired")
	end
	toadd = nil
	supramod = nil

	-- execute functions until the time limit is reached
	while todo[1]
	and clock() - ts < maxdelay do
		local task = tasks[todo[1]]
		local params = task.params or {}
		params[#params+1] = task.time
		local func = task.func
		supramod = task.mod_origin
		tasks[todo[1]] = nil
		table.remove(todo, 1)
		func(unpack(params))
	end
	supramod = nil
end)

-- requested by raymoo
minetest.register_on_shutdown(function()
	-- execute functions until nothing is left
	while todo[1] do
		local task = tasks[todo[1]]
		tasks[todo[1]] = nil
		table.remove(todo, 1)
		if task.run_on_shutdown then
			local params = task.params or {}
			params[#params+1] = task.time -- time is wrong here
			local func = task.func
			supramod = task.mod_origin
			func(unpack(params))
		end
	end
end)


local time = (clock()-load_time_start)/1000000
local msg = "[function_delayer] loaded after ca. "..time.." seconds"
if time > 0.01 then
	print(msg)
else
	minetest.log("info", msg)
end
