// BasicShader.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <SOIL\SOIL.h>
#include <cube.h>
#include <spheret.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>

// using namespace std;

GLuint programObject;

glm::mat4 modelViewMatrix;
glm::mat3 normalMatrix;
glm::mat4 MVP;

glm::mat4 projectionMatrix;
glm::mat4 modelMatrix;
glm::mat4 viewMatrix;

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;

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);

	earthDayHandle = SOIL_load_OGL_texture ( "earthday.png",
		SOIL_LOAD_RGB,
		SOIL_CREATE_NEW_ID,
		0);

	earthNightHandle = SOIL_load_OGL_texture ( "earthnight.jpg",
		SOIL_LOAD_RGB,
		SOIL_CREATE_NEW_ID,
		0);
}

void transformations() {

	//glLoadIdentity();
	modelMatrix = glm::mat4(1.0f);
	//gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
	glm::vec3 eye(0.0f, 0.0f, 5.0f);
	glm::vec3 center(0.0f, 0.0f, 0.0f);
	glm::vec3 up(0.0f, 1.0f, 0.0f);
	viewMatrix= glm::lookAt(eye, center, up);

	//glTranslatef(0, 0, cameraDistance)
	modelMatrix= glm::translate(modelMatrix, glm::vec3(0.0f, 0.0f, cameraDistance));
	//glRotatef(cameraAngleY, 1.0, 0.0, 0.0)
	modelMatrix= glm::rotate(modelMatrix, cameraAngleY, glm::vec3(1.0f, 0.0f, 0.0f));
	//glRotatef(cameraAngleX, 0.0, 1.0, 0.0)
	modelMatrix= glm::rotate(modelMatrix, cameraAngleX, glm::vec3(0.0f, 1.0f, 0.0f));

	modelViewMatrix = viewMatrix * modelMatrix;
	MVP= projectionMatrix * viewMatrix * modelMatrix;
	normalMatrix= glm::mat3(MVP);
	normalMatrix= glm::inverseTranspose(normalMatrix);

	GLuint MVPLoc= glGetUniformLocation(programObject, "MVP");
	glUniformMatrix4fv(MVPLoc, 1, GL_FALSE, glm::value_ptr(MVP));

	GLuint normalMatrixLoc= glGetUniformLocation(programObject, "normalMatrix");
	glUniformMatrix3fv(normalMatrixLoc, 1, GL_FALSE, glm::value_ptr(normalMatrix));

	GLuint modelViewMatrixLoc= glGetUniformLocation(programObject, "modelViewMatrix");
	glUniformMatrix4fv(modelViewMatrixLoc, 1, GL_FALSE, glm::value_ptr(modelViewMatrix));
}



void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	transformations();

	//Prepnuti VAO
	glBindVertexArray(VAOHandle[0]);
	glDrawArrays(GL_TRIANGLES, 0, 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);}

//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;
	}

	if(mouseRightDown) {
		cameraDistance += (y - mouseY);
		mouseY = y;
	}
}


void GlutCallbacks()
{
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	glutMouseFunc(mouse);
	glutMotionFunc(mouseMotion);
	//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, 0.0f, 3.0f, -4.0f);
}

int _tmain(int argc, char** argv)
{
	InitGlut(argc, argv);
	glewInit();
	GLInit();

	CheckVersions();

	GlutCallbacks();

	CreateFirstShader();

	glutMainLoop();
	return 0;
}