﻿#include "GL/glew.h"
#include "GLFW/glfw3.h"
#include "glm/glm.hpp"
#include "./glm/ext.hpp" // perspective, translate, rotate
#include <iostream>
#include "utils.h"
#include "stb_imageloader.h"
using namespace std;
float cameraX, cameraY, cameraZ;
float cubeLocX, cubeLocY, cubeLocZ;
int direction = 0;
void installLights(glm::mat4 vMatrix);
////*GLuint tex[2];
#define numVAOs 2
#define numVBOs 4
// allocate variables used in display() function, so that they won't need to be allocated during rendering
GLuint mvLoc, projLoc, nLoc;
int width, height;
float aspect;
// locations for shader uniform variables
GLuint globalAmbLoc, ambLoc, diffLoc, specLoc, posLoc, mAmbLoc, mDiffLoc, mSpecLoc, mShiLoc;
glm::mat4 pMat, vMat, mMat, mvMat, invTrMat;
glm::vec3 currentLightPos, lightPosV; // light position as Vector3f, in both model and view space
float lightPos[3]; // light position as float array
// initial light location
// white light properties
float globalAmbient[4] = { 0.7f, 0.7f, 0.7f, 1.0f };
float lightAmbient[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
float lightDiffuse[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
float lightSpecular[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
// gold material properties - array initialised and set up using following funcs found in utils
float* matAmb = goldAmbient();
float* matDif = goldDiffuse();
float* matSpe = goldSpecular();
float matShi = goldShininess();
void setupVertices(void);
GLuint renderingProgram;
GLuint vao[numVAOs];
GLuint vbo[numVBOs];
void moveCamera(float Px, float Pz, float Vtorx, float Vtorz)
{
	cout << "Px" << Px << "  Pz=" << Pz << "  Vtorx=" << Vtorx << "  Vtorz=" << Vtorz << endl;
	vMat = glm::lookAt(glm::vec3(Px, -2.0, Pz), glm::vec3(Px + Vtorx, -2.0, Pz + Vtorz), glm::vec3(0.0, 1.0, 0.0));
}
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
	static int last = 0;
	static float Px = 0, Py = -2, Pz = 18, Vtorx = 0, Vtorz = -1.0, angle = 0;
	switch (key) {
	case GLFW_KEY_A:
		//look left
		angle = angle - 5;
		Vtorx = sin(angle * 0.01745);  //
		Vtorz = -cos(angle * 0.01745); //assume camera default is pointing down negative z axis.
		break;
	case GLFW_KEY_D:
		//look right
		angle = angle + 5;
		Vtorx = sin(angle * 0.01745);  //
		Vtorz = -cos(angle * 0.01745); //assume camera default is pointing down negative z axis.
		break;
	case GLFW_KEY_W:
		//move forward along line of sight
		Px = Px + 0.1 * Vtorx;
		Pz = Pz + 0.1 * Vtorz;
		break;
	case GLFW_KEY_S:
		//move backwards along line of sight
		Px = Px - 0.1 * Vtorx;
		Pz = Pz - 0.1 * Vtorz;
		//vz= vz + 0.1;
		break;
	case GLFW_KEY_Z:
		//strafe left
		cout << "A pressed" << last << endl;
		last++;
		direction++;
		break;
	case GLFW_KEY_X:
		//strafe right
		cout << "D pressed" << last << endl;
		direction--;
		break;
	case GLFW_KEY_G:
		//strafe right
		cout << "G pressed" << last << endl;
		direction--;
		break;
	case 27:
		exit(0);
		break;
	default:
		break;
	}
	moveCamera(Px, Pz, Vtorx, Vtorz); //best placed where you’d normally use glm::lookat
	//better to set flags in this func to say keys have been pressed then use these flags
	//to decide when to call camera elsewhere in prog 
	currentLightPos= glm::vec3(Px,Py,Pz);  //this attaches light to camera
}




void init(GLFWwindow* window) {

	int x, y, n;
	int force_channels = 4; //RGB and A
	
	renderingProgram = createShaderProgram(); //load, compile, create, attach and link shader programmes done here
	cameraX = 0.0f; cameraY = 0.0f; cameraZ = 18.0f; //camera position
	currentLightPos = glm::vec3(cameraX, cameraY,cameraZ);
	cubeLocX = 0.0f; cubeLocY = -2.0f; cubeLocZ = 0.0f; // shift cube down Y to reveal perspective
	vMat = glm::lookAt(glm::vec3(cameraX, cameraY, cameraZ), glm::vec3(cubeLocX, cubeLocY, cubeLocZ), glm::vec3(0.0f, 1.01f, 0.0f));
	// build perspective matrix -  make sure this alines with camera initial locations
	glfwGetFramebufferSize(window, &width, &height); //need these to fit clip space to window
	aspect = (float)width / (float)height;
	pMat = glm::perspective(1.0472f, aspect, 0.05f, 100.0f); // 1.0472 radians = 60 degrees
	setupVertices(); //initial vertices x,y,z values
	glEnable(GL_DEPTH_TEST);
}
void display(GLFWwindow* window, double currentTime)
//runs 60 times a second by default in glfw - see main loop
{
	glClear(GL_DEPTH_BUFFER_BIT);
	glClearColor(0.8, 0.8, 0.9, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);
	glUseProgram(renderingProgram); //specify linked program to use

	//get a reference to the shader variable Vcol
	GLuint VcolLoc = glGetUniformLocation(renderingProgram, "Vcol");
	mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
	projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
	nLoc = glGetUniformLocation(renderingProgram, "norm_matrix");

	// copy perspective and MV matrices to corresponding uniform variables
	glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
	glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
	glUniformMatrix4fv(nLoc, 1, GL_FALSE, glm::value_ptr(invTrMat));
	// adjust OpenGL settings
	glBindVertexArray(vao[0]);
	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); //house mesh bound to VBO
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, vbo[3]); //house metex bound to VBO
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(1);
	//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	int xOffset = 0, yOffset = 0;
	int numHouses = 3;
	int numStreets = 2;
	//draw 6 houses in two streets of three
	float houseCols[9] = { 1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0 };
	// set up lights based on the current light’s position
	
	for (int i = 0; i < numStreets; i++)
	{
		for (int j = 0; j < numHouses; j++)
		{
			xOffset = j * 2;
			yOffset = i * 2;


			//Create new model matrix and then mv matrix (no change to camera position/orientation so view matrix not touched)
			mMat = glm::translate(glm::mat4(1.0f), glm::vec3(cubeLocX + xOffset, cubeLocY, cubeLocZ + yOffset));
			mMat = glm::rotate(mMat, glm::radians(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
			//currentLightPos = glm::vec3(initialLightLoc.x, initialLightLoc.y, initialLightLoc.z);
			installLights(vMat);
			mvMat = vMat * mMat;
			//copy new mv matrix to shader	
			// build the inverse-transpose of the MV matrix, for transforming normal vectors
			invTrMat = glm::transpose(glm::inverse(mvMat));
			glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
			glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
			glUniformMatrix4fv(nLoc, 1, GL_FALSE, glm::value_ptr(invTrMat));
			glDrawArrays(GL_TRIANGLES, 0, 48);  //draw a house

		}
	}
	


}

int main(void) {
	if (!glfwInit()) { exit(EXIT_FAILURE); }
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	GLFWwindow* window = glfwCreateWindow(600, 600, "glfw window", NULL, NULL);
	glfwSetKeyCallback(window, key_callback);
	glfwMakeContextCurrent(window);
	if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); }
	glfwSwapInterval(1);
	init(window);
	while (!glfwWindowShouldClose(window)) {
		display(window, glfwGetTime());
		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	glfwDestroyWindow(window);
	glfwTerminate();
	exit(EXIT_SUCCESS);
}


void setupVertices(void) {

	float house1VertexPos[144] = {
		//A House
			 0.0, 0.0, 0.0,//front 
			 0.0, -2.0, 0.0,
			 1.0, 0.0, 0.0,
			0.0, -2.0, 0.0,
			1.0, -2.0, 0.0,
			1.0, 0.0, 0.0,

			1.0, 0.0, 0.0,//right 
			1.0, -2.0, 0.0,
			1.0, -2.0, -1.0,
			1.0, 0.0, 0.0,
			1.0, -2.0, -1.0,
			1.0, 0.0, -1.0,

			0.0, 0.0, 0.0,//left 
			0.0, 0.0, -1.0,
			0.0, -2.0, -1.0,
			0.0, 0.0, 0.0,
			0.0, -2.0, -1.0,
			0.0, -2.0, 0.0,

			0.0, -2.0, 0.0, //bottom
			0.0, -2.0, -1.0,
			1.0, -2.0, -1.0,
			0.0, -2.0, 0.0,
			1.0, -2.0, -1.0,
			1.0, -2.0, 0.0,

			0.0, 0.0, -1.0,//back 
			1.0, -2.0, -1.0,
			1.0, 0.0, -1.0,
			0.0, 0.0, -1.0,
			0.0, -2.0, -1.0,
			1.0, -2.0, -1.0,
		0.0, 0.0, 0.0,//front gable 
			0.5, 1.25, 0.0,		
			1.0, 0.0, 0.0,


			0.0, 0.0, -1.0,//back gable 
			0.5, 1.25, -1.0,
			1.0, 0.0, -1.0,


			0.0, 0.0, 0.0,//left roof 
			0.5, 1.25, 0.0,
			0.5, 1.25, -1.0,
			0.0, 0.0, 0.0,
			0.5, 1.25, -1.0,
			0.0, 0.0, -1.0,

			0.5, 1.25, 0.0,//right roof 
			1.0, 0.0, 0.0,
			1.0, 0.0, -1.0,
			0.5, 1.25, 0.0,
			1.0, 0.0, -1.0,
			0.5, 1.25, -1.0
	};
		float house1Tex[96] = {
		//A House
	/*			1.0,0.0,0.0,
	1.0,1.0,0.0,
	174.0 / 255.0,85.0 / 255.0,186.0 / 255.0,
	0.0,1.0,0.0,
	0.0,0.0,1.0,
	233.0 / 255.0,66.0 / 255.0,17.0 / 255.0
	0.0, 0.0, 0.0,*/
		//front 
			1.0, 0.0,
			1.0, 1.0,
			0.0, 1.0,
			1.0, 0.0,
			0.0, 1.0,
			0.0, 0.0,
			//right 
			1.0, 0.0,
			1.0, 1.0,
			0.0, 1.0,
			1.0, 0.0,
			0.0, 1.0,
			0.0, 0.0,
			//left 
			1.0, 0.0,
			1.0, 1.0,
			0.0, 1.0,
			1.0, 0.0,
			0.0, 1.0,
			0.0, 0.0,
			//bottom
			1.0, 0.0,
			1.0, 1.0,
			0.0, 1.0,
			1.0, 0.0,
			0.0, 1.0,
			0.0, 0.0,
			//back 
			1.0, 0.0,
			1.0, 1.0,
			0.0, 1.0,
			1.0, 0.0,
			0.0, 1.0,
			0.0, 0.0,
			//front gable 
			0.0, 1.0,
			0.0, 0.0,
			1.0, 0.0,
			//back gable 
			0.0, 1.0,
			0.0, 0.0,
			1.0, 0.0,
			//left roof 
			1.0, 0.0,
			1.0, 1.0,
			0.0, 1.0,
			1.0, 0.0,
			0.0, 1.0,
			0.0, 0.0,
			//right roof 
			1.0, 0.0,
			1.0, 1.0,
			0.0, 1.0,
			1.0, 0.0,
			0.0, 1.0,
			0.0, 0.0
		};

		float house1Norm[144] = {
			//A House
				 0.0, 0.0, 1.0,//front 
				 0.0, 0.0, 1.0,
				 0.0, 0.0, 1.0,
				0.0, 0.0, 1.0,
				0.0, 0.0, 1.0,
				0.0, 0.0, 1.0,

				1.0, 0.0, 0.0,//right 
				1.0, 0.0, 0.0,
				1.0, 0.0, 0.0,
				1.0, 0.0, 0.0,
				1.0, 0.0, 0.0,
				1.0, 0.0, 0.0,

				-1.0, 0.0, 0.0,//left 
				-1.0, 0.0, 0.0,
				-1.0, 0.0, 0.0,
				-1.0, 0.0, 0.0,
				-1.0, 0.0, 0.0,
				-1.0, 0.0, 0.0,

				0.0, -1.0, 0.0, //bottom
				0.0, -1.0, 0.0,
				0.0, -1.0, 0.0,
				0.0, -1.0, 0.0,
				0.0, -1.0, 0.0,
				0.0, -1.0, 0.0,

				0.0, 0.0, -1.0,//back 
				0.0, 0.0, -1.0,
				 0.0, 0.0, -1.0,
				0.0, 0.0, -1.0,
				0.0, 0.0, -1.0,
				0.0, 0.0, -1.0,

			0.0, 0.0, 1.0,//front gable 
				0.0, 0.0, 1.0,
				0.0, 0.0, 1.0,

				0.0, 0.0, -1.0,//back gable 
				0.0, 0.0, -1.0,
				0.0, 0.0, -1.0,


				-0.9285,0.3714,0.0,//left roof 
				-0.9285,0.3714,0.0, 
				-0.9285,0.3714,0.0, 
				-0.9285,0.3714,0.0, 
				-0.9285,0.3714,0.0, 
				-0.9285,0.3714,0.0,

				0.9285,0.3714,0.0,//right roof 
				0.9285,0.3714,0.0,
				0.9285,0.3714,0.0,
				0.9285,0.3714,0.0,
				0.9285,0.3714,0.0,
				0.9285,0.3714,0.0
		};

		float house2VertexPos[54] = {
			

	

			0.0, 0.0, 0.0,//front gable 
				1.0, 0.0, 0.0,
				0.5, 1.25, 0.0,

				0.0, 0.0, -1.0,//back gable 
				0.5, 1.25, -1.0,
				1.0, 0.0, -1.0,


				0.0, 0.0, 0.0,//left roof 
				0.5, 1.25, 0.0,
				0.5, 1.25, -1.0,
				0.0, 0.0, 0.0,
				0.5, 1.25, -1.0,
				0.0, 0.0, -1.0,

				0.5, 1.25, 0.0,//right roof 
				1.0, 0.0, 0.0,
				1.0, 0.0, -1.0,
				0.5, 1.25, 0.0,
				1.0, 0.0, -1.0,
				0.5, 1.25, -1.0
		};
		float house2Tex[36] = {
			//A House
		
			//front gable 
			0.0, 1.0,
			0.0, 0.0,
			1.0, 0.0,
			//back gable 
			0.0, 1.0,
			0.0, 0.0,
			1.0, 0.0,
			//left roof 
			1.0, 0.0,
			1.0, 1.0,
			0.0, 1.0,
			1.0, 0.0,
			0.0, 1.0,
			0.0, 0.0,
			//right roof 
			1.0, 0.0,
			1.0, 1.0,
			0.0, 1.0,
			1.0, 0.0,
			0.0, 1.0,
			0.0, 0.0
		};
		float house2Norm[54] ={
			//A House
		
			0.0, 0.0, 1.0,//front gable 
			0.0, 0.0, 1.0,
			0.0, 0.0, 1.0,

			0.0, 0.0, -1.0,//back gable 
			0.0, 0.0, -1.0,
			0.0, 0.0, -1.0,


			-0.9385, 0.3714, 0.0,//left roof 
			-0.9385, 0.3714, 0.0,
			-0.9385, 0.3714, 0.0,
			-0.9385, 0.3714, 0.0,
			-0.9385, 0.3714, 0.0,
			-0.9385, 0.3714, 0.0,

			0.9385, 0.3714, 0.0,//right roof 
			0.9385, 0.3714, 0.0,
			0.9385, 0.3714, 0.0,
			0.9385, 0.3714, 0.0,
			0.9385, 0.3714, 0.0,
			0.9385, 0.3714, 0.0
		};


	//initialse VAO and VBOs
	glGenVertexArrays(numVAOs, vao); //we will use two VAOs
	glBindVertexArray(vao[0]);
	glGenBuffers(numVBOs, vbo);//we have 4 VBOs
	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);//first VBO
	glBufferData(GL_ARRAY_BUFFER, sizeof(house1VertexPos), house1VertexPos, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);//2nd VBO
	glBufferData(GL_ARRAY_BUFFER, sizeof(house1Tex), house1Tex, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, vbo[3]);//2nd VBO
	glBufferData(GL_ARRAY_BUFFER, sizeof(house1Norm), house1Norm, GL_STATIC_DRAW);
	//glBindVertexArray(vao[1]);
	//glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);//3nd VBO
	//glBufferData(GL_ARRAY_BUFFER, sizeof(house2VertexPos), house2VertexPos, GL_STATIC_DRAW);
	//glBindBuffer(GL_ARRAY_BUFFER, vbo[3]);//4th VBO
	//glBufferData(GL_ARRAY_BUFFER, sizeof(house2Tex), house2Tex, GL_STATIC_DRAW);
	//glBufferData(GL_ARRAY_BUFFER, sizeof(house1Norm), house1Norm, GL_STATIC_DRAW);


}

