#include <stdio.h>
#include "../headers/Settings.h"
#include "../headers/GeMeshRenderer.h"


GeMeshRenderer :: GeMeshRenderer(GeMeshRenderer* meshRenderer) {
	geometry = meshRenderer->geometry;
	geometry->refcount ++;
	
	shadowGeometry.verticesExtrude = new GLdouble*[8];
	shadowGeometry.triLightFacing = new bool*[8];
	for(int i=0;i<8;i++) {
		shadowGeometry.verticesExtrude[i] = NULL;
		shadowGeometry.triLightFacing[i] = 0;
	}
	shadowGeometry.silhouetteEdges = new std::list<GeEdge>[8];
	shadowGeometry.silhouetteUpToDate = 0;
}

GeMeshRenderer :: GeMeshRenderer(GeGeometry* geometry) {
	GLuint subdata_size_per_element = sizeof(GLdouble)*geometry->vertexcount;
	GLuint subdata_size = subdata_size_per_element*3;

    this->geometry = geometry;
	geometry->refcount = 1;
	geometry->vbo_subdata_size = subdata_size;
	geometry->display_list_id = glGenLists(1);
	geometry->display_list_mode = (GLubyte)-1;
	
	geometry->normalsPoint = new GLdouble[geometry->polygon_vertex_count*2];
	shadowGeometry.verticesExtrude = new GLdouble*[8];
	shadowGeometry.triLightFacing = new bool*[8];
	for(int i=0;i<8;i++) {
		shadowGeometry.verticesExtrude[i] = NULL;
		shadowGeometry.triLightFacing[i] = 0;
	}
	shadowGeometry.silhouetteEdges = new std::list<GeEdge>[8];
	shadowGeometry.silhouetteUpToDate = 0;

	glGenBuffersARB(1, &this->geometry->vboid);
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, geometry->vboid);
	
	if (geometry->texcoord != NULL) {
		// reserve memory for vertices+normals+texture coordinates
		glBufferDataARB(GL_ARRAY_BUFFER_ARB, subdata_size_per_element*8, 0, GL_STREAM_DRAW_ARB);
		// write vertices
		glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, 0, subdata_size, geometry->vertices);
		// write normals
		glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, subdata_size, subdata_size, geometry->normals);
		// write texture coordinates
		glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, subdata_size*2, subdata_size_per_element*2, geometry->texcoord);
	}
	else {
		// reserve memory for vertices+normals
		glBufferDataARB(GL_ARRAY_BUFFER_ARB, subdata_size*2, 0, GL_STREAM_DRAW_ARB);
		// write vertices
		glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, 0, subdata_size, geometry->vertices);
		// write normals
		glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, subdata_size, subdata_size, geometry->normals);
	}
	
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
}

GeMeshRenderer :: ~GeMeshRenderer(void) {
	geometry->refcount --;
	if (geometry->refcount <= 0) {
		glDeleteBuffersARB(1, &geometry->vboid);
		glDeleteLists(geometry->display_list_id, 1);
		delete(geometry->vertices);
		delete(geometry->normals);
		if (geometry->texcoord != NULL) delete(geometry->texcoord);
		delete(geometry);
	}
	for(int i=0;i<8;i++) {
		if(shadowGeometry.verticesExtrude[i] != 0)
			delete[] shadowGeometry.verticesExtrude[i];
		if(shadowGeometry.triLightFacing[i] != 0)
			delete[] shadowGeometry.triLightFacing[i];
	}
	delete[] shadowGeometry.verticesExtrude;
	delete[] shadowGeometry.triLightFacing;
	delete[] shadowGeometry.silhouetteEdges;
}

