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

struct _Drawer2DData Drawer2D;
#define Font_IsBitmap(font) (!(font)->handle)

void DrawTextArgs_Make(struct DrawTextArgs* args, STRING_REF const cc_string* text, struct FontDesc* font, cc_bool useShadow) {
	args->text = *text;
	args->font = font;
	args->useShadow = useShadow;
}

void DrawTextArgs_MakeEmpty(struct DrawTextArgs* args, struct FontDesc* font, cc_bool useShadow) {
	args->text = String_Empty;
	args->font = font;
	args->useShadow = useShadow;
}


/*########################################################################################################################*
*-----------------------------------------------------Font functions------------------------------------------------------*
*#########################################################################################################################*/
int Drawer2D_AdjHeight(int point) { return Math_CeilDiv(point * 3, 2); }

void Font_MakeBitmapped(struct FontDesc* desc, int size, int flags) {
	/* TODO: Scale X and Y independently */
	size = Display_ScaleY(size);
	desc->handle = NULL;
	desc->size   = size;
	desc->flags  = flags;
	desc->height = Drawer2D_AdjHeight(size);
}

void Font_Make(struct FontDesc* desc, int size, int flags) {
	if (Drawer2D.BitmappedText) {
		Font_MakeBitmapped(desc, size, flags);
	} else {
		SysFont_MakeDefault(desc, size, flags);
	}
}

void Font_Free(struct FontDesc* desc) {
	desc->size = 0;
	if (Font_IsBitmap(desc)) return;

	SysFont_Free(desc);
	desc->handle = NULL;
}

static struct Bitmap fontBitmap;
static int tileSize = 8; /* avoid divide by 0 if default.png missing */
/* So really 16 characters per row */
#define LOG2_CHARS_PER_ROW 4
static int tileWidths[256];

/* Finds the right-most non-transparent pixel in each tile in default.png */
static void CalculateTextWidths(void) {
	int width = fontBitmap.width, height = fontBitmap.height;
	BitmapCol* row;
	int i, x, y, xx, tileY;

	for (y = 0; y < height; y++) {
		tileY = y / tileSize;
		row   = Bitmap_GetRow(&fontBitmap, y);
		i     = 0 | (tileY << LOG2_CHARS_PER_ROW);

		/* Iterate through each tile on current scanline */
		for (x = 0; x < width; x += tileSize, i++) {
			/* Iterate through each pixel of the given character, on the current scanline */
			for (xx = tileSize - 1; xx >= 0; xx--) {
				if (!BitmapCol_A(row[x + xx])) continue;

				/* Check if this is the pixel furthest to the right, for the current character */
				tileWidths[i] = max(tileWidths[i], xx + 1);
				break;
			}
		}
	}
	tileWidths[' '] = tileSize / 4;
}

static void FreeFontBitmap(void) {
	int i;
	for (i = 0; i < Array_Elems(tileWidths); i++) tileWidths[i] = 0;
	Mem_Free(fontBitmap.scan0);
}

cc_bool Font_SetBitmapAtlas(struct Bitmap* bmp) {
	/* If not all of these cases are accounted for, end up overwriting memory after tileWidths */
	if (bmp->width != bmp->height) {
		static const cc_string msg = String_FromConst("&cWidth of default.png must equal its height");
		Logger_WarnFunc(&msg);
		return false;
	} else if (bmp->width < 16) {
		static const cc_string msg = String_FromConst("&cdefault.png must be at least 16 pixels wide");
		Logger_WarnFunc(&msg);
		return false;
	} else if (!Math_IsPowOf2(bmp->width)) {
		static const cc_string msg = String_FromConst("&cWidth of default.png must be a power of two");
		Logger_WarnFunc(&msg);
		return false;
	}

	/* TODO: Use shift instead of mul/div */
	FreeFontBitmap();
	fontBitmap = *bmp;
	tileSize   = bmp->width >> LOG2_CHARS_PER_ROW;

	CalculateTextWidths();
	return true;
}

void Font_SetPadding(struct FontDesc* desc, int amount) {
	if (!Font_IsBitmap(desc)) return;
	desc->height = desc->size + Display_ScaleY(amount) * 2;
}


