#include "SystemFonts.h"
#include "Drawer2D.h"
#include "String.h"
#include "Funcs.h"
#include "Platform.h"
#include "ExtMath.h"
#include "Logger.h"
#include "Game.h"
#include "Event.h"
#include "Stream.h"
#include "Utils.h"
#include "Errors.h"
#include "Window.h"
#include "Options.h"

static char defaultBuffer[STRING_SIZE];
static cc_string font_default = String_FromArray(defaultBuffer);

void SysFont_SetDefault(const cc_string* fontName) {
	String_Copy(&font_default, fontName);
	Event_RaiseVoid(&ChatEvents.FontChanged);
}

static void OnInit(void) {
	Options_Get(OPT_FONT_NAME, &font_default, "");
	if (Game_ClassicMode) font_default.length = 0;
}

struct IGameComponent SystemFonts_Component = {
	OnInit /* Init  */
};


/*########################################################################################################################*
*------------------------------------------------------Fallback font------------------------------------------------------*
*#########################################################################################################################*/
#define FallbackFont_ValidChar(c) ((c) > 32 && (c) < 127)
#define FallbackFont_ToIndex(c)   ((c) - 33) /* First valid char is ! */
#define SPACE_WIDTH 2
#define CELL_SIZE 8

#define FallbackFont_GetRows(c) (FallbackFont_ValidChar(c) ? font_bitmap[FallbackFont_ToIndex(c)] : missing_cell)
#define FallbackFont_GetScale(size) ((size) >> 3)

static const cc_uint8 missing_cell[CELL_SIZE] = { 
	0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF 
};

