#include "im_render.h"
#include "entity.h"
#include "camera.h"
#include "gl_load.h"
#include "../common/array.h"
#include "../common/num_types.h"
#include "shader.h"
#include "../common/log.h"
#include "geometry.h"

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

#define MAX_IM_VERTICES 2048
#define MAX_IM_GEOMETRIES (MAX_IM_VERTICES / 2)

static struct
{
	struct IM_Vertex current_vertices[MAX_IM_VERTICES];
	struct IM_Geom   geometries[MAX_IM_GEOMETRIES];
	uint             vao;
	uint             vbo;
	int              im_shader;
	int              curr_geom;
	int              curr_vertex;
}
IM_State;

static struct IM_Geom* active_geom = NULL;
static int current_vertex_index    = 0;

static void im_geom_reset(struct IM_Geom* geom);
static int  im_sort_func(const void* p1, const void* p2);

void im_init(void)
{
	glGenVertexArrays(1, &IM_State.vao);
	glBindVertexArray(IM_State.vao);

	glGenBuffers(1, &IM_State.vbo);
	glBindBuffer(GL_ARRAY_BUFFER, IM_State.vbo);
	GL_CHECK(glBufferData(GL_ARRAY_BUFFER, sizeof(struct IM_Vertex) * MAX_IM_VERTICES * MAX_IM_GEOMETRIES, NULL, GL_STREAM_DRAW));

	//Position
	GL_CHECK(glVertexAttribPointer(ATTRIB_LOC_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(struct IM_Vertex), 0));
	GL_CHECK(glEnableVertexAttribArray(ATTRIB_LOC_POSITION));

	////Color
	//GL_CHECK(glVertexAttribPointer(ATTRIB_LOC_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof(struct IM_Vertex), sizeof(vec3)));
	//GL_CHECK(glEnableVertexAttribArray(ATTRIB_LOC_COLOR));

	glBindBuffer(GL_ARRAY_BUFFER, 0);

	glBindVertexArray(0);

	memset(&IM_State.geometries[0], 0, sizeof(struct IM_Geom) * MAX_IM_GEOMETRIES);
	memset(&IM_State.current_vertices[0], 0, sizeof(struct IM_Vertex) * MAX_IM_VERTICES);
	IM_State.curr_geom   = -1;
	IM_State.curr_vertex =  0;

	IM_State.im_shader = shader_create("im_geom.vert", "im_geom.frag", NULL);
}

void im_cleanup(void)
{
	shader_remove(IM_State.im_shader);
	glDeleteBuffers(1, &IM_State.vbo);
	glDeleteVertexArrays(1, &IM_State.vao);

	memset(&IM_State.geometries[0], 0, sizeof(struct IM_Geom) * MAX_IM_GEOMETRIES);
	memset(&IM_State.current_vertices[0], 0, sizeof(struct IM_Vertex) * MAX_IM_VERTICES);
	IM_State.vao         =  0;
	IM_State.vbo         =  0;
	IM_State.curr_geom   = -1;
	IM_State.curr_vertex =  0;
	IM_State.im_shader   = -1;
}

void im_begin(vec3 position, quat rotation, vec3 scale, vec4 color, int draw_mode, int draw_order)
{
	if(active_geom)
	{
		log_error("im_begin", "im_begin called before im_end");
		return;
	}
	IM_State.curr_geom++;
	active_geom = &IM_State.geometries[IM_State.curr_geom];
	im_geom_reset(active_geom);
	active_geom->start_index = IM_State.curr_vertex;
	active_geom->type = IGT_DYNAMIC;
	active_geom->draw_mode = draw_mode;
	active_geom->draw_order = draw_order;
	vec3_assign(&active_geom->position, &position);
	vec3_assign(&active_geom->scale, &scale);
	vec4_assign(&active_geom->color, &color);
	quat_assign(&active_geom->rotation, &rotation);
}

void im_pos(float x, float y, float z)
{
	if(IM_State.curr_vertex == MAX_IM_VERTICES)
	{
		log_error("im_pos", "Buffer full!");
		return;
	}
	current_vertex_index++;
	vec3_fill(&IM_State.current_vertices[current_vertex_index].position, x, y, z);
	IM_State.curr_vertex++;
}

