#!/usr/bin/nickle

autoimport ParseArgs
import File;

typedef struct {
	int[]	bytes;
	int	width;
	int	height;
	int	x_off;
	int	y_off;
	int	advance;
	int	encoding;
	int	location;
} glyph_t;

typedef struct {
	glyph_t[...]	glyphs;
	int		default_char;
	int		ascent;
} font_t;

glyph_t
read_glyph(file f)
{
	glyph_t	glyph = { .encoding = -1, .bytes = (int[...]){}, .width = 0 };

	while (!File::end(f)) {
		string	l = fgets(f);

		string[*] tokens = String::split(l, " ");
		if (dim(tokens) == 0)
			continue;

		switch (tokens[0]) {
		case "ENCODING":
			glyph.encoding = atoi(tokens[1]);
			break;
		case "DWIDTH":
			glyph.advance = atoi(tokens[1]);
			break;
		case "BBX":
			glyph.width = atoi(tokens[1]);
			glyph.height = atoi(tokens[2]);
			glyph.x_off = atoi(tokens[3]);
			glyph.y_off = glyph.height + atoi(tokens[4]);
			break;
		case "ENDCHAR":
			return glyph;
		case "BITMAP":
			int byte_stride = ceil(glyph.width/8);
			for (int y = 0; y < glyph.height; y++) {
				string l = fgets(f);
				for (int x = 0; x < byte_stride; x++) {
					string v = String::substr(l, x * 2, 2);
					glyph.bytes[dim(glyph.bytes)] = atoi(v, 16);
				}
			}
			break;
		}
	}
	return glyph;
}

font_t read_font(file f) {
	font_t	font = { .glyphs = {}, .default_char = -1 };
	bool in_head = true;

	while (in_head && !File::end(f)) {
		string l = File::fgets(f);

		string[*] tokens = String::split(l, " ");
		switch (tokens[0]) {
		case "DEFAULT_CHAR":
			font.default_char = atoi(tokens[1]);
			break;
		case "FONT_ASCENT":
			font.ascent = atoi(tokens[1]);
			break;
		case "CHARS":
			in_head = false;
			break;
		}
	}
	while (!File::end(f)) {
		glyph_t	glyph = read_glyph(f);
		if (glyph.encoding == -1)
			break;
		font.glyphs[dim(font.glyphs)] = glyph;
	}
	return font;
}

int
flip_byte(int x)
{
	int	dest = 0;

	for (int i = 0; i < 8; i++)
		dest |= ((x >> (7 - i)) & 1) << i;
	return dest;
}

