-- LUALOCALS < ---------------------------------------------------------
local error, ipairs, math, minetest, next, pairs, piredo_api,
      piredo_schems, string, table, vector
    = error, ipairs, math, minetest, next, pairs, piredo_api,
      piredo_schems, string, table, vector
local math_ceil, math_floor, math_random, string_format, string_match,
      table_sort
    = math.ceil, math.floor, math.random, string.format, string.match,
      table.sort
-- LUALOCALS > ---------------------------------------------------------

local api = piredo_api.get_mod_api()
local hash = minetest.hash_node_position

local sendqueue = {}

local function queuesend(pos, size)
	for x = math_floor(pos.x / 16), math_ceil((pos.x + size.x) / 16) do
		for y = math_floor(pos.y / 16), math_ceil((pos.y + size.y) / 16) do
			for z = math_floor(pos.z / 16), math_ceil((pos.z + size.z) / 16) do
				local p = vector.new(x, y, z)
				sendqueue[hash(p)] = p
			end
		end
	end
end

do
	local hertz = 250
	local batch = {}
	local batchpos = 1
	local timeaccum = 0
	local active
	minetest.register_globalstep(function(dtime)
			if batchpos > #batch then
				if not next(sendqueue) then return end
				batchpos = 1
				batch = {}
				for _, v in pairs(sendqueue) do batch[#batch + 1] = v end
				sendqueue = {}
				for i = #batch, 2, -1 do
					local j = math_random(1, i)
					local tmp = batch[i]
					batch[i] = batch[j]
					batch[j] = tmp
				end
			end
			if not active then
				minetest.log("info", "mapblock flush started")
				active = true
			end
			timeaccum = timeaccum + dtime
			local qty = math_floor(timeaccum * hertz)
			timeaccum = timeaccum - qty / hertz
			for _ = 1, qty do
				if batchpos > #batch then break end
				for _, player in ipairs(minetest.get_connected_players()) do
					player:send_mapblock(batch[batchpos])
				end
				batchpos = batchpos + 1
			end
			if batchpos > #batch and not next(sendqueue) then
				minetest.log("info", "mapblock flush finished")
				active = nil
			end
		end)
end

api.registered_on_putschems = {}
function api.register_on_putschem(f)
	api.registered_on_putschems[#api.registered_on_putschems + 1] = f
end

function api.putschem(pos, room, noevents)
	local roomdata = piredo_schems[room] or error(string_format("room %q not found", room))
	if not string_match(room, "^clear_") then
		api.statedb.loaded = api.statedb.loaded or {}
		api.statedb.loaded[hash(pos)] = {
			pos = pos,
			room = room
		}
	end
	if roomdata.pos then pos = vector.add(pos, roomdata.pos) end
	api.savedb()
	queuesend(pos, roomdata.size)
	minetest.place_schematic(pos, roomdata.mts)
	if noevents then return end
	minetest.after(0, function()
			for i = 1, #api.registered_on_putschems do
				(api.registered_on_putschems[i])(pos, room, roomdata)
			end
		end)
end

function api.clearschems()
	if not api.statedb.loaded then return end
	for _, v in pairs(api.statedb.loaded) do
		local size = piredo_schems[v.room].size.x > 20 and 40 or 20
		api.putschem(v.pos, "clear_" .. size, true)
	end
	api.statedb.loaded = nil
	api.savedb()
end

do
	local sortedschems = {}
	for k in pairs(piredo_schems) do
		sortedschems[#sortedschems + 1] = k
	end
	table_sort(sortedschems)
	function api.find_schematics(prefix)
		local t = {}
		for i = 1, #sortedschems do
			local k = sortedschems[i]
			if piredo_api.starts_with(k, prefix) then
				t[#t + 1] = k
			end
		end
		return t
	end
end
