-- File: tkz_elements_path.lua
-- Copyright (c) 2023–2025 Alain Matthes
-- SPDX-License-Identifier: LPPL-1.3c
-- Maintainer: Alain Matthes

path = {}
path.__index = path

function path:new(data)
	if data ~= nil and type(data) ~= "table" then
		error("Expected table or nil in path constructor, got " .. type(data), 2)
	end
	return setmetatable(data or {}, self)
end

setmetatable(path, {
	__call = function(cls, ...)
		return cls:new(...)
	end,
})

function path.__add(p1, p2)
	local result = {}
	for _, pt in ipairs(p1) do
		result[#result + 1] = pt
	end
	for _, pt in ipairs(p2) do
		result[#result + 1] = pt
	end
	return path:new(result)
end

function path.__unm(p)
	local result = {}
	for i = #p, 1, -1 do
		result[#result + 1] = p[i]
	end
	return path:new(result)
end

function path.__sub(p1, p2)
	return p1 + -p2
end

function path:__tostring()
	return "path: { " .. table.concat(self, " , ") .. " }"
end

function path:add_point(z, decimals)
	table.insert(self, utils.format_point(z, decimals))
end

function path:concat(sep)
	sep = sep or " "
	return table.concat(self, sep)
end

function path:add_pair_to_path(z1, z2, decimals)
	decimals = decimals or 5
	local x1 = utils.format_coord(z1.re, decimals)
	local y1 = utils.format_coord(z1.im, decimals)
	local x2 = utils.format_coord(z2.re, decimals)
	local y2 = utils.format_coord(z2.im, decimals)
	local pt = string.format("%s/%s/%s/%s", x1, y1, x2, y2)
	table.insert(self, pt)
end
path.add_pair = path.add_pair_to_path

function path:show()
	for _, pt in ipairs(self) do
		tex.print(pt)
	end
end

function path:copy()
	local c = {}
	for i, pt in ipairs(self) do
		c[i] = pt
	end
	return path:new(c)
end

-- Translation
function path:translate(dx, dy)
	local moved = {}
	for _, pt in ipairs(self) do
		local x, y = utils.parse_point(pt)
		local newx = x + dx
		local newy = y + dy
		table.insert(moved, string.format("(%s,%s)", checknumber_(newx), checknumber_(newy)))
	end
	return path:new(moved)
end

-- Homothétie de centre (cx, cy), rapport k
function path:homothety(center, k)
	local scaled = {}
	for _, pt in ipairs(self) do
		local x, y = utils.parse_point(pt)
		local newx = center.re + k * (x - center.re)
		local newy = center.im + k * (y - center.im)
		table.insert(scaled, string.format("(%s,%s)", checknumber_(newx), checknumber_(newy)))
	end
	return path:new(scaled)
end

-- Rotation autour de (cx, cy) d'un angle theta en radians
function path:rotate(center, theta)
	local rotated = {}
	local cos_t = math.cos(theta)
	local sin_t = math.sin(theta)
	for _, pt in ipairs(self) do
		local x, y = utils.parse_point(pt)
		local dx, dy = x - center.re, y - center.im
		local newx = center.re + dx * cos_t - dy * sin_t
		local newy = center.im + dx * sin_t + dy * cos_t
		table.insert(rotated, string.format("(%s,%s)", checknumber_(newx), checknumber_(newy)))
	end
	return path:new(rotated)
end

-- Fermeture
function path:close()
	if #self == 0 then
		return self
	end

	local x1, y1 = utils.parse_point(self[1])
	local x2, y2 = utils.parse_point(self[#self])

	if x1 ~= x2 or y1 ~= y2 then
		local closed = {}
		for i, pt in ipairs(self) do
			closed[i] = pt
		end
		closed[#closed + 1] = self[1]
		return path:new(closed)
	else
		return self
	end
end

-- Sous-chemin
function path:sub(i1, i2)
	local subp = {}
	for i = i1 or 1, i2 or #self do
		subp[#subp + 1] = self[i]
	end
	return path:new(subp)
end

function path:concat_rawpair(sep)
	sep = sep or ","
	local list = {}
	for i = 1, #self do
		local x, y = self[i]:match("%(([^,]+),([^%)]+)%)")
		list[#list + 1] = x .. "/" .. y
	end
	return table.concat(list, sep)
end

function path:get_rawpair_list()
	local result = {}
	for _, pt in ipairs(self) do
		local x, y = utils.parse_point(pt)
		table.insert(result, string.format("%.5f/%.5f", x, y))
	end
	return result
end
function path:count()
	return #self
end

function path:get_point(i)
	local x, y = self:get_number_path(i)
	return point(x, y)
end

local function parse_xy(entry)
	if type(entry) == "table" then
		-- au cas où tu ranges déjà sous forme table
		if entry.x_num and entry.y_num then return entry.x_num, entry.y_num end
		if entry.x and entry.y then return tonumber(entry.x), tonumber(entry.y) end
	end
	-- formats tolérés: "(x,y)" | "x,y" | "x/y"
	local s = tostring(entry)
	local x, y = s:match("%(?%s*([%+%-]?%d+%.?%d*)%s*[,/ ]%s*([%+%-]?%d+%.?%d*)%s*%)?")
	return tonumber(x), tonumber(y)
end

--  coordinates of the i-th point
function path:get_number_path(i)
	local item = self[i]
	if not item then
		error(("path:get_number_path: index %s hors limites (1..%d)"):format(tostring(i), #self))
	end
	local x, y = parse_xy(item)
	if not x or not y then
		error(("path:get_number_path: impossible d’analyser l’entrée #%d"):format(i))
	end
	return x, y
end

--  Practical iterator
-- for i, x, y in PA.A:iter() do
	-- -- do something with (i, x, y)
-- end
function path:iter()
	local i, n = 0, #self
	return function()
		i = i + 1
		if i <= n then
			local x, y = self:get_number_path(i)
			return i, x, y
		end
	end
end

return path
