/*
 * Copyright (C) 2005-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#define MAX_WIDTH	32
#define MAX_HEIGHT	32
#define MAX_CHARS	(64*1024)

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static struct {
	char font[32];
	unsigned char ord;
	unsigned int width;
	unsigned int height;
	unsigned int xmin, xmax, ymin, ymax;
	unsigned char pattern[MAX_WIDTH][MAX_HEIGHT];
} glyph[MAX_CHARS];
static unsigned int nglyphs = 0;

static unsigned int line = 1;

static const char * inputfile = "font_info.dat";
static const char * inputfile2 = "glyph_info.dat";


static void
syntax(const char *myfile) __attribute__((__noreturn__));
static void
syntax(const char *myfile)
{
	fprintf(stderr, "%s: syntax error in line %d.\n", myfile, line);
	exit(1);
}

static int
font_lex(const char *curfile, FILE *fp, char *font, unsigned char *ordp)
{
	for (;;) {
		int c;

		c = fgetc(fp);
		if (c == EOF) {
			/* End-Of-File */
			return c;

		} else if (c == ' ' || c == '\t' || c == '\r') {
			/* Empty Space */
			continue;

		} else if (c == '#') {
			/* Comment */
			do {
				c = fgetc(fp);
			} while (c != '\n' && c != EOF);
			ungetc(c, fp);
			continue;

		} else if (c == '\n') {
			/* End-Of-Line */
			line++;
			return c;

		} else if (('A' <= c && c <= 'Z')
			|| ('a' <= c && c <= 'z')
			|| c == '_') {
			/* Font Name */
			while (('A' <= c && c <= 'Z')
			    || ('a' <= c && c <= 'z')
			    || ('0' <= c && c <= '9')
			    || c == '_'
			    || c == '-'
			    || c == '.') {
				*font++ = c;
				c = fgetc(fp);
			}
			*font++ = '\0';
			ungetc(c, fp);
			return 'A';

		} else if ('0' <= c && c <= '9') {
			/* Decimal Number */
			*ordp = 0;
			while ('0' <= c && c <= '9') {
				*ordp *= 10;
				*ordp += c - '0';
				c = fgetc(fp);
			}
			ungetc(c, fp);
			return '0';

		} else if (c == '\'') {
			c = fgetc(fp);
			*ordp = (unsigned char) c;
			c = fgetc(fp);
			assert(c == '\'');
			return '0';

		} else if (c == '.') {
			/* . */
			return c;

		} else if (c == '@') {
			/* . */
			return c;

		} else {
			/* Unknown Character */
			syntax(curfile);
			/*NOTREACHED*/
		}
	}
}

static void
font_read(const char* myfile )
{
	FILE *fp_in;

	fp_in = fopen(myfile, "r");
	assert(fp_in != (FILE *) 0);
	
	for (;;) {
		char font[32];
		unsigned char ord;
		int c;

		c = font_lex(myfile, fp_in, font, &ord);
		if (c == EOF) {
			/* End-Of-File */
			break;

		} else if (c == '\n') {
			/* Empty Line */
			continue;

		} else if (c == 'A') {
			/*
			 * Character Definition
			 */
			/* Font Name */
			strcpy(glyph[nglyphs].font, font);

			/* Ord Number */
			c = font_lex(myfile, fp_in, font, &ord);
			if (c != '0') {
				syntax(myfile);
				/*NOTREACHED*/
			}
			glyph[nglyphs].ord = ord;

			/* Pattern (and Width/Height) */
			glyph[nglyphs].width = 0;
			glyph[nglyphs].height = 0;

			/* Skip end-of-line after ord number. */
			c = font_lex(myfile, fp_in, font, &ord);
			if (c != '\n') {
				syntax(myfile);
				/*NOTREACHED*/
			}

			/*
			 * Read first line of pattern.
			 * Get width of pattern.
			 */
			for (;;) {
				c = font_lex(myfile, fp_in, font, &ord);
				if (c == '\n') {
					break;
				} else if (c == '.') {
					glyph[nglyphs].pattern[glyph[nglyphs].width][0] = 0;
				} else if (c == '@') {
					glyph[nglyphs].pattern[glyph[nglyphs].width][0] = 1;
				} else {
					syntax(myfile);
					/*NOTREACHED*/
				}
				glyph[nglyphs].width++;
			}
			glyph[nglyphs].height++;

			/*
			 * Read next lines of pattern.
			 * Get height of pattern.
			 */
			for (;;) {
				unsigned int x;

				c = font_lex(myfile, fp_in, font, &ord);
				if (c == '\n' || c == EOF) {
					break;
				}
				for (x = 0; x < glyph[nglyphs].width; x++) {
					if (x != 0) {
						c = font_lex(myfile, fp_in, font, &ord);
					}
					if (c == '.') {
						glyph[nglyphs].pattern[x][glyph[nglyphs].height] = 0;
					} else if (c == '@') {
						glyph[nglyphs].pattern[x][glyph[nglyphs].height] = 1;
					} else {
						syntax(myfile);
						/*NOTREACHED*/
					}
				}

				/* Skip end-of-line after pattern line. */
				c = font_lex(myfile, fp_in, font, &ord);
				if (c != '\n') {
					syntax(myfile);
					/*NOTREACHED*/
				}

				glyph[nglyphs].height++;
			}
			nglyphs++;

		} else {
			syntax(myfile);
			/*NOTREACHED*/
		}
	}

	(void) fclose(fp_in);
}

