/***************************************************************************/
/* This is a simple demo program written for CSE 167 by Ravi Ramamoorthi   */
/* This program corresponds to the first OpenGL lecture.                   */
/*                                                                         */
/* Successive lectures/iterations make this program more complex.          */
/* This is the first simple program to draw a ground plane allowing zooming*/  
/* The intent is to show how to draw a simple scene.                       */
/* Modified in August 2016 by Hoang Tran to exclusively use modern OpenGL  */
/***************************************************************************/


// I start with the modified first opengl program to do shaders
// I am going to add cubes and teapots to my scene.

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
// OSX requires its own headers.
#ifdef __APPLE__
#include <OpenGL/gl3.h>
#include <OpenGL/glext.h>
#include <GLUT/glut.h>
#else
#include <GL/glew.h>
#include <GL/glut.h>
#endif
// Usage of degrees is deprecated. Use radians for glm functions.
#define GLM_FORCE_RADIANS
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "shaders.h"
#include "geometry.h"
#include <fstream>
#include <sstream>
#include <iostream>


int mouseoldx, mouseoldy ; // For mouse motion
GLfloat eyeloc = 2.0 ; // Where to look from; initially 0 -2, 2
GLfloat teapotloc = -0.5 ; // ** NEW ** where the teapot is located
GLint animate = 0 ; // ** NEW ** whether to animate or not
GLuint vertexshader, fragmentshader, shaderprogram ; // shaders
GLuint projectionPos, modelviewPos; // Locations of uniform variables
glm::mat4 projection, modelview; // The mvp matrices themselves
glm::mat4 identity(1.0f); // An identity matrix used for making transformation matrices

const int DEMO = 4  ; // ** NEW ** To turn on and off features

// Treat this as a destructor function. Delete any dynamically allocated memory here
void deleteBuffers() {
glDeleteVertexArrays(numobjects + ncolors, VAOs);
glDeleteVertexArrays(1, &teapotVAO);
glDeleteBuffers(numperobj*numobjects + ncolors, buffers);
glDeleteBuffers(3, teapotbuffers);
}

void display(void)
{
	// clear all pixels  
	// If (DEMO >= 2) also clear the depth buffer 

	if (DEMO >= 2) glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) ; 
	else glClear (GL_COLOR_BUFFER_BIT);

	// draw white polygon (square) of unit length centered at the origin
	// Note that vertices must generally go counterclockwise
	// Change from the first program, in that I just made it white.

	// Does the order of drawing matter?  What happens if I draw the ground 
	// after the pillars?  I will show this in class 

	// Start with no modifications made to the model matrix
	glUniformMatrix4fv(modelviewPos, 1, GL_FALSE, &(modelview)[0][0]);
	drawobject(FLOOR) ;

	// Now draw several cubes with different transforms, colors
	// I continue to use the deprecated push-pop and matrix mode 
	// Since it is convenient (or you have to write your own stack).  

	if (DEMO > 0) {
		// We now maintain a stack for the modelview matrices. Changes made to the stack after pushing are discarded once it is popped.
		pushMatrix(modelview);
		// 1st pillar 
		// glm functions build a new matrix. They don't actually modify the passed in matrix.
		// Consequently, we need to assign this result to modelview.
		modelview = modelview * glm::translate(identity, glm::vec3(-0.4, -0.4, 0.0));
		glUniformMatrix4fv(modelviewPos, 1, GL_FALSE, &(modelview)[0][0]);
		drawcolor(CUBE, 0) ;
		popMatrix(modelview);

		// 2nd pillar 
		pushMatrix(modelview);
		modelview = modelview * glm::translate(identity, glm::vec3(0.4, -0.4, 0.0));
		glUniformMatrix4fv(modelviewPos, 1, GL_FALSE, &(modelview)[0][0]);
		drawcolor(CUBE, 1) ;
		popMatrix(modelview);

		// 3rd pillar 
		pushMatrix(modelview);
		modelview = modelview * glm::translate(identity, glm::vec3(0.4, 0.4, 0.0));
		glUniformMatrix4fv(modelviewPos, 1, GL_FALSE, &(modelview)[0][0]);
		drawcolor(CUBE, 2) ;
		popMatrix(modelview);

		// 4th pillar 
		pushMatrix(modelview);
		modelview = modelview * glm::translate(identity, glm::vec3(-0.4, 0.4, 0.0));
		glUniformMatrix4fv(modelviewPos, 1, GL_FALSE, &(modelview)[0][0]);
		drawcolor(CUBE, 3) ;
		popMatrix(modelview);
	}

	// Draw a teapot. GLUT's teapot uses the old OpenGL, which is not usable in the modern OpenGL context, therefore a 3D model of the teapot is loaded (in the init() func) and drawn instead.
	if (DEMO >= 3) {
		//  ** NEW ** Put a teapot in the middle that animates
		pushMatrix(modelview);
		modelview = modelview * glm::translate(identity, glm::vec3(teapotloc, 0.0, 0.0));

		//  The following two transforms set up and center the teapot 
		//  Remember that transforms right-multiply the modelview matrix (top of the stack)
		modelview = modelview * glm::translate(identity, glm::vec3(0.0, 0.0, 0.1));
		modelview = modelview * glm::rotate(identity, glm::pi<float>() / 2.0f, glm::vec3(1.0, 0.0, 0.0));
		float size = 0.235f; // Teapot size
		modelview = modelview * glm::scale(identity, glm::vec3(size, size, size));
		glUniformMatrix4fv(modelviewPos, 1, GL_FALSE, &(modelview)[0][0]);
		drawteapot() ;
		popMatrix(modelview);
	}

	// Does order of drawing matter? 
	// What happens if I draw the ground after the pillars? 
	// I will show this in class.

	// glUniformMatrix4fv(modelviewPos, 1, GL_FALSE, &(modelview)[0][0]); 
	// drawobject(FLOOR) ; 

	// don't wait!  
	// start processing buffered OpenGL routines 

	if (DEMO >= 2) glutSwapBuffers() ; 
	glFlush ();
}