/* 8x8 font bitmap, represented with 1 bit for each pixel */
/* Source: Goodly's texture pack for ClassiCube */
static const cc_uint8 font_bitmap[][CELL_SIZE] = {
	  { 0x01,0x01,0x01,0x01,0x01,0x00,0x01,0x00 }, /* ! */
	  { 0x05,0x05,0x05,0x00,0x00,0x00,0x00,0x00 }, /* " */
	  { 0x0A,0x0A,0x1F,0x0A,0x1F,0x0A,0x0A,0x00 }, /* # */
	  { 0x04,0x1F,0x01,0x1F,0x10,0x1F,0x04,0x00 }, /* $ */
	  { 0x00,0x21,0x11,0x08,0x04,0x22,0x21,0x00 }, /* % */
	  { 0x0C,0x12,0x0C,0x2E,0x19,0x11,0x2E,0x00 }, /* & */
	  { 0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00 }, /* ' */
	  { 0x04,0x02,0x01,0x01,0x01,0x02,0x04,0x00 }, /* ( */
	  { 0x01,0x02,0x04,0x04,0x04,0x02,0x01,0x00 }, /* ) */
	  { 0x00,0x02,0x07,0x02,0x05,0x00,0x00,0x00 }, /* * */
	  { 0x00,0x04,0x04,0x1F,0x04,0x04,0x00,0x00 }, /* + */
	  { 0x00,0x00,0x00,0x00,0x00,0x02,0x02,0x01 }, /* , */
	  { 0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00 }, /* - */
	  { 0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x00 }, /* . */
	  { 0x08,0x08,0x04,0x04,0x02,0x02,0x01,0x00 }, /* / */
	  { 0x06,0x09,0x0D,0x0B,0x09,0x09,0x06,0x00 }, /* 0 */
	  { 0x02,0x03,0x02,0x02,0x02,0x02,0x07,0x00 }, /* 1 */
	  { 0x06,0x09,0x08,0x04,0x02,0x09,0x0F,0x00 }, /* 2 */
	  { 0x06,0x09,0x08,0x06,0x08,0x09,0x06,0x00 }, /* 3 */
	  { 0x05,0x05,0x05,0x0F,0x04,0x04,0x04,0x00 }, /* 4 */
	  { 0x0F,0x01,0x07,0x08,0x08,0x09,0x06,0x00 }, /* 5 */
	  { 0x06,0x09,0x01,0x07,0x09,0x09,0x06,0x00 }, /* 6 */
	  { 0x0F,0x08,0x08,0x04,0x04,0x02,0x02,0x00 }, /* 7 */
	  { 0x06,0x09,0x09,0x06,0x09,0x09,0x06,0x00 }, /* 8 */
	  { 0x06,0x09,0x09,0x0E,0x08,0x09,0x06,0x00 }, /* 9 */
	  { 0x00,0x01,0x01,0x00,0x00,0x01,0x01,0x00 }, /* : */
	  { 0x00,0x02,0x02,0x00,0x00,0x02,0x02,0x01 }, /* ; */
	  { 0x00,0x04,0x02,0x01,0x02,0x04,0x00,0x00 }, /* < */
	  { 0x00,0x00,0x1F,0x00,0x00,0x1F,0x00,0x00 }, /* = */
	  { 0x00,0x01,0x02,0x04,0x02,0x01,0x00,0x00 }, /* > */
	  { 0x07,0x09,0x08,0x04,0x02,0x00,0x02,0x00 }, /* ? */
	  { 0x0E,0x11,0x1D,0x1D,0x1D,0x01,0x0E,0x00 }, /* @ */
	  { 0x06,0x09,0x09,0x0F,0x09,0x09,0x09,0x00 }, /* A */
	  { 0x07,0x09,0x09,0x07,0x09,0x09,0x07,0x00 }, /* B */
	  { 0x06,0x09,0x01,0x01,0x01,0x09,0x06,0x00 }, /* C */
	  { 0x07,0x09,0x09,0x09,0x09,0x09,0x07,0x00 }, /* D */
	  { 0x0F,0x01,0x01,0x07,0x01,0x01,0x0F,0x00 }, /* E */
	  { 0x0F,0x01,0x01,0x07,0x01,0x01,0x01,0x00 }, /* F */
	  { 0x06,0x09,0x01,0x0D,0x09,0x09,0x06,0x00 }, /* G */
	  { 0x09,0x09,0x09,0x0F,0x09,0x09,0x09,0x00 }, /* H */
	  { 0x07,0x02,0x02,0x02,0x02,0x02,0x07,0x00 }, /* I */
	  { 0x08,0x08,0x08,0x08,0x08,0x09,0x07,0x00 }, /* J */
	  { 0x09,0x09,0x05,0x03,0x05,0x09,0x09,0x00 }, /* K */
	  { 0x01,0x01,0x01,0x01,0x01,0x01,0x0F,0x00 }, /* L */
	  { 0x11,0x1B,0x15,0x11,0x11,0x11,0x11,0x00 }, /* M */
	  { 0x09,0x0B,0x0D,0x09,0x09,0x09,0x09,0x00 }, /* N */
	  { 0x06,0x09,0x09,0x09,0x09,0x09,0x06,0x00 }, /* O */
	  { 0x07,0x09,0x09,0x07,0x01,0x01,0x01,0x00 }, /* P */
	  { 0x06,0x09,0x09,0x09,0x09,0x05,0x0E,0x00 }, /* Q */
	  { 0x07,0x09,0x09,0x07,0x09,0x09,0x09,0x00 }, /* R */
	  { 0x06,0x09,0x01,0x06,0x08,0x09,0x06,0x00 }, /* S */
	  { 0x07,0x02,0x02,0x02,0x02,0x02,0x02,0x00 }, /* T */
	  { 0x09,0x09,0x09,0x09,0x09,0x09,0x06,0x00 }, /* U */
	  { 0x11,0x11,0x11,0x11,0x11,0x0A,0x04,0x00 }, /* V */
	  { 0x11,0x11,0x11,0x11,0x15,0x1B,0x11,0x00 }, /* W */
	  { 0x11,0x11,0x0A,0x04,0x0A,0x11,0x11,0x00 }, /* X */
	  { 0x11,0x11,0x0A,0x04,0x04,0x04,0x04,0x00 }, /* Y */
	  { 0x0F,0x08,0x04,0x02,0x01,0x01,0x0F,0x00 }, /* Z */
	  { 0x07,0x01,0x01,0x01,0x01,0x01,0x07,0x00 }, /* [ */
	  { 0x01,0x01,0x02,0x02,0x04,0x04,0x08,0x00 }, /* \ */
	  { 0x07,0x04,0x04,0x04,0x04,0x04,0x07,0x00 }, /* ] */
	  { 0x04,0x0A,0x11,0x00,0x00,0x00,0x00,0x00 }, /* ^ */
	  { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F }, /* _ */
	  { 0x01,0x01,0x02,0x00,0x00,0x00,0x00,0x00 }, /* ` */
	  { 0x00,0x00,0x0E,0x09,0x09,0x0D,0x0B,0x00 }, /* a */
	  { 0x01,0x01,0x07,0x09,0x09,0x09,0x07,0x00 }, /* b */
	  { 0x00,0x00,0x06,0x09,0x01,0x09,0x06,0x00 }, /* c */
	  { 0x08,0x08,0x0E,0x09,0x09,0x09,0x0E,0x00 }, /* d */
	  { 0x00,0x00,0x06,0x09,0x0F,0x01,0x0E,0x00 }, /* e */
	  { 0x06,0x01,0x07,0x01,0x01,0x01,0x01,0x00 }, /* f */
	  { 0x00,0x00,0x0E,0x09,0x09,0x0E,0x08,0x07 }, /* g */
	  { 0x01,0x01,0x07,0x09,0x09,0x09,0x09,0x00 }, /* h */
	  { 0x01,0x00,0x01,0x01,0x01,0x01,0x01,0x00 }, /* i */
	  { 0x08,0x00,0x08,0x08,0x08,0x08,0x09,0x06 }, /* j */
	  { 0x01,0x01,0x09,0x05,0x03,0x05,0x09,0x00 }, /* k */
	  { 0x01,0x01,0x01,0x01,0x01,0x01,0x02,0x00 }, /* l */
	  { 0x00,0x00,0x0B,0x15,0x15,0x11,0x11,0x00 }, /* m */
	  { 0x00,0x00,0x07,0x09,0x09,0x09,0x09,0x00 }, /* n */
	  { 0x00,0x00,0x06,0x09,0x09,0x09,0x06,0x00 }, /* o */
	  { 0x00,0x00,0x07,0x09,0x09,0x07,0x01,0x01 }, /* p */
	  { 0x00,0x00,0x0E,0x09,0x09,0x0E,0x08,0x08 }, /* q */
	  { 0x00,0x00,0x05,0x03,0x01,0x01,0x01,0x00 }, /* r */
	  { 0x00,0x00,0x0E,0x01,0x06,0x08,0x07,0x00 }, /* s */
	  { 0x02,0x02,0x07,0x02,0x02,0x02,0x02,0x00 }, /* t */
	  { 0x00,0x00,0x09,0x09,0x09,0x09,0x0E,0x00 }, /* u */
	  { 0x00,0x00,0x09,0x09,0x09,0x05,0x03,0x00 }, /* v */
	  { 0x00,0x00,0x11,0x11,0x15,0x15,0x1A,0x00 }, /* w */
	  { 0x00,0x00,0x05,0x05,0x02,0x05,0x05,0x00 }, /* x */
	  { 0x00,0x00,0x09,0x09,0x09,0x0E,0x08,0x07 }, /* y */
	  { 0x00,0x00,0x0F,0x08,0x04,0x02,0x0F,0x00 }, /* z */
	  { 0x04,0x02,0x02,0x01,0x02,0x02,0x04,0x00 }, /* { */
	  { 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01 }, /* | */
	  { 0x01,0x02,0x02,0x04,0x02,0x02,0x01,0x00 }, /* } */
	  { 0x00,0x00,0x26,0x19,0x00,0x00,0x00,0x00 }, /* ~ */
};

static int Fallback_CellWidth(const cc_uint8* rows) {
	int y, width, widest = 0;

	for (y = 0; y < CELL_SIZE; y++) 
	{
		widest = max(widest, rows[y]);
	}
	width = Math_ilog2(widest) + 1;

	return width + 1; /* add 1 for padding */
}

