-- LUALOCALS < ---------------------------------------------------------
local ItemStack, minetest, nodecore, pairs, setmetatable, vector
    = ItemStack, minetest, nodecore, pairs, setmetatable, vector
-- LUALOCALS > ---------------------------------------------------------

local modname = minetest.get_current_modname()

-- Register nodes that can be replaced by dynamic lights

local canreplace = {air = 0}

local true_airlike = {
	drawtype = "airlike",
	pointable = false,
	walkable = false,
	climbable = false,
	buildable_to = true,
	floodable = true,
	air_equivalent = true,
	paramtype = "light",
	light_source = 0,
	sunlight_propagates = true,
}

minetest.after(0, function()
		for k, v in pairs(minetest.registered_nodes) do
			local ok = not canreplace[k]
			for dk, dv in pairs(true_airlike) do
				ok = ok and v[dk] == dv
			end
			if ok then canreplace[k] = 0 end
		end
	end)

-- API for checking if dynamic lights are valid

local ttl = 0.25

local active_lights = {}

local function setup_light(pos, check)
	active_lights[minetest.hash_node_position(pos)] = {
		exp = nodecore.gametime + ttl,
		check = check
	}
	nodecore.dnt_set(pos, modname .. ":dynalight_check")
end

local function check_light(pos)
	local data = active_lights[minetest.hash_node_position(pos)]
	if not data then return minetest.remove_node(pos) end
	if nodecore.gametime < data.exp then return end
	if data.check and data.check() then
		data.exp = nodecore.gametime + ttl
		nodecore.dnt_set(pos, modname .. ":dynalight_check")
		return
	end
	minetest.remove_node(pos)
	return true
end

nodecore.register_dnt({
		name = modname .. ":dynalight_check",
		nodenames = {"group:dynamic_light"},
		ignore_stasis = true,
		time = ttl,
		autostart = true,
		autostart_time = 0,
		action = check_light
	})

-- Register dynamic light nodes

local function dynamic_light_node(level) return modname .. ":light" .. level end
nodecore.dynamic_light_node = dynamic_light_node

for level = 1, nodecore.light_sun - 1 do
	local name = dynamic_light_node(level)
	local def = {
		description = minetest.registered_nodes.air.description,
		light_source = level,
		air_equivalent = true,
		groups = {dynamic_light = level}
	}
	for k, v in pairs(true_airlike) do def[k] = def[k] or v end
	minetest.register_node(":" .. name, def)
	canreplace[name] = level
end

minetest.register_alias("nc_torch:wield_light", dynamic_light_node(8))

-- API for adding dynamic lights to world

local function dynamic_light_add(pos, level, check, exact)
	if not pos then return end
	local old = minetest.get_node(pos)
	local name = old.name
	local curlight = canreplace[name]
	if not curlight then
		if exact then return end
		return dynamic_light_add({x = pos.x, y = pos.y - 1, z = pos.z}, level, check, true)
		or dynamic_light_add({x = pos.x, y = pos.y + 1, z = pos.z}, level, check, true)
		or dynamic_light_add({x = pos.x + 1, y = pos.y, z = pos.z}, level, check, true)
		or dynamic_light_add({x = pos.x - 1, y = pos.y, z = pos.z}, level, check, true)
		or dynamic_light_add({x = pos.x, y = pos.y, z = pos.z + 1}, level, check, true)
		or dynamic_light_add({x = pos.x, y = pos.y, z = pos.z - 1}, level, check, true)
	end
	if level < 1 then return end
	if level > nodecore.light_sun - 1 then level = nodecore.light_sun - 1 end
	local setname = dynamic_light_node(level)
	pos = vector.round(pos)
	if curlight <= level then
		local ll = nodecore.get_node_light(pos)
		if ll and ll > level then return end
	end
	if curlight > level and not check_light(pos) then return end
	if name ~= setname then nodecore.set_node_check(pos, {name = setname}, old) end
	setup_light(pos, check)
	return true
end
nodecore.dynamic_light_add = dynamic_light_add

-- Automatic player wield lights

local function lightsrc(stack)
	local def = minetest.registered_items[stack:get_name()] or {}
	return def.light_source or 0
end

local function player_wield_light_pos(player, speed)
	local pos = player:get_pos()
	pos.y = pos.y + player:get_properties().eye_height
	local ld = player:get_look_dir()
	speed = player:get_player_control().up and speed or 0.5
	pos.x = pos.x + ld.x * speed
	pos.z = pos.z + ld.z * speed
	return vector.round(pos)
end
local function player_wield_light(player, data)
	local glow = 0
	local srcidx, srcstack
	for idx, stack in pairs(player:get_inventory():get_list("main")) do
		local src = lightsrc(stack)
		if src > glow then
			glow = src
			srcidx = idx
			srcstack = stack:get_name()
		end
	end
	if glow < 1 then return end
	local speed = data and data.physics and data.physics.speed or 1
	local pos = player_wield_light_pos(player, speed)
	local pname = player:get_player_name()
	return dynamic_light_add(pos, glow, function()
			local pl = minetest.get_player_by_name(pname)
			if not pl then return end
			local pp = player_wield_light_pos(pl, speed)
			if not vector.equals(pos, pp) then return end
			return pl:get_inventory():get_stack("main", srcidx)
			:get_name() == srcstack
		end)
end

nodecore.register_playerstep({
		label = "player wield light",
		action = function(player, data)
			if nodecore.player_visible(player) then
				return player_wield_light(player, data)
			end
		end
	})

-- Automatic entity light sources

local function entlight(self, ...)
	local stack = ItemStack(self.node and self.node.name or self.itemstring or "")
	local src = lightsrc(stack)
	if src > 0 then
		local pos = self.object:get_pos()
		if not pos then return ... end
		pos = vector.round(pos)
		nodecore.dynamic_light_add(pos, src, function()
				local curpos = self.object and self.object:get_pos()
				return curpos and vector.equals(vector.round(curpos), pos)
			end)
	end
	return ...
end
for _, name in pairs({"item", "falling_node"}) do
	local def = minetest.registered_entities["__builtin:" .. name]
	local ndef = {
		on_step = function(self, ...)
			return entlight(self, def.on_step(self, ...))
		end
	}
	setmetatable(ndef, def)
	minetest.register_entity(":__builtin:" .. name, ndef)
end

-- shade for light-scattering effect

local shadenode = modname .. ":shade"

minetest.register_node(shadenode, nodecore.underride({
			description = minetest.registered_nodes.air.description,
			air_equivalent = true,
			sunlight_propagates = false
		}, true_airlike))

nodecore.register_dnt({
		name = modname .. ":shadenode_check",
		nodenames = {shadenode},
		time = 1,
		autostart = true,
		action = function(pos)
			return minetest.remove_node(pos)
		end
	})

function nodecore.dynamic_shade_add(pos, above)
	local old = minetest.get_node(pos)
	local name = old.name
	if canreplace[name] then
		return nodecore.set_node_check(pos, {name = shadenode}, old)
	end
	if above and above > 0 then
		return nodecore.dynamic_shade_add({
				x = pos.x, y = pos.y + 1, z = pos.z
			}, above - 1)
	end
end

-- When digging a node that emits light when placed, and would
-- emit light when held, there can be a brief flicker of darkness
-- when MT predicts the dug node is gone but the dynamic light
-- hasn't been send to the player yet.

nodecore.register_on_register_item(function(_, def)
		if def.light_source and def.light_source > 0
		and def.node_dig_prediction == nil then
			def.node_dig_prediction = modname .. ":light" .. def.light_source
		end
	end)
