#include <stdio.h>

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

#include "vertexShader.c"
#include "fragmentShader.c"

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

/**
 *            0.15  0.25       
 *               |  |          
 *                             
 *      0__1     8__9   ___0.6 
 *      |  |     |  |          
 *      |  |     |  |          
 *      |  4_____6  |   ___0.05
 *      |  5_____7  |          
 *      |  |     |  |          
 *      |  |     |  |          
 *      2__3     10_11         
 *                             
 *      12__________14  __-0.75
 *      13__________15  __-0.85
 *                             
 */

#define RESTART 345678


typedef struct {
	GLfloat x;
	GLfloat y;
	GLfloat value;
} Point;

GLuint program;
GLuint vao;

GLfloat rotation;

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);
	}

	size_t size = sizeof(Point) * 16;
	Point* corners = (Point *) malloc(size);

	corners[0].x = -0.25f;
	corners[0].y = 0.6f;
	corners[0].value = 0.0625f;

	corners[1].x = -0.15f;
	corners[1].y = 0.6f;
	corners[1].value = 0.125f;

	corners[2].x = -0.25f;
	corners[2].y = -0.6f;
	corners[2].value = 0.1875f;

	corners[3].x = -0.15f;
	corners[3].y = -0.6f;
	corners[3].value = 0.25f;

	corners[4].x = -0.15f;
	corners[4].y = 0.05f;
	corners[4].value = 0.3125f;

	corners[5].x = -0.15f;
	corners[5].y = -0.05f;
	corners[5].value = 0.375f;

	corners[6].x = 0.15f;
	corners[6].y = 0.05f;
	corners[6].value = 0.4375f;

	corners[7].x = 0.15f;
	corners[7].y = -0.05f;
	corners[7].value = 0.5f;

	corners[8].x = 0.15f;
	corners[8].y = 0.6f;
	corners[8].value = 0.5625f;

	corners[9].x = 0.25f;
	corners[9].y = 0.6f;
	corners[9].value = 0.625f;

	corners[10].x = 0.15f;
	corners[10].y = -0.6f;
	corners[10].value = 0.6875f;

	corners[11].x = 0.25f;
	corners[11].y = -0.6f;
	corners[11].value = 0.75f;

	corners[12].x = -0.25f;
	corners[12].y = -0.75f;
	corners[12].value = 0.8125f;

	corners[13].x = -0.25f;
	corners[13].y = -0.85f;
	corners[13].value = 0.875f;

	corners[14].x = 0.25f;
	corners[14].y = -0.75f;
	corners[14].value = 0.9375f;

	corners[15].x = 0.25f;
	corners[15].y = -0.85f;
	corners[15].value = 1.0f;

	GLuint triangleVertexBufferObject;
	glGenBuffers(1, &triangleVertexBufferObject);
	glBindBuffer(GL_ARRAY_BUFFER, triangleVertexBufferObject);
	glBufferData(GL_ARRAY_BUFFER, size, corners, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	free(corners);


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


	glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
}

void draw(void) {
	glClear(GL_COLOR_BUFFER_BIT);
	glUseProgram(program);
	glBindVertexArray(vao);

	rotation = rotation + 0.01f;
	if (rotation > 2 * 3.1415926f) {
		rotation = 0;
	}

	glUniform1f(glGetUniformLocation(program, "rotation"), rotation);

	glFrontFace(GL_CW);
	glCullFace(GL_BACK);
	glEnable(GL_CULL_FACE);


	glEnable(GL_PRIMITIVE_RESTART);
	glPrimitiveRestartIndex(RESTART);

	GLuint indices[] = {0, 1, 2, 3, RESTART, 4, 6, 5, 7, RESTART, 8, 9, 10, 11, RESTART, 12, 14, 13, 15};

	glDrawElements(GL_TRIANGLE_STRIP, sizeof(indices) / sizeof(GLuint),  GL_UNSIGNED_INT, indices);
}

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

int main(void) {
	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;
}