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

#include "VolumeShadows.h"
#include "../Math/Plane/Plane.h"
#include "../Scene/Scene.h"
#include "../Scene/scene_nodes.h"
#include "../Util/OpenGLFuncs.h"
#include "../Camera/Camera.h"
#include "../Math/Vector.h"
#include "../Math/Matrix.h"

BEGIN_TEG_NAMESPACE();

// VolumeShadows constructor.
VolumeShadows::VolumeShadows() {}

// VolumeShadows destructor.
VolumeShadows::~VolumeShadows() {
	std::map<std::pair<SmartPointer<LightNode>, SmartPointer<GeometryNode>>, ShadowInstance>::iterator it = instances.begin();
	std::map<std::pair<SmartPointer<LightNode>, SmartPointer<GeometryNode>>, ShadowInstance>::iterator end = instances.end();
	for (; it != end; ++it) {
		glDeleteLists(it->second.shadow2DL, 1);
		glDeleteLists(it->second.shadow3DL, 1);
	}
	instances.clear();
}

// Method to draw the shadows.
void VolumeShadows::render(void) { }

// Method to render the shadow of each object in the scene.
void VolumeShadows::render(const SmartPointer<LightNode>& light, std::vector<SmartPointer<GeometryNode>>& models) {
	// Check if each model have calculated the planes and conectivity ....
	const int nModels = static_cast<int> (models.size());
	for (int i = 0; i < nModels; ++i) checkGeometry(light, models[i]);

	// para solucionar que las sombras de diferentes objetos se vean de diferente color ...
	glClear(GL_STENCIL_BUFFER_BIT);
	
	glPushMatrix();
		// Set the parameters ..
		glDepthMask(GL_FALSE);
		glDepthFunc(GL_LEQUAL);

		glEnable(GL_STENCIL_TEST);
		glColorMask(0, 0, 0, 0);
		glStencilFunc(GL_ALWAYS, 1, 0xffffffff);

		// First pass, stencil operation decreases stencil value
		glFrontFace(GL_CCW);
		glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);


		// Draw the shadow of each model
		for (int i = 0; i < nModels; ++i) {
			// Check if the model cast shadows
			if (!models[i]->cast_shadows()) continue;
			std::pair<SmartPointer<LightNode>, SmartPointer<GeometryNode>> lg(light, models[i]);

			glPushMatrix();
				glLoadMatrixf(models[i]->model_view().m);

			//	if (instances[lg].calculateShadow) {
					shadows1(&instances[lg]);

			//		glDeleteLists(instances[lg].shadow2DL, 1);
			//		instances[lg].shadow2DL = glGenLists(1);			

					// TODO: Check glIsList(instances[i].shadow2DL)
			//		glNewList(instances[lg].shadow2DL, GL_COMPILE);
						
						shadows2(models[i]->model(), &instances[lg]);
					
			//		glEndList();
			//	}

			//	glCallList(instances[lg].shadow2DL);

			glPopMatrix();
		}


		// Second pass, stencil operation increases stencil value
		glFrontFace(GL_CW);
		glStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
		
		// Draw the shadow of each model
		//const int nModels = static_cast<int> (models.size());
		for (int i = 0; i < nModels; ++i) {
			// Check if the model cast shadows
			if (!models[i]->cast_shadows()) continue;
			std::pair<SmartPointer<LightNode>, SmartPointer<GeometryNode>> lg(light, models[i]);

			glPushMatrix();
				glLoadMatrixf(models[i]->model_view().m);

			//	if (instances[lg].calculateShadow) {

			//		glDeleteLists(instances[lg].shadow3DL, 1);
			//		instances[lg].shadow3DL = glGenLists(1);			
					// if (!glIsList(instances[i].shadow3DL))
					//	PRINT_LOG(TYPE_WARNING, "The Shadow Display list #%d don't was created", i);
			//		glNewList(instances[lg].shadow3DL, GL_COMPILE);

						shadows3(models[i]->model(), &instances[lg]);
				
			//		glEndList();
			//		instances[lg].calculateShadow = false;
			//	}

			//	glCallList(instances[lg].shadow3DL);

			glPopMatrix();
		}


		glFrontFace(GL_CCW);
		glColorMask(1, 1, 1, 1);

	glPopMatrix();

	// Draw a shadowing rectangle covering the entire screen
	glColor4f(0.0f, 0.0f, 0.0f, SHADOW_OPACITY);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glStencilFunc(GL_NOTEQUAL, 0, 0xffffffff);
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
	glPushMatrix();
		glLoadIdentity();
		glBegin(GL_TRIANGLE_STRIP);
		  ::glVertex3f(-0.1f, 0.1f, -0.10f);
		  ::glVertex3f(-0.1f, -0.1f, -0.10f);
		  ::glVertex3f( 0.1f, 0.1f, -0.10f);
		  ::glVertex3f( 0.1f, -0.1f, -0.10f);
		glEnd();
	glPopMatrix();
	glDisable(GL_BLEND);

	glDepthFunc(GL_LEQUAL);
	glDepthMask(GL_TRUE);
	glDisable(GL_STENCIL_TEST);
}

