// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025 - 2026  Medusa Slockbower
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================

///
/// \file fennec/math/matrix.h
/// \brief the \ref fennec_math_matrix
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///

#ifndef FENNEC_MATH_MATRIX_H
#define FENNEC_MATH_MATRIX_H

///
///
/// \page fennec_math_matrix Matrices
///
/// \brief The fennec Matrix Math Module
///
/// \code #include <fennec/math/matrix.h> \endcode
///
///
///
///

#include <fennec/math/detail/_forward.h>
#include <fennec/math/detail/_matrix.h>

#include <fennec/containers/array.h>

#include <fennec/math/geometric.h>
#include <fennec/math/vector_traits.h>

namespace fennec
{


///
/// \brief returns a **copy** of the column \emph{i} of matrix \emph{m}
/// \param m the matrix
/// \param i the index of the row
/// \returns a **copy** of the column at index \emph{i}
template<typename scalar, size_t rows, size_t...cols>
constexpr vec<scalar, rows> column(const matrix<scalar, rows, cols...>& m, size_t i) noexcept {
	return m[i];
}


///
/// \brief returns a **copy** of the row \emph{i} of matrix \emph{m}
/// \param m the matrix
/// \param i the index of the row
/// \returns a **copy** of the row at index \emph{i}
template<typename scalar, size_t rows, size_t...cols>
constexpr vec<scalar, sizeof...(cols)> row(const matrix<scalar, rows, cols...>& m, size_t i) noexcept {
	return vec<scalar, sizeof...(cols)>(m[cols][i]...);
}


template<typename ScalarT>
using tmat2x2 = mat<ScalarT, 2, 2>; //!< helper for creating 2x2 matrices of the specified type.
template<typename ScalarT> using tmat2x3 = mat<ScalarT, 2, 3>; //!< helper for creating 2x3 matrices of the specified type.
template<typename ScalarT> using tmat2x4 = mat<ScalarT, 2, 4>; //!< helper for creating 2x4 matrices of the specified type.
template<typename ScalarT> using tmat3x2 = mat<ScalarT, 3, 2>; //!< helper for creating 3x2 matrices of the specified type.
template<typename ScalarT> using tmat3x3 = mat<ScalarT, 3, 3>; //!< helper for creating 3x3 matrices of the specified type.
template<typename ScalarT> using tmat3x4 = mat<ScalarT, 3, 4>; //!< helper for creating 3x4 matrices of the specified type.
template<typename ScalarT> using tmat4x2 = mat<ScalarT, 4, 2>; //!< helper for creating 4x2 matrices of the specified type.
template<typename ScalarT> using tmat4x3 = mat<ScalarT, 4, 3>; //!< helper for creating 4x3 matrices of the specified type.
template<typename ScalarT> using tmat4x4 = mat<ScalarT, 4, 4>; //!< helper for creating 4x4 matrices of the specified type.

using mat2 = tmat2x2<float_t>; //!< Specification for glsl float matrices
using mat3 = tmat3x3<float_t>; //!< Specification for glsl float matrices
using mat4 = tmat4x4<float_t>; //!< Specification for glsl float matrices

using mat2x2 = tmat2x2<float_t>; //!< Specification for sized glsl float matrices
using mat2x3 = tmat2x3<float_t>; //!< Specification for sized glsl float matrices
using mat2x4 = tmat2x4<float_t>; //!< Specification for sized glsl float matrices
using mat3x2 = tmat3x2<float_t>; //!< Specification for sized glsl float matrices
using mat3x3 = tmat3x3<float_t>; //!< Specification for sized glsl float matrices
using mat3x4 = tmat3x4<float_t>; //!< Specification for sized glsl float matrices
using mat4x2 = tmat4x2<float_t>; //!< Specification for sized glsl float matrices
using mat4x3 = tmat4x3<float_t>; //!< Specification for sized glsl float matrices
using mat4x4 = tmat4x4<float_t>; //!< Specification for sized glsl float matrices

using dmat2 = tmat2x2<double_t>; //!< Specification for glsl double matrices
using dmat3 = tmat3x3<double_t>; //!< Specification for glsl double matrices
using dmat4 = tmat3x3<double_t>; //!< Specification for glsl double matrices

using dmat2x2 = tmat2x2<double_t>; //!< Specification for size glsl double matrices
using dmat2x3 = tmat2x3<double_t>; //!< Specification for size glsl double matrices
using dmat2x4 = tmat2x4<double_t>; //!< Specification for size glsl double matrices
using dmat3x2 = tmat3x2<double_t>; //!< Specification for size glsl double matrices
using dmat3x3 = tmat3x3<double_t>; //!< Specification for size glsl double matrices
using dmat3x4 = tmat3x4<double_t>; //!< Specification for size glsl double matrices
using dmat4x2 = tmat4x2<double_t>; //!< Specification for size glsl double matrices
using dmat4x3 = tmat4x3<double_t>; //!< Specification for size glsl double matrices
using dmat4x4 = tmat4x4<double_t>; //!< Specification for size glsl double matrices


///
/// \brief Multiply matrix \emph{x} by matrix \emph{y} component-wise.
/// \details Multiply matrix x by matrix y component-wise, i.e., result[i][j] is the scalar product of x[i][j] and y[i][j].<br><br>
///          Note: to get linear algebraic matrix multiplication, use
///          the multiply operator (*)
/// \param x the first matrix
/// \param y the second matrix
/// \returns the resulting product in a matrix of the same size and base type
template<typename scalar, size_t rows, size_t...cols>
constexpr matrix<scalar, rows, cols...> matrixCompMult(const matrix<scalar, rows, cols...>& x, const matrix<scalar, rows, cols...>& y) noexcept {
	return matrix<scalar, rows, cols...>(x[cols] * y[cols] ...);
}

///
/// \brief Performs a linear algebraic multiply, multiplying \emph{c} by the components of \emph{r}, producing a matrix.
///
/// \details Treats the first parameter \emph{c} as a column vector (matrix
///          with one column) and the second parameter \emph{r} as a row
///          vector (matrix with one row) and does a linear algebraic
///          matrix multiply \emph{c \cross r}, yielding a matrix whose number of
///          rows is the number of components in \emph{c} and whose
///          number of columns is the number of components in \emph{r}.
/// \param c the column vector
/// \param r the row vector
/// \returns the resulting matrix produced by the linear algebraic product
template<typename scalar, size_t...s0, size_t...s1>
constexpr matrix<scalar, sizeof...(s0), s1...> outerProduct(const vector<scalar, s0...>& c, const vector<scalar, s1...>& r) noexcept {
	return matrix<scalar, sizeof...(s0), s1...>(
		c * r[s1]...
	);
}

///
/// \brief get the transpose of \emph{m}
/// \param m the matrix to transpose
/// \returns a matrix that is the transpose of \emph{m}
/// \details The input matrix m is not modified.
template<typename scalar, size_t rows, size_t...cols>
constexpr mat<scalar, rows, sizeof...(cols)> transpose(const matrix<scalar, rows, cols...>& m) noexcept {
	return mat<scalar, rows, sizeof...(cols)>::transpose(m);
}

///
/// \brief Returns the determinant of \emph{m}.
/// \returns the determinant of m.
template<typename scalar, size_t rows, size_t...cols>
constexpr scalar determinant(const matrix<scalar, rows, cols...>&) noexcept {
	static_assert(false, "implementation undefined");
	return 0;
}

///
/// \brief Returns the determinant of \emph{m}.
/// \returns \math{\textbf{m}^{-1}}
template<typename scalar, size_t rows, size_t...cols>
constexpr matrix<scalar, rows, cols...> inverse(const matrix<scalar, rows, cols...>&) noexcept {
	static_assert(false, "implementation undefined");
	return 1;
}

///
/// \brief matrix structure
/// \tparam ScalarT The scalar type
/// \tparam RowsV The number of rows
/// \tparam ColIndicesV The indices of the columns
template<typename ScalarT, size_t RowsV, size_t...ColIndicesV> requires(is_scalar_v<ScalarT>)
struct matrix
{
// Assertions ==========================================================================================================