// ** NEW ** in this assignment, is an animation of a teapot 
// Hitting p will pause this animation; see keyboard callback

void animation(void) {
	teapotloc = teapotloc + 0.005 ;
	if (teapotloc > 0.5) teapotloc = -0.5 ;
	glutPostRedisplay() ;  
}


// Defines a Mouse callback to zoom in and out 
// This is done by modifying gluLookAt         
// The actual motion is in mousedrag           
// mouse simply sets state for mousedrag       
void mouse(int button, int state, int x, int y) 
{
	if (button == GLUT_LEFT_BUTTON) {
		if (state == GLUT_UP) {
			// Do Nothing ;
		}
		else if (state == GLUT_DOWN) {
			mouseoldx = x ; mouseoldy = y ; // so we can move wrt x , y 
		}
	}
	else if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) { 
		// Reset lookAt
		eyeloc = 2.0 ;
		modelview = glm::lookAt(glm::vec3(0, -eyeloc, eyeloc), glm::vec3(0, 0, 0), glm::vec3(0, 1, 1));
		// Send the updated matrix over to the shader
		glUniformMatrix4fv(modelviewPos, 1, GL_FALSE, &modelview[0][0]);
		glutPostRedisplay() ;
	}
}

void mousedrag(int x, int y) {
	int yloc = y - mouseoldy  ;    // We will use the y coord to zoom in/out
	eyeloc  += 0.005*yloc ;         // Where do we look from
	if (eyeloc < 0) eyeloc = 0.0 ;
	mouseoldy = y ;

	/* Set the eye location */
	modelview = glm::lookAt(glm::vec3(0, -eyeloc, eyeloc), glm::vec3(0, 0, 0), glm::vec3(0, 1, 1));
	// Send the updated matrix over to the shader
	glUniformMatrix4fv(modelviewPos, 1, GL_FALSE, &modelview[0][0]);

	glutPostRedisplay();
}

// Defines what to do when various keys are pressed 
void keyboard (unsigned char key, int x, int y) 
{
	switch (key) {
		case 27:  // Escape to quit
			exit(0) ;
			break ;
		case 'p': // ** NEW ** to pause/restart animation
			animate = !animate ;
			if (DEMO >= 3) {
				if (animate) glutIdleFunc(animation) ;
				else glutIdleFunc(NULL) ;
			}
			break ;
		default:
			break ;
	}
}

