/*
John Quarles's Code Samples, Vol I:
3/7/11: Blending, Depth Buffering, Culling, Texture Mapping, Simple 1st person Camera

*/
#include <stdlib.h>
#include "glm.h"
#include "glut.h"
#include <stdarg.h>
#include <iostream>
#include <memory.h>
#include <time.h>
#include <fstream>
using namespace std;

GLMmodel *model1,*model2,*model3;
GLuint liliTexName;
// Default Image dimensions
int imageWidth = 640;
int imageHeight = 480;


//OpenGL Calls
void setupGL();
void CheckGLError();

//Callbacks
void Display();
void Idle();
void Reshape(int w, int h);
void Keyboard (unsigned char key, int , int );
void MouseButton(int button, int state, int x, int y);
void PassiveMouseMotion(int x, int y);

//font
GLvoid *font_style = GLUT_BITMAP_HELVETICA_12;

// a cleanup function
void quit(int i = 0);

void main(int argc, char **argv)
{
	// INITIALIZE THE GLUT WINDOW
	glutInit(&argc, argv);  
	glutInitWindowSize(imageWidth, imageHeight);
	glutInitDisplayString("rgb double");
	glutInitWindowPosition(0, 0);
	glutCreateWindow("Project #1");

	//SETUP GLUT CALLBACKS
	cout << "Setting up callbacks... ";
	glutDisplayFunc(Display);
	glutKeyboardFunc(Keyboard);
	glutMouseFunc(MouseButton);
	//glutMotionFunc(MouseMotion);
	glutReshapeFunc(Reshape);
	glutIdleFunc(Idle);
	glutPassiveMotionFunc(PassiveMouseMotion);
	cout << "[completed]\n";
	

	//SETUP MISC GL
	setupGL();

	CheckGLError();
	model1= glmReadOBJ("data/cube.obj");
	glmFacetNormals(model1);
	model2= glmReadOBJ("data/sphere.obj");
	model3= glmReadOBJ("data/android-3d1.obj");
	glEnable(GL_LIGHTING);
		glEnable(GL_NORMALIZE);
		glShadeModel(GL_FLAT);
	glEnable(GL_LIGHT0);	 

//LOAD A TEXTURE WITH GLM (similar with DevIL in assignment 3 template)*************************************
CBmp lili =  CBmp("./data/lili.bmp");
if(lili.init())
			{
				
				glGenTextures(1,&liliTexName);
				glBindTexture(GL_TEXTURE_2D, liliTexName);
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
				glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,lili.width,lili.height,0,GL_RGB,GL_UNSIGNED_BYTE,lili.m_pDibBuffer);
			}
	glutMainLoop();
}



// This function checks the state of openGL and prints 
// an error if an error has occurred
void CheckGLError()
{
  	GLenum error;
  	error = glGetError();
  	if (error!=GL_NO_ERROR)
  	{
		cout << "OpenGL reports an error: "<< gluErrorString(error) << endl;
		quit(1);
  	}
}

//drawstr method from Nate Robbins tutorials, 1997
void 
drawstr(GLuint x, GLuint y, char* format, ...)
{
	
    va_list args;
    char buffer[255], *s;
    
    va_start(args, format);
    vsprintf(buffer, format, args);
    va_end(args);
    
    glRasterPos2i(x, y);
    for (s = buffer; *s; s++)
        glutBitmapCharacter(font_style, *s);
}


