#include <string.h>
#include <ogcsys.h>

#include <ft2build.h>
#include FT_FREETYPE_H

#include "../config.h"
#include "panic.h"
#include "theme.h"
#include "font.h"

#include "droid_ttf.h"
#include "droidbold_ttf.h"

#define ROUNDUP4B(x) ((x + 4 - 1) & ~(4 - 1))

#define X_RATIO (widescreen?WIDESCREEN_RATIO:1.0)

//#define FONT_CHECKER

FT_Library library;

typedef struct {
	int codepoint;
	int next_idx;
	int glyph_index;
	int valid;
	gfx_entity *texture;
	int x, y;
	int w, h;
	FT_Pos dx, dy;
} font_glyph;

typedef struct {
	const void *data;
	u32 data_len;
	FT_Face face;
	int max_glyphs;
	int num_glyphs;
	int em_w, em_h;
	int em_height;
	int ascender;
	int descender;
	int height;
	font_glyph *glyphs;
} font_face;

static font_face *fonts[FONT_MAX];

typedef struct {
	int size;
	const void *data;
	const u32 *data_len; // bin2s stupidity
} font_default;

const font_default default_fonts[FONT_MAX] =
{
	[FONT_LABEL]		= { 16, droidbold_ttf, &droidbold_ttf_size},
	[FONT_DLGTITLE]		= { 20, droidbold_ttf, &droidbold_ttf_size},
	[FONT_MEMO]			= { 16, droid_ttf, &droid_ttf_size},
	[FONT_APPNAME]		= { 20, droidbold_ttf, &droidbold_ttf_size},
	[FONT_APPDESC]		= { 16, droidbold_ttf, &droidbold_ttf_size},
	[FONT_BUTTON]		= { 20, droidbold_ttf, &droidbold_ttf_size},
	[FONT_BUTTON_DESEL]	= { 20, droid_ttf, &droid_ttf_size},
};

#define GLYPH_CACHE_ROOT 64
#define GLYPH_CACHE_INIT 32
#define GLYPH_CACHE_GROW 32

font_glyph *font_get_char(font_id id, int codepoint);

#if 0
static const char *utf8(u32 codepoint)
{
	static char buf[5];
	if (codepoint < 0x80) {
		buf[0] = codepoint;
		buf[1] = 0;
	} else if (codepoint < 0x800) {
		buf[0] = 0xC0 | (codepoint>>6);
		buf[1] = 0x80 | (codepoint&0x3F);
		buf[2] = 0;
	} else if (codepoint < 0x10000) {
		buf[0] = 0xE0 | (codepoint>>12);
		buf[1] = 0x80 | ((codepoint>>6)&0x3F);
		buf[2] = 0x80 | (codepoint&0x3F);
		buf[3] = 0;
	} else if (codepoint < 0x110000) {
		buf[0] = 0xF0 | (codepoint>>18);
		buf[1] = 0x80 | ((codepoint>>12)&0x3F);
		buf[2] = 0x80 | ((codepoint>>6)&0x3F);
		buf[3] = 0x80 | (codepoint&0x3F);
		buf[4] = 0;
	} else {
		buf[0] = '?';
		buf[1] = 0;
	}
	return buf;
}
#endif