// Method to calculate the ecuation plane of each face
void VolumeShadows::calculatePlanes(const Group& g, std::vector<ShadowPlane>& s) {
	// Obtain the number of faces
	// Calculate the plane of each face
	const int nFaces = static_cast<int> (g.faceList.size());
	for(int i = 0; i < nFaces; ++i) {
		// Obtain the vertex of the face.
		const Vec3f& v1 = g.vertexList[g.faceList[i].vertex.x];
		const Vec3f& v2 = g.vertexList[g.faceList[i].vertex.y];
		const Vec3f& v3 = g.vertexList[g.faceList[i].vertex.z];

		// Calculate the plane that make the 3 points
		s[i].plane = math::Plane(v1, v2, v3);
	}
}

// TODO: creo que deberia inializar los neigh con o -1 tal vez...

// Method to determin what face are adyacent to other
void VolumeShadows::SetConnectivity(const Group& g, std::vector<ShadowPlane>& s) {
	// Obtain the number of faces in the group
	const int nFaces = static_cast<int> (g.faceList.size());

	// Determine what face are neightbours to other
	for(int i = 0; i < nFaces - 1; ++i) {
		for(int j = 0; j < nFaces; ++j) {
			for(int ki = 0; ki < 3; ++ki) {
				if (!s[i].neigh[ki]) continue;
				for(int kj = 0; kj < 3; ++kj) {
					// Obtain ...
					unsigned int p1i = ki;
					unsigned int p1j = kj;
					unsigned int p2i = (ki + 1) % 3;
					unsigned int p2j = (kj + 1) % 3;

					// Obtain the indices of the vertex
					p1i = g.faceList[i].vertex[p1i];
					p1j = g.faceList[j].vertex[p1j];
					p2i = g.faceList[i].vertex[p2i];
					p2j = g.faceList[j].vertex[p2j];

					// TODO: change the name ...
					// Calculate the ...
					p1i = ((p1i + p2i) - ::abs((int)(p1i - p2i))) / 2;
					p2i = ((p1i + p2i) + ::abs((int)(p1i - p2i))) / 2;
					p1j = ((p1j + p2j) - ::abs((int)(p1j - p2j))) / 2;
					p2j = ((p1j + p2j) + ::abs((int)(p1j - p2j))) / 2;

					// Check if the faces are neighbours
					if((p1i == p1j) && (p2i == p2j)) {
						s[i].neigh.values[ki] = j + 1;	  
						s[j].neigh.values[kj] = i + 1;
					}
				}
			}
		}
	}
}


