#ifndef __gxframework_matrix3_h__
	#define __gxframework_matrix3_h__

#include "shared.h"
#include "gxmath.h"
#include "vector3.h"

namespace Gx
{
	class Matrix3
	{
	public:
		Matrix3() { }

		Matrix3(const Vector3& ncol0, const Vector3& ncol1, const Vector3& ncol2)
			: column0(ncol0), column1(ncol1), column2(ncol2) { }

		explicit Matrix3(float values[]) :
		column0(values[0],values[1],values[2]),
			column1(values[3],values[4],values[5]),
			column2(values[6],values[7],values[8]) { }

		Matrix3(const Vector3& diagonal) :
		column0(diagonal.x,0.0f,0.0f),
			column1(0.0f,diagonal.y,0.0f),
			column2(0.0f,0.0f,diagonal.z) { }

		Matrix3(const Matrix3& m)
			: column0(m.column0), column1(m.column1), column2(m.column2) { }

		// Check it - return const reference?
		Matrix3& operator=(const Matrix3& m) {
			column0 = m.column0;
			column1 = m.column1;
			column2 = m.column2;
			return *this;
		}

		static const Matrix3 Zero;
		static const Matrix3 Identity;

		static Matrix3 createDiagonal(const Vector3& d) {
			return Matrix3(
				Vector3(d.x,0.0f,0.0f),
				Vector3(0.0f,d.y,0.0f),
				Vector3(0.0f,0.0f,d.z));
		}

		Matrix3 getTranspose() const {
			return Matrix3(
				Vector3(column0.x, column1.x, column2.x),
				Vector3(column0.y, column1.y, column2.y),
				Vector3(column0.z, column1.z, column2.z));
		}

		Matrix3 getInverse() const {
			const float det = getDeterminant();
			Matrix3 inverse;

			if ( det != 0 )
			{
				const float invDet = 1.0f / det;

				inverse.column0[0] = invDet * (column1[1]*column2[2] - column2[1]*column1[2]);							
				inverse.column0[1] = invDet *-(column0[1]*column2[2] - column2[1]*column0[2]);
				inverse.column0[2] = invDet * (column0[1]*column1[2] - column0[2]*column1[1]);

				inverse.column1[0] = invDet *-(column1[0]*column2[2] - column1[2]*column2[0]);
				inverse.column1[1] = invDet * (column0[0]*column2[2] - column0[2]*column2[0]);
				inverse.column1[2] = invDet *-(column0[0]*column1[2] - column0[2]*column1[0]);

				inverse.column2[0] = invDet * (column1[0]*column2[1] - column1[1]*column2[0]);
				inverse.column2[1] = invDet *-(column0[0]*column2[1] - column0[1]*column2[0]);
				inverse.column2[2] = invDet * (column0[0]*column1[1] - column1[0]*column0[1]);

				return inverse;
			}
			else
			{
				return Matrix3::Identity;
			}
		}

		float getDeterminant() const {
			return column0.dot( column1.cross(column2) );

			/*return column0.x * column1.y * column2.z +
			column0.y * column1.z * column2.x +
			column0.z * column1.x * column2.y -
			column0.x * column1.z * column2.y -
			column0.y * column1.x * column2.z -
			column0.z * column1.y * column2.x;*/
		}

		void transpose() {
			// Rewrite it!
			*this = getTranspose();
		}

		void inverse() {
			// Rewrite it!
			*this = getInverse();
		}

		Matrix3 operator-() const {
			return Matrix3(-column0, -column1, -column2);
		}

		Matrix3& operator+=(const Matrix3& m) {
			column0 += m.column0;
			column1 += m.column1;
			column2 += m.column2;
			return *this;
		}

		Matrix3& operator-=(const Matrix3& m) {
			column0 -= m.column0;
			column1 -= m.column1;
			column2 -= m.column2;
			return *this;
		}

		Matrix3& operator*=(const float scalar) {
			column0 *= scalar;
			column1 *= scalar;
			column2 *= scalar;
			return *this;
		}

		// Element access, mathematical way!
		float operator()(unsigned int row, unsigned int col) const {
			return (*this)[col][row];
		}

		// Element access, mathematical way!
		float& operator()(unsigned int row, unsigned int col) {
			return (*this)[col][row];
		}

		// Transform vector by matrix, equal to v' = M*v
		Vector3 transform(const Vector3& v) const {
			return column0 * v.x + column1 * v.y + column2 * v.z;
		}

		// Transform vector by matrix transpose, v' = M^t*v
		Vector3 transformTranspose(const Vector3& v) const {
			return Vector3(column0.dot(v), column1.dot(v), column2.dot(v));
		}

		// Rotate vector by matrix, equal to v' = M*v
		Vector3 rotate(const Vector3& v) const {
			return transform(v);
		}

		const float* begin() const {
			return &column0.x;
		}

		Vector3& operator[](unsigned int col) {
			return (&column0)[col];
		}

		const Vector3& operator[](unsigned int col) const {
			return (&column0)[col];
		}

		Vector3 column0, column1, column2;
	};

	__declspec(selectany) const Matrix3 Matrix3::Zero = Matrix3(Vector3(0.0f), Vector3(0.0f), Vector3(0.0f));

	__declspec(selectany) const Matrix3 Matrix3::Identity = Matrix3(Vector3(1.0f, 0.0f, 0.0f),
		Vector3(0.0f, 1.0f, 0.0f), Vector3(0.0f, 0.0f, 1.0f));

	static Matrix3 operator+(const Matrix3& m1, const Matrix3& m2)
	{
		return Matrix3(
			m1.column0 + m2.column0,
			m1.column1 + m2.column1,
			m1.column2 + m2.column2);
	}

	static Matrix3 operator-(const Matrix3& m1, const Matrix3& m2)
	{
		return Matrix3(
			m1.column0 - m2.column0,
			m1.column1 - m2.column1,
			m1.column2 - m2.column2);
	}

	static Matrix3 operator*(const Matrix3& m, const float scalar)
	{
		return Matrix3(m.column0 * scalar, m.column1 * scalar, m.column2 * scalar);
	}

	static Matrix3 operator*(const float scalar, const Matrix3& m)
	{
		return m * scalar;
	}

	static Matrix3 operator*(const Matrix3& m, const Vector3& v)
	{
		return m.transform(v);
	}

	static Matrix3 operator*(const Matrix3& m1, const Matrix3& m2)
	{
		// Rows from this <dot> columns from other
		// column0 = transform(other.column0) etc
		return Matrix3(
			m1.transform(m2.column0),
			m1.transform(m2.column1),
			m1.transform(m2.column2));
	}

	static std::ostream& operator<<(std::ostream& stream, const Matrix3& m)
	{
		stream << std::endl <<
			"{" << m.column0.x << " " << m.column1.x << " " << m.column2.x << "}" << std::endl <<
			"{" << m.column0.y << " " << m.column1.y << " " << m.column2.y << "}" << std::endl <<
			"{" << m.column0.z << " " << m.column1.z << " " << m.column2.z << "}";

		return stream;
	}
}

#endif /* ~__gxframework_matrix3_h__ */