#pragma once
#define MLR_MLRPRIMITIVEBASE_HPP

#include <MLR\MLR.hpp>
#include <MLR\GOSVertexPool.hpp>

namespace MidLevelRenderer {

	struct ClipPolygon2;

	struct DataStorage {
		Stuff::DynamicArrayOf<BYTE> index;
		Stuff::DynamicArrayOf<BYTE>	lengths;
		Stuff::DynamicArrayOf<Stuff::Point3D> coords;
		Stuff::DynamicArrayOf<Vector2DScalar> texCoords;
		Stuff::DynamicArrayOf<ColorType> colors;
		Stuff::DynamicArrayOf<Stuff::Vector3D> normals;
	};

	class MLRPrimitiveBase__ClassData;
	class MLRTexture;
	class MLRLight;
	class MLRClippingState;

	extern Stuff::ReadOnlyArrayOf<BYTE>	temp_lengths;

	//##########################################################################
	//####################    MLRPrimitiveBase    ##############################
	//##########################################################################
	// this is the abstract base class for all geometry. it has contains geometry
	// with one and only one renderer state !!!

	class _declspec(novtable) MLRPrimitiveBase:
		public Stuff::RegisteredClass
	{
		friend class MLRShape;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Initialization
	//
	public:
		static void
			InitializeClass();
		static void
			TerminateClass();
		typedef MLRPrimitiveBase__ClassData ClassData;
		static ClassData
			*DefaultData;

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

		~MLRPrimitiveBase();

	public:
		MLRPrimitiveBase(ClassData *class_data);

		typedef MLRPrimitiveBase*
			(*Factory)(
				Stuff::MemoryStream *stream,
				int version
			);

		static MLRPrimitiveBase*
			Make(
				Stuff::MemoryStream *stream,
				int version
			);

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

	// Subprimitves are units in which this geometry is split off
	// ie. nr of polygons in a polygon mesh or number of tripstrips
	// in a tristriped mesh, every of this subprimitves has another
	// number which is type specific
	// ie. number of vertices in a polygon or number of vertices in
	// a tristrip
	// the data for the coord/color/texcoord/normal or index
	// ARE IN THIS ORDER
		virtual int
			GetNumPrimitives() = 0;

		virtual void
			SetSubprimitiveLengths(
				const BYTE *length_array,
				int subprimitive_count,
				bool setFace=true
			) = 0;

	// returns the number of subprimitives
	 	virtual void
			GetSubprimitiveLengths(const BYTE **length_array, int*) = 0;


	// ==============================================================

		virtual void	SetReferenceState(const MLRState& _state, int=0)
			{ Check_Object(this); referenceState = _state; };
		virtual const MLRState&
			GetReferenceState(int=0) const
				{ Check_Object(this); return referenceState; }; 
		virtual const MLRStateBase&
			GetCurrentState(int=0) const
				{ Check_Object(this); return state; }; 

		virtual void
			CombineStates (const MLRState& master)
				{ Check_Object(this); state.CombineIntoBase(master, referenceState); }; 

		int
			GetNumVertices()
				{ Check_Object(this); return coords.GetLength(); }

		virtual void
			SetCoordData(
				const Stuff::Point3D *array,
				int point_count
			);
		virtual void
			GetCoordData(
				const Stuff::Point3D **array,
				int *point_count
			);

		virtual void
			SetTexCoordData(
				const Vector2DScalar *array,
				int point_count
			);
		virtual void
			GetTexCoordData(
				const Vector2DScalar **array,
				int *point_count
			);

	//	is to call befor clipping, parameter: camera point
		virtual int	FindBackFace(const Stuff::Point3D&, const Stuff::Normal3D&) = 0;

		virtual void	Lighting(MLRLight* const*, int nrLights, BYTE=3) = 0;
		virtual DWORD	CenterLighting(MLRLight* const*, int nrLights, BYTE=3) = 0;

		static	void	InitializeDraw();

		virtual	void	InitializeDrawPrimitive(BYTE, int=0);

		virtual int	GetVisible () 
			{ Check_Object(this); return visible; }

		virtual GOSVertex*
			GetGOSVertices(int=0)
				{ Check_Object(this); return gos_vertices; }

		int
			GetNumGOSVertices()
				{ Check_Object(this); return numGOSVertices; }

		virtual GOSVertex2UV*
			GetGOSVertices2UV(int=0)
				{ Check_Object(this); return NULL; }

		virtual GOSVertex3UV*
			GetGOSVertices3UV(int=0)
				{ Check_Object(this); return NULL; }

		virtual DWORD
			GetTexture2()
				{ Check_Object(this); return 0; }

		virtual DWORD
			GetTexture3()
				{ Check_Object(this); return 0; }

		int
			GetSortDataMode()
				{ Check_Object(this); return drawMode; }

		virtual bool
			CastRay(
				Stuff::Line3D *line,
				Stuff::Normal3D *normal
			);

		virtual void
			PaintMe(const ColorType *paintMe) = 0;

		virtual int
			TransformAndClip(Stuff::Matrix4D*, MLRClippingState, GOSVertexPool*, FogData*, DWORD=0xffffffff, bool=false) = 0;

		virtual void
			TransformNoClip(Stuff::Matrix4D*, GOSVertexPool*, FogData*, DWORD=0xffffffff, bool=false) = 0;

		virtual int
			GetNumPasses()
				{ Check_Object(this); return passes; }

		virtual void
			CleanMe()
				{ Check_Object(this); }

		virtual void
			HurtMe(const Stuff::LinearMatrix4D&, Stuff::Scalar radius)
				{ Check_Object(this); }

		virtual MLRPrimitiveBase*
			HurtMe(const Stuff::LinearMatrix4D&, Stuff::Scalar radius, MLRTexture*)
				{ Check_Object(this); return NULL; }

		virtual void
			DrawMyShadow(const Stuff::LinearMatrix4D&, Stuff::UnitVector3D&, Stuff::Scalar, DWORD=0xff800000)
				{ Check_Object(this); return; }

		virtual bool
			FogMesh(const Stuff::LinearMatrix4D *worldToShape, const Stuff::Point3D *cameraPos, FogData *fogData)
				{ Check_Object(this); return false; }

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// This functions using the static buffers
	//
		void
			SetClipCoord(const Stuff::Point3D &point, int index)
				{
					Check_Object(this); Verify(clipExtraCoords->GetLength() > index);
					(*clipExtraCoords)[index].x = point.x;
					(*clipExtraCoords)[index].y = point.y;
					(*clipExtraCoords)[index].z = point.z;
				}
		void
			FlashClipCoords(int num)
				{
					Check_Object(this); Verify(clipExtraCoords->GetLength() > num);
					dataStore->coords.SetLength(num);
					Stuff::Point3D *c = dataStore->coords.GetData();

					for(int i=0;i<num;i++)
					{
						c[i].x = (*clipExtraCoords)[i].x;
						c[i].y = (*clipExtraCoords)[i].y;
						c[i].z = (*clipExtraCoords)[i].z;
					}
					SetCoordData(dataStore->coords.GetData(), num);
				}
		void
			SetClipTexCoord(const Vector2DScalar &uvs, int index)
				{
					Check_Object(this); Verify(clipExtraTexCoords->GetLength() > index);

					(*clipExtraTexCoords)[index] = uvs;
				}
		void
			FlashClipTexCoords(int num)
				{
					Check_Object(this); Verify(clipExtraTexCoords->GetLength() > num);
					dataStore->texCoords.SetLength(num);
					Mem_Copy(dataStore->texCoords.GetData(), clipExtraTexCoords->GetData(), sizeof(Vector2DScalar)*num, sizeof(Vector2DScalar)*num);
					SetTexCoordData(dataStore->texCoords.GetData(), num);
				}

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// extra channel enum
	//
	enum ExtraChannel {
		FogChannel = 0,
		SpecularRedChannel,
		MaxNumberOfExtraChannel
	};

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Reference counting
	//
	public:
		void
			AttachReference()
				{Check_Object(this); ++referenceCount;}
		void
			DetachReference()
				{
					Check_Object(this); Verify(referenceCount > 0);
					if ((--referenceCount) == 0)
					{
						Unregister_Object(this);
						delete this;
					}
				}

		int
			GetReferenceCount()
				{return referenceCount;}

	protected:
		WORD
			referenceCount;
		WORD
			numGOSVertices;

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

		void
			GetExtend(Stuff::ExtentBox *box);

		DataStorage *dataStore;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Fog data
	//
	public:
		static void
			SetFogTableEntry(int entry, Stuff::Scalar fogNearClip, Stuff::Scalar fogFarClip, Stuff::Scalar fogDensity);
		static void
			GetFogTableEntry(int entry, Stuff::Scalar * pfogNearClip, Stuff::Scalar * pfogFarClip, Stuff::Scalar * pfogDensity);
		static Stuff::Scalar
				fogTable[Limits::Max_Number_Of_FogStates][4];

	protected:
		virtual void
			Transform(Stuff::Matrix4D*);

		static ClipPolygon2 *clipBuffer;

		BYTE	visible;	//	primitive visibilty per frame
		BYTE	passes;
		BYTE	drawMode;
		BYTE	channelUse;

//		int		numPrimitives;	// Number of primitives, e.g. - num quads 
//		Replaced by GetNumPrimitives

//		int		numVertices;	// number of verts for stats and vert arrays
//		Replaced by GetNumVertices

		Stuff::ReadOnlyArrayOf<Stuff::Point3D> coords;	// Base address of coordinate list 
		Stuff::ReadOnlyArrayOf<Vector2DScalar> texCoords;	// Base address of texture coordinate list 

		static Stuff::DynamicArrayOf<Stuff::Vector4D> *transformedCoords;

		static Stuff::DynamicArrayOf<ColorType> *clipExtraColors; // , Max_Number_Vertices_Per_Mesh

		static Stuff::DynamicArrayOf<MLRClippingState> *clipPerVertex;  // , Max_Number_Vertices_Per_Mesh

		static Stuff::DynamicArrayOf<Stuff::Vector4D> *clipExtraCoords;  // , Max_Number_Vertices_Per_Mesh
		static Stuff::DynamicArrayOf<Vector2DScalar> *clipExtraTexCoords;  // , Max_Number_Vertices_Per_Mesh

		static Stuff::DynamicArrayOf<WORD> *clipExtraLength;  // , Max_Number_Primitives_Per_Frame

		MLRStateBase	state;
		MLRState	referenceState;

		GOSVertex *gos_vertices;
//		WORD
//			numGOSVertices;		moved up for alignment reasons

		static Stuff::DynamicArrayOf<BYTE> *extraChannels[2*MaxNumberOfExtraChannel];
	};