	static_assert(is_arithmetic_v<ScalarT> or is_bool_v<ScalarT>);


// Typedefs & Constants ================================================================================================

	///
	/// \brief number of rows in the matrix
	static constexpr size_t rows = RowsV;

	///
	/// \brief number of columns in the matrix
	static constexpr size_t columns = sizeof...(ColIndicesV);

	///
	/// \brief number of total components
	static constexpr size_t num_components = rows * columns;

	///
	/// \brief vector class correspondant to the rows
	using row_t = vec<ScalarT, columns>;

	///
	/// \brief vector class correspondant to the columns
	using column_t = vec<ScalarT, rows>;

	///
	/// \brief base scalar type
	using scalar_t = ScalarT;

	///
	/// \brief alias for this matrix type
	using matrix_t = matrix;

	///
	/// \brief alias for the transpose matrix type
	using transpose_t = mat<ScalarT, rows, columns>;


	///
	/// \brief matrix array data
	array<column_t, columns> data;



// Constructors ========================================================================================================

	/// \name Constructors
	/// @{


	///
	/// \brief default constructor, initializes elements with 0
	///
	/// \details
	constexpr matrix() = default;


	///
	/// \brief copy constructor
	///
	/// \details
	/// \param mat matrix to copy
	constexpr matrix(const matrix_t& mat)
		: data{ mat.data } {
	}

