/*
 * 86Box	A hypervisor and IBM PC system emulator that specializes in
 *		running old operating systems and software designed for IBM
 *		PC systems and compatibles from 1981 through fairly recent
 *		system designs based on the PCI bus.
 *
 *		This file is part of the 86Box distribution.
 *
 *		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 behavior
 *		with a separate thread.
 *
 * **NOTE**	This driver is not finished yet:
 *
 *		- cursor will blink at very high speed if used on a machine
 *		  with clock greater than 4.77MHz. We should  "scale down"
 *		  this speed, to become relative to a 4.77MHz-based system.
 *
 *		- pgc_plot() should be overloaded by clones if they support
 *		  modes other than WRITE and INVERT, like the IM-1024.
 *
 *		- test it with the Windows 1.x driver?
 *
 *		This is expected to be done shortly.
 *
 *
 *
 * Authors:	Fred N. van Kempen, <decwiz@yahoo.com>
 *		John Elliott, <jce@seasip.info>
 *
 *		Copyright 2019 Fred N. van Kempen.
 *		Copyright 2019 John Elliott.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free  Software  Foundation; either  version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is  distributed in the hope that it will be useful, but
 * WITHOUT   ANY  WARRANTY;  without  even   the  implied  warranty  of
 * MERCHANTABILITY  or FITNESS  FOR A PARTICULAR  PURPOSE. See  the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the:
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place - Suite 330
 *   Boston, MA 02111-1307
 *   USA.
 */
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <wchar.h>
#include <ctype.h>
#include <math.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/io.h>
#include <86box/mem.h>
#include <86box/rom.h>
#include <86box/timer.h>
#include <86box/device.h>
#include <86box/pit.h>
#include <86box/plat.h>
#include <86box/video.h>
#include <86box/vid_cga.h>
#include <86box/vid_pgc.h>


#define PGC_CGA_WIDTH	640
#define PGC_CGA_HEIGHT	400

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

#define WAKE_DELAY	(TIMER_USEC * 500)


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",
    "Unknown \r"
};


/* Initial palettes */
static const uint32_t init_palette[6][256] = {
#include <86box/vid_pgc_palette.h>
};


static video_timings_t timing_pgc = {VIDEO_ISA, 8, 16, 32,   8, 16, 32};


#ifdef ENABLE_PGC_LOG
int pgc_do_log = ENABLE_PGC_LOG;


static void
pgc_log(const char *fmt, ...)
{
    va_list ap;

    if (pgc_do_log) {
	va_start(ap, fmt);
	pclog_ex(fmt, ap);
	va_end(ap);
    }
}
#else
#define pgc_log(fmt, ...)
#endif


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


/*
 * 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.
 */
static int
output_byte(pgc_t *dev, uint8_t val)
{
    /* If output buffer full, wait for it to empty. */
    while (!dev->stopped && dev->mapram[0x302] == (uint8_t)(dev->mapram[0x303] - 1)) {
	pgc_log("PGC: output buffer state: %02x %02x  Sleeping\n",
		dev->mapram[0x302], dev->mapram[0x303]);
	dev->waiting_output_fifo = 1;
	pgc_sleep(dev);
    }

    if (dev->mapram[0x3ff]) {
	/* Reset triggered. */
	pgc_reset(dev);
	return 0;
    }

    dev->mapram[0x100 + dev->mapram[0x302]] = val;
    dev->mapram[0x302]++;

    pgc_log("PGC: output %02x: new state: %02x %02x\n", val,
	    dev->mapram[0x302], dev->mapram[0x303]);

    return 1;
}


/* Helper to write an entire string to the output buffer. */
static int
output_string(pgc_t *dev, const char *s)
{
    while (*s) {
	if (! output_byte(dev, *s)) return 0;
	s++;
    }

    return 1;
}


/* As output_byte, for the error buffer. */
static int
error_byte(pgc_t *dev, uint8_t val)
{
    /* If error buffer full, wait for it to empty. */
    while (!dev->stopped && dev->mapram[0x304] == dev->mapram[0x305] - 1) {
	dev->waiting_error_fifo = 1;
	pgc_sleep(dev);
    }

    if (dev->mapram[0x3ff]) {
	/* Reset triggered. */
	pgc_reset(dev);
	return 0;
    }

    dev->mapram[0x200 + dev->mapram[0x304]] = val;
    dev->mapram[0x304]++;

    return 1;
}


/* As output_string, for the error buffer. */
static int
error_string(pgc_t *dev, const char *s)
{
    while (*s) {
	if (! error_byte(dev, *s)) return 0;
	s++;
    }

    return 1;
}


/*
 * Read 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.
 */
static int
input_byte(pgc_t *dev, uint8_t *result)
{
    /* If input buffer empty, wait for it to fill. */
    while (!dev->stopped && (dev->mapram[0x300] == dev->mapram[0x301])) {
	dev->waiting_input_fifo = 1;
	pgc_sleep(dev);
    }

    if (dev->stopped)
	return 0;

    if (dev->mapram[0x3ff]) {
	/* Reset triggered. */
	pgc_reset(dev);
	return 0;
    }

    *result = dev->mapram[dev->mapram[0x301]];
    dev->mapram[0x301]++;

    return 1;
}


/*
 * Read a byte and interpret as ASCII.
 *
 * Ignore control characters other than CR, LF or tab.
 */
