#include "2d.h"
#include "state.h"
#include "../debug/logger.h"

static const u32 PIXELS_PER_SEGMENT = 3;

// Internal use functions
static void set_texture_in_shader(r_shader *shader, r_texture *texture, v4 color = {1,1,1,1}, u32 texture_index = 0);

// Immediate functions
void r_2d_immediate_segment(v2 a, v2 b, v4 a_color, v4 b_color, f32 thickness)
{
	glDisable(GL_DEPTH_TEST);

	// Shader
	glUseProgram(r_render_state.shader_2d.id);
	glUniform1i(r_render_state.shader_2d.has_texture[0], 0);

	// Vertex buffer data
	GLuint gl_VAO, gl_VBO;
	glGenVertexArrays(1, &gl_VAO);
	glBindVertexArray(gl_VAO);
	glGenBuffers(1, &gl_VBO);
	glBindBuffer(GL_ARRAY_BUFFER, gl_VBO);

	f32 data[12] = { a.x, a.y, b.x, b.y, a_color.r, a_color.g, a_color.b, a_color.a, b_color.r, b_color.g, b_color.b, b_color.a };
	glBufferData(GL_ARRAY_BUFFER, 2*sizeof(v2)+2*sizeof(v4), data, GL_STATIC_DRAW);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(v4), (void*)(2*sizeof(v2)));

	// Draw
	glLineWidth(thickness);
	glDrawArrays(GL_LINES, 0, 2);

	// Deinit
	glDeleteBuffers(1, &gl_VBO);
	glDeleteVertexArrays(1, &gl_VAO);

	glEnable(GL_DEPTH_TEST);
}

void r_2d_immediate_polygonal_chain(u64 count, v2 *vertices, v4 color, f32 thickness)
{
	glDisable(GL_DEPTH_TEST);

	// Shader
	glUseProgram(r_render_state.shader_2d.id);
	glUniform1i(r_render_state.shader_2d.has_texture[0], 0);

	// Vertex buffer data
	GLuint gl_VAO, gl_VBO;
	glGenVertexArrays(1, &gl_VAO);
	glBindVertexArray(gl_VAO);
	glGenBuffers(1, &gl_VBO);
	glBindBuffer(GL_ARRAY_BUFFER, gl_VBO);

	glBufferData(GL_ARRAY_BUFFER, count*sizeof(v2), vertices, GL_STATIC_DRAW);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
	//glDisableVertexAttribArray(1);
	glVertexAttrib4f(1, color.r, color.g, color.b, color.a);

	// Draw
	glLineWidth(thickness);
	glDrawArrays(GL_LINE_STRIP, 0, count);

	// Deinit
	glDeleteBuffers(1, &gl_VBO);
	glDeleteVertexArrays(1, &gl_VAO);

	glEnable(GL_DEPTH_TEST);
}

void r_2d_immediate_triangle(v2 a, v2 b, v2 c, v4 a_color, v4 b_color, v4 c_color, v2 a_uv, v2 b_uv, v2 c_uv, r_texture *texture)
{
	glDisable(GL_DEPTH_TEST);

	// Shader
	glUseProgram(r_render_state.shader_2d.id);
	// Texture
	set_texture_in_shader(&r_render_state.shader_2d, texture);

	// Vertex buffer data
	GLuint gl_VAO, gl_VBO;
	glGenVertexArrays(1, &gl_VAO);
	glBindVertexArray(gl_VAO);
	glGenBuffers(1, &gl_VBO);
	glBindBuffer(GL_ARRAY_BUFFER, gl_VBO);

	f32 data[24] = {
		a.x, a.y, b.x, b.y, c.x, c.y,
		a_color.r, a_color.g, a_color.b, a_color.a,
		b_color.r, b_color.g, b_color.b, b_color.a,
		c_color.r, c_color.g, c_color.b, c_color.a,
		a_uv.x, a_uv.y, b_uv.x, b_uv.y, c_uv.x, c_uv.y
	};
	glBufferData(GL_ARRAY_BUFFER, 3*sizeof(v2)+3*sizeof(v4)+3*sizeof(v2), data, GL_STATIC_DRAW);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(v4), (void*)(3*sizeof(v2)));
	glEnableVertexAttribArray(2);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)(3*sizeof(v2)+3*sizeof(v4)));

	// Draw
	glDrawArrays(GL_TRIANGLES, 0, 3);

	// Deinit
	glDeleteBuffers(1, &gl_VBO);
	glDeleteVertexArrays(1, &gl_VAO);

	glEnable(GL_DEPTH_TEST);
}

