/*PGC emulation*/
#include <stdlib.h>
#include <math.h>
#include <ctype.h>
#include "ibm.h"
#include "device.h"
#include "io.h"
#include "mem.h"
#include "timer.h"
#include "thread.h"
#include "video.h"
#include "vid_pgc.h"
#include <assert.h>

/* This implements just enough of the Professional Graphics Controller to
 * act as a basis for the Vermont Microsystems IM-1024. 
 *
 * PGC features implemented include:
 * > The CGA-compatible display modes
 * > Switching to and from native mode
 * > Communicating with the host PC
 * 
 * Numerous features are implemented partially or not at all, such as:
 * > 2D drawing
 * > 3D drawing
 * > Command lists
 * Some of these are marked TODO.
 *
 * The PGC has two display modes: CGA (in which it appears in the normal CGA 
 * memory and I/O ranges) and native (in which all functions are accessed
 * through reads and writes to 1k of memory at 0xC6000). The PGC's 8088
 * processor monitors this buffer and executes instructions left there
 * for it. We simulate this behaviour with a separate thread.
 */

#define PGC_CGA_WIDTH 640
#define PGC_CGA_HEIGHT 400

static int pgc_parse_command(pgc_core_t *pgc, const pgc_command_t **pcmd);
int	pgc_clist_byte(pgc_core_t *pgc, uint8_t *val);




static const char *pgc_err_msgs[] = 
{
	"Range   \r",
	"Integer \r",
	"Memory  \r",
	"Overflow\r",
	"Digit   \r",
	"Opcode  \r",
	"Running \r",
	"Stack   \r",
	"Too long\r",
	"Area    \r",
	"Missing \r" 
};

#define HWORD(u) ((u) >> 16)
#define LWORD(u) ((u) & 0xFFFF)


/* Initial palettes */
uint32_t init_palette[6][256] = 
{
#include "pgc_palettes.h"
};



/* When idle, the PGC drawing thread sleeps. pgc_wake() awakens it - but
 * not immediately. Like the Voodoo, it has a short delay so that writes 
 * can be batched */
#define WAKE_DELAY (TIMER_USEC * 500)
void pgc_wake(pgc_core_t *pgc)
{
	if (!timer_is_enabled(&pgc->wake_timer))
	{
		timer_set_delay_u64(&pgc->wake_timer, WAKE_DELAY);
	}
}


/* When the wake timer expires, that's when the drawing thread is actually
 * woken */
static void pgc_wake_timer(void *p)
{
	pgc_core_t *pgc = (pgc_core_t *)p;

	PGCLOG(("pgc: Waking up\n"));
	thread_set_event(pgc->pgc_wake_thread);
}

/* This is called by the drawing thread when it's waiting for the host
 * to put more input in its input FIFO, or drain output from its output
 * FIFO. */
void pgc_sleep(pgc_core_t *pgc)
{

	PGCLOG(("pgc: Sleeping on %d %d %d 0x%02x 0x%02x\n",
			pgc->waiting_input_fifo,
			pgc->waiting_output_fifo,
			pgc->waiting_error_fifo,
			pgc->mapram[0x300], pgc->mapram[0x301])); 
	/* Race condition: If host wrote to the PGC during the pclog() that
	 * won't be noticed */
	if (pgc->waiting_input_fifo && pgc->mapram[0x300] != pgc->mapram[0x301])
	{
		pgc->waiting_input_fifo = 0;
		return;
	}
	/* Same if they read */
	if (pgc->waiting_output_fifo && pgc->mapram[0x302] != (uint8_t)(pgc->mapram[0x303] - 1))	
	{
		pgc->waiting_output_fifo = 0;
		return;
	}
	thread_wait_event(pgc->pgc_wake_thread, -1);
	thread_reset_event(pgc->pgc_wake_thread);

}

/* Switch between CGA mode (DISPLAY 1) and native mode (DISPLAY 0) */
void pgc_setdisplay(pgc_core_t *pgc, int cga)
{
	PGCLOG(("pgc_setdisplay(%d): cga_selected=%d cga_enabled=%d\n",
			cga, pgc->cga_selected, pgc->cga_enabled));
	if (pgc->cga_selected != (pgc->cga_enabled && cga))
	{
		pgc->cga_selected = (pgc->cga_enabled && cga);

		if (pgc->cga_selected)
		{
			mem_mapping_enable(&pgc->cga_mapping);
			pgc->screenw = PGC_CGA_WIDTH;
			pgc->screenh = PGC_CGA_HEIGHT;
		}
		else	
		{
			mem_mapping_disable(&pgc->cga_mapping);
			pgc->screenw = pgc->visw;
			pgc->screenh = pgc->vish;
		}
		pgc_recalctimings(pgc);
	}
}





/* Convert coordinates based on the current window / viewport to raster 
 * coordinates. */
void pgc_dto_raster(pgc_core_t *pgc, double *x, double *y)
{
/*	double x0 = *x, y0 = *y; */

	*x += (pgc->vp_x1 - pgc->win_x1);
	*y += (pgc->vp_y1 - pgc->win_y1);

/* 	PGCLOG(("Coords to raster: (%f, %f) -> (%f, %f)\n", x0, y0, *x, *y)); */
}


/* Overloads that take ints */
void pgc_sto_raster(pgc_core_t *pgc, int16_t *x, int16_t *y)
{
	double xd = *x, yd = *y;

	pgc_dto_raster(pgc, &xd, &yd);
	*x = (int16_t)xd;
	*y = (int16_t)yd;
}

void pgc_ito_raster(pgc_core_t *pgc, int32_t *x, int32_t *y)
{
	double xd = *x, yd = *y;

	pgc_dto_raster(pgc, &xd, &yd);
	*x = (int32_t)xd;
	*y = (int32_t)yd;
}


/* Add a byte to a command list. We allow command lists to be 
 * arbitrarily large */
int pgc_commandlist_append(pgc_commandlist_t *list, uint8_t v)
{
	if (list->listmax == 0 || list->list == NULL)
	{
		list->list = malloc(4096);
		if (!list->list) 
		{
			PGCLOG(("Out of memory initialising command list\n"));
			return 0;
		}
		list->listmax = 4096;
	}
	while (list->wrptr >= list->listmax)
	{
		uint8_t *buf = realloc(list->list, 2 * list->listmax);
		if (!buf) 
		{
			PGCLOG(("Out of memory growing command list\n"));
			return 0;
		}
		list->list = buf;
		list->listmax *= 2;
	}
	list->list[list->wrptr++] = v;
	return 1;
}

/* Beginning of a command list. Parse commands up to the next CLEND,
 * storing them (in hex form) in the named command list. */
static void hndl_clbeg(pgc_core_t *pgc)
{
	uint8_t param;
	pgc_commandlist_t cl;
	const pgc_command_t *cmd;

	memset(&cl, 0, sizeof(cl));
	if (!pgc_param_byte(pgc, &param)) return;

	PGCLOG(("CLBEG(%d)\n", param));
	while (1)
	{
		if (!pgc_parse_command(pgc, &cmd))
		{
			/* PGC has been reset */
			return;	
		} 
		if (!cmd)
		{
			pgc_error(pgc, PGC_ERROR_OPCODE);
			return;		
		}
		else if (pgc->hex_command == 0x71)	/* CLEND */
		{
			pgc->clist[param] = cl;
			return;
		}
		else
		{
			if (!pgc_commandlist_append(&cl, pgc->hex_command))
			{
				pgc_error(pgc, PGC_ERROR_OVERFLOW);
				return;
			}		
			if (cmd->parser)
			{
				if (!(*cmd->parser)(pgc, &cl, cmd->p))
				{
					return;
				}
			}
		}
	}
}

static void hndl_clend(pgc_core_t *pgc)
{
	/* Shouldn't happen outside a CLBEG */
}

/* Execute a command list. If one was already executing, remember it so 
 * we can return to it afterwards. */
static void hndl_clrun(pgc_core_t *pgc)
{
	uint8_t param;
	pgc_commandlist_t *clprev = pgc->clcur;

	if (!pgc_param_byte(pgc, &param)) return;

	pgc->clcur = &pgc->clist[param];
	pgc->clcur->rdptr = 0;
	pgc->clcur->repeat = 1;
	pgc->clcur->chain = clprev;
}

/* Execute a command list multiple times. */
static void hndl_cloop(pgc_core_t *pgc)
{
	uint8_t param;
	int16_t repeat;
	pgc_commandlist_t *clprev = pgc->clcur;

	if (!pgc_param_byte(pgc, &param)) return;
	if (!pgc_param_word(pgc, &repeat)) return;

	pgc->clcur = &pgc->clist[param];
	pgc->clcur->rdptr = 0;
	pgc->clcur->repeat = repeat;
	pgc->clcur->chain = clprev;
}


/* Read back a command list */
static void hndl_clread(pgc_core_t *pgc)
{
	uint8_t param;
	int n;

	if (!pgc_param_byte(pgc, &param)) return;

	for (n = 0; n < pgc->clist[param].wrptr; n++)
	{
		if (!pgc_result_byte(pgc, pgc->clist[param].list[n]))
			return;
	}
}


/* Delete a command list */
static void hndl_cldel(pgc_core_t *pgc)
{
	uint8_t param;

	if (!pgc_param_byte(pgc, &param)) return;
	memset(&pgc->clist[param], 0, sizeof(pgc_commandlist_t));
}



/* Clear the screen to a specified colour */
static void hndl_clears(pgc_core_t *pgc)
{
	uint8_t param;
	int y;

	if (!pgc_param_byte(pgc, &param)) return;

	for (y = 0; y < pgc->screenh; y++)
		memset(pgc->vram + y * pgc->maxw, param, pgc->screenw);
}

/* Select drawing colour */
static void hndl_color(pgc_core_t *pgc)
{
	uint8_t param;

	if (!pgc_param_byte(pgc, &param)) return;

	pgc->colour = param;
	PGCLOG(("COLOR(%d)\n", param));
}