void GeMeshRenderer :: render(void) {
	switch (settings.renderMode) {
		case RENDER_VERTEX_ARRAY:
			//glMultMatrixd(geometry->transformation);
			renderMeshVertexArray();
		break;

		case RENDER_VBO:
			//glMultMatrixd(geometry->transformation);
			renderMeshVBO();
		break;

		default:
		case RENDER_IMMEDIATE:
			if ((settings.flags & FLAG_USE_DISPLAY_LISTS) != 0) {
				if (geometry->display_list_mode != RENDER_IMMEDIATE) {
					glNewList(geometry->display_list_id, GL_COMPILE);
					//glMultMatrixd(geometry->transformation);
					renderMeshImmediate();
					glEndList();
					geometry->display_list_mode = settings.renderMode;
				}
				glCallList(geometry->display_list_id);
			}
			else {
				//glMultMatrixd(geometry->transformation);
				renderMeshImmediate();
			}
		break;
	}
}
	
void GeMeshRenderer :: renderMeshImmediate(void) {
	GLuint i;
	/*
	computeSilhouetteEdges(NxVec3(5.0f, 5.0f, 5.0f));
	extrudeSilhouette(NxVec3(5.0f, 5.0f, 5.0f));
	//printf("bla: %d\n", geometry->silhouetteEdges->size());
	
	glLineWidth(10.0);

	glBegin(GL_LINES);
		std::list<GeEdge>::iterator iter = geometry->silhouetteEdges.begin(); 

		for(int i=0; iter != geometry->silhouetteEdges.end();iter++, i+=6)
		{
			glVertex3dv(&(geometry->vertices[iter->vertexIndex[0]*3]));
			glVertex3dv(&(geometry->vertices[iter->vertexIndex[1]*3]));
		}
	glEnd();
	*/	
	/*
	glDisable(GL_LIGHTING);
	glColor3f(1.0f, 1.0f, 1.0f);
	//glLineWidth(1.0);
	for(int i=0;i<geometry->polygon_vertex_count/3;i++)
	{
		glBegin(GL_LINES);
		glVertex3dv(&(geometry->normalsPoint[i*6]));
		glVertex3dv(&(geometry->normalsPoint[i*6+3]));
		glEnd();
	}
	glEnable(GL_LIGHTING);
	*/
	// draw normals
	
	/*glBegin(GL_LINES);
	
		for (i = 0; i < geometry->polygon_vertex_count; i++) {			
			glVertex3d(geometry->vertices[i*3], geometry->vertices[i*3+1], geometry->vertices[i*3+2]);			
			glVertex3d(geometry->vertices[i*3]+geometry->normals[i*3]/3.0f, 
					   geometry->vertices[i*3+1]+geometry->normals[i*3+1]/3.0f, 
					   geometry->vertices[i*3+2]+geometry->normals[i*3+2]/3.0f);
		}
	glEnd();
	*/

	glBegin(GL_TRIANGLES);
	if (geometry->texcoord != NULL) {
		for (i = 0; i < geometry->polygon_vertex_count; i++) {
			/*
			if((shadowGeometry.triLightFacing != NULL) && (shadowGeometry.triLightFacing[0] != NULL)) {
				if (shadowGeometry.triLightFacing[0][i/3])
					glColor3f(1.0f, 0.0f, 0.0f);
				else
					glColor3f(0.0f, 1.0f, 0.0f);
			}
			else
				glColor3f(0.0f, 0.0f, 1.0f);
				*/
			glTexCoord2dv(&(geometry->texcoord[i*2]));
			glNormal3dv(&(geometry->normals[i*3]));
			glVertex3dv(&(geometry->vertices[i*3]));
		}
	}
	else {
		for (i = 0; i < geometry->polygon_vertex_count; i++) {
			/*
			if((shadowGeometry.triLightFacing != NULL) && (shadowGeometry.triLightFacing[0] != NULL)) {
				if (shadowGeometry.triLightFacing[0][i/3])
					glColor3f(1.0f, 0.0f, 0.0f);
				else
					glColor3f(0.0f, 1.0f, 0.0f);
			}
			else
				glColor3f(0.0f, 0.0f, 1.0f);
				*/
			glNormal3dv(&(geometry->normals[i*3]));
			glVertex3dv(&(geometry->vertices[i*3]));
		}
	}
	glEnd();
}

