--[[

	TechAge
	=======

	Copyright (C) 2019-2021 Joachim Stolberg

	GPL v3
	See LICENSE.txt for more information

	Laser basis functions

]]--

local Entities = {}
local SIZES = {1, 2, 3, 6, 12, 24, 48}  -- for laser entities
local GAP_MIN = 1                       -- between 2 blocks
local GAP_MAX = 2 * 48                  -- between 2 blocks

-- Return the area (pos1,pos2) for a destination node
local function get_pos_range(pos, dir)
	local pos1 = vector.add(pos, vector.multiply(dir, GAP_MIN + 1))  -- min
	local pos2 = vector.add(pos, vector.multiply(dir, GAP_MAX + 1))  -- max
	return pos1, pos2
end

-- Return first pos after start pos and the destination pos
local function get_positions(pos, mem, dir)
	local pos1 = vector.add(pos, dir)  -- start pos
	local _, pos2 = get_pos_range(pos, dir)  -- last pos
	local _, pos3 = minetest.line_of_sight(pos1, pos2)
	pos3 = pos3 or pos2  -- destination node pos
	if not mem.peer_node_pos or not vector.equals(pos3, mem.peer_node_pos) then
		mem.peer_node_pos = pos3
		local dist = vector.distance(pos1, pos3)
		if dist > GAP_MIN and dist <= GAP_MAX then
			return true, pos1, pos3  -- new values
		else
			return false -- invalid values
		end
	end
	return true  -- no new values
end

-- return both both laser entities the pos and length
local function get_laser_length_and_pos(pos1, pos2, dir)
	local dist = vector.distance(pos1, pos2)

	for _, size in ipairs(SIZES) do
		if dist <= (size * 2) then
			pos1 = vector.add     (pos1, vector.multiply(dir, (size / 2) - 0.5))
			pos2 = vector.subtract(pos2, vector.multiply(dir, (size / 2) + 0.5))
			return size, pos1, pos2
		end
	end
end

local function del_laser(pos)
	local key = minetest.hash_node_position(pos)
	local items = Entities[key]
	if items then
		local laser1, laser2 = items[1], items[2]
		laser1:remove()
		laser2:remove()
		Entities[key] = nil
	end
	return key
end

local function add_laser(pos, pos1, pos2, size, param2)
	local key = del_laser(pos)

	local laser1 = minetest.add_entity(pos1, "techage:laser" .. size)
	if laser1 then
		local yaw = math.pi / 2 * (param2 + 1)
		laser1:set_rotation({x = 0, y = yaw, z = 0})
	end

	local laser2 = minetest.add_entity(pos2, "techage:laser" .. size)
	if laser2 then
		param2 = (param2 + 2) % 4  -- flip dir
		local yaw = math.pi / 2 * (param2 + 1)
		laser2:set_rotation({x = 0, y = yaw, z = 0})
	end

	Entities[key] = {laser1, laser2}
end

for _, size in ipairs(SIZES) do
	minetest.register_entity("techage:laser" .. size, {
		initial_properties = {
			visual = "cube",
			textures = {
				"techage_laser.png",
				"techage_laser.png",
				"techage_laser.png",
				"techage_laser.png",
				"techage_laser.png",
				"techage_laser.png",
			},
			use_texture_alpha = true,
			physical = false,
			collide_with_objects = false,
			pointable = false,
			static_save = false,
			visual_size = {x = size, y = 0.05, z = 0.05},
			glow = 14,
			shaded = true,
		},
	})
end

-------------------------------------------------------------------------------
-- API functions
-------------------------------------------------------------------------------
-- if force is not true, do not redraw the laser if nothing has changed
function techage.renew_laser(pos, force)
	local mem = techage.get_mem(pos)
	if force then
		mem.peer_node_pos = nil
		mem.param2 = nil
	end
	mem.param2 = mem.param2 or minetest.get_node(pos).param2
	local dir = minetest.facedir_to_dir(mem.param2)
	local res, pos1, pos2 = get_positions(pos, mem, dir)
	if pos1 then
		local size, pos3, pos4 = get_laser_length_and_pos(pos1, pos2, dir)
		if size then
			add_laser(pos, pos3, pos4, size, mem.param2)
			return res, pos1, pos2
		end
	end
	return res
end

-- techage.del_laser(pos)
techage.del_laser = del_laser