module dlib.fonts;

import dlibincludes;
import dlib.aliases;
import dlib.util;
import dlib.alloc;

enum AtlasType
{
	None = 0,
	SoftMask,
}

enum YOrigin
{
	None = 0,
	Bottom,
}

struct FontAtlas
{
	AtlasType type;
	f32 			size;
	u32				width;
	u32				height;
	YOrigin	  y_origin;
	f32				em_size;
	f32				line_height;
	f32				ascender;
	f32				descender;
	f32				underline_y;
	f32				underline_thickness;
	Glyph[128] glyphs;
}

struct Glyph
{
	dchar ch;
	f32		advance;
	f32		plane_left;
	f32		plane_bottom;
	f32 	plane_right;
	f32 	plane_top;
	f32		atlas_left;
	f32 	atlas_bottom;
	f32		atlas_right;
	f32		atlas_top;
}

__gshared FT_Library FT_LIB;
alias FontFace = FT_Face;

struct FontAtlasBuf
{
	u8[] 	data;
	FontAtlas atlas;
}

shared static this()
{
	FT_Init_FreeType(&FT_LIB);
}

void
CloseFreeType()
{
	if(FT_LIB)
	{
		FT_Done_FreeType(FT_LIB);
	}
}

FontFace
OpenFont(u8[] data)
{
	FontFace font;
	FT_New_Memory_Face(FT_LIB, data.ptr, cast(FT_Long)data.length, 0, &font);
	return font;
}

void
CloseFont(FontFace font)
{
	if(font != null)
	{
		FT_Done_Face(font);
	}
}

i32
Float26(f32 f)
{
	return cast(i32)round(cast(f32)(1 << 6) * f);
}

u8
DeMultiply(u8 color, u8 alpha)
{
	u32 v = cast(u32)(255.0 * cast(f32)(color) / cast(f32)(alpha + f32.epsilon) + 0.5);
	return v > 255 ? 255 : cast(u8)v;
}

FontAtlasBuf
CreateAtlas(Arena* arena, FontFace font, f32 size, u32 dimension)
{
	assert(dimension >= 128, "Dimension must be at least 128");

	FontAtlasBuf atlas = {
		data: Alloc!(u8)(arena, dimension * dimension * 4),
		atlas: {
			size: size,
			width: dimension,
			height: dimension,
		},
	};

	// TODO: proper packing algorithm
	if(font != null)
	{
		i64 f_ascent = cast(i64)(font.size.metrics.ascender >> 6);
		i64 f_descent = cast(i64)(font.size.metrics.descender >> 6);
		i64 f_height = cast(i64)(font.size.metrics.height >> 6);

		u32 max_w = 0;
		u32 max_h = 0;
		u32 current_h = 0;
		u32 count = 0;

		i32 font_size = Float26(size);

		foreach(FT_ULong char_code; 0 .. 0x7F)
		{
			FT_Set_Char_Size(font, font_size, 0, 0, 0);
			FT_Error res = FT_Load_Char(font, char_code, cast(FT_Int32)FT_LOAD_RENDER);
			if(res != 0)
			{
				continue;
			}

			u32 bmp_w = font.glyph.bitmap.width;
			u32 bmp_h = font.glyph.bitmap.rows;
			if(max_w + bmp_w > dimension)
			{
				max_h += current_h;
				max_w = 0;
			}

			assert(max_h < dimension, "Unable to pack atlas within dimensions");
			
			max_w += bmp_w;
			current_h = bmp_h > current_h ? bmp_h : current_h;
			count += 1;
		}

		const u32 PADDING = 2;

		max_w = PADDING;
		max_h = PADDING;
		current_h = 0;
		count = 0;

		foreach(FT_ULong char_code; 0 .. 0x7F)
		{
			FT_Set_Char_Size(font, font_size, 0, 0, 0);
			FT_Error res = FT_Load_Char(font, char_code, cast(FT_Int32)FT_LOAD_RENDER);
			if(res != 0)
			{
				continue;
			}

			FT_GlyphSlot glyph = font.glyph;
			FT_Bitmap* bmp = &font.glyph.bitmap;
			i32 top = font.glyph.bitmap_top;
			i32 left = font.glyph.bitmap_left;

			if(max_w + bmp.rows > dimension)
			{
				max_h += cast(u32)(size) + PADDING;
				max_w = PADDING;
			}

			switch(bmp.pixel_mode)
			{
				case FT_PIXEL_MODE_BGRA:
					{
						i32 x, y;
						foreach(r; 0 .. bmp.rows)
						{
							y = cast(i32)(max_h + r);
							foreach(c; 0 .. bmp.width)
							{
								x = max_w + c;
								u64 offset = (y*dimension + x) * 4;

								u8 p_b = bmp.buffer[offset+0];
								u8 p_g = bmp.buffer[offset+1];
								u8 p_r = bmp.buffer[offset+2];
								u8 p_a = bmp.buffer[offset+3];

								atlas.data[offset+0] = DeMultiply(p_r, p_a);
								atlas.data[offset+1] = DeMultiply(p_b, p_a);
								atlas.data[offset+2] = DeMultiply(p_g, p_a);
								atlas.data[offset+3] = p_a;
							}
						}
					} break;
				case FT_PIXEL_MODE_MONO:
					{
						u32 pitch = bmp.pitch;
						u8* buf = bmp.buffer;
					 	i32 x, y;
						foreach(r; 0 .. bmp.rows)
						{
							u8 bits = 0;
							u8* buf_ptr = buf;
							y = cast(i32)(max_h + r);
							foreach(c; 0 .. bmp.width)
							{
								if((c & 7) == 0)
								{
									bits = *buf_ptr;
									buf_ptr += 1;
								}

								x = max_w + c;
								u64 offset = (y*dimension + x) * 4;

								atlas.data[offset+0] = 255;
								atlas.data[offset+1] = 255;
								atlas.data[offset+2] = 255;
								atlas.data[offset+3] = (bits & 0x80) ? 255 : 0;

								bits <<= 1;
							}

							buf += pitch;
						}
					} break;
				case FT_PIXEL_MODE_GRAY:
					{
						i32 x, y;
						foreach(r; 0 .. bmp.rows)
						{
							y = cast(i32)(max_h + r);
							foreach(c; 0 .. bmp.width)
							{
								x = max_w + c;
								u64 offset = (y*dimension + x) * 4;

								atlas.data[offset+0] = 255;
								atlas.data[offset+1] = 255;
								atlas.data[offset+2] = 255;
								atlas.data[offset+3] = bmp.buffer[r*bmp.pitch + c];
							}
						}
					} break;
				default:
					assert(false, "Unknown pixel_mode value");
					break;
			}

			Glyph* g = atlas.atlas.glyphs.ptr + char_code;

			g.ch = cast(dchar)char_code;
			g.advance = cast(f32)(glyph.advance.x >> 6);
			g.plane_left = cast(f32)left;
			g.plane_right = g.plane_left + bmp.width;
			g.plane_top = cast(f32)top;
			g.plane_bottom = g.plane_top + bmp.rows;

			g.atlas_top = max_h;
			g.atlas_left = max_w;
			g.atlas_bottom = max_h + bmp.rows;
			g.atlas_right = max_w + bmp.width;

			max_w += bmp.width + PADDING;
			current_h = bmp.rows > current_h ? bmp.rows : current_h;

			count += 1;
		}
	}

	return atlas;
}


