// BasicShader.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <SOIL\SOIL.h>
#include <cube.h>
#include <spheret.h>
#include <quadN.h>
#include <GL/glew.h>
#include <GL/glut.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/matrix_inverse.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <string.h>
#include <fstream>
#include <sstream>
#include <math.h>

// using namespace std;

const GLuint TEXTURE_WIDTH = 512;
const GLuint TEXTURE_HEIGHT = 512;

GLuint programObject;

glm::mat4 modelViewMatrix;
glm::mat3 normalMatrix;
glm::mat4 MVP;

glm::mat4 projectionMatrix;
glm::mat4 modelMatrix;
glm::mat4 viewMatrix;

glm::mat4 sphereModelViewMatrix;
glm::mat4 quadModelViewMatrix;

int mouseX = 0;
int mouseY = 0;
bool mouseLeftDown = false;
bool mouseRightDown = true;

GLfloat cameraAngleY = 0;
GLfloat cameraAngleX = 0;
GLfloat cameraDistance = 0;

GLfloat rotation = 0.0;

// VAO
GLuint VAOHandle[3];

// Texturove objekty
GLuint earthDayHandle;
GLuint earthNightHandle;

GLuint textureHandler;

GLfloat dayOrNight = 0.0;
GLint dayOrNightLoc;

GLfloat lightPosition[] = {-2.0f, -4.0f, -1.0f};

bool LoadTXTFile(const char *fileName, std::string &textFile)
	{
		std::stringstream strStream;

		std::ifstream file(fileName, std::ifstream::in);
		if(!file.good())
			return false;

		strStream << file.rdbuf();
		textFile = strStream.str();
		file.close();

		return true;
	}

void InitGlut(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(500, 500);
	glutInitWindowPosition(100, 100);
	glutCreateWindow(argv[0]);
}

void objectLoad(const int VAOH,
				const int numVertices,
				const float vertices[],
				const float normals[],
				const float texCoords[])
{
	// VBO
	GLuint VBOhandle[3];
	glGenBuffers(3, VBOhandle);

	GLuint positionBufferHandle= VBOhandle[0];
	GLuint normalBufferHandle= VBOhandle[1];
	GLuint texCoordBufferHandle= VBOhandle[2];

	if(vertices) {
		glBindBuffer(GL_ARRAY_BUFFER, positionBufferHandle);
		glBufferData(GL_ARRAY_BUFFER, 3*numVertices*sizeof(float), vertices, GL_STATIC_DRAW);
	}

	if(normals) {
		glBindBuffer(GL_ARRAY_BUFFER, normalBufferHandle);
		glBufferData(GL_ARRAY_BUFFER, 3*numVertices*sizeof(float), normals, GL_STATIC_DRAW);
	}

	if(texCoords) {
		glBindBuffer(GL_ARRAY_BUFFER, texCoordBufferHandle);
		glBufferData(GL_ARRAY_BUFFER, 2*numVertices*sizeof(float), texCoords, GL_STATIC_DRAW);
	}

	const GLint pos = 0;
	const GLint norm = 1;
	const GLint tex = 2;

	//VAO
	glBindVertexArray(VAOH);

	// Enable Vertex Attribute Array
	glEnableVertexAttribArray(pos);
	glEnableVertexAttribArray(norm);
	glEnableVertexAttribArray(tex);

	// Definovali jsme "format" pro pozici a priradili jsme ji pozici 0
	glBindBuffer(GL_ARRAY_BUFFER, positionBufferHandle);
	glVertexAttribPointer(pos, 3, GL_FLOAT, GL_FALSE, 0, (GLubyte *) NULL);

	glBindBuffer(GL_ARRAY_BUFFER, normalBufferHandle);
	glVertexAttribPointer(norm, 3, GL_FLOAT, GL_FALSE, 0, (GLubyte *) NULL);

	glBindBuffer(GL_ARRAY_BUFFER, texCoordBufferHandle);
	glVertexAttribPointer(tex, 2, GL_FLOAT, GL_FALSE, 0, (GLubyte *) NULL);
}

