-- Max. number of steps in the laser travel algorithm
local MAX_LASER_ITERATIONS = 50

-- Add a laser node to pos with the direction `dir`.
-- Dir is a direction vector, and only one direction must be set
function lzr_laser.add_laser(pos, dir)
	local node = minetest.get_node(pos)
	-- Laser through air
	if node.name == "air" then
		local dirs = lzr_laser.vector_to_dirs(dir)
		local dirstring = lzr_laser.dirs_to_dirstring(dirs)
		minetest.set_node(pos, {name="lzr_laser:laser_"..dirstring})
		pos = vector.add(pos, dir)
		return pos, dir
	-- Laser through laser (laser intersection)
	elseif minetest.get_item_group(node.name, "laser") > 0 then
		local dirnum = minetest.get_item_group(node.name, "laser")
		local dirstring_old = lzr_laser.dec2bin(dirnum, 6)

		local dirs_new = lzr_laser.vector_to_dirs(dir)
		local dirstring_new = lzr_laser.dirs_to_dirstring(dirs_new)

		local place_dirstring = lzr_laser.bitwise_or(dirstring_old, dirstring_new)
		minetest.set_node(pos, {name="lzr_laser:laser_"..place_dirstring})
		pos = vector.add(pos, dir)
		return pos, dir
	-- Mirror laser
	elseif minetest.get_item_group(node.name, "mirror") > 0 then
		local mirror_dir = lzr_laser.get_mirrored_laser_dir(pos, dir)
		if mirror_dir then
			-- Activate mirror node
			minetest.set_node(pos, {name="lzr_laser:mirror_on", param2 = node.param2})
			-- Set new pos and dir
			pos = vector.add(pos, mirror_dir)
			dir = mirror_dir
			return pos, dir
		else
			return false
		end
	-- Detector
	elseif minetest.get_item_group(node.name, "detector") > 0 then
		local detected = lzr_laser.check_detector(pos, dir)
		if detected then
			-- Activate node
			minetest.set_node(pos, {name="lzr_laser:detector_on", param2 = node.param2})
			local done = lzr_laser.check_detectors_in_area(lzr_globals.PLAYFIELD_START, lzr_globals.PLAYFIELD_END)
			if done then
				lzr_levels.next_level()
			end
		end
		-- Laser ends here
		return false
	-- Anything else: fail
	else
		return false
	end
end

function lzr_laser.emit_laser(pos)
	local node = minetest.get_node(pos)
	if minetest.get_item_group(node.name, "emitter") == 0 then
		minetest.log("error", "[lzr_laser] lzr_laser.emit_laser was called at invalid pos!")
		return false
	end
	local dir = minetest.facedir_to_dir(node.param2)
	dir = vector.multiply(dir, -1)
	local i_pos = vector.add(pos, dir)
	lzr_laser.travel_laser(i_pos, dir)
end

function lzr_laser.travel_laser(pos, dir)
	local i_pos = table.copy(pos)
	local cond = true
	local i = 0
	while cond do
		i = i + 1
		-- Halt execution for very long loops to prevent freezing the game
		if i > MAX_LASER_ITERATIONS then
			minetest.log("error", "[lzr_laser] lzr_laser.travel_laser aborted (too many iterations!)")
			break
		end
		local i_node = minetest.get_node(i_pos)
		i_pos, dir = lzr_laser.add_laser(i_pos, dir)
		if i_pos == false then
			cond = false
		end
	end
end

-- Remove all lasers in area and disable all laser blocks
function lzr_laser.clear_lasers_in_area(pos1, pos2, ignore_emitters)
	-- Remove lasers
	local lasers = minetest.find_nodes_in_area(pos1, pos2, {"group:laser"})
	minetest.bulk_set_node(lasers, {name="air"})

	-- Disable laser blocks (mirror, etc.)
	local laser_blocks = minetest.find_nodes_in_area(pos1, pos2, {"group:laser_block"})
	for b=1, #laser_blocks do
		local block_pos = laser_blocks[b]
		local block = minetest.get_node(block_pos)
		local def = minetest.registered_nodes[block.name]
		local is_ignored_emitter = false
		if ignore_emitters ~= true then
			is_ignored_emitter = minetest.get_item_group(block.name, "emitter") > 0
		end
		if def and not is_ignored_emitter then
			local inactive = def._lzr_inactive
			if inactive then
				minetest.set_node(block_pos, {name=inactive, param2=block.param2})
			end
		end
	end
end

-- Emit lasers from all active emitters
function lzr_laser.emit_lasers_in_area(pos1, pos2)
	local emitters = minetest.find_nodes_in_area(pos1, pos2, {"group:emitter"})
	for e=1, #emitters do
		local epos = emitters[e]
		local emitter = minetest.get_node(epos)
		local is_active = minetest.get_item_group(emitter.name, "emitter") == 2
		if is_active then
			lzr_laser.emit_laser(emitters[e])
		end
	end
end

-- Return true if all detectors in area are on (including if no detector)
function lzr_laser.check_detectors_in_area(pos1, pos2)
	local detectors = minetest.find_nodes_in_area(pos1, pos2, {"group:detector"})
	for d=1, #detectors do
		local dpos = detectors[d]
		local detector = minetest.get_node(dpos)
		local is_active = minetest.get_item_group(detector.name, "detector") == 2
		if not is_active then
			return false
		end
	end
	return true
end

-- Completely recalculate all lasers
function lzr_laser.full_laser_update(pos1, pos2)
	lzr_laser.clear_lasers_in_area(pos1, pos2)
	lzr_laser.emit_lasers_in_area(pos1, pos2)
end
