#include "text_draw.h"
#include "../lib/color.h"
#include "../lib/math.h"
#include "../lib/geometry.h"
#include "../lib/text.h"
#include "../lib/ds.h"
#include "../platform.h"
#include "stb_truetype.h"
#include "../debug/logger.h"
#include "../assets.h"
#include "../enginestate.h"
#include <string.h>
#include <stdlib.h>



struct gui_glyph_info
{
	utf8_codepoint codepoint;
	Rect box; // .position = offset from position for alignment, .size = size of the bitmap to draw
	v2u position; // Position of the top left border in the texture
	s32 advance;
	u32 next; // Anything >= container_capacity is to be considered NULL
	u32 previous;
};


/* Glyph caching:
 * We store a small number of sizes and a fairly large number of characters for each size.
 * Each slot will have the same width and height, so that we can easily replace it without
 * re-packing everything. This will also make the code simpler.
 * We use 0xFFFFFFFF as a placeholder for empty slots.
 *
 */

struct gui_glyph_codepoint_map
{
	utf8_codepoint codepoint;
	u32 index;
};

struct gui_glyph_texture
{
	f32 font_size;

	struct gui_glyph_codepoint_map *sorted_indices;
	gui_glyph_info *info;
	u32 oldest;
	u32 newest;
	u32 capacity;

	v2s glyph_max_size;

	r_texture *texture;
};

struct gui_glyph_cache
{
	gui_glyph_texture *glyphs;
	u32 capacity;
	u32 oldest;

	u32 max_glyphs_per_texture;
};

static void gui_glyph_cache_init();
static void gui_glyph_cache_deinit();
static gui_glyph_texture gui_glyph_texture_create(f32 font_size, u32 capacity);
static void gui_glyph_texture_destroy(gui_glyph_texture *glyphs);
static gui_glyph_texture *gui_glyph_cache_texture_for_codepoints(f32 font_size, const utf8_codepoint *codepoints, u64 count);




// Globals
static u8 *Font_File;
static stbtt_fontinfo Font_Info;
static gui_glyph_cache Glyph_Cache;


bool gui_text_draw_init()
{
	// @Feature: user specified font
	// @Cleanup: one-line file read
	p_file f;
	p_file_init(&f, "assets/fonts/DejaVuSerif-Bold.ttf");
	Buffer buf;
	buf.size = p_file_size(&f);
	buf.data = (u8*)p_alloc(buf.size + 1);
	buf.data[buf.size] = '\0';
	p_file_read(&f, &buf, buf.size);
	p_file_deinit(&f);
	Font_File = buf.data;

	if(!stbtt_InitFont(&Font_Info, Font_File, 0))
	{
		LOG(LOG_ERROR, "Cannot load font.");
		return false;
	}

	gui_glyph_cache_init();

	return true;
}

void gui_text_draw_deinit()
{
	gui_glyph_cache_deinit();
	p_free(Font_File);
}

v2 gui_text_draw_size(const char *text, f32 font_size, v2 *cursor_position)
{
	// UTF8 conversion
	u64 text_length = utf8_codepoint_count(text);
	utf8_codepoint *codepoints = (utf8_codepoint*) p_alloc(text_length * sizeof(utf8_codepoint));
	{
		u64 bytes_read = 0;
		text_length = utf8_from_string(text, &bytes_read, codepoints, text_length);
	}

	// Compute size
	v2 result = gui_utf8_text_draw_size(codepoints, text_length, font_size, cursor_position);

	p_free(codepoints);
	return result;
}

void gui_text_draw(Rect r, const char *text, f32 font_size, v4 color)
{
	// UTF8 conversion
	u64 text_length = utf8_codepoint_count(text);
	utf8_codepoint *codepoints = (utf8_codepoint*) p_alloc(text_length * sizeof(utf8_codepoint));
	{
		u64 bytes_read = 0;
		text_length = utf8_from_string(text, &bytes_read, codepoints, text_length);
	}

	// Draw
	gui_utf8_text_draw(r, codepoints, text_length, font_size, color);

	p_free(codepoints);
}


