-- https://www.geeksforgeeks.org/bresenhams-algorithm-for-3-d-line-drawing/
-- This Site is affiliated under CCBY-SA https://www.geeksforgeeks.org/legal/copyright-information/
-- JS code for generating points on a 3-D line 
-- using Bresenham's Algorithm
-- Converted from the original to Lua
function edit.calculate_line_points(p1, p2)
	p1 = table.copy(p1)
	p2 = table.copy(p2)
	local output = {table.copy(p1)}
	local d = vector.apply(vector.subtract(p1, p2), math.abs)
	local s = vector.new(
		p2.x > p1.x and 1 or -1,
		p2.y > p1.y and 1 or -1,
		p2.z > p1.z and 1 or -1
	)

	-- Driving axis is X-axis
	if d.x >= d.y and d.x >= d.z then
		local n1 = 2 * d.y - d.x
		local n2 = 2 * d.z - d.x
		while p1.x ~= p2.x do
			p1.x = p1.x + s.x
			if n1 >= 0 then
				p1.y = p1.y + s.y
				n1 = n1 - 2 * d.x
			end
			if n2 >= 0 then
				p1.z = p1.z + s.z
				n2 = n2 - 2 * d.x
			end
			n1 = n1 + 2 * d.y
			n2 = n2 + 2 * d.z
			table.insert(output, table.copy(p1))
		end

	-- Driving axis is Y-axis
	elseif d.y >= d.x and d.y >= d.z then
		local n1 = 2 * d.x - d.y
		local n2 = 2 * d.z - d.y
		while p1.y ~= p2.y do
			p1.y = p1.y + s.y
			if n1 >= 0 then
				p1.x = p1.x + s.x
				n1 = n1 - 2 * d.y
			end
			if n2 >= 0 then
				p1.z = p1.z + s.z
				n2 = n2 - 2 * d.y
			end
			n1 = n1 + 2 * d.x
			n2 = n2 + 2 * d.z
			table.insert(output, table.copy(p1))
		end

	-- Driving axis is Z-axis
	else
		local n1 = 2 * d.y - d.z
		local n2 = 2 * d.x - d.z
		while p1.z ~= p2.z do
			p1.z = p1.z + s.z
			if n1 >= 0 then
				p1.y = p1.y + s.y
				n1 = n1 - 2 * d.z
			end
			if n2 >= 0 then
				p1.x = p1.x + s.x
				n2 = n2 - 2 * d.z
			end
			n1 = n1 + 2 * d.y
			n2 = n2 + 2 * d.x
			table.insert(output, table.copy(p1))
		end
	end
	return output
end

minetest.register_entity("edit:line", {
	initial_properties = {
		visual = "cube",
		visual_size = { x = 1.1, y = 1.1 },
		physical = false,
		collide_with_objects = false,
		static_save = false,
		use_texture_alpha = true,
		glow = -1,
		backface_culling = false,
		hp_max = 1,
		textures = {
			"edit_line.png",
			"edit_line.png",
			"edit_line.png",
			"edit_line.png",
			"edit_line.png",
			"edit_line.png",
		},
	},
	on_deactivate = function(self)
		local player_data = edit.player_data[self._placer]
		self.remove_called = true
		if player_data then
			local line1 = player_data.line1
			if line1 and not line1.remove_called then
				line1.object:remove()
			end
			player_data.line1 = nil

			local line2 = player_data.line2
			if line2 and not line2.remove_called then
				line2.object:remove()
			end
			player_data.line2 = nil

			player_data.old_pointed_pos = nil
		end
	end,
})

local function place_line(player, item_name)
	local player_data = edit.player_data[player]
	if not player_data.line1 then return end

	if not item_name then
		player_data.line1.object:remove()
		return
	end

	local pos1 = player_data.line1._pos
	local pos2 = player_data.line2._pos

	local size = vector.add(vector.apply(vector.subtract(pos1, pos2), math.abs), vector.new(1, 1, 1))
	local pos = vector.new(
		math.min(pos1.x, pos2.x),
		math.min(pos1.y, pos2.y),
		math.min(pos1.z, pos2.z)
	)
	player_data.undo_schematic = edit.schematic_from_map(pos, size)

	local line_points = edit.calculate_line_points(pos1, pos2)
	local item = {name = item_name}
	for i, pos in pairs(line_points) do
		edit.place_item_like_player(player, item, pos)
	end
	player_data.line1.object:remove()
end

local function line_on_place(itemstack, player, pointed_thing)
	if not edit.on_place_checks(player) then return end

	if not pointed_thing.above then
		pointed_thing = edit.get_pointed_thing_node(player)
	end

	local pos = edit.pointed_thing_to_pos(pointed_thing)
	if not pos then return end

	local player_data = edit.player_data[player]

	if not player_data.line1 then
		player_data.line1 = edit.add_marker("edit:line", pos, player)
		if not player_data.line1 then return end
	else
		player_data.line2 = edit.add_marker("edit:line", pos, player)
		if not player_data.line2 then return end

		local diff = vector.subtract(player_data.line1._pos, pos)
		local volume = vector.add(vector.apply(diff, math.abs), 1)
		if volume.x * volume.y * volume.z > edit.max_operation_volume then
			edit.display_size_error(player)
			player_data.line1.object:remove()
			return
		end

		edit.player_select_item(player, "Select item to fill the line", place_line)
	end
	edit.old_pointed_pos = nil
end

minetest.register_tool("edit:line", {
	description = "Edit Line",
	tiles = {"edit_line.png"},
	inventory_image = "edit_line.png",
	range = 10,
	groups = {edit_place_preview = 1, not_in_creative_inventory = 1},
	on_place = line_on_place,
	on_secondary_use = line_on_place,
	_edit_get_selection_points = function(player)
		local d = edit.player_data[player]
		return d.line1 and d.line1._pos, d.line2 and d.line2._pos
	end
})
