#pragma once

#include "ElementRenderer.hpp"
#include <MLR\MLR.hpp>
#include <MLR\GOSVertex.hpp>

namespace MidLevelRenderer {
	class MLRLight;
	class MLRTexture;
	class MLRShape;
}

namespace ElementRenderer {

	class CameraElement;
	class StateChange;
	class Element__ClassData;

	typedef MidLevelRenderer::MLRLight* LightList[MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive];

	class CollisionQuery
		#if defined(_ARMOR)
			: public Stuff::Signature
		#endif
	{
	public:
		CollisionQuery(Stuff::Line3D *l, Stuff::Normal3D *n):
			m_line(l),
			m_normal(n),
			m_data(NULL)
				{Check_Pointer(this);}

		Stuff::Line3D *m_line;
		Stuff::Normal3D *m_normal;
		void *m_data;
		BYTE m_material;

		void TestInstance() const
			{}
	};

	class SphereTest
		#if defined(_ARMOR)
			: public Stuff::Signature
		#endif
	{
	public:
		enum TestCase {
			Disjoint=0,
			Intersects=1,
			Contains=2,
			Contained=4
		};

		Stuff::Sphere m_sphere;
		int m_type;

		void TestInstance() const
			{}
	};

	//#########################################################################
	//###########################  Element  ###################################
	//#########################################################################