v2 gui_utf8_text_draw_size(const utf8_codepoint *text, u64 length, f32 font_size, v2 *cursor_position)
{
	f32 font_scale;
	s32 font_ascent;
	s32 font_descent;
	s32 font_line_gap;
	s32 font_baseline;

	{
		font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
		stbtt_GetFontVMetrics(&Font_Info, &font_ascent, &font_descent, &font_line_gap);
		font_baseline = font_ascent;

		font_ascent   *= font_scale;
		font_descent  *= font_scale;
		font_line_gap *= font_scale;
		font_baseline *= font_scale;
	}

	// Compute size
	v2 size = {0, (f32)(font_ascent - font_descent)};
	{
		v2 cursor = {0, (f32)(font_ascent - font_descent)};
		for(u64 i = 0; i < length; i++)
		{
			s32 advance, lsb;
			stbtt_GetCodepointHMetrics(&Font_Info, text[i], &advance, &lsb);

			// Special characters
			if(text[i] == 10) // '\n'
			{
				advance = 0;
				cursor.x = 0;
				cursor.y += font_ascent - font_descent + font_line_gap;
				size.y = cursor.y;
			}

			// Normal characters
			cursor.x += floor(advance * font_scale); // Remember to consider kerning
			size.x = maximum(size.x, cursor.x);
		}
		if(cursor_position)
			*cursor_position = cursor;
	}

	return size;
}

void gui_utf8_text_draw(Rect r, const utf8_codepoint *text, u64 length, f32 font_size, v4 color)
{
	f32 font_scale;
	s32 font_ascent;
	s32 font_descent;
	s32 font_line_gap;
	s32 font_baseline;

	{
		font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
		stbtt_GetFontVMetrics(&Font_Info, &font_ascent, &font_descent, &font_line_gap);
		font_baseline = font_ascent;

		font_ascent   *= font_scale;
		font_descent  *= font_scale;
		font_line_gap *= font_scale;
		font_baseline *= font_scale;
	}

	// Compute glyphs
	gui_glyph_texture *glyphs = gui_glyph_cache_texture_for_codepoints(font_size, text, length);

	// Map text to quads
	v2 *vertices;
	v2 *uvs;
	u64 draw_count = 0;
	{
		vertices = (v2*) p_alloc(6 * length * sizeof(v2)); // 2 triangles, 3 vertices each = 6 vertices
		uvs      = (v2*) p_alloc(6 * length * sizeof(v2));


		v2 position = r.position;
		position.y += font_baseline;
		for(u64 i = 0; i < length; i++)
		{
			gui_glyph_codepoint_map *found = (gui_glyph_codepoint_map*) bsearch(&text[i], glyphs->sorted_indices, glyphs->capacity, sizeof(gui_glyph_codepoint_map), u32_cmp);
			if(found == NULL)
			{
				LOG(LOG_ERROR, "Cannot find codepoint 0x%X in glyph list.", text[i]);
				continue;
			}

			u64 glyph_i = glyphs->sorted_indices[found - glyphs->sorted_indices].index;
			gui_glyph_info *info = &glyphs->info[glyph_i];

			// Special characters
			if(text[i] == 10) // '\n'
			{
				position.x = r.position.x;
				position.y += font_ascent - font_descent + font_line_gap;
			}

			// Normal characters
			// Map character to its vertices
			{
				Rect r;
				Rect r_uv;
				r.position = position + info->box.position;
				r.position.x = floor(r.position.x + 0.5);
				r.position.y = floor(r.position.y + 0.5);
				r.size = info->box.size;

				r_uv.position = V2(info->position) / V2(glyphs->texture->size);
				r_uv.size     =     info->box.size / V2(glyphs->texture->size);

				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 = r_uv.position + v2{r_uv.w, 0     };
				v2 b_uv = r_uv.position + v2{0     , 0     };
				v2 c_uv = r_uv.position + v2{0     , r_uv.h};
				v2 d_uv = r_uv.position + v2{r_uv.w, r_uv.h};

				vertices[6*draw_count + 0] = a;
				vertices[6*draw_count + 1] = b;
				vertices[6*draw_count + 2] = c;
				vertices[6*draw_count + 3] = a;
				vertices[6*draw_count + 4] = c;
				vertices[6*draw_count + 5] = d;
				uvs[6*draw_count + 0] = a_uv;
				uvs[6*draw_count + 1] = b_uv;
				uvs[6*draw_count + 2] = c_uv;
				uvs[6*draw_count + 3] = a_uv;
				uvs[6*draw_count + 4] = c_uv;
				uvs[6*draw_count + 5] = d_uv;
			}


			position.x += info->advance; // Remember to consider kerning
			draw_count++;
		}
	}

	// Render quads
	r_2d_immediate_mesh(6*draw_count, vertices, color, uvs, glyphs->texture);

	p_free(vertices);
	p_free(uvs);
}