static void
generated_warning(FILE *fp)
{
	fprintf(fp, "/*\n");
	fprintf(fp, " * WARNING:\n");
	fprintf(fp, " *\n");
	fprintf(fp, " * This file is generated "
			"from %s and/or %s by ./font_gen!"
			"\n", inputfile, inputfile2);
	fprintf(fp, " * Do not edit here!\n");
	fprintf(fp, " * Edit %s and/or %s instead!\n", inputfile, inputfile2);
	fprintf(fp, " */\n");
}

static void
bios_font_gen(unsigned int height, unsigned int begin, unsigned int end)
{
	char path[256];
	FILE *fp;
	unsigned int c;


	if (begin != 0
	 || end != 256) {
		sprintf(path, "font_8x%u_%u.c", height, begin);
	} else {
		sprintf(path, "font_8x%u.c", height);
	}

	fp = fopen(path, "w");
	assert(fp != (FILE *) 0);

	generated_warning(fp);

	if (begin != 0
	 || end != 256) {
		fprintf(fp, "unsigned char font_8x%u_%u[%u * %u] = {\n",
				height, begin, end - begin, height);
	} else {
		fprintf(fp, "unsigned char font_8x%d[256 * %d] = {\n",
				height, height);
	}

	for (c = begin; c < end; c++) {
		unsigned int p;

		for (p = 0; ; p++) {
			if (p == nglyphs) {
				fprintf(stderr, "Can't find bios_8x%d ord %d.\n",
						height, c);
				exit(1);
			}
			if (strncmp(glyph[p].font, "bios", 4) == 0
			 && glyph[p].ord == c
			 && glyph[p].width == 8
			 && glyph[p].height == height) {
				unsigned int x;
				unsigned int y;

				fprintf(fp, "\t/* %d 0x%02x", c, c);
				if (c < ' ') {
					fprintf(fp, " '^%c'", c + 0x40);
				} else if (' ' <= c && c <= 126) {
					fprintf(fp, " '%c'", c);
				}
				fprintf(fp, " */\n");
				for (y = 0; y < height; y++) {
					unsigned char bits;

					bits = 0;
					for (x = 0; x < 8; x++) {
						bits <<= 1;
						if (glyph[p].pattern[x][y]) {
							bits |= 1;
						}
					}
					fprintf(fp, "\t0x%02x, /* ", bits);
					for (x = 0; x < 8; x++) {
						fprintf(fp, "%d", glyph[p].pattern[x][y]);
					}
					fprintf(fp, " */\n");
				}
				break;
			}
		}
	}
	fprintf(fp, "};\n");

	(void) fclose(fp);
}


static int
is_readable_glyph(unsigned char c) 
{
	if (('A' <= c && c <= 'Z')
	|| ('a' <= c && c <= 'z')
	|| ('0' <= c && c <= '9')
	|| c == '_'
	|| c == '-'
	|| c == '['
	|| c == ']'
	|| c == '<'
	|| c == '>'
	|| c == '('
	|| c == ')'
	|| c == '{'
	|| c == '}'
	|| c == ':'
	|| c == ';'
	|| c == '/'
	|| c == '\\'
	|| c == '\''
	|| c == '"'
	|| c == '+'
	|| c == '*'
	|| c == '#'
	|| c == '$'
	|| c == '@'
	|| c == '='
	|| c == ','
	|| c == '?'
	|| c == '~'
	|| c == '!'
#if 0
	/* attention, ' ' space isnt allowed in here, as it would result in a
	 * 0x0 glyph */
	|| c == ' '
#endif
	|| c == '.') {
		return 1;
	} 

	return 0;
}