	class _declspec(novtable) Element:
		public Stuff::Plug
	{
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Initialization
	//
	public:
		static void InitializeClass();
		static void TerminateClass();
		typedef Element__ClassData ClassData;
		static ClassData *DefaultData;

		typedef Stuff::Plug BaseClass;

		static Stuff::RGBAColor FadeColor;

		typedef MidLevelRenderer::MLRShape* (*ShapeHolder)(
			Stuff::MemoryStream *stream,
			int version,
			int length
		);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Constructors/Destructors
	//
	protected:
		Element(ClassData *class_data);
		explicit Element(
			ClassData *class_data,
			Element *element
		);
		Element(
			ClassData *class_data,
			Stuff::MemoryStream *stream,
			int version
		);

	public:
		~Element();

		typedef Element* (*Factory)(
			Stuff::MemoryStream *stream,
			int version,
			ShapeHolder shapes
		);

		virtual void Save(Stuff::MemoryStream *stream);

		static Element* Create(
			Stuff::MemoryStream *stream,
			int version,
			ShapeHolder shapes=NULL
		);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Testing
	//
	public:
		void TestInstance();

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Hierarchy
	//
	public:
		Element* GetParentElement()
			{Check_Object(this); return m_parent;}
		void DetachFromParent()
			{
				Check_Object(this); Check_Object(m_parent);
				m_parent->DetachChild(this); Verify(!m_parent);
			}
		void AttachToParent(Element *parent)
			{
				Check_Object(this); Check_Object(parent);
				Verify(!m_parent); parent->AttachChild(this);
				Check_Object(m_parent);
			}

		virtual void AttachChild(Element *child)=0;
		virtual void DetachChild(Element *child)=0;

		void SetClientData(void *data)
			{Check_Object(this); m_data = data;}
		void* GetClientData()
			{Check_Object(this); return m_data;}

	protected:
		static void SetParentElement(
			Element *child,
			Element *parent
		)
			{Check_Object(child); child->m_parent = parent;}

		Element *m_parent;
		void *m_data;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// States
	//
	public:
		enum {
			LocalToParentIsIdentityBit = 0,
			BoundsLockedBit,
			SyncStateBit,
			MatrixDirtyBit = SyncStateBit,
			BoundsWrongBit,
			SyncOnCullBit,
			CullStateBit,
			OBBBit = CullStateBit,
			CullBit,
			CullBits = 2,

			AlignXBit = CullBit+CullBits,
			SyncStateBits = AlignXBit - SyncStateBit,
			AlignYBit = AlignXBit+1,

			DrawStateBit,
			CullStateBits = DrawStateBit - CullStateBit,
			PropertyChangeBit = DrawStateBit,
			DrawStateBits = 1,

			CallbackBit = DrawStateBit+1,
			CallbackBits = 3,

			NoShadowBit = CallbackBit+CallbackBits
		};

		enum {
			LocalToParentIsIdentityFlag = 1<<LocalToParentIsIdentityBit,
			BoundsLockedFlag = 1<<BoundsLockedBit,

			SyncStateMask = 0xFFFFFFFF >> (Stuff::INT_BITS - SyncStateBits),
			SyncStateCount = 1<<SyncStateBits,
			MatrixDirtyFlag = 1<<MatrixDirtyBit,
			BoundsWrongFlag = 1<<BoundsWrongBit,
			SyncOnCullFlag = 1<<SyncOnCullBit,

			OBBFlag = 1<<OBBBit,
			CullMask = (0xFFFFFFFF >> (Stuff::INT_BITS - CullBits)) << CullBit,
			RootMode = 0,
			VolumeCullMode = 1<<CullBit,
			NeverCullMode = 2<<CullBit,
			AlwaysCullMode = 3<<CullBit,

			CullStateMask = 0xFFFFFFFF >> (Stuff::INT_BITS - CullStateBits),
			CullStateCount = 1<<CullStateBits,
			AlignXFlag = 1<<AlignXBit,
			AlignYFlag = 1<<AlignYBit,

			DrawStateMask = 0xFFFFFFFF >> (Stuff::INT_BITS - DrawStateBits),
			DrawStateCount = 1<<DrawStateBits,
			PropertyChangeFlag = 1<<PropertyChangeBit,

			CallbackMask = (0xFFFFFFFF >> (Stuff::INT_BITS - CallbackBits)) << CallbackBit,

			NoShadowFlag = 1<<NoShadowBit
		};

		bool IsLocalToParentIdentity()
			{Check_Object(this); return (m_state & LocalToParentIsIdentityFlag) != 0;}

		void LockBounds()
			{Check_Object(this); m_state |= BoundsLockedFlag;}
		void UnlockBounds()
			{Check_Object(this); m_state &= ~BoundsLockedFlag;}
		bool AreBoundsLocked()
			{Check_Object(this); return (m_state & BoundsLockedFlag) != 0;}

		void NeedMatrixSync()
			{Check_Object(this); m_state |= MatrixDirtyFlag; SetSyncState();}
		bool IsMatrixDirty()
			{Check_Object(this); return (m_state & MatrixDirtyFlag) != 0;}

		void NeedNewBounds()
			{
				Check_Object(this);
				m_state &= ~CullMask;
				m_state |= BoundsWrongFlag|VolumeCullMode;
				Verify(!IsBoundedByOBB());
				SetSyncState(); SetCullState();
			}
		bool AreBoundsWrong()
			{Check_Object(this); return (m_state & BoundsWrongFlag) != 0;}

		void ImmediateSync()
			{Check_Object(this); m_state &= ~SyncOnCullFlag;}
		void DeferredSync()
			{Check_Object(this); m_state |= SyncOnCullFlag;}
		bool IsSyncDeferred()
			{Check_Object(this); return (m_state & SyncOnCullFlag) != 0;}

		void SetOBBMode()
			{
				Check_Object(this); m_state |= OBBFlag;
				SetSyncState(); SetCullState();
			}
		void SetSphereMode()
			{
				Check_Object(this); m_state &= ~OBBFlag;
				SetSyncState(); SetCullState();
			}
		bool IsBoundedByOBB()
			{Check_Object(this); return (m_state&OBBFlag) != 0;}
		bool IsBoundedBySphere()
			{Check_Object(this); return !(m_state&OBBFlag);}

		void SetRootMode()
			{
				Check_Object(this);
				m_parent = NULL;
				m_state &= ~CullMask;
				m_state |= RootMode;
				SetSyncState(); SetCullState();
			}
		void SetAlwaysCullMode()
			{
				Check_Object(this);
				Check_Object(m_parent);
				m_state &= ~CullMask;
				m_state |= AlwaysCullMode;
				SetSyncState(); SetCullState();
			}
		void SetVolumeCullMode()
			{
				Check_Object(this);
				Check_Object(m_parent);
				m_state &= ~CullMask;
				m_state |= VolumeCullMode|MatrixDirtyFlag;
				SetSyncState(); SetCullState();
			}
		void SetNeverCullMode()
			{
				Check_Object(this);
				Check_Object(m_parent);
				m_state &= ~CullMask;
				m_state |= NeverCullMode;
				SetSyncState(); SetCullState();
			}

		unsigned GetCullMode()
			{Check_Object(this); return m_state & CullMask;}
		
		void SetCullMode(unsigned cullMode)
			{
				Check_Object(this); 
				m_state = (m_state & (~CullMask)) | cullMode;
				SetSyncState(); SetCullState();
			}

		void SetAlignX()
			{
				Check_Object(this); m_state |= AlignXFlag|SyncOnCullFlag;
				SetSyncState(); SetCullState();
			}
		void ClearAlignX()
			{
				Check_Object(this); m_state &= ~(AlignXFlag|SyncOnCullFlag);
				SetSyncState(); SetCullState();
			}
		bool IsAlignX()
			{Check_Object(this); return (m_state&AlignXFlag) != 0;}

		void SetAlignY()
			{
				Check_Object(this); m_state |= AlignYFlag|SyncOnCullFlag;
				SetSyncState(); SetCullState();
			}
		void ClearAlignY()
			{
				Check_Object(this); m_state &= ~(AlignYFlag|SyncOnCullFlag);
				SetSyncState(); SetCullState();
			}

		bool IsShadowed()
			{Check_Object(this); return (m_state&NoShadowFlag) == 0;}

		void DisableShadow()
			{Check_Object(this); m_state |= NoShadowFlag;}
		void EnableShadow()
			{Check_Object(this); m_state &= ~NoShadowFlag;}

		bool IsAlignY()
			{Check_Object(this); return (m_state&AlignYFlag) != 0;}

		unsigned GetElementState()
			{Check_Object(this); return m_state;}
		void SetElementState(unsigned state)
			{
				Check_Object(this); m_state = state;
				SetSyncState(); SetCullState(); SetDrawState();
			}

		static void ReadStateFromPage(
			Stuff::Page *page,
			const Stuff::MString &prefix,
			unsigned *state,
			unsigned *delta,
			Stuff::OBB *bounds
		);

	protected:
		unsigned m_state;

		void MatrixIsClean()
			{
				Check_Object(this);
				m_state &= ~MatrixDirtyFlag; SetSyncState();
			}
		void BoundsAreRight()
			{
				Check_Object(this);
				m_state &= ~BoundsWrongFlag; SetSyncState();
			}

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Sync support
	//
	public:
		typedef void (Element::*SyncMethod)();

		void SetLocalToParent(const Stuff::LinearMatrix4D& matrix)
			{
				Check_Object(this); Check_Object(&matrix);
				m_state &= ~LocalToParentIsIdentityFlag;
				NeedMatrixSync(); m_newLocalToParent = matrix;
			}
		void SetLocalToParentToIdentity()
			{
				Check_Object(this);
				m_state |= LocalToParentIsIdentityFlag|MatrixDirtyFlag;
				SetSyncState();
				m_newLocalToParent = Stuff::LinearMatrix4D::Identity;
			}
		const Stuff::LinearMatrix4D& GetLocalToParent()
			{Check_Object(this); return m_localToParent;}
		const Stuff::LinearMatrix4D& GetNewLocalToParent()
			{Check_Object(this); return m_newLocalToParent;}
		const Stuff::LinearMatrix4D& GetLocalToWorld()
			{
				Check_Object(this); Check_Object(&m_localToWorld);
				return m_localToWorld;
			}

		void Sync()
			{
				Check_Object(this);
				Verify(m_sync != NULL);
				(this->*m_sync)();
			}

	protected:
		virtual void SetSyncState();

		SyncMethod m_sync;

		static SyncMethod SyncMethods[SyncStateCount];

		Stuff::LinearMatrix4D
			m_newLocalToParent,
			m_localToParent,
			m_localToWorld;

		void NoneSyncMethod();
		void DirtyRootSyncMethod();
		void DeferSyncMethod();

		void CleanSyncMethod();
		void DirtySyncMethod();

		void CleanSphereSyncMethod();
		void DirtySphereSyncMethod();

		void CleanOBBSyncMethod();
		void DirtyOBBSyncMethod();

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Culling
	//
	public:
		typedef int (Element::*CameraCullMethod)(CameraElement *camera);
		typedef bool (Element::*RayCullMethod)(CollisionQuery *query);
		typedef int (Element::*SphereTestMethod)(SphereTest *test);
		struct CullMethod {
			CameraCullMethod m_cameraCull;
			RayCullMethod m_rayCull;
			SphereTestMethod m_sphereTest;
		};

		int Cull(CameraElement *camera)
			{
				Check_Object(this); Check_Pointer(camera);
				Verify(m_cameraCull != NULL);
				return (this->*m_cameraCull)(camera);
			}

		const Stuff::OBB& GetWorldOBB()
			{Check_Object(this); return m_worldOBB;}

		Stuff::OBB m_localOBB;

	protected:
		Stuff::OBB m_worldOBB;

		virtual void SetCullState();

		CameraCullMethod m_cameraCull;
		RayCullMethod m_rayCull;
		SphereTestMethod m_sphereTest;

		static CullMethod CullMethods[CullStateCount];

		int NeverCullMethod(CameraElement *camera);
		int AlwaysCullMethod(CameraElement *camera);
		int SphereCullMethod(CameraElement *camera);
		int OBBCullMethod(CameraElement *camera);

		void AlignX(CameraElement *camera);
		int DeferredXNeverCullMethod(CameraElement *camera);
		int DeferredXAlwaysCullMethod(CameraElement *camera);
		int DeferredXSphereCullMethod(CameraElement *camera);
		int DeferredXOBBCullMethod(CameraElement *camera);

		void AlignY(CameraElement *camera);
		int DeferredYNeverCullMethod(CameraElement *camera);
		int DeferredYAlwaysCullMethod(CameraElement *camera);
		int DeferredYSphereCullMethod(CameraElement *camera);
		int DeferredYOBBCullMethod(CameraElement *camera);

		void AlignXY(CameraElement *camera);
		int DeferredXYNeverCullMethod(CameraElement *camera);
		int DeferredXYAlwaysCullMethod(CameraElement *camera);
		int DeferredXYSphereCullMethod(CameraElement *camera);
		int DeferredXYOBBCullMethod(CameraElement *camera);

		bool NeverRayCullMethod(CollisionQuery *query);
		bool AlwaysRayCullMethod(CollisionQuery *query);
		bool SphereRayCullMethod(CollisionQuery *query);
		bool OBBRayCullMethod(CollisionQuery *query);

		int NeverSphereTestMethod(SphereTest *test);
		int AlwaysSphereTestMethod(SphereTest *test);
		int SphereSphereTestMethod(SphereTest *test);
		int OBBSphereTestMethod(SphereTest *test);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Collision
	//
	public:
		bool CastRay(CollisionQuery *query)
			{
				Check_Object(this); Check_Object(query);
				Verify(m_rayCull != NULL);
				if ((this->*m_rayCull)(query))
					return CastCulledRay(query);
				return false;
			}

		Element* FindSmallestElementContaining(SphereTest *test)
			{
				Check_Object(this); Check_Object(test);
				Verify(m_sphereTest != NULL);
				Verify(test->m_type == SphereTest::Contains);
				if (((this->*m_sphereTest)(test)&SphereTest::Contains) != 0)
					return FindSmallestElementContainingCulled(test);
				return NULL;
			}

	protected:
		virtual bool CastCulledRay(CollisionQuery *query)=0;
		virtual Element* FindSmallestElementContainingCulled(SphereTest *test)=0;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Drawing
	//
	public:
		typedef void (Element::*DrawMethod)(
			CameraElement *camera,
			const StateChange *inherited_state,
			int culling_state
		);

		void DrawTo(
			CameraElement *camera,
			const StateChange *inherited_state,
			int culling_state
		)
			{
				Check_Object(this); Check_Pointer(camera);
				Verify(m_draw != NULL);
				(this->*m_draw)(
					camera,
					inherited_state,
					culling_state
				);
			}

		virtual int CountTriangles()=0;

		static int MixLights(
			LightList new_lights,
			const LightList parent_lights,
			const LightList child_lights
		);

		virtual void CleanDamage();

		virtual void ApplyDamage(
			const Stuff::LinearMatrix4D &damage_spot,
			Stuff::Scalar radius
		);

		virtual void ApplyDamageDecal(
			const Stuff::LinearMatrix4D &damage_spot,
			Stuff::Scalar radius,
			MidLevelRenderer::MLRTexture *texture
		);

		virtual void CastShadow(
			const Stuff::LinearMatrix4D &shadow_to_world,
			const Stuff::UnitVector3D &sun_in_world,
			Stuff::Scalar radius
		);

		virtual void MakeFootStep(
			MidLevelRenderer::MLRShape *shape,
			const Stuff::LinearMatrix4D &foot,
			Stuff::Scalar radius,
			MidLevelRenderer::MLRTexture *tex
		);

		virtual MidLevelRenderer::MLRShape* GetMLRShape(CameraElement *camera=NULL)
			{Check_Object(this); return NULL;}

		virtual void GetCoordData(Stuff::DynamicArrayOf<Stuff::Point3D> *array);

	protected:
		virtual void SetDrawState()=0;

		DrawMethod m_draw;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Debugging aids
	//
	public:
		typedef void (*Callback)(
			CameraElement *camera,
			const StateChange *state,
			int culling_state,
			Element *element
		);

		enum {
			DrawGreenBounds=1,
			DrawYellowBounds,
			DrawRedBounds,
			DrawGreenBoundsOnce,
			DrawYellowBoundsOnce,
			DrawRedBoundsOnce
		};

		void SetCallbackIndex(unsigned function)
			{
				Check_Object(this); Verify(function < 1<<CallbackBits);
				m_state &= ~CallbackMask; m_state |= function << CallbackBit;
			}
		unsigned GetCallbackIndex()
			{Check_Object(this); return (m_state&CallbackMask)>>CallbackBit;}

		static Callback* GetCallbackSet()
			{return CallbackSet;}
		static void UseCallbackSet(Callback *callback)
			{CallbackSet = callback;}

		static void DrawOBB(
			const Stuff::OBB& obb,
			const Stuff::RGBAColor& color,
			CameraElement *camera,
			const StateChange *state,
			int culling_state
		);
		static void DrawSphere(
			const Stuff::OBB& obb,
			const Stuff::RGBAColor& color,
			CameraElement *camera,
			const StateChange *state,
			int culling_state
		);

		static void DrawGreenBoundsCallback(
			CameraElement *camera,
			const StateChange *state,
			int culling_state,
			Element *element
		);
		static void DrawYellowBoundsCallback(
			CameraElement *camera,
			const StateChange *state,
			int culling_state,
			Element *element
		);
		static void DrawRedBoundsCallback(
			CameraElement *camera,
			const StateChange *state,
			int culling_state,
			Element *element
		);

		static void DrawGreenBoundsOnceCallback(
			CameraElement *camera,
			const StateChange *state,
			int culling_state,
			Element *element
		);
		static void DrawYellowBoundsOnceCallback(
			CameraElement *camera,
			const StateChange *state,
			int culling_state,
			Element *element
		);
		static void DrawRedBoundsOnceCallback(
			CameraElement *camera,
			const StateChange *state,
			int culling_state,
			Element *element
		);

		static Callback Callbacks[1<<CallbackBits];

	protected:
		static Callback *CallbackSet;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Properties
	//
	public:
		StateChange* GetStateChange()
			{Check_Object(this); return m_stateChange;}
		void AdoptStateChange(StateChange *set);
		void DestroyStateChange();

	protected:
		StateChange *m_stateChange;
	};

	#define SYNC_METHOD(t, s) static_cast<SyncMethod>(&t::s##SyncMethod)

	#define CULL_METHOD(t, c, r)\
		{\
			static_cast<CameraCullMethod>(&t::c##CullMethod),\
			static_cast<RayCullMethod>(&t::r##RayCullMethod),\
			static_cast<SphereTestMethod>(&t::r##SphereTestMethod)\
		}

	#define DRAW_METHOD(t, d) static_cast<DrawMethod>(&t::d##DrawMethod)

	//##########################################################################
	//#######################    Element__ClassData    #########################
	//##########################################################################

	class Element__ClassData:
		public Stuff::Plug__ClassData
	{
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	public:
		Element__ClassData(
			Stuff::RegisteredClass::ClassID class_id,
			const char* class_name,
			Stuff::Plug__ClassData *parent_class,
			Element::Factory element_factory
		):
			RegisteredClass__ClassData(class_id, class_name, parent_class),
			m_elementFactory(element_factory)
				{}

		Element::Factory m_elementFactory;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	public:
		void TestInstance();
	};

}