void GeMeshRenderer :: renderMeshVertexArray(void) {
	/* init */
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
	if (geometry->texcoord != NULL) {
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(2, GL_DOUBLE, 0, geometry->texcoord);
	}
    glVertexPointer(3, GL_DOUBLE, 0, geometry->vertices);
    glNormalPointer(GL_DOUBLE, 0, geometry->normals);

	/* actually draw the triangles */
	glDrawArrays (GL_TRIANGLES, 0, geometry->polygon_vertex_count);
	
	/* finish */
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
	if (geometry->texcoord != NULL) {
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}
}

void GeMeshRenderer :: renderMeshVBO(void) {
	/* init */
	// bind VBOs for vertices, normals and texture coordinates
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, geometry->vboid);
	
	glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
	if (geometry->texcoord != NULL) {
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(2, GL_DOUBLE, 0, (void*)(geometry->vbo_subdata_size*2));
	}
    glVertexPointer(3, GL_DOUBLE, 0, 0);
    glNormalPointer(GL_DOUBLE, 0, (void*)geometry->vbo_subdata_size);

	/* actually draw the triangles */
	glDrawArrays (GL_TRIANGLES, 0, geometry->polygon_vertex_count);
	//glDrawElements(GL_TRIANGLES, geometry->polygon_vertex_count, GL_UNSIGNED_INT, geometry->triangles);
	
	/* finish */
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
	if (geometry->texcoord != NULL) {
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	// bind with 0, so, switch back to normal pointer operation
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
}

void GeMeshRenderer :: shadowPass(GeLight* light, float* globalPoseMat) {
		//zPassShadow(light, globalPoseMat);

		zFailShadow(light, globalPoseMat);
}

void GeMeshRenderer :: zPassShadow(GeLight* light, float* globalPoseMat) {
	glCullFace(GL_FRONT);
	glStencilOp(GL_KEEP, GL_KEEP, GL_INCR_WRAP_EXT);
	drawShadowVolume(light, globalPoseMat, false, false);

	glCullFace(GL_BACK);
	glStencilOp(GL_KEEP, GL_KEEP, GL_DECR_WRAP_EXT);
	drawShadowVolume(light, globalPoseMat, false, false);
}

void GeMeshRenderer :: zFailShadow(GeLight* light, float* globalPoseMat) {	
	glCullFace(GL_BACK);
	glStencilOp(GL_KEEP, GL_INCR_WRAP_EXT, GL_KEEP);
	drawShadowVolume(light, globalPoseMat, true, true);

	glCullFace(GL_FRONT);
	glStencilOp(GL_KEEP, GL_DECR_WRAP_EXT, GL_KEEP);
	drawShadowVolume(light, globalPoseMat, true, true);
		
}

void GeMeshRenderer :: drawShadowVolume(GeLight* light, float* globalPoseMat, bool bdrawLightCap, bool bdrawDarkCap) {
	glPushMatrix();

	glMultMatrixf(globalPoseMat);

	NxVec3 lightPos(light->pos.x, light->pos.y, light->pos.z);	
	NxMat34 nxGlobalPoseMat, invGlobalPoseMat;

	nxGlobalPoseMat.setColumnMajor44((NxF32*)globalPoseMat);
	nxGlobalPoseMat.getInverse(invGlobalPoseMat);			
	
	// transform lightPos from world coordinate system to model coordinate system
	lightPos = invGlobalPoseMat * lightPos;
	unsigned char lightbit = 1 << light->getIndex();
	if((shadowGeometry.silhouetteUpToDate & lightbit) == 0) {
		computeSilhouetteEdges(lightPos, light->getIndex());
		extrudeSilhouette(lightPos, light->getIndex());
		shadowGeometry.silhouetteUpToDate |= lightbit;
	}
	drawSides(light->getIndex());
	if(bdrawLightCap)
		drawLightCap(light->getIndex());

	if(bdrawDarkCap)
		drawDarkCap(light->getIndex());

	glPopMatrix();
}

