#ifndef FREE_GLYPH_H
#define FREE_GLYPH_H

#include <stdbool.h>
#include <stdint.h>

#include <ft2build.h>
#include FT_FREETYPE_H

#include "vec2.h"
#include "vec4.h"
#include "glyph_attr.h"


struct glyph_info {
	struct vec2i64 a;
	struct {
		uint32_t w;
		uint32_t h;
		int32_t l;
		int32_t t;
	} b;

	float tx;
};

struct free_glyph {
	struct vec2f pos;
	struct vec2f size;
	struct vec2f uv_pos;
	struct vec2f uv_size;
	struct vec4f fg_color;
	struct vec4f bg_color;
};

DA_DEF_STRUCT(struct free_glyph, free_glyph_da);

struct free_glyph_render {
	uint32_t font_tex;
	struct vec2ui atlas_dim;
	struct free_glyph_da glyphs;
	struct {
		uint32_t vert;
		uint32_t frag;
	} shaders;
	uint32_t prog;
	struct {
		int32_t resolution;
		int32_t time;
		int32_t camera;
	} uniforms;
	uint32_t vao;
	uint32_t vbo;

	struct glyph_info g_infos[128];
};


enum free_glyph_attr_enum {
	FREE_GLYPH_ATTR_POS = 0,
	FREE_GLYPH_ATTR_SIZE,
	FREE_GLYPH_ATTR_UV_POS,
	FREE_GLYPH_ATTR_UV_SIZE,
	FREE_GLYPH_ATTR_FG_COLOR,
	FREE_GLYPH_ATTR_BG_COLOR,
	FREE_GLYPH_ATTR_TOTAL
};

struct free_glyph_render free_glyph_render_create(FT_Face font_face,
						  const char *vert_path,
						  const char *frag_path);

struct free_glyph_da free_glyph_da_create(void);
const struct glyph_attr* free_glyph_get_attrs(void);

void free_glyph_render_use(struct free_glyph_render fgr);

float free_glyph_render_calc_col(struct free_glyph_render fgr, struct app app,
				 const char *cstr, size_t cstr_size,
				 struct vec2f pos, size_t index);

float free_glyph_render_calc_col_buffer(struct free_glyph_render fgr,
					 struct app app);

struct free_glyph_render free_glyph_render_calc(
	struct free_glyph_render fgr, struct app app,
	const char *cstr, size_t cstr_size, struct vec2f pos,
	struct vec4f fg_color, struct vec4f bg_color);
struct free_glyph_render free_glyph_render_calc_buffer(
	struct free_glyph_render fgr, struct app app,
	struct vec2f pos);

void free_glyph_render_sync(struct free_glyph_render fgr);
struct free_glyph_render free_glyph_render_gen_buffer(
	struct free_glyph_render fgr);

struct free_glyph_render free_glyph_render_load_texture_atlas_or_exit(
	struct free_glyph_render fgr,
	FT_Face font_face);

struct vec2ui free_glyph_render_calc_atlas_dimensions(FT_Face font_face);

#if defined(FREE_GLYPH_IMP) || defined(IMP)

#include "shader.h"
#include "gl_util.h"

struct free_glyph_render
free_glyph_render_create(FT_Face font_face, const char *vert_path,
			 const char *frag_path)
{
	struct free_glyph_render fgr = {0};
	fgr = free_glyph_render_load_texture_atlas_or_exit(fgr, font_face);
	fgr.glyphs = free_glyph_da_create();

	fgr.shaders.vert = shader_compile_file_or_exit(
		vert_path, GL_VERTEX_SHADER);
	fgr.shaders.frag = shader_compile_file_or_exit(
		frag_path, GL_FRAGMENT_SHADER);
	fgr.prog = program_link_or_exit(fgr.shaders.vert, fgr.shaders.frag);

	glUseProgram(fgr.prog);

	fgr.uniforms.resolution = get_uniform_or_exit(fgr.prog, "resolution");
	fgr.uniforms.time = get_uniform_or_exit(fgr.prog, "time");
	fgr.uniforms.camera = get_uniform_or_exit(fgr.prog, "camera");

	fgr = free_glyph_render_gen_buffer(fgr);

	return fgr;
}

struct free_glyph_da
free_glyph_da_create(void)
{
	struct free_glyph_da glys;
	glys.cap = 1024 * 1024;
	glys.size = 0;
	glys.items = calloc(glys.cap, sizeof(struct free_glyph));
	return glys;
}

