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