int FallbackFont_TextWidth(const struct DrawTextArgs* args) { 
	cc_string left = args->text, part;
	int size  = args->font->size;
	int scale = FallbackFont_GetScale(size);
	char colorCode = 'f';
	int i, width = 0;

	while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode))
	{
		for (i = 0; i < part.length; i++) 
		{
			cc_uint8 c = part.buffer[i];
			if (c == ' ') {
				width += SPACE_WIDTH * scale;
			} else {
				width += Fallback_CellWidth(FallbackFont_GetRows(c)) * scale;
			}
		}
	}

	width = max(1, width);
	if (args->useShadow) width += 1 * scale;
	return width;
}

static void Fallback_DrawCell(struct Bitmap* bmp, int x, int y, 
					int scale, const cc_uint8* rows, BitmapCol color) {
	int xx, srcX, dstX;
	int yy, srcY, dstY;
	BitmapCol* dst_row;
	cc_uint8 src_row;

	for (srcY = 0, dstY = y; srcY < CELL_SIZE; srcY++)
	{
		for (yy = 0; yy < scale; yy++, dstY++)
		{
			if (dstY < 0 || dstY >= bmp->height) continue;
	
			dst_row = Bitmap_GetRow(bmp, dstY);
			src_row = rows[srcY];
	
			for (srcX = 0, dstX = x; srcX < CELL_SIZE; srcX++)
			{
				for (xx = 0; xx < scale; xx++, dstX++)
				{
					if (dstX < 0 || dstX >= bmp->width) continue;
		
					if (src_row & (1 << srcX)) {
						dst_row[dstX] = color;
					}
				}
			}
		}
	}
}

void FallbackFont_DrawText(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) {
	cc_string left = args->text, part;
	int size  = args->font->size;
	int scale = FallbackFont_GetScale(size);
	char colorCode = 'f';
	const cc_uint8* rows;
	BitmapCol color;
	int i;

	if (shadow) { 
		x += 1 * scale; 
		y += 1 * scale;
	}

	/* adjust coords to make drawn text match GDI fonts */
	y += (args->font->height - size) / 2;

	while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode))
	{
		color = Drawer2D_GetColor(colorCode);
		if (shadow) color = GetShadowColor(color);
	
		for (i = 0; i < part.length; i++) 
		{
			cc_uint8 c = part.buffer[i];
			if (c == ' ') { x += SPACE_WIDTH * scale; continue; }

			rows = FallbackFont_GetRows(c);

			Fallback_DrawCell(bmp, x, y, scale, rows, color);
			x += Fallback_CellWidth(rows) * scale;
		}
	}
}

static void Fallback_PlotCell(int x, int scale, const cc_uint8* rows, FallbackFont_Plotter plotter, void* ctx) {
	int xx, srcX, dstX;
	int yy, srcY, dstY;
	cc_uint8 src_row;

	for (srcY = 0; srcY < CELL_SIZE; srcY++)
	{
		src_row = rows[srcY];

		for (srcX = 0; srcX < CELL_SIZE; srcX++)
		{
			if (!(src_row & (1 << srcX))) continue;			
			dstX = x + srcX * scale;
			dstY = 0 + srcY * scale;

			for (yy = 0; yy < scale; yy++)
				for (xx = 0; xx < scale; xx++)
			{
				plotter(dstX + xx, dstY + yy, ctx);
			}
		}
	}
}

void FallbackFont_Plot(cc_string* str, FallbackFont_Plotter plotter, int scale, void* ctx) {
	const cc_uint8* rows;
	int i, x = 0;

	for (i = 0; i < str->length; i++) 
	{
		cc_uint8 c = str->buffer[i];
		if (c == ' ') { x += SPACE_WIDTH * scale; continue; }

		rows = FallbackFont_GetRows(c);

		Fallback_PlotCell(x, scale, rows, plotter, ctx);
		x += Fallback_CellWidth(rows) * scale;
	}
}


/*########################################################################################################################*
*--------------------------------------------------------Freetype---------------------------------------------------------*
*#########################################################################################################################*/
#if defined CC_BUILD_FREETYPE
#include "freetype/ft2build.h"
#include "freetype/freetype.h"
#include "freetype/ftmodapi.h"
#include "freetype/ftglyph.h"

int cc_strncmp(const char* strA, const char* strB, size_t maxCount) {
	const unsigned char* a = (const unsigned char*)strA;
	const unsigned char* b = (const unsigned char*)strB;
	int i;

	for (i = 0; a[i] && i < maxCount; i++)
	{
		if (a[i] != b[i]) return a[i] - b[i];
	}
	return 0;
}

int cc_strcmp(const char* strA, const char* strB) {
	const unsigned char* a = (const unsigned char*)strA;
	const unsigned char* b = (const unsigned char*)strB;
	int i;

	for (i = 0; a[i]; i++)
	{
		if (a[i] != b[i]) return a[i] - b[i];
	}
	return 0;
}

size_t cc_strlen(const char* a) {
	int i = 0;
	while (*a++) i++;
	return i;
}

char* cc_strstr(const char* str, const char* substr) {
	if (!substr[0]) return (char*)str;

	for (; *str; str++) 
	{
		/* Definitely not a match */
		if (*str != substr[0]) continue;

		/* It's a possible match */
		if (cc_strcmp(str, substr) == 0) return (char*)str;
	}
	return NULL;
}

int cc_memcmp(const void* ptrA, const void* ptrB, size_t num) {
	const unsigned char* a = (const unsigned char*)ptrA;
	const unsigned char* b = (const unsigned char*)ptrB;

	for (; num > 0; num--, a++, b++)
	{
		if (*a != *b) return *a - *b;
	}
	return 0;
}

void* cc_memchr(const void* ptr, int ch, size_t num) {
	const char* a = (const char*)ptr;

	for (; num > 0; num--, a++)
	{
		if (*a == ch) return (void*)a;
	}
	return NULL;
}