static int
input_char(pgc_t *dev, char *result)
{
    uint8_t ch;

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

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


/*
 * Read in the next command.
 *
 * This can be either as hex (1 byte) or ASCII (up to 6 characters).
 */
static int
read_command(pgc_t *dev)
{
    if (dev->stopped)
	return 0;

    if (dev->clcur)
	return pgc_clist_byte(dev, &dev->hex_command);

    if (dev->ascii_mode) {
	char ch;
	int count  = 0;

	while (count < 7) {
		if (dev->stopped) return 0;

		if (! input_char(dev, &ch)) return 0;

		if (is_whitespace(ch)) {
			/* Pad to 6 characters */
			while (count < 6)
				dev->asc_command[count++] = ' ';
			dev->asc_command[6] = 0;

			return 1;
		}
		dev->asc_command[count++] = toupper(ch);
	}

	return 1;
    }

    return dev->inputbyte(dev, &dev->hex_command);
}


/* Read in the next command and parse it. */
static int
parse_command(pgc_t *dev, const pgc_cmd_t **pcmd)
{
    const pgc_cmd_t *cmd;
    char match[7];

    *pcmd = NULL;
    dev->hex_command = 0;
    memset(dev->asc_command, ' ', 6);
    dev->asc_command[6] = 0;

    if (! read_command(dev)) {
	/* PGC has been reset. */
	return 0;
    }

    /*
     * Scan the list of valid commands.
     *
     * dev->commands may be a subclass list (terminated with '*')
     * or the core list (terminated with '@')
     */
    for (cmd = dev->commands; cmd->ascii[0] != '@'; cmd++) {
	/* End of subclass command list, chain to core. */
	if (cmd->ascii[0] == '*')
		cmd = dev->master;

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

    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_t *dev)
{
    const pgc_cmd_t *cmd;
    uint8_t param = 0;
    pgc_cl_t cl;

    if (! pgc_param_byte(dev, &param)) return;
    pgc_log("PGC: CLBEG(%i)\n", param);

    memset(&cl, 0x00, sizeof(pgc_cl_t));

    while (1) {
	if (! parse_command(dev, &cmd)) {
		/* PGC has been reset. */
		return;
	}
	if (!cmd) {
		pgc_error(dev, PGC_ERROR_OPCODE);
		return;
	} else if (dev->hex_command == 0x71) {
		/* CLEND */
		dev->clist[param] = cl;
		return;
	} else {
		if (! pgc_cl_append(&cl, dev->hex_command)) {
			pgc_error(dev, PGC_ERROR_OVERFLOW);
			return;
		}

		if (cmd->parser) {
			if (! (*cmd->parser)(dev, &cl, cmd->p))
				return;
		}
	}
    }
}


static void
hndl_clend(pgc_t *dev)
{
    /* Should not 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_t *dev)
{
    pgc_cl_t *clprev = dev->clcur;
    uint8_t param = 0;

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

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


/* Execute a command list multiple times. */
static void
hndl_cloop(pgc_t *dev)
{
    pgc_cl_t *clprev = dev->clcur;
    uint8_t param = 0;
    int16_t repeat = 0;

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

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


/* Read back a command list. */
static void
hndl_clread(pgc_t *dev)
{
    uint8_t param = 0;
    uint32_t n;

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

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


/* Delete a command list. */
static void
hndl_cldel(pgc_t *dev)
{
    uint8_t param = 0;

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

    memset(&dev->clist[param], 0, sizeof(pgc_cl_t));
}


/* Clear the screen to a specified color. */
static void
hndl_clears(pgc_t *dev)
{
    uint8_t param = 0;
    uint32_t y;

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

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


/* Select drawing color. */
static void
hndl_color(pgc_t *dev)
{
    uint8_t param = 0;

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

    pgc_log("PGC: COLOR(%i)\n", param);
    dev->color = param;
}


/*
 * Set drawing mode.
 *
 * 0 => Draw
 * 1 => Invert
 */
static void
hndl_linfun(pgc_t *dev)
{
    uint8_t param = 0;

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

    pgc_log("PGC: LINFUN(%i)\n", param);
    if (param < 2)
	dev->draw_mode = param;
    else
	pgc_error(dev, PGC_ERROR_RANGE);
}


/* Set the line drawing pattern. */
static void
hndl_linpat(pgc_t *dev)
{
    uint16_t param = 0;

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

    pgc_log("PGC: LINPAT(0x%04x)\n", param);
    dev->line_pattern = param;
}


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

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

    pgc_log("PGC: PRMFIL(%i)\n", param);
    if (param < 3)
	dev->fill_mode = param;
    else
	pgc_error(dev, PGC_ERROR_RANGE);
}


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

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

    pgc_log("PCG: MOVE %x.%04x,%x.%04x\n",
	HWORD(x), LWORD(x), HWORD(y), LWORD(y));
    dev->x = x;
    dev->y = y;
}


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

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

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


/* Relative move (2D). */
static void
hndl_mover(pgc_t *dev)
{
    int32_t x = 0, y = 0;

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

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


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

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

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


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

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

    offset = (dev->maxh - 1 - y) * (dev->maxw) + x;
    pgc_log("PGC: vram_addr(x=%i,y=%i) = %i\n", x, y, offset);

    if (offset < 0 || (uint32_t)offset >= (dev->maxw * dev->maxh))
	return NULL;

    return &dev->vram[offset];
}


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

    /* Suppress out-of-range writes; clip to viewport. */
    if (x < dev->vp_x1 || x > dev->vp_x2 || x >= dev->maxw ||
        y < dev->vp_y1 || y > dev->vp_y2 || y >= dev->maxh) {
	pgc_log("PGC: write_pixel clipped: (%i,%i) "
	      "vp_x1=%i vp_y1=%i vp_x2=%i vp_y2=%i "
	      "ink=0x%02x\n",
	      x, y, dev->vp_x1, dev->vp_y1, dev->vp_x2, dev->vp_y2, ink);
	return;
    }

    vram = pgc_vram_addr(dev, x, y);
    if (vram)
	*vram = ink;
}


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

    /* Suppress out-of-range reads. */
    if (x >= dev->maxw || y >= dev->maxh)
	return 0;

    vram = pgc_vram_addr(dev, x, y);
    if (vram)
	return *vram;

    return 0;
}


/*
 * Plot a point in the current color and draw mode. Raster coordinates.
 *
 * FIXME: this should be overloaded by clones if they support
 *        modes other than WRITE and INVERT, like the IM-1024.
 */
void
pgc_plot(pgc_t *dev, uint16_t x, uint16_t y)
{
    uint8_t *vram;

    /* Only allow plotting within the current viewport. */
    if (x < dev->vp_x1 || x > dev->vp_x2 || x >= dev->maxw ||
	y < dev->vp_y1 || y > dev->vp_y2 || y >= dev->maxh) {
	pgc_log("PGC: plot clipped: (%i,%i) %i <= x <= %i; %i <= y <= %i; "
		"mode=%i ink=0x%02x\n", x, y,
		dev->vp_x1, dev->vp_x2, dev->vp_y1, dev->vp_y2,
		dev->draw_mode, dev->color);
	return;
    }

    vram = pgc_vram_addr(dev, x, y);
    if (! vram) return;

    /* TODO: Does not implement the PGC plane mask (set by MASK). */
    switch (dev->draw_mode) {
	default:
	case 0:		/* WRITE */
		*vram = dev->color;
		break;

	case 1:		/* INVERT */
		*vram ^= 0xff;
		break;

	case 2:		/* XOR color */
		//FIXME: see notes
		*vram ^= dev->color;
		break;

	case 3:		/* AND color */
		//FIXME: see notes
		*vram &= dev->color;
		break;
    }
}


/*
 * 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. Return value is the
 * line pattern mask, rotated by the number of points drawn.
 */
uint16_t
pgc_draw_line_r(pgc_t *dev, 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(dev, 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 line (using PGC fixed-point coordinates). */
uint16_t
pgc_draw_line(pgc_t *dev, int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint16_t linemask)
{
    pgc_log("pgc_draw_line: (%i,%i) to (%i,%i)\n",
	    x0 >> 16, y0 >> 16, x1 >> 16, y1 >> 16);

    /* Convert from PGC fixed-point to device coordinates */
    x0 >>= 16;
    y0 >>= 16;
    pgc_ito_raster(dev, &x0, &y0);

    x1 >>= 16;
    y1 >>= 16;
    pgc_ito_raster(dev, &x1, &y1);

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


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

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

    for (x = x0; x <= x1; x++) {
	if (dev->fill_pattern[y0 & 0x0F] & mask)
		pgc_plot(dev, 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_t *dev, unsigned corners, int32_t *x, int32_t *y)
{
    double *nodex;
    double *dx;
    double *dy;
    unsigned n, nodes, i, j;
    double ymin, ymax, ypos;

    pgc_log("PGC: fill_polygon(%i corners)\n", corners);

    if (!x || !y || (corners < 2))
	return;	/* Degenerate polygon */

    nodex = (double *)malloc(corners * sizeof(double));
    dx    = (double *)malloc(corners * sizeof(double));
    dy    = (double *)malloc(corners * sizeof(double));
    if (!nodex || !dx || !dy) {
	if (nodex) {
		free(nodex);
		nodex = NULL;
	}
	if (dx) {
		free(dx);
		dx = NULL;
	}
	if (dy) {
		free(dy);
		dy = NULL;
	}
	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);

	/* And fill between them. */
	for (i = 0; i < nodes; i += 2) {
		int16_t x1 = (int16_t)nodex[i], x2 = (int16_t)nodex[i + 1],
			y1 = (int16_t)ypos, y2 = (int16_t)ypos;
		pgc_sto_raster(dev, &x1, &y1);
		pgc_sto_raster(dev, &x2, &y2);
		pgc_fill_line_r(dev, x1, x2, y1);
	}
    }

    free(nodex);
    free(dx);
    free(dy);
}


/* Draw a filled ellipse (using PGC fixed-point coordinates). */
void
pgc_draw_ellipse(pgc_t *dev, 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 = dev->y / 65536.0;
    double x0 = dev->x / 65536.0;
    double ypos, xpos;
    double x1;
    double xlast = 0.0;
    int16_t linemask = dev->line_pattern;

    pgc_log("PGC: ellipse(color=%i drawmode=%i fill=%i)\n",
	    dev->color, dev->draw_mode, dev->fill_mode);

    pgc_dto_raster(dev, &x0, &y0);

    for (ypos = 0; ypos <= h; ypos++) {
	if (ypos == 0) {
		if (dev->fill_mode)
			pgc_fill_line_r(dev, (uint16_t)(x0 - w),
					(uint16_t)(x0 + w), (uint16_t)y0);
		if (linemask & 0x8000) {
			pgc_plot(dev, (uint16_t)(x0 + w), (uint16_t)y0);
			pgc_plot(dev, (uint16_t)(x0 - w), (uint16_t)y0);
			linemask = (linemask << 1) | 1;
		} else
			linemask = linemask << 1;

		xlast = w;
	} else {
		x1 = sqrt((h * h) - (ypos * ypos)) * w / h;

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

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

		xlast = x1;
	}
    }
}


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

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

    pgc_draw_ellipse(dev, x, y);
}


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

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

    pgc_log("PGC: POLY (%i)\n", count);
    for (n = 0; n < count; n++) {
	if (! pgc_param_coord(dev, &x[n])) return;
	if (! pgc_param_coord(dev, &y[n])) return;
    }
}


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

#ifdef ENABLE_PGC_LOG
    pgc_log("PCG: parse_poly\n");
#endif
    if (! pgc_param_byte(dev, &count)) return 0;
    pgc_log("PCG: parse_poly: count=%02x\n", count);

    if (! pgc_cl_append(cl, count)) {
	pgc_error(dev, PGC_ERROR_OVERFLOW);
	return 0;
    }
    pgc_log("PCG: parse_poly: parse %i coords\n", 2 * count);

    return pgc_parse_coords(dev, cl, 2 * count);
}


/* Handle the DISPLAY command. */
static void
hndl_display(pgc_t *dev)
{
    uint8_t param;

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

    pgc_log("PGC: DISPLAY(%i)\n", param);

    if (param > 1)
	pgc_error(dev, PGC_ERROR_RANGE);
    else
	pgc_setdisplay(dev, param);
}


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

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

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

    /* In ASCII mode, what is written is a stream of bytes. */
    if (dev->ascii_mode) {
	while (col1 <= col2) {
		if (! pgc_param_byte(dev, &v1)) return;
		pgc_write_pixel(dev, col1, row, v1);
		col1++;
	}

	return;
    }

    /* In hex mode, it's RLE compressed. */
    while (col1 <= col2) {
	if (! pgc_param_byte(dev, &v1)) return;

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

		v1++;
		while (col1 <= col2 && v1 != 0)	{
			pgc_write_pixel(dev, col1, row, v2);
			col1++;
			v1--;
		}
	}
    }
}


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


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


