#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, "Face culling", 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);
	}

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

	U32 shader = create_shader_program("shaders/face_culling.vert",
					   "shaders/face_culling.frag");

	U32 grid_texture = load_texture("../../data/textures/grid.png");

	F32 a = 0.5f;
	/* NOTE(pryazha): Counter-clockwise order */
	F32 cube_vertices_ccw[] = {
		/* NOTE(pryazha): Back face */
		 a, -a, -a,  0.0f, 0.0f, /* bottom-left */
		-a,  a, -a,  1.0f, 1.0f, /* top-right */
		 a,  a, -a,  0.0f, 1.0f, /* top-left */
		 a, -a, -a,  0.0f, 0.0f, /* bottom-left */
		-a, -a, -a,  1.0f, 0.0f, /* bottom-right */
		-a,  a, -a,  1.0f, 1.0f, /* top-right */

		/* NOTE(pryazha): Front face */
		-a, -a,  a,  0.0f, 0.0f, /* bottom-left */
		 a,  a,  a,  1.0f, 1.0f, /* top-right */
		-a,  a,  a,  0.0f, 1.0f, /* top-left */
		-a, -a,  a,  0.0f, 0.0f, /* bottom-left */
		 a, -a,  a,  1.0f, 0.0f, /* bottom-right */
		 a,  a,  a,  1.0f, 1.0f, /* top-right */

		/* NOTE(pryazha): Left face */
		-a, -a, -a,  0.0f, 0.0f, /* bottom-left */
		-a,  a,  a,  1.0f, 1.0f, /* top-right */
		-a,  a, -a,  0.0f, 1.0f, /* top-left */
		-a, -a, -a,  0.0f, 0.0f, /* bottom-left */
		-a, -a,  a,  1.0f, 0.0f, /* bottom-right */
		-a,  a,  a,  1.0f, 1.0f, /* top-right */

		/* NOTE(pryazha): Right face */
		 a, -a,  a,  0.0f, 0.0f, /* bottom-left */
		 a,  a, -a,  1.0f, 1.0f, /* top-right */
		 a,  a,  a,  0.0f, 1.0f, /* top-left */
		 a, -a,  a,  0.0f, 0.0f, /* bottom-left */
		 a, -a, -a,  1.0f, 0.0f, /* bottom-right */
		 a,  a, -a,  1.0f, 1.0f, /* top-right */

		/* NOTE(pryazha): Top face */
		-a,  a,  a,  0.0f, 0.0f, /* bottom-left */
		 a,  a, -a,  1.0f, 1.0f, /* top-right */
		-a,  a, -a,  0.0f, 1.0f, /* top-left */
		-a,  a,  a,  0.0f, 0.0f, /* bottom-left */
		 a,  a,  a,  1.0f, 0.0f, /* bottom-right */
		 a,  a, -a,  1.0f, 1.0f, /* top-right */

		/* NOTE(pryazha): Bottom face */
		-a, -a, -a,  0.0f, 0.0f, /* bottom-left */
		 a, -a,  a,  1.0f, 1.0f, /* top-right */
		-a, -a,  a,  0.0f, 1.0f, /* top-left */
		-a, -a, -a,  0.0f, 0.0f, /* bottom-left */
		 a, -a, -a,  1.0f, 0.0f, /* bottom-right */
		 a, -a,  a,  1.0f, 1.0f, /* top-right */
	};

	F32 cube_vertices_cw[] = {
		/* NOTE(pryazha): Back face */
		 a, -a, -a,  0.0f, 0.0f, /* bottom-left */
		-a,  a, -a,  1.0f, 1.0f, /* top-right */
		-a, -a, -a,  1.0f, 0.0f, /* bottom-right */
		 a, -a, -a,  0.0f, 0.0f, /* bottom-left */
		 a,  a, -a,  0.0f, 1.0f, /* top-left */
		-a,  a, -a,  1.0f, 1.0f, /* top-right */

		/* NOTE(pryazha): Front face */
		-a, -a,  a,  0.0f, 0.0f, /* bottom-left */
		 a,  a,  a,  1.0f, 1.0f, /* top-right */
		 a, -a,  a,  1.0f, 0.0f, /* bottom-right */
		-a, -a,  a,  0.0f, 0.0f, /* bottom-left */
		-a,  a,  a,  0.0f, 1.0f, /* top-left */
		 a,  a,  a,  1.0f, 1.0f, /* top-right */

		/* NOTE(pryazha): Left face */
		-a, -a, -a,  0.0f, 0.0f, /* bottom-left */
		-a,  a,  a,  1.0f, 1.0f, /* top-right */
		-a, -a,  a,  1.0f, 0.0f, /* bottom-right */
		-a, -a, -a,  0.0f, 0.0f, /* bottom-left */
		-a,  a, -a,  0.0f, 1.0f, /* top-left */
		-a,  a,  a,  1.0f, 1.0f, /* top-right */

		/* NOTE(pryazha): Right face */
		 a, -a,  a,  0.0f, 0.0f, /* bottom-left */
		 a,  a, -a,  1.0f, 1.0f, /* top-right */
		 a, -a, -a,  1.0f, 0.0f, /* bottom-right */
		 a, -a,  a,  0.0f, 0.0f, /* bottom-left */
		 a,  a,  a,  0.0f, 1.0f, /* top-left */
		 a,  a, -a,  1.0f, 1.0f, /* top-right */

		/* NOTE(pryazha): Top face */
		-a,  a,  a,  0.0f, 0.0f, /* bottom-left */
		 a,  a, -a,  1.0f, 1.0f, /* top-right */
		 a,  a,  a,  1.0f, 0.0f, /* bottom-right */
		-a,  a,  a,  0.0f, 0.0f, /* bottom-left */
		-a,  a, -a,  0.0f, 1.0f, /* top-left */
		 a,  a, -a,  1.0f, 1.0f, /* top-right */

		/* NOTE(pryazha): Bottom face */
		-a, -a, -a,  0.0f, 0.0f, /* bottom-left */
		 a, -a,  a,  1.0f, 1.0f, /* top-right */
		 a, -a, -a,  1.0f, 0.0f, /* bottom-right */
		-a, -a, -a,  0.0f, 0.0f, /* bottom-left */
		-a, -a,  a,  0.0f, 1.0f, /* top-left */
		 a, -a,  a,  1.0f, 1.0f, /* top-right */
	};

	U32 cube_vao, vbo;
	glGenVertexArrays(1, &cube_vao);
	glBindVertexArray(cube_vao);
	glGenBuffers(1, &vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(cube_vertices_cw), cube_vertices_cw, 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 cube_transform = transform_default();

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

	F32 d_angle = 45.0f;

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

		/* 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);
		cube_transform = transform_rotate(cube_transform,
						  v3f(0.0f, d_angle*state.dt, 0.0f));
		model = transform_apply(cube_transform);

		glViewport(0, 0, width, height);
		glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glUseProgram(shader);
		glBindTexture(GL_TEXTURE_2D, grid_texture);
		shader_set_mat4fv(shader, "model", model);
		shader_set_mat4fv(shader, "view", view);
		shader_set_mat4fv(shader, "proj", proj);
		glBindVertexArray(cube_vao);
		glDrawArrays(GL_TRIANGLES, 0, 36);
		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);
}