static void gui_glyph_cache_init()
{
	Glyph_Cache.capacity = 4;
	Glyph_Cache.oldest   = 0;
	Glyph_Cache.glyphs   = (gui_glyph_texture*)p_alloc(sizeof(gui_glyph_texture) * Glyph_Cache.capacity);
	memset(Glyph_Cache.glyphs, 0, sizeof(gui_glyph_texture) * Glyph_Cache.capacity);
	Glyph_Cache.max_glyphs_per_texture = 256; // @Correctness: test with small values, to trigger the glyph replacement code
}

static void gui_glyph_cache_deinit()
{
	for(u32 i = 0; i < Glyph_Cache.capacity; i++)
		gui_glyph_texture_destroy(&Glyph_Cache.glyphs[i]);
	p_free(Glyph_Cache.glyphs);
	Glyph_Cache.glyphs = NULL;
	Glyph_Cache.capacity = 0;
	Glyph_Cache.oldest   = 0;
}

static gui_glyph_texture gui_glyph_texture_create(f32 font_size, u32 capacity)
{
	// Init container for glyphs and info
	gui_glyph_texture glyphs;
	glyphs.font_size      = font_size;
	glyphs.sorted_indices = (gui_glyph_codepoint_map*) p_alloc(sizeof(gui_glyph_codepoint_map) * capacity);
	glyphs.info           = (gui_glyph_info*) p_alloc(sizeof(gui_glyph_info) * capacity);
	glyphs.oldest         = 0;
	glyphs.newest         = capacity - 1;
	glyphs.capacity       = capacity;

	// Estimate max glyph size.
	// @Correctness: Text draw will fail if a bigger glyph is used.
	f32 font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
	utf8_codepoint cp[] = {' ', 'M', 'j', '{', '=', 'w', 'W'};
	glyphs.glyph_max_size = v2s{0, 0};
	for(s32 i = 0; i < sizeof(cp)/sizeof(utf8_codepoint); i++)
	{
		v2s top_left, bottom_right;
		stbtt_GetCodepointBitmapBox(&Font_Info, cp[i], font_scale, font_scale, &top_left.x, &top_left.y, &bottom_right.x, &bottom_right.y);
		v2s size = bottom_right - top_left;

		glyphs.glyph_max_size.x = maximum(glyphs.glyph_max_size.x, size.x);
		glyphs.glyph_max_size.y = maximum(glyphs.glyph_max_size.y, size.y);
	}
	LOG(LOG_DEBUG, "Font size %f not in cache. Slot size (%d %d)", font_size, glyphs.glyph_max_size.x, glyphs.glyph_max_size.y);

	// Precompile some info data
	for(u32 i = 0; i < glyphs.capacity; i++)
	{
		glyphs.info[i] = gui_glyph_info{
			.codepoint = 0xFFFFFFFF,
			.box = {0,0,0,0},
			.position  = v2u{glyphs.glyph_max_size.x * i, 0},
			.advance = 0,
			.next = i + 1, // Last .next will be >= capacity (== capacity to be precise), so we will consider it to be NULL
			.previous = ((i == 0) ? glyphs.capacity : (i - 1))
		};

		glyphs.sorted_indices[i] = gui_glyph_codepoint_map{0xFFFFFFFF, i};
	}

	// Initialize texture
	v2s texture_size = v2s{glyphs.glyph_max_size.x * glyphs.capacity, glyphs.glyph_max_size.y};
	u8 *texture_data = (u8*)p_alloc(sizeof(u8) * texture_size.x * texture_size.y);
	memset(texture_data, 0, sizeof(u8) * texture_size.x * texture_size.y);

	glyphs.texture = assets_new_textures(&engine.am, 1);
	*glyphs.texture = r_texture_create(texture_data, texture_size, R_TEXTURE_ALPHA | R_TEXTURE_NO_MIPMAP);

	return glyphs;
}

