#include <stdio.h>

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

#include <math.h>

GLuint program;
GLuint vao;

#define NUM_TRIANGLES 8

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

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

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

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

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

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

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

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

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

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 float vertValue;\n"
		"out float fragValue;\n"
		"void main() {\n"
		"  gl_Position = vec4(aPosition, 0.0, 1.0);\n"
		"  fragValue = vertValue;\n"
		"}\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 float fragValue;\n"
		"void main() {\n"
		// "  gl_FragColor = vec4(mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), fragValue), 1.0);\n"  /// 1
		// "  gl_FragColor = vec4(mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), step(0.5,fragValue)), 1.0);\n"  /// 2
		// "  gl_FragColor = vec4(mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), smoothstep(0.4, 0.6, fragValue)), 1.0);\n"  /// 3
		// "  gl_FragColor = vec4(mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), (sin(fragValue * 3.14150 * 4) + 1) / 2), 1.0);\n"  /// 4
		"  gl_FragColor = vec4(mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), step(0.5, (sin(fragValue * 3.14150 * 4) + 1) / 2)), 1.0);\n"  /// 5
		"}\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[1];

	createCenterRect(&triangleVertices[0], 0.0f, 0.0f, 1.5f, 0.1f);


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


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