void GeMeshRenderer :: drawSides(int lightIndex)
{
	std::list<GeEdge>::iterator iter;

	//glMultMatrixd(geometry->transformation);
	/*
	glLineWidth(10.0);
	
	glBegin(GL_LINES);
		iter = geometry->silhouetteEdges.begin(); 

		for(int i=0; iter != geometry->silhouetteEdges.end();iter++, i+=6)
		{
			glVertex3dv(&(geometry->vertices[iter->vertexIndex[0]*3]));
			glVertex3dv(&(geometry->vertices[iter->vertexIndex[1]*3]));
		}
	glEnd();
	glLineWidth(1.0);
	
	glPointSize(50.0f);
	glBegin(GL_POINTS);
		glVertex3d(lightPos.x, lightPos.y, lightPos.z);	
	glEnd();
	*/
	glBegin(GL_QUADS);
		iter = shadowGeometry.silhouetteEdges[lightIndex].begin(); 

		for(int i=0;iter != shadowGeometry.silhouetteEdges[lightIndex].end();iter++, i+=8)
		{
			glVertex3dv(&(geometry->vertices[iter->vertexIndex[0]*3]));
			glVertex3dv(&(geometry->vertices[iter->vertexIndex[1]*3]));
			glVertex4dv(&(shadowGeometry.verticesExtrude[lightIndex][i+4]));
			glVertex4dv(&(shadowGeometry.verticesExtrude[lightIndex][i]));
		}
	
	glEnd();
}

void GeMeshRenderer :: computeSilhouetteEdges(NxVec3 lightPos, int lightIndex)
{
	shadowGeometry.silhouetteEdges[lightIndex].clear();
	
	int triCount = geometry->polygon_vertex_count/3;

	if(shadowGeometry.triLightFacing[lightIndex] != 0)
		delete[] shadowGeometry.triLightFacing[lightIndex];
	shadowGeometry.triLightFacing[lightIndex] = new bool[triCount];
	int v1, v2, v3;
	NxVec3 triVec1, triVec2, normal, triPoint1, triPoint2, triPoint3;
	
	//printf("triCount: %d\n", triCount);
	for(int i=0;i<triCount;i++)
	{
		// Counter Clockwise Oriented
		v1 = i*9;
		v2 = i*9+3;
		v3 = i*9+6;
		
		triVec1.x = geometry->vertices[v2] - geometry->vertices[v1];
		triVec1.y = geometry->vertices[v2+1] - geometry->vertices[v1+1];
		triVec1.z = geometry->vertices[v2+2] - geometry->vertices[v1+2];

		triVec2.x = geometry->vertices[v3] - geometry->vertices[v1];
		triVec2.y = geometry->vertices[v3+1] - geometry->vertices[v1+1];
		triVec2.z = geometry->vertices[v3+2] - geometry->vertices[v1+2];
		
		//triPoint1.set(geometry->vertices[v1]);
		normal = triVec1.cross(triVec2);		
		
		triPoint1.set(geometry->vertices[v1]);
		triPoint2.set(geometry->vertices[v2]);
		triPoint3.set(geometry->vertices[v3]);

		//shadowGeometry.triLightFacing[lightIndex][i] = (normal.dot(triPoint1 - lightPos) > 0.0f);
		shadowGeometry.triLightFacing[lightIndex][i] = true;
		if(normal.dot(triPoint1 - lightPos) <= 0.0f)
			if(normal.dot(triPoint2 - lightPos) <= 0.0f)
				if(normal.dot(triPoint3 - lightPos) <= 0.0f)
					shadowGeometry.triLightFacing[lightIndex][i] = false;
		/*
		normal.normalize();
		// Calc normal point pairs
		geometry->normalsPoint[6*i] = (geometry->vertices[v1] + geometry->vertices[v2] + geometry->vertices[v3])/3;
		geometry->normalsPoint[6*i+1] = (geometry->vertices[v1+1] + geometry->vertices[v2+1] + geometry->vertices[v3+1])/3;
		geometry->normalsPoint[6*i+2] = (geometry->vertices[v1+2] + geometry->vertices[v2+2] + geometry->vertices[v3+2])/3;

		geometry->normalsPoint[6*i+3] = geometry->normalsPoint[6*i] + normal.x/5;
		geometry->normalsPoint[6*i+4] = geometry->normalsPoint[6*i+1] + normal.y/5;
		geometry->normalsPoint[6*i+5] = geometry->normalsPoint[6*i+2] + normal.z/5;
		*/
	}
	
	for(GLuint i=0;i<geometry->edgeCount;i++)
	{
		GeEdge e = geometry->edges[i];
		if(shadowGeometry.triLightFacing[lightIndex][e.triIndex[0]] != shadowGeometry.triLightFacing[lightIndex][e.triIndex[1]])
		{
			if(shadowGeometry.triLightFacing[lightIndex][e.triIndex[0]])
			{
				shadowGeometry.silhouetteEdges[lightIndex].push_back(e);	
			} else
			{
				shadowGeometry.silhouetteEdges[lightIndex].push_back(e.reverseIndices());
			}
		}
	}
}

