-- tkz_elements_functions_matrices.lua
-- date 2025/03/04
-- version 3.34c
-- Copyright 2025  Alain Matthes
-- This work may be distributed and/or modified under the
-- conditions of the LaTeX Project Public License, either version 1.3
-- of this license or (at your option) any later version.
-- The latest version of this license is in
-- http://www.latex-project.org/lppl.txt
-- and version 1.3 or later is part of all distributions of LaTeX
-- version 2005/12/01 or later.
-- This work has the LPPL maintenance status “maintained”.
-- The Current Maintainer of this work is Alain Matthes.

-- ----------------------------------------------------------------------------
function print_matrix(m, mstyle)
	local mstyle = (mstyle or "bmatrix")
	local m = (m.type == "matrix" and m.set or m)
	tex.sprint("$")
	tex.sprint("\\begin{" .. mstyle .. "}")
	for i = 1, #m do
		for j = 1, #m[1] do
			local x = m[i][j]
			local st = display(x)
			tex.sprint(st)
			if j < #m[1] then
				tex.sprint(" & ")
			end
		end
		tex.sprint("\\\\")
	end
	tex.sprint("\\end{" .. mstyle .. "}")
	tex.sprint("$")
end

function print_array(matrix)
	local mdata = (matrix.type == "matrix" and matrix.set or matrix)
	tex.sprint("\\{%")
	for i = 1, #mdata do
		local row = mdata[i]
		local row_str = "{\\{"
		for j = 1, #row do
			row_str = row_str .. " " .. tostring(row[j])
			if j < #row then
				row_str = row_str .. "  ,"
			end
		end
		if (i ~= #mdata) and (j ~= #row) then
			if i > 1 then
				row_str = row_str .. " \\}},"
			else
				row_str = row_str .. " \\}},"
			end
			tex.sprint(row_str)
		else
			if i > 1 then
				row_str = row_str .. " \\}}"
			else
				row_str = row_str .. " \\}}"
			end
			tex.sprint(row_str)
		end
	end
	tex.sprint("\\}")
end

function mul_matrix(A, B)
	local adata = (A.type == "matrix" and A.set or A)
	local bdata = (B.type == "matrix" and B.set or B)
	local C = {}
	for i = 1, #adata do
		C[i] = {}
		for j = 1, #bdata[1] do
			local num = adata[i][1] * bdata[1][j]
			for k = 2, #adata[1] do
				num = num + adata[i][k] * bdata[k][j]
			end
			C[i][j] = num
		end
	end
	return matrix:new(C)
end

function add_matrix(A, B)
	local adata = (A.type == "matrix" and A.set or A)
	local bdata = (B.type == "matrix" and B.set or B)
	local S = {}
	for i = 1, #adata do
		local T = {}
		S[i] = T
		for j = 1, #adata[1] do
			T[j] = adata[i][j] + bdata[i][j]
		end
	end
	return matrix:new(S)
end

function k_mul_matrix(n, A)
	local adata = (A.type == "matrix" and A.set or A)
	local S = {}
	for i = 1, #adata, 1 do
		local T = {}
		S[i] = T
		for j = 1, #adata[1], 1 do
			T[j] = n * adata[i][j]
		end
	end
	return matrix:new(S)
end

function transposeMatrix(A)
	local mdata = (A.type == "matrix" and A.set or A)
	local transposedMatrix = {}
	for i = 1, #mdata[1] do
		transposedMatrix[i] = {}
		for j = 1, #mdata do
			transposedMatrix[i][j] = mdata[j][i]
		end
	end
	return matrix:new(transposedMatrix)
end

-- Function to calculate the determinant of a square matrix
function determinant(A)
	local matrix = (A.type == "matrix" and A.set or A)
	if #matrix == #matrix[1] then
		local n = #matrix
		if n == 1 then
			return matrix[1][1] -- Base case for 1x1 matrix
		elseif n == 2 then
			return matrix[1][1] * matrix[2][2] - matrix[1][2] * matrix[2][1] -- Base case for 2x2 matrix
		else
			local det = 0
			for j = 1, n do
				local minor = {}
				for i = 2, n do
					minor[i - 1] = {}
					for k = 1, n do
						if k ~= j then
							minor[i - 1][#minor[i - 1] + 1] = matrix[i][k]
						end
					end
				end
				det = det + ((-1) ^ (j + 1)) * matrix[1][j] * determinant(minor) -- Recursive call for larger matrices
			end
			return det
		end
	else
		return nil
	end
end

function check_square_matrix(A)
	local matrix = (A.type == "matrix" and A.set or A)
	if #matrix == #matrix[1] then
		return true
	else
		return false
	end
end

function id_matrix(n)
	local identityMatrix = {}
	for i = 1, n do
		identityMatrix[i] = {}
		for j = 1, n do
			if i == j then
				identityMatrix[i][j] = 1
			else
				identityMatrix[i][j] = 0
			end
		end
	end
	return matrix:new(identityMatrix)
end

function inverse_2x2(A)
	local m = (A.type == "matrix" and A.set or A)
	local a, b, c, d = m[1][1], m[1][2], m[2][1], m[2][2]
	local D = A.det
	if D == 0 then
		return nil -- La matrice n'est pas inversible
	else
		local inv = {}
		inv[1] = {}
		inv[1][1] = d / D
		inv[1][2] = -b / D
		inv[2] = {}
		inv[2][1] = -c / D
		inv[2][2] = a / D
		return matrix:new(inv)
	end
end

function adjugate_(A)
	local m = (A.type == "matrix" and A.set or A)
	if #m == 2 then
		local a, b, c, d = m[2][2], -m[1][2], -m[2][1], m[1][1]
		return matrix:new({ { a, b }, { c, d } })
	elseif #m == 3 then
		local a, b, c = m[1][1], m[1][2], m[1][3]
		local d, e, f = m[2][1], m[2][2], m[2][3]
		local g, h, i = m[3][1], m[3][2], m[3][3]
		return transposeMatrix(matrix:new({
			{ e * i - f * h, -(d * i - f * g), d * h - e * g },
			{ -(b * i - c * h), a * i - c * g, -(a * h - b * g) },
			{ b * f - c * e, -(a * f - c * d), a * e - b * d },
		}))
	else
		return nil
	end
end

function inverse_3x3(A)
	local D = A.det
	if D == 0 then
		return nil -- La matrice n'est pas inversible
	else
		local adj = adjugate_(A)
		local m = (adj.type == "matrix" and adj.set or adj)
		local inv = {}
		for i = 1, 3 do
			inv[i] = {}
			for j = 1, 3 do
				inv[i][j] = m[i][j] / D
			end
		end
		return matrix:new(inv)
	end
end

-- inverse only for 2x2 or 3x3 matrix
function inv_matrix(A)
	if A.det == 0 then
		tex.print("Matrix not inversible: det = 0")
		return nil
	else
		local M = (A.type == "matrix" and A.set or A)
		local n = #M
		if n == 2 then
			local m = (A.type == "matrix" and A.set or A)
			local a, b, c, d = m[1][1], m[1][2], m[2][1], m[2][2]
			local D = A.det
			local inv = {}
			inv[1] = {}
			inv[1][1] = d / D
			inv[1][2] = -b / D
			inv[2] = {}
			inv[2][1] = -c / D
			inv[2][2] = a / D
			return matrix:new(inv)
		else
			local D = A.det
			local adj = adjugate_(A)
			local m = (adj.type == "matrix" and adj.set or adj)
			local inv = {}
			for i = 1, 3 do
				inv[i] = {}
				for j = 1, 3 do
					inv[i][j] = m[i][j] / D
				end
			end
			return matrix:new(inv)
		end
	end
end

function diagonalize_(A)
	local m = (A.type == "matrix" and A.set or A)
	local trace = m[1][1] + m[2][2]
	local a, b = m[1][1], m[1][2]
	local det = A.det
	local D = trace * trace - 4 * det
	if D > 0 then
		local va1 = (trace + math.sqrt(D)) / 2
		local va2 = (trace - math.sqrt(D)) / 2
		return matrix:new({ { va1, 0 }, { 0, va2 } }), matrix:new({ { 1, 1 }, { (va1 - a) / b, (va2 - a) / b } })
	else
		local va1 = point(trace / 2, math.sqrt(-D) / 2)
		local va2 = point(trace / 2, -math.sqrt(-D) / 2)
		return matrix:new({ { va1, 0 }, { 0, va2 } }) --,
		--   matrix : new ({{1,1},{ (va1 - a )/b, (va2 - a)/b}})
	end
end

function isDiagonal_(A)
	local matrix = (A.type == "matrix" and A.set or A)
	if check_square_matrix(A) == true then
		for i = 1, #matrix do
			for j = 1, #matrix[1] do
				if i ~= j and matrix[i][j] ~= 0 then
					return false
				end
			end
		end
		return true
	else
		return false
	end
end

function isOrthogonal_(A)
	local m = (A.type == "matrix" and A.set or A)
	if (check_square_matrix(A) == true) and (A.det ~= 0) then
		local mT = transposeMatrix(A)
		local mI = inv_matrix(A)
		if mT == mI then
			return true
		else
			return false
		end
	else
		return false
	end
end

function homogenization_(A)
	local m = (A.type == "matrix" and A.set or A)
	if A.cols ~= 1 then
		return nil
	else
		local a, b, c
		a = m[1][1]
		b = m[2][1]
		c = 1
		return matrix:new({ { a }, { b }, { c } })
	end
end

function get_element_(A, i, j)
	local m = (A.type == "matrix" and A.set or A)
	if m[i] and m[i][j] then
		return m[i][j]
	end
end

function get_htm_point(A)
	local m = (A.type == "matrix" and A.set or A)
	return point:new(m[1][1], m[2][1])
end

function htm_apply_(A, z)
	local V = homogenization_(z.mtx)
	local W = A * V
	return get_htm_point(W)
end

function htm_apply_L_(A, obj)
	local x, y
	x = htm_apply_(A, obj.pa)
	y = htm_apply_(A, obj.pb)
	return line:new(x, y)
end

function htm_apply_C_(A, obj)
	local x, y
	x = htm_apply_(A, obj.center)
	y = htm_apply_(A, obj.through)
	return circle:new(x, y)
end

function htm_apply_T_(A, obj)
	local x, y, z
	x = htm_apply_(A, obj.pa)
	y = htm_apply_(A, obj.pb)
	z = htm_apply_(A, obj.pc)
	return triangle:new(x, y, z)
end

function htm_apply_Q_(A, obj)
	local x, y, z, t
	x = htm_apply_(A, obj.pa)
	y = htm_apply_(A, obj.pb)
	z = htm_apply_(A, obj.pc)
	t = htm_apply_(A, obj.pd)
	if obj.type == "square" then
		return square:new(x, y, z, t)
	elseif obj.type == "rectangle" then
		return rectangle:new(x, y, z, t)
	elseif obj.type == "parallelogram" then
		return parallelogram:new(x, y, z, t)
	elseif obj.type == "quadrilateral" then
		return quadrilateral:new(x, y, z, t)
	end
end