//////// To draw the shadows all with the same blending ...
void VolumeShadows::shadows1(ShadowInstance* s) {
	// Obtain the number of groups
	// Obtain the number of faces in the group
	const int nGroups = static_cast<int> (s->faceList.size());
	for(int g = 0; g < nGroups; ++g) {
		const int nFaces = static_cast<int> (s->faceList[g].size());
		for (int i = 0; i < nFaces; ++i) {
			// Check if the face are in front the light or not..
			// Set visual parameter
			// Check to see if light is in front or behind the plane (face plane)
			const float side =	s->faceList[g][i].plane.a * s->lp.x +
								s->faceList[g][i].plane.b * s->lp.y +
								s->faceList[g][i].plane.c * s->lp.z +
								s->faceList[g][i].plane.d * s->lp.w;
			
			if (side > 0) s->faceList[g][i].visible = true;
			else s->faceList[g][i].visible = false;
		}
	}
}

void VolumeShadows::shadows2(SmartPointer<IModel> m, ShadowInstance* s) {
	glBegin(GL_TRIANGLES);
		// FINISH Obtain the number of groups
		const int nGroups = static_cast<int> (m->groupList.size());
		for(int g = 0; g < nGroups; ++g) {
		
			// FINISH Obtain the number of material indexes ...
			const int nMatIndex = static_cast<int> (m->groupList[g].matIndex.size());
			for (int i = 0; i < nMatIndex - 1; ++i) {
	
				// TODO: agregar otra condicion para cuando los objetos traslucidos se despliguen
				// como solidos, entoces generen sombras, simplemente utilizar el bool de scene
				if (m->groupList[g].mat[i]->alpha < 1.0) continue;

				// For each face
				for(int f = m->groupList[g].matIndex[i]; f < m->groupList[g].matIndex[i + 1]; ++f) {

					if (!s->faceList[g][f].visible) continue;
					for (int j = 0; j < 3; ++j) {

						int k = s->faceList[g][f].neigh[j];
						if ((!k) || (!s->faceList[g][k-1].visible)) {		// TODO: check if k - 1 is correct
							// Here we have an edge, we must draw a polygon
							unsigned int p1 = m->groupList[g].faceList[f].vertex.values[j];
							unsigned int p2 = m->groupList[g].faceList[f].vertex.values[(j + 1) % 3];

							// TODO: creo que el 100 representa el infinito ..
							// Calculate the length of the vector
							const Vec3f lp(s->lp.x, s->lp.y, s->lp.z);
							const Vec3f v1 = (m->groupList[g].vertexList[p1] - lp) * 100;
							const Vec3f v2 = (m->groupList[g].vertexList[p2] - lp) * 100;

							// NOTE; en teoria asi es mas chola por que no se habilita
							// a cada rato el GL_TRIANGLE_STRIP
							glVertex3f(m->groupList[g].vertexList[p1]);
							glVertex3f(m->groupList[g].vertexList[p1] + v1);
							glVertex3f(m->groupList[g].vertexList[p2]);

							glVertex3f(m->groupList[g].vertexList[p2]);
							glVertex3f(m->groupList[g].vertexList[p1] + v1);
							glVertex3f(m->groupList[g].vertexList[p2] + v2);						
						}
					}

				}
			}
		}
	glEnd();
}

