-- File: tkz_elements_vector.lua
-- Copyright (c) 2026 Alain Matthes
-- SPDX-License-Identifier: LPPL-1.3c
-- Maintainer: Alain Matthes

-- ----------------------------------------------------------------------------
vector = {}
vector.__index = vector
-- Constructor for creating a new vector
function vector:new(za, zb)
	local type = "vector"
	local mtx = matrix:new({ { za }, { zb } })
  local z = zb - za
	local dx = z.re
	local dy = z.im
	local norm = point.mod(z)
	local slope = 0
	if norm > (tkz.epsilon or 0) then
		slope = angle_normalize_(point.arg(z))
	end
local vect = {
			type  = type,
			tail  = za,
			head  = zb,
			z     = z,
			dx    = dx,
			dy    = dy,
			norm  = norm,
			slope = slope,
			mtx   = mtx,
	}

	setmetatable(vect, self)
	return vect
end

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

function vector.__add(v1, v2)
	return v1:add(v2)
end

function vector.__sub(v1, v2)
	return v1:add(v2:scale(-1))
end

function vector.__unm(v)
	return v:scale(-1)
end

-- function vector.__mul(r, v)
	-- return v:scale(r)
-- end

function vector.__mul(a, b)
	local ta, tb = type(a), type(b)

	if ta == "number" and getmetatable(b) == vector then
		return b:scale(a)
	elseif tb == "number" and getmetatable(a) == vector then
		return a:scale(b)
	else
		error("vector.__mul: expected scalar * vector or vector * scalar")
	end
end


function vector.__pow(v1, v2)
	local z
	z = point.conj(v1.head - v1.tail) * (v2.head - v2.tail)
	return z.im
end

function vector.__concat(v1, v2)
	local z
	z = (v1.head - v1.tail) * point.conj((v2.head - v2.tail))
	return z.re
end

function vector:get()
	return self.tail, self.head
end

function vector:is_zero(EPS)
	EPS = EPS or tkz.epsilon
	return self.norm <= EPS
end

function vector:is_parallel(v, EPS)
	EPS = EPS or tkz.epsilon
	if self:is_zero(EPS) or v:is_zero(EPS) then
		return false
	end
	return math.abs(self:cross(v)) <= EPS * self.norm * v.norm
end

function vector:is_orthogonal(v, EPS)
	EPS = EPS or tkz.epsilon
	if self:is_zero(EPS) or v:is_zero(EPS) then
		return false
	end
	return math.abs(self:dot(v)) <= EPS * self.norm * v.norm
end


-- Normalize the vector (unit vector)
-- function vector:normalize()
	-- local z = self.head - self.tail
	-- local d = point.abs(z)
	-- local nz = point(z.re / d, z.im / d)
	-- return vector:new(self.tail, nz + self.tail)
-- end

function vector:dot(v)
	local z = point.conj(self.z) * v.z
	return z.re
end

function vector:cross(v)
	local z = point.conj(self.z) * v.z
	return z.im
end



function vector:normalize()
  local EPS = tkz.epsilon
  if self.norm <= EPS then
  	tex.error("Cannot normalize a near-zero vector.")
  	return self
  end
	local u = point(self.z.re / self.norm, self.z.im / self.norm)
	return vector:new(self.tail, self.tail + u)
end


function vector:add(ve)
	return vector:new(self.tail, self.head + ve.head - ve.tail)
end



-- --Scale the vector by a scalar
-- function vector:scale(d)
	-- local z = scale_(self, d)
	-- return vector:new(self.tail, z)
-- end

function vector:scale(d)
	local new_head = scaled_head_(self, d)
	return vector:new(self.tail, new_head)
end


function vector:at(zc)
	return vector:new(zc, zc + self.head - self.tail)
end

function vector:angle_to(v)
	-- angle entre self et v (orienté)
	return angle_normalize_(v.slope - self.slope)
end

function vector:rotate(theta)
	return vector:new(self.tail, rotation_(self.tail, theta, self.head))
end

-- side = "ccw" (par défaut) ou "cw"
-- length optionnel pour imposer la norme du vecteur orthogonal
function vector:orthogonal(side, length)
	local theta = (side == "cw") and -math.pi / 2 or math.pi / 2

	if length then
		-- on part du vecteur normalisé, on impose sa norme, puis on tourne
		local u = self:normalize()
		local tip = self.tail + point(length * math.cos(u.slope), length * math.sin(u.slope))
		return vector:new(self.tail, rotation_(self.tail, theta, tip))
	else
		return self:rotate(theta)
	end
end

return vector