/*########################################################################################################################*
*---------------------------------------------------Drawing functions-----------------------------------------------------*
*#########################################################################################################################*/
cc_bool Drawer2D_Clamp(struct Context2D* ctx, int* x, int* y, int* width, int* height) {
	if (*x >= ctx->width || *y >= ctx->height) return false;

	/* origin is negative, move inside */
	if (*x < 0) { *width  += *x; *x = 0; }
	if (*y < 0) { *height += *y; *y = 0; }

	*width  = min(*x + *width,  ctx->width)  - *x;
	*height = min(*y + *height, ctx->height) - *y;
	return *width > 0 && *height > 0;
}
#define Drawer2D_ClampPixel(p) p = (p < 0 ? 0 : (p > 255 ? 255 : p))

void Context2D_Alloc(struct Context2D* ctx, int width, int height) {
	ctx->width  = width;
	ctx->height = height;
	ctx->meta   = NULL;

	if (!Gfx.SupportsNonPowTwoTextures) {
		/* Allocate power-of-2 sized bitmap equal to or greater than the given size */
		width  = Math_NextPowOf2(width);
		height = Math_NextPowOf2(height);
	}
	
	if (Gfx.MinTexWidth)  { width  = max(width,  Gfx.MinTexWidth);  }
	if (Gfx.MinTexHeight) { height = max(height, Gfx.MinTexHeight); }

	ctx->bmp.width  = width; 
	ctx->bmp.height = height;
	ctx->bmp.scan0  = (BitmapCol*)Mem_AllocCleared(width * height, BITMAPCOLOR_SIZE, "bitmap data");
}

void Context2D_Wrap(struct Context2D* ctx, struct Bitmap* bmp) {
	ctx->bmp    = *bmp;
	ctx->width  = bmp->width;
	ctx->height = bmp->height;
	ctx->meta   = NULL;
}

void Context2D_Free(struct Context2D* ctx) {
	Mem_Free(ctx->bmp.scan0);
}

#define BitmapColor_Raw(r, g, b) (BitmapColor_R_Bits(r) | BitmapColor_G_Bits(g) | BitmapColor_B_Bits(b))
void Gradient_Noise(struct Context2D* ctx, BitmapCol color, int variation,
					int x, int y, int width, int height) {
	struct Bitmap* bmp = (struct Bitmap*)ctx;
	BitmapCol* dst;
	int R, G, B, xx, yy, n;
	int noise, delta;
	cc_uint32 alpha;

	if (!Drawer2D_Clamp(ctx, &x, &y, &width, &height)) return;
	alpha = color & BITMAPCOLOR_A_MASK;

	for (yy = 0; yy < height; yy++) {
		dst = Bitmap_GetRow(bmp, y + yy) + x;

		for (xx = 0; xx < width; xx++, dst++) {
			n = (x + xx) + (y + yy) * 57;
			n = (n << 13) ^ n;

			/*
				float noise = 1.0f - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0f;
				int delta = (int)(noise * variation);
			*/
			/* Fixed point equivalent to the above expression */
			noise = ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff);
			delta = (((1024 - noise / 0x100000)) * variation) >> 10;

			R = BitmapCol_R(color) + delta; Drawer2D_ClampPixel(R);
			G = BitmapCol_G(color) + delta; Drawer2D_ClampPixel(G);
			B = BitmapCol_B(color) + delta; Drawer2D_ClampPixel(B);

			*dst = BitmapColor_Raw(R, G, B) | alpha;
		}
	}
}

void Gradient_Vertical(struct Context2D* ctx, BitmapCol a, BitmapCol b,
					   int x, int y, int width, int height) {
	struct Bitmap* bmp = (struct Bitmap*)ctx;
	BitmapCol* row, color;
	int xx, yy;
	float t;
	if (!Drawer2D_Clamp(ctx, &x, &y, &width, &height)) return;

	for (yy = 0; yy < height; yy++) {
		row = Bitmap_GetRow(bmp, y + yy) + x;
		t   = (float)yy / (height - 1); /* so last row has color of b */

		color = BitmapCol_Make(
			Math_Lerp(BitmapCol_R(a), BitmapCol_R(b), t),
			Math_Lerp(BitmapCol_G(a), BitmapCol_G(b), t),
			Math_Lerp(BitmapCol_B(a), BitmapCol_B(b), t),
			255);

		for (xx = 0; xx < width; xx++) { row[xx] = color; }
	}
}