void im_box(float x, float y, float z, vec3 position, quat rotation, vec4 color, int draw_mode, int draw_order)
{
	if(active_geom)
	{
		log_error("im_box", "im_box called before im_end");
		return;
	}
	IM_State.curr_geom++;
	active_geom = &IM_State.geometries[IM_State.curr_geom];
	im_geom_reset(active_geom);
	active_geom->type = IGT_PRIMITIVE;
	active_geom->draw_mode = draw_mode;
	active_geom->draw_order = draw_order;
	active_geom->prim_geom_index = geom_create_from_file("cube.symbres");
	vec3_assign(&active_geom->position, &position);
	vec3 scale =  { x, y, z }; 
	vec3_assign(&active_geom->scale, &scale);
	vec4_assign(&active_geom->color, &color);
	quat_assign(&active_geom->rotation, &rotation);
	active_geom = NULL;
}

void im_sphere(float radius, vec3 position, quat rotation, vec4 color, int draw_mode, int draw_order)
{
	if(active_geom)
	{
		log_error("im_sphere", "im_sphere called before im_end");
		return;
	}
	IM_State.curr_geom++;
	active_geom = &IM_State.geometries[IM_State.curr_geom];
	im_geom_reset(active_geom);
	active_geom->type = IGT_PRIMITIVE;
	active_geom->draw_mode = draw_mode;
	active_geom->draw_order = draw_order;
	active_geom->prim_geom_index = geom_create_from_file("sphere.symbres");
	vec3_assign(&active_geom->position, &position);
	vec3 scale =  { radius, radius, radius }; 
	vec3_assign(&active_geom->scale, &scale);
	quat_assign(&active_geom->rotation, &rotation);
	vec4_assign(&active_geom->color, &color);
	active_geom = NULL;
}

void im_line(vec3 p1, vec3 p2, vec3 position, quat rotation, vec4 color, int draw_order)
{
	im_begin(position, rotation, (vec3) { 1.f, 1.f, 1.f }, color, GDM_LINES, draw_order);
	im_pos(p1.x, p1.y, p1.z);
	im_pos(p2.x, p2.y, p2.z);
	im_end();
}

void im_circle(float radius, int num_divisions, bool filled, vec3 position, quat rotation, vec4 color, int draw_order)
{
	im_arc(radius, 0.f, 360.f, num_divisions, filled, position, rotation, color, draw_order);
}

void im_arc(float radius, float angle_start, float angle_end, int num_divisions, bool filled, vec3 position, quat rotation, vec4 color, int draw_order)
{
	float arc_degrees = angle_end - angle_start;
	if(fabsf(arc_degrees) < 0.01f)
		return;

	im_begin(position, rotation, (vec3) { 1.f, 1.f, 1.f }, color, filled ? GDM_TRIANGLE_FAN : GDM_LINE_LOOP, draw_order);

	if(arc_degrees != 360)
		im_pos(0.f, 0.f, 0.f);

	if(arc_degrees > 360.f)
	{
		arc_degrees = (int)arc_degrees % 360;
		angle_end = arc_degrees;
	}
	else if(arc_degrees < -360.f)
	{
		arc_degrees = (int)arc_degrees % -360;
		angle_end = arc_degrees;
	}
	
	if(fabsf(arc_degrees) < 0.01f)
		arc_degrees = arc_degrees < 0.f ? -0.01f : 0.01f;
	float increment = arc_degrees / num_divisions;
	if(fabsf(increment) < 0.01f)
		increment = increment < 0.f ? -0.01f : 0.01f;

	if(angle_start < angle_end)
	{
		int count = 0;
		for(float i = angle_start; i <= angle_end; i += increment)
		{
			count++;
 			im_pos(sinf(i * M_PI / 180.f) * radius, cosf(i * M_PI / 180.f) * radius, 0.f);
		}
	}
	else
	{
		for(float i = angle_start; i >= angle_end; i += increment)
			im_pos(sinf(i * M_PI / 180.f) * radius, cosf(i * M_PI / 180.f) * radius, 0.f);
	}
	
	im_end();
}

void im_ray(struct Ray* ray, float length, vec4 color, int draw_order)
{
	vec3 ending_pos = { 0.f, 0.f, 0.f };
	vec3_scale(&ending_pos, &ray->direction, length);
	vec3_add(&ending_pos, &ending_pos, &ray->origin);
	im_line(ray->origin, ending_pos, (vec3) { 0.f, 0.f, 0.f }, (quat) { 0.f, 0.f, 0.f, 1.f }, color, draw_order);
}

