/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


#ifndef	__NMATRIX4__
#define	__NMATRIX4__


		#include "math/vector.h"


namespace GS {
	class Matrix3;

/*!
	@short	4x4 Matrix.
	@author Emmanuel Julien (ejulien@gsworks.fr)
*/
class	Matrix4
{
static	Matrix4	static_identity;

public:

		NPLACEMENT_NEW(Matrix)

		/// The matrix values.
		float		m[4][4];

		bool		operator == (const Matrix4 &b) const
		{
			for	(uint i = 0; i < 4; i++)
				for	(uint j = 0; j < 4; j++)
					if	(!Math::TestEqual(m[i][j], b.m[i][j]))
						return false;
			return true;
		}

		bool		operator != (const Matrix4 &b) const
		{
			for	(uint i = 0; i < 4; i++)
				for	(uint j = 0; j < 4; j++)
					if	(!Math::TestEqual(m[i][j], b.m[i][j]))
						return true;
			return false;
		}

inline	Matrix4	operator * (const Matrix4 &b) const
		{
			#define	__M44M44(__I, __J)	m[__I][0] * b.m[0][__J] + m[__I][1] * b.m[1][__J] + m[__I][2] * b.m[2][__J] + m[__I][3] * b.m[3][__J]
			return	Matrix4(
								__M44M44(0, 0), __M44M44(1, 0), __M44M44(2, 0), __M44M44(3, 0),
								__M44M44(0, 1), __M44M44(1, 1), __M44M44(2, 1), __M44M44(3, 1),
								__M44M44(0, 2), __M44M44(1, 2), __M44M44(2, 2), __M44M44(3, 2),
								__M44M44(0, 3), __M44M44(1, 3), __M44M44(2, 3), __M44M44(3, 3)
							);
		}
const	Matrix4	operator * (float v) const
		{
			Matrix4 r;
			for	(uint j = 0; j < 4; ++j)
				for	(uint i = 0; i < 4; ++i)
					r.m[i][j] = m[i][j] * v;
			return r;
		}

const	Matrix4	operator + (const Matrix4 &b) const
		{
			Matrix4 r;
			for	(uint j = 0; j < 4; j++)
				for	(uint i = 0; i < 4; i++)
					r.m[i][j] = m[i][j] + b.m[i][j];
			return r;
		}

		/// Return the nth row.
inline	Vector4		GetRow(uint n, bool w_to_one = true) const
		{	return Vector4(m[0][n], m[1][n], m[2][n], w_to_one ? 1 : m[3][n]);	}
		/// Return the nth column.
inline	Vector4		GetColumn(uint n, bool w_to_one = true) const
		{	return Vector4(m[n][0], m[n][1], m[n][2], w_to_one ? 1 : m[n][3]);	}
		/// Set the nth row.
inline	void		SetRow(uint n, const Vector4 &r, bool w_to_one = true)
		{	m[0][n] = r.x; m[1][n] = r.y; m[2][n] = r.z; m[3][n] = w_to_one ? 1 : r.w;	}
		/// Set the nth column.
inline	void		SetColumn(uint n, const Vector4 &c, bool w_to_one = true)
		{	m[n][0] = c.x; m[n][1] = c.y; m[n][2] = c.z; m[n][3] = w_to_one ? 1 : c.w;	}

		bool Inverse(Matrix4 &out) const;
		/*!
			@short		Return the inverse matrix using a fast approximation.
			@warning	This function works only for standard 3d transformation
						matrices.
		*/
		Matrix4	InversedFast() const;
		/// Transpose matrix.
		Matrix4	Transposed() const
		{
			return Matrix4(
								m[0][0], m[0][1], m[0][2], m[0][3],
								m[1][0], m[1][1], m[1][2], m[1][3],
								m[2][0], m[2][1], m[2][2], m[2][3],
								m[3][0], m[3][1], m[3][2], m[3][3]
							);
		}
		/// Normalize matrix.
		Matrix4	AsOrthonormalBase() const;

		/// Interpolate between two 4x4 transformation matrices.
static	Matrix4	LerpAsOrthonormalBase(const Matrix4 &a, const Matrix4 &b, float k, bool fast = false);

		/// Decompose a transformation matrix into a position vector, a scale vector and a 3x3 rotation matrix.
		void		Decompose(Vector4 *position, Vector4 *scale = 0, Matrix3 *rotation = 0) const;
		/// Decompose a transformation matrix into a position vector, a scale vector and a rotation vector.
		void		Decompose(Vector4 *position, Vector4 *scale, Vector4 *rotation, Math::rOrder order = Math::rOrder_Default) const;