void Gradient_Blend(struct Context2D* ctx, BitmapCol color, int blend,
					int x, int y, int width, int height) {
	struct Bitmap* bmp = (struct Bitmap*)ctx;
	BitmapCol* dst;
	int R, G, B, xx, yy;
	if (!Drawer2D_Clamp(ctx, &x, &y, &width, &height)) return;

	/* Pre compute the alpha blended source color */
	/* TODO: Avoid shift when multiplying */
	color = BitmapCol_Make(
		BitmapCol_R(color) * blend / 255,
		BitmapCol_G(color) * blend / 255,
		BitmapCol_B(color) * blend / 255,
		0);
	blend = 255 - blend; /* inverse for existing pixels */

	for (yy = 0; yy < height; yy++) {
		dst = Bitmap_GetRow(bmp, y + yy) + x;

		for (xx = 0; xx < width; xx++, dst++) {
			/* TODO: Not shift when multiplying */
			R = BitmapCol_R(color) + (BitmapCol_R(*dst) * blend) / 255;
			G = BitmapCol_G(color) + (BitmapCol_G(*dst) * blend) / 255;
			B = BitmapCol_B(color) + (BitmapCol_B(*dst) * blend) / 255;

			*dst = BitmapColor_RGB(R, G, B);
		}
	}
}

void Context2D_DrawPixels(struct Context2D* ctx, int x, int y, struct Bitmap* src) {
	struct Bitmap* dst = (struct Bitmap*)ctx;
	int width = src->width, height = src->height;
	BitmapCol* dstRow;
	BitmapCol* srcRow;
	int xx, yy;
	if (!Drawer2D_Clamp(ctx, &x, &y, &width, &height)) return;

	for (yy = 0; yy < height; yy++) {
		srcRow = Bitmap_GetRow(src, yy);
		dstRow = Bitmap_GetRow(dst, y + yy) + x;

		for (xx = 0; xx < width; xx++) { dstRow[xx] = srcRow[xx]; }
	}
}

void Context2D_Clear(struct Context2D* ctx, BitmapCol color,
					int x, int y, int width, int height) {
	struct Bitmap* bmp = (struct Bitmap*)ctx;
	BitmapCol* row;
	int xx, yy;
	if (!Drawer2D_Clamp(ctx, &x, &y, &width, &height)) return;

	for (yy = 0; yy < height; yy++) {
		row = Bitmap_GetRow(bmp, y + yy) + x;
		for (xx = 0; xx < width; xx++) { row[xx] = color; }
	}
}


void Drawer2D_MakeTextTexture(struct Texture* tex, struct DrawTextArgs* args) {
	static struct Texture empty = { 0, Tex_Rect(0,0, 0,0), Tex_UV(0,0, 1,1) };
	struct Context2D ctx;
	int width, height;
	/* pointless to draw anything when context is lost */
	if (Gfx.LostContext) { *tex = empty; return; }

	width  = Drawer2D_TextWidth(args);
	if (!width) { *tex = empty; return; }
	height = Drawer2D_TextHeight(args);

	Context2D_Alloc(&ctx, width, height);
	{
		Context2D_DrawText(&ctx, args, 0, 0);
		Context2D_MakeTexture(tex, &ctx);
	}
	Context2D_Free(&ctx);
}

void Context2D_MakeTexture(struct Texture* tex, struct Context2D* ctx) {
	int flags = TEXTURE_FLAG_NONPOW2 | TEXTURE_FLAG_LOWRES;
	int nouv  = Gfx.Limitations & GFX_LIMIT_NO_UV_SUPPORT;
	Gfx_RecreateTexture(&tex->ID, &ctx->bmp, flags, false);
	
	/* TODO need to find a better solution in NoUVSupport case */
	tex->width  = nouv ? ctx->bmp.width  : ctx->width;
	tex->height = nouv ? ctx->bmp.height : ctx->height;
	
	tex->uv.u1  = 0.0f; tex->uv.v1 = 0.0f;
	tex->uv.u2  = (float)ctx->width  / (float)ctx->bmp.width;
	tex->uv.v2  = (float)ctx->height / (float)ctx->bmp.height;
}

cc_bool Drawer2D_ValidColorCodeAt(const cc_string* text, int i) {
	if (i >= text->length) return false;
	return BitmapCol_A(Drawer2D_GetColor(text->buffer[i])) != 0;
}