static void swap(void* v1, void* v2, int size) {
	char buffer[1024]; 

	Mem_Copy(buffer, v1, size); 
	Mem_Copy(v1,     v2, size); 
	Mem_Copy(v2, buffer, size); 
}

// https://www.geeksforgeeks.org/generic-implementation-of-quicksort-algorithm-in-c/
static void _qsort(void* v, int size, int left, int right,
					int (*comp)(const void*, const void*)) {
	void* ptrL;
	void* ptrM;
	void* ptrI;
	void* ptrE; 
	int i, last, mid;

	if (left >= right) return;
	mid = (left + right) / 2; 

	ptrL = (char*)v + (left * size); 
	ptrM = (char*)v + (mid  * size); 
	swap(ptrL, ptrM, size); 
	last = left; 

	for (i = left + 1; i <= right; i++) 
	{
		ptrI = (char*)v + (i * size); 
		if ((*comp)(ptrL, ptrI) > 0) { 
			last++;
			ptrE = (char*)v + (last * size); 
			swap(ptrI, ptrE, size); 
		} 
	}

	ptrE = (char*)v + (last * size); 
	swap(ptrL, ptrE, size); 
	_qsort(v, size, left, last - 1,  comp); 
	_qsort(v, size, last + 1, right, comp); 
}

void cc_qsort(void* v, size_t count, size_t size,
					int (*comp)(const void*, const void*)) {
	if (!count) return;
	_qsort(v, size, 0, count - 1, comp);
}



static FT_Library ft_lib;
static struct FT_MemoryRec_ ft_mem;
static struct StringsBuffer font_list;
static cc_bool fonts_changed;
/* Finds the path and face number of the given system font, with closest matching style */
static cc_string Font_Lookup(const cc_string* fontName, int flags);

struct SysFont {
	FT_Face face;
	struct Stream src, file;
	FT_StreamRec stream;
	cc_uint8 buffer[8192]; /* small buffer to minimise disk I/O */
	cc_uint16 widths[256]; /* cached width of each character glyph */
	FT_BitmapGlyph glyphs[256];	       /* cached glyphs */
	FT_BitmapGlyph shadow_glyphs[256]; /* cached glyphs (for back layer shadow) */
#ifdef CC_BUILD_DARWIN
	char filename[FILENAME_SIZE + 1];
#endif
};

static unsigned long SysFont_Read(FT_Stream s, unsigned long offset, unsigned char* buffer, unsigned long count) {
	struct SysFont* font;
	cc_result res;
	if (!count && offset > s->size) return 1;

	font = (struct SysFont*)s->descriptor.pointer;
	if (s->pos != offset) font->src.Seek(&font->src, offset);

	res = Stream_Read(&font->src, buffer, count);
	return res ? 0 : count;
}

static void SysFont_Done(struct SysFont* font) {
	int i;

	/* Close the actual underlying file */
	struct Stream* source = &font->file;
	if (!source->meta.file) return;
	source->Close(source);

	for (i = 0; i < 256; i++) 
	{
		if (!font->glyphs[i]) continue;
		FT_Done_Glyph((FT_Glyph)font->glyphs[i]);
	}
	for (i = 0; i < 256; i++) 
	{
		if (!font->shadow_glyphs[i]) continue;
		FT_Done_Glyph((FT_Glyph)font->shadow_glyphs[i]);
	}
}

static void SysFont_Close(FT_Stream stream) {
	struct SysFont* font = (struct SysFont*)stream->descriptor.pointer;
	SysFont_Done(font);
}

static cc_result SysFont_Init(const cc_string* path, struct SysFont* font, FT_Open_Args* args) {
	cc_filepath str;
	cc_file file;
	cc_uint32 size;
	cc_result res;
#ifdef CC_BUILD_DARWIN
	cc_string filename;
#endif

	Platform_EncodePath(&str, path);
	if ((res = File_Open(&file, &str))) return res;
	if ((res = File_Length(file, &size))) { File_Close(file); return res; }

	font->stream.base = NULL;
	font->stream.size = size;
	font->stream.pos  = 0;

	font->stream.descriptor.pointer = font;
	font->stream.read   = SysFont_Read;
	font->stream.close  = SysFont_Close;

	font->stream.memory = &ft_mem;
	font->stream.cursor = NULL;
	font->stream.limit  = NULL;

	args->flags    = FT_OPEN_STREAM;
	args->pathname = NULL;
	args->stream   = &font->stream;

	Stream_FromFile(&font->file, file);
	Stream_ReadonlyBuffered(&font->src, &font->file, font->buffer, sizeof(font->buffer));

	/* For OSX font suitcase files */
#ifdef CC_BUILD_DARWIN
	String_InitArray_NT(filename, font->filename);
	String_Copy(&filename, path);
	font->filename[filename.length] = '\0';
	args->pathname = font->filename;
#endif
	Mem_Set(font->widths,        0xFF, sizeof(font->widths));
	Mem_Set(font->glyphs,        0x00, sizeof(font->glyphs));
	Mem_Set(font->shadow_glyphs, 0x00, sizeof(font->shadow_glyphs));
	return 0;
}

static void* FT_AllocWrapper(FT_Memory memory, long size) { return Mem_TryAlloc(size, 1); }
static void FT_FreeWrapper(FT_Memory memory, void* block) { Mem_Free(block); }
static void* FT_ReallocWrapper(FT_Memory memory, long cur_size, long new_size, void* block) {
	return Mem_TryRealloc(block, new_size, 1);
}