int font_load(font_id id, bool use_theme)
{
	int ret;
	int i;

	const void *data;
	u32 data_len;
	int em;

	font_face *font;

	if (fonts[id])
		return 0;

	gprintf("Loading font ID %d\n", id);

	data = default_fonts[id].data;
	data_len = *default_fonts[id].data_len;
	em = default_fonts[id].size;

	if (use_theme && theme_fonts[id].data) {
		data = theme_fonts[id].data;
		data_len = theme_fonts[id].data_len;
	}

	if (use_theme && theme_fonts[id].size)
		em = theme_fonts[id].size;

	// maybe we can reuse the entire font, look for other fonts
	for (i=0; i<FONT_MAX; i++) {
		if (fonts[i] && fonts[i]->data == data && fonts[i]->em_h == em) {
			gprintf("Cloned font object from font ID %d\n", i);
			fonts[id] = fonts[i];
			return 0;
		}
	}

	font = pmalloc(sizeof(*font));
	memset(font, 0, sizeof(*font));

	font->data = data;
	font->data_len = data_len;
	font->em_h = font->em_w = em;

	font->face = NULL;

	// maybe we can reuse the FT_Font object, look for other fonts
	for (i=0; i<FONT_MAX; i++) {
		if (fonts[i] && fonts[i]->data == font->data) {
			gprintf("Reused FreeType font subobject from font ID %d\n", i);
			font->face = fonts[i]->face;
			break;
		}
	}

	if (!font->face) {
		ret = FT_New_Memory_Face(library, font->data, font->data_len, 0, &font->face);
		if (ret != 0) {
			gprintf("Failed to load font\n");
			// try to fall back
			if (use_theme && theme_fonts[id].data) {
				free(font);
				theme_fonts[id].data = NULL;
				return font_load(id, false);
			}
			return ret;
		} else {
			gprintf("Loaded font at %p\n", font->data);
		}
	}
	gprintf("Font contains %ld faces\n", font->face->num_faces);
	gprintf("Face contains %ld glyphs\n", font->face->num_glyphs);

	if (widescreen)
		font->em_w = (int)(font->em_w / WIDESCREEN_RATIO + 0.5);

	ret = FT_Set_Pixel_Sizes(font->face, font->em_w, font->em_h);
	if (ret) {
		gprintf("FT_Set_Pixel_Sizes failed: %d\n", ret);
		free(font);
		return ret;
	}

	if (!FT_HAS_KERNING(font->face))
		gprintf("Font has no usable kerning data\n");
	else
		gprintf("Font has kerning data\n");

	font->max_glyphs = GLYPH_CACHE_ROOT+GLYPH_CACHE_INIT;
	font->num_glyphs = GLYPH_CACHE_ROOT; //base "hashtable" set
	font->glyphs = pmalloc(sizeof(font_glyph) * font->max_glyphs);
	memset(font->glyphs, 0, sizeof(font_glyph) * font->max_glyphs);
	for(i=0; i<font->max_glyphs; i++) {
		font->glyphs[i].codepoint = -1;
		font->glyphs[i].next_idx = -1;
	}

	fonts[id] = font;

	font_glyph *glyph = font_get_char(id, 'M');

	font->em_height = glyph->h; // height of capital M
	font->ascender = (font->face->size->metrics.ascender+32)>>6;
	font->descender = (font->face->size->metrics.descender+32)>>6;
	font->height = (font->face->size->metrics.height+32)>>6;

	gprintf("Ascender is %d\n", font->ascender);
	gprintf("Descender is %d\n", font->descender);
	gprintf("Height is %d\n", font->height);

	gprintf("Font ID %d loaded\n", id);
	return 0;
}

