#include "primitives.h"
#include "../platform.h"
#include <string.h>

// GL utils
static GLint  gl_texture_internal_format(u32 texture_flags);
static GLenum gl_texture_format         (u32 texture_flags);
static GLenum gl_texture_type           (u32 texture_flags);

// Texture
r_texture r_texture_create(u8 *data, v2s size, u32 flags)
{
	r_texture texture;

	texture.data = data;
	texture.size = size;
	texture.flags = flags | R_TEXTURE_INITIALIZED;

	glGenTextures(1, &texture.gl_id);
	glBindTexture(GL_TEXTURE_2D, texture.gl_id);

	GLint internal_format = gl_texture_internal_format(flags);
	GLenum format         = gl_texture_format         (flags);
	GLenum type           = gl_texture_type           (flags);
	glTexImage2D(GL_TEXTURE_2D, 0, internal_format, size.x, size.y, 0, format, type, data);

	if(flags & R_TEXTURE_NO_MIPMAP)
	{
		// The default for GL_TEXTURE_{MIN,MAG}_FILTER is GL_NEAREST_MIPMAP_LINEAR, but we are not using mipmaps for this texture. If we don't change this parameter, the image will not render.
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	}
	else
	{
		glGenerateMipmap(GL_TEXTURE_2D);
	}

	if(flags & R_TEXTURE_DONT_OWN)
		texture.data = NULL;

	return texture;
}

void r_texture_resize(r_texture *texture, v2s size)
{
	glBindTexture(GL_TEXTURE_2D, texture->gl_id);
	GLint internal_format = gl_texture_internal_format(texture->flags);
	GLenum format         = gl_texture_format         (texture->flags);
	GLenum type           = gl_texture_type           (texture->flags);
	glTexImage2D(GL_TEXTURE_2D, 0, internal_format, size.x, size.y, 0, format, type, NULL);
	texture->size = size;
}

void r_texture_update(r_texture *texture, u8 *data, v2s size, v2s position, u32 stride)
{
	glBindTexture(GL_TEXTURE_2D, texture->gl_id);
	GLenum format = gl_texture_format(texture->flags);
	GLenum type   = gl_texture_type  (texture->flags);
	glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
	glTexSubImage2D(GL_TEXTURE_2D, 0, position.x, position.y, size.x, size.y, format, type, data);
	glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);

	if(texture->flags & R_TEXTURE_NO_MIPMAP)
	{
		// The default for GL_TEXTURE_{MIN,MAG}_FILTER is GL_NEAREST_MIPMAP_LINEAR, but we are not using mipmaps for this texture. If we don't change this parameter, the image will not render.
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	}
	else
	{
		glGenerateMipmap(GL_TEXTURE_2D);
	}
}

void r_texture_destroy(r_texture *texture)
{
	glDeleteTextures(1, &texture->gl_id);

	if(texture->data && !(texture->flags & R_TEXTURE_DONT_OWN))
		p_free(texture->data);
	texture->size = {0,0};
	texture->flags = R_TEXTURE_DESTROYED;
}

s32 r_texture_channels(r_texture *texture)
{
	if(texture->flags & R_TEXTURE_ALPHA)
		return 1;
	if(texture->flags & R_TEXTURE_RGB)
		return 3;
	if(texture->flags & (R_TEXTURE_RGBA | R_TEXTURE_SRGB))
		return 4;
	return 4;
}


// Cubemap
r_cubemap r_cubemap_create(float *data[6], v2s size, u32 flags)
{
	r_cubemap cubemap;

	for(u32 i = 0; i < 6; i++)
		cubemap.data[i] = data[i];
	cubemap.size = size;
	cubemap.flags = flags | R_CUBEMAP_INITIALIZED;

	glGenTextures(1, &cubemap.gl_id);
	glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.gl_id);
	for(u32 i = 0; i < 6; i++)
	{
		glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size.x, size.y, 0, GL_RGB, GL_FLOAT, data[i]);

		if(flags & R_CUBEMAP_DONT_OWN)
			cubemap.data[i] = NULL;
	}
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glGenerateMipmap(GL_TEXTURE_CUBE_MAP);

	return cubemap;
}

void r_cubemap_destroy(r_cubemap *cubemap)
{
	glDeleteTextures(1, &cubemap->gl_id);

	for(u32 i = 0; i < 6; i++)
	{
		if(cubemap->data[i] && !(cubemap->flags & R_CUBEMAP_DONT_OWN))
			p_free(cubemap->data[i]);
	}
	cubemap->size = {0,0};
	cubemap->flags = R_CUBEMAP_DESTROYED;
}