void GLInit()
{
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glClearDepth(1.0);
	glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
	//glShadeModel(GL_FLAT);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D); //NEW

	glGenVertexArrays(3, VAOHandle);

	objectLoad(VAOHandle[0], spheretNumVerts, spheretVerts, spheretNormals, spheretTexCoords);
	objectLoad(VAOHandle[1], cubeNumVerts, cubeVerts, cubeNormals, (GLfloat *) NULL);
	objectLoad(VAOHandle[2], quadNumVerts, quadVerts, quadNormals, (GLfloat *) NULL);

	glGenTextures(1, &textureHandler);
	glBindTexture(GL_TEXTURE_2D, textureHandler);

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
	//glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, GL_RGBA, 0);

	glBindTexture(GL_TEXTURE_2D, 0);
}

float calculateAlpha(){
	GLuint glutTime = glutGet(GLUT_ELAPSED_TIME);
	if (glutTime == 0) glutTime = 1;
	return  ((float) glutTime/3000.0f - (float) (glutTime / 3000))*2.0*3.1415926;
}

void transformations() {

	//Textura hloubky
	glViewport(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
	projectionMatrix = glm::mat4(1.0); //glLoadIdentity();
	projectionMatrix = glm::perspective(60.0f, (float)(TEXTURE_WIDTH/TEXTURE_HEIGHT), 1.0f, 100.0f);

	//Nastaveni bufferu
	glPushAttrib(GL_COLOR_BUFFER_BIT | GL_PIXEL_MODE_BIT);
	glDrawBuffer(GL_BACK);
	glReadBuffer(GL_BACK);

	//Vykresleni sceny

	//gluLookAt(L, L, L, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
	glm::vec3 eye(lightPosition[0], lightPosition[1], lightPosition[2]);
	glm::vec3 center(0.0f, 0.0f, 0.0f);
	glm::vec3 up(0.0f, 1.0f, 0.0f);
	viewMatrix= glm::lookAt(eye, center, up);

	modelMatrix = glm::mat4(1.0f); //LoadIdentity
	modelMatrix = glm::translate(modelMatrix, glm::vec3(0.0f, 2.0f, 0.0f)); // Translate(0, 2, 0);
	quadModelViewMatrix = viewMatrix * modelMatrix;

	// Vypnuti shaderu
	glUseProgram(0);

	// Nastaveni matic pro standardni vykreslovani
	glMatrixMode(GL_PROJECTION);
	glLoadMatrixf(glm::value_ptr(projectionMatrix));
	glMatrixMode(GL_MODELVIEW);
	glLoadMatrixf(glm::value_ptr(quadModelViewMatrix));
}

void transformations2(){
	// Posun koule v y-ovem smeru
	modelMatrix= glm::mat4(1.0f);
	modelMatrix = glm::translate(modelMatrix, glm::vec3(0.0f, sin(calculateAlpha()), 0.0f));
	sphereModelViewMatrix = viewMatrix * modelMatrix;

	glMatrixMode(GL_MODELVIEW);
	glLoadMatrixf(glm::value_ptr(sphereModelViewMatrix));
}


void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	///////////////////////////////
	// Cast 1
	///////////////////////////////

	transformations();

	// Vykresleni podlozky
	glBindVertexArray(VAOHandle[2]);
	glDrawArrays(GL_TRIANGLES, 0, (GLsizei) quadNumVerts);

	transformations2();

	// Vykresleni koule
	glBindVertexArray(VAOHandle[0]);
	glDrawArrays(GL_TRIANGLES, 0, spheretNumVerts);

	// Kopiruj do textury
	glBindTexture(GL_TEXTURE_2D, textureHandler);
	glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
	glPopAttrib();

/*	//Vykresleni textury
	glUseProgram(0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

	glBegin(GL_QUADS);
		glNormal3f(0.0, 0.0, 1.0);
		glTexCoord2f(0.0, 0.0);
		glVertex3f(-2.0, -2.0, 0.0);

		glNormal3f(0.0, 0.0, 1.0);
		glTexCoord2f(1.0, 0.0);
		glVertex3f(2.0, -2.0, 0.0);

		glNormal3f(0.0, 0.0, 1.0);
		glTexCoord2f(1.0, 1.0);
		glVertex3f(2.0, 2.0, 0.0);

		glNormal3f(0.0, 0.0, 1.0);
		glTexCoord2f(0.0, 1.0);
		glVertex3f(-2.0, 2.0, 0.0);
	glEnd(); // */

	////////////////////////////////////
	//         Cast 2
	////////////////////////////////////



//*	// Vymazani bufferu
	//Vymazani bufferu
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glColor3f(1.0f, 1.0f, 0.0f);
	glLoadIdentity();
	glTranslatef(lightPosition[0], lightPosition[1], lightPosition[2]);
	glutSolidSphere(3.0, 10, 10);
	glUseProgram(programObject);

	//Nastaveni kamery
	viewMatrix= glm::mat4(1.0f); //glLoadIdentity();
	//gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
	glm::vec3 eye= glm::vec3(0.0f, 0.0f, -5.0f); //Pozice pozorovatele
	glm::vec3 center= glm::vec3(0.0f, 0.0f, 0.0f);
	glm::vec3 up= glm::vec3(0.0f, 1.0f, 0.0f);
	viewMatrix= glm::lookAt(eye, center, up);

	modelMatrix= glm::mat4(1.0);//glLoadIdentity();
	modelMatrix= glm::translate(modelMatrix, glm::vec3(0.0f, 0.0f, (float)cameraDistance));
	//glRotatef(cameraAngleX, 1.0, 0.0, 0.0);
	modelMatrix= glm::rotate(modelMatrix, cameraAngleX, glm::vec3(1.0f, 0.0f, 0.0f));
	//glRotatef(cameraAngleY, 0.0, 1.0, 0.0);
	modelMatrix= glm::rotate(modelMatrix, cameraAngleY, glm::vec3(0.0f, 1.0f, 0.0f));
	//glRotatef(-90, 1.0, 0.0, 0.0);
	//modelMatrix= glm::rotate(modelMatrix, -90.0f, glm::vec3(1.0f, 0.0f, 0.0f));

	//Vykresleni sceny
	glm::mat4 helpMatrix= modelMatrix;
	modelMatrix= glm::translate(modelMatrix, glm::vec3(0.0f, -2.0f, 0.0f));

	//VYKRESLI PODLOZKU//
	glm::mat4 shiftMatrix= glm::mat4(0.5f, 0.0f, 0.0f, 0.0f,
										0.0f, 0.5f, 0.0f, 0.0f,
										0.0f, 0.0f, 0.5f, 0.0f,
										0.5f, 0.5f, 0.5f, 1.0f);
	glm::mat4 transformMatrix= shiftMatrix * projectionMatrix * quadModelViewMatrix;

	GLint transformMatrixLoc = glGetUniformLocation(programObject, "transformMatrix");
	glUniformMatrix4fv(transformMatrixLoc, 1, GL_FALSE, glm::value_ptr(transformMatrix));

		modelViewMatrix= viewMatrix * modelMatrix;
	MVP= projectionMatrix * viewMatrix * modelMatrix;
	// leva horni 3x3 submatice z MVP, transponovana inverze
	normalMatrix= glm::mat3(MVP);
	normalMatrix= glm::inverseTranspose(normalMatrix);

	//Posleme matice do shaderu
	GLint MVPLoc = glGetUniformLocation(programObject, "MVP");
	glUniformMatrix4fv(MVPLoc, 1, GL_FALSE, glm::value_ptr(MVP));

	GLint normalMatrixLoc= glGetUniformLocation(programObject, "normalMatrix");
	glUniformMatrix3fv(normalMatrixLoc, 1, GL_FALSE, glm::value_ptr(normalMatrix)); //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!3

	GLint ModelViewMatrixLoc = glGetUniformLocation(programObject, "modelViewMatrix");
	glUniformMatrix4fv(ModelViewMatrixLoc, 1, GL_FALSE, glm::value_ptr(modelViewMatrix));

	// Vykresleni podlozky
	glBindVertexArray(VAOHandle[2]);
	glDrawArrays(GL_TRIANGLES, 0, (GLsizei) quadNumVerts);

	// Vykresleni koule
	modelMatrix = helpMatrix;
	modelMatrix = glm::translate(modelMatrix, glm::vec3(0.0f, sin(calculateAlpha()), 0.0f));

	// Vypocteme a posleme vsechny matice pro zobrazeni koule
	transformMatrix = shiftMatrix * projectionMatrix * sphereModelViewMatrix;
	glUniformMatrix4fv(transformMatrixLoc, 1, GL_FALSE, glm::value_ptr(transformMatrix));
	modelViewMatrix = viewMatrix * modelMatrix;
	glUniformMatrix4fv(ModelViewMatrixLoc, 1, GL_FALSE, glm::value_ptr(modelViewMatrix));
	MVP = projectionMatrix * viewMatrix * modelMatrix;
	glUniformMatrix4fv(MVPLoc, 1, GL_FALSE, glm::value_ptr(MVP));
	normalMatrix = glm::mat3(MVP);
	normalMatrix = glm::inverseTranspose(normalMatrix);
	glUniformMatrix3fv(normalMatrixLoc, 1, GL_FALSE, glm::value_ptr(normalMatrix));

	glBindVertexArray(VAOHandle[0]);
	glDrawArrays(GL_TRIANGLES, 0, (GLsizei) spheretNumVerts);

// */
	glutSwapBuffers();
	glFlush();
}