font_glyph *font_get_char(font_id id, int codepoint)
{
	int ret;
	font_glyph *glyph;

	font_load(id,1);
	font_face *font = fonts[id];

	glyph = &font->glyphs[codepoint&(GLYPH_CACHE_ROOT-1)];

	if (glyph->codepoint != -1) {
		while(1) {
			if (glyph->codepoint == codepoint) {
				//gprintf("FONT: Glyph %d (%s) is cached\n", codepoint, utf8(codepoint));
				return glyph;
			}
			if (glyph->next_idx == -1) {
				glyph->next_idx = font->num_glyphs;
				if (font->num_glyphs == font->max_glyphs) {
					font->glyphs = prealloc(font->glyphs, sizeof(font_glyph) * (font->max_glyphs + GLYPH_CACHE_GROW));
					memset(&font->glyphs[font->max_glyphs], 0, GLYPH_CACHE_GROW*sizeof(font_glyph));
					font->max_glyphs += GLYPH_CACHE_GROW;
					gprintf("FONT: expanded glyph cache size to %d\n", font->max_glyphs);
				}
				glyph = &font->glyphs[font->num_glyphs];
				font->num_glyphs++;
				break;
			}
			glyph = &font->glyphs[glyph->next_idx];
		}
	}

	memset(glyph, 0, sizeof(*glyph));
	glyph->codepoint = codepoint;
	glyph->next_idx = -1;
	glyph->valid = 0;
	glyph->texture = pmalloc(sizeof(*glyph->texture));
	glyph->dx = 0;
	glyph->dy = 0;

	FT_GlyphSlot slot = font->face->glyph;
	FT_UInt glyph_index;

	ret = FT_Set_Pixel_Sizes(font->face, font->em_w, font->em_h);
	if (ret)
		return glyph;

	glyph_index = FT_Get_Char_Index(font->face, codepoint);
	ret = FT_Load_Glyph(font->face, glyph_index, FT_LOAD_DEFAULT);
	if (ret)
		return glyph;

	ret = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL);
	if (ret)
		return glyph;

	glyph->dx = slot->advance.x;
	glyph->dy = slot->advance.y;
	glyph->glyph_index = glyph_index;

	int cw = slot->bitmap.width;
	int ch = slot->bitmap.rows;

	glyph->w = cw;
	glyph->h = ch;

	if (!cw || !ch)
		return glyph;

	int tw = (cw+7)/8;
	int th = (ch+3)/4;

	int tpitch = tw * 32;

	u8 *pix = pmemalign(32, tw*th*32);
	memset(pix, 0, tw*th*32);

	int x,y;
	u8 *p = slot->bitmap.buffer;
	for(y=0; y<ch; y++) {
		u8 *lp = p;
		int ty = y/4;
		int py = y%4;
		u8 *lpix = pix + ty*tpitch + py*8;
		for(x=0; x<cw; x++) {
			int tx = x/8;
			int px = x%8;
#ifndef FONT_CHECKER
			lpix[32*tx + px] = *lp++;
#else
			if ((x+y)&1)
				lpix[32*tx + px] = 0xff;
			else
				lpix[32*tx + px] = 0;
#endif
		}
		p += slot->bitmap.pitch;
	}

	gfx_gen_tex (glyph->texture, 8*tw, 4*th, pix, GFXT_A8);

	glyph->x = slot->bitmap_left;
	glyph->y = slot->bitmap_top;
	glyph->valid = 1;

	//gprintf("FONT: Rendered and cached glyph %d (%s) at pos %d size %dx%d\n", codepoint, utf8(codepoint), glyph-font->glyphs, cw, ch);
	//gprintf("Free MEM1: %ld MEM2: %ld\n", SYS_GetArena1Size(), SYS_GetArena2Size());
	return glyph;
}

void font_kern(font_id id, int left, int right, FT_Pos *dx, FT_Pos *dy)
{
	FT_Vector delta;
	if (!FT_HAS_KERNING(fonts[id]->face)) {
		return;
	}
	if (FT_Get_Kerning(fonts[id]->face, left, right, FT_KERNING_UNFITTED, &delta))
		return;
	//gprintf("Kern font %d for glyphs %d,%d is %ld,%ld\n", id, left, right, delta.x, delta.y);
	*dx += delta.x;
	*dy += delta.y;
}


void font_init(void)
{
	int ret;

	memset(fonts, 0, sizeof(fonts));

	ret = FT_Init_FreeType(&library);
	if (ret != 0) {
		gprintf("FreeType init failed (%d)\n", ret);
		return;
	}
	gprintf("FreeType initialized\n");
}

void font_clear (void) {
	int id,id2;
	for (id=0; id<FONT_MAX; id++) {
		int g;
		if (!fonts[id])
			continue;

		// kill all clones first
		for (id2=0; id2<FONT_MAX; id2++) {
			if (id2 != id && fonts[id2] && fonts[id2] == fonts[id])
				fonts[id2] = NULL;
		}

		for (g=0; g<fonts[id]->num_glyphs; g++) {
			if (fonts[id]->glyphs[g].texture)
				free(fonts[id]->glyphs[g].texture);
			fonts[id]->glyphs[g].texture = NULL;
		}
		free(fonts[id]->glyphs);
		fonts[id]->glyphs = NULL;

		if (fonts[id]->face) {
			FT_Face face = fonts[id]->face;
			FT_Done_Face(face);
			// other fonts can share this face object, so nuke all
			for (id2=0; id2<FONT_MAX; id2++) {
				if (fonts[id2] && fonts[id2]->face == face)
					fonts[id2]->face = NULL;
			}
		}

		free(fonts[id]);
		fonts[id] = NULL;
	}
}

void font_deinit (void) {
	font_clear();
	FT_Done_FreeType(library);
}