void installLights(glm::mat4 vMatrix) {
	// convert light’s position to view space, and save it in a float array
	lightPosV = glm::vec3(vMatrix * glm::vec4(currentLightPos, 1.0));
	lightPos[0] = lightPosV.x;
	lightPos[1] = lightPosV.y;
	lightPos[2] = lightPosV.z;
	// get the locations of the light and material fields in the shader
	globalAmbLoc = glGetUniformLocation(renderingProgram, "globalAmbient");
	ambLoc = glGetUniformLocation(renderingProgram, "light.ambient");
	diffLoc = glGetUniformLocation(renderingProgram, "light.diffuse");
	specLoc = glGetUniformLocation(renderingProgram, "light.specular");
	posLoc = glGetUniformLocation(renderingProgram, "light.position");
	mAmbLoc = glGetUniformLocation(renderingProgram, "material.ambient");
	mDiffLoc = glGetUniformLocation(renderingProgram, "material.diffuse");
	mSpecLoc = glGetUniformLocation(renderingProgram, "material.specular");
	mShiLoc = glGetUniformLocation(renderingProgram, "material.shininess");
	// set the uniform light and material values in the shader
	glProgramUniform4fv(renderingProgram, globalAmbLoc, 1, globalAmbient);
	glProgramUniform4fv(renderingProgram, ambLoc, 1, lightAmbient);
	glProgramUniform4fv(renderingProgram, diffLoc, 1, lightDiffuse);
	glProgramUniform4fv(renderingProgram, specLoc, 1, lightSpecular);
	glProgramUniform3fv(renderingProgram, posLoc, 1, lightPos);
	glProgramUniform4fv(renderingProgram, mAmbLoc, 1, matAmb);
	glProgramUniform4fv(renderingProgram, mDiffLoc, 1, matDif);
	glProgramUniform4fv(renderingProgram, mSpecLoc, 1, matSpe);
	glProgramUniform1f(renderingProgram, mShiLoc, matShi);
}