#include "OpenGLModel.h"
#include "OpenGLTextureMap.h"

/////funciones auxiliares
void setFloat4(float f[4], float a, float b, float c, float d)
{
	f[0] = a;
	f[1] = b;
	f[2] = c;
	f[3] = d;
}

void color4ToFloat4(const struct aiColor4D *c, float f[4])
{
	f[0] = c->r;
	f[1] = c->g;
	f[2] = c->b;
	f[3] = c->a;
}

void Color4f(const struct aiColor4D *color, float oldColor[4])
{
	if (oldColor[0] != color->r || oldColor[1] != color->g || oldColor[2] != color->b || oldColor[3] != color->a){
		glColor4f(color->r, color->g, color->b, color->a);
		oldColor[0] = color->r;
		oldColor[1] = color->g;
		oldColor[2] = color->b;
		oldColor[3] = color->a;
	}
}

/////

OpenGLModel::OpenGLModel(const char* strPath, bool bTexturesEnabledByDefault, bool bColorMaterialEnabledByDefault, bool bIgnoreTexturePath, char* strTextureBasePath): Model3D(strPath)
{
	this->bTexturesEnabledByDefault = bTexturesEnabledByDefault;
	this->bColorMaterialEnabledByDefault = bColorMaterialEnabledByDefault;

	this->bIgnoreTexturePath = bIgnoreTexturePath;
	if (strTextureBasePath != NULL)
		this->strTextureBasePath = string(strTextureBasePath);
	else
		this->strTextureBasePath = "";

	oCenterVector = oSceneCenter;

	fScaleFactor = oSceneMax.x-oSceneMin.x;
	fScaleFactor = max(oSceneMax.y - oSceneMin.y,fScaleFactor);
	fScaleFactor = max(oSceneMax.z - oSceneMin.z,fScaleFactor);
	fScaleFactor = 1.f / fScaleFactor;

	oSceneMax *= fScaleFactor;
	oSceneMin *= fScaleFactor;
	oSceneCenter *= fScaleFactor;

	oSceneMax -= oSceneCenter;
	oSceneMin -= oSceneCenter;
	oSceneCenter.x = 0;
	oSceneCenter.y = 0;
	oSceneCenter.z = 0;
}

OpenGLModel::~OpenGLModel(void)
{
}

void OpenGLModel::preProcess()
{
	bTexturesEnabled = bTexturesEnabledByDefault;
	bColorMaterialEnabled = bColorMaterialEnabledByDefault;

	//inicializo valores auxiliares para reducir cantidad de llamados
	oldColor[0] = -1.0f;
	oldColor[1] = -1.0f;
	oldColor[2] = -1.0f;
	oldColor[3] = -1.0f;
	cDif[0] = -1.0f;
	cDif[1] = -1.0f;
	cDif[2] = -1.0f;
	cDif[3] = -1.0f;
	cAmb[0] = -1.0f;
	cAmb[1] = -1.0f;
	cAmb[2] = -1.0f;
	cAmb[3] = -1.0f;
	cSpe[0] = -1.0f;
	cSpe[1] = -1.0f;
	cSpe[2] = -1.0f;
	cSpe[3] = -1.0f;
	cEmi[0] = -1.0f;
	cEmi[1] = -1.0f;
	cEmi[2] = -1.0f;
	cEmi[3] = -1.0f;
	oldShi = -1.0f;

	nActualTexture = 0;

	glPushMatrix();

	// escalo y centro el modelo
	glScalef(fScaleFactor, fScaleFactor, fScaleFactor);
	glTranslatef( -oCenterVector.x, -oCenterVector.y, -oCenterVector.z );
}
void OpenGLModel::applyTransformation(float* fMatrix4Data)
{
	glPushMatrix();
	glMultMatrixf((float*)fMatrix4Data);
}
void OpenGLModel::unApplyLastTransformation()
{
	glPopMatrix();
}