/* Set drawing mode. 
 * 
 * 0 => Draw
 * 1 => Invert
 * 2 => XOR (IM-1024)
 * 3 => AND (IM-1024)
 *
 */
static void hndl_linfun(pgc_core_t *pgc)
{
	uint8_t param;

	if (!pgc_param_byte(pgc, &param)) return;

	/* TODO: Not range-checked. Strictly speaking we should limit to 0-1
	 * for the PGC and 0-3 for the IM-1024. */
	pgc->draw_mode = param;
	PGCLOG(("LINFUN(%d)\n", param));
}

/* Set the line drawing pattern */
static void hndl_linpat(pgc_core_t *pgc)
{
	uint16_t param;

	if (!pgc_param_word(pgc, (int16_t *)&param)) return;

	pgc->line_pattern = param;
	PGCLOG(("LINPAT(0x%04x)\n", param));
}

/* Set the polygon fill mode (0=hollow, 1=filled, 2=fast fill) */
static void hndl_prmfil(pgc_core_t *pgc)
{
	uint8_t param;

	if (!pgc_param_byte(pgc, &param)) return;

	PGCLOG(("PRMFIL(%d)\n", param));
	if (param < 3) 
	{
		pgc->fill_mode = param;
	}
	else
	{
		pgc_error(pgc, PGC_ERROR_RANGE);
	}
}


/* Set the 2D drawing position */
static void hndl_move(pgc_core_t *pgc)
{
	int32_t x = 0, y = 0;

	if (!pgc_param_coord(pgc, &x)) return;
	if (!pgc_param_coord(pgc, &y)) return;

	pgc->x = x;
	pgc->y = y;
	PGCLOG(("MOVE %x.%04x,%x.%04x\n", HWORD(x), LWORD(x), HWORD(y), LWORD(y)));
}

/* Set the 3D drawing position */
static void hndl_move3(pgc_core_t *pgc)
{
	int32_t x = 0, y = 0, z = 0;

	if (!pgc_param_coord(pgc, &x)) return;
	if (!pgc_param_coord(pgc, &y)) return;
	if (!pgc_param_coord(pgc, &z)) return;

	pgc->x = x;
	pgc->y = y;
	pgc->z = z;
}

/* Relative move (2D) */
static void hndl_mover(pgc_core_t *pgc)
{
	int32_t x = 0, y = 0;

	if (!pgc_param_coord(pgc, &x)) return;
	if (!pgc_param_coord(pgc, &y)) return;

	pgc->x += x;
	pgc->y += y;
}

/* Relative move (3D) */
static void hndl_mover3(pgc_core_t *pgc)
{
	int32_t x = 0, y = 0, z = 0;

	if (!pgc_param_coord(pgc, &x)) return;
	if (!pgc_param_coord(pgc, &y)) return;
	if (!pgc_param_coord(pgc, &z)) return;

	pgc->x += x;
	pgc->y += y;
	pgc->z += z;
}



/* Draw a line (using PGC fixed-point coordinates) */
uint16_t pgc_draw_line(pgc_core_t *pgc, int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint16_t linemask)
{

	PGCLOG(("pgc_draw_line: (%d,%d) to (%d,%d)\n", x0 >> 16, y0 >> 16,
					x1 >> 16, y1 >> 16));
	/* Convert from PGC fixed-point to device coordinates */
	x0 >>= 16;
	x1 >>= 16;
	y0 >>= 16;
	y1 >>= 16;

	pgc_ito_raster(pgc, &x0, &y0);
	pgc_ito_raster(pgc, &x1, &y1);

	return pgc_draw_line_r(pgc, x0, y0, x1, y1, linemask);
}

/* Draw a line (using raster coordinates)
   Bresenham's Algorithm from <https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C> 
 *
 * The line pattern mask to use is passed in. The return value is the line
 * pattern mask rotated by the number of points drawn.
 */
uint16_t pgc_draw_line_r(pgc_core_t *pgc, int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint16_t linemask)
{
	int32_t dx, dy, sx, sy, err, e2;

	dx = abs(x1 - x0);
	dy = abs(y1 - y0);
	sx = (x0 < x1) ? 1 : -1;
	sy = (y0 < y1) ? 1 : -1;
	err = (dx > dy ? dx : -dy) / 2;

	for(;;)	
	{
		if (linemask & 0x8000) 
		{
			pgc_plot(pgc, x0, y0);
			linemask = (linemask << 1) | 1;
		}
		else
		{
			linemask = (linemask << 1);
		}
		if (x0 == x1 && y0 == y1) break;
		e2 = err;
		if (e2 > -dx) { err -= dy; x0 += sx; }
		if (e2 <  dy) { err += dx; y0 += sy; }
	}
	return linemask;
}

/* Draw a horizontal line in the current fill pattern 
 * (using raster coordinates) */
void pgc_fill_line_r(pgc_core_t *pgc, int32_t x0, int32_t x1, int32_t y0)
{
	int32_t x;
	int32_t mask = 0x8000 >> (x0 & 0x0F);

	if (x0 > x1) { x = x1; x1 = x0; x0 = x; }

	for (x = x0; x <= x1; x++)
	{
		if (pgc->fill_pattern[y0 & 0x0F] & mask)
			pgc_plot(pgc, x, y0);
		mask = mask >> 1;
		if (mask == 0) mask = 0x8000; 
	} 
}

/* For sorting polygon nodes */
static int compare_double(const void *a, const void *b)
{
	const double *da = (const double *)a;
	const double *db = (const double *)b;

	if (*da > *db) return 1;
	if (*da < *db) return -1;
	return 0;
}

/* Draw a filled polygon (using PGC fixed-point coordinates) */
void pgc_fill_polygon(pgc_core_t *pgc, 
	unsigned corners, int32_t *x, int32_t *y)
{
	double *nodex;
	double *dx;
	double *dy;
	unsigned n, nodes, i, j;
	double ymin, ymax, ypos;

	PGCLOG(("pgc_fill_polygon(%d corners)\n", corners));

	if (corners < 2) return;	/* Degenerate polygon */
	nodex = malloc(corners * sizeof(double));
	dx    = malloc(corners * sizeof(double));
	dy    = malloc(corners * sizeof(double));
	if (!nodex || !dx || !dy) return;

	ymin = ymax = y[0] / 65536.0;
	for (n = 0; n < corners; n++)
	{
	/* Convert from PGC fixed-point to native floating-point */
		dx[n] = x[n] / 65536.0;
		dy[n] = y[n] / 65536.0;
	
		if (dy[n] < ymin) ymin = dy[n];
		if (dy[n] > ymax) ymax = dy[n];
	}
	/* Polygon fill. Based on <http://alienryderflex.com/polygon_fill/> */
	/* For each row, work out where the polygon lines intersect with 
	 * that row. */		
	for (ypos = ymin; ypos <= ymax; ypos++)
	{
		nodes = 0;
		j = corners - 1;
		for (i = 0; i < corners; i++)
		{
			if ((dy[i] < ypos && dy[j] >= ypos)
			||  (dy[j] < ypos && dy[i] >= ypos)) /* Line crosses */
			{
				nodex[nodes++] = dx[i] + (ypos-dy[i])/(dy[j]-dy[i]) * (dx[j] - dx[i]);
			}
			j = i;
		}

		/* Sort the intersections */
		if (nodes) qsort(nodex, nodes, sizeof(double), compare_double);
/*
		PGCLOG(("pgc_fill_polygon ypos=%f nodes=%d  ", ypos, nodes));
		for (i = 0; i < nodes; i++)
		{
			PGCLOG(("%f;", nodex[i]));
		}
		PGCLOG(("\n")); 
*/
		/* And fill between them */
		for (i = 0; i < nodes; i += 2)
		{
			int16_t x1 = nodex[i], x2 = nodex[i + 1],
				y1 = ypos, y2 = ypos;
			pgc_sto_raster(pgc, &x1, &y1);
			pgc_sto_raster(pgc, &x2, &y2);
/*			PGCLOG(("pgc_fill_polygon raster %d,%d to %d,%d\n", 
				x1, y1, x2, y2)); */
			pgc_fill_line_r(pgc, x1, x2, y1);
		}
	}
	free(nodex); 
	free(dx);
	free(dy);
}

	

/* Draw a filled ellipse (using PGC fixed-point coordinates) */
void pgc_draw_ellipse(pgc_core_t *pgc, int32_t x, int32_t y)
{
	/* Convert from PGC fixed-point to native floating-point */
	double h = y / 65536.0;
	double w = x / 65536.0;
	double y0 = pgc->y / 65536.0;
	double x0 = pgc->x / 65536.0;
	double ypos = 0.0, xpos = 0.0;
	double x1;
	double xlast = 0.0;
	int16_t linemask = pgc->line_pattern;

	pgc_dto_raster(pgc, &x0, &y0);
	PGCLOG(("Ellipse: Colour=%d Drawmode=%d fill=%d\n", pgc->colour, 
		pgc->draw_mode, pgc->fill_mode));
	for (ypos = 0; ypos <= h; ypos++)
	{
		if (ypos == 0)
		{
			if (pgc->fill_mode)
			{
				pgc_fill_line_r(pgc, x0 - w, x0 + w, y0);
			}
			if (linemask & 0x8000)
			{
				pgc_plot(pgc, x0 + w, y0);
				pgc_plot(pgc, x0 - w, y0);
				linemask = (linemask << 1) | 1;
			}
			else
			{
				linemask = linemask << 1;
			}
			xlast = w;
		}
		else
		{
			x1 = sqrt((h * h) - (ypos * ypos)) * w / h;

			if (pgc->fill_mode)
			{
				pgc_fill_line_r(pgc, x0 - x1, x0 + x1, y0 + ypos);
				pgc_fill_line_r(pgc, x0 - x1, x0 + x1, y0 - ypos);
			}

			/* Draw border */
			for (xpos = xlast; xpos >= x1; xpos--)
			{
				if (linemask & 0x8000)
				{
					pgc_plot(pgc, x0 + xpos, y0 + ypos);
					pgc_plot(pgc, x0 - xpos, y0 + ypos);
					pgc_plot(pgc, x0 + xpos, y0 - ypos);
					pgc_plot(pgc, x0 - xpos, y0 - ypos);
					linemask = (linemask << 1) | 1;
				}
				else
				{
					linemask = linemask << 1;
				}
				
			}
			xlast = x1;
		}
	}
}

