#include "ge_math.h"	// place before #ifdef

#ifndef __GEMATRIX_H
	#define __GEMATRIX_H

struct Matrix64;

struct Matrix32
{
	Vector32 off, v1, v2, v3;

	Matrix32()
	{
		off = Vector32(0.0f, 0.0f, 0.0f);
		v1	= Vector32(1.0f, 0.0f, 0.0f);
		v2	= Vector32(0.0f, 1.0f, 0.0f);
		v3	= Vector32(0.0f, 0.0f, 1.0f);
	}
	explicit Matrix32(const Vector32& off_in, const Vector32& v1_in, const Vector32& v2_in, const Vector32& v3_in)
	{
		off = off_in;
		v1	= v1_in;
		v2	= v2_in;
		v3	= v3_in;
	}
	explicit Matrix32(_DONTCONSTRUCT v) : off(v), v1(v), v2(v), v3(v) { }
	explicit Matrix32(const Matrix64& v);

#ifdef __LEGACY_API
	friend const Matrix32 operator ! (const Matrix32& m) { return ~m; }
	Vector32 Mul(const Vector32& v) const { return Vector32(off.x + v1.x * v.x + v2.x * v.y + v3.x * v.z, off.y + v1.y * v.x + v2.y * v.y + v3.y * v.z, off.z + v1.z * v.x + v2.z * v.y + v3.z * v.z); }
	Vector64 Mul(const Vector64& v) const { return Vector64(off.x + v1.x * v.x + v2.x * v.y + v3.x * v.z, off.y + v1.y * v.x + v2.y * v.y + v3.y * v.z, off.z + v1.z * v.x + v2.z * v.y + v3.z * v.z); }
	Vector32 MulV(const Vector32& v) const { return Vector32(v1.x * v.x + v2.x * v.y + v3.x * v.z, v1.y * v.x + v2.y * v.y + v3.y * v.z, v1.z * v.x + v2.z * v.y + v3.z * v.z); }
	Vector64 MulV(const Vector64& v) const { Float32 x = (Float32) v.x, y = (Float32) v.y, z = (Float32) v.z; return Vector64(Float64(v1.x * x + v2.x * y + v3.x * z), Float64(v1.y * x + v2.y * y + v3.y * z), Float64(v1.z * x + v2.z * y + v3.z * z)); }
	friend const Vector32 operator ^ (const Vector32& v, const Matrix32& m) { return Vector32(m.v1.x * v.x + m.v2.x * v.y + m.v3.x * v.z, m.v1.y * v.x + m.v2.y * v.y + m.v3.y * v.z, m.v1.z * v.x + m.v2.z * v.y + m.v3.z * v.z); }
	friend const Vector32 operator * (const Vector32& v, const Matrix32& m) { return Vector32(m.off.x + m.v1.x * v.x + m.v2.x * v.y + m.v3.x * v.z, m.off.y + m.v1.y * v.x + m.v2.y * v.y + m.v3.y * v.z, m.off.z + m.v1.z * v.x + m.v2.z * v.y + m.v3.z * v.z); }
	inline const Matrix64 ToLM() const;
	inline const Matrix32& ToSM() const { return *this; }
	inline const Matrix64 ToRM() const;
#endif

	/// Multiplies two matrices.
	/// The rule is m1 AFTER m2
	/// If you transform a point with the result matrix this is identical to first transforming with m2 and then with m1
	Matrix32 operator *(const Matrix32& m2) const
	{
		return Matrix32(off + v1 * m2.off.x + v2 * m2.off.y + v3 * m2.off.z,
						 v1 * m2.v1.x + v2 * m2.v1.y + v3 * m2.v1.z,
						 v1 * m2.v2.x + v2 * m2.v2.y + v3 * m2.v2.z,
						 v1 * m2.v3.x + v2 * m2.v3.y + v3 * m2.v3.z);
	}

	/// Transforms a point by a matrix
	Vector32 operator *(const Vector32& v) const
	{
		return Vector32(off.x + v1.x * v.x + v2.x * v.y + v3.x * v.z,
						 off.y + v1.y * v.x + v2.y * v.y + v3.y * v.z,
						 off.z + v1.z * v.x + v2.z * v.y + v3.z * v.z);
	}

	/// Scales all matrix components by a scalar value
	friend Matrix32 operator *(Float32 s, const Matrix32& m)
	{
		return Matrix32(s * m.off, s * m.v1, s * m.v2, s * m.v3);
	}

	/// Scales all matrix components by a scalar value
	Matrix32 operator *(Float32 s) const
	{
		return Matrix32(off * s, v1 * s, v2 * s, v3 * s);
	}

	Vector32 TransformVector(const Vector32& v) const
	{
		return Vector32(v1.x * v.x + v2.x * v.y + v3.x * v.z, v1.y * v.x + v2.y * v.y + v3.y * v.z, v1.z * v.x + v2.z * v.y + v3.z * v.z);
	}

	friend const Matrix32 operator / (const Matrix32& m, const Float32 s)
	{
		return Matrix32(m.off / s, m.v1 / s, m.v2 / s, m.v3 / s);
	}

