#pragma once
#include "../../stdafx.h"
#include "../glLibLocals.h"

#include "../glLibLight.h"
#include "../glLibMaterials.h"
#include "../glLibShader.h"
#include "../glLibTime.h"
#include "glLibObjectMesh.h"

//#include "../glLibExtAlgorithm/glLibExtAlgGraph.h"
template <class type_num> class glLibExtAlgGraph;

class glLibObject {
	friend void glLibExtrudeLightVolumes(glLibLight* light, glLibObject* object, float front, float back, glLibObject* light_volume);
	friend class glLibExtRayTracer;
	template <class type_num> friend class glLibExtAlgGraph;
	template <class type_float> friend class glLibExtPhysicsBody;
	public:
		unsigned long numof_meshes;
		unsigned long numof_vertices,numof_normals,numof_texcoords,numof_tangents,numof_faces;
		
		unsigned long numof_materials;
		map<string,glLibMaterial*> mtllib;
	protected:
		GLfloat *array_vertices, *array_normals, *array_texcoords, *array_tangents;
		vector<GLfloat> arraylist_vertices, arraylist_normals, arraylist_texcoords, arraylist_tangents;
		glLibInternal_mesh** meshes;
		
		map<string,GLuint> lists;
		double construction_time;

		bool _dynamic;
		unsigned long _dynamic_face_type;
	private:
		glLibTimer timer;

	public:
		glLibObject(void);
		glLibObject(string path);
		glLibObject(const char path[]);

		~glLibObject(void);

		void save_direct(const char path[]);
		void save_direct(      string path);

	private:
		template <typename type_float> void glLibInternal_transform_data(GLfloat* data, unsigned long length, glLibMathMatrix<type_float,char>* transform_matrix, glLibMathMatrix<type_float,char>* point_matrix, glLibMathMatrix<type_float,char>* result_matrix) {
			for (unsigned long i=0;i<length;++i) {
				point_matrix->values[0][0] = data[3*i  ];
				point_matrix->values[1][0] = data[3*i+1];
				point_matrix->values[2][0] = data[3*i+2];
				glLibMathMatrix<type_float,char>::multiply(transform_matrix,point_matrix,result_matrix);
				data[3*i  ] = result_matrix->values[0][0];
				data[3*i+1] = result_matrix->values[1][0];
				data[3*i+2] = result_matrix->values[2][0];
			}
		}
	public:
		template <typename type_float> void apply_transform(glLibMathMatrix<type_float,char>* transform_matrix, glLibFlag data_to_transform) {
			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			if (meshes==NULL) throw glLibError("Object contains no data to transform");
			if (lists.size()!=0) throw glLibError("Object already contains display list data.  In-built transformations must happen before other operations take place!");
			for (unsigned long i=0;i<numof_meshes;++i) {
				if (meshes[i]->has_arrays) throw glLibError("Object already contains array data.  In-built transformations must happen before other operations take place!");
				if (meshes[i]->has_vbos) throw glLibError("Object already contains VBO data.  In-built transformations must happen before other operations take place!");
			}
			if (transform_matrix->_columns!=transform_matrix->_rows) throw glLibError("Transform matrix is not square!");
			#endif
			//array_vertices = new GLfloat[ numof_vertices*3];

			glLibMathMatrix<type_float,char> *point_matrix, *result_matrix;
			if (transform_matrix->_columns==3) {
				point_matrix = new glLibMathMatrix<type_float,char>(3,1,GLLIB_MATRIX_EMPTY);
				result_matrix = new glLibMathMatrix<type_float,char>(3,1,GLLIB_MATRIX_EMPTY);
			}
			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			else if (transform_matrix->_columns==4) {
			#else
			else {
			#endif
				point_matrix = new glLibMathMatrix<type_float,char>(4,1,GLLIB_MATRIX_EMPTY);
				point_matrix->values[3][0] = 1;
				result_matrix = new glLibMathMatrix<type_float,char>(4,1,GLLIB_MATRIX_EMPTY);
			}
			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			else throw glLibError("Invalid dimensions for transform matrix!");
			#endif
			
			if (data_to_transform&GLLIB_VERTEX) {
				glLibInternal_transform_data(array_vertices,numof_vertices,transform_matrix,point_matrix,result_matrix);
			}
			else if (data_to_transform&GLLIB_NORMAL) {
				glLibInternal_transform_data(array_normals,numof_normals,transform_matrix,point_matrix,result_matrix);
			}
			else if (data_to_transform&GLLIB_TEXCOORD) {
				glLibInternal_transform_data(array_texcoords,numof_normals,transform_matrix,point_matrix,result_matrix);
			}
			else if (data_to_transform&GLLIB_TANGENT) {
				glLibInternal_transform_data(array_tangents,numof_normals,transform_matrix,point_matrix,result_matrix);
			}

			/*if (type&GLLIB_ALL_FLAGS) {
				throw glLibError("Unrecognized shader type in flags!");
			}*/ //TODO (see also glLibShader.cpp)

			delete point_matrix;
			delete result_matrix;
		}

		void build_arrays(void);
		//TODO: maybe build_lists, so that can call only one list if so desired?
		void build_lists(glLibShader* shader=NULL);
		void build_vbos(void);

		void draw_direct(glLibShader* shader=NULL);
		void draw_arrays(glLibShader* shader=NULL);
		void draw_lists(glLibShader* shader=NULL);
		void draw_vbos(glLibShader* shader=NULL);
	protected:
		void glLibInternal_set_mat(int mesh_index);

		void glLibInternal_init_start(void);
		void glLibInternal_init_end(void);

		void glLibInternal_make_load(const char path[]);
		void glLibInternal_make_load_obj(const char path[]);
};