	struct IcoInfo {
		int type;
		int depth;
		bool indexed;
		Stuff::Scalar radius;
		Stuff::Scalar all;
		bool onOff;
		const char *GetTypeName();
	};

	MLRShape*
		CreateIndexedIcosahedron(
			IcoInfo&,
			Stuff::DynamicArrayOf<MLRState>*
	);

	//##########################################################################
	//###################    MLRPrimitiveBase__ClassData    ####################
	//##########################################################################

	class MLRPrimitiveBase__ClassData:
		public Stuff::RegisteredClass::ClassData
	{
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	public:
		MLRPrimitiveBase__ClassData(
			Stuff::RegisteredClass::ClassID class_id,
			const char* class_name,
			Stuff::RegisteredClass::ClassData *parent_class,
			MLRPrimitiveBase::Factory primitive_factory
		):
			RegisteredClass__ClassData(class_id, class_name, parent_class),
			primitiveFactory(primitive_factory)
				{}

		MLRPrimitiveBase::Factory 
			primitiveFactory;

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

	struct ClipData2
	{
		Stuff::Vector4D *coords;
		Stuff::RGBAColor *colors;
		Vector2DScalar *texCoords;
		MLRClippingState *clipPerVertex;
		BYTE *extraChannel[MLRPrimitiveBase::MaxNumberOfExtraChannel];

		WORD length;
	};

	struct ClipPolygon2
	{
		void Init(int);
		void Destroy();

		Stuff::DynamicArrayOf<Stuff::Vector4D> coords; // [Max_Number_Vertices_Per_Polygon]
		Stuff::DynamicArrayOf<Stuff::RGBAColor> colors; //[Max_Number_Vertices_Per_Polygon];
		Stuff::DynamicArrayOf<Vector2DScalar> texCoords; //[2*Max_Number_Vertices_Per_Polygon];
		Stuff::DynamicArrayOf<MLRClippingState> clipPerVertex; //[Max_Number_Vertices_Per_Polygon];
		Stuff::DynamicArrayOf<BYTE> extraChannel[MLRPrimitiveBase::MaxNumberOfExtraChannel]; //[Max_Number_Vertices_Per_Polygon];
	};

#if 0	// still defined in "MLRPrimitive.hpp"
	inline Stuff::Scalar
		GetBC(int nr, const Stuff::Vector4D& v4d) 
	{
		switch(nr)
		{
			case 0:
				return (v4d.w - v4d.y);
			case 1:
				return v4d.y;
			case 2:
				return (v4d.w - v4d.x);
			case 3:
				return v4d.x;
			case 4:
				return v4d.z;
			case 5:
				return (v4d.w - v4d.z);
		}
		return 0.0f;
	}

	inline void
		GetDoubleBC
			(
				int nr, 
				Stuff::Scalar& result1,
				Stuff::Scalar& result2,
				const Stuff::Vector4D& v4d1, 
				const Stuff::Vector4D& v4d2
			)
	{
		switch(nr)
		{
			case 0:
				result1 = (v4d1.w - v4d1.y);
				result2 = (v4d2.w - v4d2.y);
			break;
			case 1:
				result1 = v4d1.y;
				result2 = v4d2.y;
			break;
			case 2:
				result1 = (v4d1.w - v4d1.x);
				result2 = (v4d2.w - v4d2.x);
			break;
			case 3:
				result1 = v4d1.x;
				result2 = v4d2.x;
			break;
			case 4:
				result1 = v4d1.z;
				result2 = v4d2.z;
			break;
			case 5:
				result1 = (v4d1.w - v4d1.z);
				result2 = (v4d2.w - v4d2.z);
			break;
		}
	}

	inline Stuff::Scalar
		GetLerpFactor
			(
				int nr, 
				const Stuff::Vector4D& v4d1, 
				const Stuff::Vector4D& v4d2
			)
	{
		Stuff::Scalar result1, result2;

		switch(nr)
		{
			case 0:
				result1 = (v4d1.w - v4d1.y);
				result2 = (v4d2.w - v4d2.y);
			break;
			case 1:
				result1 = v4d1.y;
				result2 = v4d2.y;
			break;
			case 2:
				result1 = (v4d1.w - v4d1.x);
				result2 = (v4d2.w - v4d2.x);
			break;
			case 3:
				result1 = v4d1.x;
				result2 = v4d2.x;
			break;
			case 4:
				result1 = v4d1.z;
				result2 = v4d2.z;
			break;
			case 5:
				result1 = (v4d1.w - v4d1.z);
				result2 = (v4d2.w - v4d2.z);
			break;
			default:
				result1 = 0.0f;
				result2 = 0.0f;
				Abort_Program("Invalid plane number used !");
			break;
		}
		Verify(!Stuff::Close_Enough(result1, result2));
		return result1 / (result1 - result2);
	}

#endif

	inline Stuff::Scalar
		GetBC(int nr, const Stuff::Vector4D& v4d) 
	{
		switch(nr)
		{
			case 0:
				return (v4d.w - v4d.y);
			case 1:
				return v4d.y;
			case 2:
				return (v4d.w - v4d.x);
			case 3:
				return v4d.x;
			case 4:
				return v4d.z;
			case 5:
				return (v4d.w - v4d.z);
		}
		return 0.0f;
	}

	inline void
		GetDoubleBC
			(
				int nr, 
				Stuff::Scalar& result1,
				Stuff::Scalar& result2,
				const Stuff::Vector4D& v4d1, 
				const Stuff::Vector4D& v4d2
			)
	{
		switch(nr)
		{
			case 0:
				result1 = (v4d1.w - v4d1.y);
				result2 = (v4d2.w - v4d2.y);
			break;
			case 1:
				result1 = v4d1.y;
				result2 = v4d2.y;
			break;
			case 2:
				result1 = (v4d1.w - v4d1.x);
				result2 = (v4d2.w - v4d2.x);
			break;
			case 3:
				result1 = v4d1.x;
				result2 = v4d2.x;
			break;
			case 4:
				result1 = v4d1.z;
				result2 = v4d2.z;
			break;
			case 5:
				result1 = (v4d1.w - v4d1.z);
				result2 = (v4d2.w - v4d2.z);
			break;
		}
	}

	inline Stuff::Scalar
		GetLerpFactor
			(
				int nr, 
				const Stuff::Vector4D& v4d1, 
				const Stuff::Vector4D& v4d2
			)
	{
		Stuff::Scalar result1, result2;

		switch(nr)
		{
			case 0:
				result1 = (v4d1.w - v4d1.y);
				result2 = (v4d2.w - v4d2.y);
			break;
			case 1:
				result1 = v4d1.y;
				result2 = v4d2.y;
			break;
			case 2:
				result1 = (v4d1.w - v4d1.x);
				result2 = (v4d2.w - v4d2.x);
			break;
			case 3:
				result1 = v4d1.x;
				result2 = v4d2.x;
			break;
			case 4:
				result1 = v4d1.z;
				result2 = v4d2.z;
			break;
			case 5:
				result1 = (v4d1.w - v4d1.z);
				result2 = (v4d2.w - v4d2.z);
			break;
			default:
				result1 = 0.0f;
				result2 = 0.0f;
				STOP(("Invalid plane number used !"));
			break;
		}
		if(result1 == 0.0f)
		{
			return 0.0f;
		}
		Verify(!Stuff::Close_Enough(result1, result2, Stuff::SMALL*0.1f));
		return result1 / (result1 - result2);
	}
}
