// Author: Juan Carlos De Abreu (jcabreur@gmail.com)

#include "Model.h"
#include "../base/LogManagement.h"

BEGIN_TEG_NAMESPACE();

IModel::IModel() : pointDL(0), gridDL(0), surfaceDL(0), boundingBoxDL(0) {}

IModel::~IModel() {}

void IModel::process() {
	// Check if i have a groplist
	if(groupList.size() <= 0) {
		PRINT_LOG(TYPE_ERROR, "The model don't have a data in the groupList");
		return;
	}

	// Initialize the bounding box
	vMin = groupList[0].vertexList[0];
	vMax = groupList[0].vertexList[0];

	// Obtain the number of groups
	const int nGroups = static_cast<int> (groupList.size());

	// Search value of the vertex of the bouding box 
	for(int i = 0; i < nGroups; ++i) {
		// Obtain the number of vertex of the group i
		const int nVertex = static_cast<int> (groupList[i].vertexList.size());

		for(int j = 0; j < nVertex; ++j) {
			// Obtain the vertex
			const Vec3f v = groupList[i].vertexList[j];
			// Check if out of the bounding box, then update this values
			if(v.x < vMin.x) vMin.x = v.x;
			if(v.y < vMin.y) vMin.y = v.y;
			if(v.z < vMin.z) vMin.z = v.z;
			if(v.x > vMax.x) vMax.x = v.x;
			if(v.y > vMax.y) vMax.y = v.y;
			if(v.z > vMax.z) vMax.z = v.z;
		}
	}
	// calculate the center of the model
	const Vec3f center = (vMin + vMax) / 2.f;
	
	// Calculate the axis with more diference in the object
	float maxDistance = 1.f / (max(vMax.x - vMin.x, max(vMax.y - vMin.y, vMax.z - vMin.z)));

	// Translate the object to (0,0,0) and scale
	for(int i = 0; i < nGroups; ++i) {
		// Obtain the number of vertex of the group i
		const int nVertex = static_cast<int> (groupList[i].vertexList.size());
		// Translate and scale the vertex of the group i
		for(int j = 0; j < nVertex; ++j) {
			groupList[i].vertexList[j] -= center;
			groupList[i].vertexList[j] *= maxDistance;
		}
	}

	// Translate the bounding box to the center
	vMin -= center;
	vMax -= center;

	// Scale the model
	vMin *= maxDistance;
	vMax *= maxDistance;

	// Create all display list of the model
	createGridDL();
	createPointDL();	
	createSurfaceDL();
	createBoundingBoxDL();
}

void IModel::createSurfaceDL() {
  for (int i = 0; i < (int)groupList.size(); ++i) {
    groupList[i].createDL();
  }
  return;
}

void IModel::createPointDL() {
	// Generate a displayList for the surface render
	pointDL = glGenLists(1);

	// Verify if the display list was created
	if(!glIsList(pointDL)) {
		PRINT_LOG(TYPE_WARNING, "The display list of the point not was created");
		return;
	}

	// Added the information of the displayList
	glNewList(pointDL, GL_COMPILE);
		// Obtain the number of groups
		const int nGroups = static_cast<int> (groupList.size());

		// For each group
		for(int i = 0; i < nGroups; ++i) {
			groupList[i].drawPoint();
		}
	glEndList();
}

void IModel::createGridDL() {
	// Generate a displayList for the surface render
	gridDL = glGenLists(1);

	// Verify if the display list was created
	if(!glIsList(gridDL)) {
		PRINT_LOG(TYPE_WARNING, "The display list of the grid not was created");
		return;
	}

	// Added the information of the displayList
	glNewList(gridDL, GL_COMPILE);
		// Obtain the number of groups
		const int nGroups = static_cast<int> (groupList.size());

		// For each group
		for(int i = 0; i < nGroups; ++i) {
			groupList[i].drawGrid();
		}
	glEndList();
}

void IModel::createBoundingBoxDL() {}

// Method to make the render the model surface with a specific material.
void IModel::drawSurface(
    const int& renderMode, const SmartPointer<Material>& customMaterial) const {
  // Check if the material is compatible with the render mode.
  if (!(renderMode & Group::TRANSLUCENT) && customMaterial->alpha < 1.0) return;
  if (!(renderMode & Group::OPAQUE_) && customMaterial->alpha >= 1.0) return;
		
  // Draw all the parts of the model.
  BEGIN_MATERIAL(customMaterial, renderMode);
    const int nGroups = static_cast<int> (groupList.size());
    for (int i = 0; i < nGroups; ++i) {
      const int nDisplayList = static_cast<int> (groupList[i].dl.size());
	  for (int j = 0; j < nDisplayList; ++j) glCallList(groupList[i].dl[j]);
    }
  END_MATERIAL(customMaterial, renderMode);
}

void IModel::drawGrid() const {
  glCallList(gridDL);
}

void IModel::drawPoint() const {
  glCallList(pointDL);
}

void IModel::drawSurface(int renderMode)  { 
  for (int i = 0; i < (int)groupList.size(); ++i)
    groupList[i].drawDL(renderMode);
}

void IModel::drawBoundingBox() const {
  glCallList(boundingBoxDL);
}

END_TEG_NAMESPACE();