void r_2d_immediate_quad(v2 a, v2 b, v2 c, v2 d, v4 color, v2 a_uv, v2 b_uv, v2 c_uv, v2 d_uv, r_texture *texture)
{
	glDisable(GL_DEPTH_TEST);

	// Shader
	glUseProgram(r_render_state.shader_2d.id);
	// Texture
	set_texture_in_shader(&r_render_state.shader_2d, texture);

	// Vertex buffer data
	GLuint gl_VAO, gl_VBO;
	glGenVertexArrays(1, &gl_VAO);
	glBindVertexArray(gl_VAO);
	glGenBuffers(1, &gl_VBO);
	glBindBuffer(GL_ARRAY_BUFFER, gl_VBO);

	f32 data[16] = {
		a.x, a.y, b.x, b.y, d.x, d.y, c.x, c.y,
		a_uv.x, a_uv.y, b_uv.x, b_uv.y, d_uv.x, d_uv.y, c_uv.x, c_uv.y
	};
	glBufferData(GL_ARRAY_BUFFER, 4*sizeof(v2)+4*sizeof(v2), data, GL_STATIC_DRAW);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
	//glDisableVertexAttribArray(1);
	glVertexAttrib4f(1, color.r, color.g, color.b, color.a);
	glEnableVertexAttribArray(2);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)(4*sizeof(v2)));

	// Draw
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	// Deinit
	glDeleteBuffers(1, &gl_VBO);
	glDeleteVertexArrays(1, &gl_VAO);

	glEnable(GL_DEPTH_TEST);
}

void r_2d_immediate_rectangle(Rect r, v4 color, Rect uv, r_texture *texture)
{
	v2 a = r.position + v2{r.w, 0  };
	v2 b = r.position + v2{0  , 0  };
	v2 c = r.position + v2{0  , r.h};
	v2 d = r.position + v2{r.w, r.h};
	v2 a_uv = uv.position + v2{uv.w, 0   };
	v2 b_uv = uv.position + v2{0   , 0   };
	v2 c_uv = uv.position + v2{0   , uv.h};
	v2 d_uv = uv.position + v2{uv.w, uv.h};
	r_2d_immediate_quad(a, b, c, d, color, a_uv, b_uv, c_uv, d_uv, texture);
}