#define FONT_CACHE_FILE "fontscache.txt"
static cc_string font_candidates[] = {
	String_FromConst(""),			     /* replaced with font_default */
	String_FromConst("Arial"),		     /* preferred font on all platforms */
	String_FromConst("Liberation Sans"), /* Nice looking fallbacks for linux */
	String_FromConst("Nimbus Sans"),
	String_FromConst("Bitstream Charter"),
	String_FromConst("Cantarell"),
	String_FromConst("DejaVu Sans Book"), 
	String_FromConst("Century Schoolbook L Roman"), /* commonly available on linux */
	String_FromConst("Liberation Serif"), /* for SerenityOS */
	String_FromConst("Slate For OnePlus"), /* Android 10, some devices */
	String_FromConst("Roboto"), /* Android (broken on some Android 10 devices) */
	String_FromConst("Geneva"), /* for ancient macOS versions */
	String_FromConst("Droid Sans"), /* for old Android versions */
#if defined CC_BUILD_SYMBIAN
	String_FromConst("Nokia Sans TitleSmBd S60"),
	String_FromConst("Nokia Sans S60"),
	String_FromConst("Nokia Hindi S60"),
	String_FromConst("Series 60 Sans TitleSmBd"),
	String_FromConst("Series 60 Sans"),
	String_FromConst("Series 60 Hindi"),
	String_FromConst("Series 60 Korean"),
	String_FromConst("Heisei Kaku Gothic S60"),
#endif
	String_FromConst("Google Sans") /* Droid Sans is now known as Google Sans on some Android devices (e.g. a Pixel 6) */
};

static void InitFreeTypeLibrary(void) {
	FT_Error err;
	if (ft_lib) return;

	ft_mem.alloc   = FT_AllocWrapper;
	ft_mem.free    = FT_FreeWrapper;
	ft_mem.realloc = FT_ReallocWrapper;

	err = FT_New_Library(&ft_mem, &ft_lib);
	if (err) Process_Abort2(err, "Failed to init freetype");
	FT_Add_Default_Modules(ft_lib);
}

static cc_bool loadedPlatformFonts;
/* Updates fonts list cache with platform's list of fonts */
/* This should be avoided due to overhead potential */
static void SysFonts_LoadPlatform(void) {
	if (loadedPlatformFonts) return;
	loadedPlatformFonts = true;

	/* TODO this basically gets called all the time on non-Window platforms */
	/* Maybe we can cache the default system font to avoid this extra work? */
	if (font_list.count == 0)
		Window_ShowDialog("One time load", "Initialising font cache, this can take several seconds.");

	InitFreeTypeLibrary();
	Platform_LoadSysFonts();

	if (fonts_changed) SysFonts_SaveCache();
}

static cc_bool loadedCachedFonts;
static void SysFonts_LoadCached(void) {
	if (loadedCachedFonts) return;
	loadedCachedFonts = true;

	EntryList_UNSAFE_Load(&font_list, FONT_CACHE_FILE);
}

void SysFonts_SaveCache(void) {
	EntryList_Save(&font_list, FONT_CACHE_FILE);
}


/* Some language-specific fonts don't support English letters */
/* and show entirely as '[]' - better off ignoring such fonts */
static cc_bool SysFonts_SkipFont(FT_Face face) {
	if (!face->charmap) return false;

	return FT_Get_Char_Index(face, 'a') == 0 && FT_Get_Char_Index(face, 'z') == 0
		&& FT_Get_Char_Index(face, 'A') == 0 && FT_Get_Char_Index(face, 'Z') == 0;
}

static void SysFonts_Add(const cc_string* path, FT_Face face, int index, char type, const char* defStyle) {
	cc_string key;   char keyBuffer[STRING_SIZE];
	cc_string value; char valueBuffer[FILENAME_SIZE];
	cc_string style = String_Empty;

	if (!face->family_name || !(face->face_flags & FT_FACE_FLAG_SCALABLE)) return;
	/* don't want 'Arial Regular' or 'Arial Bold' */
	if (face->style_name) {
		style = String_FromReadonly(face->style_name);
		if (String_CaselessEqualsConst(&style, defStyle)) style.length = 0;
	}
	if (SysFonts_SkipFont(face)) type = 'X';

	String_InitArray(key, keyBuffer);
	if (style.length) {
		String_Format3(&key, "%c %c %r", face->family_name, face->style_name, &type);
	} else {
		String_Format2(&key, "%c %r", face->family_name, &type);
	}

	String_InitArray(value, valueBuffer);
	String_Format2(&value, "%s,%i", path, &index);

	Platform_Log2("Face: %s = %s", &key, &value);
	EntryList_Set(&font_list, &key, &value, '=');
	fonts_changed = true;
}

static int SysFonts_DoRegister(const cc_string* path, int faceIndex, SysFont_RegisterCallback callback) {
	struct SysFont font;
	FT_Open_Args args;
	FT_Error err;
	int flags, count;

	if (SysFont_Init(path, &font, &args)) return 0;
	err = FT_New_Face(ft_lib, &args, faceIndex, &font.face);
	if (err) { SysFont_Done(&font); return 0; }

	flags = font.face->style_flags;
	count = font.face->num_faces;

	if (flags == (FT_STYLE_FLAG_BOLD | FT_STYLE_FLAG_ITALIC)) {
		SysFonts_Add(path, font.face, faceIndex, 'Z', "Bold Italic");
	} else if (flags == FT_STYLE_FLAG_BOLD) {
		SysFonts_Add(path, font.face, faceIndex, 'B', "Bold");
	} else if (flags == FT_STYLE_FLAG_ITALIC) {
		SysFonts_Add(path, font.face, faceIndex, 'I', "Italic");
	} else if (flags == 0) {
		SysFonts_Add(path, font.face, faceIndex, 'R', "Regular");
	}

	if (callback) callback(path);
	FT_Done_Face(font.face);
	return count;
}

cc_result SysFonts_Register(const cc_string* path, SysFont_RegisterCallback callback) {
	cc_string entry, name, value;
	cc_string fontPath, index;
	int i, count;

	/* if font is already known, skip it */
	for (i = 0; i < font_list.count; i++) {
		StringsBuffer_UNSAFE_GetRaw(&font_list, i, &entry);
		String_UNSAFE_Separate(&entry, '=', &name, &value);

		String_UNSAFE_Separate(&value, ',', &fontPath, &index);
		if (String_CaselessEquals(path, &fontPath)) return 0;
	}

	count = SysFonts_DoRegister(path, 0, callback);
	/* there may be more than one font in a font file */
	for (i = 1; i < count; i++) {
		SysFonts_DoRegister(path, i, callback);
	}
	return 0;
}


