#ifndef _TCUMATRIX_HPP
#define _TCUMATRIX_HPP
/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief 	Templatized matrix class.
 *//*--------------------------------------------------------------------*/

#include "tcuDefs.hpp"
#include "tcuVector.hpp"
#include "tcuArray.hpp"

namespace tcu
{

// Templated matrix class.
template <typename T, int Rows, int Cols>
class Matrix
{
public:
	typedef	Vector<T, Rows>			Element;
	typedef	T						Scalar;

	enum
	{
		SIZE = Cols,
		ROWS = Rows,
		COLS = Cols,
	};

									Matrix				(void);
	explicit						Matrix				(const T& src);
	explicit						Matrix				(const T src[Rows*Cols]);
									Matrix				(const Vector<T, Rows>& src);
									Matrix				(const Matrix<T, Rows, Cols>& src);
									~Matrix				(void);

	Matrix<T, Rows, Cols>&			operator=			(const Matrix<T, Rows, Cols>& src);
	Matrix<T, Rows, Cols>&			operator*=			(const Matrix<T, Rows, Cols>& src);

	void							setRow				(int rowNdx, const Vector<T, Cols>& vec);
	void							setColumn			(int colNdx, const Vector<T, Rows>& vec);

	Vector<T, Cols>					getRow				(int ndx) const;
	Vector<T, Rows>&				getColumn			(int ndx);
	const Vector<T, Rows>&			getColumn			(int ndx) const;

	Vector<T, Rows>&				operator[]			(int ndx)					{ return getColumn(ndx);	}
	const Vector<T, Rows>&			operator[]			(int ndx) const				{ return getColumn(ndx);	}

	inline const T&					operator()			(int row, int col) const	{ return m_data[col][row];	}
	inline T&						operator()			(int row, int col)			{ return m_data[col][row];	}