/* Handle the ELIPSE (sic) command */
static void hndl_ellipse(pgc_core_t *pgc)
{
	int32_t x = 0, y = 0;

	if (!pgc_param_coord(pgc, &x)) return;
	if (!pgc_param_coord(pgc, &y)) return;

	pgc_draw_ellipse(pgc, x, y);
}




/* Handle the POLY command */
static void hndl_poly(pgc_core_t *pgc)
{
	uint8_t count;
	int32_t x[256];
	int32_t y[256];
	int32_t n;

	if (!pgc_param_byte(pgc, &count)) return;

	for (n = 0; n < count; n++)
	{
		if (!pgc_param_coord(pgc, &x[n])) return;
		if (!pgc_param_coord(pgc, &y[n])) return;
	}
	PGCLOG(("POLY (%d)\n", count));
}

/* Parse but don't execute a POLY command (for adding to a command list) */
static int parse_poly(pgc_core_t *pgc, pgc_commandlist_t *cl, int c)
{
	uint8_t count;

	PGCLOG(("parse_poly\n"));
	if (!pgc_param_byte(pgc, &count)) return 0;
	PGCLOG(("parse_poly: count=%02x\n", count));

	if (!pgc_commandlist_append(cl, count))
	{
		pgc_error(pgc, PGC_ERROR_OVERFLOW);
		return 0;	
	}
	PGCLOG(("parse_poly: parse %d coords\n", 2 * count));
	return pgc_parse_coords(pgc, cl, 2 * count);
}

	
/* Parse but don't execute a command with a fixed number of byte parameters */
int pgc_parse_bytes(pgc_core_t *pgc, pgc_commandlist_t *cl, int count)
{
	uint8_t *param = malloc(count);
	int n;

	if (!param)
	{
		pgc_error(pgc, PGC_ERROR_OVERFLOW);
		return 0;	
	}
	for (n = 0; n < count; n++)
	{
		if (!pgc_param_byte(pgc, &param[n])) 
		{
			free(param);
			return 0;
		}
	}
	for (n = 0; n < count; n++)
	{
		if (!pgc_commandlist_append(cl, param[n]))
		{
			pgc_error(pgc, PGC_ERROR_OVERFLOW);
			free(param);
			return 0;	
		}
	}
	free(param);
	return 1;
}


/* Parse but don't execute a command with a fixed number of word parameters */
int pgc_parse_words(pgc_core_t *pgc, pgc_commandlist_t *cl, int count)
{
	int16_t *param = malloc(count * sizeof(int16_t));
	int n;

	if (!param)
	{
		pgc_error(pgc, PGC_ERROR_OVERFLOW);
		return 0;	
	}
	for (n = 0; n < count; n++)
	{
		if (!pgc_param_word(pgc, &param[n])) return 0;
	}
	for (n = 0; n < count; n++)
	{
		if (!pgc_commandlist_append(cl, param[n] & 0xFF) ||
		    !pgc_commandlist_append(cl, param[n] >> 8))
		{
			pgc_error(pgc, PGC_ERROR_OVERFLOW);
			free(param);
			return 0;	
		}
	}
	return 1;
}



/* Parse but don't execute a command with a fixed number of coord parameters */
int pgc_parse_coords(pgc_core_t *pgc, pgc_commandlist_t *cl, int count)
{
	int32_t *param = malloc(count * sizeof(int32_t));
	int n;

	if (!param)
	{
		pgc_error(pgc, PGC_ERROR_OVERFLOW);
		return 0;	
	}
	for (n = 0; n < count; n++)
	{
		if (!pgc_param_coord(pgc, &param[n])) return 0;
	}
/* Here's how the real PGC serialises coords:
 *
 * 100.5 -> 64 00 00 80  ie 0064.8000 
 * 100.3 -> 64 00 CD 4C  ie 0064.4CCD
 * 
 */
	for (n = 0; n < count; n++)
	{
		/* Serialise integer part */
		if (!pgc_commandlist_append(cl, (param[n] >> 16) & 0xFF) ||
		    !pgc_commandlist_append(cl, (param[n] >> 24) & 0xFF) ||
		/* Serialise fraction part */
		    !pgc_commandlist_append(cl, (param[n]      ) & 0xFF) ||
		    !pgc_commandlist_append(cl, (param[n] >>  8) & 0xFF))
		{
			pgc_error(pgc, PGC_ERROR_OVERFLOW);
			free(param);
			return 0;	
		}
	}
	return 1;
}



/* Handle the DISPLAY command */
static void hndl_display(pgc_core_t *pgc)
{
	uint8_t param;

	if (!pgc_param_byte(pgc, &param)) return;

	PGCLOG(("DISPLAY(%d)\n", param));
	if (param > 1) 
	{
		pgc_error(pgc, PGC_ERROR_RANGE);
	}
	else
	{
		pgc_setdisplay(pgc, param);
	}
}


/* Handle the IMAGEW command (memory to screen blit) */
static void hndl_imagew(pgc_core_t *pgc)
{
	int16_t row, col1, col2;
	uint8_t v1, v2;

	if (!pgc_param_word(pgc, &row)) return;
	if (!pgc_param_word(pgc, &col1)) return;
	if (!pgc_param_word(pgc, &col2)) return;

	if (row >= pgc->screenh || col1 >= pgc->maxw || col2 >= pgc->maxw)
	{
		pgc_error(pgc, PGC_ERROR_RANGE);
		return;
	}

	/* In ASCII mode, what is written is a stream of bytes */
	if (pgc->ascii_mode)
	{
		while (col1 <= col2)
		{
			if (!pgc_param_byte(pgc, &v1)) return;
			pgc_write_pixel(pgc, col1, row, v1);
			++col1;
		}
		return;
	}
	else	/* In hex mode, it's RLE compressed */
	{
		while (col1 <= col2)
		{
			if (!pgc_param_byte(pgc, &v1)) return;

			if (v1 & 0x80)	/* Literal run */
			{
				v1 -= 0x7F;
				while (col1 <= col2 && v1 != 0)	
				{	
					if (!pgc_param_byte(pgc, &v2)) return;
					pgc_write_pixel(pgc, col1, row, v2);
					++col1;
					--v1;
				}
			}
			else	/* Repeated run */
			{
				if (!pgc_param_byte(pgc, &v2)) return;
		
				++v1;	
				while (col1 <= col2 && v1 != 0)	
				{	
					pgc_write_pixel(pgc, col1, row, v2);
					++col1;
					--v1;
				}
			}	
		}
	}
}


/* Select one of the built-in palettes */
static void pgc_init_lut(pgc_core_t *pgc, int param)
{
	if (param >= 0 && param < 6)
	{
		memcpy(pgc->palette, init_palette[param], sizeof(pgc->palette));
	}
	else if (param == 0xFF)
	{
		memcpy(pgc->palette, pgc->userpal, sizeof(pgc->palette));
	}
	else
	{
		pgc_error(pgc, PGC_ERROR_RANGE);
	}
}

/* Save the current palette */
static void hndl_lutsav(pgc_core_t *pgc)
{
	memcpy(pgc->userpal, pgc->palette, sizeof(pgc->palette));
}


/* Handle LUTINT (select palette) */
static void hndl_lutint(pgc_core_t *pgc)
{
	uint8_t param;

	if (!pgc_param_byte(pgc, &param)) return;

	pgc_init_lut(pgc, param);
}


/* Handle LUTRD (read palette register) */
static void hndl_lutrd(pgc_core_t *pgc)
{
	uint8_t param;
	uint32_t col;

	if (!pgc_param_byte(pgc, &param)) return;

	col = pgc->palette[param];

	pgc_result_byte(pgc, (col >> 20) & 0x0F);	
	pgc_result_byte(pgc, (col >> 12) & 0x0F);	
	pgc_result_byte(pgc, (col >>  4) & 0x0F);	
}

/* Handle LUT (write palette register) */
static void hndl_lut(pgc_core_t *pgc)
{
	uint8_t param[4];
	int n;

	for (n = 0; n < 4; n++)
	{
		if (!pgc_param_byte(pgc, &param[n])) return;
		if (n > 0 && param[n] > 15)
		{
			pgc_error(pgc, PGC_ERROR_RANGE);
			param[n] &= 0x0F;
		}
	}
	pgc->palette[param[0]] = makecol((param[1] * 0x11), 
					 (param[2] * 0x11), 
					 (param[3] * 0x11));
}

/* LUT8RD and LUT8 are extensions implemented by several PGC clones, so
 * here are functions that implement them even though they aren't 
 * used by the PGC */
void pgc_hndl_lut8rd(pgc_core_t *pgc)
{
	uint8_t param;
	uint32_t col;

	if (!pgc_param_byte(pgc, &param)) return;

	col = pgc->palette[param];

	pgc_result_byte(pgc, (col >> 16) & 0xFF);	
	pgc_result_byte(pgc, (col >>  8) & 0xFF);	
	pgc_result_byte(pgc, col & 0xFF);
}

void pgc_hndl_lut8(pgc_core_t *pgc)
{
	uint8_t param[4];
	int n;

	for (n = 0; n < 4; n++)
	{
		if (!pgc_param_byte(pgc, &param[n])) return;
	}
	pgc->palette[param[0]] = makecol((param[1]), (param[2]), (param[3]));
}


