module ugui;

import cache;
//#include <grapheme.h>
//#include <assert.h>

//#include "stb_truetype.h"
//#include "stbimage_write.h"

// unicode code point, different type for a different hash
def Codepoint = uint;

/* width and height of a glyph contain the kering advance
 * (u,v)
 *   +-------------*---+ -
 *   |       ^     |   | ^
 *   |       |oy   |   | |
 *   |       v     |   | |
 *   |      .ii.   |   | |
 *   |     @@@@@@. |<->| |
 *   |    V@Mio@@o |adv| |h
 *   |    :i.  V@V |   | |
 *   |      :oM@@M |   | |
 *   |    :@@@MM@M |   | |
 *   |    @@o  o@M |   | |
 *   |<->:@@.  M@M |   | |
 *   |ox  @@@o@@@@ |   | |
 *   |    :M@@V:@@.|   | v
 *   +-------------*---+ -
 *   |<------------->|
 *           w
 */
struct Glyph {
    Codepoint code;
	uint u, v;
	ushort w, h, a, x, y;
}

def GlyphCache = cache::Cache(<Codepoint, Glyph, 1024>);

// identity map the ASCII range
fn uint Codepoint.hash(Codepoint code) => code < 128 ? code : ((uint)code).hash();

struct FontAtlas {
	uint width, height;
	char* atlas;
	uint glyph_max_w, glyph_max_h;
	int size;
	int file_size;
	char *file;
	void *priv;
}


macro is_utf8(char c) => c & 0x80;
const uint BDEPTH = 1;
const uint BORDER = 4;

// FIXME: as of now only monospaced fonts look decent since no
//        kerning information is stored

struct Priv @private {
	stbtt_fontinfo stb;
	float scale;
	int baseline;
	unsigned char *bitmap;
	struct cache c;
}
//#define PRIV(x) ((struct priv *)x->priv)


struct font_atlas * font_init(void)
{
	struct font_atlas *p = emalloc(sizeof(struct font_atlas));
	memset(p, 0, sizeof(struct font_atlas));
	p->priv = emalloc(sizeof(struct priv));
	memset(p->priv, 0, sizeof(struct priv));
	PRIV(p)->c = cache_init();
	return p;
}


// loads a font into memory, storing all the ASCII characters in the atlas, each font
// atlas structure holds glyphs of a specific size in pixels
// NOTE: size includes ascend and descend (so 12 does not mean that 'A' is 12px tall)
int font_load(struct font_atlas *atlas, const char *path, int size)
{
	if (!atlas || !path)
		return -1;

	int err;

	dump_file(path, &(atlas->file), &(atlas->file_size));

	err = stbtt_InitFont(&(PRIV(atlas)->stb), (unsigned char *)atlas->file, 0);
	if (err == 0) return -1;

	int ascent, descent, linegap, baseline;
	int x0,y0,x1,y1;
	float scale;
	stbtt_GetFontVMetrics(&(PRIV(atlas)->stb), &ascent, &descent, &linegap);
	stbtt_GetFontBoundingBox(&(PRIV(atlas)->stb), &x0, &y0, &x1, &y1);
	scale = stbtt_ScaleForPixelHeight(&(PRIV(atlas)->stb), size);
	baseline = scale * -y0;
	atlas->glyph_max_w = (scale*x1) - (scale*x0);
	atlas->glyph_max_h = (baseline+scale*y1) - (baseline+scale*y0);
	atlas->atlas = emalloc(CACHE_SIZE*BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h);
	memset(atlas->atlas, 0, CACHE_SIZE*BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h);
	PRIV(atlas)->baseline = atlas->glyph_max_h - baseline;
	PRIV(atlas)->scale = scale;
	PRIV(atlas)->bitmap = emalloc(BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h);
	// FIXME: make this a square atlas
	atlas->width  = atlas->glyph_max_w*CACHE_SIZE/4;
	atlas->height = atlas->glyph_max_h*4;
	atlas->size = size;

	// preallocate all ascii characters
	for (char c = ' '; c <= '~'; c++) {
		if (!font_get_glyph_texture(atlas, c, NULL))
			return -1;
	}

	return 0;
}


int font_free(struct font_atlas *atlas)
{
	efree(atlas->atlas);
	efree(atlas->file);
	efree(PRIV(atlas)->bitmap);
	cache_free(&PRIV(atlas)->c);
	efree(atlas->priv);
	efree(atlas);
	return 0;
}


// TODO: time and take the median of the time it takes to generate the cache and
//       the time it takes to draw the glyph
const struct font_glyph * font_get_glyph_texture(struct font_atlas *atlas, unsigned int code, int *updated)
{
	int _u = 0;
	if (!updated) updated = &_u;

	const struct font_glyph *r;
	if ((r = cache_search(&PRIV(atlas)->c, code)) != NULL) {
		*updated = 0;
		return r;
	}

	*updated = 1;
	// generate the sdf and put it into the cache
	// TODO: generate the whole block at once
	int idx = stbtt_FindGlyphIndex(&PRIV(atlas)->stb, code);
	int x0,y0,x1,y1,gw,gh,l,off_x,off_y,adv,base;
	base = atlas->glyph_max_h - PRIV(atlas)->baseline;
	stbtt_GetGlyphBitmapBoxSubpixel(
		&PRIV(atlas)->stb,
		idx,
		PRIV(atlas)->scale,
		PRIV(atlas)->scale,
		0,0,
		&x0,&y0,
		&x1, &y1);
	gw = x1 - x0;
	gh = y1 - y0;
	stbtt_GetGlyphHMetrics(&PRIV(atlas)->stb, idx, &adv, &l);
	adv *= PRIV(atlas)->scale;
	off_x = PRIV(atlas)->scale*l;
	off_y = atlas->glyph_max_h+y0;
	stbtt_MakeGlyphBitmapSubpixel(
		&PRIV(atlas)->stb,
		PRIV(atlas)->bitmap,
		atlas->glyph_max_w,
		atlas->glyph_max_h,
		atlas->glyph_max_w,
		PRIV(atlas)->scale,
		PRIV(atlas)->scale,
		0, 0,
		idx);

	// TODO: bounds check usign atlas height
	// TODO: clear spot area in the atlas before writing on it
	unsigned int spot = cache_get_free_spot(&PRIV(atlas)->c);
	unsigned int ty   = ((atlas->glyph_max_w * spot) / atlas->width) * atlas->glyph_max_h;
	unsigned int tx   = (atlas->glyph_max_w * spot) % atlas->width;
	unsigned int w    = atlas->width;

	unsigned char *a = (void *)atlas->atlas;

	//printf("max:%d %d spot:%d : %d %d %d %d\n", atlas->glyph_max_w, atlas->glyph_max_h, spot, tx, ty, off_x, off_y);

	for (int y = 0; y < gh; y++) {
		for (int x = 0; x < gw; x++) {
			int c, r;
			r = (ty+y)*w;
			c = tx+x;
			a[r+c] = PRIV(atlas)->bitmap[y*atlas->glyph_max_w+x];
		}
	}

	struct font_glyph g = {
		.codepoint = code,
		.u = tx,
		.v = ty,
		.w = gw,
		.h = gh,
		.x = off_x,
		.y = off_y-base,
		.a = adv,
	};
	return cache_insert_at(&PRIV(atlas)->c, &g, g.codepoint, spot);
}


void font_dump(const struct font_atlas *atlas, const char *path)
{
	stbi_write_png(
		path,
		atlas->width,
		atlas->height,
		BDEPTH,
		atlas->atlas,
		BDEPTH*atlas->width);
}