/*PC200/PPC video emulation. */
#include <stdlib.h>
#include "ibm.h"
#include "device.h"
#include "io.h"
#include "mem.h"
#include "nmi.h"
#include "timer.h"
#include "video.h"
#include "vid_cga.h"
#include "vid_mda.h"
#include "vid_pc200.h"

/* #define PC200LOG(x) pclog x */
#define PC200LOG(x)

/* Display type */
#define PC200_CGA 0  /* CGA monitor */
#define PC200_MDA 1  /* MDA monitor */
#define PC200_TV 2   /* Television */
#define PC200_LCDC 3 /* PPC512 LCD as CGA*/
#define PC200_LCDM 4 /* PPC512 LCD as MDA*/

int pc200_is_mda;

static uint32_t blue, green;

static uint32_t lcdcols[256][2][2];

typedef struct pc200_t {
        mem_mapping_t cga_mapping;
        mem_mapping_t mda_mapping;

        cga_t cga;
        mda_t mda;

        pc_timer_t timer;

        uint8_t emulation;   /* Which display are we emulating? */
        uint8_t dipswitches; /* DIP switches 1-3 */
        uint8_t crtc_index;  /* CRTC index readback
                              * Bit 7: CGA control port written
                              * Bit 6: Operation control port written
                              * Bit 5: CRTC register written
                              * Bits 0-4: Last CRTC register selected */
        uint8_t operation_ctrl;
        uint8_t reg_3df;
} pc200_t;