/* Handle AREAPT (set 16x16 fill pattern) */
static void hndl_areapt(pgc_core_t *pgc)
{
	int16_t pattern[16];
	int n;

	for (n = 0; n < 16; n++)
	{
		if (!pgc_param_word(pgc, &pattern[n])) return;
	}
	memcpy(pgc->fill_pattern, pattern, sizeof(pgc->fill_pattern));
	PGCLOG(("AREAPT(%04x %04x %04x %04x...)\n",
		pattern[0] & 0xFFFF, pattern[1] & 0xFFFF, 
		pattern[2] & 0xFFFF, pattern[3] & 0xFFFF));
}


/* Handle CA (select ASCII mode) */
static void hndl_ca(pgc_core_t *pgc)
{
	pgc->ascii_mode = 1;
}

/* Handle CX (select hex mode) */
static void hndl_cx(pgc_core_t *pgc)
{
	pgc->ascii_mode = 0;
}

/* CA and CX remain valid in hex mode; they are handled as command 0x43 ('C') 
 * with a one-byte parameter */
static void hndl_c(pgc_core_t *pgc)
{
	uint8_t param;

	if (!pgc->inputbyte(pgc, &param)) return;

	if (param == 'A') pgc->ascii_mode = 1;
	if (param == 'X') pgc->ascii_mode = 0;
}

/* RESETF resets the PGC */
static void hndl_resetf(pgc_core_t *pgc)
{
	pgc_reset(pgc);
}



/* TJUST sets text justify settings */
static void hndl_tjust(pgc_core_t *pgc)
{
	uint8_t param[2];

	if (!pgc->inputbyte(pgc, &param[0])) return;
	if (!pgc->inputbyte(pgc, &param[1])) return;

	if (param[0] >= 1 && param[0] <= 3 &&
	    param[1] >= 1 && param[1] <= 3)
	{
		pgc->tjust_h = param[0];
		pgc->tjust_v = param[1];
	}
	else
	{
		pgc_error(pgc, PGC_ERROR_RANGE);
	}
}

/* TSIZE controls text horizontal spacing */
static void hndl_tsize(pgc_core_t *pgc)
{
	int32_t param = 0;

	if (!pgc_param_coord(pgc, &param)) return;

	pgc->tsize = param;
	PGCLOG(("TSIZE %d\n", param));
}


/* VWPORT sets up the viewport (roughly, the clip rectangle) in raster 
 * coordinates, measured from the bottom left of the screen */
static void hndl_vwport(pgc_core_t *pgc)
{
	int16_t x1, x2, y1, y2;

	if (!pgc_param_word(pgc, &x1)) return;
	if (!pgc_param_word(pgc, &x2)) return;
	if (!pgc_param_word(pgc, &y1)) return;
	if (!pgc_param_word(pgc, &y2)) return;

	PGCLOG(("VWPORT %d,%d,%d,%d\n", x1,x2,y1,y2));
	pgc->vp_x1 = x1;
	pgc->vp_x2 = x2;
	pgc->vp_y1 = y1;
	pgc->vp_y2 = y2;
}

/* WINDOW defines the coordinate system in use */
static void hndl_window(pgc_core_t *pgc)
{
	int16_t x1, x2, y1, y2;

	if (!pgc_param_word(pgc, &x1)) return;
	if (!pgc_param_word(pgc, &x2)) return;
	if (!pgc_param_word(pgc, &y1)) return;
	if (!pgc_param_word(pgc, &y2)) return;

	PGCLOG(("WINDOW %d,%d,%d,%d\n", x1,x2,y1,y2));
	pgc->win_x1 = x1;
	pgc->win_x2 = x2;
	pgc->win_y1 = y1;
	pgc->win_y2 = y2;
}



/* The list of commands implemented by this mini-PGC. In order to support
 * the original PGC and clones, we support two lists; core commands (listed
 * below) and subclass commands (listed in the clone).
 * 
 * Each row has five parameters:
 * 	ASCII-mode command
 * 	Hex-mode command
 * 	Function that executes this command
 * 	Function that parses this command when building a command list
 * 	Parameter for the parse function
 *
 * TODO: This list omits numerous commands present in a genuine PGC
 *       (ARC, AREA, AREABC, BUFFER, CIRCLE etc etc).
 * TODO: Some commands don't have a parse function (for example, IMAGEW) 
 *
 * The following ASCII entries have special meaning:
 * ~~~~~~  command is valid only in hex mode
 * ******  end of subclass command list, now process core command list
 * @@@@@@  end of core command list
 *
 */
static const pgc_command_t pgc_core_commands[] =
{
	{ "AREAPT", 0xE7, hndl_areapt, pgc_parse_words, 16 },
	{ "AP",     0xE7, hndl_areapt, pgc_parse_words, 16 },
	{ "~~~~~~", 0x43, hndl_c },	/* Handle CA / CX in hex mode */
	{ "CA",     0xD2, hndl_ca },		
	{ "CLBEG",  0x70, hndl_clbeg },
	{ "CB",     0x70, hndl_clbeg },
	{ "CLDEL",  0x74, hndl_cldel, pgc_parse_bytes, 1 },
	{ "CD",     0x74, hndl_cldel, pgc_parse_bytes, 1 },
	{ "CLEND",  0x71, hndl_clend },
	{ "CLRUN",  0x72, hndl_clrun, pgc_parse_bytes, 1 },
	{ "CR",     0x72, hndl_clrun, pgc_parse_bytes, 1 },
	{ "CLRD",   0x75, hndl_clread, pgc_parse_bytes, 1 },
	{ "CRD",    0x75, hndl_clread, pgc_parse_bytes, 1 },
	{ "CLOOP",  0x73, hndl_cloop },
	{ "CL",     0x73, hndl_cloop },
	{ "CLEARS", 0x0F, hndl_clears, pgc_parse_bytes, 1 },
	{ "CLS",    0x0F, hndl_clears, pgc_parse_bytes, 1 },
	{ "COLOR",  0x06, hndl_color, pgc_parse_bytes, 1 },
	{ "C",      0x06, hndl_color, pgc_parse_bytes, 1 },
	{ "CX",     0xD1, hndl_cx },
	{ "DISPLA", 0xD0, hndl_display, pgc_parse_bytes, 1 },
	{ "DI",     0xD0, hndl_display, pgc_parse_bytes, 1 },
	{ "ELIPSE", 0x39, hndl_ellipse, pgc_parse_coords, 2 },
	{ "EL",     0x39, hndl_ellipse, pgc_parse_coords, 2 },
	{ "IMAGEW", 0xD9, hndl_imagew },
	{ "IW",     0xD9, hndl_imagew },
	{ "LINFUN", 0xEB, hndl_linfun, pgc_parse_bytes, 1 },
	{ "LF",     0xEB, hndl_linfun, pgc_parse_bytes, 1 },
	{ "LINPAT", 0xEA, hndl_linpat, pgc_parse_words, 1 },
	{ "LP",     0xEA, hndl_linpat, pgc_parse_words, 1 },
	{ "LUTINT", 0xEC, hndl_lutint, pgc_parse_bytes, 1 },
	{ "LI",     0xEC, hndl_lutint, pgc_parse_bytes, 1 },
	{ "LUTRD",  0x50, hndl_lutrd, pgc_parse_bytes, 1 },
	{ "LUTSAV", 0xED, hndl_lutsav, NULL, 0 },
	{ "LUT",    0xEE, hndl_lut, pgc_parse_bytes, 4 },
	{ "MOVE",   0x10, hndl_move, pgc_parse_coords, 2 },
	{ "M",      0x10, hndl_move, pgc_parse_coords, 2 },
	{ "MOVE3",  0x12, hndl_move3, pgc_parse_coords, 3 },
	{ "M3",     0x12, hndl_move3, pgc_parse_coords, 3 },
	{ "MOVER",  0x11, hndl_mover, pgc_parse_coords, 2 },
	{ "MR",     0x11, hndl_mover, pgc_parse_coords, 2 },
	{ "MOVER3", 0x13, hndl_mover3, pgc_parse_coords, 3 },
	{ "MR3",    0x13, hndl_mover3, pgc_parse_coords, 3 },
	{ "PRMFIL", 0xE9, hndl_prmfil, pgc_parse_bytes, 1 },
	{ "PF",     0xE9, hndl_prmfil, pgc_parse_bytes, 1 },
	{ "POLY",   0x30, hndl_poly,   parse_poly },
	{ "P",      0x30, hndl_poly,   parse_poly },
	{ "RESETF", 0x04, hndl_resetf, NULL, 0 },
	{ "RF",     0x04, hndl_resetf, NULL, 0 },
	{ "TJUST",  0x85, hndl_tjust, pgc_parse_bytes, 2 },
	{ "TJ",     0x85, hndl_tjust, pgc_parse_bytes, 2 },
	{ "TSIZE",  0x81, hndl_tsize, pgc_parse_coords, 1 },
	{ "TS",     0x81, hndl_tsize, pgc_parse_coords, 1 },
	{ "VWPORT", 0xB2, hndl_vwport, pgc_parse_words, 4 },
	{ "VWP",    0xB2, hndl_vwport, pgc_parse_words, 4 },
	{ "WINDOW", 0xB3, hndl_window, pgc_parse_words, 4 },
	{ "WI",     0xB3, hndl_window, pgc_parse_words, 4 }, 

	{ "@@@@@@", 0x00, NULL }
};


/* Writes to CGA registers are copied into the transfer memory buffer */
void pgc_out(uint16_t addr, uint8_t val, void *p)
{
	pgc_core_t *pgc = (pgc_core_t *)p;

	switch(addr)
	{
		case 0x3D0: case 0x3D2: case 0x3D4: case 0x3D6:
			    pgc->mapram[0x3D0] = val; break;
		case 0x3D1: case 0x3D3: case 0x3D5: case 0x3D7:
			    if (pgc->mapram[0x3D0] < 18)
			    {
			        pgc->mapram[0x3E0 + pgc->mapram[0x3D0]] = val; 
			    }
			    break;
		case 0x3D8: pgc->mapram[0x3D8] = val; break;
		case 0x3D9: pgc->mapram[0x3D9] = val; break;
	}
	
}