void OpenGLModel::processMesh(const struct aiMaterial *pMtl, const struct aiFace* pFaces, unsigned int nNumFaces, aiVector3D* aVertices, aiColor4D* aColorsByVertexIndex, aiVector3D* aNormalsByVertexIndex, aiVector3D* aTextureCoordsByVertexIndex)
{

	//cantidad de texturas para el material de la mesh	
	unsigned int nCont = aiGetMaterialTextureCount(pMtl, aiTextureType_DIFFUSE);
	aiString pStr;
	aiGetMaterialString(pMtl,AI_MATKEY_TEXTURE_DIFFUSE(0),&pStr);

	
	if(nCont > 0) 
	{
		if (!bTexturesEnabled && bTexturesEnabledByDefault)
		{
			glEnable(GL_TEXTURE_2D);
			bTexturesEnabled = true;
		}
		
		string oTexturePath = pStr.data;
		GLuint nTextureID = OpenGLTextureMap::getInstance()->GetTextureID(oTexturePath, strTextureBasePath, bIgnoreTexturePath);
		if(nActualTexture != nTextureID || nTextureID == 0)
		{
			glBindTexture(GL_TEXTURE_2D, nTextureID);
			nActualTexture = nTextureID;
		}
	}
	else
	{
		if (bTexturesEnabled)
		{
			glDisable(GL_TEXTURE_2D);
			bTexturesEnabled = false;
		}
	}

	if(aColorsByVertexIndex != NULL) {
		if (!bColorMaterialEnabled && bColorMaterialEnabledByDefault)
		{
			glEnable(GL_COLOR_MATERIAL);
			bColorMaterialEnabled = true;
		}
	} 
	else
	{
		if (bColorMaterialEnabled)
		{
			glDisable(GL_COLOR_MATERIAL);
			bColorMaterialEnabled = false;
		}
	}
	
	applyMaterial(pMtl);

	for (unsigned int t = 0; t < nNumFaces; ++t)
	{
		const struct aiFace* face = &pFaces[t];
		GLenum face_mode;

		if ((t == 0) || ( t > 0 && face->mNumIndices != pFaces[t-1].mNumIndices))
		{

			if (t > 0)
			{
				glEnd();
			}

			switch(face->mNumIndices)
			{
				case 1: face_mode = GL_POINTS; break;
				case 2: face_mode = GL_LINES; break;
				case 3: face_mode = GL_TRIANGLES; break;
				default: face_mode = GL_POLYGON; break;
			}

			glBegin(face_mode);
		}

		for(unsigned int i = 0; i < face->mNumIndices; i++)
		{
			int index = face->mIndices[i];

			if(aColorsByVertexIndex != NULL)
			{
				Color4f(&aColorsByVertexIndex[index], oldColor);
			}

			if(aNormalsByVertexIndex != NULL)
			{
				glNormal3fv(&aNormalsByVertexIndex[index].x);
			}
			
			if(nCont > 0 && aTextureCoordsByVertexIndex != NULL)
			{
				glTexCoord2fv(&aTextureCoordsByVertexIndex[index].x);
			}

			if(_pSceneAnimator != NULL )
			{
				if (_aVertexModified[index])
				{
					glVertex3fv(&_aVertices[index][0]);
				}
				else
				{
					glVertex3fv(&aVertices[index].x);
				}

			}
			else
			{
				glVertex3fv(&aVertices[index].x);
			}
		}
	}

	glEnd();
}

void OpenGLModel::postProcess()
{
	if (bTexturesEnabledByDefault != bTexturesEnabled)
	{
		if (bTexturesEnabled)
		{
			glDisable(GL_TEXTURE_2D);
		}
		else
		{
			glEnable(GL_TEXTURE_2D);
		}
	}

	if (bColorMaterialEnabledByDefault != bColorMaterialEnabled)
	{
		if (bColorMaterialEnabled)
		{
			glDisable(GL_COLOR_MATERIAL);
		}
		else
		{
			glEnable(GL_COLOR_MATERIAL);
		}
	}

	glPopMatrix();

	oUpdatedSceneMin -= oCenterVector;
	oUpdatedSceneMax -= oCenterVector;
	oUpdatedSceneCenter -= oCenterVector;

	oUpdatedSceneMin *= fScaleFactor;
	oUpdatedSceneMax *= fScaleFactor;
	oUpdatedSceneCenter *= fScaleFactor;
}