	///
	/// \brief move constructor
	///
	/// \details
	/// \param mat matrix to move
	constexpr matrix(matrix_t&& mat) noexcept
		: data{ mat.data } {
	}

	///
	/// \brief scalar conversion operator
	/// \tparam OScalarT the scalar type of the other matrix
	/// \param mat the matrix to convert
	template<typename OScalarT>
	constexpr matrix(const matrix<OScalarT, RowsV, ColIndicesV...>& mat)
		: matrix() {
		for (size_t i = 0; i < columns; ++i) {
			for (size_t j = 0; j < rows; ++j) {
				data[i][j] = scalar_t(mat[i][j]);
			}
		}
	}

	///
	/// \brief scalar conversion operator
	/// \tparam OScalarT the scalar type of the other matrix
	/// \param mat the matrix to convert
	template<typename OScalarT>
	constexpr matrix(matrix<OScalarT, RowsV, ColIndicesV...>&& mat) noexcept
		: matrix() {
		for (size_t i = 0; i < columns; ++i) {
			for (size_t j = 0; j < rows; ++j) {
				data[i][j] = scalar_t(mat[i][j]);
			}
		}
	}


	///
	/// \brief scalar constructor, initializes a diagonal matrix with a scale of \p s
	///
	/// \details
	/// This function creates a diagonal matrix such that `(.*?)` would result in a matrix
	/// <table>
	/// <caption id="fennec_table_matrix_diagonal"></caption>
	/// <tr><th>   <th> 0 <th> 1 <th> 2
	/// <tr><th> 0 <td>2.0<td>0.0<td>0.0
	/// <tr><th> 1 <td>0.0<td>2.0<td>0.0
	/// <tr><th> 2 <td>0.0<td>0.0<td>2.0
	/// </table><br><br>
	///
	/// \param s scalar value
	constexpr matrix(scalar_t s) {
		(((ColIndicesV < rows) ? data[ColIndicesV][ColIndicesV] = s : 0), ...);
	}