/* Read back the CGA registers */
uint8_t pgc_in(uint16_t addr, void *p)
{
	pgc_core_t *pgc = (pgc_core_t *)p;

	switch(addr)
	{
		case 0x3D0: case 0x3D2: case 0x3D4: case 0x3D6:
			    return pgc->mapram[0x3D0];
		case 0x3D1: case 0x3D3: case 0x3D5: case 0x3D7:
			    if (pgc->mapram[0x3D0] < 18)
			    {
			        return pgc->mapram[0x3E0 + pgc->mapram[0x3D0]];
			    }
			    return 0xFF;

		case 0x3D8: return pgc->mapram[0x3D8];
		case 0x3D9: return pgc->mapram[0x3D9];
		case 0x3DA: return pgc->mapram[0x3DA];
	}
	return 0xFF;
}

/* Memory write to the transfer buffer */
void pgc_write(uint32_t addr, uint8_t val, void *p)
{
	pgc_core_t *pgc = (pgc_core_t *)p;

	/* It seems variable whether the PGC maps 1k or 2k at 0xC6000. 
	 * Map 2k here in case a clone requires it */
	if (addr >= 0xC6000 && addr < 0xC6800)
	{
		addr &= 0x7FF;

		/* If one of the FIFOs has been updated, this may cause
		 * the drawing thread to be woken */

		if (pgc->mapram[addr] != val)
		{
			pgc->mapram[addr] = val;
			switch (addr)
			{
				case 0x300: /* Input write pointer */
				if (pgc->waiting_input_fifo &&
					pgc->mapram[0x300] != pgc->mapram[0x301])
				{
					pgc->waiting_input_fifo = 0;
					pgc_wake(pgc);
				}
				break;

				case 0x303:	/* Output read pointer */
				if (pgc->waiting_output_fifo &&
					pgc->mapram[0x302] != (uint8_t)(pgc->mapram[0x303] - 1))	
				{
					pgc->waiting_output_fifo = 0;
					pgc_wake(pgc);
				}
				break;
			
				case 0x305:	/* Error read pointer */
				if (pgc->waiting_error_fifo &&
					pgc->mapram[0x304] != (uint8_t)(pgc->mapram[0x305] - 1))
				{
					pgc->waiting_error_fifo = 0;
					pgc_wake(pgc);
				}
				break;

				case 0x306:	/* Cold start flag */
				/* XXX This should be in IM-1024 specific code */
				pgc->mapram[0x306] = 0;
				break;

				case 0x030C:	/* Display type */
				pgc_setdisplay(p, pgc->mapram[0x30C]);
				pgc->mapram[0x30D] = pgc->mapram[0x30C];
				break;


				case 0x3FF:	/* Reboot the PGC 
						(handled on core thread) */
				pgc_wake(pgc);
				break;
			} // end switch (addr)
		}
	}
	if (addr >= 0xB8000 && addr < 0xBC000 && pgc->cga_selected)
	{
		addr &= 0x3FFF;
		pgc->cga_vram[addr] = val;
	}
}


uint8_t pgc_read(uint32_t addr, void *p)
{
	pgc_core_t *pgc = (pgc_core_t *)p;

	if (addr >= 0xC6000 && addr < 0xC6800)
	{
		addr &= 0x7FF;

		return pgc->mapram[addr];
	}
	if (addr >= 0xB8000 && addr < 0xBC000 && pgc->cga_selected)
	{
		addr &= 0x3FFF;
		return pgc->cga_vram[addr];
	}
	return 0xFF;
}

/* Called by the drawing thread to read the next byte from the input 
 * buffer. If no byte available will sleep until one is. Returns 0 if 
 * a PGC reset has been triggered by a write to 0xC63FF */
int pgc_input_byte(pgc_core_t *pgc, uint8_t *result)
{
	/* If input buffer empty, wait for it to fill */
	while (pgc->mapram[0x300] == pgc->mapram[0x301])	
	{
		pgc->waiting_input_fifo = 1;
		pgc_sleep(pgc);	
	}
	if (pgc->mapram[0x3FF])	/* Reset triggered */
	{
		pgc_reset(pgc);
		return 0;
	}

	*result = pgc->mapram[pgc->mapram[0x301]];
	++pgc->mapram[0x301];
	return 1;
}

/* Called by the drawing thread to write a byte to the output buffer.
 * If buffer is full will sleep until it is not. Returns 0 if
 * a PGC reset has been triggered by a write to 0xC63FF */
int pgc_output_byte(pgc_core_t *pgc, uint8_t val)
{
	/* If output buffer full, wait for it to empty */
	while (pgc->mapram[0x302] == (uint8_t)(pgc->mapram[0x303] - 1))	
	{
		PGCLOG(("Output buffer state: %02x %02x  Sleeping\n",
			pgc->mapram[0x302], pgc->mapram[0x303]));
		pgc->waiting_output_fifo = 1;
		pgc_sleep(pgc);	
	}
	if (pgc->mapram[0x3FF])	/* Reset triggered */
	{
		pgc_reset(pgc);
		return 0;
	}
	pgc->mapram[0x100 + pgc->mapram[0x302]] = val;
	++pgc->mapram[0x302];
/*
	PGCLOG(("Output %02x: new state: %02x %02x\n", val,
		pgc->mapram[0x302], pgc->mapram[0x303])); */
	return 1;
}

/* Helper to write an entire string to the output buffer */
int pgc_output_string(pgc_core_t *pgc, const char *s)
{
	while (*s)
	{
		if (!pgc_output_byte(pgc, *s)) return 0;
		++s;
	}
	return 1;
}

/* As pgc_output_byte, for the error buffer */
int pgc_error_byte(pgc_core_t *pgc, uint8_t val)
{
	/* If error buffer full, wait for it to empty */
	while (pgc->mapram[0x304] == pgc->mapram[0x305] - 1)	
	{
		pgc->waiting_error_fifo = 1;
		pgc_sleep(pgc);	
	}
	if (pgc->mapram[0x3FF])	/* Reset triggered */
	{
		pgc_reset(pgc);
		return 0;
	}
	pgc->mapram[0x200 + pgc->mapram[0x304]] = val;
	++pgc->mapram[0x304];
	return 1;
}

/* As pgc_output_string, for the error buffer */
int pgc_error_string(pgc_core_t *pgc, const char *s)
{
	while (*s)
	{
		if (!pgc_error_byte(pgc, *s)) return 0;
		++s;
	}
	return 1;
}


/* Report an error, either in ASCII or in hex */
int pgc_error(pgc_core_t *pgc, int err)
{
	if (pgc->mapram[0x307])	/* Errors enabled? */
	{
		if (pgc->ascii_mode)
		{
			if (err >= PGC_ERROR_RANGE && err <= PGC_ERROR_MISSING)
				return pgc_error_string(pgc, pgc_err_msgs[err]);
			return pgc_error_string(pgc, "Unknown error\r");
		}
		else
		{
			return pgc_error_byte(pgc, err);
		}
	}
	return 1;
}


static inline int is_whitespace(char ch)
{
	return (ch != 0 && strchr(" \r\n\t,;()+-", ch) != NULL);
}


/* Read a byte and interpret as ASCII: ignore control characters other than
 * CR, LF or tab. */
int pgc_input_char(pgc_core_t *pgc, char *result)
{
	uint8_t ch;

	while (1)
	{
		if (!pgc->inputbyte(pgc, &ch)) return 0;

		ch &= 0x7F;
		if (ch == '\r' || ch == '\n' || ch == '\t' || ch >= ' ')
		{
			*result = toupper(ch);
			return 1;
		}
	}
}


/* Parameter passed is not a number: abort */
static int err_digit(pgc_core_t *pgc)
{
	uint8_t asc;

	do	/* Swallow everything until the next separator */
	{
		if (!pgc->inputbyte(pgc, &asc)) return 0;
	}	
	while (!is_whitespace(asc));	
	pgc_error(pgc, PGC_ERROR_DIGIT);
	return 0;
}


typedef enum parse_state_t
{
	PS_MAIN,
	PS_FRACTION,
	PS_EXPONENT
} parse_state_t;

/* Read in a PGC coordinate, either as hex (4 bytes) or ASCII (xxxx.yyyyEeee)
 * Returns 0 if PGC reset detected while the value is being read */