cc_bool Drawer2D_UNSAFE_NextPart(cc_string* left, cc_string* part, char* colorCode) {
	BitmapCol color;
	char cur;
	int i;

	/* check if current part starts with a colour code */
	if (left->length >= 2 && left->buffer[0] == '&') {
		cur   = left->buffer[1];
		color = Drawer2D_GetColor(cur);
		
		if (BitmapCol_A(color)) {
			*colorCode = cur;
			left->buffer += 2;
			left->length -= 2;
		}
	}

	for (i = 0; i < left->length; i++) 
	{
		if (left->buffer[i] == '&' && Drawer2D_ValidColorCodeAt(left, i + 1)) break;
	}

	/* advance string starts and lengths */
	part->buffer  = left->buffer;
	part->length  = i;
	left->buffer += i;
	left->length -= i;

	return part->length > 0 || left->length > 0;
}

cc_bool Drawer2D_IsEmptyText(const cc_string* text) {
	cc_string left = *text, part;
	char colorCode;

	while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode))
	{
		if (part.length) return false;
	}
	return true;
}

void Drawer2D_WithoutColors(cc_string* str, const cc_string* src) {
	cc_string left = *src, part;
	char colorCode;

	while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode))
	{
		String_AppendString(str, &part);
	}
}

char Drawer2D_LastColor(const cc_string* text, int start) {
	int i;
	if (start >= text->length) start = text->length - 1;
	
	for (i = start; i >= 0; i--) {
		if (text->buffer[i] != '&') continue;
		if (Drawer2D_ValidColorCodeAt(text, i + 1)) {
			return text->buffer[i + 1];
		}
	}
	return '\0';
}
cc_bool Drawer2D_IsWhiteColor(char c) { return c == '\0' || c == 'f' || c == 'F'; }

/* TODO: Needs to account for DPI */
#define Drawer2D_ShadowOffset(point) (point / 8)
#define Drawer2D_XPadding(point) (Math_CeilDiv(point, 8))
static int Drawer2D_Width(int point, char c) {
	return Math_CeilDiv(tileWidths[(cc_uint8)c] * point, tileSize);
}

void Drawer2D_ReducePadding_Tex(struct Texture* tex, int point, int scale) {
	int padding;
	float vAdj;
	if (!Drawer2D.BitmappedText) return;

	padding = (tex->height - point) / scale;
	vAdj    = (float)padding / Math_NextPowOf2(tex->height);
	tex->uv.v1 += vAdj; tex->uv.v2 -= vAdj;
	tex->height -= (cc_uint16)(padding * 2);
}

void Drawer2D_ReducePadding_Height(int* height, int point, int scale) {
	int padding;
	if (!Drawer2D.BitmappedText) return;

	padding = (*height - point) / scale;
	*height -= padding * 2;
}

void Drawer2D_Fill(struct Bitmap* bmp, int x, int y, int width, int height, BitmapCol color) {
	BitmapCol* row;
	int xx, yy;

	for (yy = y; yy < y + height; yy++) {
		if (yy >= bmp->height) return;
		row = Bitmap_GetRow(bmp, yy);

		for (xx = x; xx < x + width; xx++) {
			if (xx >= bmp->width) break;
			row[xx] = color;
		}
	}
}

