/*
    psftools: Manipulate console fonts in the .PSF format
    Copyright (C) 2003, 2021  John Elliott <seasip.webmaster@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "cnvshell.h"
#include "psflib.h"

/* Compile a textual description (as generated by vfont2txt) to a vfont */

static char helpbuf[2048];
static int vversion = 1;
static int big_endian = 0; 
static VFONT vfont;

/* Program name */
char *cnv_progname = "TXT2VFONT";

/* ddash = 1 if option started with a double-dash; else 0 */
/* Return NULL if OK, else error string */
char *cnv_set_option(int ddash, char *variable, char *value)
{
	if (!stricmp(variable, "v1")) { vversion = 1; return NULL; }
	if (!stricmp(variable, "v2")) { vversion = 2; return NULL; }
	if (!stricmp(variable, "be") || !stricmp(variable, "big-endian")) 
		{ big_endian = 1; return NULL; }
	if (!stricmp(variable, "le") || !stricmp(variable, "little-endian")) 
		{ big_endian = 0; return NULL; }


	if (strlen(variable) > 2000) variable[2000] = 0;
	sprintf(helpbuf, "Unknown option: %s\n", variable);
	return helpbuf;
}


/* Return help string */
char *cnv_help(void)
{
	sprintf(helpbuf, "Syntax: %s textfile vfont { options }\n\n", 
			cnv_progname);
	strcat (helpbuf, "Options: \n"
		    "        --v1            - Output in vfont version 1 format (default).\n"
		    "        --v2            - Output in proposed vfont version 2 format.\n"
		    "        --big-endian    - Output a big-endian file (v1 only).\n");
	return helpbuf;
}

#define ATSTART  0
#define INHEADER 1
#define INCHAR   2
#define INBITMAP 3

#define NOVALUE 65536L


typedef struct charstate
{
	long charleft;
	long charright;
	long charup;
	long chardown;
	long charwidth;
	psf_dword char_w;
	psf_dword char_h;
	psf_dword char_pitch;
	psf_dword char_bytes;

	unsigned char *bitmap;

} CHARSTATE;

void reset_charstate(CHARSTATE *s)
{
	s->charleft = NOVALUE;
	s->charright = NOVALUE;
	s->charup = NOVALUE;
	s->chardown = NOVALUE;
	s->charwidth = NOVALUE;
	s->bitmap = NULL;
}

char *check_charstate(CHARSTATE *s, int line)
{
	if (s->charleft  == NOVALUE) 
	{
		sprintf(helpbuf, "Error on line %d: Character 'Left' dimension not set", line);
		return helpbuf;
	}
	if (s->charright  == NOVALUE) 
	{
		sprintf(helpbuf, "Error on line %d: Character 'Right' dimension not set", line);
		return helpbuf;
	}
	if (s->charup == NOVALUE) 
	{
		sprintf(helpbuf, "Error on line %d: Character 'Up' dimension not set", line);
		return helpbuf;
	}
	if (s->chardown  == NOVALUE) 
	{
		sprintf(helpbuf, "Error on line %d: Character 'Down' dimension not set", line);
		return helpbuf;
	}
	if (s->charwidth == NOVALUE) s->charwidth = s->charleft + s->charright;
	return NULL;
}

char *create_charstate(CHARSTATE *s, int line)
{
	char *str = check_charstate(s, line);
	if (str) return str;

	s->char_w = s->charleft + s->charright;
	s->char_h = s->charup + s->chardown;
	s->char_pitch = (s->char_w + 7) / 8;
	s->char_bytes = s->char_h * s->char_pitch;

	s->bitmap = malloc(s->char_bytes);
	if (s->bitmap == NULL) return psf_error_string(PSF_E_NOMEM);
	memset(s->bitmap, 0, s->char_bytes);
	return NULL;
}


