// ===================================================================
//
// OBJmodel.h
// OBJ/MTL wrapper class - provide functions to load OBJ geometry file and MTL material file
//	It can also uploads the data into VBO, which can be than handle though VBO ids
//
// Initial coding by Martin Ruzicka

#ifndef OBJ_MODEL_H
#define OBJ_MODEL_H

#include "./geometry/OBJreader.h"
#include "./geometry/modelGroupStorage.h"
#include "./material/MTLreader.h"
#include "./geometry/ModelObject.h"
#include "./material/MaterialManager.h"
#include "./texture/TextureManager.h"


///Auxiliary structure to hold range interval <from, to)
struct Range
{
		//interval <from, to)
	unsigned int from;	
	unsigned int to;
};

void initModel(ObjReader<ModelGroupStorage> &reader, ModelGroupStorage &model_storage);


void readObjModel(const string &path, ModelGroupStorage& dstObjData);
void constructVboArrays(	ModelGroupStorage& srcObjData, vcoord_buffer &dst_vcoord_buffer,tcoord_buffer &dst_tcoord_buffer,
							ncoord_buffer& dst_ncoord_buffer, funion_coord_buffer&  dst_union_face_buffer );

void constructTriangleFacesArray(	vector<Group>& srcGroups, funion_coord_buffer&  srcPolygonFacesArray, 
									funion_coord_buffer&  dstTriangleFacesArray, vector<Range>& dstMeshesRange );

void uploadToGPU(	const string &vbo_name, VBOmanager& vbo_manager,  vcoord_buffer &src_vcoord_buffer, 
					vector<tcoord_buffer> &src_tcoord_multi_buffer, ncoord_buffer& src_ncoord_buffer,
					funion_coord_buffer &src_face_array, VBOids &dst_vbo_id) ;

void constructMeshes( vector<Range>& meshes_range, vector<MeshObject> &dst_meshes);

template<typename MATERIAL>
void appendMaterialToMeshes( vector<Group>& groups, const MaterialManager<MATERIAL>& src_materials, vector<MeshObject> &src_dst_meshes )
{
	for (int i=0; i<groups.size(); ++i)
	{
		src_dst_meshes[i].material=src_materials.getMaterial(groups[i].material_name);
	}
}

void appendGeometryToMeshes(vector<Group>& groups,VBOids& vboID, vector<MeshObject> &src_dst_meshes);
void appendTexturesToMeshes(vector<Group>& groups, TextureManager& textures, const string& texturesPrefix, vector<MeshObject> &src_dst_meshes);


/*
	Wrapper for functions above
*/

///Holds parsed and processed obj 3D model data.
template<typename MATERIAL>
class ModelOBJ
{
private:
	ModelGroupStorage modelData;
	vector<Group> groups;
	tcoord_buffer tCoordBuffer;
	ncoord_buffer nCoordBuffer;
	vcoord_buffer vCoordBuffer;
	funion_coord_buffer fCoordBuffer;	/**< Face buffer */
	funion_coord_buffer fTCoordBuffer;	/**< Triangle face buffer */
	vector<Range> meshesRange;
	VBOids vboID;
	vector<MeshObject> meshes;

public:
	ModelOBJ( const string& path, VBOmanager& vbo_manager, const string &vbo_name, const MaterialManager<MATERIAL>& materials, 
			 string texturesPrefix, TextureManager& textures);

	const vcoord_buffer& getVcoordArray( void );
	const tcoord_buffer& getTcoordArray( void );
	const ncoord_buffer& getNcoordArray( void );
	const funion_coord_buffer& getFcoordArray( void );	/**< Faces ( can be composed from many vertices ) */
	const funion_coord_buffer& getTFcoordArray(void);	/**< Triangle faces ( are compsed from 3 vertices) */
	const  vector<Range>& getTFrange( void );
	const vector<Group>& getGroups( void );
	const VBOids& getVboID( void );
	const vector<MeshObject>& getFinalMeshes( void );
};


template<typename MATERIAL>
ModelOBJ<MATERIAL>::ModelOBJ(	const string& path, VBOmanager& vbo_manager, const string &vbo_name, 
								const MaterialManager<MATERIAL>& materials, string texturesPrefix, TextureManager& textures)
{
	readObjModel( path, this->modelData );
	modelData.get_groups( this->groups );
	constructVboArrays( this->modelData, this->vCoordBuffer, this->tCoordBuffer, this->nCoordBuffer, this->fCoordBuffer);
	vector<tcoord_buffer> multi_tcoord_buffer;
	multi_tcoord_buffer.push_back( this->tCoordBuffer );	//zatim pracuji pouze s 1 sadou texturovych koordinatu (tj bez multitexturingu)
	constructTriangleFacesArray( this->groups, this->fCoordBuffer, this->fTCoordBuffer, this->meshesRange );
	uploadToGPU( vbo_name, vbo_manager, this->vCoordBuffer, multi_tcoord_buffer, this->nCoordBuffer, this->fTCoordBuffer, this->vboID );
	constructMeshes( this->meshesRange, this->meshes );
	appendGeometryToMeshes( this->groups, vboID, this->meshes);
	appendMaterialToMeshes( this->groups, materials, this->meshes);
	appendTexturesToMeshes( this->groups, textures, texturesPrefix, this->meshes);
}

template<typename MATERIAL> const vcoord_buffer& ModelOBJ<MATERIAL>::getVcoordArray( void )
{
	return this->vCoordBuffer;
}
template<typename MATERIAL> const tcoord_buffer& ModelOBJ<MATERIAL>::getTcoordArray( void )
{
	return this->tCoordBuffer;
}
template<typename MATERIAL> const ncoord_buffer& ModelOBJ<MATERIAL>::getNcoordArray( void )
{
	return this->nCoordBuffer;
}
template<typename MATERIAL> const funion_coord_buffer& ModelOBJ<MATERIAL>::getFcoordArray( void )
{
	return this->fCoordBuffer;
}
template<typename MATERIAL> const funion_coord_buffer& ModelOBJ<MATERIAL>::getTFcoordArray(void)
{
	return this->fTCoordBuffer;
}
template<typename MATERIAL> const vector<Range>& ModelOBJ<MATERIAL>::getTFrange( void )
{
	return this->meshesRange;
}
template<typename MATERIAL> const vector<Group>& ModelOBJ<MATERIAL>::getGroups( void )
{
	return this->groups;
}
template<typename MATERIAL> const VBOids& ModelOBJ<MATERIAL>::getVboID( void )
{
	return this->vboID;
}
template<typename MATERIAL> const vector<MeshObject>& ModelOBJ<MATERIAL>::getFinalMeshes( void )
{
	return this->meshes;
}

#endif