const struct glyph_attr *
free_glyph_get_attrs(void)
{
	static const struct glyph_attr
		free_glyph_attr[FREE_GLYPH_ATTR_TOTAL] = {
		[FREE_GLYPH_ATTR_POS] = {
			.size = 2,
			.type = GL_FLOAT,
			.norm = GL_FALSE,
			.stride = sizeof(struct free_glyph),
			.offset = offsetof(struct free_glyph, pos)
		},
		[FREE_GLYPH_ATTR_SIZE] = {
			.size = 2,
			.type = GL_FLOAT,
			.norm = GL_FALSE,
			.stride = sizeof(struct free_glyph),
			.offset = offsetof(struct free_glyph, size)
		},
		[FREE_GLYPH_ATTR_UV_POS] = {
			.size = 2,
			.type = GL_FLOAT,
			.norm = GL_FALSE,
			.stride = sizeof(struct free_glyph),
			.offset = offsetof(struct free_glyph, uv_pos)
		},
		[FREE_GLYPH_ATTR_UV_SIZE] = {
			.size = 2,
			.type = GL_FLOAT,
			.norm = GL_FALSE,
			.stride = sizeof(struct free_glyph),
			.offset = offsetof(struct free_glyph, uv_size)
		},
		[FREE_GLYPH_ATTR_FG_COLOR] = {
			.size = 4,
			.type = GL_FLOAT,
			.norm = GL_FALSE,
			.stride = sizeof(struct free_glyph),
			.offset = offsetof(struct free_glyph, fg_color)
		},
		[FREE_GLYPH_ATTR_BG_COLOR] = {
			.size = 4,
			.type = GL_FLOAT,
			.norm = GL_FALSE,
			.stride = sizeof(struct free_glyph),
			.offset = offsetof(struct free_glyph, bg_color)
		},
	};

	return free_glyph_attr;
}

void
free_glyph_render_use(struct free_glyph_render fgr)
{
	glBindVertexArray(fgr.vao);
	glBindBuffer(GL_ARRAY_BUFFER, fgr.vbo);
	glUseProgram(fgr.prog);
}

float
free_glyph_render_calc_col(struct free_glyph_render fgr, struct app app,
			   const char *cstr, size_t cstr_size, struct vec2f pos,
			   size_t index)
{
	struct vec2f pen = pos;
	struct vec2f dim = vec2f_from_ui(fgr.atlas_dim);
	if ( index > cstr_size ) {
		index = cstr_size - 1;
	}
	for ( size_t i = 0; i < index; ++i ) {
		char c = cstr[i];
		if ( c == '\n' ) {
			pen.x = 0;
			pen.y -= dim.y;
			continue;
		}
		struct glyph_info gi = fgr.g_infos[(int64_t)c];
		if ( c == '\t' ) {
			c = ' ';
			gi = fgr.g_infos[(int64_t)c];
			gi.a.x *= app.cfg.tab_size;
		}

		pen.x += (float) gi.a.x;
		pen.y += (float) gi.a.y;
	}
	return pen.x;
}

float
free_glyph_render_calc_col_buffer(struct free_glyph_render fgr,
				  struct app app)
{
	return free_glyph_render_calc_col(fgr, app, app.buf.data.items,
					  app.buf.data.size, vec2fs(0),
					  app.buf.cur);
}

struct free_glyph_render
free_glyph_render_calc(struct free_glyph_render fgr, struct app app,
		       const char *cstr, size_t cstr_size, struct vec2f pos,
		       struct vec4f fg_color, struct vec4f bg_color)
{
	struct vec2f pen = pos;
	struct vec2f dim = vec2f_from_ui(fgr.atlas_dim);
	for ( size_t i = 0; i < cstr_size; ++i ) {
		char c = cstr[i];
		if ( c == '\n' ) {
			pen.x = 0;
			pen.y -= dim.y;
			continue;
		}
		struct glyph_info gi = fgr.g_infos[(int64_t)c];
		if ( c == '\t' ) {
			c = ' ';
			gi = fgr.g_infos[(int64_t)c];
			gi.a.x *= app.cfg.tab_size;
		}

		float w = (float)gi.b.w;
		float h = (float)gi.b.h;

		struct free_glyph gly = {
			.pos = {
				pen.x + (float) gi.b.l,
				pen.y + (float) gi.b.t
			},
			.size = {w, -h},
			.uv_pos = { gi.tx, 0.0f },
			.uv_size = { w / dim.x, h / dim.y },
			.fg_color = fg_color,
			.bg_color = bg_color,
		};

		pen.x += (float) gi.a.x;
		pen.y += (float) gi.a.y;

		DA_APPEND(fgr.glyphs, gly);
	}
	return fgr;
}