void r_2d_immediate_rounded_rectangle(Rect r, f32 radius, v4 color)
{
	radius = clamp(0, minimum(abs(r.w), abs(r.h)) / 2, radius);

	// Should compute PIXELS_PER_SEGMENT from the size of the radius
	u32 num_of_segments = floor(0.5 + radius * TAU * 0.25 / PIXELS_PER_SEGMENT);

	// Split into 9 sections:
	// - 5 quads (center, top, bottom, left, right)
	// - 4 semicircles (corners)
	// Inner vertices (CCW starting from 1st quadrant/upper-right)
	v2 i0, i1, i2, i3;
	i0.x = r.x + r.w - radius;
	i0.y = r.y          + radius;
	i1.x = r.x          + radius;
	i1.y = r.y          + radius;
	i2.x = r.x          + radius;
	i2.y = r.y + r.h - radius;
	i3.x = r.x + r.w - radius;
	i3.y = r.y + r.h - radius;

	// Outer vertices
	v2 o0, o1, o2, o3, o4, o5, o6, o7;
	o0.x = i0.x;
	o0.y = i0.y - radius;
	o1.x = i1.x;
	o1.y = i1.y - radius;
	o2.x = i1.x - radius;
	o2.y = i1.y;
	o3.x = i2.x - radius;
	o3.y = i2.y;
	o4.x = i2.x;
	o4.y = i2.y + radius;
	o5.x = i3.x;
	o5.y = i3.y + radius;
	o6.x = i3.x + radius;
	o6.y = i3.y;
	o7.x = i0.x + radius;
	o7.y = i0.y;

	// Reserve space for vertices
	u32 vertices_count = 30; // 5 quads, specified by 2 triangles each = 5 quads * 2 triangles * 3 vertices = 30 vertices
	vertices_count += num_of_segments * 12; // Add corner semicircles = 4 corners * N triangles * 3 vertices = N * 12


	// Build 5 quads
	v2 vertices[vertices_count] = {
		i0, i1, i2,  i0, i2, i3, // Center quad: i0, i1, i2, i3
		o0, o1, i1,  o0, i1, i0, // Top    quad: o0, o1, i1, i0
		i1, o2, o3,  i1, o3, i2, // Left   quad: i1, o2, o3, i2
		i3, i2, o4,  i3, o4, o5, // Bottom quad: i3, i2, o4, o5
		o7, i0, i3,  o7, i3, o6  // Right  quad: o7, i0, i3, o6
	};
	u32 corner_offset = 30;
	// Corner semicircles
	f32 factor = TAU * .25 / num_of_segments;
	v2 inner_vertices[4] = {i0, i1, i2, i3};
	for(u32 quadrant = 0; quadrant < 4; quadrant++)
	{
		for(u32 i = quadrant*num_of_segments; i < (quadrant+1)*num_of_segments; i++)
		{
			v2 inner = inner_vertices[quadrant];
			v2 a = inner + radius * v2{cos( i    * factor), -sin( i    * factor)};
			v2 b = inner + radius * v2{cos((i+1) * factor), -sin((i+1) * factor)};
			vertices[corner_offset + 3*i + 0] = inner;
			vertices[corner_offset + 3*i + 1] = a;
			vertices[corner_offset + 3*i + 2] = b;
		}
	}

	r_2d_immediate_mesh(vertices_count, vertices, color, NULL, NULL);
}

void r_2d_immediate_mesh(u64 count, v2 *vertices, v4 color, v2 *uvs, r_texture *texture)
{
	glDisable(GL_DEPTH_TEST);

	// Shader
	glUseProgram(r_render_state.shader_2d.id);
	// Texture
	set_texture_in_shader(&r_render_state.shader_2d, texture);

	// Vertex buffer data
	GLuint gl_VAO, gl_vertices, gl_uvs;
	glGenVertexArrays(1, &gl_VAO);
	glBindVertexArray(gl_VAO);
	glGenBuffers(1, &gl_vertices);
	glGenBuffers(1, &gl_uvs);

	glBindBuffer(GL_ARRAY_BUFFER, gl_vertices);
	glBufferData(GL_ARRAY_BUFFER, count * sizeof(v2), vertices, GL_STATIC_DRAW);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
	//glDisableVertexAttribArray(1);
	glVertexAttrib4f(1, color.r, color.g, color.b, color.a);
	glBindBuffer(GL_ARRAY_BUFFER, gl_uvs);
	glBufferData(GL_ARRAY_BUFFER, count * sizeof(v2), uvs     , GL_STATIC_DRAW);
	glEnableVertexAttribArray(2);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);

	// Draw
	glDrawArrays(GL_TRIANGLES, 0, count);

	// Deinitvoid r_2d_draw_mesh(r_2d_mesh *mesh, r_texture *texture);
	glDeleteBuffers(1, &gl_vertices);
	glDeleteBuffers(1, &gl_uvs);
	glDeleteVertexArrays(1, &gl_VAO);

	glEnable(GL_DEPTH_TEST);
}