	///
	/// \brief Piece-wise constructor
	///
	/// \details
	/// \tparam ArgsT
	/// \param args
	template<typename...ArgsT> requires(total_component_count_v<ArgsT...> == num_components)
	constexpr matrix(ArgsT&&...args) {
		matrix::_construct(fennec::forward<ArgsT>(args)...);
	}

	/// @}

// Assignment Operators ================================================================================================

	/// \name Assignment Operators
	/// @{

	///
	/// \brief copy assignment operator
	///
	/// \details
	/// \returns a reference to **this** after copying the contents of \p rhs
	/// \param rhs the matrix to copy
	constexpr matrix_t& operator=(const matrix_t& rhs) {
		data = rhs.data; return *this;
	}

	///
	/// \brief move assignment operator
	///
	/// \details
	/// \returns a reference to **this** after moving the contents of \p rhs
	/// \param rhs the matrix to move
	constexpr matrix_t& operator=(matrix_t&& rhs) noexcept {
		data = rhs.data; return *this;
	}

	/// @}


// Access Operators ====================================================================================================

	/// \name Access Operator
	/// @{

	///
	/// \details
	/// \param i the index
	/// \returns the column at index \emph{i}
	constexpr column_t& operator[](size_t i) {
		return data[i];
	}

	///
	/// \brief returns the column at index \emph{i}
	///
	/// \details
	/// \param i the index
	/// \returns the column at index \emph{i}
	constexpr const column_t& operator[](size_t i) const {
		return data[i];
	}

	///
	/// \details
	/// \param i the column
	/// \param j the row
	/// \returns the element in column \emph{i}, row \emph{j}
	constexpr scalar_t& operator[](size_t i, size_t j) {
		return data[i][j];
	}

	///
	/// \brief returns the cell in row \p j column \p i
	///
	/// \details
	/// \param i the column
	/// \param j the row
	/// \returns the element in column \emph{i}, row \emph{j}
	constexpr scalar_t operator[](size_t i, size_t j) const {
		return data[i][j];
	}

	/// @}

// Scalar Operators ====================================================================================================

	/// \name Scalar Operators
	/// @{

	///
	/// \brief \ref fennec::matrix "matrix" - \ref scalar addition operator.
	/// \param lhs the matrix
	/// \param rhs the scalar
	/// \returns a copy of the matrix with each component having been added by the scalar
	constexpr friend matrix_t operator+(const matrix_t& lhs, scalar_t rhs) {
		return matrix_t(lhs[ColIndicesV] + rhs ...);
	}

	///
	/// \brief \ref scalar - \ref fennec::matrix "matrix" addition operator.
	/// \param lhs the scalar
	/// \param rhs the matrix
	/// \returns a copy of the matrix with each component having been added by the scalar
	constexpr friend matrix_t operator+(scalar_t lhs, const matrix_t& rhs) {
		return matrix_t(lhs + rhs[ColIndicesV] ...);
	}

	///
	/// \brief \ref fennec::matrix "matrix" - \ref scalar subtraction operator.
	/// \param lhs the matrix
	/// \param rhs the scalar
	/// \returns a copy of the matrix with each component having been subtracted by the scalar
	constexpr friend matrix_t operator-(const matrix_t& lhs, scalar_t rhs) {
		return matrix_t(lhs[ColIndicesV] - rhs ...);
	}

	///
	/// \brief \ref scalar - \ref fennec::matrix "matrix" subtraction operator.
	/// \param lhs the scalar
	/// \param rhs the matrix
	/// \returns a copy of the matrix with each component having been subtracted by the scalar
	constexpr friend matrix_t operator-(scalar_t lhs, const matrix_t& rhs) {
		return matrix_t(lhs - rhs[ColIndicesV] ...);
	}

	///
	/// \brief \ref fennec::matrix "matrix" - \ref scalar multiplication operator.
	/// \param lhs the matrix
	/// \param rhs the scalar
	/// \returns a copy of the matrix with each component having been multiplied by the scalar
	constexpr friend matrix_t operator*(const matrix_t& lhs, scalar_t rhs) {
		return matrix_t(lhs[ColIndicesV] * rhs ...);
	}