static u32 utf8_get_char(const char **s)
{
	const char *c = *s;
	u32 mbc = '?';

	if (!c[0])
		return 0;

	if (c[0] <= 0x7f) {
		mbc = c[0];
		c++;
	} else if (c[0] >= 0xc0 && c[0] <= 0xdf) {
		if ((c[1]&0xc0) != 0x80) {
			c++;
			if (c[0])
				c++;
		} else {
			mbc = (c[1]&0x3f) | ((c[0]&0x1f)<<6);
			c+=2;
		}
	} else if (c[0] >= 0xe0 && c[0] <= 0xef) {
		if (((c[1]&0xc0) != 0x80) || ((c[2]&0xc0) != 0x80)) {
			c++;
			if (c[0])
				c++;
			if (c[0])
				c++;
		} else {
			mbc = (c[2]&0x3f) | ((c[1]&0x3f)<<6) | ((c[0]&0x1f)<<12);
			c+=3;
		}
	} else if (c[0] >= 0xf0 && c[0] <= 0xf7) {
		if (((c[1]&0xc0) != 0x80) || ((c[2]&0xc0) != 0x80) || ((c[3]&0xc0) != 0x80)) {
			c++;
			if (c[0])
				c++;
			if (c[0])
				c++;
			if (c[0])
				c++;
		} else {
			mbc = (c[3]&0x3f) | ((c[2]&0x3f)<<6) | ((c[1]&0x3f)<<12) | ((c[0]&0x1f)<<18);
			c+=4;
		}
	} else {
		c++;
	}
	*s = c;
	return mbc;
}

int font_get_ascender(font_id id) {
	font_load(id,1);

	return fonts[id]->ascender;
}

int font_get_height(font_id id) {
	font_load(id,1);

	return fonts[id]->ascender - fonts[id]->descender;
}

int font_get_em_height(font_id id) {
	font_load(id,1);

	return fonts[id]->em_height;
}

int font_get_y_spacing(font_id id) {
	font_load(id,1);

	return fonts[id]->height;
}

int font_get_min_y(font_id id) {
	font_load(id,1);

	return -fonts[id]->descender;
}

u16 font_get_string_width (font_id id, const char *s, int count) {
	int i = 0;
	u32 mbc;
	int cx = 0;
	int cy = 0;

	FT_Pos cdx = 0, cdy = 0;
	u32 previous = 0;

	font_load(id,1);

	while((mbc = utf8_get_char(&s))) {
		if (mbc == '\n') {
			if (*s)
				mbc = ' ';
			else
				continue;
		}
		if (mbc == '\r')
			continue;
		font_glyph *glyph = font_get_char(id, mbc);

		if (previous)
			font_kern(id, previous, glyph->glyph_index, &cdx, &cdy);

		cx += (cdx+32) >> 6;
		cy += (cdy+32) >> 6;

		cdx = glyph->dx;
		cdy = glyph->dy;
		previous = glyph->glyph_index;
		i++;
		if (i == count)
			break;
	}

	cx += (cdx+32) >> 6;
	cy += (cdy+32) >> 6;

	return cx;
}

int font_get_char_count (font_id id, const char *s, u16 max_width) {
	//u16 res = 0;
	int i = 0;
	u32 mbc;
	int cx = 0;
	int cy = 0;

	FT_Pos cdx = 0, cdy = 0;
	u32 previous = 0;

	max_width /= X_RATIO;

	font_load(id,1);

	while((mbc = utf8_get_char(&s))) {
		if (mbc == '\n') {
			if (*s)
				mbc = ' ';
			else
				continue;
		}
		if (mbc == '\r')
			continue;
		font_glyph *glyph = font_get_char(id, mbc);

		if (previous)
			font_kern(id, previous, glyph->glyph_index, &cdx, &cdy);

		cx += (cdx+32) >> 6;
		cy += (cdy+32) >> 6;

		if (max_width && (cx >= max_width))
			return i;

		cdx = glyph->dx;
		cdy = glyph->dy;
		previous = glyph->glyph_index;
		i++;
	}

	return i;
}

