-- LUALOCALS < ---------------------------------------------------------
local klots, math, minetest, pairs, vector
    = klots, math, minetest, pairs, vector
local math_floor, math_random
    = math.floor, math.random
-- LUALOCALS > ---------------------------------------------------------

if klots.editmode then return end

local modname = minetest.get_current_modname()

local hash = minetest.hash_node_position
local unhash = minetest.get_position_from_hash

------------------------------------------------------------------------

local check_queue = {}
local check_queue_dirty
local function klot_update_ents(pos)
	pos = vector.round(pos)
	check_queue[hash(pos)] = pos
	check_queue_dirty = true
end
klots.klot_update_ents = klot_update_ents

local klot_ents = {}
local function objremove(ent, obj)
	klot_ents[ent] = nil
	return (obj or ent.object):remove()
end

local klotdefs = {}
minetest.register_on_mods_loaded(function()
		for k, v in pairs(minetest.registered_nodes) do
			if v.groups.klot then
				klotdefs[k] = v
				while #v.tiles < 6 do
					v.tiles[#v.tiles + 1] = v.tiles[#v.tiles]
				end
			end
		end
	end)

klots.tweenfrom = klots.tweenfrom or {}
local function klotcheck(self)
	local obj = self.object
	if not (obj and obj:get_pos()) then
		klot_ents[self] = nil
		return
	end

	local rp = self.pos

	local node = minetest.get_node_or_nil(rp)
	local def = node and klotdefs[node.name]
	if not def then return objremove(self, obj) end

	local tweenfrom = klots.tweenfrom[self.poskey]
	if tweenfrom then
		klots.tweenfrom[self.poskey] = nil
		obj:set_pos(tweenfrom)
		if tweenfrom.sound then
			minetest.sound_play(modname .. "_slider_move", {
					pos = tweenfrom,
					object = obj,
					gain = 0.6
				}, true)
		end
		obj:move_to(rp)
	elseif not vector.equals(obj:get_pos(), rp) then
		obj:set_pos(rp)
	end

	return obj:set_properties({
			physical = false,
			collide_with_objects = false,
			collisionbox = {0, 0, 0, 0, 0, 0},
			visual = "cube",
			visual_size = {x = 1, y = 1},
			textures = def.tiles,
			is_visible = true,
			pointable = false,
			static_save = false
		})
end

local entname = modname .. ":klot"
minetest.register_entity(entname, {
		initial_properties = {
			is_visible = false,
			pointable = false,
			statis_save = false
		},
		klotcheck = klotcheck
	})

local check_retry
local function check_retry_add(key, val)
	if not check_retry then
		check_retry = {}
		minetest.after(1 + math_random(), function()
				local total = 0
				for k, v in pairs(check_retry) do
					total = total + 1
					check_queue[k] = v
				end
				check_queue_dirty = true
				check_retry = nil
				minetest.log("warning", "klot entity retry: " .. total)
			end)
	end
	check_retry[key] = val
end

minetest.register_globalstep(function()
		if not check_queue_dirty then return end
		local batch = check_queue
		check_queue = {}
		check_queue_dirty = nil

		for ent in pairs(klot_ents) do
			if (ent.name == entname) and (not ent.gone) then
				local key = ent.poskey
				if key then
					local data = batch[key]
					if data then
						if data.n then
							objremove(ent)
						else
							klotcheck(ent)
							data.n = true
						end
					end
				end
			end
		end
		for poskey, data in pairs(batch) do
			if (not data.n) and klotdefs[minetest.get_node(data).name] then
				local obj = minetest.add_entity(data, entname)
				local ent = obj and obj:get_luaentity()
				if ent then
					klot_ents[ent] = true
					ent.poskey = poskey
					ent.pos = unhash(poskey)
					klotcheck(ent)
				else
					check_retry_add(poskey, data)
				end
			end
		end
	end)

------------------------------------------------------------------------

minetest.register_lbm({
		name = modname .. ":init",
		run_at_every_load = true,
		nodenames = {"group:klot"},
		action = klot_update_ents
	})

minetest.register_abm({
		label = "klot check",
		interval = 1,
		chance = 1,
		nodenames = {"group:klot"},
		action = klot_update_ents
	})

local emerge_radius = 31
emerge_radius = {x = emerge_radius, y = emerge_radius, z = emerge_radius}
local function urgent_load_ents(player)
	local pos = player:get_pos()
	minetest.emerge_area(
		vector.subtract(pos, emerge_radius),
		vector.add(pos, emerge_radius),
		function(blockpos)
			local minp = vector.multiply(blockpos, 16)
			local maxp = vector.add(minp, {x = 15, y = 15, z = 15})
			local found = minetest.find_nodes_in_area(minp, maxp, "group:klot")
			for i = 1, #found do klot_update_ents(found[i]) end
		end)
end
minetest.register_on_joinplayer(urgent_load_ents)
klots.register_on_player_teleport(urgent_load_ents)

local olddel = minetest.delete_area
function minetest.delete_area(pos1, pos2, ...)
	local function helper(...)
		local minp = {
			x = math_floor(pos1.x / 16) * 16,
			y = math_floor(pos1.y / 16) * 16,
			z = math_floor(pos1.z / 16) * 16,
		}
		local maxp = {
			x = math_floor(pos2.x / 16) * 16 + 15,
			y = math_floor(pos2.y / 16) * 16 + 15,
			z = math_floor(pos2.z / 16) * 16 + 15,
		}
		for ent in pairs(klot_ents) do
			local pos = ent.pos
			if pos.x >= minp.x and pos.x <= maxp.x
			and pos.y >= minp.y and pos.y <= maxp.y
			and pos.z >= minp.z and pos.z <= maxp.z then
				objremove(ent, ent.object)
			end
		end
		return ...
	end
	return helper(olddel(pos1, pos2, ...))
end