/*
 * prepares glyphs by cutting of empty columns and lines at the boundaries
 */
static void
prepare_glyphs(void)
{
	int c;
	
	for (c = 0; c < nglyphs; c++) {
		unsigned int x;
		unsigned int y;
		unsigned short xmin, ymin, xmax, ymax;

		/* skip non-readable glyphs */
		if (!(is_readable_glyph(glyph[c].ord))) {
			continue;
		}

		xmin=0;
		ymin=0;
		xmax=glyph[c].width-1;
		ymax=glyph[c].height-1;
		
		/* throw away empty lines at the beginning of the glyph */
		for (y=0; y<glyph[c].height; ++y) {
			unsigned char empty;
			empty=1;
			
			for (x=0; x<glyph[c].width; ++x) {
				if (glyph[c].pattern[x][y]) {
					empty=0;
					break;
				}
			}

			if (empty) {
				ymin++;
			} else {
				break;
			}
			
		}

		/* throw away empty lines at the end of the glyph */
		for (y=glyph[c].height-1; y>ymin; --y) {
			unsigned char empty;
			empty=1;
			
			for (x=0; x<glyph[c].width; ++x) {
				if (glyph[c].pattern[x][y]) {
					empty=0;
					break;
				}
			}

			if (empty) {
				ymax--;
			} else {
				break;
			}
			
		}

		/* throw away empty columns at the left of the glyph */
		for (x=0; x<glyph[c].width; ++x) {
			unsigned char empty;
			empty=1;
			
			for (y=ymin; y<=ymax; ++y) {
				if (glyph[c].pattern[x][y]) {
					empty=0;
					break;
				}
			}

			if (empty) {
				xmin++;
			} else {
				break;
			}
			
		}

		/* throw away empty columns at the right of the glyph */
		for (x=xmax; x>xmin; --x) {
			unsigned char empty;
			empty=1;
			
			for (y=ymin; y<=ymax; ++y) {
				if (glyph[c].pattern[x][y]) {
					empty=0;
					break;
				}
			}

			if (empty) {
				xmax--;
			} else {
				break;
			}
			
		}

		glyph[c].xmin=xmin;
		glyph[c].xmax=xmax;
		glyph[c].ymin=ymin;
		glyph[c].ymax=ymax;
	}
}


static void
delete_doubles (void)
{
	int i, j;
	int x, y;	

	for (i=0; i<(nglyphs-1); i++) {
		/* skip non-readable glyphs */
		if (!(is_readable_glyph(glyph[i].ord))) {
			continue;
		}
		
		for (j=i+1; j<nglyphs; j++) {
			int sameglyphs=1;

			/* skip non-readable glyphs */
			if (!(is_readable_glyph(glyph[j].ord))) {
				continue;
			}
			/* skip different characters */
			if (glyph[i].ord!=glyph[j].ord) {
				continue;
			}
			/* different width */
			if (   (glyph[i].xmax-glyph[i].xmin)
			    != (glyph[j].xmax-glyph[j].xmin) ) {
				continue;
			}
			/* different height */
			if (   (glyph[i].ymax-glyph[i].ymin)
			    != (glyph[j].ymax-glyph[j].ymin) ) {
				continue;
			}
			
			/* different glyphs? */
			for (x=0; (x<=(glyph[i].xmax-glyph[i].xmin)) && sameglyphs; ++x) {
				for (y=0; (y<=(glyph[i].ymax-glyph[i].ymin)) && sameglyphs; ++y) {
					int x1,y1,x2,y2;

					x1=x+glyph[i].xmin;
					y1=y+glyph[i].ymin;
					x2=x+glyph[j].xmin;
					y2=y+glyph[j].ymin;
					
					if (   glyph[i].pattern[x1][y1] 
					    != glyph[j].pattern[x2][y2]) {
						sameglyphs=0;
						break;
					}
				}
			}
			if (! sameglyphs) {
				continue;
			}

			/* here we are, 2 completely identical glyphs 
			 * -> eliminate second one */
			glyph[j].ord=0;
		}
	}



}