static cc_string Font_LookupOf(const cc_string* fontName, const char type) {
	cc_string name; char nameBuffer[STRING_SIZE + 2];
	String_InitArray(name, nameBuffer);

	String_Format2(&name, "%s %r", fontName, &type);
	return EntryList_UNSAFE_Get(&font_list, &name, '=');
}

static cc_string Font_DoLookup(const cc_string* fontName, int flags) {
	cc_string path = String_Empty;

	if (flags & FONT_FLAGS_BOLD) path = Font_LookupOf(fontName, 'B');
	return path.length ? path : Font_LookupOf(fontName, 'R');
}

static cc_string Font_Lookup(const cc_string* fontName, int flags) {
	cc_string path;
	
	SysFonts_LoadCached();
	path = Font_DoLookup(fontName, flags);
	if (path.length) return path;

	SysFonts_LoadPlatform();
	return Font_DoLookup(fontName, flags);
}


const cc_string* SysFonts_UNSAFE_GetDefault(void) {
	cc_string* font, path;
	int i;
	font_candidates[0] = font_default;

	for (i = 0; i < Array_Elems(font_candidates); i++) {
		font = &font_candidates[i];
		if (!font->length) continue;

		path = Font_Lookup(font, FONT_FLAGS_NONE);
		if (path.length) return font;
	}
	return &String_Empty;
}

void SysFonts_GetNames(struct StringsBuffer* buffer) {
	cc_string entry, name, path;
	int i;
	SysFonts_LoadCached();
	SysFonts_LoadPlatform();

	for (i = 0; i < font_list.count; i++) {
		StringsBuffer_UNSAFE_GetRaw(&font_list, i, &entry);
		String_UNSAFE_Separate(&entry, '=', &name, &path);

		/* only want Regular fonts here */
		if (name.length < 2 || name.buffer[name.length - 1] != 'R') continue;
		name.length -= 2;
		StringsBuffer_Add(buffer, &name);
	}
	StringsBuffer_Sort(buffer);
}

#define TEXT_CEIL(x) (((x) + 63) >> 6)
cc_result SysFont_Make(struct FontDesc* desc, const cc_string* fontName, int size, int flags) {
	struct SysFont* font;
	cc_string value, path, index;
	int faceIndex, dpiX, dpiY;
	FT_Open_Args args;
	FT_Error err;

	desc->size   = size;
	desc->flags  = flags;
	desc->handle = NULL;

	value = Font_Lookup(fontName, flags);
	if (!value.length) return ERR_INVALID_ARGUMENT;
	String_UNSAFE_Separate(&value, ',', &path, &index);
	Convert_ParseInt(&index, &faceIndex);

	font = (struct SysFont*)Mem_TryAlloc(1, sizeof(struct SysFont));
	if (!font) return ERR_OUT_OF_MEMORY;

	InitFreeTypeLibrary();
	if ((err = SysFont_Init(&path, font, &args))) { Mem_Free(font); return err; }
	desc->handle = font;

	/* TODO: Use 72 instead of 96 dpi for mobile devices */
	dpiX = (int)(DisplayInfo.ScaleX * 96);
	dpiY = (int)(DisplayInfo.ScaleY * 96);

	if ((err = FT_New_Face(ft_lib, &args, faceIndex, &font->face)))     return err;
	if ((err = FT_Set_Char_Size(font->face, size * 64, 0, dpiX, dpiY))) return err;

	/* height of any text when drawn with the given system font */
	desc->height = TEXT_CEIL(font->face->size->metrics.height);
	return 0;
}

void SysFont_MakeDefault(struct FontDesc* desc, int size, int flags) {
	cc_string* font;
	cc_result res;
	int i;
	font_candidates[0] = font_default;

	for (i = 0; i < Array_Elems(font_candidates); i++) {
		font = &font_candidates[i];
		if (!font->length) continue;
		res  = SysFont_Make(desc, &font_candidates[i], size, flags);

		/* Cached system fonts list may be outdated - force update it */
		if (res == ReturnCode_FileNotFound && !loadedPlatformFonts) {
			StringsBuffer_Clear(&font_list);
			SysFonts_LoadPlatform();
			res = SysFont_Make(desc, &font_candidates[i], size, flags);
		}

		if (res == ERR_INVALID_ARGUMENT) {
			/* Fon't doesn't exist in list, skip over it */
		} else if (res) {
			Font_Free(desc);
			Logger_SysWarn2(res, "creating font", font);
		} else {
			if (i) String_Copy(&font_candidates[0], font);
			return;
		}
	}

	Window_ShowDialog("Failed to init default font", "Falling back to built-in font");
	Font_MakeBitmapped(desc, size, flags);
}

void SysFont_Free(struct FontDesc* desc) {
	struct SysFont* font = (struct SysFont*)desc->handle;
	FT_Done_Face(font->face);
	Mem_Free(font);
}

int SysFont_TextWidth(struct DrawTextArgs* args) {
	struct SysFont* font = (struct SysFont*)args->font->handle;
	FT_Face face   = font->face;
	cc_string text = args->text;
	int i, width = 0, charWidth;
	FT_Error res;
	cc_unichar uc;

	for (i = 0; i < text.length; i++) {
		char c = text.buffer[i];
		if (c == '&' && Drawer2D_ValidColorCodeAt(&text, i + 1)) {
			i++; continue; /* skip over the color code */
		}

		charWidth = font->widths[(cc_uint8)c];
		/* need to calculate glyph width */
		if (charWidth == UInt16_MaxValue) {
			uc  = Convert_CP437ToUnicode(c);
			res = FT_Load_Char(face, uc, 0);

			if (res) {
				Platform_Log2("Error %e measuring width of %r", &res, &c);
				charWidth = 0;
			} else {
				charWidth = face->glyph->advance.x;		
			}

			font->widths[(cc_uint8)c] = charWidth;
		}
		width += charWidth;
	}
	if (!width) return 0;

	width = TEXT_CEIL(width);
	if (args->useShadow) width += 2;
	return width;
}