static void gui_glyph_texture_destroy(gui_glyph_texture *glyphs)
{
	if(glyphs->sorted_indices)
		p_free(glyphs->sorted_indices);
	if(glyphs->info)
		p_free(glyphs->info);
	if(glyphs->texture)
		r_texture_destroy(glyphs->texture);
	glyphs->sorted_indices = NULL;
	glyphs->info = NULL;
	glyphs->texture = NULL;

	glyphs->font_size = 0;
	glyphs->oldest = 0;
	glyphs->newest = 0;
	glyphs->capacity = 0;
}

static gui_glyph_texture *gui_glyph_cache_texture_for_codepoints(f32 font_size, const utf8_codepoint *codepoints, u64 count)
{
	// Approximate font_size. We don't really want to build different bitmaps for size 12.000000 and 12.000001.
	// This will also prevent floating point rounding errors from rebuilding the cache.
	font_size = floor(font_size * 10) / 10;

	// Find cached texture for this size or build a new one
	gui_glyph_texture *glyphs = NULL;
	for(u32 i = 0; i < Glyph_Cache.capacity; i++)
	{
		//LOG(LOG_DEBUG, "Font size: %f - Cached: %f", font_size, Glyph_Cache.glyphs[i].font_size);
		if(abs(Glyph_Cache.glyphs[i].font_size - font_size) < 0.01)
		{
			glyphs = &Glyph_Cache.glyphs[i];
		}
	}
	if(glyphs == NULL)
	{
		//LOG(LOG_DEBUG, "Size not matched");
		glyphs = &Glyph_Cache.glyphs[Glyph_Cache.oldest];
		Glyph_Cache.oldest = (Glyph_Cache.oldest + 1) % Glyph_Cache.capacity;

		gui_glyph_texture_destroy(glyphs);
		*glyphs = gui_glyph_texture_create(font_size, Glyph_Cache.max_glyphs_per_texture);
	}



	// Build list of unique codepoints (so that we don't render the same codepoint twice)
	utf8_codepoint *unique = (utf8_codepoint*) p_alloc(count * sizeof(utf8_codepoint));
	memcpy(unique, codepoints, count * sizeof(utf8_codepoint));
	u64 unique_count = make_unique(unique, count, sizeof(utf8_codepoint), u32_cmp);
	if(unique_count > glyphs->capacity)
		LOG(LOG_ERROR, "Unique codepoints count > cache capacity. Some codepoints will not be rendered.");

	// Find which codepoints are not already in the cache and need to be rendered
	utf8_codepoint to_render[unique_count];
	u32 to_render_count = 0;
	for(u32 i = 0; i < unique_count; i++)
	{
		gui_glyph_codepoint_map *found = (gui_glyph_codepoint_map*) bsearch(&unique[i], glyphs->sorted_indices, glyphs->capacity, sizeof(gui_glyph_codepoint_map), u32_cmp);
		if(found == NULL)
		{
			// Not found -> add to the list of glyphs to render
			to_render[to_render_count] = unique[i];
			to_render_count++;
		}
		else
		{
			// Found -> mark it as recent, so that is does not get deleted prematurely
			u32 index = glyphs->sorted_indices[found - glyphs->sorted_indices].index;
			if(index == glyphs->newest)
			{
				// Already the newest. Do nothing.
			}
			else if(index == glyphs->oldest)
			{
				// We have no previous to fix, only next
				u32 next = glyphs->info[index].next;
				glyphs->info[next].previous = glyphs->capacity; // Next is the new oldest -> no previous
				glyphs->oldest = next;

				// Set index as last element
				glyphs->info[index].next     = glyphs->capacity;
				glyphs->info[index].previous = glyphs->newest;
				glyphs->info[glyphs->newest].next = index;
				glyphs->newest = index;
			}
			else
			{
				// We in between the list. We have both previous and next elements to fix.
				u32 previous = glyphs->info[index].previous;
				u32 next     = glyphs->info[index].next;
				glyphs->info[previous].next = next;
				glyphs->info[next].previous = previous;

				// Set index as last element
				glyphs->info[index].next     = glyphs->capacity;
				glyphs->info[index].previous = glyphs->newest;
				glyphs->info[glyphs->newest].next = index;
				glyphs->newest = index;
			}
		}
	}

	p_free(unique);


	// Get info for rendering
	f32 font_scale;
	s32 font_ascent;
	s32 font_descent;
	s32 font_line_gap;
	s32 font_baseline;
	{
		font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
		stbtt_GetFontVMetrics(&Font_Info, &font_ascent, &font_descent, &font_line_gap);
		font_baseline = font_ascent;

		font_ascent   *= font_scale;
		font_descent  *= font_scale;
		font_line_gap *= font_scale;
		font_baseline *= font_scale;
	}

	// Render glyph in its appropriate place
	for(u32 i = 0; i < to_render_count; i++)
	{
		u32 index = glyphs->oldest;
		glyphs->oldest = glyphs->info[index].next;
		glyphs->info[glyphs->oldest].previous = glyphs->capacity;

		glyphs->info[index].next = glyphs->capacity;
		glyphs->info[index].previous = glyphs->newest;
		glyphs->info[index].codepoint = to_render[i];

		glyphs->info[glyphs->newest].next = index;
		glyphs->newest = index;


		// Complete gui_glyph_info structure and render
		gui_glyph_info *info = &glyphs->info[index];

		v2s top_left, bottom_right;
		stbtt_GetCodepointBitmapBox(&Font_Info, info->codepoint, font_scale, font_scale, &top_left.x, &top_left.y, &bottom_right.x, &bottom_right.y);
		s32 advance, lsb;
		stbtt_GetCodepointHMetrics(&Font_Info, info->codepoint, &advance, &lsb);

		v2s size = bottom_right - top_left;

		// Special codepoints
		if(info->codepoint == 10) // '\n'
		{
			size = {0, 0};
			advance = 0;
		}

		info->box.position = V2(top_left);
		info->box.size = V2(size);
		//info->position = v2u{glyphs->glyph_max_size.x * index, 0}; // Commented because it's already pre-computed.
		info->advance = advance * font_scale;

		// @Correctness: needs to be premultiplied alpha
		stbtt_MakeCodepointBitmap(&Font_Info, glyphs->texture->data + info->position.x, info->box.size.x, info->box.size.y, glyphs->texture->size.x, font_scale, font_scale, info->codepoint);
		r_texture_update(glyphs->texture, glyphs->texture->data + info->position.x, V2S(info->box.size), V2S(info->position), glyphs->texture->size.x);
	}

	// Build sorted array with indices that point to the element
	u32 nonempty_count = glyphs->capacity;
	for(u32 i = 0; i < glyphs->capacity; i++)
	{
		glyphs->sorted_indices[i] = gui_glyph_codepoint_map{glyphs->info[i].codepoint, i};
		if(glyphs->info[i].codepoint == 0xFFFFFFFF)
		{
			// When the cache is mostly empty, this makes the sorting way faster.
			nonempty_count = i;
			break;
		}
	}
	qsort(glyphs->sorted_indices, nonempty_count, sizeof(gui_glyph_codepoint_map), u32_cmp);

	return glyphs;
}