// This function draws the scene
float angle=0;
float zpos =0;
float eyex,eyey,eyez, lookdirx,lookdiry =0;
float lookdirz =-1;
float sidediry, sidedirz = 0;
float sidedirx =1;
GLfloat viewMatrix[16];
void Display()
{	
	glEnable(GL_LIGHTING);
	glEnable(GL_DEPTH_TEST);	
	if(angle>360)
		angle=0;
	angle+=.1;

	// clear the screen
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glMatrixMode(GL_MODELVIEW);

	glLoadIdentity();
//SIMPLE FIRST PERSON CAMERA**********************************************************************************
// There are a lot of ways to make this camera better. This one is based on vector manipulation only and it works!
//see PassiveMouseMotion() and Keyboard() also
	gluLookAt(eyex,eyey,eyez,eyex+lookdirx,eyey+lookdiry,eyez+lookdirz,0,1,0);
	glGetFloatv(GL_MODELVIEW_MATRIX, viewMatrix);
	sidedirx=viewMatrix[0];
	sidediry=viewMatrix[4];
	sidedirz=viewMatrix[8];
	
	glPushMatrix();
	GLfloat m[16] ;
	glGetFloatv(GL_MODELVIEW_MATRIX,m);
	glTranslatef(-2,0,0);
	glutSolidCube(1);
	glPopMatrix();
//CULLING***************************************************************
	//glEnable(GL_CULL_FACE);
	//You can specify to cull front or back by using the following ( default is GL_BACK):
	//glCullFace(GL_FRONT /* or GL_FRONT or even GL_FRONT_AND_BACK */);
	// the winding order of the polygon. The default is counter clockwise, but you can change that using:
	//glFrontFace(GL_CCW /* or GL_CW */);
	/*glBegin(GL_TRIANGLES);
		glVertex3f(0,1,0);		
		glVertex3f(1,1,0);
		glVertex3f(1,0,0);
		
	glEnd();
*/

// TEXTURE A QUAD**************************************************************************
glPushMatrix();
	glTranslatef(4,0,0);
	glEnable(GL_TEXTURE_2D);

	//change the GL_MODULATE parameter for diffent effects. Look in the redbood for examples
	glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
	glBindTexture(GL_TEXTURE_2D, liliTexName);
	GLfloat quad_mat[] =  {1,1,1,1};
	glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE, quad_mat);
	glBegin(GL_QUADS);
	glTexCoord2f(0,0);
	glVertex3f(0,0,0);

	glTexCoord2f(0,1);
	glVertex3f(1,0,0);

	glTexCoord2f(1,1);
	glVertex3f(1,1,0);

	glTexCoord2f(1,0);
	glVertex3f(0,1,0);

	glEnd();
glPopMatrix();
glDisable(GL_TEXTURE_2D);



	GLfloat light_position[] = { 0.0, 10.0, 10.0, 1.0 };
	GLfloat light_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
	GLfloat light_specular[] = { 0.1, 0.1, 0.1, 1.0 };
	GLfloat light_diffuse[] = { 1, 1, 1, 1.0 };
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
	glShadeModel(GL_SMOOTH);

	
	//DEPTH TESTING***************************************************************
glPushMatrix();
	glTranslatef(0,0,zpos);
	glRotatef(angle,0,1,0);

	//change the way the z-buffer algorithm does depth tests. GL_LESS by default
	//GL_LESS. GL_GREATER, GL_EQUAL, GL_GEQUAL, GL_LEQUAL, GL_NEVER, GL_ALWAYS
	glDepthFunc(GL_LESS);
	GLfloat red_mat[] =  {1,0,0,1};
	GLfloat blue_mat[] =  {0,0,1,1};
	//draw a cube an a sphere to be overlapping
	glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE, red_mat);
	glutSolidCube(1.99999);
	glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE, blue_mat);
	//glTranslatef(0,0,-.01);
	//glutSolidCube(1.9999999999999999999999);
	//glDepthFunc(GL_GREATER);
	//glutSolidSphere(1.4,50,50);
glPopMatrix();
/*
//BLENDING***************************************************************	
	glEnable(GL_BLEND);
	//change the arguments for other effects
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	//note the 4th coordinate - alpha
	GLfloat green_mat_a[] =  {0,1,0,.6};
	glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE, green_mat_a);
	glutSolidSphere(1.4,50,50);

	glDisable(GL_BLEND);

	
*/