static uint8_t crtcmask[32] = {0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f, 0xf3, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff,
                               0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

/* LCD colour mappings
 *
 * 0 => solid green
 * 1 => blue on green
 * 2 => green on blue
 * 3 => solid blue
 */
static unsigned char mapping1[256] = {
        /*	0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F */
        /*00*/ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        /*10*/ 2, 0, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1,
        /*20*/ 2, 2, 0, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1,
        /*30*/ 2, 2, 2, 0, 2, 2, 1, 1, 2, 2, 2, 1, 2, 2, 1, 1,
        /*40*/ 2, 2, 1, 1, 0, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1,
        /*50*/ 2, 2, 1, 1, 2, 0, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1,
        /*60*/ 2, 2, 2, 2, 2, 2, 0, 1, 2, 2, 2, 2, 2, 2, 1, 1,
        /*70*/ 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 1,
        /*80*/ 2, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
        /*90*/ 2, 2, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1,
        /*A0*/ 2, 2, 2, 1, 2, 2, 1, 1, 2, 2, 0, 1, 2, 2, 1, 1,
        /*B0*/ 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 0, 2, 2, 1, 1,
        /*C0*/ 2, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, 0, 1, 1, 1,
        /*D0*/ 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 0, 1, 1,
        /*E0*/ 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 0, 1,
        /*F0*/ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0,
};

static unsigned char mapping2[256] = {
        /*	0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F */
        /*00*/ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        /*10*/ 1, 3, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2,
        /*20*/ 1, 1, 3, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2,
        /*30*/ 1, 1, 1, 3, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 2, 2,
        /*40*/ 1, 1, 2, 2, 3, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2,
        /*50*/ 1, 1, 2, 2, 1, 3, 2, 2, 1, 1, 2, 2, 1, 2, 2, 2,
        /*60*/ 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, 1, 1, 1, 1, 2, 2,
        /*70*/ 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 2,
        /*80*/ 2, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
        /*90*/ 1, 1, 2, 2, 2, 2, 2, 2, 1, 3, 2, 2, 2, 2, 2, 2,
        /*A0*/ 1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 3, 2, 1, 1, 2, 2,
        /*B0*/ 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 3, 1, 1, 2, 2,
        /*C0*/ 1, 1, 2, 2, 1, 2, 2, 2, 1, 1, 2, 2, 3, 2, 2, 2,
        /*D0*/ 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 3, 2, 2,
        /*E0*/ 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 3, 2,
        /*F0*/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3,
};

static void set_lcd_cols(uint8_t mode_reg) {
        unsigned char *mapping = (mode_reg & 0x80) ? mapping2 : mapping1;
        int c;

        for (c = 0; c < 256; c++) {
                switch (mapping[c]) {
                case 0:
                        lcdcols[c][0][0] = lcdcols[c][1][0] = green;
                        lcdcols[c][0][1] = lcdcols[c][1][1] = green;
                        break;

                case 1:
                        lcdcols[c][0][0] = lcdcols[c][1][0] = lcdcols[c][1][1] = green;
                        lcdcols[c][0][1] = blue;
                        break;

                case 2:
                        lcdcols[c][0][0] = lcdcols[c][1][0] = lcdcols[c][1][1] = blue;
                        lcdcols[c][0][1] = green;
                        break;

                case 3:
                        lcdcols[c][0][0] = lcdcols[c][1][0] = blue;
                        lcdcols[c][0][1] = lcdcols[c][1][1] = blue;
                        break;
                }
        }
}

void pc200_out(uint16_t addr, uint8_t val, void *p) {
        pc200_t *pc200 = (pc200_t *)p;
        cga_t *cga = &pc200->cga;
        mda_t *mda = &pc200->mda;
        uint8_t old;

        PC200LOG(("pc200_out(0x%04x), %02x\n", addr, val));

        /* MDA writes ============================================================== */
        switch (addr) {
        case 0x3b1:
        case 0x3b3:
        case 0x3b5:
        case 0x3b7:

                /* Writes banned to CRTC registers 0-11? */
                if (!(pc200->operation_ctrl & 0x40) && mda->crtcreg <= 11) {
                        pc200->crtc_index = 0x20 | (mda->crtcreg & 0x1f);
                        if (pc200->operation_ctrl & 0x80) {
                                PC200LOG(("NMI raised from CRTC register access: reg=0x%02x val=0x%02x nmi_mask=%d\n",
                                          mda->crtcreg & 0x1F, val, nmi_mask));
                                nmi = 1;
                        }
                        pc200->reg_3df = val;
                        return;
                }
                PC200LOG(("CRTM[%02x]:=%02x\n", mda->crtcreg, val));
                old = mda->crtc[mda->crtcreg];
                mda->crtc[mda->crtcreg] = val & crtcmask[mda->crtcreg];
                if (old != val) {
                        if (mda->crtcreg < 0xe || mda->crtcreg > 0x10) {
                                fullchange = changeframecount;
                                mda_recalctimings(mda);
                        }
                }
                return;

        case 0x3b8:
                old = mda->ctrl;
                mda->ctrl = val;
                if ((mda->ctrl ^ old) & 3) {
                        mda_recalctimings(mda);
                }
                pc200->crtc_index &= 0x1F;
                pc200->crtc_index |= 0x80;
                if (pc200->operation_ctrl & 0x80) {
                        PC200LOG(("NMI raised from mode control access: val=0x%02x nmi_mask=%d\n", val, nmi_mask));
                        nmi = 1;
                }
                return;

                /* CGA writes ============================================================== */
        case 0x3d1:
        case 0x3d3:
        case 0x3d5:
        case 0x3d7:

                /* Writes banned to CRTC registers 0-11? */
                if (!(pc200->operation_ctrl & 0x40) && cga->crtcreg <= 11) {
                        pc200->crtc_index = 0x20 | (cga->crtcreg & 0x1f);
                        if (pc200->operation_ctrl & 0x80) {
                                PC200LOG(("NMI raised from CRTC register access: reg=0x%02x val=0x%02x nmi_mask=%d\n",
                                          cga->crtcreg & 0x1F, val, nmi_mask));
                                nmi = 1;
                        }
                        pc200->reg_3df = val;
                        return;
                }
                PC200LOG(("CRTC[%02x]:=%02x\n", cga->crtcreg, val));
                old = cga->crtc[cga->crtcreg];
                cga->crtc[cga->crtcreg] = val & crtcmask[cga->crtcreg];
                if (old != val) {
                        if (cga->crtcreg < 0xe || cga->crtcreg > 0x10) {
                                fullchange = changeframecount;
                                cga_recalctimings(cga);
                        }
                }
                return;

        case 0x3d8:
                old = cga->cgamode;
                cga->cgamode = val;
                if ((cga->cgamode ^ old) & 3) {
                        cga_recalctimings(cga);
                }
                pc200->crtc_index &= 0x1F;
                pc200->crtc_index |= 0x80;
                if (pc200->operation_ctrl & 0x80) {
                        PC200LOG(("NMI raised from mode control access: val=0x%02x nmi_mask=%d\n", val, nmi_mask));
                        nmi = 1;
                } else
                        set_lcd_cols(val);
                return;

                /* PC200 control port writes ============================================== */
        case 0x3de:
                pc200->crtc_index = 0x1f;
                /* NMI only seems to be triggered if the value being written has the high
                 * bit set (enable NMI). So it only protects writes to this port if you
                 * let it? */
                if (val & 0x80) {
                        PC200LOG(("NMI raised from operation control access: val=0x%02x nmi_mask=%d\n", val, nmi_mask));
                        pc200->operation_ctrl = val;
                        pc200->crtc_index |= 0x40;
                        nmi = 1;
                        return;
                }
                timer_disable(&pc200->cga.timer);
                timer_disable(&pc200->mda.timer);
                timer_disable(&pc200->timer);
                pc200->operation_ctrl = val;
                /* Bits 0 and 1 control emulation and output mode */
                if (val & 1) /* Monitor */
                {
                        pc200->emulation = (val & 2) ? PC200_MDA : PC200_CGA;
                } else if (romset == ROM_PPC512) {
                        pc200->emulation = (val & 2) ? PC200_LCDM : PC200_LCDC;
                } else {
                        pc200->emulation = PC200_TV;
                }
                PC200LOG(("Video emulation set to %d\n", pc200->emulation));
                if (pc200->emulation == PC200_CGA || pc200->emulation == PC200_TV)
                        timer_advance_u64(&pc200->cga.timer, 1);
                else if (pc200->emulation == PC200_MDA)
                        timer_advance_u64(&pc200->mda.timer, 1);
                else
                        timer_advance_u64(&pc200->timer, 1);

                /* Bit 2 disables the IDA. We don't support dynamic enabling
                 * and disabling of the IDA (instead, PCEM disconnects the
                 * IDA from the bus altogether) so don't implement this */

                /* Enable the appropriate memory ranges depending whether
                 * the IDA is configured as MDA or CGA */
                if (pc200->emulation == PC200_MDA || pc200->emulation == PC200_LCDM) {
                        mem_mapping_disable(&pc200->cga_mapping);
                        mem_mapping_enable(&pc200->mda_mapping);
                } else {
                        mem_mapping_disable(&pc200->mda_mapping);
                        mem_mapping_enable(&pc200->cga_mapping);
                }
                return;
        }
        if (addr >= 0x3D0 && addr <= 0x3DF) {
                cga_out(addr, val, cga);
        }
        if (addr >= 0x3B0 && addr <= 0x3BB) {
                mda_out(addr, val, mda);
        }
}

uint8_t pc200_in(uint16_t addr, void *p) {
        pc200_t *pc200 = (pc200_t *)p;
        cga_t *cga = &pc200->cga;
        mda_t *mda = &pc200->mda;
        uint8_t temp;

        PC200LOG(("pc200_in(0x%04x)\n", addr));
        switch (addr) {
        case 0x3B8:
                PC200LOG(("  = %02x\n", mda->ctrl));
                return mda->ctrl;

        case 0x3D8:
                PC200LOG(("  = %02x\n", cga->cgamode));
                return cga->cgamode;

        case 0x3DD:
                temp = pc200->crtc_index;  /* Read NMI reason */
                pc200->crtc_index &= 0x1f; /* Reset NMI reason */
                nmi = 0;                   /* And reset NMI flag */
                PC200LOG(("  = %02x\n", temp));
                return temp;

        case 0x3DE:
                PC200LOG(("  = %02x\n", ((pc200->operation_ctrl & 0xc7) | pc200->dipswitches)));
                return ((pc200->operation_ctrl & 0xc7) | pc200->dipswitches);

        case 0x3DF:
                return pc200->reg_3df;
        }
        if (addr >= 0x3D0 && addr <= 0x3DF) {
                return cga_in(addr, cga);
        }
        if (addr >= 0x3B0 && addr <= 0x3BB) {
                return mda_in(addr, mda);
        }
        return 0xFF;
}

void lcd_draw_char_80(pc200_t *pc200, uint32_t *buffer, uint8_t chr, uint8_t attr, int drawcursor, int blink, int sc, int mode160,
                      uint8_t control) {
        int c;
        uint8_t bits = fontdat[chr + pc200->cga.fontbase][sc];
        uint8_t bright = 0;
        uint16_t mask;

        if (attr & 8) /* bright */
        {
                /* The brightness algorithm appears to be: replace any bit sequence 011
                 * with 001 (assuming an extra 0 to the left of the byte).
                 */
                bright = bits;
                for (c = 0, mask = 0x100; c < 7; c++, mask >>= 1) {
                        if (((bits & mask) == 0) && ((bits & (mask >> 1)) != 0) && ((bits & (mask >> 2)) != 0)) {
                                bright &= ~(mask >> 1);
                        }
                }
                bits = bright;
        }

        if (drawcursor)
                bits ^= 0xFF;

        for (c = 0, mask = 0x80; c < 8; c++, mask >>= 1) {
                if (mode160)
                        buffer[c] = (attr & mask) ? blue : green;
                else if (control & 0x20) /* blinking */
                        buffer[c] = lcdcols[attr & 0x7F][blink][(bits & mask) ? 1 : 0];
                else
                        buffer[c] = lcdcols[attr][blink][(bits & mask) ? 1 : 0];
        }
}

void lcd_draw_char_40(pc200_t *pc200, uint32_t *buffer, uint8_t chr, uint8_t attr, int drawcursor, int blink, int sc,
                      uint8_t control) {
        int c;
        uint8_t bits = fontdat[chr + pc200->cga.fontbase][sc];
        uint8_t mask = 0x80;

        if (attr & 8) /* bright */
        {
                bits = bits & (bits >> 1);
        }
        if (drawcursor)
                bits ^= 0xFF;

        for (c = 0; c < 8; c++, mask >>= 1) {
                if (control & 0x20) {
                        buffer[c * 2] = buffer[c * 2 + 1] = lcdcols[attr & 0x7F][blink][(bits & mask) ? 1 : 0];
                } else {
                        buffer[c * 2] = buffer[c * 2 + 1] = lcdcols[attr][blink][(bits & mask) ? 1 : 0];
                }
        }
}

void lcdm_poll(pc200_t *pc200) {
        mda_t *mda = &pc200->mda;
        uint16_t ca = (mda->crtc[15] | (mda->crtc[14] << 8)) & 0x3fff;
        int drawcursor;
        int x;
        int oldvc;
        uint8_t chr, attr;
        int oldsc;
        int blink;
        if (!mda->linepos) {
                timer_advance_u64(&pc200->timer, mda->dispofftime);
                mda->stat |= 1;
                mda->linepos = 1;
                oldsc = mda->sc;
                if ((mda->crtc[8] & 3) == 3)
                        mda->sc = (mda->sc << 1) & 7;
                if (mda->dispon) {
                        if (mda->displine < mda->firstline) {
                                mda->firstline = mda->displine;
                                video_wait_for_buffer();
                        }
                        mda->lastline = mda->displine;
                        for (x = 0; x < mda->crtc[1]; x++) {
                                chr = mda->vram[(mda->ma << 1) & 0xfff];
                                attr = mda->vram[((mda->ma << 1) + 1) & 0xfff];
                                drawcursor = ((mda->ma == ca) && mda->con && mda->cursoron);
                                blink = ((mda->blink & 16) && (mda->ctrl & 0x20) && (attr & 0x80) && !drawcursor);

                                lcd_draw_char_80(pc200, &((uint32_t *)(buffer32->line[mda->displine]))[x * 8], chr, attr,
                                                 drawcursor, blink, mda->sc, 0, mda->ctrl);
                                mda->ma++;
                        }
                }
                mda->sc = oldsc;
                if (mda->vc == mda->crtc[7] && !mda->sc) {
                        mda->stat |= 8;
                        //                        printf("VSYNC on %i %i\n",vc,sc);
                }
                mda->displine++;
                if (mda->displine >= 500)
                        mda->displine = 0;
        } else {
                timer_advance_u64(&pc200->timer, mda->dispontime);
                if (mda->dispon)
                        mda->stat &= ~1;
                mda->linepos = 0;
                if (mda->vsynctime) {
                        mda->vsynctime--;
                        if (!mda->vsynctime) {
                                mda->stat &= ~8;
                                //                                printf("VSYNC off %i %i\n",vc,sc);
                        }
                }
                if (mda->sc == (mda->crtc[11] & 31) || ((mda->crtc[8] & 3) == 3 && mda->sc == ((mda->crtc[11] & 31) >> 1))) {
                        mda->con = 0;
                        mda->coff = 1;
                }
                if (mda->vadj) {
                        mda->sc++;
                        mda->sc &= 31;
                        mda->ma = mda->maback;
                        mda->vadj--;
                        if (!mda->vadj) {
                                mda->dispon = 1;
                                mda->ma = mda->maback = (mda->crtc[13] | (mda->crtc[12] << 8)) & 0x3fff;
                                mda->sc = 0;
                        }
                } else if (mda->sc == mda->crtc[9] || ((mda->crtc[8] & 3) == 3 && mda->sc == (mda->crtc[9] >> 1))) {
                        mda->maback = mda->ma;
                        mda->sc = 0;
                        oldvc = mda->vc;
                        mda->vc++;
                        mda->vc &= 127;
                        if (mda->vc == mda->crtc[6])
                                mda->dispon = 0;
                        if (oldvc == mda->crtc[4]) {
                                //                                printf("Display over at %i\n",displine);
                                mda->vc = 0;
                                mda->vadj = mda->crtc[5];
                                if (!mda->vadj)
                                        mda->dispon = 1;
                                if (!mda->vadj)
                                        mda->ma = mda->maback = (mda->crtc[13] | (mda->crtc[12] << 8)) & 0x3fff;
                                if ((mda->crtc[10] & 0x60) == 0x20)
                                        mda->cursoron = 0;
                                else
                                        mda->cursoron = mda->blink & 16;
                        }
                        if (mda->vc == mda->crtc[7]) {
                                mda->dispon = 0;
                                mda->displine = 0;
                                mda->vsynctime = 16;
                                if (mda->crtc[7]) {
                                        //                                        printf("Lastline %i Firstline %i
                                        //                                        %i\n",lastline,firstline,lastline-firstline);
                                        x = mda->crtc[1] * 8;
                                        mda->lastline++;
                                        if (x != xsize || (mda->lastline - mda->firstline) != ysize) {
                                                xsize = x;
                                                ysize = mda->lastline - mda->firstline;
                                                //                                                printf("Resize to %i,%i - R1
                                                //                                                %i\n",xsize,ysize,crtcm[1]);
                                                if (xsize < 64)
                                                        xsize = 656;
                                                if (ysize < 32)
                                                        ysize = 200;
                                                updatewindowsize(xsize, ysize << 1);
                                        }

                                        video_blit_memtoscreen(0, mda->firstline, 0, ysize, xsize, ysize);

                                        frames++;
                                        video_res_x = mda->crtc[1];
                                        video_res_y = mda->crtc[6];
                                        video_bpp = 0;
                                }
                                mda->firstline = 1000;
                                mda->lastline = 0;
                                mda->blink++;
                        }
                } else {
                        mda->sc++;
                        mda->sc &= 31;
                        mda->ma = mda->maback;
                }
                if ((mda->sc == (mda->crtc[10] & 31) || ((mda->crtc[8] & 3) == 3 && mda->sc == ((mda->crtc[10] & 31) >> 1)))) {
                        mda->con = 1;
                        //                        printf("Cursor on - %02X %02X %02X\n",crtcm[8],crtcm[10],crtcm[11]);
                }
        }
}

void lcdc_poll(pc200_t *pc200) {
        cga_t *cga = &pc200->cga;
        int drawcursor;
        int x, c;
        int oldvc;
        uint8_t chr, attr;
        uint16_t dat;
        int oldsc;
        uint16_t ca;
        int blink;

        ca = (cga->crtc[15] | (cga->crtc[14] << 8)) & 0x3fff;

        if (!cga->linepos) {
                timer_advance_u64(&pc200->timer, cga->dispofftime);
                cga->cgastat |= 1;
                cga->linepos = 1;
                oldsc = cga->sc;
                if ((cga->crtc[8] & 3) == 3)
                        cga->sc = ((cga->sc << 1) + cga->oddeven) & 7;
                if (cga->cgadispon) {
                        if (cga->displine < cga->firstline) {
                                cga->firstline = cga->displine;
                                video_wait_for_buffer();
                                //                                printf("Firstline %i\n",firstline);
                        }
                        cga->lastline = cga->displine;

                        if (cga->cgamode & 1) {
                                for (x = 0; x < cga->crtc[1]; x++) {
                                        chr = cga->charbuffer[x << 1];
                                        attr = cga->charbuffer[(x << 1) + 1];
                                        drawcursor = ((cga->ma == ca) && cga->con && cga->cursoron);
                                        blink = ((cga->cgablink & 16) && (cga->cgamode & 0x20) && (attr & 0x80) && !drawcursor);
                                        lcd_draw_char_80(pc200, &((uint32_t *)(buffer32->line[cga->displine]))[x * 8], chr, attr,
                                                         drawcursor, blink, cga->sc, cga->cgamode & 0x40, cga->cgamode);
                                        cga->ma++;
                                }
                        } else if (!(cga->cgamode & 2)) {
                                for (x = 0; x < cga->crtc[1]; x++) {
                                        chr = cga->vram[((cga->ma << 1) & 0x3fff)];
                                        attr = cga->vram[(((cga->ma << 1) + 1) & 0x3fff)];
                                        drawcursor = ((cga->ma == ca) && cga->con && cga->cursoron);
                                        blink = ((cga->cgablink & 16) && (cga->cgamode & 0x20) && (attr & 0x80) && !drawcursor);
                                        lcd_draw_char_40(pc200, &((uint32_t *)(buffer32->line[cga->displine]))[x * 16], chr, attr,
                                                         drawcursor, blink, cga->sc, cga->cgamode);
                                        cga->ma++;
                                }
                        } else /* Graphics mode */
                        {
                                for (x = 0; x < cga->crtc[1]; x++) {
                                        dat = (cga->vram[((cga->ma << 1) & 0x1fff) + ((cga->sc & 1) * 0x2000)] << 8) |
                                              cga->vram[((cga->ma << 1) & 0x1fff) + ((cga->sc & 1) * 0x2000) + 1];
                                        cga->ma++;
                                        for (c = 0; c < 16; c++) {
                                                ((uint32_t *)buffer32->line[cga->displine])[(x << 4) + c] =
                                                        (dat & 0x8000) ? blue : green;
                                                dat <<= 1;
                                        }
                                }
                        }
                } else {
                        if (cga->cgamode & 1)
                                hline(buffer32, 0, cga->displine, (cga->crtc[1] << 3), green);
                        else
                                hline(buffer32, 0, cga->displine, (cga->crtc[1] << 4), green);
                }

                if (cga->cgamode & 1)
                        x = (cga->crtc[1] << 3);
                else
                        x = (cga->crtc[1] << 4);

                cga->sc = oldsc;
                if (cga->vc == cga->crtc[7] && !cga->sc)
                        cga->cgastat |= 8;
                cga->displine++;
                if (cga->displine >= 360)
                        cga->displine = 0;
        } else {
                timer_advance_u64(&pc200->timer, cga->dispontime);
                cga->linepos = 0;
                if (cga->vsynctime) {
                        cga->vsynctime--;
                        if (!cga->vsynctime)
                                cga->cgastat &= ~8;
                }
                if (cga->sc == (cga->crtc[11] & 31) || ((cga->crtc[8] & 3) == 3 && cga->sc == ((cga->crtc[11] & 31) >> 1))) {
                        cga->con = 0;
                        cga->coff = 1;
                }
                if ((cga->crtc[8] & 3) == 3 && cga->sc == (cga->crtc[9] >> 1))
                        cga->maback = cga->ma;
                if (cga->vadj) {
                        cga->sc++;
                        cga->sc &= 31;
                        cga->ma = cga->maback;
                        cga->vadj--;
                        if (!cga->vadj) {
                                cga->cgadispon = 1;
                                cga->ma = cga->maback = (cga->crtc[13] | (cga->crtc[12] << 8)) & 0x3fff;
                                cga->sc = 0;
                        }
                } else if (cga->sc == cga->crtc[9]) {
                        cga->maback = cga->ma;
                        cga->sc = 0;
                        oldvc = cga->vc;
                        cga->vc++;
                        cga->vc &= 127;

                        if (cga->vc == cga->crtc[6])
                                cga->cgadispon = 0;

                        if (oldvc == cga->crtc[4]) {
                                cga->vc = 0;
                                cga->vadj = cga->crtc[5];
                                if (!cga->vadj)
                                        cga->cgadispon = 1;
                                if (!cga->vadj)
                                        cga->ma = cga->maback = (cga->crtc[13] | (cga->crtc[12] << 8)) & 0x3fff;
                                if ((cga->crtc[10] & 0x60) == 0x20)
                                        cga->cursoron = 0;
                                else
                                        cga->cursoron = cga->cgablink & 8;
                        }

                        if (cga->vc == cga->crtc[7]) {
                                cga->cgadispon = 0;
                                cga->displine = 0;
                                cga->vsynctime = 16;
                                if (cga->crtc[7]) {
                                        if (cga->cgamode & 1)
                                                x = (cga->crtc[1] << 3);
                                        else
                                                x = (cga->crtc[1] << 4);
                                        cga->lastline++;
                                        if (x != xsize || (cga->lastline - cga->firstline) != ysize) {
                                                xsize = x;
                                                ysize = cga->lastline - cga->firstline;
                                                if (xsize < 64)
                                                        xsize = 656;
                                                if (ysize < 32)
                                                        ysize = 200;
                                                updatewindowsize(xsize, (ysize << 1));
                                        }

                                        video_blit_memtoscreen(0, cga->firstline, 0, (cga->lastline - cga->firstline), xsize,
                                                               (cga->lastline - cga->firstline));
                                        frames++;

                                        video_res_x = xsize - 16;
                                        video_res_y = ysize;
                                        if (cga->cgamode & 1) {
                                                video_res_x /= 8;
                                                video_res_y /= cga->crtc[9] + 1;
                                                video_bpp = 0;
                                        } else if (!(cga->cgamode & 2)) {
                                                video_res_x /= 16;
                                                video_res_y /= cga->crtc[9] + 1;
                                                video_bpp = 0;
                                        } else if (!(cga->cgamode & 16)) {
                                                video_res_x /= 2;
                                                video_bpp = 2;
                                        } else {
                                                video_bpp = 1;
                                        }
                                }
                                cga->firstline = 1000;
                                cga->lastline = 0;
                                cga->cgablink++;
                                cga->oddeven ^= 1;
                        }
                } else {
                        cga->sc++;
                        cga->sc &= 31;
                        cga->ma = cga->maback;
                }
                if (cga->cgadispon)
                        cga->cgastat &= ~1;
                if ((cga->sc == (cga->crtc[10] & 31) || ((cga->crtc[8] & 3) == 3 && cga->sc == ((cga->crtc[10] & 31) >> 1))))
                        cga->con = 1;
                if (cga->cgadispon && (cga->cgamode & 1)) {
                        for (x = 0; x < (cga->crtc[1] << 1); x++)
                                cga->charbuffer[x] = cga->vram[(((cga->ma << 1) + x) & 0x3fff)];
                }
        }
}

void pc200_poll(void *p) {
        pc200_t *pc200 = (pc200_t *)p;

        switch (pc200->emulation) {
                //		case PC200_CGA:
                //		case PC200_TV:
                //		pc200->cga.vidtime = pc200->vidtime;
                //		cga_poll(&pc200->cga);
                //		pc200->vidtime = pc200->cga.vidtime;
                //		return;

                //		case PC200_MDA:
                //		pc200->mda.vidtime = pc200->vidtime;
                //		mda_poll(&pc200->mda);
                //		pc200->vidtime = pc200->mda.vidtime;
                //		return;

        case PC200_LCDM:
                lcdm_poll(pc200);
                return;
        case PC200_LCDC:
                lcdc_poll(pc200);
                return;
        }
}

void *pc200_init() {
        int display_type, contrast;

        pc200_t *pc200 = malloc(sizeof(pc200_t));

        memset(pc200, 0, sizeof(pc200_t));

        pc200->emulation = device_get_config_int("video_emulation");
        display_type = device_get_config_int("display_type");
        contrast = device_get_config_int("contrast");

        /* Default to CGA */
        pc200->dipswitches = 0x10;

        /* DIP switches for PC200. Switches 2,3 give video emulation.
         * Switch 1 is 'swap floppy drives' (not implemented) */
        if (romset == ROM_PC200)
                switch (pc200->emulation) {
                case PC200_CGA:
                        pc200->dipswitches = 0x10;
                        break;
                case PC200_MDA:
                        pc200->dipswitches = 0x30;
                        break;
                case PC200_TV:
                        pc200->dipswitches = 0x00;
                        break;
                        /* The other combination is 'IDA disabled' (0x20) - see
                         * amstrad.c */
                }
        /* DIP switches for PPC512. Switch 1 is CRT/LCD. Switch 2
         * is MDA / CGA. Switch 3 disables IDA, not implemented. */
        else
                switch (pc200->emulation) {
                case PC200_CGA:
                        pc200->dipswitches = 0x08;
                        break;
                case PC200_MDA:
                        pc200->dipswitches = 0x18;
                        break;
                case PC200_LCDC:
                        pc200->dipswitches = 0x00;
                        break;
                case PC200_LCDM:
                        pc200->dipswitches = 0x10;
                        break;
                }
        /* Flag whether the card is behaving like a CGA or an MDA, so that
         * DIP switches are reported correctly */
        pc200_is_mda = (pc200->emulation == PC200_MDA || pc200->emulation == PC200_LCDM);
        PC200LOG(("pc200: DIP switches = %02x\n", pc200->dipswitches));

        pc200->cga.vram = pc200->mda.vram = malloc(0x4000);
        cga_init(&pc200->cga);
        mda_init(&pc200->mda);

        /* Attribute 8 is white on black (on a real MDA it's black on black) */
        mda_setcol(0x08, 0, 1, 15);
        mda_setcol(0x88, 0, 1, 15);
        /* Attribute 64 is black on black (on a real MDA it's white on black) */
        mda_setcol(0x40, 0, 1, 0);
        mda_setcol(0xC0, 0, 1, 0);

        pc200->cga.fontbase = (device_get_config_int("codepage") & 3) * 256;

        //        pclog("pc200=%p pc200->timer=%p pc200->cga=%p pc200->mda=%p pc200->mda->timer=%p\n", pc200, &pc200->timer,
        //        &pc200->cga, &pc200->mda, &pc200->mda->timer);
        timer_add(&pc200->timer, pc200_poll, pc200, 1);
        mem_mapping_add(&pc200->mda_mapping, 0xb0000, 0x08000, mda_read, NULL, NULL, mda_write, NULL, NULL, NULL, 0, &pc200->mda);
        mem_mapping_add(&pc200->cga_mapping, 0xb8000, 0x08000, cga_read, NULL, NULL, cga_write, NULL, NULL, NULL, 0, &pc200->cga);
        io_sethandler(0x03d0, 0x0010, pc200_in, NULL, NULL, pc200_out, NULL, NULL, pc200);
        io_sethandler(0x03b0, 0x000c, pc200_in, NULL, NULL, pc200_out, NULL, NULL, pc200);

        green = makecol(0x1C, 0x71, 0x31);
        blue = makecol(0x0f, 0x21, 0x3f);
        cgapal_rebuild(display_type, contrast);
        set_lcd_cols(0);

        timer_disable(&pc200->cga.timer);
        timer_disable(&pc200->mda.timer);
        timer_disable(&pc200->timer);
        if (pc200->emulation == PC200_CGA || pc200->emulation == PC200_TV)
                timer_enable(&pc200->cga.timer);
        else if (pc200->emulation == PC200_MDA)
                timer_enable(&pc200->mda.timer);
        else
                timer_enable(&pc200->timer);

        return pc200;
}

void pc200_close(void *p) {
        pc200_t *pc200 = (pc200_t *)p;

        free(pc200->cga.vram);
        free(pc200);
}

void pc200_speed_changed(void *p) {
        pc200_t *pc200 = (pc200_t *)p;

        cga_recalctimings(&pc200->cga);
        mda_recalctimings(&pc200->mda);
}

device_config_t pc200_config[] = {{
                                          .name = "video_emulation",
                                          .description = "Display type",
                                          .type = CONFIG_SELECTION,
                                          .selection = {{
                                                                .description = "CGA monitor",
                                                                .value = PC200_CGA,
                                                        },
                                                        {
                                                                .description = "MDA monitor",
                                                                .value = PC200_MDA,
                                                        },
                                                        {
                                                                .description = "Television",
                                                                .value = PC200_TV,
                                                        },
                                                        {.description = ""}},
                                          .default_int = PC200_CGA,
                                  },
                                  {.name = "display_type",
                                   .description = "Monitor type",
                                   .type = CONFIG_SELECTION,
                                   .selection = {{.description = "RGB", .value = DISPLAY_RGB},
                                                 {.description = "RGB (no brown)", .value = DISPLAY_RGB_NO_BROWN},
                                                 {.description = "Green Monochrome", .value = DISPLAY_GREEN},
                                                 {.description = "Amber Monochrome", .value = DISPLAY_AMBER},
                                                 {.description = "White Monochrome", .value = DISPLAY_WHITE},
                                                 {.description = ""}},
                                   .default_int = DISPLAY_RGB},
                                  {.name = "codepage",
                                   .description = "Hardware font",
                                   .type = CONFIG_SELECTION,
                                   .selection = {{.description = "US English", .value = 3},
                                                 {.description = "Portugese", .value = 2},
                                                 {.description = "Norwegian", .value = 1},
                                                 {.description = "Greek", .value = 0},
                                                 {.description = ""}},
                                   .default_int = 3},
                                  {.type = -1}};

device_config_t ppc512_config[] = {{
                                           .name = "video_emulation",
                                           .description = "Display type",
                                           .type = CONFIG_SELECTION,
                                           .selection = {{
                                                                 .description = "CGA monitor",
                                                                 .value = PC200_CGA,
                                                         },
                                                         {
                                                                 .description = "MDA monitor",
                                                                 .value = PC200_MDA,
                                                         },
                                                         {
                                                                 .description = "LCD (CGA mode)",
                                                                 .value = PC200_LCDC,
                                                         },
                                                         {
                                                                 .description = "LCD (MDA mode)",
                                                                 .value = PC200_LCDM,
                                                         },
                                                         {.description = ""}},
                                           .default_int = PC200_LCDC,
                                   },
                                   {.name = "display_type",
                                    .description = "External monitor",
                                    .type = CONFIG_SELECTION,
                                    .selection = {{.description = "RGB", .value = DISPLAY_RGB},
                                                  {.description = "RGB (no brown)", .value = DISPLAY_RGB_NO_BROWN},
                                                  {.description = "Green Monochrome", .value = DISPLAY_GREEN},
                                                  {.description = "Amber Monochrome", .value = DISPLAY_AMBER},
                                                  {.description = "White Monochrome", .value = DISPLAY_WHITE},
                                                  {.description = ""}},
                                    .default_int = DISPLAY_RGB},
                                   {.name = "codepage",
                                    .description = "Hardware font",
                                    .type = CONFIG_SELECTION,
                                    .selection = {{.description = "US English", .value = 3},
                                                  {.description = "Portugese", .value = 2},
                                                  {.description = "Norwegian", .value = 1},
                                                  {.description = "Greek", .value = 0},
                                                  {.description = ""}},
                                    .default_int = 3},
                                   {.type = -1}};

device_t pc200_device = {"Amstrad PC200 (video)", 0,    pc200_init, pc200_close, NULL,
                         pc200_speed_changed,     NULL, NULL,       pc200_config};

device_t ppc512_device = {"Amstrad PPC512 (video)", 0,    pc200_init, pc200_close,  NULL,
                          pc200_speed_changed,      NULL, NULL,       ppc512_config};