	///
	/// \brief \ref scalar - \ref fennec::matrix "matrix" multiplication operator.
	/// \param lhs the scalar
	/// \param rhs the matrix
	/// \returns a copy of the matrix with each component having been multiplied by the scalar
	constexpr friend matrix_t operator*(scalar_t lhs, const matrix_t& rhs) {
		return matrix_t(lhs * rhs[ColIndicesV] ...);
	}

	///
	/// \brief \ref fennec::matrix "matrix" - \ref scalar division operator.
	/// \param lhs the matrix
	/// \param rhs the scalar
	/// \returns a copy of the matrix with each component having been divided by the scalar
	constexpr friend matrix_t operator/(const matrix_t& lhs, scalar_t rhs) {
		return matrix_t(lhs[ColIndicesV] / rhs ...);
	}

	///
	/// \brief \ref scalar - \ref fennec::matrix "matrix" division operator.
	/// \param lhs the scalar
	/// \param rhs the matrix
	/// \returns a copy of the matrix with each component having been divided by the scalar
	constexpr friend matrix_t operator/(scalar_t lhs, const matrix_t& rhs) {
		return matrix_t(lhs / rhs[ColIndicesV] ...);
	}



	///
	/// \brief \ref fennec::matrix "matrix" - \ref scalar addition assignment operator.
	/// \param lhs the matrix
	/// \param rhs the scalar
	/// \returns the matrix after having each component added by the scalar
	constexpr friend matrix_t& operator+=(matrix_t& lhs, scalar_t rhs) {
		((lhs[ColIndicesV] += rhs), ...);
		return lhs;
	}

	///
	/// \brief \ref scalar - \ref fennec::matrix "matrix" addition assignment operator.
	/// \param lhs the scalar
	/// \param rhs the matrix
	/// \returns the matrix after having each component added by the scalar
	constexpr friend matrix_t& operator+=(scalar_t lhs, matrix_t& rhs) {
		((rhs[ColIndicesV] += lhs), ...);
		return rhs;
	}

	///
	/// \brief \ref fennec::matrix "matrix" - \ref scalar subtraction assignment operator.
	/// \param lhs the matrix
	/// \param rhs the scalar
	/// \returns the matrix after having each component subtracted by the scalar
	constexpr friend matrix_t& operator-=(matrix_t& lhs, scalar_t rhs) {
		((lhs[ColIndicesV] -= rhs), ...);
		return lhs;
	}

	///
	/// \brief \ref scalar - \ref fennec::matrix "matrix" addition assignment operator.
	/// \param lhs the scalar
	/// \param rhs the matrix
	/// \returns the matrix after having each component added by the scalar
	constexpr friend matrix_t& operator-=(scalar_t lhs, matrix_t& rhs) {
		((rhs[ColIndicesV] -= lhs), ...);
		return rhs;
	}

	///
	/// \brief \ref fennec::matrix "matrix" - \ref scalar multiplication assignment operator.
	/// \param lhs the matrix
	/// \param rhs the scalar
	/// \returns the matrix after having each component multiplied by the scalar
	constexpr friend matrix_t& operator*=(matrix_t& lhs, scalar_t rhs) {
		((lhs[ColIndicesV] *= rhs), ...);
		return lhs;
	}

	///
	/// \brief \ref scalar - \ref fennec::matrix "matrix" multiplication assignment operator.
	/// \param lhs the scalar
	/// \param rhs the matrix
	/// \returns the matrix after having each component multiplied by the scalar
	constexpr friend matrix_t& operator*=(scalar_t lhs, matrix_t& rhs) {
		((rhs[ColIndicesV] *= lhs), ...);
		return rhs;
	}

