#include "GL/glew.h"
#include "GLFW/glfw3.h"

#include "pwyazh.h"
#include "pwyazh_GL.h"

#include "common.h"

int main(void)
{
	GLFWwindow *window;
	State state;
	S32 width, height;
	Input input;

	if (glfwInit() == GLFW_FALSE) {
		fprintf(stderr, "[ERROR] Failed to initialize glfw.\n");
		return(1);
	}

	width = 1024;
	height = 768;

	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	window = glfwCreateWindow(width, height, "Framebuffers", 0, 0);
	if (!window) {
		fprintf(stderr, "[ERROR] Failed to create window.\n");
		glfwTerminate();
		return(1);
	}

	glfwMakeContextCurrent(window);

	if (glewInit() != GLEW_OK) {
		fprintf(stderr, "[ERROR] Failed to initialize glew.\n");
		glfwTerminate();
		return(1);
	}

	U32 screen_shader = create_shader_program("shaders/screen.vert",
						  "shaders/screen.frag");
	U32 cube_shader = create_shader_program("shaders/cube.vert",
					       	"shaders/cube.frag");
	U32 marble_texture = load_texture("../../data/textures/marble.jpg");
	U32 metal_texture = load_texture("../../data/textures/metal.png");
	U32 window_texture = load_texture("../../data/textures/window.png");

	U32 fbo, texture_colorbuffer, rbo;
	glGenFramebuffers(1, &fbo);
	glBindFramebuffer(GL_FRAMEBUFFER, fbo);
	glGenTextures(1, &texture_colorbuffer);
	glBindTexture(GL_TEXTURE_2D, texture_colorbuffer);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glBindTexture(GL_TEXTURE_2D, 0);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
			       GL_TEXTURE_2D, texture_colorbuffer, 0);
	glGenRenderbuffers(1, &rbo);
	glBindRenderbuffer(GL_RENDERBUFFER, rbo);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
	glBindRenderbuffer(GL_RENDERBUFFER, 0);
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
				  GL_RENDERBUFFER, rbo);
	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
		fprintf(stderr, "[ERROR]: Framebuffer is not complete.\n");
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	F32 a = 0.5f;
	F32 vertices[] = {
		-a, -a, -a,  0.0f, 0.0f,
		 a, -a, -a,  1.0f, 0.0f,
		 a,  a, -a,  1.0f, 1.0f,
		 a,  a, -a,  1.0f, 1.0f,
		-a,  a, -a,  0.0f, 1.0f,
		-a, -a, -a,  0.0f, 0.0f,

		-a, -a,  a,  0.0f, 0.0f,
		 a, -a,  a,  1.0f, 0.0f,
		 a,  a,  a,  1.0f, 1.0f,
		 a,  a,  a,  1.0f, 1.0f,
		-a,  a,  a,  0.0f, 1.0f,
		-a, -a,  a,  0.0f, 0.0f,

		-a,  a,  a,  1.0f, 0.0f,
		-a,  a, -a,  1.0f, 1.0f,
		-a, -a, -a,  0.0f, 1.0f,
		-a, -a, -a,  0.0f, 1.0f,
		-a, -a,  a,  0.0f, 0.0f,
		-a,  a,  a,  1.0f, 0.0f,

		 a,  a,  a,  1.0f, 0.0f,
		 a,  a, -a,  1.0f, 1.0f,
		 a, -a, -a,  0.0f, 1.0f,
		 a, -a, -a,  0.0f, 1.0f,
		 a, -a,  a,  0.0f, 0.0f,
		 a,  a,  a,  1.0f, 0.0f,

		-a, -a, -a,  0.0f, 1.0f,
		 a, -a, -a,  1.0f, 1.0f,
		 a, -a,  a,  1.0f, 0.0f,
		 a, -a,  a,  1.0f, 0.0f,
		-a, -a,  a,  0.0f, 0.0f,
		-a, -a, -a,  0.0f, 1.0f,

		-a,  a, -a,  0.0f, 1.0f,
		 a,  a, -a,  1.0f, 1.0f,
		 a,  a,  a,  1.0f, 0.0f,
		 a,  a,  a,  1.0f, 0.0f,
		-a,  a,  a,  0.0f, 0.0f,
		-a,  a, -a,  0.0f, 1.0f
	};

	Transform cube_positions[] = {
		transform_default(),
		transform_make_translate(v3f(a, 0.0f, -4.0f*a))
	};

	V3F platform_pos = v3f(10.0f, 0.2f, 10.0f);
	V3F platform_scale = v3f(0.0f, -(a+(a*0.2f)+0.01f), 0.0f);
	Transform platform_transform = transform_make_scale_translate(platform_pos,
								      platform_scale);
	U32 cube_vao, quad_vao, vbo;
	glGenVertexArrays(1, &cube_vao);
	glBindVertexArray(cube_vao);
	glGenBuffers(1, &vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(F32), (void *)0);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(F32), (void *)(3*sizeof(F32)));
	glBindVertexArray(0);

	F32 quad_vertices[] = {
		-1.0f, -1.0f, 0.0f,  0.0f, 0.0f,
		 1.0f, -1.0f, 0.0f,  1.0f, 0.0f,
		 1.0f,  1.0f, 0.0f,  1.0f, 1.0f,
		-1.0f, -1.0f, 0.0f,  0.0f, 0.0f,
		 1.0f,  1.0f, 0.0f,  1.0f, 1.0f,
		-1.0f,  1.0f, 0.0f,  0.0f, 1.0f
	};

	glGenVertexArrays(1, &quad_vao);
	glBindVertexArray(quad_vao);
	glGenBuffers(1, &vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(quad_vertices), quad_vertices, GL_STATIC_DRAW);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(F32), (void *)0);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(F32), (void *)(3*sizeof(F32)));
	glBindVertexArray(0);

	Transform window_transforms[] = {
		transform_make_scale_translate(v3f(0.5f, 0.5f, 0.5f), v3f(0.0f, 0.0f, a+0.1f)),
		transform_make_scale_translate(v3f(0.5f, 0.5f, 0.5f), v3f(0.0f, 0.0f, 2.0f)),
		transform_make_scale_translate(v3f(0.5f, 0.5f, 0.5f), v3f(1.0f, 0.0f, -1.0f)),
		transform_make_scale_translate(v3f(0.5f, 0.5f, 0.5f), v3f(-2.0f, 0.0f, 0.5f)),
		transform_make_scale_translate(v3f(0.5f, 0.5f, 0.5f), v3f(-3.0f, 0.0f, -3.0f)),
		transform_make_scale_translate(v3f(0.5f, 0.5f, 0.5f), v3f(3.0f, 0.0f, 0.2)),
	};

	F32 angle = 0.0f;
	for (S32 i = 0; i < (S32)ArrayCount(window_transforms); ++i) {
		Transform *transform = window_transforms+i;
		*transform = transform_rotate(*transform, v3f(0.0f, angle, 0.0f));
		angle += 30.0f;
	}

	state.camera = (Camera) {
		v3f(0.0f, 0.0f, 3.0f),
		90.0f, 0.1f, 100.0f,
		0.0f, 0.0f
	};

	F32 target_fps = 60.0f;
	F32 target_spf = 1.0f/target_fps;
	F32 last_time = glfwGetTime();

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

		process_glfw_keyboard(window, &input);
		glfwGetFramebufferSize(window, &width, &height);

		/* INFO(pryazha): Update */
		V3F target = v3f_zero();

		if (key_first_press(input.exit))
			glfwSetWindowShouldClose(window, GLFW_TRUE);

		V3F dv = get_dv_camera_orbital(&input, state.camera.pos,
					       target, state.dt, 3.0f);
		state.camera.pos = v3f_add(state.camera.pos, dv);

		input_update_last_state(&input);

		Transform sorted[ArrayCount(window_transforms)];
		MemoryCopyArray(sorted, window_transforms);
		for (S32 i = 0; i < (S32)ArrayCount(sorted); ++i) {
			for (S32 j = 1; j < (S32)ArrayCount(sorted); ++j) {
				F32 dist_left = v3f_length(v3f_sub(sorted[j-1].translate, state.camera.pos));
				F32 dist_right = v3f_length(v3f_sub(sorted[j].translate, state.camera.pos));
				if (dist_left < dist_right) {
					Transform temp = sorted[j];
					sorted[j] = sorted[j-1];
					sorted[j-1] = temp;
				}
			}
		}

		/* INFO(pryazha): Render */
		MAT4 model, view, proj;

		view = look_at(state.camera.pos, target, v3f(0.0f, 1.0f, 0.0f));
		proj = perspective(state.camera.fovx, (F32)width/(F32)height,
				   state.camera.near, state.camera.far);

		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
		glViewport(0, 0, width, height);
		glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glEnable(GL_DEPTH_TEST);
		glUseProgram(cube_shader);
		shader_set_mat4fv(cube_shader, "view", view);
		shader_set_mat4fv(cube_shader, "proj", proj);
		glBindTexture(GL_TEXTURE_2D, marble_texture);
		glBindVertexArray(cube_vao);
		for (S32 i = 0; i < (S32)ArrayCount(cube_positions); ++i) {
			model = transform_apply(cube_positions[i]);
			shader_set_mat4fv(cube_shader, "model", model);
			glDrawArrays(GL_TRIANGLES, 0, 36);
		}

		glBindTexture(GL_TEXTURE_2D, metal_texture);
		model = transform_apply(platform_transform);
		shader_set_mat4fv(cube_shader, "model", model);
		glDrawArrays(GL_TRIANGLES, 0, 36);
		glBindVertexArray(0);

		glEnable(GL_BLEND);
		glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
		glUseProgram(cube_shader);
		glBindTexture(GL_TEXTURE_2D, window_texture);
		shader_set_mat4fv(cube_shader, "view", view);
		shader_set_mat4fv(cube_shader, "proj", proj);
		glBindVertexArray(quad_vao);
		for (S32 i = 0; i < (S32)ArrayCount(sorted); ++i) {
			model = transform_apply(sorted[i]);
			shader_set_mat4fv(cube_shader, "model", model);
			glDrawArrays(GL_TRIANGLES, 0, 6);
		}
		glBindVertexArray(0);
		glDisable(GL_BLEND);

		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		glViewport(0, 0, width, height);
		glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		glUseProgram(screen_shader);
		glDisable(GL_DEPTH_TEST);
		glBindTexture(GL_TEXTURE_2D, texture_colorbuffer);
		glBindVertexArray(quad_vao);
		shader_set_mat4fv(screen_shader, "model", mat4_identity());
		glDrawArrays(GL_TRIANGLES, 0, 6);
		glBindVertexArray(0);

		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
		glViewport(0, 0, width, height);
		glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glEnable(GL_DEPTH_TEST);
		V3F f = v3f(view.m2.x, view.m2.y, view.m2.z);
		target = v3f_add(state.camera.pos, v3f_negate(f));
		view = look_at(state.camera.pos, target, v3f(0.0f, 1.0f, 0.0f));
		glUseProgram(cube_shader);
		shader_set_mat4fv(cube_shader, "view", view);
		shader_set_mat4fv(cube_shader, "proj", proj);
		glBindTexture(GL_TEXTURE_2D, marble_texture);
		glBindVertexArray(cube_vao);
		for (S32 i = 0; i < (S32)ArrayCount(cube_positions); ++i) {
			model = transform_apply(cube_positions[i]);
			shader_set_mat4fv(cube_shader, "model", model);
			glDrawArrays(GL_TRIANGLES, 0, 36);
		}

		glBindTexture(GL_TEXTURE_2D, metal_texture);
		model = transform_apply(platform_transform);
		shader_set_mat4fv(cube_shader, "model", model);
		glDrawArrays(GL_TRIANGLES, 0, 36);
		glBindVertexArray(0);

		glEnable(GL_BLEND);
		glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
		glUseProgram(cube_shader);
		glBindTexture(GL_TEXTURE_2D, window_texture);
		shader_set_mat4fv(cube_shader, "view", view);
		shader_set_mat4fv(cube_shader, "proj", proj);
		glBindVertexArray(quad_vao);
		for (S32 i = 0; i < (S32)ArrayCount(sorted); ++i) {
			model = transform_apply(sorted[i]);
			shader_set_mat4fv(cube_shader, "model", model);
			glDrawArrays(GL_TRIANGLES, 0, 6);
		}
		glBindVertexArray(0);
		glDisable(GL_BLEND);

		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		glViewport(0, 0, width, height);
		glUseProgram(screen_shader);
		glDisable(GL_DEPTH_TEST);
		glBindTexture(GL_TEXTURE_2D, texture_colorbuffer);
		glBindVertexArray(quad_vao);
		model = transform_apply(transform_make_scale_translate(v3f(0.3f, 0.3f, 0.3f), v3f(-0.7f, 0.7f, 0.0f)));
		shader_set_mat4fv(screen_shader, "model", model);
		glDrawArrays(GL_TRIANGLES, 0, 6);
		glBindVertexArray(0);

		glfwSwapBuffers(window);

		F32 elapsed = glfwGetTime()-last_time;
		if (elapsed < target_spf) {
			U32 sleep_time = (U32)(target_spf-elapsed);
			if (sleep_time > 0)
				sleep(sleep_time);
		}
		F32 current_time = glfwGetTime();
		state.dt = current_time-last_time;
		last_time = current_time;
	}

	glfwTerminate();
	return(0);
}