/* Handle LUTINT (select palette). */
static void
hndl_lutint(pgc_t *dev)
{
    uint8_t param;

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

    init_lut(dev, param);
}


/* Handle LUTRD (read palette register). */
static void
hndl_lutrd(pgc_t *dev)
{
    uint8_t param;
    uint32_t col;

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

    col = dev->palette[param];

    pgc_result_byte(dev, (col >> 20) & 0x0f);
    pgc_result_byte(dev, (col >> 12) & 0x0f);
    pgc_result_byte(dev, (col >>  4) & 0x0f);
}


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

    for (n = 0; n < 4; n++) {
	if (! pgc_param_byte(dev, &param[n])) return;
	if (n > 0 && param[n] > 15) {
		pgc_error(dev, PGC_ERROR_RANGE);
		param[n] &= 0x0f;
	}
    }

    dev->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_t *dev)
{
    uint8_t param;
    uint32_t col;

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

    col = dev->palette[param];

    pgc_result_byte(dev, (col >> 16) & 0xff);
    pgc_result_byte(dev, (col >>  8) & 0xff);
    pgc_result_byte(dev, col & 0xff);
}


void
pgc_hndl_lut8(pgc_t *dev)
{
    uint8_t param[4];
    int n;

    for (n = 0; n < 4; n++)
	if (! pgc_param_byte(dev, &param[n])) return;

    dev->palette[param[0]] = makecol((param[1]), (param[2]), (param[3]));
}