static void DrawGrayscaleGlyph(FT_Bitmap* img, struct Bitmap* bmp, int x, int y, BitmapCol col) {
	cc_uint8* src;
	BitmapCol* dst;
	cc_uint8 I, invI; /* intensity */
	int xx, yy;

	for (yy = 0; yy < img->rows; yy++) {
		if ((unsigned)(y + yy) >= (unsigned)bmp->height) continue;
		src = img->buffer + (yy * img->pitch);
		dst = Bitmap_GetRow(bmp, y + yy) + x;

		for (xx = 0; xx < img->width; xx++, src++, dst++) {
			if ((unsigned)(x + xx) >= (unsigned)bmp->width) continue;

			I = *src; invI = UInt8_MaxValue - I;
			if (!I) continue;

			/* TODO: Support transparent text */
			/* dst->A = ((col.A * intensity) >> 8) + ((dst->A * invIntensity) >> 8);*/
			/* TODO: Not shift when multiplying */
			*dst = BitmapCol_Make(
				((BitmapCol_R(col) * I) >> 8) + ((BitmapCol_R(*dst) * invI) >> 8),
				((BitmapCol_G(col) * I) >> 8) + ((BitmapCol_G(*dst) * invI) >> 8),
				((BitmapCol_B(col) * I) >> 8) + ((BitmapCol_B(*dst) * invI) >> 8),
				                           I  + ((BitmapCol_A(*dst) * invI) >> 8)
			);
		}
	}
}

static void DrawBlackWhiteGlyph(FT_Bitmap* img, struct Bitmap* bmp, int x, int y, BitmapCol col) {
	cc_uint8* src;
	BitmapCol* dst;
	cc_uint8 intensity;
	int xx, yy;

	for (yy = 0; yy < img->rows; yy++) {
		if ((unsigned)(y + yy) >= (unsigned)bmp->height) continue;
		src = img->buffer + (yy * img->pitch);
		dst = Bitmap_GetRow(bmp, y + yy) + x;

		for (xx = 0; xx < img->width; xx++, dst++) {
			if ((unsigned)(x + xx) >= (unsigned)bmp->width) continue;
			intensity = src[xx >> 3];

			/* TODO: transparent text (don't set A to 255) */
			if (intensity & (1 << (7 - (xx & 7)))) {
				*dst = col | BitmapColor_A_Bits(255);
			}
		}
	}
}

static FT_Vector shadow_delta = { 83, -83 };
void SysFont_DrawText(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) {
	struct SysFont* font  = (struct SysFont*)args->font->handle;
	FT_BitmapGlyph* glyphs = font->glyphs;

	FT_Face face   = font->face;
	cc_string text = args->text;
	int descender, height, begX = x;
	BitmapCol color;
	
	/* glyph state */
	FT_BitmapGlyph glyph;
	FT_Bitmap* img;
	int i, offset;
	FT_Error res;
	cc_unichar uc;

	if (shadow) {
		glyphs = font->shadow_glyphs;
		FT_Set_Transform(face, NULL, &shadow_delta);
	}

	height    = args->font->height;
	descender = TEXT_CEIL(face->size->metrics.descender);

	color = Drawer2D.Colors['f'];
	if (shadow) color = GetShadowColor(color);

	for (i = 0; i < text.length; i++) {
		char c = text.buffer[i];
		if (c == '&' && Drawer2D_ValidColorCodeAt(&text, i + 1)) {
			color = Drawer2D_GetColor(text.buffer[i + 1]);

			if (shadow) color = GetShadowColor(color);
			i++; continue; /* skip over the color code */
		}

		glyph = glyphs[(cc_uint8)c];
		if (!glyph) {
			uc  = Convert_CP437ToUnicode(c);
			res = FT_Load_Char(face, uc, FT_LOAD_RENDER);

			if (res) {
				Platform_Log2("Error %e drawing %r", &res, &text.buffer[i]);
				continue;
			}

			/* due to FT_LOAD_RENDER, glyph is always a bitmap one */
			FT_Get_Glyph(face->glyph, (FT_Glyph*)&glyph); /* TODO: Check error */
			glyphs[(cc_uint8)c] = glyph;
		}

		offset = (height + descender) - glyph->top;
		x += glyph->left; y += offset;

		img = &glyph->bitmap;
		if (img->num_grays == 2) {
			DrawBlackWhiteGlyph(img, bmp, x, y, color);
		} else {
			DrawGrayscaleGlyph(img, bmp, x, y, color);
		}

		x += TEXT_CEIL(glyph->root.advance.x >> 10);
		x -= glyph->left; y -= offset;
	}

	if (args->font->flags & FONT_FLAGS_UNDERLINE) {
		int ul_pos   = FT_MulFix(face->underline_position,  face->size->metrics.y_scale);
		int ul_thick = FT_MulFix(face->underline_thickness, face->size->metrics.y_scale);

		int ulHeight = TEXT_CEIL(ul_thick);
		int ulY	     = height + TEXT_CEIL(ul_pos);
		Drawer2D_Fill(bmp, begX, ulY + y, x - begX, ulHeight, color);
	}

	if (shadow) FT_Set_Transform(face, NULL, NULL);
}
#elif defined CC_BUILD_WEB
static cc_string font_arial = String_FromConst("Arial");

const cc_string* SysFonts_UNSAFE_GetDefault(void) {
	/* Fallback to Arial as default font */
	/* TODO use serif instead?? */
	return font_default.length ? &font_default : &font_arial;
}

