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