void VolumeShadows::shadows3(SmartPointer<IModel> m, ShadowInstance* s) {
	glBegin(GL_TRIANGLES);
		// FINISH Obtain the number of groups
		const int nGroups = static_cast<int> (m->groupList.size());
		for(int g = 0; g < nGroups; ++g) {
		
			// FINISH Obtain the number of material indexes ...
			const int nMatIndex = static_cast<int> (m->groupList[g].matIndex.size());
			for (int i = 0; i < nMatIndex - 1; ++i) {
	
				// TODO: agregar otra condicion para cuando los objetos traslucidos se despliguen
				// como solidos, entoces generen sombras, simplemente utilizar el bool de scene
				//if (m->groupList[g].mat[i].alpha < 1.0) continue;
				if (m->groupList[g].mat[i]->alpha < 1.0) continue;

				// For each face
				for(int f = m->groupList[g].matIndex[i]; f < m->groupList[g].matIndex[i + 1]; ++f) {

					if (!s->faceList[g][f].visible) continue;
					for (int j = 0; j < 3; ++j) {

						int k = s->faceList[g][f].neigh[j];
						if ((!k) || (!s->faceList[g][k-1].visible)) {		// TODO: check if k - 1 is correct
							// Here we have an edge, we must draw a polygon
							unsigned int p1 = m->groupList[g].faceList[f].vertex.values[j];
							unsigned int p2 = m->groupList[g].faceList[f].vertex.values[(j + 1) % 3];

							// TODO: creo que el 100 representa el infinito ..
							// Calculate the length of the vector
							const Vec3f lp(s->lp.x, s->lp.y, s->lp.z);
							const Vec3f v1 = (m->groupList[g].vertexList[p1] - lp) * 100;
							const Vec3f v2 = (m->groupList[g].vertexList[p2] - lp) * 100;

							// NOTE; en teoria asi es mas chola por que no se habilita a
							// cada rato el GL_TRIANGLE_STRIP
							glVertex3f(m->groupList[g].vertexList[p1]);
							glVertex3f(m->groupList[g].vertexList[p1] + v1);
							glVertex3f(m->groupList[g].vertexList[p2]);
							
							glVertex3f(m->groupList[g].vertexList[p2]);
							glVertex3f(m->groupList[g].vertexList[p1] + v1);
							glVertex3f(m->groupList[g].vertexList[p2] + v2);
						}
					}

				}
			}
		}
	glEnd();
}

// Check if is necesary calculate the planes and check conectivity for a geometry
void VolumeShadows::checkGeometry(SmartPointer<LightNode> l, SmartPointer<GeometryNode> g) {
  std::pair<SmartPointer<LightNode>, SmartPointer<GeometryNode>> lg(l, g);
  if (instances.find(lg) != instances.end()) {
    // Check the prev modelView to know if is necessary recalculate the shadows.
    if (instances[lg].prevModelView != g->model_view()) {
      instances[lg].prevModelView = g->model_view();
      instances[lg].calculateShadow = true;
    }
	if (instances[lg].prevLightPosition != l->position()) {
      instances[lg].prevLightPosition = l->position();
	  instances[lg].calculateShadow = true;
	}
	// Update the lp ...
	if (instances[lg].calculateShadow) {
		instances[lg].lp = math::transpose(math::inverse(g->model_view())) * l->position();
	}
	return;
  }

  // Check if exist a geometry with the same model, to get the
  std::map<std::pair<SmartPointer<LightNode>, SmartPointer<GeometryNode>>, ShadowInstance>::iterator it = instances.begin();
  std::map<std::pair<SmartPointer<LightNode>, SmartPointer<GeometryNode>>, ShadowInstance>::iterator end = instances.end();
  for(; it != end; ++it) {
    if (it->first.second->model() == g->model()) {	instances[lg] = it->second; return;	}
  }

  // Calculate the planes and conectivity for the model...
  instances[lg] = ShadowInstance(g->model());	

  // Try to load the ady planes from a file.
  const std::tstring vShadowPath = GetAPathFromOne(g->model()->path, _T("tmp/") + GetBaseFileName(g->model()->path) + _T(".vshadow"));
  if (!instances[lg].load(vShadowPath)) {
    // Calculate the ...
    for (int j = 0; j < (int)g->model()->groupList.size(); ++j) {	
      calculatePlanes(g->model()->groupList[j], instances[lg].faceList[j]);
      SetConnectivity(g->model()->groupList[j], instances[lg].faceList[j]);
    }

    // Save as a tmp file..
    instances[lg].save(vShadowPath);
  }
}

END_TEG_NAMESPACE();