void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei) w, (GLsizei) h);
	//glMatrixMode(GL_PROJECTION);
	//glLoadIdentity();
	projectionMatrix= glm::mat4(1.0f);
	        //(Left, Right, Bottom, Top, Near, Far);
	//glFrustum(-1.0, 1.0, -1.0, 1.0, 1.5, 20.0);
	projectionMatrix= glm::frustum(-1.0f, 1.0f, -1.0f, 1.0f, 1.5f, 20.0f);
	//glMatrixMode(GL_MODELVIEW);
}

void keyboard(unsigned char key, int x, int y)
{
	if ((int)key == 27)
		exit(0);
	if (key == ' ') {
		if (dayOrNight > 0.5) {
			dayOrNight = 0.0;
			glUniform1f(dayOrNightLoc, dayOrNight);
		} else {
			dayOrNight = 1.0;
			glUniform1f(dayOrNightLoc, dayOrNight);
		}
		glutPostRedisplay();
	}
	if (key == 'n') {
		glUniform1f(dayOrNightLoc, 2.0);
		glutPostRedisplay();
	}

}

//void myTimer(int val) {
//	rotation+=1.0;
//	glutTimerFunc(50, myTimer, 0);
//	glutPostRedisplay();
//}

void mouse(int button, int state, int x, int y){
	mouseX = x;
	mouseY = y;

	if(button == GLUT_LEFT_BUTTON) {
		if(state == GLUT_DOWN) {
			mouseLeftDown = true;
		} else if(state == GLUT_UP) {
			mouseLeftDown = false;
		}
	}

	if(button == GLUT_RIGHT_BUTTON) {
		if(state == GLUT_DOWN) {
			mouseRightDown = true;
		} else if(state == GLUT_UP) {
			mouseRightDown = false;
		}
	}
}