// 2D mesh
r_2d_mesh r_2d_mesh_create(u64 count, v2 *vertices, v4 *colors, v2 *uvs)
{
	r_2d_mesh mesh;

	mesh.vertices = vertices;
	mesh.colors = colors;
	mesh.uvs = uvs;
	mesh.count = count;

	glGenVertexArrays(1, &mesh.gl_VAO);
	glBindVertexArray(mesh.gl_VAO);

	glGenBuffers(1, &mesh.gl_vertex_buffer);
	glGenBuffers(1, &mesh.gl_color_buffer);
	glGenBuffers(1, &mesh.gl_uv_buffer);

	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_vertex_buffer);
	glBufferData(GL_ARRAY_BUFFER, mesh.count * sizeof(v2), mesh.vertices, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);

	glEnableVertexAttribArray(1);
	glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_color_buffer);
	glBufferData(GL_ARRAY_BUFFER, mesh.count * sizeof(v4), mesh.colors, GL_STATIC_DRAW);
	glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(v4), (void*)0);

	if(mesh.uvs)
	{
		glEnableVertexAttribArray(2);
		glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_uv_buffer);
		glBufferData(GL_ARRAY_BUFFER, mesh.count * sizeof(v2), mesh.uvs, GL_STATIC_DRAW);
		glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
	}

	return mesh;
}

void r_2d_mesh_destroy(r_2d_mesh *mesh)
{
	glBindVertexArray(mesh->gl_VAO);
	glDeleteBuffers(1, &mesh->gl_vertex_buffer);
	glDeleteBuffers(1, &mesh->gl_color_buffer);
	glDeleteBuffers(1, &mesh->gl_uv_buffer);
	glDeleteVertexArrays(1, &mesh->gl_VAO);

	if(mesh->vertices)
		p_free(mesh->vertices);
	if(mesh->colors)
		p_free(mesh->colors);
	if(mesh->uvs)
		p_free(mesh->uvs);
}


// 3D mesh
r_mesh r_mesh_create(u64 indices_count, u32 *indices, u64 vertices_count, v3 *vertices, v3 *normals, v3 *tangents, v2 *uvs)
{
	r_mesh mesh;

	mesh.vertices = vertices;
	mesh.normals = normals;
	mesh.tangents = tangents;
	mesh.uvs = uvs;
	mesh.vertices_count = vertices_count;
	mesh.indices = indices;
	mesh.indices_count = indices_count;

	glGenVertexArrays(1, &mesh.gl_VAO);
	glBindVertexArray(mesh.gl_VAO);

	glGenBuffers(1, &mesh.gl_vertex_buffer);
	glGenBuffers(1, &mesh.gl_normal_buffer);
	glGenBuffers(1, &mesh.gl_tangent_buffer);
	glGenBuffers(1, &mesh.gl_uv_buffer);
	glGenBuffers(1, &mesh.gl_index_buffer);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl_index_buffer);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh.indices_count * sizeof(u32), mesh.indices, GL_STATIC_DRAW);

	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_vertex_buffer);
	glBufferData(GL_ARRAY_BUFFER, mesh.vertices_count * sizeof(v3), mesh.vertices, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(v3), (void*)0);

	if(mesh.normals)
	{
		glEnableVertexAttribArray(1);
		glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_normal_buffer);
		glBufferData(GL_ARRAY_BUFFER, mesh.vertices_count * sizeof(v3), mesh.normals, GL_STATIC_DRAW);
		glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(v3), (void*)0);
	}

	if(mesh.tangents)
	{
		glEnableVertexAttribArray(2);
		glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_tangent_buffer);
		glBufferData(GL_ARRAY_BUFFER, mesh.vertices_count * sizeof(v3), mesh.tangents, GL_STATIC_DRAW);
		glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(v3), (void*)0);
	}

	if(mesh.uvs)
	{
		glEnableVertexAttribArray(3);
		glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_uv_buffer);
		glBufferData(GL_ARRAY_BUFFER, mesh.vertices_count * sizeof(v2), mesh.uvs, GL_STATIC_DRAW);
		glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
	}

	return mesh;
}

void r_mesh_destroy(r_mesh *mesh)
{
	glBindVertexArray(mesh->gl_VAO);
	glDeleteBuffers(1, &mesh->gl_vertex_buffer);
	glDeleteBuffers(1, &mesh->gl_normal_buffer);
	glDeleteBuffers(1, &mesh->gl_uv_buffer);
	glDeleteBuffers(1, &mesh->gl_index_buffer);
	glDeleteVertexArrays(1, &mesh->gl_VAO);

	if(mesh->vertices)
		p_free(mesh->vertices);
	if(mesh->normals)
		p_free(mesh->normals);
	if(mesh->uvs)
		p_free(mesh->uvs);
	if(mesh->indices)
		p_free(mesh->indices);
}


