#pragma once

/**
 * \file Matrix.h
 * \brief Contient la définition d'une matrice
 */

#include <cmath>
#include <cstddef>
#include <string>
#include <vector>

#include "NR.h"

/**
 * \class Matrix
 * \brief Représente une matrice d'éléments
 */
class Matrix {
  public:
	typedef long double Element;
	typedef std::vector<Element>::iterator iterator;

  private:
	std::size_t m_Raws;
	std::size_t m_Columns;
	std::vector<Element> m_Data;

  public:
	/**
	 * \brief Constructeur par défaut. Crée une matrice de taille nulle
	 */
	Matrix() : m_Raws(0), m_Columns(0) {}

	/**
	 * \brief Construit une matrice de taille donnée remplie de données aléatoires (et peut-être invalides !)
	 * \param a_Raws Le nombre de lignes
	 * \param a_Columns Le nombre de colonne
	 */
	Matrix(std::size_t a_Raws, std::size_t a_Columns);

	/**
	 * \brief Construit une matrice de taille donnée avec des éléments donnés.\n
	 * Exemple :
	 * \code Matrix(2, 2, {1, 2, 3, 4}) \endcode construit la matrice \n
	 * [1, 2]\n
	 * [3, 4]
	 * \param a_Raws Le nombre de lignes
	 * \param a_Columns Le nombre de colonne
	 * \param a_Elements Les élements à mettre
	 */
	Matrix(std::size_t a_Raws, std::size_t a_Columns, std::initializer_list<Element>&& a_Elements);

	~Matrix() {}

	/**
	 * \brief Retourne le nombre de lignes de la matrice
	 */
	std::size_t GetRawCount() const;

	/**
	 * \brief Retourne le nombre de colonnes de la matrice
	 */
	std::size_t GetColumnCount() const;

	/**
	 * \brief Transpose la matrice
	 */
	void Transpose();

	/**
	 * \brief Augmente la matrice actuelle à droite avec une autre
	 * \param a_Right Une matrice avec le bon nombre de lignes
	 * \pre Les deux matrices doivent avoir le même nombre de lignes
	 */
	void Augment(const Matrix& a_Right);

	/**
	 * \brief Augmente la matrice actuelle en dessous avec une autre
	 * \param a_Bottom Une matrice avec le bon nombre de colonnes
	 * \pre Les deux matrices doivent avoir le même nombre de colonnes
	 */
	void AugmentBottom(const Matrix& a_Bottom);

	/**
	 * \brief Affecte tous les coefficients de la matrice à un élément
	 * \param a_Element L'élément à affecter
	 */
	void Fill(Element a_Element);

	/**
	 * \brief Retourne la sous-matrice spécifiée
	 * \param a_RawOrigin L'indice de la première ligne de la matrice à récupérer
	 * \param a_ColumnOrigin L'indice de la première colonne de la matrice à récupérer
	 * \param a_RawCount Le nombre de lignes de la sous-matrice
	 * \param a_ColumnCount Le nombre de colonnes de la sous-matrice
	 * \pre a_RawOrigin + a_RawCount <= GetRawCount()
	 * \pre a_ColumnOrigin + a_ColumnCount <= GetColumnCount()
	 */
	Matrix SubMatrix(std::size_t a_RawOrigin, std::size_t a_ColumnOrigin, std::size_t a_RawCount, std::size_t a_ColumnCount) const;

	Matrix operator+(const Matrix& a_Other) const;
	Matrix operator-(const Matrix& a_Other) const;

	bool operator==(const Matrix& a_Other) const;

	/**
	 * \brief Effectue un produit matriciel
	 */
	Matrix operator*(const Matrix& a_Other) const;

	/**
	 * \brief Retourne l'élément à l'indice recherché
	 * \param a_Raw L'indice de la ligne
	 * \param a_Column L'indice de la colonne
	 */
	Element& at(std::size_t a_Raw, std::size_t a_Column);

	/**
	 * \brief Retourne l'élément à l'indice recherché (version constante)
	 * \param a_Raw L'indice de la ligne
	 * \param a_Column L'indice de la colonne
	 */
	Element at(std::size_t a_Raw, std::size_t a_Column) const;

	/**
	 * \brief Construit une matrice identité de taille donnée
	 * \param a_Size La taille de la matrice carrée
	 */
	static Matrix Identity(std::size_t a_Size);

	/**
	 * \brief Construit une matrice colonne à partir de données existantes.\n
	 * Exemple :
	 * \code
	 * Matrix::ColumnVector({1, 2, 3, 4});
	 * \endcode
	 * construit une matrice de 4 lignes et 1 colonne de coordonnées (1, 2, 3, 4)
	 */
	static Matrix ColumnVector(std::initializer_list<Element>&& a_Elements);

	/**
	 * \brief Construit une matrice ligne à partir de données existantes.\n
	 * Exemple :
	 * \code
	 * Matrix::RawVector({1, 2, 3, 4});
	 * \endcode
	 * construit une matrice de 1 ligne et 4 colonnes de coordonnées (1, 2, 3, 4)
	 */
	static Matrix RawVector(std::initializer_list<Element>&& a_Elements);

	iterator begin();
	iterator end();

	iterator GetLineIterator(std::size_t a_Raw);
};

template <typename T>
bool IsEqualZero(const T& var) {
	return std::abs(var) < std::pow(10, -5);
}

template <>
inline bool IsEqualZero(const int& var) {
	return var == 0;
}

template <>
inline bool IsEqualZero(const long& var) {
	return var == 0;
}

template <>
inline bool IsEqualZero(const NR& var) {
	return var == 0;
}
