#pragma once

#include "Stuff.hpp"
#include "LinearMatrix.hpp"
#include "Sphere.hpp"

namespace Stuff {class OBB;}

#if !defined(Spew)
	void Spew(
		const char* group,
		const Stuff::OBB &box
	);
#endif

namespace Stuff {

	class Line3D;

	class OBB
	{
	public:
		void TestInstance() const;

		static OBB Identity;

		LinearMatrix4D localToParent;
		Vector3D axisExtents;
		Scalar sphereRadius;

		#if !defined(Spew)
			friend void ::Spew(
				const char* group,
				const OBB &box
			);
		#endif

		Scalar GetVolume() const
			{Check_Object(this); return 8.0f*axisExtents.x*axisExtents.y*axisExtents.z;}

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Construction
	//
	public:
		OBB()
			{}
		OBB(const OBB& obb):
			localToParent(obb.localToParent),
			axisExtents(obb.axisExtents),
			sphereRadius(obb.sphereRadius)
				{}
		OBB(
			const LinearMatrix4D &origin,
			const Vector3D &extents
		):
			localToParent(origin),
			axisExtents(extents),
			sphereRadius(extents.GetLength())
				{}
		OBB(
			const LinearMatrix4D &origin,
			const Vector3D &extents,
			Scalar radius
		):
			localToParent(origin),
			axisExtents(extents),
			sphereRadius(radius)
				{}

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Assignment
	//
	public:
		OBB& operator=(const OBB& obb)
			{
				Check_Pointer(this); Check_Object(&obb);
				localToParent = obb.localToParent;
				axisExtents = obb.axisExtents;
				sphereRadius = obb.sphereRadius;
				return *this;
			}
		OBB& operator=(const ExtentBox& box);

		OBB& BuildSphere(const Sphere& sphere)
			{
				Check_Pointer(this); Check_Object(&sphere);
				sphereRadius = sphere.radius;
				localToParent.BuildTranslation(sphere.center);
				return *this;
			}
		int ComputeBounds(ReadOnlyArrayOf<Point3D> &points);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Transforms
	//
	public:
		OBB& Multiply(
				const OBB& obb,
				const LinearMatrix4D &matrix
			);
		inline OBB& MultiplySphereOnly(
				const OBB& obb,
				const LinearMatrix4D &matrix
			)
			{
				Check_Pointer(this);
				Check_Object(&obb);
				Check_Object(&matrix);

#if USE_ASSEMBLER_CODE

				Scalar *f = localToParent.entries;

				_asm {
					mov         edx, matrix
					push        esi
					mov         esi, obb.localToParent
					
					mov         eax, f

					fld         dword ptr [edx+4]		//	m[1][0]
					fmul        dword ptr [esi+01ch]	//	obb.localToParent(3,1)
					
					fld         dword ptr [edx+8]		//	m[2][0]
					fmul        dword ptr [esi+02Ch]	//	obb.localToParent(3,2)

					fxch		st(1)
					fadd        dword ptr [edx+0Ch]		//	m[3][0]
					
					fld         dword ptr [edx]			//	m[0][0]
					fmul        dword ptr [esi+0Ch]		//	obb.localToParent(3,0)

					fxch		st(2)
					faddp       st(1),st

					fld         dword ptr [edx+14h]		//	m[1][1]
					fmul        dword ptr [esi+01ch]	//	obb.localToParent(3,1)

					fxch		st(2)
					faddp       st(1),st

					fld         dword ptr [edx+18h]		//	m[2][1]
					fmul        dword ptr [esi+02ch]	//	obb.localToParent(3,2)
					
					fxch		st(1)
					fstp        dword ptr [eax+0ch]		//	localToParent(3,0)

					fadd        dword ptr [edx+1Ch]		//	m[3][1]

					fld         dword ptr [edx+10h]		//	m[0][1]
					fmul        dword ptr [esi+0ch]		//	obb.localToParent(3,0)

					fxch		st(2)
					faddp       st(1),st

					fld         dword ptr [edx+24h]		//	m[1][2]
					fmul        dword ptr [esi+01ch]	//	obb.localToParent(3,1)

					fxch		st(2)
					faddp       st(1),st

					fld         dword ptr [edx+28h]		//	m[2][2]
					fmul        dword ptr [esi+02ch]	//	obb.localToParent(3,2)
					
					fxch		st(1)
					fstp        dword ptr [eax+01ch]	//	localToParent(3,1)

					fadd        dword ptr [edx+2Ch]		//	m[3][2]
					
					fld         dword ptr [edx+20h]		//	m[0][2]
					fmul        dword ptr [esi+0ch]		//	obb.localToParent(3,0)

					fxch		st(2)
					faddp       st(1),st

					pop         esi

					faddp       st(1),st

					fstp        dword ptr [eax+02ch]	//	localToParent(3,2)
				}
#else
				localToParent(3,0) =
					obb.localToParent(3,0)*matrix(0,0)
					 + obb.localToParent(3,1)*matrix(1,0)
					 + obb.localToParent(3,2)*matrix(2,0)
					 + matrix(3,0);
				localToParent(3,1) =
					obb.localToParent(3,0)*matrix(0,1)
					 + obb.localToParent(3,1)*matrix(1,1)
					 + obb.localToParent(3,2)*matrix(2,1)
					 + matrix(3,1);
				localToParent(3,2) =
					obb.localToParent(3,0)*matrix(0,2)
					 + obb.localToParent(3,1)*matrix(1,2)
					 + obb.localToParent(3,2)*matrix(2,2)
					 + matrix(3,2);
#endif

				sphereRadius = obb.sphereRadius;
				return *this;
			}

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Intersection functions
	//
	public:
		enum SeparatingAxis {
			NoSeparation = 0,
			A0,
			A1,
			A2,
			B0,
			B1,
			B2,
			A0xB0,
			A0xB1,
			A0xB2,
			A1xB0,
			A1xB1,
			A1xB2,
			A2xB0,
			A2xB1,
			A2xB2
		};

		SeparatingAxis FindSeparatingAxis(const OBB& box) const;
		//
		// Intersection functions
		//
		bool Contains(const Point3D &point) const;
		bool Intersects(const Plane &plane) const;

		void Union(
			const OBB &first,
			const OBB &second
		);

		SeparatingAxis CalculateSeparationVector(
			Vector3D *vector,
			const OBB &obb
		);
		void CalculateSecondarySeparationVector(
			Vector3D *vector,
			const OBB &obb,
			const UnitVector3D &primary
		);
	};

	inline Sphere& Sphere::operator =(const OBB &obb)
	{
		Check_Pointer(this); Check_Object(&obb);
		center = obb.localToParent;
		radius = obb.sphereRadius;
		return *this;
	}

}

namespace MemoryStreamIO {

	inline Stuff::MemoryStream& Read(
		Stuff::MemoryStream* stream,
		Stuff::OBB *output
	)
		{return stream->ReadBytes(output, sizeof(*output));}
	inline Stuff::MemoryStream& Write(
		Stuff::MemoryStream* stream,
		const Stuff::OBB *input
	)
		{return stream->WriteBytes(input, sizeof(*input));}

}