static void DrawBitmappedTextCore(struct Bitmap* bmp, struct DrawTextArgs* args, int x, int y, cc_bool shadow) {
	BitmapCol color;
	cc_string text = args->text;
	int i, point   = args->font->size, count = 0;

	int xPadding;
	int srcX, srcY, dstX, dstY;
	int fontX, fontY;
	int srcWidth, dstWidth;
	int dstHeight, begX, xx, yy;
	int cellY, underlineY, underlineHeight;

	BitmapCol* srcRow, src;
	BitmapCol* dstRow;

	cc_uint8 coords[DRAWER2D_MAX_TEXT_LENGTH];
	BitmapCol colors[DRAWER2D_MAX_TEXT_LENGTH];
	cc_uint16 dstWidths[DRAWER2D_MAX_TEXT_LENGTH];

	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 */
		}

		coords[count] = c;
		colors[count] = color;
		dstWidths[count] = Drawer2D_Width(point, c);
		count++;
	}

	dstHeight = point; begX = x;
	/* adjust coords to make drawn text match GDI fonts */
	y += (args->font->height - dstHeight) / 2;
	xPadding  = Drawer2D_XPadding(point);

	for (yy = 0; yy < dstHeight; yy++) {
		dstY = y + yy;
		if ((unsigned)dstY >= (unsigned)bmp->height) continue;

		fontY  = 0 + yy * tileSize / dstHeight;
		dstRow = Bitmap_GetRow(bmp, dstY);

		for (i = 0; i < count; i++) {
			srcX   = (coords[i] & 0x0F) * tileSize;
			srcY   = (coords[i] >> 4)   * tileSize;
			srcRow = Bitmap_GetRow(&fontBitmap, fontY + srcY);

			srcWidth = tileWidths[coords[i]];
			dstWidth = dstWidths[i];
			color    = colors[i];

			for (xx = 0; xx < dstWidth; xx++) {
				fontX = srcX + xx * srcWidth / dstWidth;
				src   = srcRow[fontX];
				if (!BitmapCol_A(src)) continue;

				dstX = x + xx;
				if ((unsigned)dstX >= (unsigned)bmp->width) continue;

				/* TODO: Transparent text by multiplying by col.A */
				/* TODO: Not shift when multiplying */
				/* TODO: avoid BitmapCol_A shift */
				dstRow[dstX] = BitmapCol_Make(
					BitmapCol_R(src) * BitmapCol_R(color) / 255,
					BitmapCol_G(src) * BitmapCol_G(color) / 255,
					BitmapCol_B(src) * BitmapCol_B(color) / 255,
					BitmapCol_A(src));
			}
			x += dstWidth + xPadding;
		}
		x = begX;
	}

	if (!(args->font->flags & FONT_FLAGS_UNDERLINE)) return;
	/* scale up bottom row of a cell to drawn text font */
	cellY = (8 - 1) * dstHeight / 8;
	underlineY      = y + cellY;
	underlineHeight = dstHeight - cellY;

	for (i = 0; i < count; ) {
		dstWidth = 0;
		color    = colors[i];

		for (; i < count && color == colors[i]; i++) {
			dstWidth += dstWidths[i] + xPadding;
		}
		Drawer2D_Fill(bmp, x, underlineY, dstWidth, underlineHeight, color);
		x += dstWidth;
	}
}

static void DrawBitmappedText(struct Bitmap* bmp, struct DrawTextArgs* args, int x, int y) {
	int offset = Drawer2D_ShadowOffset(args->font->size);

	if (!fontBitmap.scan0) {
		if (args->useShadow) FallbackFont_DrawText(args, bmp, x, y, true);
		FallbackFont_DrawText(args, bmp, x, y, false);
		return;
	}

	if (args->useShadow) {
		DrawBitmappedTextCore(bmp, args, x + offset, y + offset, true);
	}
	DrawBitmappedTextCore(bmp, args, x, y, false);
}

static int MeasureBitmappedWidth(const struct DrawTextArgs* args) {
	int i, point = args->font->size;
	int xPadding, width;
	cc_string text;

	if (!fontBitmap.scan0) return FallbackFont_TextWidth(args);

	/* adjust coords to make drawn text match GDI fonts */
	xPadding = Drawer2D_XPadding(point);
	width    = 0;

	text = args->text;
	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 */
		}
		width += Drawer2D_Width(point, c) + xPadding;
	}
	if (!width) return 0;

	/* Remove padding at end */
	if (!(args->font->flags & FONT_FLAGS_PADDING)) width -= xPadding;

	if (args->useShadow) { width += Drawer2D_ShadowOffset(point); }
	return width;
}

void Context2D_DrawText(struct Context2D* ctx, struct DrawTextArgs* args, int x, int y) {
	struct Bitmap* bmp = (struct Bitmap*)ctx;
	if (Drawer2D_IsEmptyText(&args->text)) return;
	if (Font_IsBitmap(args->font)) { DrawBitmappedText(bmp, args, x, y); return; }

	if (args->useShadow) { SysFont_DrawText(args, bmp, x, y, true); }
	SysFont_DrawText(args, bmp, x, y, false);
}

int Drawer2D_TextWidth(struct DrawTextArgs* args) {
	if (Font_IsBitmap(args->font)) return MeasureBitmappedWidth(args);
	return SysFont_TextWidth(args);
}

int Drawer2D_TextHeight(struct DrawTextArgs* args) {
	return Font_CalcHeight(args->font, args->useShadow);
}

int Font_CalcHeight(const struct FontDesc* font, cc_bool useShadow) {
	int height = font->height;
	if (Font_IsBitmap(font)) {
		if (useShadow) { height += Drawer2D_ShadowOffset(font->size); }
	} else {
		if (useShadow) height += 2;
	}
	return height;
}