void print_font(file out, font_t font, string font_name) {
	int	width = font.glyphs[0].width;
	int	height = font.glyphs[0].height;
	int	max_width = width;
	int	max_height = height;
	int[128] pos = { 0 ... };
	int[...] bytes;
	bool fixed_size = true;

	for (int i = 1; i < dim(font.glyphs); i++) {
		if (font.glyphs[i].width != width ||
		   font.glyphs[i].height != height)
		{
			fixed_size = false;
		}
		max_width = max(max_width, font.glyphs[i].width);
		max_height = max(max_height, font.glyphs[i].height);
	}

	if (font.default_char == -1)
		font.default_char = font.glyphs[0].encoding;

	/* build byte array */
	for (int i = 0; i < dim(font.glyphs); i++) {
		if (font.glyphs[i].encoding < dim(pos))
		{
			pos[font.glyphs[i].encoding] = dim(bytes);
			for (int b = 0; b < dim(font.glyphs[i].bytes); b++)
				bytes[dim(bytes)] = font.glyphs[i].bytes[b];
		}
	}

	/* Fill in default glyph */
	for (int i = 0; i < dim(pos); i++)
		if (pos[i] == -1)
			pos[i] = pos[font.default_char];

	fprintf(out, "#include <ao_draw.h>\n");
	fprintf(out, "static const uint8_t %s_bytes[%d] = {", font_name, dim(bytes));
	for (int b = 0; b < dim(bytes); b++) {
		if ((b & 15) == 0)
			fprintf(out, "\n\t");
		else
			fprintf(out, " ");
		fprintf(out, "0x%02x,", flip_byte(bytes[b]));
	}
	fprintf(out, "\n};\n\n");

	fprintf(out, "static const uint16_t %s_pos[%d] = {", font_name, dim(pos));
	for (int i = 0; i < dim(pos); i++) {
		if ((i & 7) == 0)
			fprintf(out, "\n\t");
		else
			fprintf(out, " ");
		fprintf(out, "%6d,", pos[i]);
	}
	fprintf(out, "\n};\n\n");

	fprintf(out, "#define GLYPH_WIDTH_MAX %d\n", max_width);
	fprintf(out, "#define GLYPH_HEIGHT_MAX %d\n", max_height);
	if (fixed_size) {
		fprintf(out, "#define GLYPH_WIDTH %d\n", width);
		fprintf(out, "#define GLYPH_HEIGHT %d\n", height);
		fprintf(out, "#define GLYPH_ASCENT %d\n", font.ascent);
	} else {
		fprintf(out, "static const struct ao_glyph_metrics %s_metrics[%d] = {\n", font_name, dim(pos));
		for (int i = 0; i < dim(pos); i++) {
			int g = 0;
			for (int j = 0; j < dim(font.glyphs); j++) {
				if (font.glyphs[j].encoding == i) {
					g = j;
					break;
				}
			}
			string c;
			if (' ' <= i && i <= '~')
				c = sprintf("%c", i);
			else
				c = sprintf("%02x", i);
			fprintf(out, "    { .width = %d, .height = %d, .x_off = %d, .y_off = %d, .advance = %d }, /* %s */\n",
			       font.glyphs[g].width, font.glyphs[g].height,
			       font.glyphs[g].x_off, font.glyphs[g].y_off, font.glyphs[g].advance, c);
		}
		fprintf(out, "};\n");
	}
	fprintf(out, "const struct ao_font %s_font = {\n", font_name);
	fprintf(out, "\t.bytes = %s_bytes,\n", font_name);
	fprintf(out, "\t.pos = %s_pos,\n", font_name);
	if (!fixed_size)
		fprintf(out, "\t.metrics = %s_metrics,\n", font_name);
	fprintf(out, "\t.max_width = %d,\n", max_width);
	fprintf(out, "\t.max_height = %d,\n", max_height);
	fprintf(out, "\t.ascent = %d,\n", font.ascent);
	fprintf(out, "};\n");
	int max_stride = (max_width + 31) >> 5;
	fprintf(out, "__asm__(\".balign 4\\n.comm ao_glyph_temp 0x%x\");\n", max_stride * max_height * 4);
}

string
to_c(string a)
{
	string r = "";
	for (int i = String::rindex(a, "/") + 1; i < String::length(a); i++) {
		int c = a[i];
		if (c == '.')
			break;
		if (!(Ctype::isalnum(c) || c == '_'))
			c = '_';
		r += String::new(c);
	}
	if (!Ctype::isalpha(r[0]))
		r = "_" + r;
	return r;
}

void
do_font(string name, file out)
{
	twixt (file f = File::open(name, "r"); File::close(f)) {
		font_t font = read_font(f);
		print_font(out, font, to_c(name));
	}
}

argdesc argd = {
	args = {
		{
			.var = (arg_var.arg_string) &(string output),
			.name = "output",
			.abbr = 'o',
			.expr_name = "file",
			.desc = "Output file name"
		},
	},
	posn_args = {
		{
			var = (arg_var.arg_string) &(string input),
			name = "input",
		}
	}
};

parseargs(&argd, &argv)

if (is_uninit(&output))
	do_font(input, stdout);
else {
	twixt(file f = File::open(output, "w"); File::close(f))
		do_font(input, f);
}