		/// Apply to vector array.
inline	void		Apply(Vector4 *o, const Vector4 *i, uint n = 1) const
		{
			for	(uint c = 0; c < n; c++)
			{
				o[c].x = i[c].x * m[0][0] + i[c].y * m[0][1] + i[c].z * m[0][2] + i[c].w * m[0][3];
				o[c].y = i[c].x * m[1][0] + i[c].y * m[1][1] + i[c].z * m[1][2] + i[c].w * m[1][3];
				o[c].z = i[c].x * m[2][0] + i[c].y * m[2][1] + i[c].z * m[2][2] + i[c].w * m[2][3];
				o[c].w = i[c].x * m[3][0] + i[c].y * m[3][1] + i[c].z * m[3][2] + i[c].w * m[3][3];
			}
		}
		/// Apply upper-left 3x3 sub-matrix to vector array.
inline	void		ApplyRotation(Vector4 *o, const Vector4 *i, uint n = 1) const
		{
			for	(uint c = 0; c < n; c++)
			{
				o[c].x = i[c].x * m[0][0] + i[c].y * m[0][1] + i[c].z * m[0][2];
				o[c].y = i[c].x * m[1][0] + i[c].y * m[1][1] + i[c].z * m[1][2];
				o[c].z = i[c].x * m[2][0] + i[c].y * m[2][1] + i[c].z * m[2][2];
				o[c].w = 1.f;
			}
		}

		/// Set values.
		void		Set	(
							float m00, float m10, float m20, float m30,
							float m01, float m11, float m21, float m31,
							float m02, float m12, float m22, float m32,
							float m03, float m13, float m23, float m33
						)
		{
			m[0][0] = m00; m[1][0] = m10; m[2][0] = m20; m[3][0] = m30;
			m[0][1] = m01; m[1][1] = m11; m[2][1] = m21; m[3][1] = m31;
			m[0][2] = m02; m[1][2] = m12; m[2][2] = m22; m[3][2] = m32;
			m[0][3] = m03; m[1][3] = m13; m[2][3] = m23; m[3][3] = m33;
		}

		/// Identity matrix.
static	const Matrix4 &IdentityMatrix()
		{	return static_identity;	}

		/// Translation matrix.
static	Matrix4	TranslationMatrix(const Vector4 &t);
		/// Scale matrix.
static	Matrix4	ScaleMatrix(const Vector4 &s);
		/// From matrix3.
static	Matrix4	FromMatrix3(const Matrix3 &mtx);
		/// Position/scale/rotation/offset matrix.
static	Matrix4	TransformationMatrix(const Vector4 &p, const Vector4 &r, const Vector4 &s, const Vector4 *o = 0);
		/// Position/scale/rotation/offset matrix.
static	Matrix4	TransformationMatrix(const Vector4 &p, const Matrix3 &r, const Vector4 &s, const Vector4 *o = 0);

		NML::Tag	*AsMetaTag(const char *id) const;
		bool		FromMetaTag(NML::Tag &);

		Matrix4(
					float m00, float m10, float m20, float m30,
					float m01, float m11, float m21, float m31,
					float m02, float m12, float m22, float m32,
					float m03, float m13, float m23, float m33
				)
		{
			m[0][0] = m00; m[1][0] = m10; m[2][0] = m20; m[3][0] = m30;
			m[0][1] = m01; m[1][1] = m11; m[2][1] = m21; m[3][1] = m31;
			m[0][2] = m02; m[1][2] = m12; m[2][2] = m22; m[3][2] = m32;
			m[0][3] = m03; m[1][3] = m13; m[2][3] = m23; m[3][3] = m33;
		}
		Matrix4() {}
};


/*
	@short	4x4 matrix with inverse.
	@author	Emmanuel Julien (ejulien@gsworks.fr)
*/
class	Matrix4WithInverse
{
protected:

		NPLACEMENT_NEW(Matrix)

		Matrix4	matrix,
					imatrix;

		/// Commit a matrix change.
		void		Commit();

public:

		/// Return the matrix inverse.
		const Matrix4 &Get() const;
		/// Return the matrix inverse.
		const Matrix4 &GetInverse() const;

		/// Set matrix.
		void		Set(const Matrix4 &m);

		/// Return the nth row.
		Vector4		GetRow(uint n, bool w_1 = true) const;
		/// Return the nth column.
		Vector4		GetColumn(uint n, bool w_1 = true) const;
		/// Set the nth row.
		void		SetRow(uint n, const Vector4 &row, bool w_1 = true);
		/// Set the nth column.
		void		SetColumn(uint n, const Vector4 &col, bool w_1 = true);

		bool		FromMetaTag(NML::Tag &tag);
		NML::Tag	*AsMetaTag(const char *id) const;

		Matrix4WithInverse()
		{
			matrix = Matrix4::IdentityMatrix();
			imatrix = Matrix4::IdentityMatrix();
		}
};

} // GS


#endif	// __NMATRIX4__