/* Reshapes the window appropriately */
void reshape(int w, int h)
{
	glViewport (0, 0, (GLsizei) w, (GLsizei) h);

	// Think about the rationale for this choice for glm::perspective 
	// What would happen if you changed near and far planes? 
	// Note that the field of view takes in a radian angle!
	projection = glm::perspective(30.0f / 180.0f * glm::pi<float>(), (GLfloat)w / (GLfloat)h, 1.0f, 10.0f);
	glUniformMatrix4fv(projectionPos, 1, GL_FALSE, &projection[0][0]);
}


void init (void) 
{
	/* select clearing color 	*/
	glClearColor (0.0, 0.0, 0.0, 0.0);

	/* initialize viewing values  */
	projection = glm::mat4(1.0f); // The identity matrix

	// Think about this.  Why is the up vector not normalized?
	modelview = glm::lookAt(glm::vec3(0, -eyeloc, eyeloc), glm::vec3(0, 0, 0), glm::vec3(0, 1, 1));

	// Now create the buffer objects to be used in the scene later
	// Remember to delete all the VAOs and VBOs that you create when the program terminates!
	glGenVertexArrays(numobjects + ncolors, VAOs);
	glGenVertexArrays(1, &teapotVAO);
	glGenBuffers(numperobj * numobjects + ncolors, buffers) ; 
	glGenBuffers(3, teapotbuffers);

	// Initialize the objects we're going to draw
	initobject(FLOOR, (GLfloat *) floorverts, sizeof(floorverts), (GLfloat *) floorcol, sizeof (floorcol), (GLubyte *) floorinds, sizeof (floorinds), GL_TRIANGLES) ; 
	initcubes(CUBE, (GLfloat *)cubeverts, sizeof(cubeverts), (GLubyte *)cubeinds, sizeof(cubeinds), GL_TRIANGLES);
	loadteapot();

	if (DEMO >= 2) {
		// Enable the depth test
		glEnable(GL_DEPTH_TEST) ;
		glDepthFunc (GL_LESS) ; // The default option
	}

	// Initialize the shader program
	vertexshader = initshaders(GL_VERTEX_SHADER, "shaders/nop.vert") ;
	fragmentshader = initshaders(GL_FRAGMENT_SHADER, "shaders/nop.frag") ;
	shaderprogram = initprogram(vertexshader, fragmentshader) ;
	// Get the positions of the uniform variables
	projectionPos = glGetUniformLocation(shaderprogram, "projection");
	modelviewPos = glGetUniformLocation(shaderprogram, "modelview");
	// Pass the projection and modelview matrices to the shader
	glUniformMatrix4fv(projectionPos, 1, GL_FALSE, &projection[0][0]);
	glUniformMatrix4fv(modelviewPos, 1, GL_FALSE, &(modelview)[0][0]);
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);

	// Requests the type of buffers (Single, RGB).
	// Think about what buffers you would need...

	// Request the depth if needed, later swith to double buffer 
	if (DEMO >= 2) {
		// Due to its unique headers, OSX systems require another flag
#ifdef __APPLE__
		glutInitDisplayMode(GLUT_3_2_CORE_PROFILE | GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
#else
		glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
#endif
	}
	else { // Same reason as above
#ifdef __APPLE__
		glutInitDisplayMode(GLUT_3_2_CORE_PROFILE | GLUT_SINGLE | GLUT_RGB);
#else
		glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
#endif
	}

	glutInitWindowSize (500, 500); 
	glutInitWindowPosition (100, 100);
	glutCreateWindow ("Simple Demo wih Shaders");

  //GLew not needed on OSX due to its unique headers
#ifndef __APPLE__
	GLenum err = glewInit();
	if (GLEW_OK != err) {
		std::cerr << "Error: " << glewGetString(err) << std::endl;
	}
#endif

	init (); // Always initialize first

	// Now, we define callbacks and functions for various tasks.
	glutDisplayFunc(display); 
	glutReshapeFunc(reshape) ;
	glutKeyboardFunc(keyboard);
	glutMouseFunc(mouse) ;
	glutMotionFunc(mousedrag) ;

	glutMainLoop(); // Start the main code
	deleteBuffers(); // Program terminate. Destroy dynamically allocated memory.
	return 0;   /* ANSI C requires main to return int. */
}