//DRAW TEXT TO SCREEN INTUITIVELY************************************************ 
	glLoadIdentity();
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	gluOrtho2D(0,640,480,0);
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_LIGHTING);
	glColor3f(1,1,1);
	drawstr(5, 10, "z = %.1f", zpos);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	
	
	/*
//GET AT VERTICES IN GLM******************************************************************
	int face0vert0index = model1->triangles[0].vindices[0]; // a triangle w/ 3 vertices
	float vertex0coord0 = model1->vertices[3 * model3->triangles[0].vindices[0]]; // vertex 0
	float vertex0coord1 = model1->vertices[3 * model3->triangles[0].vindices[1]]; // vertex 1
	float vertex0coord2 = model1->vertices[3 * model3->triangles[0].vindices[2]]; // vertex 2
	
	float normalforvertex0coord0 = model1->normals[3 * model3->triangles[0].nindices[0]]; // the normal of vertex 0
	// transformations are typically not defined within the obj model file, so you probably dont have to worry about that
	*/	
	
	// swap the buffers
	glutSwapBuffers();
	
}


// set up GL stuff
void setupGL() {

	// set the clear color
	glClearColor(0.5, 0.5, 0.5, 1.0);  // background color	
}


// This function is continuously called when events are not being received
// by the window.  This is a good place to update the state of your objects 
// after every frame.
void Idle() {
	glutPostRedisplay();

}

// keyboard handler
float speed = .1;
void Keyboard (unsigned char key, int , int )
{
	switch(key)
    {
		// if q, exit
		case 'q':
			quit(1);
			break;
		case 'w': //move forward
			eyex+=lookdirx*speed;
			eyez+=lookdirz*speed;
			break;
		case 's'://move backward
			eyex-=lookdirx*speed;
			eyez-=lookdirz*speed;
			break;
		case 'a':
			 break;
		case 'd':
			 break;
	}
}

void MouseButton(int button, int state, int x, int y)
{
	switch (button) 
	{
		case GLUT_LEFT_BUTTON:
			zpos+=1; // moves some of the geometry
		    break;
		case GLUT_MIDDLE_BUTTON:
		    break;
		case GLUT_RIGHT_BUTTON:
			zpos-=1;// moves some of the geometry
		    break;
	}
}

float turnSpeed = .01; // how fast the mouse rotates the camera
/*This implements a 1st person mouse look using only vectors. Think about better ways to do this...	
We make the default mouse position to be the center of the window. You could put it other places, but this is the safest place. Why? */
void PassiveMouseMotion(int x, int y)
{	

	//did we move the mouse at all?
	int deltaX = x - imageWidth/2;
	int deltaY = y - imageHeight/2;

	
	if( deltaX == 0 && deltaY == 0 ) return; //if we didnt move the mouse, dont do anything
	
		// update the look direction based on the side direction and which way we moved the mouse
		lookdirx+= sidedirx*deltaX*turnSpeed; 
		lookdirz+= sidedirz*deltaX*turnSpeed;
		float lookup = lookdiry- deltaY*turnSpeed;
		if( lookup <1.9 && lookup >-1.9)// What happens if we get rid of this check? Why? Go look at gluLookat in display...
			lookdiry=lookup;		
		glutWarpPointer(imageWidth/2,imageHeight/2); // put the cursor back to the center of the screen
	
	
}

// This functions handles what happens when the window is reshaped
void Reshape(int w, int h) 
{
	imageWidth = w;
	imageHeight = h;
	glViewport(0,0,w,h);
	double aspect = ((double)w) / ((double)h);
	glMatrixMode(GL_PROJECTION);
   	glLoadIdentity();
	gluPerspective(60,aspect,.01, 1000);
	//glOrtho(-2,2,-2,2,1,100);
   
}

// a cleanup function.  call this when you want to exit.
void quit(int i) 
{
	exit(i);
}