/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
****************************************************************************/

#pragma once

#include "glmVector3.h"

namespace glm
{
	/**
	* \brief A rotation matrix
	*
	* \ingroup rotations
	* \author Golaem <dev@golaem.com>
	*
	*/
	class GLM_CORE_API RotationMatrix
	{

	public:

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared);

		/**
		* \brief This matrix datas.
		*/
		Vector3 _matrix[3];

		/**
		* \brief Default constructor.
		* \param createIdentityMatrix If true the identity matrix is created, otherwise the zero matrix is (default is false).
		*/
		RotationMatrix();

		/**
		* \brief Constructor from the 9 scalar elements.
		* \param f00 Scalar at [0,0]
		* \param f01 Scalar at [0,1]
		* \param f02 Scalar at [0,2]
		* \param f10 Scalar at [1,0]
		* \param f11 Scalar at [1,1]
		* \param f12 Scalar at [1,2]
		* \param f20 Scalar at [2,0]
		* \param f21 Scalar at [2,1]
		* \param f22 Scalar at [2,2]
		*/
		RotationMatrix(
			float f00, float f01, float f02,
			float f10, float f11, float f12,
			float f20, float f21, float f22);

		/**
		* \brief operator= overload.
		*/
		RotationMatrix & operator= (RotationMatrix const & m);

		/**
		* \brief invert the matrix
		* \return the inverted matrix
		*/
		RotationMatrix inverse() const;

		/**
		* \brief set current Matrix to Identity
		*/
		void setIdentity()
		{
			_matrix[0].setValues(1.0f, 0.f, 0.f);
			_matrix[1].setValues(0.0f, 1.f, 0.f);
			_matrix[2].setValues(0.0f, 0.f, 1.f);
		}

		/**
		* \brief M*D
		* \param diag D
		* \return M*D
		*/
		RotationMatrix timesDiagonal(const Vector3& diag) const;

		/**
		* \brief Defines the coordinate system of the matrix from a primary z axis and a secondary y axis.
		* \param vz Primary axis.
		* \param vy Secondary axis.
		*/
		void setAxis_Zy(const Vector3& vz, const Vector3& vy);

		/**
		* \brief copy constructor
		*/
		RotationMatrix(const RotationMatrix & otherRotationMatrix);

		//@}

	public:
		//------------------------------------------------------------
		/**
		* \name ValueAccessor
		* These methods can get values inside the matrix from given row and column numbers.
		*/ //------------------------------------------------------------
		//@{

		/**
		* \brief operator[] returns const row
		*/
		const Vector3 &  operator [] (unsigned int r) const;

		/**
		* \brief operator[] returns row
		*/
		Vector3 &  operator [] (unsigned int r);

		/**
		* \brief get a column of the matrix
		*/
		inline void column(unsigned int c, Vector3& resultColumn) const;


		//@}

		//************************************************************
		//! @name Operators
		//************************************************************
		//@{

		/**
		* \brief Addition of two matrices
		*/
		RotationMatrix operator+ (const RotationMatrix & m) const;

		/**
		* \brief Subtraction of two matrices
		*/
		RotationMatrix operator- (const RotationMatrix & m) const;

		/**
		* \brief Multiplication of two matrices
		*/
		RotationMatrix operator* (RotationMatrix const & m) const;

		/**
		* \brief Transpose of the matrix
		*/
		RotationMatrix transpose() const;

		/**
		* \brief Scalar multiplication
		*/
		RotationMatrix operator* (float v) const;

		/**
		* \brief Scalar division
		*/
		RotationMatrix operator/ (float v) const;

		/**
		* \brief operator= float
		*/
		RotationMatrix & operator= (float v);
		//@}

		//************************************************************
		//! @name Comparison operators
		//************************************************************
		//@{

		/**
		* \brief operator==
		*/
		bool operator==(const RotationMatrix & matrix2) const;

		/**
		* \brief operator!=
		*/
		bool operator!=(const RotationMatrix & matrix2) const;

		/**
		* \brief approxEqual
		*/
		static bool approxEqual(const RotationMatrix& mat1, const RotationMatrix& mat2, float precision = GLM_NUMERICAL_PRECISION);
		//@}
	};
}