// Common meshes
r_mesh r_mesh_build_cube()
{
	v3 v[24] = {
		{0.5, 0.5, -0.5},
		{0.5, 0.5, -0.5},
		{0.5, 0.5, -0.5},
		{0.5, -0.5, -0.5},
		{0.5, -0.5, -0.5},
		{0.5, -0.5, -0.5},
		{0.5, 0.5, 0.5},
		{0.5, 0.5, 0.5},
		{0.5, 0.5, 0.5},
		{0.5, -0.5, 0.5},
		{0.5, -0.5, 0.5},
		{0.5, -0.5, 0.5},
		{-0.5, 0.5, -0.5},
		{-0.5, 0.5, -0.5},
		{-0.5, 0.5, -0.5},
		{-0.5, -0.5, -0.5},
		{-0.5, -0.5, -0.5},
		{-0.5, -0.5, -0.5},
		{-0.5, 0.5, 0.5},
		{-0.5, 0.5, 0.5},
		{-0.5, 0.5, 0.5},
		{-0.5, -0.5, 0.5},
		{-0.5, -0.5, 0.5},
		{-0.5, -0.5, 0.5}
	};
	v3 n[24] = {
		{0.0, 1.0, 0.0},
		{1.0, 0.0, 0.0},
		{0.0, 0.0, -1.0},
		{0.0, -1.0, 0.0},
		{1.0, 0.0, 0.0},
		{0.0, 0.0, -1.0},
		{0.0, 1.0, 0.0},
		{0.0, 0.0, 1.0},
		{1.0, 0.0, 0.0},
		{0.0, 0.0, 1.0},
		{0.0, -1.0, 0.0},
		{1.0, 0.0, 0.0},
		{0.0, 1.0, 0.0},
		{-1.0, 0.0, 0.0},
		{0.0, 0.0, -1.0},
		{-1.0, 0.0, 0.0},
		{0.0, -1.0, 0.0},
		{0.0, 0.0, -1.0},
		{0.0, 1.0, 0.0},
		{0.0, 0.0, 1.0},
		{-1.0, 0.0, 0.0},
		{0.0, 0.0, 1.0},
		{-1.0, 0.0, 0.0},
		{0.0, -1.0, 0.0}
	};
	u32 i[36] = {12, 6, 0, 7, 21, 9, 20, 15, 22, 3, 23, 16, 1, 11, 4, 14, 5, 17, 12, 18, 6, 7, 19, 21, 20, 13, 15, 3, 10, 23, 1, 8, 11, 14, 2, 5};

	v3  *vertices = (v3*)p_alloc(24 * sizeof(v3));
	v3  *normals  = (v3*)p_alloc(24 * sizeof(v3));
	u32 *indices  = (u32*)p_alloc(36 * sizeof(u32));

	memcpy(vertices, v, 24 * sizeof(v3));
	memcpy(normals , n, 24 * sizeof(v3));
	memcpy(indices , i, 36 * sizeof(u32));

	return r_mesh_create(36, indices, 24, vertices, normals, NULL, NULL);
}



// Framebuffers
r_framebuffer r_framebuffer_create(v2s size, u32 flags)
{
	r_framebuffer fb;
	fb.flags = flags;
	fb.size = size;

	glGenFramebuffers(1, &fb.gl_id);
	glBindFramebuffer(GL_FRAMEBUFFER, fb.gl_id);

	fb.color_texture = r_texture_create(NULL, size, R_TEXTURE_RGBA | R_TEXTURE_HDR | R_TEXTURE_NO_MIPMAP);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.color_texture.gl_id, 0);

	fb.gl_depth_id = 0;
	if(flags & R_FRAMEBUFFER_DEPTH)
	{
		glGenRenderbuffers(1, &fb.gl_depth_id);
		glBindRenderbuffer(GL_RENDERBUFFER, fb.gl_depth_id);
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.x, size.y);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb.gl_depth_id);
	}

	return fb;
}

void r_framebuffer_destroy(r_framebuffer *fb)
{
	r_texture_destroy(&fb->color_texture);
	if(fb->flags & R_FRAMEBUFFER_DEPTH)
		glDeleteRenderbuffers(1, &fb->gl_depth_id);
	glDeleteFramebuffers(1, &fb->gl_id);
}

void r_framebuffer_update_size(r_framebuffer *fb, v2s size)
{
	glBindFramebuffer(GL_FRAMEBUFFER, fb->gl_id);

	r_texture_resize(&fb->color_texture, size);

	if(fb->flags & R_FRAMEBUFFER_DEPTH)
	{
		glBindRenderbuffer(GL_RENDERBUFFER, fb->gl_depth_id);
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.x, size.y);
	}

	fb->size = size;
}



// GL utils

static GLint gl_texture_internal_format(u32 texture_flags)
{
	if(texture_flags & R_TEXTURE_ALPHA)
		return GL_RED;
	else if(texture_flags & R_TEXTURE_RGB)
		return (texture_flags & R_TEXTURE_HDR) ? GL_RGB16F  : GL_RGB;
	else if(texture_flags & R_TEXTURE_RGBA)
		return (texture_flags & R_TEXTURE_HDR) ? GL_RGBA16F : GL_RGBA;
	else if(texture_flags & R_TEXTURE_SRGB)
		return GL_SRGB_ALPHA;
	return GL_RGBA;
}

static GLenum gl_texture_format(u32 texture_flags)
{
	if(texture_flags & R_TEXTURE_ALPHA)
		return GL_RED;
	else if(texture_flags & R_TEXTURE_RGB)
		return GL_RGB;
	else if(texture_flags & R_TEXTURE_RGBA)
		return GL_RGBA;
	else if(texture_flags & R_TEXTURE_SRGB)
		return GL_RGBA;
	return GL_RGBA;
}

static GLenum gl_texture_type(u32 texture_flags)
{
	if(texture_flags & R_TEXTURE_HDR)
		return GL_FLOAT;
	return GL_UNSIGNED_BYTE;
}
