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