void r_2d_immediate_rectangle_outline(Rect r, v4 color, f32 thickness)
{
	v2 vertices[5];
	vertices[0] = r.position + v2{  0,   0};
	vertices[1] = r.position + v2{  0, r.h};
	vertices[2] = r.position + v2{r.w, r.h};
	vertices[3] = r.position + v2{r.w,   0};
	vertices[4] = r.position + v2{  0,   0};
	r_2d_immediate_polygonal_chain(5, vertices, color, thickness);
}

void r_2d_immediate_rounded_rectangle_outline(Rect r, f32 radius, v4 color, f32 thickness)
{
	radius = clamp(0, minimum(abs(r.w), abs(r.h)) / 2, radius);

	// Should compute PIXELS_PER_SEGMENT from the size of the radius
	u32 num_of_segments = floor(0.5 + radius * TAU * 0.25 / PIXELS_PER_SEGMENT);

	// Split into 9 sections:
	// - 5 quads (center, top, bottom, left, right)
	// - 4 semicircles (corners)
	// Inner vertices (CCW starting from 1st quadrant/upper-right)
	v2 i0, i1, i2, i3;
	i0.x = r.x + r.w - radius;
	i0.y = r.y          + radius;
	i1.x = r.x          + radius;
	i1.y = r.y          + radius;
	i2.x = r.x          + radius;
	i2.y = r.y + r.h - radius;
	i3.x = r.x + r.w - radius;
	i3.y = r.y + r.h - radius;

	// Reserve space for vertices
	u32 vertices_count = 1 + 4 + 4*num_of_segments; // Starting vertex (1) + one for each side (4) + one for each segment (4*num_of_segments)

	v2 inner_vertices[4] = {i0, i1, i2, i3};

	v2 vertices[vertices_count] = {
		i3 + v2{radius, 0} // Starting vertex
	};
	u32 v_index = 1;

	// Corner semicircles
	f32 factor = TAU * .25 / num_of_segments;
	for(u32 quadrant = 0; quadrant < 4; quadrant++)
	{
		v2 inner = inner_vertices[quadrant];
		for(u32 i = quadrant*num_of_segments; i < (quadrant+1)*num_of_segments; i++)
		{
			v2 a = inner + radius * v2{cos( i    * factor), -sin( i    * factor)};
			vertices[v_index] = a;
			v_index++;
		}
		vertices[v_index] = inner + radius * v2{cos( (quadrant+1)*num_of_segments    * factor), -sin( (quadrant+1)*num_of_segments    * factor)};
		v_index++;
	}

	r_2d_immediate_polygonal_chain(vertices_count, vertices, color, thickness);
}



void r_2d_draw_mesh(r_2d_mesh *mesh, r_texture *texture)
{
	glDisable(GL_DEPTH_TEST);

	// Shader
	glUseProgram(r_render_state.shader_2d.id);
	// Texture
	set_texture_in_shader(&r_render_state.shader_2d, texture);

	// Draw
	glBindVertexArray(mesh->gl_VAO);
	glDrawArrays(GL_TRIANGLES, 0, mesh->count);

	glEnable(GL_DEPTH_TEST);
}



// Internal use functions
static void set_texture_in_shader(r_shader *shader, r_texture *texture, v4 color, u32 texture_index)
{
	// Remember to call glUseProgram before using this
	if(texture)
	{
		glUniform1i(shader->has_texture[texture_index], 1);
		glUniform1i(shader->texture[texture_index], 0);
		glUniform1i(shader->texture_channels[texture_index], r_texture_channels(texture));
		glActiveTexture(GL_TEXTURE0 + texture_index);
		glBindTexture(GL_TEXTURE_2D, texture->gl_id);
		glUniform4f(shader->color[texture_index], color.r, color.g, color.b, color.a);
	}
	else
	{
		glUniform1i(shader->has_texture[texture_index], 0);
	}
}