namespace glm
{
	//----------------------------------------------------------------------------
	// operator[] returns a const row
	//----------------------------------------------------------------------------
	inline const Vector3&  RotationMatrix::operator [] (unsigned int r) const
	{
		GLM_DEBUG_ASSERT((r < 3));//, "Matrix<nbLines,nbColumns>::[line] indice hors limites" );
		return _matrix[r];
	}

	//----------------------------------------------------------------------------
	// operator[] returns a row
	//----------------------------------------------------------------------------
	inline Vector3&  RotationMatrix::operator [] (unsigned int r)
	{
		GLM_DEBUG_ASSERT((r < 3));//, "Matrix<nbLines,nbColumns>::[line] indice hors limites" );
		return _matrix[r];
	}

	//----------------------------------------------------------------------------
	// column, returns a column extracted of each row
	//----------------------------------------------------------------------------
	inline void RotationMatrix::column(unsigned int c, Vector3 & resultColumn) const
	{
		GLM_DEBUG_ASSERT((c < 3));//, "Matrix<nbLines,nbColumns>::[line] indice hors limites" );
		resultColumn.x = _matrix[0][c];
		resultColumn.y = _matrix[1][c];
		resultColumn.z = _matrix[2][c];
	}

	//----------------------------------------------------------------------------
	// operator+
	//----------------------------------------------------------------------------
	inline RotationMatrix RotationMatrix::operator+ (const RotationMatrix & m) const
	{
		RotationMatrix result;

		result[0] = _matrix[0] + m._matrix[0];
		result[1] = _matrix[1] + m._matrix[1];
		result[2] = _matrix[2] + m._matrix[2];

		return result;
	}

	//----------------------------------------------------------------------------
	// operator-
	//----------------------------------------------------------------------------
	inline RotationMatrix RotationMatrix::operator- (const RotationMatrix & m) const
	{
		RotationMatrix result;

		result[0] = _matrix[0] - m._matrix[0];
		result[1] = _matrix[1] - m._matrix[1];
		result[2] = _matrix[2] - m._matrix[2];

		return result;
	}

	//----------------------------------------------------------------------------
	// operator* float
	//----------------------------------------------------------------------------
	inline RotationMatrix RotationMatrix::operator* (float v) const
	{
		RotationMatrix result;
		result[0] = _matrix[0] * v;
		result[1] = _matrix[1] * v;
		result[2] = _matrix[2] * v;
		return result;
	}

	//----------------------------------------------------------------------------
	// operator/ float
	//----------------------------------------------------------------------------
	inline RotationMatrix RotationMatrix::operator/ (float v) const
	{
		RotationMatrix result;
		result[0] = _matrix[0] / v;
		result[1] = _matrix[1] / v;
		result[2] = _matrix[2] / v;
		return result;
	}

	//----------------------------------------------------------------------------
	// operator= float
	//----------------------------------------------------------------------------
	inline RotationMatrix & RotationMatrix::operator= (float v)
	{
		_matrix[0].fill(v);
		_matrix[1].fill(v);
		_matrix[2].fill(v);
		return *this;
	}

	//----------------------------------------------------------------------------
	// operator==
	//----------------------------------------------------------------------------
	inline bool RotationMatrix::operator==(const RotationMatrix & matrix2) const
	{
		return (_matrix[0] == matrix2[0]
			&& _matrix[1] == matrix2[1]
			&& _matrix[2] == matrix2[2]);
	}

	//----------------------------------------------------------------------------
	// operator!=
	//----------------------------------------------------------------------------
	inline bool RotationMatrix::operator!=(const RotationMatrix & matrix2) const
	{
		return !((*this) == matrix2);
	}

	//----------------------------------------------------------------------------
	// approxEqual
	//----------------------------------------------------------------------------
	inline bool RotationMatrix::approxEqual(const RotationMatrix& mat1, const RotationMatrix& mat2, float precision)
	{
		return (glm::approxEqual(mat1[0], mat2[0], precision)
			&& glm::approxEqual(mat1[1], mat2[1], precision)
			&& glm::approxEqual(mat1[2], mat2[2], precision));
	}

	//----------------------------------------------------------------------------
	// operator* ( Matrix vector )
	//----------------------------------------------------------------------------
	inline Vector3 operator*(const RotationMatrix& matrix, const Vector3& vector)
	{
		Vector3 result;
		result[0] = matrix[0] * vector;
		result[1] = matrix[1] * vector;
		result[2] = matrix[2] * vector;
		return result;
	}
}