	Array<T, Rows*Cols>				getRowMajorData		(void) const;
	Array<T, Rows*Cols>				getColumnMajorData	(void) const;

private:
	Vector<Vector<T, Rows>, Cols>	m_data;
} DE_WARN_UNUSED_TYPE;

// Operators.

// Mat * Mat.
template <typename T, int Rows0, int Cols0, int Rows1, int Cols1>
Matrix<T, Rows0, Cols1> operator* (const Matrix<T, Rows0, Cols0>& a, const Matrix<T, Rows1, Cols1>& b);

// Mat * Vec (column vector).
template <typename T, int Rows, int Cols>
Vector<T, Rows> operator* (const Matrix<T, Rows, Cols>& mtx, const Vector<T, Cols>& vec);

// Vec * Mat (row vector).
template <typename T, int Rows, int Cols>
Vector<T, Cols> operator* (const Vector<T, Rows>& vec, const Matrix<T, Rows, Cols>& mtx);

// Further operations

template <typename T, int Size>
struct SquareMatrixOps
{
	static T						doDeterminant	(const Matrix<T, Size, Size>& mat);
	static Matrix<T, Size, Size>	doInverse		(const Matrix<T, Size, Size>& mat);
};

template <typename T>
struct SquareMatrixOps<T, 2>
{
	static T						doDeterminant	(const Matrix<T, 2, 2>& mat);
	static Matrix<T, 2, 2>			doInverse		(const Matrix<T, 2, 2>& mat);
};

template <typename T>
struct SquareMatrixOps<T, 3>
{
	static T						doDeterminant	(const Matrix<T, 3, 3>& mat);
	static Matrix<T, 3, 3>			doInverse		(const Matrix<T, 3, 3>& mat);
};

template <typename T>
struct SquareMatrixOps<T, 4>
{
	static T						doDeterminant	(const Matrix<T, 4, 4>& mat);
	static Matrix<T, 4, 4>			doInverse		(const Matrix<T, 4, 4>& mat);
};

namespace matrix
{

template <typename T, int Size>
T determinant (const Matrix<T, Size, Size>& mat)
{
	return SquareMatrixOps<T, Size>::doDeterminant(mat);
}

template <typename T, int Size>
Matrix<T, Size, Size> inverse (const Matrix<T, Size, Size>& mat)
{
	return SquareMatrixOps<T, Size>::doInverse(mat);
}

} // matrix

// Template implementations.

template <typename T>
T SquareMatrixOps<T, 2>::doDeterminant (const Matrix<T, 2, 2>& mat)
{
	return mat(0,0) * mat(1,1) - mat(1,0) * mat(0,1);
}

template <typename T>
T SquareMatrixOps<T, 3>::doDeterminant (const Matrix<T, 3, 3>& mat)
{
	return	+ mat(0,0) * mat(1,1) * mat(2,2)
			+ mat(0,1) * mat(1,2) * mat(2,0)
			+ mat(0,2) * mat(1,0) * mat(2,1)
			- mat(0,0) * mat(1,2) * mat(2,1)
			- mat(0,1) * mat(1,0) * mat(2,2)
			- mat(0,2) * mat(1,1) * mat(2,0);
}

template <typename T>
T SquareMatrixOps<T, 4>::doDeterminant (const Matrix<T, 4, 4>& mat)
{
	using matrix::determinant;

	const T minorMatrices[4][3*3] =
	{
		{
			mat(1,1),	mat(2,1),	mat(3,1),
			mat(1,2),	mat(2,2),	mat(3,2),
			mat(1,3),	mat(2,3),	mat(3,3),
		},
		{
			mat(1,0),	mat(2,0),	mat(3,0),
			mat(1,2),	mat(2,2),	mat(3,2),
			mat(1,3),	mat(2,3),	mat(3,3),
		},
		{
			mat(1,0),	mat(2,0),	mat(3,0),
			mat(1,1),	mat(2,1),	mat(3,1),
			mat(1,3),	mat(2,3),	mat(3,3),
		},
		{
			mat(1,0),	mat(2,0),	mat(3,0),
			mat(1,1),	mat(2,1),	mat(3,1),
			mat(1,2),	mat(2,2),	mat(3,2),
		}
	};

	return	+ mat(0,0) * determinant(Matrix<T, 3, 3>(minorMatrices[0]))
			- mat(0,1) * determinant(Matrix<T, 3, 3>(minorMatrices[1]))
			+ mat(0,2) * determinant(Matrix<T, 3, 3>(minorMatrices[2]))
			- mat(0,3) * determinant(Matrix<T, 3, 3>(minorMatrices[3]));
}

template <typename T>
Matrix<T, 2, 2> SquareMatrixOps<T, 2>::doInverse (const Matrix<T, 2, 2>& mat)
{
	using matrix::determinant;

	const T			det		= determinant(mat);
	Matrix<T, 2, 2>	retVal;

	retVal(0, 0) =  mat(1, 1) / det;
	retVal(0, 1) = -mat(0, 1) / det;
	retVal(1, 0) = -mat(1, 0) / det;
	retVal(1, 1) =  mat(0, 0) / det;

	return retVal;
}

template <typename T>
Matrix<T, 3, 3> SquareMatrixOps<T, 3>::doInverse (const Matrix<T, 3, 3>& mat)
{
	// Blockwise inversion
	using matrix::inverse;

	const T areaA[2*2] =
	{
		mat(0,0),	mat(0,1),
		mat(1,0),	mat(1,1)
	};
	const T areaB[2] =
	{
		mat(0,2),
		mat(1,2),
	};
	const T areaC[2] =
	{
		mat(2,0),	mat(2,1),
	};
	const T areaD[1] =
	{
		mat(2,2)
	};
	const T nullField[4] = { T(0.0f) };

	const Matrix<T, 2, 2>	invA = inverse(Matrix<T, 2, 2>(areaA));
	const Matrix<T, 2, 1>	matB =         Matrix<T, 2, 1>(areaB);
	const Matrix<T, 1, 2>	matC =         Matrix<T, 1, 2>(areaC);
	const Matrix<T, 1, 1>	matD =         Matrix<T, 1, 1>(areaD);

	const T					schurComplement = T(1.0f) / (matD - matC*invA*matB)(0,0);
	const Matrix<T, 2, 2>	zeroMat         = Matrix<T, 2, 2>(nullField);

	const Matrix<T, 2, 2>	blockA = invA + invA*matB*schurComplement*matC*invA;
	const Matrix<T, 2, 1>	blockB = (zeroMat-invA)*matB*schurComplement;
	const Matrix<T, 1, 2>	blockC = matC*invA*(-schurComplement);
	const T					blockD = schurComplement;

	const T result[3*3] =
	{
		blockA(0,0),	blockA(0,1),	blockB(0,0),
		blockA(1,0),	blockA(1,1),	blockB(1,0),
		blockC(0,0),	blockC(0,1),	blockD,
	};

	return Matrix<T, 3, 3>(result);
}

template <typename T>
Matrix<T, 4, 4> SquareMatrixOps<T, 4>::doInverse (const Matrix<T, 4, 4>& mat)
{
	// Blockwise inversion
	using matrix::inverse;

	const T areaA[2*2] =
	{
		mat(0,0),	mat(0,1),
		mat(1,0),	mat(1,1)
	};
	const T areaB[2*2] =
	{
		mat(0,2),	mat(0,3),
		mat(1,2),	mat(1,3)
	};
	const T areaC[2*2] =
	{
		mat(2,0),	mat(2,1),
		mat(3,0),	mat(3,1)
	};
	const T areaD[2*2] =
	{
		mat(2,2),	mat(2,3),
		mat(3,2),	mat(3,3)
	};
	const T nullField[4] = { T(0.0f) };

	const Matrix<T, 2, 2> invA = inverse(Matrix<T, 2, 2>(areaA));
	const Matrix<T, 2, 2> matB =         Matrix<T, 2, 2>(areaB);
	const Matrix<T, 2, 2> matC =         Matrix<T, 2, 2>(areaC);
	const Matrix<T, 2, 2> matD =         Matrix<T, 2, 2>(areaD);

	const Matrix<T, 2, 2> schurComplement = inverse(matD - matC*invA*matB);
	const Matrix<T, 2, 2> zeroMat         = Matrix<T, 2, 2>(nullField);

	const Matrix<T, 2, 2> blockA = invA + invA*matB*schurComplement*matC*invA;
	const Matrix<T, 2, 2> blockB = (zeroMat-invA)*matB*schurComplement;
	const Matrix<T, 2, 2> blockC = (zeroMat-schurComplement)*matC*invA;
	const Matrix<T, 2, 2> blockD = schurComplement;

	const T result[4*4] =
	{
		blockA(0,0),	blockA(0,1),	blockB(0,0),	blockB(0,1),
		blockA(1,0),	blockA(1,1),	blockB(1,0),	blockB(1,1),
		blockC(0,0),	blockC(0,1),	blockD(0,0),	blockD(0,1),
		blockC(1,0),	blockC(1,1),	blockD(1,0),	blockD(1,1),
	};

	return Matrix<T, 4, 4>(result);
}

// Initialize to identity.
template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols>::Matrix (void)
{
	for (int row = 0; row < Rows; row++)
		for (int col = 0; col < Cols; col++)
			(*this)(row, col) = (row == col) ? T(1) : T(0);
}

// Initialize to diagonal matrix.
template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols>::Matrix (const T& src)
{
	for (int row = 0; row < Rows; row++)
		for (int col = 0; col < Cols; col++)
			(*this)(row, col) = (row == col) ? src : T(0);
}

// Initialize from data array.
template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols>::Matrix (const T src[Rows*Cols])
{
	for (int row = 0; row < Rows; row++)
		for (int col = 0; col < Cols; col++)
			(*this)(row, col) = src[row*Cols + col];
}

// Initialize to diagonal matrix.
template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols>::Matrix (const Vector<T, Rows>& src)
{
	DE_STATIC_ASSERT(Rows == Cols);
	for (int row = 0; row < Rows; row++)
		for (int col = 0; col < Cols; col++)
			(*this)(row, col) = (row == col) ? src.m_data[row] : T(0);
}

// Copy constructor.
template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols>::Matrix (const Matrix<T, Rows, Cols>& src)
{
	*this = src;
}

// Destructor.
template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols>::~Matrix (void)
{
}

// Assignment operator.
template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols>& Matrix<T, Rows, Cols>::operator= (const Matrix<T, Rows, Cols>& src)
{
	for (int row = 0; row < Rows; row++)
		for (int col = 0; col < Cols; col++)
			(*this)(row, col) = src(row, col);
	return *this;
}

// Multipy and assign op
template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols>& Matrix<T, Rows, Cols>::operator*= (const Matrix<T, Rows, Cols>& src)
{
	*this = *this * src;
	return *this;
}

template <typename T, int Rows, int Cols>
void Matrix<T, Rows, Cols>::setRow (int rowNdx, const Vector<T, Cols>& vec)
{
	for (int col = 0; col < Cols; col++)
		(*this)(rowNdx, col) = vec.m_data[col];
}

template <typename T, int Rows, int Cols>
void Matrix<T, Rows, Cols>::setColumn (int colNdx, const Vector<T, Rows>& vec)
{
	m_data[colNdx] = vec;
}

template <typename T, int Rows, int Cols>
Vector<T, Cols> Matrix<T, Rows, Cols>::getRow (int rowNdx) const
{
	Vector<T, Cols> res;
	for (int col = 0; col < Cols; col++)
		res[col] = (*this)(rowNdx, col);
	return res;
}

template <typename T, int Rows, int Cols>
Vector<T, Rows>& Matrix<T, Rows, Cols>::getColumn (int colNdx)
{
	return m_data[colNdx];
}

template <typename T, int Rows, int Cols>
const Vector<T, Rows>& Matrix<T, Rows, Cols>::getColumn (int colNdx) const
{
	return m_data[colNdx];
}

template <typename T, int Rows, int Cols>
Array<T, Rows*Cols> Matrix<T, Rows, Cols>::getColumnMajorData (void) const
{
	Array<T, Rows*Cols> a;
	T* dst = a.getPtr();
	for (int col = 0; col < Cols; col++)
		for (int row = 0; row < Rows; row++)
			*dst++ = (*this)(row, col);
	return a;
}

template <typename T, int Rows, int Cols>
Array<T, Rows*Cols> Matrix<T, Rows, Cols>::getRowMajorData (void) const
{
	Array<T, Rows*Cols> a;
	T* dst = a.getPtr();
	for (int row = 0; row < Rows; row++)
		for (int col = 0; col < Cols; col++)
			*dst++ = (*this)(row, col);
	return a;
}

// Multiplication of two matrices.
template <typename T, int Rows0, int Cols0, int Rows1, int Cols1>
Matrix<T, Rows0, Cols1> operator* (const Matrix<T, Rows0, Cols0>& a, const Matrix<T, Rows1, Cols1>& b)
{
	DE_STATIC_ASSERT(Cols0 == Rows1);
	Matrix<T, Rows0, Cols1> res;
	for (int row = 0; row < Rows0; row++)
	{
		for (int col = 0; col < Cols1; col++)
		{
			T v = T(0);
			for (int ndx = 0; ndx < Cols0; ndx++)
				v += a(row,ndx) * b(ndx,col);
			res(row,col) = v;
		}
	}
	return res;
}

// Multiply of matrix with column vector.
template <typename T, int Rows, int Cols>
Vector<T, Rows> operator* (const Matrix<T, Rows, Cols>& mtx, const Vector<T, Cols>& vec)
{
	Vector<T, Rows> res;
	for (int row = 0; row < Rows; row++)
	{
		T v = T(0);
		for (int col = 0; col < Cols; col++)
			v += mtx(row,col) * vec.m_data[col];
		res.m_data[row] = v;
	}
	return res;
}

// Multiply of matrix with row vector.
template <typename T, int Rows, int Cols>
Vector<T, Cols> operator* (const Vector<T, Rows>& vec, const Matrix<T, Rows, Cols>& mtx)
{
	Vector<T, Cols> res;
	for (int col = 0; col < Cols; col++)
	{
		T v = T(0);
		for (int row = 0; row < Rows; row++)
			v += mtx(row,col) * vec.m_data[row];
		res.m_data[col] = v;
	}
	return res;
}

// Common typedefs.
typedef Matrix<float, 2, 2>		Matrix2f;
typedef Matrix<float, 3, 3>		Matrix3f;
typedef Matrix<float, 4, 4>		Matrix4f;
typedef Matrix<double, 2, 2>	Matrix2d;
typedef Matrix<double, 3, 3>	Matrix3d;
typedef Matrix<double, 4, 4>	Matrix4d;

// GLSL-style naming \note CxR.
typedef Matrix2f			Mat2;
typedef Matrix<float, 3, 2>	Mat2x3;
typedef Matrix<float, 4, 2>	Mat2x4;
typedef Matrix<float, 2, 3>	Mat3x2;
typedef Matrix3f			Mat3;
typedef Matrix<float, 4, 3>	Mat3x4;
typedef Matrix<float, 2, 4>	Mat4x2;
typedef Matrix<float, 3, 4>	Mat4x3;
typedef Matrix4f			Mat4;

// Matrix-scalar operators.

template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols> operator+ (const Matrix<T, Rows, Cols>& mtx, T scalar)
{
	Matrix<T, Rows, Cols> res;
	for (int col = 0; col < Cols; col++)
		for (int row = 0; row < Rows; row++)
			res(row, col) = mtx(row, col) + scalar;
	return res;
}

template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols> operator- (const Matrix<T, Rows, Cols>& mtx, T scalar)
{
	Matrix<T, Rows, Cols> res;
	for (int col = 0; col < Cols; col++)
		for (int row = 0; row < Rows; row++)
			res(row, col) = mtx(row, col) - scalar;
	return res;
}

template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols> operator* (const Matrix<T, Rows, Cols>& mtx, T scalar)
{
	Matrix<T, Rows, Cols> res;
	for (int col = 0; col < Cols; col++)
		for (int row = 0; row < Rows; row++)
			res(row, col) = mtx(row, col) * scalar;
	return res;
}

template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols> operator/ (const Matrix<T, Rows, Cols>& mtx, T scalar)
{
	Matrix<T, Rows, Cols> res;
	for (int col = 0; col < Cols; col++)
		for (int row = 0; row < Rows; row++)
			res(row, col) = mtx(row, col) / scalar;
	return res;
}

// Matrix-matrix component-wise operators.

template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols> operator+ (const Matrix<T, Rows, Cols>& a, const Matrix<T, Rows, Cols>& b)
{
	Matrix<T, Rows, Cols> res;
	for (int col = 0; col < Cols; col++)
		for (int row = 0; row < Rows; row++)
			res(row, col) = a(row, col) + b(row, col);
	return res;
}

template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols> operator- (const Matrix<T, Rows, Cols>& a, const Matrix<T, Rows, Cols>& b)
{
	Matrix<T, Rows, Cols> res;
	for (int col = 0; col < Cols; col++)
		for (int row = 0; row < Rows; row++)
			res(row, col) = a(row, col) - b(row, col);
	return res;
}

template <typename T, int Rows, int Cols>
Matrix<T, Rows, Cols> operator/ (const Matrix<T, Rows, Cols>& a, const Matrix<T, Rows, Cols>& b)
{
	Matrix<T, Rows, Cols> res;
	for (int col = 0; col < Cols; col++)
		for (int row = 0; row < Rows; row++)
			res(row, col) = a(row, col) / b(row, col);
	return res;
}

} // tcu

#endif // _TCUMATRIX_HPP