struct free_glyph_render
free_glyph_render_calc_buffer(struct free_glyph_render fgr,
			      struct app app,
			      struct vec2f size)
{
	return free_glyph_render_calc(fgr, app,
				      app.buf.data.items, app.buf.data.size,
				      size, vec4fs(1), vec4fs(0));
}

void
free_glyph_render_sync(struct free_glyph_render fgr)
{
	glBufferSubData(GL_ARRAY_BUFFER, 0,
			(ssize_t) (fgr.glyphs.size * sizeof(struct free_glyph)),
			fgr.glyphs.items);
}

struct free_glyph_render
free_glyph_render_gen_buffer(struct free_glyph_render fgr)
{
	glGenVertexArrays(1, &fgr.vao);
	glBindVertexArray(fgr.vao);

	glGenBuffers(1, &fgr.vbo);
	glBindBuffer(GL_ARRAY_BUFFER, fgr.vbo);
	glBufferData(GL_ARRAY_BUFFER,
		     (ssize_t)(fgr.glyphs.cap * sizeof(struct free_glyph)),
		     fgr.glyphs.items,
		     GL_DYNAMIC_DRAW);
	glyph_attr_declare(free_glyph_get_attrs(), FREE_GLYPH_ATTR_TOTAL);
	return fgr;
}

struct free_glyph_render
free_glyph_render_load_texture_atlas_or_exit(struct free_glyph_render fgr,
					     FT_Face font_face)
{
	struct vec2ui dim = free_glyph_render_calc_atlas_dimensions(font_face);
	fgr.atlas_dim = dim;

	glActiveTexture(GL_TEXTURE0);
	glGenTextures(1, &fgr.font_tex);
	glBindTexture(GL_TEXTURE_2D, fgr.font_tex);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, (int32_t)dim.x, (int32_t)dim.y,
		     0, GL_RED, GL_UNSIGNED_BYTE, 0);

	FT_GlyphSlot g = font_face->glyph;
	uint64_t x = 0;
	for ( uint64_t i = 0; i < 128; ++i ) {
		if ( FT_Load_Char(font_face, i, FT_LOAD_RENDER) ) {
			fprintf(stderr, "[ERROR] Failed to load char: %c\n",
				(char)i);
			exit(EXIT_FAILURE);
		}
		if ( FT_Render_Glyph(g, FT_RENDER_MODE_NORMAL) ) {
			fprintf(stderr, "[ERROR] Failed to render char: %c\n",
				(char)i);
			exit(EXIT_FAILURE);
		}

		glTexSubImage2D(GL_TEXTURE_2D, 0, (int32_t) x, 0,
				(int32_t) g->bitmap.width,
				(int32_t) g->bitmap.rows,
				GL_RED, GL_UNSIGNED_BYTE,
				g->bitmap.buffer);

		fgr.g_infos[i] = (struct glyph_info) {
			.a = {
				.x = g->advance.x >> 6,
				.y = g->advance.y >> 6,
			},
			.b = {
				.w = g->bitmap.width,
				.h = g->bitmap.rows,
				.l = g->bitmap_left,
				.t = g->bitmap_top,
			},
			.tx = ((float) x) / ((float) dim.x),
		};

		x += g->bitmap.width;
	}

	return fgr;
}

struct vec2ui
free_glyph_render_calc_atlas_dimensions(FT_Face font_face)
{
	struct vec2ui dim = {0};

	for ( uint64_t i = 0; i < 128; ++i ) {
		if ( FT_Load_Char(font_face, i, FT_LOAD_RENDER) ) {
			fprintf(stderr, "[WARNING] Failed to load char: %c\n",
				(char)i);
			continue;
		}

		dim.x += font_face->glyph->bitmap.width;
		uint32_t row = font_face->glyph->bitmap.rows;
		dim.y = ( dim.y < row ) * row
			+ ( ! (dim.y < row) ) * dim.y;
	}

	return dim;
}


#endif /* defined(FREE_GLYPH_IMP) || defined(IMP) */

#endif
