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

#ifndef __GEMATRIX_H
#define __GEMATRIX_H

struct LMatrix;

struct SMatrix
{
	SVector off, v1, v2, v3;

	SMatrix(void)
	{
		off = SVector(0.0f, 0.0f, 0.0f);
		v1  = SVector(1.0f, 0.0f, 0.0f);
		v2  = SVector(0.0f, 1.0f, 0.0f);
		v3  = SVector(0.0f, 0.0f, 1.0f);
	}
	SMatrix(const SVector &off_in, const SVector &v1_in, const SVector &v2_in, const SVector &v3_in)
	{
		off = off_in;
		v1  = v1_in;
		v2  = v2_in;
		v3  = v3_in;
	}
	SMatrix(_DONTCONSTRUCT v): off(v), v1(v), v2(v), v3(v) {}
	
	friend const SMatrix operator * (const SMatrix &m1, const SMatrix &m2)
	{
		SMatrix erg(DC);

		erg.off.x = m1.off.x + m1.v1.x * m2.off.x + m1.v2.x * m2.off.y + m1.v3.x * m2.off.z;
		erg.off.y = m1.off.y + m1.v1.y * m2.off.x + m1.v2.y * m2.off.y + m1.v3.y * m2.off.z;
		erg.off.z = m1.off.z + m1.v1.z * m2.off.x + m1.v2.z * m2.off.y + m1.v3.z * m2.off.z;

		erg.v1.x = m1.v1.x * m2.v1.x + m1.v2.x * m2.v1.y + m1.v3.x * m2.v1.z;
		erg.v1.y = m1.v1.y * m2.v1.x + m1.v2.y * m2.v1.y + m1.v3.y * m2.v1.z;
		erg.v1.z = m1.v1.z * m2.v1.x + m1.v2.z * m2.v1.y + m1.v3.z * m2.v1.z;

		erg.v2.x = m1.v1.x * m2.v2.x + m1.v2.x * m2.v2.y + m1.v3.x * m2.v2.z;
		erg.v2.y = m1.v1.y * m2.v2.x + m1.v2.y * m2.v2.y + m1.v3.y * m2.v2.z;
		erg.v2.z = m1.v1.z * m2.v2.x + m1.v2.z * m2.v2.y + m1.v3.z * m2.v2.z;

		erg.v3.x = m1.v1.x * m2.v3.x + m1.v2.x * m2.v3.y + m1.v3.x * m2.v3.z;
		erg.v3.y = m1.v1.y * m2.v3.x + m1.v2.y * m2.v3.y + m1.v3.y * m2.v3.z;
		erg.v3.z = m1.v1.z * m2.v3.x + m1.v2.z * m2.v3.y + m1.v3.z * m2.v3.z;
		return erg;
	}

	friend const SVector operator * (const SMatrix &m, const SVector &v)
	{
		return SVector(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);
	}

	friend const SVector operator ^ (const SVector &v, const SMatrix &m)
	{
		return SVector(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 SVector operator * (const SVector &v, const SMatrix &m)
	{
		return SVector(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);
	}

	friend const SMatrix operator * (const SReal s, const SMatrix &m)
	{
		return SMatrix(s*m.off, s*m.v1, s*m.v2, s*m.v3);
	}

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

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

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

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

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

	friend const SMatrix operator ! (const SMatrix &m)
	{
		SMatrix mi(DC);

		SReal 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 SMatrix();

		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();
	}

	SVector Mul(const SVector &v) const
	{
		return SVector(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);
	}

	LVector Mul(const LVector &v) const
	{
		return LVector(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);
	}

	SVector MulV(const SVector &v) const
	{
		return SVector(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);
	}

	LVector MulV(const LVector &v) const
	{
		SReal x=SCO v.x,y=SCO v.y,z=SCO v.z;
		return LVector(LReal(v1.x*x+v2.x*y+v3.x*z),LReal(v1.y*x+v2.y*y+v3.y*z),LReal(v1.z*x+v2.z*y+v3.z*z));
	}

	SMatrix GetTensorMatrix(void) const
	{
		return SMatrix(0.0f,SVector(v3.z*v2.y-v3.y*v2.z,v3.x*v2.z-v3.z*v2.x,v3.y*v2.x-v3.x*v2.y),SVector(v3.y*v1.z-v1.y*v3.z,v3.z*v1.x-v3.x*v1.z,v1.y*v3.x-v3.y*v1.x),SVector(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 SVector &v)
	{
		v1 *= v.x;
		v2 *= v.y;
		v3 *= v.z;
	}

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

	inline const LMatrix ToLM() const;
	inline const SMatrix &ToSM() const { return *this; }

	#ifdef __SINGLEPRECISION
		inline const SMatrix &ToRM() const { return *this; }
	#else
		inline const LMatrix ToRM() const;
	#endif

	SVector &operator [](LONG i) const { return ((SVector*)this)[i&3]; }

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

inline const SVector & SVector::operator *= (const SMatrix &m)
{
	SReal xx = m.off.x + m.v1.x * x + m.v2.x * y + m.v3.x * z;
	SReal yy = m.off.y + m.v1.y * x + m.v2.y * y + m.v3.y * z;
	SReal 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 SVector & SVector::operator ^= (const SMatrix &m)
{
	SReal xx = m.v1.x * x + m.v2.x * y + m.v3.x * z;
	SReal yy = m.v1.y * x + m.v2.y * y + m.v3.y * z;
	SReal zz = m.v1.z * x + m.v2.z * y + m.v3.z * z;
	x = xx;
	y = yy;
	z = zz;
	return *this;
}

#endif