static void
pattern_font_gen(unsigned int height)
{
	char path[1024];
	FILE *fp;
	unsigned int c;

	sprintf(path, "glyphs_8x%u.c", height);

	fp = fopen(path, "w");
	assert(fp != (FILE *) 0);

	generated_warning(fp);

	fprintf(fp, "struct {\n");
	fprintf(fp, "\tunsigned short ord;\n");
	fprintf(fp, "\tunsigned char pattern[%u];\n", height);
	fprintf(fp, "} glyph_8x%u[] = {\n", height);

	for (c = 0; c < nglyphs; c++) {
		unsigned int x;
		unsigned int y;

		if (glyph[c].ord == 0) {
			continue;
		}

		if (glyph[c].width != 8
		 || glyph[c].height != height) {
			continue;
		}

		fprintf(fp, "\t{ %d, {\n", glyph[c].ord);

		for (y = 0; y < height; y++) {
			unsigned char bits;

			bits = 0;
			for (x = 0; x < 8; x++) {
				bits <<= 1;
				if (glyph[c].pattern[x][y]) {
					bits |= 1;
				}
			}
			fprintf(fp, "\t\t0x%02x, /* ", bits);
			for (x = 0; x < 8; x++) {
				fprintf(fp, "%d", glyph[c].pattern[x][y]);
			}
			fprintf(fp, " */\n");
		}

		fprintf(fp, "\t}},\n");
	}

	fprintf(fp, "};\n");

	(void) fclose(fp);
}

static void
font_gen(void)
{
	FILE *fp;
	unsigned int c;
	int dlh;
	int num_readable_glyphs=0;

	/*
	 * Write BIOS font files.
	 */
	bios_font_gen(8, 0, 256);
	bios_font_gen(8, 0, 128);
	bios_font_gen(8, 128, 256);
	bios_font_gen(14, 0, 256);
	bios_font_gen(16, 0, 256);

	/*
	 * Write pattern matcher font file.
	 */
	pattern_font_gen(8);
	pattern_font_gen(14);
	pattern_font_gen(16);

	font_read(inputfile2);

	/*
	 * Write pattern matcher samples file for classification.
	 */
	fp = fopen("samples.c", "w");
	assert(fp != (FILE *) 0);

	generated_warning(fp);

	fprintf(fp, "#include \"samples.h\"\n\n");

	fprintf(fp, "/* the number of samples in the array */\n");

	prepare_glyphs();

	delete_doubles();
	
	for (dlh=0; dlh<nglyphs; ++dlh) {
		unsigned char z;
		
		z=glyph[dlh].ord;
		if (is_readable_glyph(z))  {
			num_readable_glyphs++;
		}
	}
	fprintf(fp, "const int NSAMPLES = %d;\n\n", num_readable_glyphs);

	fprintf(fp, "sample_struct sample[] = {\n");

	
	for (c = 0; c < nglyphs; c++) {
		unsigned int x;
		unsigned int y;
		unsigned short xmin, ymin, xmax, ymax;

		/* skip non-readable glyphs */
		if (!(is_readable_glyph(glyph[c].ord))) {
			continue;
		}

		xmin=glyph[c].xmin;
		xmax=glyph[c].xmax;
		ymin=glyph[c].ymin;
		ymax=glyph[c].ymax;
		
		fprintf(fp, "\t{ %d, \n", glyph[c].ord);
		fprintf(fp, "\t %d, \n", (xmax-xmin+1)*(ymax-ymin+1));
		fprintf(fp, "\t %d, \n", xmax-xmin+1);
		fprintf(fp, "\t %d, {\n", ymax-ymin+1);

		assert((xmax-xmin) < MAX_WIDTH);
		assert((ymax-ymin) < MAX_HEIGHT);
		
		for (y = ymin; y <= ymax; y++) {
			unsigned char bits;

			bits = 0;
			fprintf(fp, "\t\t/* ");
			for (x = xmin; x <= xmax; x++) {
				if (glyph[c].pattern[x][y]) {
					fprintf(fp, "@");
				} else {
					fprintf(fp,".");
				}
			}
			fprintf(fp, " */  ");
			for (x = xmin; x <= xmax; x++) {
				if (glyph[c].pattern[x][y]) {
					fprintf(fp, "PATTERN_1, ");
				} else {
					fprintf(fp, "PATTERN_0, ");
				}
			}
			fprintf(fp, "\n");
		}
		
		fprintf(fp, "\t}},\n");
	}

	fprintf(fp, "};\n");

	(void) fclose(fp);
}

int
main(int argc, const char * argv[])
{
	if (argc>=2) inputfile=argv[1];
	if (argc>=3) inputfile2=argv[2];

	font_read(inputfile);
	font_gen();
	
	return 0;
}