	///
	/// \brief \ref fennec::matrix "matrix" - \ref scalar division assignment operator.
	/// \param lhs the matrix
	/// \param rhs the scalar
	/// \returns the matrix after having each component divided by the scalar
	constexpr friend matrix_t& operator/=(matrix_t& lhs, scalar_t rhs) {
		((lhs[ColIndicesV] /= rhs), ...);
		return lhs;
	}

	///
	/// \brief \ref scalar - \ref fennec::matrix "matrix" division assignment operator.
	/// \param lhs the scalar
	/// \param rhs the matrix
	/// \returns the matrix after having each component divided by the scalar
	constexpr friend matrix_t& operator/=(scalar_t lhs, matrix_t& rhs) {
		((rhs[ColIndicesV] /= lhs), ...);
		return rhs;
	}



	///
	/// \brief \ref fennec::matrix "matrix" - \ref scalar modulo operator.
	/// \param lhs the matrix
	/// \param rhs the scalar
	/// \returns a copy of the matrix with each component having been reduced modulo by the scalar
	constexpr friend matrix_t operator%(const matrix_t& lhs, scalar_t rhs) requires is_integral_v<scalar_t> {
		matrix_t retval = lhs;
		((retval[ColIndicesV] %= rhs), ...);
		return retval;
	}

	///
	/// \brief \ref scalar - \ref fennec::matrix "matrix" modulo operator.
	/// \param lhs the scalar
	/// \param rhs the matrix
	/// \returns a copy of the matrix with each component having been reduced modulo by the scalar
	constexpr friend matrix_t operator%(scalar_t lhs, const matrix_t& rhs) requires is_integral_v<scalar_t> {
		matrix_t retval = rhs;
		((retval[ColIndicesV] %= lhs), ...);
		return retval;
	}

	///
	/// \brief \ref fennec::matrix "matrix" - \ref scalar modulo assignment operator.
	/// \param lhs the matrix
	/// \param rhs the scalar
	/// \returns the matrix after having each component reduced modulo by the scalar
	constexpr friend matrix_t& operator%=(matrix_t& lhs, scalar_t rhs) requires is_integral_v<scalar_t> {
		((lhs[ColIndicesV] %= rhs), ...);
		return lhs;
	}

	///
	/// \brief \ref scalar - \ref fennec::matrix "matrix" modulo assignment operator.
	/// \param lhs the scalar
	/// \param rhs the matrix
	/// \returns the matrix after having each component reduced modulo by the scalar
	constexpr friend matrix_t& operator%=(scalar_t lhs, matrix_t& rhs) requires is_integral_v<scalar_t> {
		((rhs[ColIndicesV] %= lhs), ...);
		return rhs;
	}

	/// @}

// Vector Operators ====================================================================================================

	/// \name \ref fennec::matrix "matrix" - \ref fennec::vector "vector" Operators
	/// @{

	///
	/// \brief performs a linear algebraic multiply
	/// \param lhs the matrix
	/// \param rhs the vector
	/// \returns a vector containing the dot products of \emph{rhs} with each row of \emph{lhs}
	constexpr friend column_t operator*(const matrix_t& lhs, const row_t& rhs) {
		return _mul(lhs, rhs);
	}

	///
	/// \brief performs a linear algebraic multiply
	/// \param lhs the vector
	/// \param rhs the matrix
	/// \returns a vector containing the dot products of \emph{lhs} with each column of \emph{rhs}
	constexpr friend row_t operator*(const column_t& lhs, const matrix_t& rhs) {
		return row_t(fennec::dot(fennec::column(rhs, ColIndicesV), lhs) ...);
	}

	/// @}



// Matrix Operators ====================================================================================================

	/// \name \ref fennec::matrix "matrix" - \ref fennec::matrix "matrix" Operators
	/// @{

	///
	/// \brief matrix comparison operator
	/// \param lhs the first matrix
	/// \param rhs the second matrix
	/// \returns a boolean value that contains \emph{true} when all components of \emph{lhs} and \emph{rhs} are equal and \emph{false} otherwise
	constexpr friend bool operator==(const matrix_t& lhs, const matrix_t& rhs) {
		return lhs.data == rhs.data;
	}