int pgc_param_coord(pgc_core_t *pgc, int32_t *value)
{
	uint8_t asc;
	int sign = 1;
	int esign = 1;
	int n;
	uint16_t dp = 1;
	uint16_t integer = 0;
	uint16_t frac = 0;
	uint16_t exponent = 0;
	uint32_t res;
	parse_state_t state = PS_MAIN;
	uint8_t encoded[4];

	/* If there is a command list running, pull the bytes out of that
	 * command list */
	if (pgc->clcur) 
	{
		for (n = 0; n < 4; n++)
		{
			if (!pgc_clist_byte(pgc, &encoded[n])) return 0;
		}
		integer = (((int16_t)encoded[1]) << 8) | encoded[0];
		frac    = (((int16_t)encoded[3]) << 8) | encoded[2];

		*value = (((int32_t)integer) << 16) | frac;
		return 1;
	}
	/* If in hex mode, read in the encoded integer and fraction parts 
	 * from the hex stream */
	if (!pgc->ascii_mode)
	{
		for (n = 0; n < 4; n++)
		{
			if (!pgc->inputbyte(pgc, &encoded[n])) return 0;
		}
		integer = (((int16_t)encoded[1]) << 8) | encoded[0];
		frac    = (((int16_t)encoded[3]) << 8) | encoded[2];

		*value = (((int32_t)integer) << 16) | frac;
		return 1;
	}
	/* Parsing an ASCII value */

	/* Skip separators */
	do
	{
		if (!pgc->inputbyte(pgc, &asc)) return 0;
		if (asc == '-') sign = -1;
	}	
	while (is_whitespace(asc));	

	/* There had better be a digit next */
	if (!isdigit(asc)) 	
	{
		pgc_error(pgc, PGC_ERROR_MISSING);
		return 0;
	}
	do
	{
		switch (asc)
		{
/* Decimal point is acceptable in 'main' state (start of fraction)
 * not otherwise */
			case '.':
				if (state == PS_MAIN)
				{
					if (!pgc->inputbyte(pgc, &asc)) return 0;
					state = PS_FRACTION;
					continue;
				}
				else
				{
					pgc_error(pgc, PGC_ERROR_MISSING);
					return err_digit(pgc);
				}
				break;
			/* Scientific notation */
			case 'd': case 'D': case 'e': case 'E':
				esign = 1;	
				if (!pgc->inputbyte(pgc, &asc)) return 0;
				if (asc == '-') 
				{
					sign = -1;
					if (!pgc->inputbyte(pgc, &asc)) return 0;
				}
				state = PS_EXPONENT;
				continue;
			/* Should be a number or a separator */
			default:
				if (is_whitespace(asc)) break;
				if (!isdigit(asc))
				{
					pgc_error(pgc, PGC_ERROR_MISSING);
					return err_digit(pgc);
				}
				asc -= '0';	/* asc is digit */
				switch (state)
				{
					case PS_MAIN: 
						integer = (integer * 10)+asc;
						if (integer & 0x8000)	/* Overflow */
						{
							pgc_error(pgc, PGC_ERROR_RANGE);
							integer = 0x7FFF;	
						}
						break;
					case PS_FRACTION:
						frac = (frac * 10)+asc;
						dp *= 10;
						break;
					case PS_EXPONENT:
						exponent = (exponent * 10)+asc;
						break;
				}
			
		}
		if (!pgc->inputbyte(pgc, &asc)) return 0;
	}	
	while (!is_whitespace(asc));	


	res = (frac << 16) / dp;
	PGCLOG(("integer=%u frac=%u exponent=%u dp=%d res=0x%08x\n",
		integer, frac, exponent, dp, res));

	res = (res & 0xFFFF) | (integer << 16);
	if (exponent)
	{
		for (n = 0; n < exponent; n++)
		{
			if (esign > 0) res *= 10;
			else	       res /= 10;
		}
	}
	*value = sign*res;	

	return 1;
}

/* Pull the next byte from the current command list */
int pgc_clist_byte(pgc_core_t *pgc, uint8_t *val)
{
	if (pgc->clcur == NULL) return 0;

	if (pgc->clcur->rdptr < pgc->clcur->wrptr)
	{
		*val = pgc->clcur->list[pgc->clcur->rdptr++];
	}
	else 
	{
		*val = 0;
	}
	/* If we've reached the end, reset to the beginning and
	 * (if repeating) run the repeat */
	if (pgc->clcur->rdptr >= pgc->clcur->wrptr)
	{
		pgc->clcur->rdptr = 0;
		--pgc->clcur->repeat;
		if (pgc->clcur->repeat == 0)
		{
			pgc->clcur = pgc->clcur->chain;
		}
	}
	return 1;
}


/* Read in a byte, either as hex (1 byte) or ASCII (decimal).
 * Returns 0 if PGC reset detected while the value is being read */
int pgc_param_byte(pgc_core_t *pgc, uint8_t *val)
{
	int32_t c;

	if (pgc->clcur) return pgc_clist_byte(pgc, val);

	if (!pgc->ascii_mode) return pgc->inputbyte(pgc, val);

	if (!pgc_param_coord(pgc, &c)) return 0;
	c = (c >> 16);	/* Drop fractional part */

	if (c > 255)
	{
		pgc_error(pgc, PGC_ERROR_RANGE);
		return 0;
	}
	*val = (uint8_t)c;
	return 1;
}

/* Read in a word, either as hex (2 bytes) or ASCII (decimal).
 * Returns 0 if PGC reset detected while the value is being read */
int pgc_param_word(pgc_core_t *pgc, int16_t *val)
{
	int32_t c;

	if (pgc->clcur) 
	{
		uint8_t lo, hi;

		if (!pgc_clist_byte(pgc, &lo)) return 0;
		if (!pgc_clist_byte(pgc, &hi)) return 0;
		*val = (((int16_t)hi) << 8) | lo;
		return 1;
	}

	if (!pgc->ascii_mode)
	{
		uint8_t lo, hi;

		if (!pgc->inputbyte(pgc, &lo)) return 0;
		if (!pgc->inputbyte(pgc, &hi)) return 0;
		*val = (((int16_t)hi) << 8) | lo;
		return 1;
	}

	if (!pgc_param_coord(pgc, &c)) return 0;

	c = (c >> 16);
	if (c > 0x7FFF || c < -0x7FFF)
	{
		pgc_error(pgc, PGC_ERROR_RANGE);
		return 0;
	}
	*val = (int16_t)c;
	return 1;
}


/* Output a byte, either as hex or ASCII depending on the mode */
int pgc_result_byte(pgc_core_t *pgc, uint8_t val)
{
	char buf[20];

	if (!pgc->ascii_mode) return pgc_output_byte(pgc, val);

	if (pgc->result_count) 
	{
		if (!pgc_output_byte(pgc, ',')) return 0;
	}
	sprintf(buf, "%d", val);
	++pgc->result_count;
	return pgc_output_string(pgc, buf);	
}


/* Output a word, either as hex or ASCII depending on the mode */
int pgc_result_word(pgc_core_t *pgc, int16_t val)
{
	char buf[20];

	if (!pgc->ascii_mode) 
	{
		if (!pgc_output_byte(pgc, val & 0xFF)) return 0;
		return pgc_output_byte(pgc, val >> 8);
	}

	if (pgc->result_count) 
	{
		if (!pgc_output_byte(pgc, ',')) return 0;
	}
	sprintf(buf, "%d", val);
	++pgc->result_count;
	return pgc_output_string(pgc, buf);	
}


/* Write a screen pixel (x and y are raster coordinates, ink is the
 * value to write) */
void pgc_write_pixel(pgc_core_t *pgc, uint16_t x, uint16_t y, uint8_t ink)
{
	uint8_t *vram;

	/* Suppress out-of-range writes; clip to viewport */
	if (x < pgc->vp_x1 || x > pgc->vp_x2 || x >= pgc->maxw || 
	    y < pgc->vp_y1 || y > pgc->vp_y2 || y >= pgc->maxh) 
	{
		PGCLOG(("pgc_write_pixel clipped: (%d,%d) "
			"vp_x1=%d vp_y1=%d vp_x2=%d vp_y2=%d "
			"ink=0x%02x\n", x, y, 
				pgc->vp_x1, pgc->vp_y1, 
				pgc->vp_x2, pgc->vp_y2, ink));
		return;
	}
	vram = pgc_vram_addr(pgc, x, y);
	if (vram) *vram = ink;
}

/* Read a screen pixel (x and y are raster coordinates) */
uint8_t pgc_read_pixel(pgc_core_t *pgc, uint16_t x, uint16_t y)
{
	uint8_t *vram;

	/* Suppress out-of-range reads */
	if (x >= pgc->maxw || y >= pgc->maxh) 
	{
		return 0;
	}
	vram = pgc_vram_addr(pgc, x, y);
	if (vram) return *vram;
	return 0;
}




/* Plot a point in the current colour and draw mode. Raster coordinates. */
void pgc_plot(pgc_core_t *pgc, uint16_t x, uint16_t y)
{
	uint8_t *vram;

	/* Only allow plotting within the current viewport. */
	if (x < pgc->vp_x1 || x > pgc->vp_x2 || x >= pgc->maxw || 
	    y < pgc->vp_y1 || y > pgc->vp_y2 || y >= pgc->maxh) 
	{
		PGCLOG(("pgc_plot clipped: (%d,%d) %d <= x <= %d; %d <= y <= %d; "
			"mode=%d ink=0x%02x\n", x, y, 
			pgc->vp_x1, pgc->vp_x2, pgc->vp_y1, pgc->vp_y2,
			pgc->draw_mode, pgc->colour));
		return;
	}
	vram = pgc_vram_addr(pgc, x, y);
	if (!vram) return;

	/* TODO: Does not implement the PGC plane mask (set by MASK) */
	switch (pgc->draw_mode)
	{
		default:
		case 0: *vram = pgc->colour; break;	/* Write */
		case 1: *vram ^= 0xFF; break;		/* Invert */
		case 2: *vram ^= pgc->colour; break;	/* XOR colour */
		case 3: *vram &= pgc->colour; break;	/* AND */
	}
}


/* Given raster coordinates, find the matching address in PGC video RAM */
uint8_t *pgc_vram_addr(pgc_core_t *pgc, int16_t x, int16_t y)
{
	int offset;

/* We work from the bottom left-hand corner */
	if (y < 0 || y >= pgc->maxh || x < 0 || x >= pgc->maxw) return NULL;

	offset = (pgc->maxh - 1 -y) * (pgc->maxw) + x;
//	PGCLOG(("pgc_vram_addr x=%d y=%d offset=%d\n", x, y, offset);

	if (offset < 0 || offset >= (pgc->maxw * pgc->maxh))
	{
		return NULL;
	}
	return &pgc->vram[offset];
}



/* Read in the next command, either as hex (1 byte) or ASCII (up to 6 
 * characters) */
int pgc_read_command(pgc_core_t *pgc)
{
	if (pgc->clcur)
	{
		return pgc_clist_byte(pgc, &pgc->hex_command);
	}
	else if (pgc->ascii_mode)
	{
		char ch;	
		int count  = 0;

		while (count < 7)
		{
			if (!pgc_input_char(pgc, &ch)) return 0;
			if (is_whitespace(ch))
			{
				/* Pad to 6 characters */
				while (count < 6)
					pgc->asc_command[count++] = ' ';
				pgc->asc_command[6] = 0;
				return 1;
			}
			pgc->asc_command[count++] = toupper(ch);
		}
		return 1;
	}
	else
	{	
		return pgc->inputbyte(pgc, &pgc->hex_command);
	}
}