void mouseMotion(int x, int y) {
	if(mouseLeftDown) {
		cameraAngleY += (x - mouseX);
		cameraAngleX += (y - mouseY);
		mouseX = x;
		mouseY = y;
		glutPostRedisplay();
	}

	if(mouseRightDown) {
		cameraDistance += (y - mouseY);
		mouseY = y;
		glutPostRedisplay();
	}
}

void myIdle() {
	glutPostRedisplay();
}


void GlutCallbacks()
{
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	glutMouseFunc(mouse);
	glutMotionFunc(mouseMotion);
	glutIdleFunc(myIdle);
	//glutTimerFunc(50, myTimer, 0);
}

void CheckVersions()
{
	char * version, * vendor, * renderer, * shadingLanguage;

	version = (char *) glGetString(GL_VERSION);
	vendor = (char *) glGetString(GL_VENDOR);
	renderer = (char *) glGetString(GL_RENDERER);
	shadingLanguage = (char *) glGetString(GL_SHADING_LANGUAGE_VERSION);

	std::cout << "Verze OpenGL:" << version << "\n";
	std::cout << "Vyrobce karty:" << vendor << "\n";
	std::cout << "Karta:" << renderer << "\n";
	std::cout << "Verze GLSL:" << shadingLanguage << "\n\n";
}