void OpenGLModel::applyMaterial(const struct aiMaterial *mtl)
{
	float c[4];

	int ret1, ret2;
	struct aiColor4D diffuse;
	struct aiColor4D specular;
	struct aiColor4D ambient;
	struct aiColor4D emission;
	float shininess, strength;
	unsigned int max;

	setFloat4(c, 0.8f, 0.8f, 0.8f, 1.0f);
	if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_DIFFUSE, &diffuse))
		color4ToFloat4(&diffuse, c);
	if (c[0] != cDif[0] || c[1] != cDif[1] || c[2] != cDif[2] || c[3] != cDif[3]){
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, c);
		cDif[0] = c[0];
		cDif[1] = c[1];
		cDif[2] = c[2];
		cDif[3] = c[3];
	}

	setFloat4(c, 0.0f, 0.0f, 0.0f, 1.0f);
	if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_SPECULAR, &specular))
		color4ToFloat4(&specular, c);
	if (c[0] != cSpe[0] || c[1] != cSpe[1] || c[2] != cSpe[2] || c[3] != cSpe[3]){
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, c);
		cSpe[0] = c[0];
		cSpe[1] = c[1];
		cSpe[2] = c[2];
		cSpe[3] = c[3];
	}

	setFloat4(c, 0.2f, 0.2f, 0.2f, 1.0f);
	if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_AMBIENT, &ambient))
		color4ToFloat4(&ambient, c);
	if (c[0] < 0.2f)
		c[0] = 0.2f;
	if (c[1] < 0.2f)
		c[1] = 0.2f;
	if (c[2] < 0.2f)
		c[2] = 0.2f;
	if (c[0] != cAmb[0] || c[1] != cAmb[1] || c[2] != cAmb[2] || c[3] != cAmb[3]){
		glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, c);
		cAmb[0] = c[0];
		cAmb[1] = c[1];
		cAmb[2] = c[2];
		cAmb[3] = c[3];
	}

	setFloat4(c, 0.0f, 0.0f, 0.0f, 1.0f);
	if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_EMISSIVE, &emission))
		color4ToFloat4(&emission, c);
	if (c[0] != cEmi[0] || c[1] != cEmi[1] || c[2] != cEmi[2] || c[3] != cEmi[3]){
		glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, c);
		cEmi[0] = c[0];
		cEmi[1] = c[1];
		cEmi[2] = c[2];
		cEmi[3] = c[3];
	}

	max = 1;
	ret1 = aiGetMaterialFloatArray(mtl, AI_MATKEY_SHININESS, &shininess, &max);
	max = 1;
	ret2 = aiGetMaterialFloatArray(mtl, AI_MATKEY_SHININESS_STRENGTH, &strength, &max);
	if((ret1 == AI_SUCCESS) && (ret2 == AI_SUCCESS)){

		if (shininess * strength != oldShi){
			glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess * strength);
			oldShi = shininess * strength;
		}

	}
	else {
		if (0.0f != oldShi){
			glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 0.0f);
			oldShi = 0.0f;
		}
		setFloat4(c, 0.0f, 0.0f, 0.0f, 0.0f);
		if (c[0] != cSpe[0] || c[1] != cSpe[1] || c[2] != cSpe[2] || c[3] != cSpe[3]){
			glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, c);
			cSpe[0] = c[0];
			cSpe[1] = c[1];
			cSpe[2] = c[2];
			cSpe[3] = c[3];
		}
	}

}

GLint OpenGLModel::generateDisplayList()
{
	//genero display list
	int list = glGenLists(1);

	aiString pStr;

	//recorro todos los materiales y para aquellos que usen texturas las cargo
	for (unsigned int i = 0; i < _pScene->mNumMaterials; i++)
	{

		//verifico si el material tiene texturas
		if (aiGetMaterialTextureCount(_pScene->mMaterials[i], aiTextureType_DIFFUSE) > 0)
		{
				aiGetMaterialString(_pScene->mMaterials[i],AI_MATKEY_TEXTURE_DIFFUSE(0),&pStr);
				string oTexturePath = pStr.data;
				GLuint texturaID = OpenGLTextureMap::getInstance()->GetTextureID(oTexturePath, strTextureBasePath, bIgnoreTexturePath);
		}
	}

	glNewList(list, GL_COMPILE);
		process();
	glEndList();

	return list;
}