/* Read in the next command and parse it */
static int pgc_parse_command(pgc_core_t *pgc, const pgc_command_t **pcmd)
{
	const pgc_command_t *cmd;
	char match[7];

	*pcmd = NULL;
	pgc->hex_command = 0;
	memset(pgc->asc_command, ' ', 6);
	pgc->asc_command[6] = 0;
	if (!pgc_read_command(pgc))
	{
		/* PGC has been reset */
		return 0;
	}
/* Scan the list of valid commands. pgc->pgc_commands may be a subclass
 * list (terminated with '*') or the core list (terminated with '@') */
	for (cmd = pgc->pgc_commands; cmd->ascii[0] != '@'; cmd++)
	{
		/* End of subclass command list, chain to core */
		if (cmd->ascii[0] == '*') cmd = pgc_core_commands;

		/* If in ASCII mode match on the ASCII command */
		if (pgc->ascii_mode && !pgc->clcur)
		{
			sprintf(match, "%-6.6s", cmd->ascii);		
			if (!strncmp(match, pgc->asc_command, 6))
			{
				*pcmd = cmd;
				pgc->hex_command = cmd->hex;
				break;
			}
		}
		else	/* Otherwise match on the hex command */
		{
			if (cmd->hex == pgc->hex_command)
			{
				sprintf(pgc->asc_command, "%-6.6s",
					cmd->ascii);
				*pcmd = cmd;
				break;
			}
		}
	}
	return 1;
}


/* The PGC drawing thread main loop. Read in commands and execute them ad
 * infinitum */
void pgc_core_thread(void *p)
{
	pgc_core_t *pgc = (pgc_core_t *)p;
	const pgc_command_t *cmd;

	PGCLOG(("pgc_core_thread begins"));
	while (1)
	{
		if (!pgc_parse_command(pgc, &cmd))
		{
			/* PGC has been reset */
			continue;
		} 
		PGCLOG(("PGC command: [%02x] '%s' found=%d\n", 
			pgc->hex_command, pgc->asc_command, (cmd != NULL)));

		if (cmd)
		{
			pgc->result_count = 0;
			(*cmd->handler)(pgc);
		}
		else
		{
			pgc_error(pgc, PGC_ERROR_OPCODE);
		}
	}
}

void pgc_recalctimings(pgc_core_t *pgc)
{
	double disptime, _dispontime, _dispofftime;
	double pixel_clock = (cpuclock * (double)(1ull << 32)) / (pgc->cga_selected ? 25175000.0 : pgc->native_pixel_clock);

	/* Use a fixed 640 columns, like the T3100e */
        disptime = pgc->screenw + 11;
        _dispontime = pgc->screenw * pixel_clock;
        _dispofftime = (disptime - pgc->screenw) * pixel_clock;
        pgc->dispontime  = (uint64_t)_dispontime;
        pgc->dispofftime = (uint64_t)_dispofftime;
}

/* Draw the display in CGA (640x400) text mode */
void pgc_cga_text(pgc_core_t *pgc, int w)
{
	int x, c;
	uint8_t chr, attr;
        int drawcursor = 0;
	uint32_t cols[2];
	int pitch = (pgc->mapram[0x3E9] + 1) * 2;
	uint16_t sc = (pgc->displine & 0x0F) % pitch;
        uint16_t ma = (pgc->mapram[0x3ED] | (pgc->mapram[0x3EC] << 8)) & 0x3fff;
        uint16_t ca = (pgc->mapram[0x3EF] | (pgc->mapram[0x3EE] << 8)) & 0x3fff;
	uint8_t *addr;
	int cw = (w == 80) ? 8 : 16;

	addr = &pgc->cga_vram[((ma << 1) + (((pgc->displine / pitch)*w)) * 2) & 0x3ffe];
	ma += (pgc->displine / pitch) * w;

	for (x = 0; x < w; x++)
	{
		chr  = addr[0];
		attr = addr[1];
		addr += 2;
 
		/* Cursor enabled? */           
		if (ma == ca && (pgc->cgablink & 8) && 
			(pgc->mapram[0x3EA] & 0x60) != 0x20)
		{
    			drawcursor = ((pgc->mapram[0x3EA] & 0x1F) <= (sc >> 1)) 
				&&   ((pgc->mapram[0x3EB] & 0x1F) >= (sc >> 1));
		}
		else	drawcursor = 0;
                if (pgc->mapram[0x3D8] & 0x20)
		{
			cols[1] = attr & 15;
			cols[0] = (attr >> 4) & 7;
                        if ((pgc->cgablink & 8) && (attr & 0x80) && !drawcursor) 
				cols[1] = cols[0];
		}
		else
		{ 
			cols[1] = attr & 15; 
			cols[0] = attr >> 4;
		}
		if (drawcursor) 
		{ 
			for (c = 0; c < cw; c++)
			{ 
				if (w == 80) 
					((uint32_t *)buffer32->line[pgc->displine & 2047])[(x << 3) + c] = cols[(fontdatm[chr + pgc->fontbase][sc] & (1 << (c ^ 7))) ? 1 : 0] ^ 0xffffff;
				else
                                        ((uint32_t *)buffer32->line[pgc->displine & 2047])[(x << 4) + c] = cols[(fontdatm[chr + pgc->fontbase][sc] & (1 << (c ^ 7))) ? 1 : 0] ^ 0xffffff;
			}
		}
		else
		{ 
			for (c = 0; c < cw; c++) 
			{
				if (w == 80)
					((uint32_t *)buffer32->line[pgc->displine & 2047])[(x << 3) + c] = cols[(fontdatm[chr + pgc->fontbase][sc] & (1 << (c ^ 7))) ? 1 : 0];
				else
                                        ((uint32_t *)buffer32->line[pgc->displine & 2047])[(x << 4) + c] = cols[(fontdatm[chr + pgc->fontbase][sc] & (1 << ((c >> 1) ^ 7))) ? 1 : 0];
			}
		}
		ma++;
	}
}


/* Draw the display in CGA (320x200) graphics mode */
void pgc_cga_gfx40(pgc_core_t *pgc)
{
	int x, c;
	uint32_t cols[4];
	int	col;
        uint16_t ma = (pgc->mapram[0x3ED] | (pgc->mapram[0x3EC] << 8)) & 0x3fff;
	uint8_t *addr;
        uint16_t dat;

	cols[0] = pgc->mapram[0x3D9] & 15; 
	col = (pgc->mapram[0x3D9] & 16) ? 8 : 0; 

	if (pgc->mapram[0x3D8] & 4) 
	{ 
		cols[1] = col | 3; 
		cols[2] = col | 4; 
		cols[3] = col | 7;
	}
	else if (pgc->mapram[0x3D9] & 32) 
	{ 
		cols[1] = col | 3; 
		cols[2] = col | 5; 
		cols[3] = col | 7; 
	} 
	else 
	{ 
		cols[1] = col | 2; 
		cols[2] = col | 4; 
		cols[3] = col | 6; 
	}
	for (x = 0; x < 40; x++)
	{
		addr = &pgc->cga_vram[(ma + 2 * x + 80 * (pgc->displine >> 2) + 0x2000 * ((pgc->displine >> 1) & 1)) & 0x3FFF];
		dat = (addr[0] << 8) | addr[1];
		pgc->ma++;
		for (c = 0; c < 8; c++) 
		{ 
			((uint32_t *)buffer32->line[pgc->displine & 2047])[(x << 4) + (c << 1)] =
			((uint32_t *)buffer32->line[pgc->displine & 2047])[(x << 4) + (c << 1) + 1] = cols[dat >> 14];
			dat <<= 2; 
		}
	}
}


/* Draw the display in CGA (640x200) graphics mode */
void pgc_cga_gfx80(pgc_core_t *pgc)
{
	int x, c;
	uint32_t cols[2];
        uint16_t ma = (pgc->mapram[0x3ED] | (pgc->mapram[0x3EC] << 8)) & 0x3fff;
	uint8_t *addr;
        uint16_t dat;

	cols[0] = 0;
	cols[1] = pgc->mapram[0x3D9] & 15; 

	for (x = 0; x < 40; x++)
	{
		addr = &pgc->cga_vram[(ma + 2 * x + 80 * (pgc->displine >> 2) + 0x2000 * ((pgc->displine >> 1) & 1)) & 0x3FFF];
		dat = (addr[0] << 8) | addr[1];
		pgc->ma++;
		for (c = 0; c < 16; c++)
		{ 
			((uint32_t *)buffer32->line[pgc->displine & 2047])[(x << 4) + c] = cols[dat >> 15];
			dat <<= 1;
		}
	}
}



/* Draw the screen in CGA mode. Based on the simplified WY700 renderer 
 * rather than the original CGA, since the PGC doesn't have a real 6845 */