void Drawer2D_DrawClippedText(struct Context2D* ctx, struct DrawTextArgs* args,
								int x, int y, int maxWidth) {
	char strBuffer[512];
	struct DrawTextArgs part;
	int i, width;

	width = Drawer2D_TextWidth(args);
	/* No clipping needed */
	if (width <= maxWidth) { Context2D_DrawText(ctx, args, x, y); return; }
	part = *args;

	String_InitArray(part.text, strBuffer);
	String_Copy(&part.text, &args->text);
	String_Append(&part.text, '.');
	
	for (i = part.text.length - 2; i > 0; i--) {
		part.text.buffer[i] = '.';
		/* skip over trailing spaces */
		if (part.text.buffer[i - 1] == ' ') continue;

		part.text.length = i + 2;
		width            = Drawer2D_TextWidth(&part);
		if (width <= maxWidth) { Context2D_DrawText(ctx, &part, x, y); return; }

		/* If down to <= 2 chars, try omitting the .. */
		if (i > 2) continue;
		part.text.length = i;
		width            = Drawer2D_TextWidth(&part);
		if (width <= maxWidth) { Context2D_DrawText(ctx, &part, x, y); return; }
	}
}


/*########################################################################################################################*
*---------------------------------------------------Drawer2D component----------------------------------------------------*
*#########################################################################################################################*/
static void DefaultPngProcess(struct Stream* stream, const cc_string* name) {
	struct Bitmap bmp;
	cc_result res;

	if ((res = Png_Decode(&bmp, stream))) {
		Logger_SysWarn2(res, "decoding", name);
		Mem_Free(bmp.scan0);
	} else if (Font_SetBitmapAtlas(&bmp)) {
		Event_RaiseVoid(&ChatEvents.FontChanged);
	} else {
		Mem_Free(bmp.scan0);
	}
}
static struct TextureEntry default_entry = { "default.png", DefaultPngProcess };


/* The default 16 colours are the CGA 16 color palette (without special brown colour) */
/*   See https://en.wikipedia.org/wiki/Color_Graphics_Adapter#With_an_RGBI_monitor for reference */
/* The 16 hex colours below were produced from the following formula: */
/*   R = 191 * ((hex >> 2) & 1) + 64 * (hex >> 3) */
/*   G = 191 * ((hex >> 1) & 1) + 64 * (hex >> 3) */
/*   B = 191 * ((hex >> 0) & 1) + 64 * (hex >> 3) */
static const BitmapCol defaults_0_9[] = {
	BitmapColor_RGB(  0,   0,   0), /* 0 */
	BitmapColor_RGB(  0,   0, 191), /* 1 */
	BitmapColor_RGB(  0, 191,   0), /* 2 */
	BitmapColor_RGB(  0, 191, 191), /* 3 */
	BitmapColor_RGB(191,   0,   0), /* 4 */
	BitmapColor_RGB(191,   0, 191), /* 5 */
	BitmapColor_RGB(191, 191,   0), /* 6 */
	BitmapColor_RGB(191, 191, 191), /* 7 */
	BitmapColor_RGB( 64,  64,  64), /* 8 */
	BitmapColor_RGB( 64,  64, 255)  /* 9 */
};
static const BitmapCol defaults_a_f[] = {
	BitmapColor_RGB( 64, 255,  64), /* A */
	BitmapColor_RGB( 64, 255, 255), /* B */
	BitmapColor_RGB(255,  64,  64), /* C */
	BitmapColor_RGB(255,  64, 255), /* D */
	BitmapColor_RGB(255, 255,  64), /* E */
	BitmapColor_RGB(255, 255, 255), /* F */
};

static void OnReset(void) {
	Mem_Set(Drawer2D.Colors, 0, sizeof(Drawer2D.Colors));

	Mem_Copy(&Drawer2D.Colors['0'], defaults_0_9, sizeof(defaults_0_9));
	Mem_Copy(&Drawer2D.Colors['a'], defaults_a_f, sizeof(defaults_a_f));
	Mem_Copy(&Drawer2D.Colors['A'], defaults_a_f, sizeof(defaults_a_f));
}

static void OnInit(void) {
	OnReset();
	TextureEntry_Register(&default_entry);

	Drawer2D.BitmappedText    = Game_ClassicMode || !Options_GetBool(OPT_USE_CHAT_FONT, false);
	Drawer2D.BlackTextShadows = Options_GetBool(OPT_BLACK_TEXT, false);
}

static void OnFree(void) { 
	FreeFontBitmap();
	fontBitmap.scan0 = NULL;
}

struct IGameComponent Drawer2D_Component = {
	OnInit,  /* Init  */
	OnFree,  /* Free  */
	OnReset, /* Reset */
};
