#include <stdio.h>

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include "vertexShader.c"
#include "fragmentShader.c"
#include "matrixMath.h"

#include <stdlib.h>
#include <math.h>
#include <string.h>

#define RESTART 345678

GLuint program;
GLuint vao;
GLuint cubeIndicesBufferObject;

GLfloat aspectRatio = 1.0f;

GLfloat step = 0.0f;

GLfloat cube[] = {
	 1.0f,  1.0f,  1.0f,
	 1.0f,  1.0f, -1.0f,
	 1.0f, -1.0f,  1.0f,
	 1.0f, -1.0f, -1.0f,
	-1.0f,  1.0f,  1.0f,
	-1.0f,  1.0f, -1.0f,
	-1.0f, -1.0f,  1.0f,
	-1.0f, -1.0f, -1.0f
};

GLfloat ground[] = {
	1.0f, 0.0f, 1.0f,
	1.0f, 0.0f, -1.0f,
	-1.0f, 0.0f, 0.0f,
	-1.0f, 0.0f, -1.0f
};

GLuint cubeIndices[] = {
	0, 1, 2,
	1, 3, 2,

	1, 7, 3,
	1, 5, 7,

	4, 6, 5,
	5, 6, 7,

	0, 2, 4,
	4, 2, 6,

	7, 6, 3,
	6, 2, 3,

	4, 5, 1,
	4, 1, 0
};

GLuint groundIndices[] = {
	0, 1, 2,
	1, 3, 2
};


void init(void) {
	// create and compile vertex shader
	GLchar *vertexText = malloc(vertexShader_glsl_len);
	memcpy(vertexText, vertexShader_glsl, vertexShader_glsl_len);
	const GLchar *vertexTextConst = vertexText;

	GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexTextConst, &vertexShader_glsl_len);
	glCompileShader(vertexShader);


	GLint status;
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status);
	
	if (!status) {
		printf("Error compiling vertex shader: ");
		GLchar infoLog[1024];
		glGetShaderInfoLog(vertexShader, 1024, NULL, infoLog);
		printf("%s",infoLog);
	}

	free(vertexText);
	vertexText = NULL;
	vertexTextConst = NULL;



	// create and compile fragment shader
	
	GLchar *fragmentText = malloc(fragmentShader_glsl_len);
	memcpy(fragmentText, fragmentShader_glsl, fragmentShader_glsl_len);
	const GLchar *fragmentTextConst = fragmentText;

	GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentTextConst, &fragmentShader_glsl_len);
	glCompileShader(fragmentShader);

	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &status);
	
	if (!status) {
		printf("Error compiling fragment shader: ");
		GLchar infoLog[1024];
		glGetShaderInfoLog(fragmentShader, 1024, NULL, infoLog);
		printf("%s",infoLog);
	}

	free(fragmentText);
	fragmentText = NULL;
	fragmentTextConst = NULL;

	// create and link shader program
	program = glCreateProgram();
	glAttachShader(program, vertexShader);
	glAttachShader(program, fragmentShader);
	glLinkProgram(program);

	glGetProgramiv(program, GL_LINK_STATUS, &status);

	if (!status) {
		printf("Error linking program: ");
		GLchar infoLog[1024];
		glGetProgramInfoLog(program, 1024, NULL, infoLog);
		printf("%s",infoLog);
	}
	glValidateProgram(program);


	glGetProgramiv(program, GL_VALIDATE_STATUS, &status);

	if (!status) {
		printf("Error validating program: ");
		GLchar infoLog[1024];
		glGetProgramInfoLog(program, 1024, NULL, infoLog);
		printf("%s",infoLog);
	}


	GLuint triangleVertexBufferObject;
	glGenBuffers(1, &triangleVertexBufferObject);
	glBindBuffer(GL_ARRAY_BUFFER, triangleVertexBufferObject);
	glBufferData(GL_ARRAY_BUFFER, sizeof(cube), cube, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);


	// create vertex array object
	glGenVertexArrays(1, &vao);
	glBindVertexArray(vao);
	glBindBuffer(GL_ARRAY_BUFFER, triangleVertexBufferObject);
	glVertexAttribPointer(
		0,
		3,
		GL_FLOAT,
		GL_FALSE,
		sizeof(GLfloat) * 3,
		0
	);
	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);


	// ENABLE BACKFACE CULLING
	glFrontFace(GL_CW);
	glEnable(GL_CULL_FACE);


	glEnable(GL_PRIMITIVE_RESTART);
	glPrimitiveRestartIndex(RESTART);

	// DEFINE INDEX ARRAY FOR ELEMENT DRAWING
	glGenBuffers(1, &cubeIndicesBufferObject);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeIndicesBufferObject);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cubeIndices), cubeIndices, GL_STATIC_DRAW);


	glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
}

void draw(void) {
	glClear(GL_COLOR_BUFFER_BIT);
	glUseProgram(program);
	glBindVertexArray(vao);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeIndicesBufferObject);

	step += 0.002f;
	if (step > 1.0f) step -= 1.0f;

	GLfloat M[16];
	identity(M);

	// GLfloat cameraPosition[3] = {sin(step * 2 * 3.14169f), 0.3f, 0.3f};
	GLfloat cameraPosition[3] = {0.6f, 0.3f, 0.3f};
	GLfloat cubePosition[3] = {0.0f, 0.0f, sin(step * 2 * 3.14159)};
	GLfloat up[3] = {0.0f, 1.0f, 0.0f};


	lookAt(M, cameraPosition, cubePosition, up);

	// GLfloat scaleFactor = sin(step * 3.14159f * 2) * 0.1f + 0.2f;
	GLfloat scaleFactor = 0.5f;

	GLfloat aspectScale[3] = {1.0f / aspectRatio, 1.0f, 1.0f};
	GLfloat scaleBy[3] = {scaleFactor, scaleFactor, scaleFactor};

	// rotateY(M, M, step * 3.14159f * 2);
	// rotateX(M, M, step * 3.14159f * 2 + 1.0f);
	// rotateZ(M, M, step * 3.14159f * 2 + 0.5f);
	scale(M, M, aspectScale);

	// translate(M, M, cubePosition);
	scale(M, M, scaleBy);

	glUniformMatrix4fv(glGetUniformLocation(program, "transformation"), 1, GL_FALSE, M);

	glDrawElements(GL_TRIANGLES, sizeof(cubeIndices) / sizeof(GLuint),  GL_UNSIGNED_INT, NULL);
}

void framebuffer_size_callback(GLFWwindow *window, int width, int height) {
	glViewport(0, 0, width, height);
	aspectRatio = (float)width / height;
}

int main(void) {

	// GLfloat a[3];
	// GLfloat b[3];

	// vec3Zero(a);
	// vec3Zero(b);

	// a[0] = 1;
	// a[1] = 2;
	// a[2] = 3;

	// vec3Normalise(a, a);

	// printf("%f %f %f\n", a[0], a[1], a[2]);
	// return 0;

	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow *window = glfwCreateWindow(600, 600, "Computergrafik 1", NULL, NULL);

	if (!window) {
		printf("Failed to create window\n");
		glfwTerminate();
		return -1;
	}


	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
	glfwMakeContextCurrent(window);

	glewInit();

	printf("OpenGL version supported by this platform (%s):\n", glGetString(GL_VERSION));


	init();

	while (!glfwWindowShouldClose(window)) {
		draw();

		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	glfwTerminate();

	return 0;
}