int font_wrap_string (char ***lines, font_id id, const char *s,
						u16 max_width) {
	const char *p = s;
	char **res = NULL;
	int line = 0;
	int start = 0;
	int end = 0;
	int ls = -1;
	bool lb;

	int cx = 0;
	int cy = 0;

	int i = 0;
	u32 mbc;

	FT_Pos cdx = 0, cdy = 0;
	u32 previous = 0;

	max_width /= X_RATIO;

	font_load(id,1);

	while (true) {
		i = p - s;
		mbc = utf8_get_char(&p);
		lb = false;

		if (mbc == ' ')
			ls = p - s;

		if ((mbc == '\n') || mbc == 0) {
			lb = true;
			end = i;
			i = p - s;
		} else if (mbc == '\r') {
			continue;
		} else {
			font_glyph *glyph = font_get_char(id, mbc);

			if (previous)
				font_kern(id, previous, glyph->glyph_index, &cdx, &cdy);

			cx += (cdx+32) >> 6;
			cy += (cdy+32) >> 6;

			int w = (glyph->dx+32) >> 6;
			if ((glyph->w + glyph->x) > w)
				w = glyph->w + glyph->x;

			if ((cx + w) >= max_width) {
				lb = true;
				if (ls <= start) {
					if (i == start)
						i++;
					end = i;
					p = &s[i];
				} else {
					end = ls;
					i = ls;
					p = &s[i];
				}
			}

			cdx = glyph->dx;
			cdy = glyph->dy;
			previous = glyph->glyph_index;
		}

		if (lb) {
			res = prealloc (res, (line + 1) * sizeof (char **));
			if (end <= start)
				res[line] = NULL;
			else {
				res[line] = strndup (&s[start], end - start);
			}

			line++;
			start = i;
			cx = 0;
			cy = 0;
			cdx = 0;
			cdy = 0;
			previous = 0;
		}

		if (mbc == 0)
			break;
	}

	*lines = res;
	return line;
}

void font_free_lines (char **lines, u32 count) {
	int i;

	for (i = 0; i < count; ++i)
		free (lines[i]);

	free (lines);
}

void font_plot_string (gfx_queue_entry *entries, int count, font_id id,
					   const char *s, u16 x, u16 y, u16 layer, u16 width,
					   font_xalign xalign, font_yalign yalign) {
	int cx;
	int cy;


	if (!count)
		return;

	cx = x;
	cy = y;

	cx /= X_RATIO;

	switch (xalign) {
	case FA_LEFT:
		break;

	case FA_CENTERED:
		cx += (width/X_RATIO - font_get_string_width (id, s, count)) / 2;
		break;

	case FA_RIGHT:
		cx += width/X_RATIO - font_get_string_width (id, s, count);
		break;
	}

	switch (yalign) {
	case FA_ASCENDER:
		cy += fonts[id]->ascender;
		break;
	case FA_EM_TOP:
		cy += fonts[id]->em_height;
		break;
	case FA_EM_CENTERED:
		cy += fonts[id]->em_height/2;
		break;
	case FA_BASELINE:
		break;
	case FA_DESCENDER:
		cy += fonts[id]->descender;
		break;
	}

	FT_Pos cdx = 0, cdy = 0;
	u32 previous = 0;
	int first = 1;
	u32 mbc;

	while ((mbc = utf8_get_char(&s))) {
		if (mbc == '\n') {
			if (*s)
				mbc = ' ';
			else
				break;
		}
		if (mbc == '\r')
			continue;
		font_glyph *glyph = font_get_char(id, mbc);

		if (previous)
			font_kern(id, previous, glyph->glyph_index, &cdx, &cdy);

		cx += (cdx+32) >> 6;
		cy += (cdy+32) >> 6;

		if (!glyph->valid) {
			entries->type = GFXQ_NULL;
			goto next_glyph;
		}

		// fudgity fudge, helps make text left-align slightly better/nicer,
		// especially with differing fonts.
		// do this only for chars with glyph->x > 0. Those with glyph->x < 0
		// are usually kerned together and look better a bit to the left anyway.
		if(xalign == FA_LEFT && first && glyph->x > 0)
			cx -= glyph->x;

		gfx_qe_entity (entries, glyph->texture,
			((float)(cx + glyph->x)) * X_RATIO,
			cy - glyph->y,
			layer, theme_fonts[id].color);

		//gprintf("Render %d (%s) at %d %d -> %d %d size %d %d\n", mbc, utf8(mbc), cx, cy, cx + glyph->x, cy - glyph->y + fonts[id]->height, glyph->texture->w, glyph->texture->h);

		first = 0;

next_glyph:
		count--;
		entries++;

		if (!count)
			break;

		cdx = glyph->dx;
		cdy = glyph->dy;
		previous = glyph->glyph_index;
	}

	if (count) {
		gprintf("BUG: %d queue entries empty, padding with NULLs\n", count);
		while (count--) {
			entries->type = GFXQ_NULL;
			// this superfluous statement carefully chosen for optimal crashiness
			entries++->entity.coords.z = 0;
		}
	}
}