void CreateFirstShader()
{
	GLint status;
	const GLuint TEXT_BUFFER_SIZE = 5000;

	// 1. Vytvoreni objektu shaderu, ulozeni id
	GLuint vertexShaderObject;
	GLuint fragmentShaderObject;
	vertexShaderObject = glCreateShader(GL_VERTEX_SHADER);
	fragmentShaderObject = glCreateShader(GL_FRAGMENT_SHADER);

	// 2. Nahrati zdrojoveho kodu ze souboru
	std::string vertexShaderTextString;
	std::string fragmentShaderTextString;
	const char * vertexShaderName = "VertexShader.txt";
	const char * fragmentShaderName = "FragmentShader.txt";

	if(LoadTXTFile(vertexShaderName, vertexShaderTextString))
		std::cout << vertexShaderName << " loaded\n";
	else
		std::cout << vertexShaderName << " loading ERROR!!!\n";
	if(LoadTXTFile(fragmentShaderName, fragmentShaderTextString))
		std::cout << fragmentShaderName << " loaded\n";
	else
		std::cout << fragmentShaderName << " loading ERROR!!!\n";

	const char * vs = vertexShaderTextString.c_str();
	const char * fs = fragmentShaderTextString.c_str();
	glShaderSource(vertexShaderObject, 1, &vs, NULL);
	glShaderSource(fragmentShaderObject, 1, &fs, NULL);

	// 3. Kompilace shaderu

	glCompileShader(vertexShaderObject);
	glGetShaderiv(vertexShaderObject, GL_COMPILE_STATUS, &status);
	if(status)
		std::cout << "Vertex shader compiled\n";
	else {
		std::cout << "Vertex shader compilation ERROR!!!\n";
		GLchar infoLog[TEXT_BUFFER_SIZE];
		glGetShaderInfoLog(vertexShaderObject, TEXT_BUFFER_SIZE, NULL, infoLog);
		std::cout << infoLog << "\n";
	}

	glCompileShader(fragmentShaderObject);
	glGetShaderiv(fragmentShaderObject, GL_COMPILE_STATUS, &status);
	if(status)
		std::cout << "Fragment shader compiled\n";
	else {
		std::cout << "Fragment shader compilation ERROR!!!\n";
		GLchar infoLog[TEXT_BUFFER_SIZE];
		glGetShaderInfoLog(fragmentShaderObject, TEXT_BUFFER_SIZE, NULL, infoLog);
		std::cout << infoLog << "\n";
	}

	// 4. Vytvoreni programu objektu

	programObject = glCreateProgram();

	// 5. Pripojeni objektu shaderu k programovemu objektu
	glAttachShader(programObject, vertexShaderObject);
	glAttachShader(programObject, fragmentShaderObject);

	// 6. Slinkovani programu
	glLinkProgram(programObject);
	glGetProgramiv(programObject, GL_LINK_STATUS, &status);
	if(status)
		std::cout << "Linking OK" << "\n";
	else {
		std::cout << "Linking ERROR!!!" << "\n";
		GLchar infoLog[TEXT_BUFFER_SIZE];
		glGetProgramInfoLog(programObject, TEXT_BUFFER_SIZE, NULL, infoLog);
		std::cout << infoLog << "\n";
	}

	// 7. Pouziti programu
	glUseProgram(programObject);

	// Zjisteni lokace uniformni promenne
	GLint lightPositionLoc;
	lightPositionLoc = glGetUniformLocation(programObject, "lightPosition");
	glUniform3f(lightPositionLoc, lightPosition[0], lightPosition[1], lightPosition[2]);

	// Posleme cislo texturove jednotky do shaderu (tj. "sampler")
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, textureHandler);
	GLint lightShadowMapLoc;
	lightShadowMapLoc = glGetUniformLocation(programObject, "lightShadowMap");
	glUniform1i(lightShadowMapLoc, 0);


}

int _tmain(int argc, char** argv)
{
	InitGlut(argc, argv);
	glewInit();
	GLInit();

	CheckVersions();

	GlutCallbacks();

	CreateFirstShader();

	glutMainLoop();
	return 0;
}