#ifndef TILE_GLYPH_H
#define TILE_GLYPH_H

#include "app.h"
#include "buffer.h"

#include "glyph_attr.h"

struct tile_glyph {
	struct vec2i tile;
	int32_t c;
	struct vec4f fg_color;
	struct vec4f bg_color;
};

DA_DEF_STRUCT(struct tile_glyph, tile_glyph_da);

enum tile_glyph_attr_enum {
	TILE_GLYPH_ATTR_TILE = 0,
	TILE_GLYPH_ATTR_C,
	TILE_GLYPH_ATTR_FG_COLOR,
	TILE_GLYPH_ATTR_BG_COLOR,
	TILE_GLYPH_ATTR_TOTAL
};

struct tile_glyph_render {
	uint32_t font_tex;
	struct {
		uint32_t vert;
		uint32_t frag;
	} shaders;
	uint32_t prog;
	struct {
		int32_t resolution;
		int32_t scale;
		int32_t time;
		int32_t camera;
	} uniforms;
	uint32_t vao;
	uint32_t vbo;
	struct tile_glyph_da glyphs;
};

struct ret_uint32_t_uint32_t {
	uint32_t f1;
	uint32_t f2;
};

struct tile_glyph_render tile_glyph_render_create(const char *font_path,
						  const char *vert_path,
						  const char *frag_path);

struct tile_glyph_da tile_glyph_da_create(void);
const struct glyph_attr* tile_glyph_get_attrs(void);

struct tile_glyph_render tile_glyph_render_calc(
	struct tile_glyph_render tgr, const char *cstr, size_t cstr_size,
	struct vec2i tile, struct vec4f fg_color, struct vec4f bg_color);

struct tile_glyph_render  tile_glyph_render_calc_buffer(
	struct tile_glyph_render tgr, struct buffer buf, struct vec2i tile);

struct tile_glyph_render tile_glyph_render_sync(struct tile_glyph_render tgr);
struct tile_glyph_render tile_glyph_render_gen_buffer(
	struct tile_glyph_render tgr);

struct tile_glyph_render tile_glyph_render_load_texture_atlas_or_exit(
	struct tile_glyph_render tgr,
	const char *filepath);

struct tile_glyph_render tile_glyph_compile_shaders_or_exit(
	struct tile_glyph_render tgr,
	const char *vert_path, const char *frag_path);

#if defined(TILE_GLYPH_IMP) || defined(IMP)

#ifdef STB_IMAGE_IMPLEMENTATION
  #undef STB_IMAGE_IMPLEMENTATION
  #include "stb_image.h"
  #define STB_IMAGE_IMPLEMENTATION
#else
  #include "stb_image.h"
#endif

#include "gl_util.h"
#include "unwrap.h"

struct tile_glyph_render
tile_glyph_render_create(const char *font_path, const char *vert_path,
			 const char *frag_path)
{
	struct tile_glyph_render tgr = {0};
	tgr = tile_glyph_render_load_texture_atlas_or_exit(tgr, font_path);

	tgr.glyphs = tile_glyph_da_create();

	/* Loading shaders and linking prog */
	tgr = tile_glyph_compile_shaders_or_exit(tgr, vert_path, frag_path);
	tgr.prog = program_link_or_exit(tgr.shaders.vert, tgr.shaders.frag);

	glUseProgram(tgr.prog);

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

	glUniform1f(tgr.uniforms.scale, 1.0f);

	tgr = tile_glyph_render_gen_buffer(tgr);

	return tgr;
}

struct tile_glyph_da
tile_glyph_da_create(void)
{
	struct tile_glyph_da glys;
	glys.cap = 1024 * 1024;
	glys.size = 0;
	glys.items = calloc(glys.cap, sizeof(struct tile_glyph));
	return glys;
}