void pgc_cga_poll(pgc_core_t *pgc)
{
        int c;
	uint32_t cols[2];

        if (!pgc->linepos)
        {
                timer_advance_u64(&pgc->timer, pgc->dispofftime);
                pgc->mapram[0x3DA] |= 1;
                pgc->linepos = 1;
                if (pgc->cgadispon)
                {
                        if (pgc->displine == 0)
                        {
                                video_wait_for_buffer();
                        }
                       
                        if ((pgc->mapram[0x3D8] & 0x12) == 0x12)
			{
				pgc_cga_gfx80(pgc);	
			}
			else if (pgc->mapram[0x3D8] & 0x02)
			{
				pgc_cga_gfx40(pgc);
			}
                        else if (pgc->mapram[0x3D8] & 0x01)
			{
				pgc_cga_text(pgc, 80);
			}
			else 
			{
				pgc_cga_text(pgc, 40);
			}
                }
                else
                {
                        cols[0] = ((pgc->mapram[0x3D8] & 0x12) == 0x12) ? 0 : (pgc->mapram[0x3D9] & 15);
                        hline(buffer32, 0, pgc->displine & 2047, PGC_CGA_WIDTH, cols[0]);
                }

		for (c = 0; c < PGC_CGA_WIDTH; c++)
			((uint32_t *)buffer32->line[pgc->displine & 2047])[c] = cgapal[((uint32_t *)buffer32->line[pgc->displine & 2047])[c] & 0xf];

		pgc->displine++;
		if (pgc->displine == PGC_CGA_HEIGHT)
		{
                	pgc->mapram[0x3DA] |= 8;
                	pgc->cgadispon = 0;
		}
		if (pgc->displine == PGC_CGA_HEIGHT + 32)
		{
                	pgc->mapram[0x3DA] &= ~8;
                	pgc->cgadispon = 1;
			pgc->displine = 0;
		}
        }
        else
        {
		if (pgc->cgadispon)
		{
			pgc->mapram[0x3DA] &= ~1;
		}
                timer_advance_u64(&pgc->timer, pgc->dispontime);
                pgc->linepos = 0;

		if (pgc->displine == PGC_CGA_HEIGHT)
		{
			if (PGC_CGA_WIDTH != xsize || PGC_CGA_HEIGHT != ysize)
                        {
                                xsize = PGC_CGA_WIDTH;
                                ysize = PGC_CGA_HEIGHT;
                                updatewindowsize(xsize, ysize);
                        } 
			video_blit_memtoscreen(0, 0, 0, ysize, xsize, ysize);
			frames++;

			/* We have a fixed 640x400 screen for
			 * CGA modes */
			video_res_x = PGC_CGA_WIDTH; 
			video_res_y = PGC_CGA_HEIGHT; 
			switch (pgc->mapram[0x3D8] & 0x12)
			{
				case 0x12: video_bpp = 1; break;
				case 0x02: video_bpp = 2; break;
				default:   video_bpp = 0; break;
			}
                        pgc->cgablink++;
                }
        }
}



/* Draw the screen in CGA or native mode. */
void pgc_poll(void *p)
{
	pgc_core_t *pgc = (pgc_core_t *)p;
	int x, y;

	if (pgc->cga_selected)
	{
		pgc_cga_poll(pgc);
		return;
	}
/* Not CGA, so must be native mode */
        if (!pgc->linepos)
        {
                timer_advance_u64(&pgc->timer, pgc->dispofftime);
                pgc->mapram[0x3DA] |= 1;
                pgc->linepos = 1;
                if (pgc->cgadispon && pgc->displine < pgc->maxh)
                {
			if (pgc->displine == 0)
			{
				video_wait_for_buffer();
			}
			/* Don't know why pan needs to be multiplied by -2, but
			 * the IM1024 driver uses PAN -112 for an offset of 
			 * 224. */
			y = pgc->displine - 2 * pgc->pan_y;
			for (x = 0; x < pgc->screenw; x++)
			{
				if (x + pgc->pan_x < pgc->maxw)
				{
					((uint32_t *)buffer32->line[pgc->displine & 2047])[x] = pgc->palette[pgc->vram[y * pgc->maxw + x]];
				}
				else
				{
					((uint32_t *)buffer32->line[pgc->displine & 2047])[x] = pgc->palette[0];
				}
			}
		}
                else
                {
                        hline(buffer32, 0, pgc->displine & 2047, pgc->screenw, pgc->palette[0]);
                }
		pgc->displine++;
		if (pgc->displine == pgc->screenh)
		{
                	pgc->mapram[0x3DA] |= 8;
                	pgc->cgadispon = 0;
		}
		if (pgc->displine == pgc->screenh + 32)
		{
                	pgc->mapram[0x3DA] &= ~8;
                	pgc->cgadispon = 1;
			pgc->displine = 0;
		}
        }
        else
        {
		if (pgc->cgadispon)
		{
			pgc->mapram[0x3DA] &= ~1;
		}
                timer_advance_u64(&pgc->timer, pgc->dispontime);
                pgc->linepos = 0;

		if (pgc->displine == pgc->screenh)
		{
			if (pgc->screenw != xsize || pgc->screenh != ysize)
                        {
                                xsize = pgc->screenw;
                                ysize = pgc->screenh;
                                updatewindowsize(xsize, ysize);
                        } 
			video_blit_memtoscreen(0, 0, 0, ysize, xsize, ysize);
			frames++;

			video_res_x = pgc->screenw;
			video_res_y = pgc->screenh;
			video_bpp = 8;
                        pgc->cgablink++;
                }
        }
}


/* Initialise RAM and registers to default values */
void pgc_reset(pgc_core_t *pgc)
{
	int n;

	memset(pgc->mapram, 0, sizeof(pgc->mapram));
/* There is no point in emulating the 'CGA disable' jumper as this is only
 * appropriate for a dual-head system, not emulated by PCEM */
	pgc->mapram[0x30B] = pgc->cga_enabled = 1;
	pgc->mapram[0x3F8] = 0x03;	/* Minor version */
	pgc->mapram[0x3F9] = 0x01;	/* Minor version */
	pgc->mapram[0x3FB] = 0xA5;	/* } */
	pgc->mapram[0x3FC] = 0x5A;	/* PGC self-test passed */
	pgc->mapram[0x3FD] = 0x55;	/* } */
	pgc->mapram[0x3FE] = 0x5A;	/* } */

	pgc->ascii_mode = 1;	/* Start off in ASCII mode */
	pgc->line_pattern = 0xFFFF;
	memset(pgc->fill_pattern, 0xFF, sizeof(pgc->fill_pattern));
	pgc->colour = 0xFF;
	pgc->tjust_h = 1;
	pgc->tjust_v = 1;

	/* Reset panning */
	pgc->pan_x = 0;
	pgc->pan_y = 0;

	/* Reset clipping */
	pgc->vp_x1 = 0;
	pgc->vp_y1 = 0;
	pgc->vp_x2 = pgc->visw - 1;
	pgc->vp_y2 = pgc->vish - 1;

	/* Empty command lists */
	for (n = 0; n < 256; n++)
	{
		pgc->clist[n].wrptr = 0;
		pgc->clist[n].rdptr = 0;
		pgc->clist[n].repeat = 0;
		pgc->clist[n].chain = 0;
	}
	pgc->clcur = NULL;
	/* Select CGA display */
	pgc->cga_selected = -1;
	pgc_setdisplay(pgc, pgc->cga_enabled);
	pgc->mapram[0x30C] = pgc->cga_enabled;
	pgc->mapram[0x30D] = pgc->cga_enabled;
	/* Default palette is 0 */
	pgc_init_lut(pgc, 0);
	hndl_lutsav(pgc);
}


/* Initialisation code common to the PGC and its subclasses. 
 *
 * Pass the 'input byte' function in since this is overridden in 
 * the IM-1024, and needs to be set before the drawing thread is 
 * launched */
void pgc_core_init(pgc_core_t *pgc, int maxw, int maxh, int visw, int vish,
	int (*inpbyte)(struct pgc_core_t *pgc, uint8_t *result), double native_pixel_clock)
{
	int n;

	mem_mapping_add(&pgc->mapping, 0xC6000, 0x800, 
		pgc_read, NULL, NULL, 
		pgc_write, NULL, NULL, NULL, MEM_MAPPING_EXTERNAL, pgc);
	mem_mapping_add(&pgc->cga_mapping, 0xB8000, 0x8000, 
		pgc_read, NULL, NULL, 
		pgc_write, NULL, NULL, NULL, MEM_MAPPING_EXTERNAL, pgc);
	io_sethandler(0x3D0, 0x0010, pgc_in, NULL, NULL, pgc_out, NULL, NULL,
		pgc);

	pgc->maxw = maxw;
	pgc->maxh = maxh;
	pgc->visw = visw;
	pgc->vish = vish;
	pgc->vram = malloc(maxw * maxh);
	pgc->cga_vram = malloc(0x4000);
	pgc->clist = calloc(256, sizeof(pgc_commandlist_t));
	pgc->clcur = NULL;
	pgc->native_pixel_clock = native_pixel_clock;

	memset(pgc->vram, 0, maxw * maxh);
	memset(pgc->cga_vram, 0, 0x4000);
	/* Empty command lists */
	for (n = 0; n < 256; n++)
	{
		pgc->clist[n].list = NULL;
		pgc->clist[n].listmax = 0;
		pgc->clist[n].wrptr = 0;
		pgc->clist[n].rdptr = 0;
		pgc->clist[n].repeat = 0;
		pgc->clist[n].chain = NULL;
	}

	pgc_reset(pgc);
	pgc->inputbyte = inpbyte;
	pgc->pgc_commands = pgc_core_commands;
	pgc->pgc_wake_thread = thread_create_event();
	pgc->pgc_thread = thread_create(pgc_core_thread, pgc);

        timer_add(&pgc->timer, pgc_poll, (void *)pgc, 1);

        timer_add(&pgc->wake_timer, pgc_wake_timer, (void *)pgc, 0);
}


/* Initialisation code specific to the PGC */
void *pgc_standalone_init()
{
	pgc_core_t *pgc = malloc(sizeof(pgc_core_t));
	memset(pgc, 0, sizeof(pgc_core_t));

	/* Framebuffer and screen are both 640x480 */
	pgc_core_init(pgc, 640, 480, 640, 480, pgc_input_byte, 25175000.0);
	return pgc;
}


void pgc_close(void *p)
{
        pgc_core_t *pgc = (pgc_core_t *)p;
        
        thread_kill(pgc->pgc_thread);
        thread_destroy_event(pgc->pgc_wake_thread);

	if (pgc->cga_vram)
	{
        	free(pgc->cga_vram);
	}
	if (pgc->vram)
	{
        	free(pgc->vram);
	}
        free(pgc);
}

void pgc_speed_changed(void *p)
{
        pgc_core_t *pgc = (pgc_core_t *)p;

        pgc_recalctimings(pgc);
}


device_config_t pgc_config[] =
{
	{
		.type = -1
	}
};

device_t pgc_device =
{
        "PGC",
        0,
        pgc_standalone_init,
        pgc_close,
        NULL,
        pgc_speed_changed,
        NULL,
        NULL,
        pgc_config
};