void im_ray_origin_dir(vec3 origin, vec3 direction, float length, vec4 color, int draw_order)
{
	vec3 ending_pos = { 0.f, 0.f, 0.f };
	vec3_scale(&ending_pos, &direction, length);
	vec3_add(&ending_pos, &ending_pos, &origin);
	im_line(origin, ending_pos, (vec3) { 0.f, 0.f, 0.f }, (quat) { 0.f, 0.f, 0.f, 1.f }, color, draw_order);
}

void im_end(void)
{
	active_geom->num_vertices = current_vertex_index + 1;
	GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, IM_State.vbo));
	GL_CHECK(glBufferSubData(GL_ARRAY_BUFFER,
					sizeof(struct IM_Vertex) * active_geom->start_index,
					sizeof(struct IM_Vertex) * active_geom->num_vertices,
					&IM_State.current_vertices[0]));
	
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	active_geom = NULL;
	current_vertex_index = -1;
	memset(&IM_State.current_vertices[0], 0, sizeof(struct IM_Vertex) * MAX_IM_VERTICES);
}

void im_render(struct Camera* active_viewer)
{
	if(IM_State.curr_geom == -1)
		return;

	/* If there are more than one geometries, sort by draw order. Geometries with lower draw order get drawn first */
	if(IM_State.curr_geom + 1 > 1)
		qsort(IM_State.geometries, IM_State.curr_geom + 1, sizeof(struct IM_Geom), &im_sort_func);
	

	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
	glEnable(GL_MULTISAMPLE);
	glDisable(GL_CULL_FACE);
	shader_bind(IM_State.im_shader);
	{
		static mat4 mvp, translation, rotation, scale;

		for(int i = 0; i <= IM_State.curr_geom; i++)
		{
			struct IM_Geom* geom = &IM_State.geometries[i];
			mat4_identity(&mvp);
			
			mat4_identity(&scale);
			mat4_identity(&translation);
			mat4_identity(&rotation);

			mat4_scale(&scale, geom->scale.x, geom->scale.y, geom->scale.z);
			mat4_translate(&translation, geom->position.x, geom->position.y, geom->position.z);
			mat4_from_quat(&rotation, &geom->rotation);

			mat4_mul(&mvp, &mvp, &translation);
			mat4_mul(&mvp, &mvp, &rotation);
			mat4_mul(&mvp, &mvp, &scale);

			mat4_mul(&mvp, &active_viewer->view_proj_mat, &mvp);

			shader_set_uniform_mat4(IM_State.im_shader, "mvp", &mvp);
			shader_set_uniform_vec4(IM_State.im_shader, "geom_color", &geom->color);
			if(geom->type == IGT_DYNAMIC)
			{
				GL_CHECK(glBindVertexArray(IM_State.vao));
				GL_CHECK(glDrawArrays(draw_modes[geom->draw_mode], geom->start_index, geom->num_vertices));
				GL_CHECK(glBindVertexArray(0));
			}
			else
			{
				GL_CHECK(glPolygonMode(GL_FRONT_AND_BACK, GL_LINE));
				geom_render(geom->prim_geom_index, geom->draw_mode);
				GL_CHECK(glPolygonMode(GL_FRONT_AND_BACK, GL_FILL));
			}
		}

	}
	shader_unbind();
	glEnable(GL_CULL_FACE);
	glDisable(GL_BLEND);
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_MULTISAMPLE);

	IM_State.curr_geom   = -1;
	IM_State.curr_vertex =  0;
}

void im_geom_reset(struct IM_Geom* geom)
{
	vec3_fill(&geom->position, 0.f, 0.f, 0.f);
	quat_identity(&geom->rotation);
	vec3_fill(&geom->scale, 1.f, 1.f, 1.f);
	vec4_fill(&geom->color, 0.f, 0.f, 0.f, 0.f);
	geom->type = -1;
	geom->start_index = -1;
	geom->num_vertices = 0;
	geom->prim_geom_index = -1;
	geom->draw_mode = -1;
	geom->draw_order = -1;
}

int im_sort_func(const void* p1, const void* p2)
{
	struct IM_Geom* g1 = (struct IM_Geom*)p1;
	struct IM_Geom* g2 = (struct IM_Geom*)p2;
	if(g1->draw_order < g2->draw_order)
		return -1;
	else if(g1->draw_order == g2->draw_order)
		return 0;
	else
		return 1;
}

