#pragma once

#include "3DMaterial.h"												// CMaterial
#include "3DMaterialManager.h"								// CMaterialManager
#include "3DTriangleBunch.h"									// C3DTriangleBunch
#include <assert.h>														// assert()

#include <vector>															// STL vector<>
#include <map>																// STL map<>
#include <hash_map>														// STL hash_multimap<>
using namespace std;													// STL

struct IDirect3DVertexBuffer8;
struct IVertexBuffer;

// faster but has to be checked for every card (CAPS)
//#define SIZE_VERTEXBUFFER			(256*256/3)				// MaxVertexIndex
//#define SIZE_INDEXBUFFER			(256*256)				// MaxPrimitiveCount (D3DFMT_INDEX16)

// should work for every card
#define SIZE_VERTEXBUFFER			(2000)				// MaxVertexIndex
#define SIZE_INDEXBUFFER			(1000)				// MaxPrimitiveCount (D3DFMT_INDEX16)

class C3DObject
{
	// existance: per vertex
	class CVertexLoadHelper
	{
	public:
		//! constructor
		CVertexLoadHelper() :m_PosIndex(0xffffffff),m_TexIndex(0xffffffff), m_NormIndex(0xffffffff), m_BaseIndex(0xffffffff), m_VertexSmoothingGroup(0)
		{}

		DWORD																m_PosIndex;								//!< from .OBJ, 0xffffffff means unassigned, m_ObjVerticesPos
		DWORD																m_TexIndex;								//!< from .OBJ, 0xffffffff means unassigned, m_ObjVerticesUV
		DWORD																m_NormIndex;							//!< from .OBJ, 0xffffffff means unassigned, index in m_ObjVerticesNorm
		DWORD																m_BaseIndex;							//!< from .OBJ, 0xffffffff means unassigned, index in m_ObjVerticesBase
		DWORD																m_VertexSmoothingGroup;		//!< from .OBJ 32Bit from 3DStudio MAX
	};


	// helper to get order for CVertexLoadHelper
	struct CVertexLoadOrder: public std::binary_function< CVertexLoadHelper, CVertexLoadHelper, bool>
	{
		bool operator() ( const CVertexLoadHelper &a, const CVertexLoadHelper &b ) const
		{
			// first sort by position
			if(a.m_PosIndex<b.m_PosIndex)return(true);
			if(a.m_PosIndex>b.m_PosIndex)return(false);

			// then by texture
			if(a.m_TexIndex<b.m_TexIndex)return(true);
			if(a.m_TexIndex>b.m_TexIndex)return(false);

			// then by normal
			if(a.m_NormIndex<b.m_NormIndex)return(true);
			if(a.m_NormIndex>b.m_NormIndex)return(false);

			// then by base
			if(a.m_BaseIndex<b.m_BaseIndex)return(true);
			if(a.m_BaseIndex>b.m_BaseIndex)return(false);

			// then by smoothing group
			if(a.m_VertexSmoothingGroup<b.m_VertexSmoothingGroup)return(true);
			if(a.m_VertexSmoothingGroup>b.m_VertexSmoothingGroup)return(false);

			return(false);
		}
	};

	// existance: per triangle
	class CTriangleIndicesLoadHelper
	{
	public:
		CVertexLoadHelper															m_LoadedVertex[3];			//!< position,texture,normal index as loaded
		DWORD																					m_SmoothingGroup;				//!< for creating correct normals
		C3DMaterial *																	m_pMaterial;						//!< 0..
	};


	class CBase3
	{
	public:
		D3DXVECTOR3																		m_BaseVectors[3];				//!< binormal,tangent,normal
	};

	// existance: single
	class CLoadHelper
	{
	public:
		vector<CTriangleIndicesLoadHelper>						m_TriIndices;						//!< 3 vertex indices per face
		vector<C3DMaterial*>													m_Materials;						//!< vector with all materials
		bool																					m_bHasTex;							//!< texture uv information
		bool																					m_bHasNormals;					//!< normal information
		vector<D3DXVECTOR3>														m_ObjVerticesPos;				//!< indexed by m_PosIndex
		vector<D3DXVECTOR3>														m_ObjVerticesNormal;		//!< indexed by m_TexIndex
		vector<D3DXVECTOR2>														m_ObjVerticesUV;				//!< indexed by m_NormIndex
		vector<CBase3>																m_ObjVerticesBase;			//!< indexed by m_BaseIndex, bumpmapping base vectors (object/clone/tangent)
	};

	class CRenderTriangle
	{
	public:
		DWORD																					m_NewVertexIndex[3];		//!< 3 indices in m_Vertices
	};

	class CRenderVertices
	{
	public:
		vector<CRenderTriangle>												m_NewTriIndices;				//!< in CLoadHelper::m_Vertices[]
		map<CVertexLoadHelper,DWORD,CVertexLoadOrder>	m_OBJVertToVertexNo;		//!< to find the POLYBUMPVERTEX for a CVertexLoadHelper object
		vector<POLYBUMPVERTEX>												m_Vertices;							//!< stored vertices for every material
	};

public:
	//! constructor
	C3DObject( void );

	//! destructor
	virtual ~C3DObject( void );

	//! load the .OBJ file format (Alias Wavefront)
	//! /param inRenderer reference to the renderer
	//! /param inszFileName
	//! /param inbObjectSpace
	bool LoadOBJ( C3DRenderer &inRenderer, const char *inszFileName, bool inbObjectSpace );

	//! render the object (every material)
	//! /param inRenderer reference to the renderer
	//! /param inScene reference to the scene
	void Render( C3DRenderer &inRenderer, C3DScene &inScene );