void GeMeshRenderer :: extrudeSilhouette(NxVec3 lightPos, int lightIndex) {

	int n = shadowGeometry.silhouetteEdges[lightIndex].size();
	if(shadowGeometry.verticesExtrude[lightIndex] != 0)
		delete[] shadowGeometry.verticesExtrude[lightIndex];
	shadowGeometry.verticesExtrude[lightIndex] = new GLdouble[8*n];
	
	NxVec3 v1, v2, v1Ex, v2Ex;

	std::list<GeEdge>::iterator iter = shadowGeometry.silhouetteEdges[lightIndex].begin(); 	
	
	for(int i=0;iter != shadowGeometry.silhouetteEdges[lightIndex].end();iter++, i+=8)
	{
		v1.set(&geometry->vertices[iter->vertexIndex[0]*3]);
		v2.set(&geometry->vertices[iter->vertexIndex[1]*3]);
		v1Ex = v1-lightPos; 
		v2Ex = v2-lightPos;
		v1Ex.normalize();
		v2Ex.normalize();
		v1Ex = v1 + v1Ex*300;
		v2Ex = v2 + v2Ex*300;

		shadowGeometry.verticesExtrude[lightIndex][i]   = v1Ex.x;
		shadowGeometry.verticesExtrude[lightIndex][i+1] = v1Ex.y;
		shadowGeometry.verticesExtrude[lightIndex][i+2] = v1Ex.z;
		shadowGeometry.verticesExtrude[lightIndex][i+3] = 1;

		shadowGeometry.verticesExtrude[lightIndex][i+4] = v2Ex.x;
		shadowGeometry.verticesExtrude[lightIndex][i+5] = v2Ex.y;
		shadowGeometry.verticesExtrude[lightIndex][i+6] = v2Ex.z;
		shadowGeometry.verticesExtrude[lightIndex][i+7] = 1;
	}
}

void GeMeshRenderer :: drawLightCap(int lightIndex) {
	int triCount = geometry->polygon_vertex_count/3;

	glBegin(GL_TRIANGLES);
	for(int i=0;i<triCount;i++) {
		if(!shadowGeometry.triLightFacing[lightIndex][i]) {		
			glVertex3dv(&geometry->vertices[i*9]);
			glVertex3dv(&geometry->vertices[i*9+3]);
			glVertex3dv(&geometry->vertices[i*9+6]);
		}
	}
	glEnd();
}

void GeMeshRenderer :: drawDarkCap(int lightIndex) {
	
	glBegin(GL_TRIANGLES);
	for(int i=8;i < shadowGeometry.silhouetteEdges[lightIndex].size()*8;i+=8)
	{
		glVertex3dv(&shadowGeometry.verticesExtrude[lightIndex][0]);
		glVertex3dv(&shadowGeometry.verticesExtrude[lightIndex][i]);
		glVertex3dv(&shadowGeometry.verticesExtrude[lightIndex][i+4]);
	}
	glEnd();
}