char *charstate_to_vfont(VFONT *vf, psf_dword nchar, CHARSTATE *state)
{
	int rv;

	vf->vf_dispatch[nchar].vfd_addr = vf->vf_size;
	vf->vf_dispatch[nchar].vfd_nbytes = state->char_bytes;
	vf->vf_dispatch[nchar].vfd_left = state->charleft;
	vf->vf_dispatch[nchar].vfd_right = state->charright;
	vf->vf_dispatch[nchar].vfd_up = state->charup;
	vf->vf_dispatch[nchar].vfd_down = state->chardown;
	vf->vf_dispatch[nchar].vfd_width = state->charwidth;
	rv = vfont_realloc(vf, vf->vf_size + state->char_bytes);
	if (rv) return psf_error_string(rv);
	memcpy(&vf->vf_bitmap[vfont.vf_dispatch[nchar].vfd_addr], 
			state->bitmap, state->char_bytes);
	return NULL;
}


char *cnv_execute(FILE *infile, FILE *outfile)
{	
	int rv, n, x = 0, y = 0;
	int state = 0;	
	char linebuf[2000];
	char unibuf[2000];
	char *c;
	int version = -1, flags = -1, length = -1, extend = -1;
	int nchar = 0;
	int line = 0, havebitmap = 0;
	CHARSTATE chstate;

	reset_charstate(&chstate);
	if (big_endian && version == 2)
	{
		return "A version 2 vfont cannot be big-endian";
	}

	vfont_new(&vfont);
	while (fgets(linebuf, sizeof(linebuf), infile))
	{
		++line;
/* If a long line, devour the rest of it */
		if (!strchr(linebuf, '\n'))
		{
			do
			{
				n = fgetc(infile);
			}
			while (n != EOF && n != '\n');	
		}
		c = strchr(linebuf, '\n'); if (c) *c = 0;
		c = strstr(linebuf, "//"); if (c) *c = 0;

		if (linebuf[0] == 0) continue;
		if (linebuf[0] == '%')
		{
			if (!strncmp(linebuf + 1, "VFONT2", 4) && state == ATSTART)
			{
				state = INHEADER;
				continue;
			}
/* %STOP: Immediately stop parsing */
			else if (!strncmp(linebuf + 1, "STOP", 4))
			{
				break;	
			}	
			else if (state == INHEADER)
			{
/* End of header. Flush it */
				if (version == -1) return "Version not set";
				if (flags == -1)   return "Flags not set";
				if (length == -1)  return "Length not set";
				if (version > 0)   return "Versions greater than 0 are not supported"; 
				rv = vfont_create(&vfont, length, 0, (flags & 1));
				if (vversion == 2)
				{
					vfont.vf_magic = VFONT_MAGIC_V2;
					vfont.vf_be    = 0;
				}
				else
				{
					vfont.vf_magic = VFONT_MAGIC_V1;
					vfont.vf_be    = big_endian;
				}
				if (extend != -1) vfont.vf_xtend = extend;

				if (rv) return psf_error_string(rv);
				state = INCHAR;
				unibuf[0] = 0;
				reset_charstate(&chstate);	
			}
			else if (state == INCHAR || state == INBITMAP)
			{
				if (version == -1) return "Version not set";
				if (havebitmap)
				{
					c = check_charstate(&chstate, line);
					if (c) return c;
/* Flush the character we're building */
					if (nchar < vfont.vf_length) 
					{
						c = charstate_to_vfont(&vfont, nchar, &chstate);
						if (c) return c;
						free(chstate.bitmap);
						chstate.bitmap = NULL;
					}	
				}
				if (unibuf[0])
				{
					rv = vfont_unicode_from_string(&vfont, nchar, unibuf);
					if (rv) 
					{
						fprintf(stderr, "Line %d: Failed to decode %s\n", line, unibuf);
						return psf_error_string(rv);
					}
				}
				havebitmap = 0;
				state = INCHAR;
				if (chstate.bitmap) free(chstate.bitmap);
				chstate.bitmap = NULL;
				reset_charstate(&chstate);
				nchar++;
				unibuf[0] = 0;
			}
			else 
			{
				fprintf(stderr, "Line %d: Unexpected %% line\n", line);
				return "Invalid input format";
			}	
		}
		else
		{
			c = strstr(linebuf, ": ");
			if (c)
			{
				*c = 0;
				c++;
				while (*c == ' ') c++;
				if (state == INHEADER)
				{
					if (!strcmp(linebuf, "Version")) 
						version = atol(c);
					else if (!strcmp(linebuf, "Flags"))
						flags = atol(c);
					else if (!strcmp(linebuf, "Length"))
						length = atol(c);
					else if (!strcmp(linebuf, "Extend"))
						extend = atol(c);
/* BitmapLength, MaxWidth and MaxHeight are output by vfont2txt, but 
 * ignored by txt2vfont since they are calculated fields */
					else if (!strcmp(linebuf, "BitmapLength"))
						;
					else if (!strcmp(linebuf, "MaxWidth"))
						;
					else if (!strcmp(linebuf, "MaxHeight"))
						;
					else
					{
						fprintf(stderr, "Line %d: Unknown variable name '%s'\n", line, linebuf);
						return "Invalid input format";
					}
				}
				else if (state == INCHAR)
				{
					if (!strcmp(linebuf, "Left"))
						chstate.charleft = atoi(c);	
					else if (!strcmp(linebuf, "Right"))
						chstate.charright = atoi(c);	
					else if (!strcmp(linebuf, "Up"))
						chstate.charup = atoi(c);	
					else if (!strcmp(linebuf, "Down"))
						chstate.chardown = atoi(c);	
					else if (!strcmp(linebuf, "Width"))
						chstate.charwidth = atoi(c);	
					else if (!strcmp(linebuf, "Unicode"))
						strcpy(unibuf, c);
					else if (!strcmp(linebuf, "Bitmap"))
					{
						strcpy(linebuf, c);
/* When starting a bitmap, we need dimensions */
						c = create_charstate(&chstate, line);
						if (c) return c;
						state = INBITMAP;	
						havebitmap = 0;
						x = y = 0;
					} 
					else
					{
						fprintf(stderr, "Line %d: Unknown variable name '%s'\n", line, linebuf);
						return "Invalid input format";
					}
				}
				else	/* Not INCHAR and not INHEADER */
				{
					fprintf(stderr, "Line %d: Unknown variable name '%s'\n", line, linebuf);
					return "Invalid input format";
				}
				if (state != INBITMAP) continue;
			} /* end if (c) */
			if (state == INBITMAP)
			{
				psf_byte *dest, mask;
				c = linebuf;
				while (*c)
				{
					if (!strchr("-#O@=|", *c))
					{
						c++;
						continue;
					}
					dest = chstate.bitmap + (y * chstate.char_pitch) + (x/8);
					mask = 0x80 >> (x & 7);
					if (*c == '#' || *c == '@') 
							*dest |= mask;
					else	       *dest &= ~mask;

					if ((*c == '@' || *c == 'O') 
					    && (x != chstate.charleft || 
					        y != chstate.charup))
					{
						fprintf(stderr, 
  "Warning (character %d): %c sign (base point position) does not match\n"
  "                       the position derived from left/right/up/down.\n", 
				nchar, *c);
					}

					x++;
					if (x >= chstate.char_w) { x = 0; y++; }
					if (y >= chstate.char_h) 
					{
						state = INCHAR;
						havebitmap = 1;	
						break;
					}
					++c;
				}
				continue;
			}	
			fprintf(stderr, "Line %d: Not a Variable: Value pair\n", line);
			return "Invalid input format";
		}
	}
	if (state == INCHAR && havebitmap)
	{
		c = charstate_to_vfont(&vfont, nchar, &chstate);
		if (c) return c;
		free(chstate.bitmap);
		chstate.bitmap = NULL;

		if (unibuf[0])
		{
			rv = vfont_unicode_from_string(&vfont, nchar, unibuf);
			if (rv) return psf_error_string(rv);
		}
	}
	if (version == 1) vfont_force_v1(&vfont);
	if (version == 2) vfont_force_v2(&vfont);
	rv = vfont_write(&vfont, outfile);
	vfont_delete(&vfont);	
	if (rv) return psf_error_string(rv);
	return NULL;
}