const struct glyph_attr *
tile_glyph_get_attrs(void)
{
	static const struct glyph_attr
		tile_glyph_attr[TILE_GLYPH_ATTR_TOTAL] = {
		[TILE_GLYPH_ATTR_TILE] = {
			.size = 2,
			.type = GL_INT,
			.norm = GL_FALSE,
			.stride = sizeof(struct tile_glyph),
			.offset = offsetof(struct tile_glyph, tile)
		},
		[TILE_GLYPH_ATTR_C] = {
			.size = 1,
			.type = GL_INT,
			.norm = GL_FALSE,
			.stride = sizeof(struct tile_glyph),
			.offset = offsetof(struct tile_glyph, c)
		},
		[TILE_GLYPH_ATTR_FG_COLOR] = {
			.size = 4,
			.type = GL_FLOAT,
			.norm = GL_FALSE,
			.stride = sizeof(struct tile_glyph),
			.offset = offsetof(struct tile_glyph, fg_color)
		},
		[TILE_GLYPH_ATTR_BG_COLOR] = {
			.size = 4,
			.type = GL_FLOAT,
			.norm = GL_FALSE,
			.stride = sizeof(struct tile_glyph),
			.offset = offsetof(struct tile_glyph, bg_color)
		},
	};

	return tile_glyph_attr;
}

struct tile_glyph_render
tile_glyph_render_calc(struct tile_glyph_render tgr,
		   const char *cstr, size_t cstr_size, struct vec2i tile,
		   struct vec4f fg_color, struct vec4f bg_color)
{
	struct vec2i pen = tile;
	int32_t row = 0;
	for ( size_t i = 0; i < cstr_size; ++i ) {
		char c = cstr[i];
		if ( c == '\n' ) {
			pen.y -= 1;
			row = 0;
			continue;
		}
		struct tile_glyph gly = {
			.tile = vec2i_add(pen, vec2i(row, 0)),
			.c = c,
			.fg_color = fg_color,
			.bg_color = bg_color
		};
		DA_APPEND(tgr.glyphs, gly);
		++row;
	}
	return tgr;
}

struct tile_glyph_render
tile_glyph_render_calc_buffer(struct tile_glyph_render tgr, struct buffer buf,
			      struct vec2i tile)
{
	return tile_glyph_render_calc(tgr, buf.data.items, buf.data.size,
				      tile, vec4fs(1), vec4fs(0));
}

struct tile_glyph_render
tile_glyph_render_sync(struct tile_glyph_render tgr)
{
	glBufferSubData(GL_ARRAY_BUFFER, 0,
			(ssize_t) (tgr.glyphs.size
				   * sizeof(struct tile_glyph)),
			tgr.glyphs.items);
	return tgr;
}

struct tile_glyph_render
tile_glyph_render_gen_buffer(struct tile_glyph_render tgr)
{
	glGenVertexArrays(1, &tgr.vao);
	glBindVertexArray(tgr.vao);

	glGenBuffers(1, &tgr.vbo);
	glBindBuffer(GL_ARRAY_BUFFER, tgr.vbo);
	glBufferData(GL_ARRAY_BUFFER,
		     (ssize_t)(tgr.glyphs.cap * sizeof(struct tile_glyph)),
		     tgr.glyphs.items,
		     GL_DYNAMIC_DRAW);
	glyph_attr_declare(tile_glyph_get_attrs(), TILE_GLYPH_ATTR_TOTAL);
	return tgr;
}

struct tile_glyph_render
tile_glyph_render_load_texture_atlas_or_exit(struct tile_glyph_render tgr,
					     const char *filepath)
{
	int32_t w, h, n;
	uint8_t *data = stbi_load(filepath,
				  &w, &h, &n,
				  STBI_rgb_alpha);

	if ( data == NULL ) {
		fprintf(stderr, "ERROR loading file %s: %s",
			filepath, stbi_failure_reason());
		exit(EXIT_FAILURE);
	}

	glActiveTexture(GL_TEXTURE0);
	glGenTextures(1, &tgr.font_tex);
	glBindTexture(GL_TEXTURE_2D, tgr.font_tex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
			GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
			GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
			GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
			GL_CLAMP_TO_EDGE);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h,
		     0, GL_RGBA, GL_UNSIGNED_BYTE,
		     data);
	stbi_image_free(data);
	return tgr;
}

struct tile_glyph_render
tile_glyph_compile_shaders_or_exit(struct tile_glyph_render tgr,
				   const char *vert_path, const char *frag_path)
{
	tgr.shaders.vert = shader_compile_file_or_exit(vert_path,
						       GL_VERTEX_SHADER);
	tgr.shaders.frag = shader_compile_file_or_exit(frag_path,
						       GL_FRAGMENT_SHADER);
	return tgr;
}

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

#endif