	///
	/// \brief matrix comparison operator
	/// \param lhs the first matrix
	/// \param rhs the second matrix
	/// \returns a boolean value that contains \emph{true} when all components of \emph{lhs} and \emph{rhs} are not equal and \emph{false} otherwise
	constexpr friend bool operator!=(const matrix_t& lhs, const matrix_t& rhs) {
		return lhs.data != rhs.data;
	}

	///
	/// \brief performs a linear algebraic matrix multiplication
	/// \param lhs the rows to multiply with
	/// \param rhs the columns to multiply with
	/// \returns a matrix with the result of the multiplication
	template<size_t ORowsV, size_t...OColIndicesV> requires(columns == ORowsV)
	constexpr friend matrix<scalar_t, RowsV, OColIndicesV...> operator*(const matrix_t& lhs, const matrix<scalar_t, ORowsV, OColIndicesV...>& rhs) {
		return matrix<scalar_t, RowsV, OColIndicesV...>(
			matrix::_mul(lhs, rhs[OColIndicesV])...
		);
	}

	///
	/// \brief performs a linear algebraic matrix multiplication assignment
	/// \param rhs the columns to multiply with
	/// \returns a reference to \emph{this}
	template<size_t ORowsV, size_t...OColIndicesV> requires(columns == ORowsV)
	constexpr matrix<scalar_t, RowsV, OColIndicesV...>& operator*=(const matrix<scalar_t, ORowsV, OColIndicesV...>& rhs) {
		return *this = *this * rhs;
	}

	/// @}



// Private Helpers =====================================================================================================

public:

	///
	/// \param mat the matrix to transpose
	/// \returns \math{\textbf{m}^T}
	static constexpr matrix_t transpose(const transpose_t& mat) {
		return matrix_t(fennec::row(mat, ColIndicesV)...);
	}

private:

	template<size_t i0 = 0>
	constexpr void _construct() {
		// base case, does nothing, this will get optimized away
	}

	// helper for parsing parameter packs
	template<size_t i0 = 0, typename HeadT, typename...RestT>
	constexpr void _construct(HeadT&& head, RestT&&...rest) {
		matrix::_insert<i0>(head); // insert the head element
		matrix::_construct<i0 + component_count_v<HeadT>>(std::forward<RestT>(rest)...); // propagate to the rest of the arguments
	}

	// helper for inserting a scalar value
	template<size_t i0 = 0>
	constexpr void _insert(scalar_t s) {
		data[i0 / rows][i0 % rows] = s;
	}

	// helper for inserting a scalar value of differing type
	template<size_t i0 = 0, typename OScalarT> requires(is_arithmetic_v<OScalarT>)
	constexpr void _insert(OScalarT s) {
		data[i0 / rows][i0 % rows] = scalar_t(s);
	}

	// helper for inserting a vector
	template<size_t i0 = 0, size_t...i>
	constexpr void _insert(const vector<scalar_t, i...>& v) {
		(matrix::_insert<i0 + i>(v[i]), ...);
	}

	// helper for inserting a vector of differing type
	template<size_t i0 = 0, typename OScalarT, size_t...i>
	constexpr void _insert(const vector<OScalarT, i...>& v) {
		(matrix::_insert<i0 + i>(v[i]), ...);
	}

	// helper for a linear algebraic multiply
	static constexpr column_t _mul(const matrix_t& lhs, const row_t& rhs) {
		// the compiler will optimize this better than writing out a specific definition
		// when compared to glm or CxxSwizzle, this is faster by a significant margin
		// all implementations provide 7 decimal places of precision
		return ((lhs[ColIndicesV] * rhs[ColIndicesV]) + ...);
	}
};


// Internal ============================================================================================================

}


#endif // FENNEC_MATH_MATRIX_H