/* Handle AREAPT (set 16x16 fill pattern). */
static void
hndl_areapt(pgc_t *dev)
{
    int16_t pat[16];
    int n;

    for (n = 0; n < 16; n++)
	if (! pgc_param_word(dev, &pat[n])) return;

    pgc_log("PGC: AREAPT(%04x %04x %04x %04x...)\n",
	    pat[0] & 0xffff, pat[1] & 0xffff, pat[2] & 0xffff, pat[3] & 0xffff);

    memcpy(dev->fill_pattern, pat, sizeof(dev->fill_pattern));
}


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


/* Handle CX (select hex mode). */
static void
hndl_cx(pgc_t *dev)
{
    dev->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_t *dev)
{
    uint8_t param;

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

    if (param == 'A')
	dev->ascii_mode = 1;

    if (param == 'X')
	dev->ascii_mode = 0;
}


/* RESETF resets the PGC. */
static void
hndl_resetf(pgc_t *dev)
{
    pgc_reset(dev);
}


/* TJUST sets text justify settings. */
static void
hndl_tjust(pgc_t *dev)
{
    uint8_t param[2];

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

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


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

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

    pgc_log("PGC: TSIZE %i\n", param);
    pgc->tsize = 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_t *dev)
{
    int16_t x1, x2, y1, y2;

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

    pgc_log("PGC: VWPORT %i,%i,%i,%i\n", x1,x2,y1,y2);
    dev->vp_x1 = x1;
    dev->vp_x2 = x2;
    dev->vp_y1 = y1;
    dev->vp_y2 = y2;
}


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

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

    pgc_log("PGC: WINDOW %i,%i,%i,%i\n", x1,x2,y1,y2);
    dev->win_x1 = x1;
    dev->win_x2 = x2;
    dev->win_y1 = y1;
    dev->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_cmd_t pgc_commands[] = {
    { "AREAPT",	0xe7, hndl_areapt,	pgc_parse_words,	16	},
    { "AP",     0xe7, hndl_areapt,	pgc_parse_words,	16	},
    { "~~~~~~", 0x43, hndl_c,		NULL,			0	},
    { "CA",     0xd2, hndl_ca,		NULL,			0	},
    { "CLBEG",  0x70, hndl_clbeg,	NULL,			0	},
    { "CB",     0x70, hndl_clbeg,	NULL,			0	},
    { "CLDEL",  0x74, hndl_cldel,	pgc_parse_bytes,	1	},
    { "CD",     0x74, hndl_cldel,	pgc_parse_bytes,	1	},
    { "CLEND",  0x71, hndl_clend,	NULL,			0	},
    { "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,	NULL,			0	},
    { "CL",     0x73, hndl_cloop,	NULL,			0	},
    { "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,		NULL,			0	},
    { "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,	NULL,			0	},
    { "IW",     0xd9, hndl_imagew,	NULL,			0	},
    { "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,		0	},
    { "P",      0x30, hndl_poly,	parse_poly,		0	},
    { "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,		NULL,			0	}
};


/* When the wake timer expires, that's when the drawing thread is actually
 * woken */
static void
wake_timer(void *priv)
{
    pgc_t *dev = (pgc_t *)priv;

#ifdef ENABLE_PGC_LOG
    pgc_log("PGC: woke up\n");
#endif

    thread_set_event(dev->pgc_wake_thread);
}


/*
 * The PGC drawing thread main loop.
 *
 * Read in commands and execute them ad infinitum.
 */
static void
pgc_thread(void *priv)
{
    pgc_t *dev = (pgc_t *)priv;
    const pgc_cmd_t *cmd;

#ifdef ENABLE_PGC_LOG
    pgc_log("PGC: thread begins\n");
#endif

    for (;;) {
	if (! parse_command(dev, &cmd)) {
		/* Are we shutting down? */
		if (dev->stopped) {
#ifdef ENABLE_PGC_LOG
			pgc_log("PGC: Thread stopping...\n");
#endif
			dev->stopped = 0;
			break;
		}

		/* Nope, just a reset. */
		continue;
	}

	pgc_log("PGC: Command: [%02x] '%s' found = %i\n",
		dev->hex_command, dev->asc_command, (cmd != NULL));

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

#ifdef ENABLE_PGC_LOG
    pgc_log("PGC: thread stopped\n");
#endif
}


/* Parameter passed is not a number: abort. */
static int
err_digit(pgc_t *dev)
{
    uint8_t asc;

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

    pgc_error(dev, PGC_ERROR_DIGIT);

    return 0;
}


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

    if (! dev->ascii_mode)
	return output_byte(dev, val);

    if (dev->result_count) {
	if (! output_byte(dev, ',')) return 0;
    }
    sprintf(buf, "%i", val);
    dev->result_count++;

    return output_string(dev, buf);
}


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

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

    if (dev->result_count) {
	if (! output_byte(dev, ',')) return 0;
    }
    sprintf(buf, "%i", val);
    dev->result_count++;

    return output_string(dev, buf);
}


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

    return 1;
}


/* Initialize RAM and registers to default values. */
void
pgc_reset(pgc_t *dev)
{
    int n;

    memset(dev->mapram, 0x00, sizeof(dev->mapram));

    /* The 'CGA disable' jumper is not currently implemented. */
    dev->mapram[0x30b] = dev->cga_enabled = 1;
    dev->mapram[0x30c] = dev->cga_enabled;
    dev->mapram[0x30d] = dev->cga_enabled;

    dev->mapram[0x3f8] = 0x03;		/* minor version */
    dev->mapram[0x3f9] = 0x01;		/* minor version */
    dev->mapram[0x3fb] = 0xa5;		/* } */
    dev->mapram[0x3fc] = 0x5a;		/* PGC self-test passed */
    dev->mapram[0x3fd] = 0x55;		/* } */
    dev->mapram[0x3fe] = 0x5a;		/* } */

    dev->ascii_mode = 1;		/* start off in ASCII mode */
    dev->line_pattern = 0xffff;
    memset(dev->fill_pattern, 0xff, sizeof(dev->fill_pattern));
    dev->color = 0xff;
    dev->tjust_h = 1;
    dev->tjust_v = 1;

    /* Reset panning. */
    dev->pan_x = 0;
    dev->pan_y = 0;

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

    /* Empty command lists. */
    for (n = 0; n < 256; n++) {
	dev->clist[n].wrptr = 0;
	dev->clist[n].rdptr = 0;
	dev->clist[n].repeat = 0;
	dev->clist[n].chain = 0;
    }
    dev->clcur = NULL;

    /* Select CGA display. */
    dev->cga_selected = -1;
    pgc_setdisplay(dev, dev->cga_enabled);

    /* Default palette is 0. */
    init_lut(dev, 0);
    hndl_lutsav(dev);
}


/* Switch between CGA mode (DISPLAY 1) and native mode (DISPLAY 0). */
void
pgc_setdisplay(pgc_t *dev, int cga)
{
    pgc_log("PGC: setdisplay(%i): cga_selected=%i cga_enabled=%i\n",
	    cga, dev->cga_selected, dev->cga_enabled);

    if (dev->cga_selected != (dev->cga_enabled && cga)) {
	dev->cga_selected = (dev->cga_enabled && cga);
	dev->displine = 0;

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

	pgc_recalctimings(dev);
    }
}


/*
 * 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.
 */
void
pgc_wake(pgc_t *dev)
{
    if (!timer_is_enabled(&dev->wake_timer))
	timer_set_delay_u64(&dev->wake_timer, WAKE_DELAY);
}


/* Wait for more input data, or for output to drain. */
void
pgc_sleep(pgc_t *dev)
{
    pgc_log("PGC: sleeping on %i %i %i %i 0x%02x 0x%02x\n",
	    dev->stopped,
	    dev->waiting_input_fifo, dev->waiting_output_fifo,
	    dev->waiting_error_fifo, dev->mapram[0x300], dev->mapram[0x301]);

    /* Avoid entering waiting state. */
    if (dev->stopped) {
	dev->waiting_input_fifo = 0;
	dev->waiting_output_fifo = 0;
	return;
    }

    /* Race condition: If host wrote to the PGC during the that
     * won't be noticed */
    if (dev->waiting_input_fifo &&
	dev->mapram[0x300] != dev->mapram[0x301]) {
	dev->waiting_input_fifo = 0;
	return;
    }

    /* Same if they read. */
    if (dev->waiting_output_fifo &&
	dev->mapram[0x302] != (uint8_t)(dev->mapram[0x303] - 1)) {
	dev->waiting_output_fifo = 0;
	return;
    }

    thread_wait_event(dev->pgc_wake_thread, -1);
    thread_reset_event(dev->pgc_wake_thread);
}


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

    if (dev->clcur->rdptr < dev->clcur->wrptr)
	*val = dev->clcur->list[dev->clcur->rdptr++];
    else
	*val = 0;

    /* If we've reached the end, reset to the beginning and
     * (if repeating) run the repeat */
    if (dev->clcur->rdptr >= dev->clcur->wrptr) {
	dev->clcur->rdptr = 0;
	dev->clcur->repeat--;
	if (dev->clcur->repeat == 0)
		dev->clcur = dev->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_t *dev, uint8_t *val)
{
    int32_t c;

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

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

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

    c = (c >> 16);	/* drop fractional part */
    if (c > 255) {
	pgc_error(dev, 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_t *dev, int16_t *val)
{
    uint8_t lo, hi;
    int32_t c;

    if (dev->clcur) {
	if (! pgc_clist_byte(dev, &lo)) return 0;
	if (! pgc_clist_byte(dev, &hi)) return 0;
	*val = (((int16_t)hi) << 8) | lo;

	return 1;
    }

    if (! dev->ascii_mode) {
	if (! dev->inputbyte(dev, &lo)) return 0;
	if (! dev->inputbyte(dev, &hi)) return 0;
	*val = (((int16_t)hi) << 8) | lo;

	return 1;
    }

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

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

    return 1;
}


typedef enum {
    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_t *dev, 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 (dev->clcur) {
	for (n = 0; n < 4; n++)
		if (! pgc_clist_byte(dev, &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 (! dev->ascii_mode) {
	for (n = 0; n < 4; n++)
		if (! dev->inputbyte(dev, &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 (! dev->inputbyte(dev, &asc)) return 0;
	if (asc == '-') sign = -1;
    }	while (is_whitespace(asc));

    /* There had better be a digit next. */
    if (! isdigit(asc)) 	{
	pgc_error(dev, 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 (! dev->inputbyte(dev, &asc)) return 0;
				state = PS_FRACTION;
				continue;
			} else {
				pgc_error(dev, PGC_ERROR_MISSING);
				return err_digit(dev);
			}
			break;

		/* Scientific notation. */
		case 'd':
		case 'D':
		case 'e':
		case 'E':
			esign = 1;
			if (! dev->inputbyte(dev, &asc)) return 0;
			if (asc == '-') {
				sign = -1;
				if (! dev->inputbyte(dev, &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(dev, PGC_ERROR_MISSING);
				return err_digit(dev);
			}
			asc -= '0';	/* asc is digit */

			switch (state) {
				case PS_MAIN:
					integer = (integer * 10)+asc;
					if (integer & 0x8000) {
						/* Overflow */
						pgc_error(dev, 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 (! dev->inputbyte(dev, &asc)) return 0;
    } while (! is_whitespace(asc));

    res = (frac << 16) / dp;
    pgc_log("PGC: integer=%u frac=%u exponent=%u dp=%i res=0x%08lx\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;
}


/*
 * Add a byte to a command list.
 *
 * We allow command lists to be arbitrarily large.
 */
int
pgc_cl_append(pgc_cl_t *list, uint8_t v)
{
    uint8_t *buf;

    if (list->listmax == 0 || list->list == NULL) {
	list->list = (uint8_t *)malloc(4096);
	if (!list->list) {
#ifdef ENABLE_PGC_LOG
		pgc_log("PGC: out of memory initializing command list\n");
#endif
		return 0;
	}
	list->listmax = 4096;
    }

    while (list->wrptr >= list->listmax) {
	buf = (uint8_t *)realloc(list->list, 2 * list->listmax);
	if (!buf) {
#ifdef ENABLE_PGC_LOG
		pgc_log("PGC: out of memory growing command list\n");
#endif
		return 0;
	}
	list->list = buf;
	list->listmax *= 2;
    }

    list->list[list->wrptr++] = v;

    return 1;
}


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

    if (! param) {
	pgc_error(dev, PGC_ERROR_OVERFLOW);
	return 0;
    }

    for (n = 0; n < count; n++) {
	if (! pgc_param_byte(dev, &param[n])) {
		free(param);
		return 0;
	}

	if (! pgc_cl_append(cl, param[n])) {
		pgc_error(dev, 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_t *dev, pgc_cl_t *cl, int count)
{
    int16_t *param = (int16_t *)malloc(count * sizeof(int16_t));
    int n;

    if (! param) {
	pgc_error(dev, PGC_ERROR_OVERFLOW);
	return 0;
    }

    for (n = 0; n < count; n++) {
	if (! pgc_param_word(dev, &param[n])) {
		free(param);
		return 0;
	}

	if (!pgc_cl_append(cl, param[n] & 0xff) ||
	    !pgc_cl_append(cl, param[n] >> 8)) {
		pgc_error(dev, PGC_ERROR_OVERFLOW);
		free(param);
		return 0;
	}
    }

    free(param);

    return 1;
}


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

    if (! param) {
	pgc_error(dev, PGC_ERROR_OVERFLOW);
	return 0;
    }

    for (n = 0; n < count; n++) {
	if (! pgc_param_coord(dev, &param[n])) {
		free(param);
		return 0;
	}
    }

    /* Here is how the real PGC serializes 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++) {
	/* Serialize integer part. */
	if (!pgc_cl_append(cl, (param[n] >> 16) & 0xff) ||
	    !pgc_cl_append(cl, (param[n] >> 24) & 0xff) ||

	/* Serialize fraction part. */
	    !pgc_cl_append(cl, (param[n]      ) & 0xff) ||
	    !pgc_cl_append(cl, (param[n] >>  8) & 0xff)) {
		pgc_error(dev, PGC_ERROR_OVERFLOW);
		free(param);
		return 0;
	}
    }

    free(param);

    return 1;
}


/* Convert coordinates based on the current window / viewport to raster
 * coordinates. */
void
pgc_dto_raster(pgc_t *dev, double *x, double *y)
{
#ifdef ENABLE_PGC_LOG
    double x0 = *x, y0 = *y;
#endif

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

    pgc_log("PGC: coords to raster: (%f, %f) -> (%f, %f)\n", x0, y0, *x, *y);
}


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

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


void
pgc_ito_raster(pgc_t *dev, int32_t *x, int32_t *y)
{
    double xd = *x, yd = *y;

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


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

    /* Use a fixed 640x400 display. */
    disptime = dev->screenw + 11;
    _dispontime = dev->screenw * pixel_clock;
    _dispofftime = (disptime - dev->screenw) * pixel_clock;
    dev->dispontime = (uint64_t)(_dispontime);
    dev->dispofftime = (uint64_t)(_dispofftime);
}


/* Write to CGA registers are copied into the transfer memory buffer. */
void
pgc_out(uint16_t addr, uint8_t val, void *priv)
{
    pgc_t *dev = (pgc_t *)priv;

    pgc_log("PGC: out(%04x, %02x)\n", addr, val);

    switch(addr) {
	case 0x03d0:	/* CRTC Index register */
	case 0x03d2:
	case 0x03d4:
	case 0x03d6:
		dev->mapram[0x03d0] = val;
		break;

	case 0x03d1:	/* CRTC Data register */
	case 0x03d3:
	case 0x03d5:
	case 0x03d7:
		if (dev->mapram[0x03d0] < 18)
			dev->mapram[0x03e0 + dev->mapram[0x03d0]] = val;
		break;

	case 0x03d8:	/* CRTC Mode Control register */
		dev->mapram[0x03d8] = val;
		break;

	case 0x03d9:	/* CRTC Color Select register */
		dev->mapram[0x03d9] = val;
		break;
    }
}


/* Read back the CGA registers. */
uint8_t
pgc_in(uint16_t addr, void *priv)
{
    pgc_t *dev = (pgc_t *)priv;
    uint8_t ret = 0xff;

    switch(addr) {
	case 0x03d0:	/* CRTC Index register */
	case 0x03d2:
	case 0x03d4:
	case 0x03d6:
		ret = dev->mapram[0x03d0];
		break;

	case 0x03d1:	/* CRTC Data register */
	case 0x03d3:
	case 0x03d5:
	case 0x03d7:
		if (dev->mapram[0x03d0] < 18)
			ret = dev->mapram[0x03e0 + dev->mapram[0x03d0]];
		break;

	case 0x03d8:	/* CRTC Mode Control register */
		ret = dev->mapram[0x03d8];
		break;

	case 0x03d9:	/* CRTC Color Select register */
		ret = dev->mapram[0x03d9];
		break;

	case 0x03da:	/* CRTC Status register */
		ret = dev->mapram[0x03da];
		break;
    }

    pgc_log("PGC: in(%04x) = %02x\n", addr, ret);

    return ret;
}


/* Memory write to the transfer buffer. */
/* TODO: Check the CGA mapping repeat stuff. */
void
pgc_write(uint32_t addr, uint8_t val, void *priv)
{
    pgc_t *dev = (pgc_t *)priv;

    /*
     * 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 (dev->mapram[addr] != val) {
		dev->mapram[addr] = val;

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

			case 0x303:	/* output read pointer */
				if (dev->waiting_output_fifo &&
				    dev->mapram[0x302] != (uint8_t)(dev->mapram[0x303] - 1)) {
					dev->waiting_output_fifo = 0;
					pgc_wake(dev);
				}
				break;

			case 0x305:	/* error read pointer */
				if (dev->waiting_error_fifo &&
				    dev->mapram[0x304] != (uint8_t)(dev->mapram[0x305] - 1)) {
					dev->waiting_error_fifo = 0;
					pgc_wake(dev);
				}
				break;

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

			case 0x30c:	/* display type */
				pgc_setdisplay(priv, dev->mapram[0x30c]);
				dev->mapram[0x30d] = dev->mapram[0x30c];
				break;

			case 0x3ff:	/* reboot the PGC */
				pgc_wake(dev);
				break;
		}
	}
    }

    if (addr >= 0xb8000 && addr < 0xc0000 && dev->cga_selected) {
	addr &= 0x3fff;
	dev->cga_vram[addr] = val;
    }
}


/* TODO: Check the CGA mapping repeat stuff. */
uint8_t
pgc_read(uint32_t addr, void *priv)
{
    pgc_t *dev = (pgc_t *)priv;
    uint8_t ret = 0xff;

    if (addr >= 0xc6000 && addr < 0xc6800) {
	addr &= 0x7ff;
	ret = dev->mapram[addr];
    } else if (addr >= 0xb8000 && addr < 0xc0000 && dev->cga_selected) {
	addr &= 0x3fff;
	ret = dev->cga_vram[addr];
    }

    return ret;
}


/* Draw the display in CGA (640x400) text mode. */
void
pgc_cga_text(pgc_t *dev, int w)
{
    int x, c;
    uint8_t chr, attr;
    int drawcursor = 0;
    uint32_t cols[2];
    int pitch = (dev->mapram[0x3e9] + 1) * 2;
    uint16_t sc = (dev->displine & 0x0f) % pitch;
    uint16_t ma = (dev->mapram[0x3ed] | (dev->mapram[0x3ec] << 8)) & 0x3fff;
    uint16_t ca = (dev->mapram[0x3ef] | (dev->mapram[0x3ee] << 8)) & 0x3fff;
    uint8_t *addr;
    uint32_t val;
    int cw = (w == 80) ? 8 : 16;

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

    for (x = 0; x < w; x++) {
	chr  = *addr++;
	attr = *addr++;

	/* Cursor enabled? */
	if (ma == ca && (dev->cgablink & 8) &&
	    (dev->mapram[0x3ea] & 0x60) != 0x20) {
		drawcursor = ((dev->mapram[0x3ea] & 0x1f) <= (sc >> 1)) &&
			      ((dev->mapram[0x3eb] & 0x1f) >= (sc >> 1));
	} else
		drawcursor = 0;

	if (dev->mapram[0x3d8] & 0x20) {
		cols[1] = (attr & 15) + 16;
		cols[0] = ((attr >> 4) & 7) + 16;
		if ((dev->cgablink & 8) && (attr & 0x80) && !drawcursor)
			cols[1] = cols[0];
	} else {
		cols[1] = (attr & 15) + 16;
		cols[0] = (attr >> 4) + 16;
	}

	for (c = 0; c < cw; c++) {
		if (drawcursor)
			val = cols[(fontdatm[chr + dev->fontbase][sc] & (1 << (c ^ 7))) ? 1 : 0] ^ 0x0f;
		else
			val = cols[(fontdatm[chr + dev->fontbase][sc] & (1 << (c ^ 7))) ? 1 : 0];
		buffer32->line[dev->displine][(x * cw) + c] = val;
	}

	ma++;
    }
}


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

    cols[0] = (dev->mapram[0x3d9] & 15) + 16;
    col = ((dev->mapram[0x3d9] & 16) ? 8 : 0) + 16;

    /* From John Elliott's site:
       On a real CGA, if bit 2 of port 03D8h and bit 5 of port 03D9h are both set,
       the palette used in graphics modes is red/cyan/white. On a PGC, it's
       magenta/cyan/white. You still get red/cyan/white if bit 5 of port 03D9h is
       not set. This is a firmware issue rather than hardware. */
    if (dev->mapram[0x3d9] & 32) {
	cols[1] = col | 3;
	cols[2] = col | 5;
	cols[3] = col | 7;
    } else if (dev->mapram[0x3d8] & 4) {
	cols[1] = col | 3;
	cols[2] = col | 4;
	cols[3] = col | 7;
    } else {
	cols[1] = col | 2;
	cols[2] = col | 4;
	cols[3] = col | 6;
    }

    for (x = 0; x < 40; x++) {
	addr = &dev->cga_vram[(ma + 2 * x + 80 * (dev->displine >> 2) + 0x2000 * ((dev->displine >> 1) & 1)) & 0x3fff];
	dat = (addr[0] << 8) | addr[1];
	dev->ma++;
	for (c = 0; c < 8; c++) {
		buffer32->line[dev->displine][(x << 4) + (c << 1)] =
		buffer32->line[dev->displine][(x << 4) + (c << 1) + 1] = cols[dat >> 14];
		dat <<= 2;
	}
    }
}


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

    cols[0] = 16;
    cols[1] = (dev->mapram[0x3d9] & 15) + 16;

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


/* Draw the screen in CGA mode. */
void
pgc_cga_poll(pgc_t *dev)
{
    uint32_t cols[2];

    if (! dev->linepos) {
	timer_advance_u64(&dev->timer, dev->dispofftime);
	dev->mapram[0x03da] |= 1;
	dev->linepos = 1;

	if (dev->cgadispon) {
		if (dev->displine == 0)
			video_wait_for_buffer();

		if ((dev->mapram[0x03d8] & 0x12) == 0x12)
			pgc_cga_gfx80(dev);
		else if (dev->mapram[0x03d8] & 0x02)
			pgc_cga_gfx40(dev);
		else if (dev->mapram[0x03d8] & 0x01)
			pgc_cga_text(dev, 80);
		else
			pgc_cga_text(dev, 40);
	} else {
		cols[0] = ((dev->mapram[0x03d8] & 0x12) == 0x12) ? 0 : ((dev->mapram[0x03d9] & 15) + 16);
		hline(buffer32, 0, dev->displine, PGC_CGA_WIDTH, cols[0]);
	}

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

	if (dev->displine == PGC_CGA_HEIGHT) {
		if (PGC_CGA_WIDTH != xsize || PGC_CGA_HEIGHT != ysize) {
			xsize = PGC_CGA_WIDTH;
			ysize = PGC_CGA_HEIGHT;
			set_screen_size(xsize, ysize);

			if (video_force_resize_get())
				video_force_resize_set(0);
		}
		video_blit_memtoscreen_8(0, 0, 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 (dev->mapram[0x3d8] & 0x12) {
			case 0x12:
				video_bpp = 1;
				break;

			case 0x02:
				video_bpp = 2;
				break;

			default:
				video_bpp = 0;
				break;
		}
		dev->cgablink++;
	}
    }
}


/* Draw the screen in CGA or native mode. */
void
pgc_poll(void *priv)
{
    pgc_t *dev = (pgc_t *)priv;
    uint32_t x, y;

    if (dev->cga_selected) {
	pgc_cga_poll(dev);
	return;
    }

    /* Not CGA, so must be native mode. */
    if (! dev->linepos) {
	timer_advance_u64(&dev->timer, dev->dispofftime);
	dev->mapram[0x3da] |= 1;
	dev->linepos = 1;
	if (dev->cgadispon && (uint32_t)dev->displine < dev->maxh) {
		if (dev->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 = dev->displine - 2 * dev->pan_y;
		for (x = 0; x < dev->screenw; x++) {
			if (x + dev->pan_x < dev->maxw)
				buffer32->line[dev->displine][x] = dev->palette[dev->vram[y * dev->maxw + x]];
			else
				buffer32->line[dev->displine][x] = dev->palette[0];
		}
	} else {
		hline(buffer32, 0, dev->displine, dev->screenw, dev->palette[0]);
	}

	if (++dev->displine == dev->screenh) {
               	dev->mapram[0x3da] |= 8;
               	dev->cgadispon = 0;
	}

	if (dev->displine == dev->screenh + 32) {
               	dev->mapram[0x3da] &= ~8;
               	dev->cgadispon = 1;
		dev->displine = 0;
	}
    } else {
	if (dev->cgadispon)
		dev->mapram[0x3da] &= ~1;
	timer_advance_u64(&dev->timer, dev->dispontime);
	dev->linepos = 0;

	if (dev->displine == dev->screenh) {
		if (dev->screenw != xsize || dev->screenh != ysize) {
			xsize = dev->screenw;
			ysize = dev->screenh;
			set_screen_size(xsize, ysize);

			if (video_force_resize_get())
				video_force_resize_set(0);
		}
		video_blit_memtoscreen(0, 0, xsize, ysize);
		frames++;

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


void
pgc_speed_changed(void *priv)
{
    pgc_t *dev = (pgc_t *)priv;

    pgc_recalctimings(dev);
}


void
pgc_close_common(void *priv)
{
    pgc_t *dev = (pgc_t *)priv;

    /*
     * Close down the worker thread by setting a
     * flag, and then simulating a reset so it
     * stops reading data.
     */
#ifdef ENABLE_PGC_LOG
    pgc_log("PGC: telling thread to stop...\n");
#endif
    dev->stopped = 1;
    dev->mapram[0x3ff] = 1;
    if (dev->waiting_input_fifo || dev->waiting_output_fifo) {
	/* Do an immediate wake-up. */
	wake_timer(priv);
    }

    /* Wait for thread to stop. */
#ifdef ENABLE_PGC_LOG
    pgc_log("PGC: waiting for thread to stop...\n");
#endif
    // while (dev->stopped);
    thread_wait(dev->pgc_thread);
#ifdef ENABLE_PGC_LOG
    pgc_log("PGC: thread stopped, closing up.\n");
#endif

    if (dev->cga_vram)
       	free(dev->cga_vram);
    if (dev->vram)
       	free(dev->vram);
}


void
pgc_close(void *priv)
{
    pgc_t *dev = (pgc_t *)priv;

    pgc_close_common(priv);

    free(dev);
}


/*
 * Initialization 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_init(pgc_t *dev, int maxw, int maxh, int visw, int vish,
	      int (*inpbyte)(pgc_t *, uint8_t *), double npc)
{
    int i;

    /* Make it a 16k mapping at C4000 (will be C4000-C7FFF),
       because of the emulator's granularity - the original
       mapping will conflict with hard disk controller BIOS'es. */
    mem_mapping_add(&dev->mapping, 0xc4000, 16384,
		    pgc_read,NULL,NULL, pgc_write,NULL,NULL,
		    NULL, MEM_MAPPING_EXTERNAL, dev);
    mem_mapping_add(&dev->cga_mapping, 0xb8000, 32768,
		    pgc_read,NULL,NULL, pgc_write,NULL,NULL,
		    NULL, MEM_MAPPING_EXTERNAL, dev);

    io_sethandler(0x03d0, 16,
		  pgc_in,NULL,NULL, pgc_out,NULL,NULL, dev);

    dev->maxw = maxw;
    dev->maxh = maxh;
    dev->visw = visw;
    dev->vish = vish;

    dev->vram = (uint8_t *)malloc(maxw * maxh);
    memset(dev->vram, 0x00, maxw * maxh);
    dev->cga_vram = (uint8_t *)malloc(16384);
    memset(dev->cga_vram, 0x00, 16384);

    /* Create and initialize command lists. */
    dev->clist = (pgc_cl_t *)malloc(256 * sizeof(pgc_cl_t));
    memset(dev->clist, 0x00, 256 * sizeof(pgc_cl_t));
    for (i = 0; i < 256; i++) {
	dev->clist[i].list = NULL;
	dev->clist[i].listmax = 0;
	dev->clist[i].wrptr = 0;
	dev->clist[i].rdptr = 0;
	dev->clist[i].repeat = 0;
	dev->clist[i].chain = NULL;
    }
    dev->clcur = NULL;
    dev->native_pixel_clock = npc;

    pgc_reset(dev);

    dev->inputbyte = inpbyte;
    dev->master = dev->commands = pgc_commands;
    dev->pgc_wake_thread = thread_create_event();
    dev->pgc_thread = thread_create(pgc_thread, dev);

    timer_add(&dev->timer, pgc_poll, dev, 1);

    timer_add(&dev->wake_timer, wake_timer, dev, 0);
}


static void *
pgc_standalone_init(const device_t *info)
{
    pgc_t *dev;

    dev = (pgc_t *)malloc(sizeof(pgc_t));
    memset(dev, 0x00, sizeof(pgc_t));
    dev->type = info->local;

    /* Framebuffer and screen are both 640x480. */
    pgc_init(dev, 640, 480, 640, 480, input_byte, 25175000.0);

    video_inform(VIDEO_FLAG_TYPE_CGA, &timing_pgc);

    return(dev);
}


const device_t pgc_device = {
    "PGC",
    "pgc",
    DEVICE_ISA, 0,
    pgc_standalone_init,
    pgc_close,
    NULL,
    { NULL },
    pgc_speed_changed,
    NULL,
    NULL
};