void SysFonts_GetNames(struct StringsBuffer* buffer) {
	static const char* font_names[] = { 
		"Arial", "Arial Black", "Courier New", "Comic Sans MS", "Georgia", "Garamond", 
		"Helvetica", "Impact", "Tahoma", "Times New Roman", "Trebuchet MS", "Verdana",
		"cursive", "fantasy", "monospace", "sans-serif", "serif", "system-ui"
	};
	int i;

	for (i = 0; i < Array_Elems(font_names); i++) {
		cc_string str = String_FromReadonly(font_names[i]);
		StringsBuffer_Add(buffer, &str);
	}
}

cc_result SysFont_Make(struct FontDesc* desc, const cc_string* fontName, int size, int flags) {
	desc->size   = size;
	desc->flags  = flags;
	desc->height = Drawer2D_AdjHeight(size);

	desc->handle = Mem_TryAlloc(fontName->length + 1, 1);
	if (!desc->handle) return ERR_OUT_OF_MEMORY;
	
	String_CopyToRaw(desc->handle, fontName->length + 1, fontName);
	return 0;
}

void SysFont_MakeDefault(struct FontDesc* desc, int size, int flags) {
	SysFont_Make(desc, SysFonts_UNSAFE_GetDefault(), size, flags);
}

void SysFont_Free(struct FontDesc* desc) {
	Mem_Free(desc->handle);
}

void SysFonts_SaveCache(void) { }
cc_result SysFonts_Register(const cc_string* path, SysFont_RegisterCallback callback) {
	return ERR_NOT_SUPPORTED;
}

extern void   interop_SetFont(const char* font, int size, int flags);
extern double interop_TextWidth(const char* text, const int len);
extern double interop_TextDraw(const char* text, const int len, struct Bitmap* bmp, int x, int y, cc_bool shadow, const char* hex);

int SysFont_TextWidth(struct DrawTextArgs* args) {
	struct FontDesc* font = args->font;
	cc_string left = args->text, part;
	double width   = 0;
	char colorCode;

	interop_SetFont(font->handle, font->size, font->flags);
	while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode))
	{
		char buffer[NATIVE_STR_LEN];
		int len = String_EncodeUtf8(buffer, &part);
		width += interop_TextWidth(buffer, len);
	}
	return Math_Ceil(width);
}

void SysFont_DrawText(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) {
	struct FontDesc* font = args->font;
	cc_string left  = args->text, part;
	BitmapCol color;
	char colorCode = 'f';
	double xOffset = 0;
	char hexBuffer[7];
	cc_string hex;

	/* adjust y position to more closely match FreeType drawn text */
	y += (args->font->height - args->font->size) / 2;
	interop_SetFont(font->handle, font->size, font->flags);

	while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode))
	{
		char buffer[NATIVE_STR_LEN];
		int len = String_EncodeUtf8(buffer, &part);

		color = Drawer2D_GetColor(colorCode);
		if (shadow) color = GetShadowColor(color);

		String_InitArray(hex, hexBuffer);
		String_Append(&hex, '#');
		String_AppendHex(&hex, BitmapCol_R(color));
		String_AppendHex(&hex, BitmapCol_G(color));
		String_AppendHex(&hex, BitmapCol_B(color));

		/* TODO pass as double directly instead of (int) ?*/
		xOffset += interop_TextDraw(buffer, len, bmp, x + (int)xOffset, y, shadow, hexBuffer);
	}
}
#elif defined CC_BUILD_IOS
/* implemented in interop_ios.m */
extern void interop_GetFontNames(struct StringsBuffer* buffer);
extern cc_result interop_SysFontMake(struct FontDesc* desc, const cc_string* fontName, int size, int flags);
extern void interop_SysMakeDefault(struct FontDesc* desc, int size, int flags);
extern void interop_SysFontFree(void* handle);
extern int interop_SysTextWidth(struct DrawTextArgs* args);
extern void interop_SysTextDraw(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow);

void SysFonts_SaveCache(void) { }
cc_result SysFonts_Register(const cc_string* path, SysFont_RegisterCallback callback) {
	return ERR_NOT_SUPPORTED;
}

const cc_string* SysFonts_UNSAFE_GetDefault(void) {
	return &String_Empty;
}

void SysFonts_GetNames(struct StringsBuffer* buffer) {
	interop_GetFontNames(buffer);
}

cc_result SysFont_Make(struct FontDesc* desc, const cc_string* fontName, int size, int flags) {
	return interop_SysFontMake(desc, fontName, size, flags);
}

void SysFont_MakeDefault(struct FontDesc* desc, int size, int flags) {
	interop_SysMakeDefault(desc, size, flags);
}

void SysFont_Free(struct FontDesc* desc) {
	interop_SysFontFree(desc->handle);
}

int SysFont_TextWidth(struct DrawTextArgs* args) {
	return interop_SysTextWidth(args);
}

void SysFont_DrawText(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) {
	interop_SysTextDraw(args, bmp, x, y, shadow);
}
#else
void SysFonts_SaveCache(void) { }
cc_result SysFonts_Register(const cc_string* path, SysFont_RegisterCallback callback) {
	return ERR_NOT_SUPPORTED;
}

const cc_string* SysFonts_UNSAFE_GetDefault(void) { return &String_Empty; }

void SysFonts_GetNames(struct StringsBuffer* buffer) { }

cc_result SysFont_Make(struct FontDesc* desc, const cc_string* fontName, int size, int flags) {
	size *= DisplayInfo.ScaleY;
	/* Round upwards to nearest 8 */
	size = (size + 7) & ~0x07;

	desc->size   = size;
	desc->flags  = flags;
	desc->height = Drawer2D_AdjHeight(size);

	desc->handle = (void*)1;
	return 0;
}

void SysFont_MakeDefault(struct FontDesc* desc, int size, int flags) {
	SysFont_Make(desc, NULL, size, flags);
}

void SysFont_Free(struct FontDesc* desc) {
	desc->handle = NULL;
}

int SysFont_TextWidth(struct DrawTextArgs* args) {
	return FallbackFont_TextWidth(args);
}

void SysFont_DrawText(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) {
	FallbackFont_DrawText(args, bmp, x, y, shadow);
}
#endif
