#pragma once
#include "Maths.h"
#include "Assimp/include/assimp.h"
#include "Assimp/include/aiPostProcess.h"
#include "Assimp/include/aiScene.h"
#include "Assimp/include/SceneAnimator.h"


//***************Model3D***************
//Clase base abstracta para el cargado de un modelo
//es responsable del cargado del modelo, manejo de la animacion y procesamiento de la estructura del modelo.
//Un modelo esta formado por una estructura jerarquica de nodos. Cada nodo tiene una matriz de transformacion asociada y puede tener varias mallas de vertices.
//Cada malla de vertices esta asociada a un material (incluye textura e informacion de color y luz).
//La funcion "process()" es la funcion encargada del procesamiento del modelo. Al ejecutarse, primero se ejecuta "preProcess()" para realizar cualquier accion
//necesaria antes del comienzo del procesamiento del modelo y lo ultimo que se ejecuta es "postProcess()" al final de "process()".
//Para cada nodo "process()" realiza la transformacion correspondiente llamando a "applyTransformation(float* fMatrix4Data)" donde fMatrix4Data son los valores
//de una matriz4x4 en formato ya compatible con OpenGL. Luego de procesado un nodo se llama a "unApplyLastTransformation()" donde se debe deshacer la transformacion
//del nodo (ej OpenGL: si al hacer applyTransformation se hizo glPushMatrix() y luego se aplico la transformación, entonces en este caso se debera hacer glPopMatrix para
//volver al estado anterior a la transformacion). Luego de aplicada la transformacion del nodo, se llama a "processMesh" para realizar las acciones deseadas
//con los datos del modelo.
//Los vectores "oSceneMin" y "oSceneMax" representan una Bounding Box alineada a los ejes que contiene al modelo en estado no animado y "oSceneCenter" es el centro de
//esa Bounding Box. En caso de que se desee el modelo centrado hay que realizar una traslación de -oSceneCenter.
//Las clases derivadas deben de encargarse de implementar opcionalmente las acciones de	"preProcess()" y "postProcess()" y obligatoriamente de "applyTransformation",
//"unApplyLastTransformation" y "processMesh". Ver "OpenGLModel" como ejemplo.
//***************Model3D***************

class Model3D
{
public:
	//constructora recibe la ruta al archivo del modelo
	Model3D(const char* strPath);
	//retorna si el modelo fue cargado correctamente
	bool isModelLoaded();
	//procesa el modelo
	//bUpdateBBox indica si se calcula nuevamente el bounding box del modelo para la posicion actual de la animacion
	void process(bool bUpdateBBox = false);
	//devuelve true en caso de que el modelo tenga animaciones
	bool hasAnimations();
	//devuelve cantidad de animaciones
	int numberOfAnimations();
	//devuelve el nombre de la n-animacion, en caso de que no exista devuelve NULL
	char* animationName(unsigned int n);
	//setea la animacion a utilizar, se puede limitar la ejecucion de la animacion al intervalo
	//comprendido entre fSecondsIni y fSecondsEnd. En caso de que no formen un intervalo valido
	//no se aplica el limite a la animacion
	bool setAnimation(unsigned int n, float fSecondsIni = 0.0f, float fSecondsEnd = 0.0f);
	//devuelve la animacion actual seleccionada, -1 en caso de que no haya una animacion seleccionada
	int getActualAnimation();
	//el modelo pasa a no tener una animacion seleccionada
	void disableAnimation();
	//tiempo de avance en segundos para la animacion, en caso de que no haya animacion seleccionada no tiene efecto
	void advance(float fSeconds);
	//establece el modelo en el tiempo fSeconds de la animacion actual, en caso de que no haya animacion seleccionada no tiene efecto
	void setAnimationTime(float fSeconds);
	//destructora, elimina estructuras auxiliares
	virtual ~Model3D(void);

protected:
	virtual void preProcess() {};
	virtual void applyTransformation(float* fMatrix4Data) = 0;
	virtual void unApplyLastTransformation() = 0;
	virtual void processMesh(const struct aiMaterial *pMtl, const struct aiFace* pFaces, unsigned int nNumFaces, aiVector3D* aVertices, aiColor4D* aColorsByVertexIndex, aiVector3D* aNormalsByVertexIndex, aiVector3D* aTextureCoordsByVertexIndex) = 0;
	virtual void postProcess() {};

private:
	void getBoundingBox(Vector3* pMin, Vector3* pMax);
	void getBoundingBoxForNode(const struct aiNode* pNd, Vector3* pMin, Vector3* pMax, struct aiMatrix4x4* pTrafo);
	void recursiveProcess(const struct aiNode* pNd, bool bUpdateBBox);


public:
	//vectores min, max y centro del bounding box del modelo en estado original
	Vector3 oSceneMin, oSceneMax, oSceneCenter;

	//vectores min, max y centro del bounding box del modelo en el ultimo momento que se calculo
	Vector3 oUpdatedSceneMin, oUpdatedSceneMax, oUpdatedSceneCenter;

protected:
	const struct aiScene* _pScene;
	SceneAnimator* _pSceneAnimator;
	float _fSecondsCount, _fSecondsCountIni, _fSecondsCountEnd;
	int _nActualAnimation;

	//estructuras auxiliares para animar
	aiVector3D* _aVertices;
	bool* _aVertexModified;
	unsigned int maxMeshVertices;

	//bandera para indicar que es el primer vertice que se usa para actualizar el bounding box
	bool _bFirstBBoxUpdate;
};