	friend const Matrix32 operator + (const Matrix32& m1, const Matrix32& m2)
	{
		return Matrix32(m1.off + m2.off, m1.v1 + m2.v1, m1.v2 + m2.v2, m1.v3 + m2.v3);
	}

	friend const Matrix32 operator - (const Matrix32& m1, const Matrix32& m2)
	{
		return Matrix32(m1.off - m2.off, m1.v1 - m2.v1, m1.v2 - m2.v2, m1.v3 - m2.v3);
	}

	friend Bool operator == (const Matrix32& m1, const Matrix32& m2)
	{
		return (m1.off == m2.off) && (m1.v1 == m2.v1) && (m1.v2 == m2.v2) && (m1.v3 == m2.v3);
	}

	friend Bool operator != (const Matrix32& m1, const Matrix32& m2)
	{
		return !(m1 == m2);
	}

	friend const Matrix32 operator ~ (const Matrix32& m)
	{
		Matrix32 mi(DC);

		Float32 det = (m.v1.x * (m.v2.y * m.v3.z - m.v3.y * m.v2.z) +
									 m.v2.x * (m.v3.y * m.v1.z - m.v1.y * m.v3.z) +
									 m.v3.x * (m.v1.y * m.v2.z - m.v2.y * m.v1.z));

		if (det == 0.0f)
			return Matrix32();

		det = 1.0f / det;
		mi.off.x = (m.v2.x * (m.off.y * m.v3.z - m.v3.y * m.off.z) +
								m.v3.x * (m.off.z * m.v2.y - m.off.y * m.v2.z) +
								m.off.x * (m.v3.y * m.v2.z - m.v2.y * m.v3.z)) * det;
		mi.off.y = (m.v3.x * (m.off.y * m.v1.z - m.v1.y * m.off.z) +
								m.off.x * (m.v1.y * m.v3.z - m.v3.y * m.v1.z) +
								m.v1.x * (m.v3.y * m.off.z - m.off.y * m.v3.z)) * det;
		mi.off.z = (m.off.x * (m.v2.y * m.v1.z - m.v1.y * m.v2.z) +
								m.v1.x * (m.v2.z * m.off.y - m.v2.y * m.off.z) +
								m.v2.x * (m.off.z * m.v1.y - m.off.y * m.v1.z)) * det;

		mi.v1.x = (m.v2.y * m.v3.z - m.v3.y * m.v2.z) * det;
		mi.v1.y = (m.v3.y * m.v1.z - m.v1.y * m.v3.z) * det;
		mi.v1.z = (m.v1.y * m.v2.z - m.v2.y * m.v1.z) * det;

		mi.v2.x = (m.v2.z * m.v3.x - m.v3.z * m.v2.x) * det;
		mi.v2.y = (m.v3.z * m.v1.x - m.v1.z * m.v3.x) * det;
		mi.v2.z = (m.v1.z * m.v2.x - m.v2.z * m.v1.x) * det;

		mi.v3.x = (m.v2.x * m.v3.y - m.v3.x * m.v2.y) * det;
		mi.v3.y = (m.v3.x * m.v1.y - m.v1.x * m.v3.y) * det;
		mi.v3.z = (m.v1.x * m.v2.y - m.v2.x * m.v1.y) * det;

		return mi;
	}

	void Normalize(void)
	{
		v1.Normalize();
		v2.Normalize();
		v3.Normalize();
	}

	Matrix32 GetTensorMatrix(void) const
	{
		return Matrix32(Vector32(0.0f), Vector32(v3.z * v2.y - v3.y * v2.z, v3.x * v2.z - v3.z * v2.x, v3.y * v2.x - v3.x * v2.y), Vector32(v3.y * v1.z - v1.y * v3.z, v3.z * v1.x - v3.x * v1.z, v1.y * v3.x - v3.y * v1.x), Vector32(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x));
	}

	void Scale(const Vector32& v)
	{
		v1 *= v.x;
		v2 *= v.y;
		v3 *= v.z;
	}

	void Scale(Float32 r)
	{
		v1 *= r;
		v2 *= r;
		v3 *= r;
	}

	Vector32& operator [](Int32 i) const { return ((Vector32*)this)[i & 3]; }

	Vector32 Scale(void) const { return Vector32(Len(v1), Len(v2), Len(v3)); }
};

	#ifdef __LEGACY_API
inline const Vector32& Vector32::operator *= (const Matrix32& m) { Float32 xx = m.off.x + m.v1.x * x + m.v2.x * y + m.v3.x * z; Float32 yy = m.off.y + m.v1.y * x + m.v2.y * y + m.v3.y * z; Float32 zz = m.off.z + m.v1.z * x + m.v2.z * y + m.v3.z * z; x = xx; y = yy; z = zz; return *this; }
inline const Vector32& Vector32::operator ^= (const Matrix32& m) { Float32 xx = m.v1.x * x + m.v2.x * y + m.v3.x * z; Float32 yy = m.v1.y * x + m.v2.y * y + m.v3.y * z; Float32 zz = m.v1.z * x + m.v2.z * y + m.v3.z * z; x = xx; y = yy; z = zz; return *this; }
	#endif

#endif