	//! render the tangent vector of the object
	//! /param inRenderer reference to the renderer
	//! /param infScale scale factor for the vectors
	//! /param inbVertexBaseVectors true=vertex base vectors (colored) show be displayed), false=otherwise
	//! /param inbTriBaseVectors true=triangle base vectors (colored) show be displayed), false=otherwise
	//! /param inbNormals true=normal vectors (grey) show be displayed), false=otherwise
	void RenderDebugHelper( C3DRenderer &inRenderer, float infScale );

	//! only used for debugging
	void Debug( void );

	//! get the bounding box O(1) (could be used to find the midpoint)
	//! /param outMin minimum values for each axis
	//! /param outMax maximum values for each axis
	void GetBoundingBox( D3DXVECTOR3 &outvMin, D3DXVECTOR3 &outvMax ) const;

	//! is set after load
	void GetStats( DWORD &outdwFaces, DWORD &outdwVertices );

	// ********************************************************************************

	C3DMaterialManager									m_MaterialManager;		//!< materials of the object

private:

	vector<IDirect3DVertexBuffer8 *>		m_VertexBuffers;			//!< used like this m_VertexBuffers[index/SIZE_VERTEXBUFFER][index%SIZE_VERTEXBUFFER]
	vector<IDirect3DIndexBuffer8 *>			m_IndexBuffers;				//!< three vertex indices for each triangles
	vector<C3DTriangleBunch>						m_TriangleBunch;			//!< every triangle in one bunch has the same material,vertexbuffer and indexbuffer


	//! free the vertexbuffers, indexbuffers, trianglebunch and materials
	void FreeData( void );

	//!
	//! /param inpDevice interface pointer of the DirectX8Device
	//! /param indwVertexCout requested size of the vertexbuffer
	IDirect3DVertexBuffer8 *CreateNewVertexBuffer( IDirect3DDevice8 *inpDevice, DWORD indwVertexCout );

	//!
	//! /param inpDevice interface pointer of the DirectX8Device
	//! /param indwVertexCout requested size of the indexbuffer
	IDirect3DIndexBuffer8 *CreateNewIndexBuffer( IDirect3DDevice8 *inpDevice, DWORD indwIndexCout );

	//!
	//! /param inRenderer reference to the renderer
	//! /param p_Start
	//! /param inoutData
	//! /return true=everything was fine, false=an error occured
	bool _LoadOBJ( C3DRenderer &inRenderer, unsigned char *p_Start, CLoadHelper &inoutData );

	//! /param p_Start
	//! /param out
	bool _GetMTLNameFromOBJ( unsigned char *p_Start, string &out );

	//! load the materials to an .OBJ file
	//! /param inRenderer reference to the renderer
	//! /param inszFileName path and filename of the material definition (.MTL) file
	//! /param inoutData
	//! /return true=everything was fine, false=an error occured
	bool _LoadMTL( C3DRenderer &inRenderer, const char *inszFileName, CLoadHelper &outData );

	//! calculates the bounding box and sorts the vertices in the index and vertex buffers
	//! /param inRenderer reference to the renderer
	//! /param inData Data from _LoadOBJ() and _LoadMTL()
	//! /return true=everything was fine, false=an error occured
	bool _InsertSurfaceData( C3DRenderer &inRenderer, CLoadHelper &inData, CRenderVertices &inDataR );

	//! for testing (e.g. to produce PS2 input data) - material are all put together
//	bool _ExportSurfaceData(	CLoadHelper &inData, CRenderVertices &inDataR, const char *inszObjName, 
//		const char *inszVerticesFilePath, const char *inszIndicesFilePath );

	//! recreate the normal information (with smoothing groups)
	//! /param inData Data from _LoadOBJ() and _LoadMTL()
	//! /param inVPos vertzex position (old index)
	//! /param inVTex texture uv data per vertex (old index)
	void _RecreateNormals( CLoadHelper &inData );

	//! recreate the tangent information (with smoothing groups)
	//! make sure the normals are calculated before calling this
	//! /param inoutData Data from _LoadOBJ() and _LoadMTL()
	void _RecreateTangentBase( CLoadHelper &inoutData );

	//! make sure the normals are calculated before calling this
	//! /param inoutData Data from _BuildRenderVertices()
	void _RecreateCloneBase( CLoadHelper &inData, CRenderVertices &inoutData );

	//! /param inoutData Data from _LoadOBJ() and _LoadMTL()
	void _RecreateObjectBase( CLoadHelper &inoutData );

	//!
	void _BuildRenderVertices( CLoadHelper &inData, CRenderVertices &outData );


private:

	D3DXVECTOR3								m_vMinBoundingBox;				//!< minimum values for each axis
	D3DXVECTOR3								m_vMaxBoundingBox;				//!< maximum values for each axis
	bool											m_bObjectSpace;						//!< true=normal texture is in object space, false otherwise
	
	//
//	static D3DXVECTOR3 CalcTangentVector( const D3DXVECTOR3 invPos[3], const float infS[3], const float infT[3] );

	static void GetObjectSpaceVectors( D3DXVECTOR3 &outvA, D3DXVECTOR3 &outvB, D3DXVECTOR3 &outvC );
	static void GetCloneSpaceVectors( D3DXVECTOR3 &outvA, D3DXVECTOR3 &outvB, D3DXVECTOR3 &outvC );

	IDirect3DVertexBuffer8 *PushToVertexBuffer( C3DRenderer &inRenderer, vector<DWORD> &invIndices, vector<POLYBUMPVERTEX> &invVertices );
	IDirect3DIndexBuffer8 *PushToIndexBuffer( C3DRenderer &inRenderer, vector<WORD> &invIndices );

	friend class CTriangleInputProxy;
};




