#pragma once
#include "Adept.hpp"

namespace Adept {

	class Entity;
	class Entity__CollisionQuery;

	//#########################################################################
	//#####################    CollisionVolume    #############################
	//#########################################################################

	class CollisionVolume:
		public Plug
	{
	public:
		static void InitializeClass();
		static void TerminateClass();

		typedef Entity* (*ChildFinder)(Entity *parent, const char *name);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Constructors/Destructors
	//
	public:
		CollisionVolume();
		CollisionVolume(
			Entity *entity,
			Stuff::MemoryStream *stream,
			int version
		);
		CollisionVolume(
			Entity *main_entity,
			ChildFinder find_function,
			Entity *joint_entity,
			Stuff::MemoryStream *stream,
			int version
		);
		CollisionVolume(const CollisionVolume &bounds);

		~CollisionVolume();

		void Save(Stuff::MemoryStream *stream);

		void TestInstance() const
			{}

		virtual const char* GetName();

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Collision traversal
	//
	public:
		void FindEntityToWorld(
			Stuff::LinearMatrix4D *entity_to_world,
			Entity *base,
			const Stuff::LinearMatrix4D &base_to_world
		);
		CollisionVolume* CastRay(
			Entity__CollisionQuery *query,
			Entity *base,
			const Stuff::LinearMatrix4D &base_to_world
		);
		CollisionVolume* CollideOBB(
			CollisionVolume *solid,
			Entity *base,
			const Stuff::LinearMatrix4D &base_to_world
		);

		typedef void (*WithinCallback)(CollisionVolume *solid, Entity *original_caller);
		void FindSolidsWithin(
			const Stuff::Sphere &sphere,
			WithinCallback callback,
			Entity *original_caller,
			Entity *base,
			const Stuff::LinearMatrix4D &base_to_world
		);

		enum {
			e_IsDestroyed = 0x80,
			e_GoodEnough = 0x40,
			e_DontDestroy = 0x20,
			e_MaterialMask = 0x1f
		};
		bool IsDestroyed()
			{Check_Object(this); return (m_material&e_IsDestroyed) != 0;}

		void Destroy()
			{
				Check_Object(this); Verify(!(m_material&(e_IsDestroyed|e_DontDestroy)));
				m_material |= e_IsDestroyed;
			}
		void Restore()
			{Check_Object(this); m_material &= ~e_IsDestroyed;}

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Data members
	//
	public:
		OBB
			m_localSpaceBounds,
			m_worldSpaceBounds;
		Stuff::ChainOf<CollisionVolume*> m_children;
		Entity *m_entity;
		BYTE m_material;

		enum {CurrentOBBVersion = 3};

		static int ReadOBBVersion(Stuff::MemoryStream *stream);
		static void WriteOBBVersion(Stuff::MemoryStream *stream);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Debugging aids
	//
	public:
		enum {
			DrawOBBRejected=1,
			DrawOBBTraversed,
			DrawOBBStruck,
			DrawOBBPolygonsTested
		};

		static void DrawOBBRejectedCallback(
			ElementRenderer::CameraElement *camera,
			const ElementRenderer::StateChange *state,
			int culling_state,
			ElementRenderer::Element *element
		);
		static void DrawOBBTraversedCallback(
			ElementRenderer::CameraElement *camera,
			const ElementRenderer::StateChange *state,
			int culling_state,
			ElementRenderer::Element *element
		);
		static void DrawOBBStruckCallback(
			ElementRenderer::CameraElement *camera,
			const ElementRenderer::StateChange *state,
			int culling_state,
			ElementRenderer::Element *element
		);
		static void DrawOBBPolygonsTestedCallback(
			ElementRenderer::CameraElement *camera,
			const ElementRenderer::StateChange *state,
			int culling_state,
			ElementRenderer::Element *element
		);
	};

}

#if 1
#define RAYCAST_LOGIC(string) LOGIC("Ray Casting::" string)
#else
#define RAYCAST_LOGIC(string)
#endif
