#include <stdio.h>

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

#include <math.h>

GLuint program;
GLuint vao;

GLfloat offsetX = -0.75f;
GLfloat offsetY = 0.4f;

GLfloat speedX = 0.0005f;
GLfloat speedY = -0.0005f;

#define NUM_TRIANGLES 8

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

typedef struct {
	GLfloat r;
	GLfloat g;
	GLfloat b;
} Color;

typedef struct {
	Point p1;
	Point p2;
	Point p3;
} Triangle;

typedef struct {
	Triangle t1;
	Triangle t2;
} TriangleRect;

void hueToRgb(GLfloat hue, GLfloat* r, GLfloat* g, GLfloat* b) {
	GLfloat x = hue / 60;
	while (x > 2) x -= 2;
	x = x - 1;
	x = 1 - fabs(x);

	if (hue < 60) {
		*r = 1.0f;
		*g = x;
		*b = 0;
	} else if (hue < 120) {
		*r = x;
		*g = 1.0f;
		*b = 0;
	} else if (hue < 180) {
		*r = 0;
		*g = 1.0f;
		*b = x;
	} else if (hue < 240) {
		*r = 0;
		*g = x;
		*b = 1.0f;
	} else if (hue < 300) {
		*r = x;
		*g = 0;
		*b = 1.0f;
	} else {
		*r = 1.0f;
		*g = 0;
		*b = x;
	}

}

void createRect(TriangleRect* rect, GLfloat posx, GLfloat posy, GLfloat width, GLfloat height) {
	rect->t1.p1.x = posx;
	rect->t1.p1.y = posy;

	rect->t1.p2.x = posx + width;
	rect->t1.p2.y = posy;

	rect->t1.p3.x = posx;
	rect->t1.p3.y = posy + height;

	rect->t2.p1.x = posx + width;
	rect->t2.p1.y = posy;

	rect->t2.p2.x = posx + width;
	rect->t2.p2.y = posy + height;

	rect->t2.p3.x = posx;
	rect->t2.p3.y = posy + height;
}

void createCenterRect(TriangleRect* rect, GLfloat posx, GLfloat posy, GLfloat width, GLfloat height) {
	createRect(rect, posx - width / 2, posy - height / 2, width, height);
}

void init(void) {
	// create and compile vertex shader
	const char *vertexText = 
		"#version 330 core\n"
		"layout (location = 0) in vec2 aPosition;\n"
		"layout (location = 1) in vec3 aColor;\n"
		// "uniform vec3 color;\n"
		"uniform vec2 offset;"
		"out vec3 vertexColor;\n"
		"void main() {\n"
		"  gl_Position = vec4(aPosition + offset, 0.0, 1.0);\n"
		"  vertexColor = aColor;"
		"}\n";
	GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexText, NULL);
	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);
	}

	// create and compile fragment shader
	const char *fragmentText = 
		"#version 330 core\n"
		"in vec3 vertexColor;\n"
		"void main() {\n"
		"  gl_FragColor = vec4(vertexColor, 1.0);\n"
		"}\n";
	GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentText, NULL);
	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);
	}

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


	TriangleRect triangleVertices[4];

	createCenterRect(&triangleVertices[0], -0.2f, 0.0f, 0.1f, 1.2f);
	createCenterRect(&triangleVertices[1],  0.2f, 0.0f, 0.1f, 1.2f);
	createCenterRect(&triangleVertices[2],  0.0f, 0.0f, 0.3f, 0.1f);
	createCenterRect(&triangleVertices[3],  0.0f, -0.8f, 0.5f, 0.1f);


	Color colors[24];
	for (int i = 0; i < 48; i++) {
		hueToRgb(i * 15.0f, &colors[i].r, &colors[i].g, &colors[i].b);
	}


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


	// CREATE COLOR BUFFER
	GLuint colorBufferObject;
	glGenBuffers(1, &colorBufferObject);
	glBindBuffer(GL_ARRAY_BUFFER, colorBufferObject);
	glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);


	// 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);
	glBindBuffer(GL_ARRAY_BUFFER, colorBufferObject);
	glVertexAttribPointer(
		1,
		3,
		GL_FLOAT,
		GL_FALSE,
		0,
		0
	);
	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);

	offsetX += speedX;
	offsetY += speedY;

	if (offsetX >= 0.75f || offsetX <= -0.75f) speedX *= -1;
	if (offsetY >= 0.4f || offsetY <=-0.15f) speedY *= -1;

	// SET COLOR
	glUniform2f(glGetUniformLocation(program, "offset"), offsetX, offsetY);


	glDrawArrays(GL_TRIANGLES, 0, NUM_TRIANGLES * 3);
}

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

int main(void) {
	printf("Hello World!\n");

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

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

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


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

	glewInit();

	init();

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

		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	glfwTerminate();

	return 0;
}