/*Hercules InColor emulation*/

#include <stdlib.h>
#include "ibm.h"
#include "device.h"
#include "mem.h"
#include "timer.h"
#include "video.h"
#include "vid_incolor.h"


/* extended CRTC registers */

#define INCOLOR_CRTC_XMODE   20 /* xMode register */
#define INCOLOR_CRTC_UNDER   21	/* Underline */
#define INCOLOR_CRTC_OVER    22 /* Overstrike */
#define INCOLOR_CRTC_EXCEPT  23 /* Exception */
#define INCOLOR_CRTC_MASK    24 /* Plane display mask & write mask */
#define INCOLOR_CRTC_RWCTRL  25 /* Read/write control */
#define INCOLOR_CRTC_RWCOL   26 /* Read/write colour */
#define INCOLOR_CRTC_PROTECT 27 /* Latch protect */
#define INCOLOR_CRTC_PALETTE 28 /* Palette */

/* character width */
#define INCOLOR_CW    ((incolor->crtc[INCOLOR_CRTC_XMODE] & INCOLOR_XMODE_90COL) ? 8 : 9)

/* mode control register */
#define INCOLOR_CTRL_GRAPH   0x02
#define INCOLOR_CTRL_ENABLE  0x08
#define INCOLOR_CTRL_BLINK   0x20
#define INCOLOR_CTRL_PAGE1   0x80

/* CRTC status register */
#define INCOLOR_STATUS_HSYNC 0x01		/* horizontal sync */
#define INCOLOR_STATUS_LIGHT 0x02
#define INCOLOR_STATUS_VIDEO 0x08
#define INCOLOR_STATUS_ID    0x50		/* Card identification */
#define INCOLOR_STATUS_VSYNC 0x80		/* -vertical sync */

/* configuration switch register */
#define INCOLOR_CTRL2_GRAPH 0x01
#define INCOLOR_CTRL2_PAGE1 0x02

/* extended mode register */
#define INCOLOR_XMODE_RAMFONT 0x01
#define INCOLOR_XMODE_90COL   0x02


/* Read/write control */
#define INCOLOR_RWCTRL_WRMODE   0x30
#define INCOLOR_RWCTRL_POLARITY 0x40

/* exception register */
#define INCOLOR_EXCEPT_CURSOR  0x0F		/* Cursor colour */
#define INCOLOR_EXCEPT_PALETTE 0x10		/* Enable palette register */
#define INCOLOR_EXCEPT_ALTATTR 0x20		/* Use alternate attributes */



/* Default palette */
static unsigned char defpal[16] = 
{
	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
	0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
};

static uint32_t incolor_rgb[64];

/* Mapping of inks to RGB */
static unsigned char init_rgb[64][3] =
{
				// rgbRGB
        { 0x00, 0x00, 0x00 },	// 000000
        { 0x00, 0x00, 0xaa },	// 000001
        { 0x00, 0xaa, 0x00 },	// 000010
        { 0x00, 0xaa, 0xaa },	// 000011
        { 0xaa, 0x00, 0x00 },	// 000100
        { 0xaa, 0x00, 0xaa },	// 000101
        { 0xaa, 0xaa, 0x00 },	// 000110
        { 0xaa, 0xaa, 0xaa },	// 000111
        { 0x00, 0x00, 0x55 },	// 001000
        { 0x00, 0x00, 0xff },	// 001001
        { 0x00, 0xaa, 0x55 },	// 001010
        { 0x00, 0xaa, 0xff },	// 001011
        { 0xaa, 0x00, 0x55 },	// 001100
        { 0xaa, 0x00, 0xff },	// 001101
        { 0xaa, 0xaa, 0x55 },	// 001110
        { 0xaa, 0xaa, 0xff },	// 001111
        { 0x00, 0x55, 0x00 },	// 010000
        { 0x00, 0x55, 0xaa },	// 010001
        { 0x00, 0xff, 0x00 },	// 010010
        { 0x00, 0xff, 0xaa },	// 010011
        { 0xaa, 0x55, 0x00 },	// 010100
        { 0xaa, 0x55, 0xaa },	// 010101
        { 0xaa, 0xff, 0x00 },	// 010110
        { 0xaa, 0xff, 0xaa },	// 010111
        { 0x00, 0x55, 0x55 },	// 011000
        { 0x00, 0x55, 0xff },	// 011001
        { 0x00, 0xff, 0x55 },	// 011010
        { 0x00, 0xff, 0xff },	// 011011
        { 0xaa, 0x55, 0x55 },	// 011100
        { 0xaa, 0x55, 0xff },	// 011101
        { 0xaa, 0xff, 0x55 },	// 011110
        { 0xaa, 0xff, 0xff },	// 011111
        { 0x55, 0x00, 0x00 },	// 100000
        { 0x55, 0x00, 0xaa },	// 100001
        { 0x55, 0xaa, 0x00 },	// 100010
        { 0x55, 0xaa, 0xaa },	// 100011
        { 0xff, 0x00, 0x00 },	// 100100
        { 0xff, 0x00, 0xaa },	// 100101
        { 0xff, 0xaa, 0x00 },	// 100110
        { 0xff, 0xaa, 0xaa },	// 100111
        { 0x55, 0x00, 0x55 },	// 101000
        { 0x55, 0x00, 0xff },	// 101001
        { 0x55, 0xaa, 0x55 },	// 101010
        { 0x55, 0xaa, 0xff },	// 101011
        { 0xff, 0x00, 0x55 },	// 101100
        { 0xff, 0x00, 0xff },	// 101101
        { 0xff, 0xaa, 0x55 },	// 101110
        { 0xff, 0xaa, 0xff },	// 101111
        { 0x55, 0x55, 0x00 },	// 110000
        { 0x55, 0x55, 0xaa },	// 110001
        { 0x55, 0xff, 0x00 },	// 110010
        { 0x55, 0xff, 0xaa },	// 110011
        { 0xff, 0x55, 0x00 },	// 110100
        { 0xff, 0x55, 0xaa },	// 110101
        { 0xff, 0xff, 0x00 },	// 110110
        { 0xff, 0xff, 0xaa },	// 110111
        { 0x55, 0x55, 0x55 },	// 111000
        { 0x55, 0x55, 0xff },	// 111001
        { 0x55, 0xff, 0x55 },	// 111010
        { 0x55, 0xff, 0xff },	// 111011
        { 0xff, 0x55, 0x55 },	// 111100
        { 0xff, 0x55, 0xff },	// 111101
        { 0xff, 0xff, 0x55 },	// 111110
        { 0xff, 0xff, 0xff },	// 111111
};



typedef struct incolor_t
{
        mem_mapping_t mapping;
        
        uint8_t crtc[32];
        int crtcreg;

        uint8_t ctrl, ctrl2, stat;

        int dispontime, dispofftime;
        int64_t vidtime;
        
        int firstline, lastline;

        int linepos, displine;
        int vc, sc;
        uint16_t ma, maback;
        int con, coff, cursoron;
        int dispon, blink;
        int vsynctime, vadj;

	uint8_t palette[16];	/* EGA-style 16 -> 64 palette registers */
	uint8_t palette_idx;	/* Palette write index */
	uint8_t latch[4];	/* Memory read/write latches */
        uint8_t *vram;
} incolor_t;

void incolor_recalctimings(incolor_t *incolor);
void incolor_write(uint32_t addr, uint8_t val, void *p);
uint8_t incolor_read(uint32_t addr, void *p);


void incolor_out(uint16_t addr, uint8_t val, void *p)
{
        incolor_t *incolor = (incolor_t *)p;
/*        pclog("InColor out %04X %02X\n",addr,val); */
        switch (addr)
        {
                case 0x3b0: case 0x3b2: case 0x3b4: case 0x3b6:
                incolor->crtcreg = val & 31;
                return;
                case 0x3b1: case 0x3b3: case 0x3b5: case 0x3b7:
		if (incolor->crtcreg > 28) return;
		/* Palette load register */
		if (incolor->crtcreg == INCOLOR_CRTC_PALETTE)	
		{
			incolor->palette[incolor->palette_idx % 16] = val;
			++incolor->palette_idx;
		} 
                incolor->crtc[incolor->crtcreg] = val;
                if (incolor->crtc[10] == 6 && incolor->crtc[11] == 7) /*Fix for Generic Turbo XT BIOS, which sets up cursor registers wrong*/
                {
                        incolor->crtc[10] = 0xb;
                        incolor->crtc[11] = 0xc;
                }
                incolor_recalctimings(incolor);
                return;
                case 0x3b8:
                incolor->ctrl = val;
                return;
                case 0x3bf:
                incolor->ctrl2 = val;
                if (val & 2)
                        mem_mapping_set_addr(&incolor->mapping, 0xb0000, 0x10000);
                else
                        mem_mapping_set_addr(&incolor->mapping, 0xb0000, 0x08000);
                return;
        }
}

uint8_t incolor_in(uint16_t addr, void *p)
{
        incolor_t *incolor = (incolor_t *)p;
/*        pclog("InColor in %04X %02X %04X:%04X %04X\n",addr,(incolor->stat & 0xF) | ((incolor->stat & 8) << 4),CS,pc,CX); */
        switch (addr)
        {
                case 0x3b0: case 0x3b2: case 0x3b4: case 0x3b6:
                return incolor->crtcreg;
                case 0x3b1: case 0x3b3: case 0x3b5: case 0x3b7:
		if (incolor->crtcreg > 28) return 0xff;
		incolor->palette_idx = 0;	/* Read resets the palette index */
                return incolor->crtc[incolor->crtcreg];
                case 0x3ba:
		/* 0x50: InColor card identity */
                return (incolor->stat & 0xf) | ((incolor->stat & 8) << 4) | 0x50;
        }
        return 0xff;
}

void incolor_write(uint32_t addr, uint8_t val, void *p)
{
        incolor_t *incolor = (incolor_t *)p;

	int plane;

	unsigned char wmask = incolor->crtc[INCOLOR_CRTC_MASK];
	unsigned char wmode = incolor->crtc[INCOLOR_CRTC_RWCTRL] & INCOLOR_RWCTRL_WRMODE;
	unsigned char fg    = incolor->crtc[INCOLOR_CRTC_RWCOL] & 0x0F;
	unsigned char bg    = (incolor->crtc[INCOLOR_CRTC_RWCOL] >> 4)&0x0F;
	unsigned char w;
	unsigned char vmask;	/* Mask of bit within byte */
	unsigned char pmask;	/* Mask of plane within colour value */
	unsigned char latch;

        egawrites++;

	/* Horrible hack, I know, but it's the only way to fix the 440FX BIOS filling the VRAM with garbage until Tom fixes the memory emulation. */
	if ((cs == 0xE0000) && (cpu_state.pc == 0xBF2F) && (romset == ROM_440FX))  return;
	if ((cs == 0xE0000) && (cpu_state.pc == 0xBF77) && (romset == ROM_440FX))  return;

	addr &= 0xFFFF;

	/* In text mode, writes to the bottom 16k always touch all 4 planes */
	if (!(incolor->ctrl & INCOLOR_CTRL_GRAPH) && addr < 0x4000)
	{
		incolor->vram[addr] = val;
		return;
	}

	/* There are four write modes:
	 * 0: 1 => foreground,    0 => background
	 * 1: 1 => foreground,    0 => source latch
 	 * 2: 1 => source latch,  0 => background
	 * 3: 1 => source latch,  0 => ~source latch 
	 */
	pmask = 1;
	for (plane = 0; plane < 4; pmask <<= 1, wmask >>= 1, addr += 0x10000,
		plane++)
	{
		if (wmask & 0x10) /* Ignore writes to selected plane */
		{
			continue;
		}
		latch = incolor->latch[plane];
		for (vmask = 0x80; vmask != 0; vmask >>= 1) 
		{
			switch (wmode) 
			{
				case 0x00:
					if (val & vmask) w = (fg & pmask);
					else		 w = (bg & pmask);
					break;
				case 0x10:
					if (val & vmask) w = (fg    & pmask);
					else		 w = (latch & vmask);
					break;
				case 0x20:
					if (val & vmask) w = (latch & vmask);
					else		 w = (bg    & pmask);
					break;
				case 0x30:
					if (val & vmask) w = (latch    & vmask);
					else		 w = ((~latch) & vmask);
					break;
			}
		/* w is nonzero to write a 1, zero to write a 0 */
			if (w)	incolor->vram[addr] |= vmask;
			else	incolor->vram[addr] &= ~vmask; 	
		}
	}
}

uint8_t incolor_read(uint32_t addr, void *p)
{
        incolor_t *incolor = (incolor_t *)p;
	unsigned plane;
	unsigned char lp    = incolor->crtc[INCOLOR_CRTC_PROTECT];
	unsigned char value = 0;
	unsigned char dc;	/* "don't care" register */
	unsigned char bg;	/* background colour */
	unsigned char fg;
	unsigned char mask, pmask;

        egareads++;

	addr &= 0xFFFF;
	/* Read the four planes into latches */
	for (plane = 0; plane < 4; plane++, addr += 0x10000) 
	{
		incolor->latch[plane] &= lp;
		incolor->latch[plane] |= (incolor->vram[addr] & ~lp);
	}
	addr &= 0xFFFF;
	/* In text mode, reads from the bottom 16k assume all planes have
	 * the same contents */
	if (!(incolor->ctrl & INCOLOR_CTRL_GRAPH) && addr < 0x4000)
	{
		return incolor->latch[0];
	}
	/* For each pixel, work out if its colour matches the background */
	for (mask = 0x80; mask != 0; mask >>= 1) 
	{
		fg = 0;
    		dc = incolor->crtc[INCOLOR_CRTC_RWCTRL] & 0x0F;
    		bg = (incolor->crtc[INCOLOR_CRTC_RWCOL] >> 4) & 0x0F;
		for (plane = 0, pmask = 1; plane < 4; plane++, pmask <<= 1) 
		{
			if (dc & pmask) 
			{
				fg |= (bg & pmask); 
			} 
			else if (incolor->latch[plane] & mask) 
			{
				fg |= pmask;
			}
		}		
		if (bg == fg) value |= mask;
	}	
	if (incolor->crtc[INCOLOR_CRTC_RWCTRL] & INCOLOR_RWCTRL_POLARITY) 
	{
		value = ~value;
	}
	return value;
}



void incolor_recalctimings(incolor_t *incolor)
{
        double disptime;
	double _dispontime, _dispofftime;
        disptime = incolor->crtc[0] + 1;
        _dispontime  = incolor->crtc[1];
        _dispofftime = disptime - _dispontime;
        _dispontime  *= MDACONST;
        _dispofftime *= MDACONST;
	incolor->dispontime  = (int)(_dispontime  * (1 << TIMER_SHIFT));
	incolor->dispofftime = (int)(_dispofftime * (1 << TIMER_SHIFT));
}


static void incolor_draw_char_rom(incolor_t *incolor, int x, uint8_t chr, uint8_t attr)
{
	unsigned            i;
	int                 elg, blk;
	unsigned            ull;
	unsigned            val;
	unsigned	    ifg, ibg;
	const unsigned char *fnt;
	uint32_t	    fg, bg;
	int		    cw = INCOLOR_CW;

	blk = 0;
	if (incolor->ctrl & INCOLOR_CTRL_BLINK) 
	{
		if (attr & 0x80) 
		{
			blk = (incolor->blink & 16);
		}
		attr &= 0x7f;
	}

	if (incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_ALTATTR) 
	{
		/* MDA-compatible attributes */
		ibg = 0;
		ifg = 7;
		if ((attr & 0x77) == 0x70)	 /* Invert */
		{
			ifg = 0;
			ibg = 7;
		}	
		if (attr & 8) 
		{
			ifg |= 8;	/* High intensity FG */
		}
		if (attr & 0x80) 
		{
			ibg |= 8;	/* High intensity BG */
		}
		if ((attr & 0x77) == 0)	/* Blank */
		{
			ifg = ibg;
		}
		ull = ((attr & 0x07) == 1) ? 13 : 0xffff;
	} 
	else 
	{
		/* CGA-compatible attributes */
		ull = 0xffff;
		ifg = attr & 0x0F;
		ibg = (attr >> 4) & 0x0F;
	}
	if (incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_PALETTE) 
	{
		fg = incolor_rgb[incolor->palette[ifg]];
		bg = incolor_rgb[incolor->palette[ibg]];
	} 
	else 
	{
		fg = incolor_rgb[defpal[ifg]];
		bg = incolor_rgb[defpal[ibg]];
	}

	/* ELG set to stretch 8px character to 9px */
	elg = ((chr >= 0xc0) && (chr <= 0xdf));

	fnt = &(fontdatm[chr][incolor->sc]);

	if (blk)
	{
		val = 0x000;	/* Blinking, draw all background */
	}
	else if (incolor->sc == ull) 
	{
		val = 0x1ff;	/* Underscore, draw all foreground */
	}
	else 
	{
		val = fnt[0] << 1;
	
		if (elg) 
		{
			val |= (val >> 1) & 1;
		}
	}
	for (i = 0; i < cw; i++) 
	{
		((uint32_t *)buffer32->line[incolor->displine])[x * cw + i] = (val & 0x100) ? fg : bg;
		val = val << 1;
	}
}


static void incolor_draw_char_ram4(incolor_t *incolor, int x, uint8_t chr, uint8_t attr)
{
	unsigned            i;
	int                 elg, blk;
	unsigned            ull;
	unsigned            val[4];
	unsigned	    ifg, ibg, cfg, pmask, plane;
	const unsigned char *fnt;
	uint32_t	    fg;
	int		    cw = INCOLOR_CW;
	int                 blink   = incolor->ctrl & INCOLOR_CTRL_BLINK;
	int                 altattr = incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_ALTATTR;
	int                 palette = incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_PALETTE;

	blk = 0;
	if (blink)
	{
		if (attr & 0x80) 
		{
			blk = (incolor->blink & 16);
		}
		attr &= 0x7f;
	}

	if (altattr)
	{
		/* MDA-compatible attributes */
		ibg = 0;
		ifg = 7;
		if ((attr & 0x77) == 0x70)	 /* Invert */
		{
			ifg = 0;
			ibg = 7;
		}	
		if (attr & 8) 
		{
			ifg |= 8;	/* High intensity FG */
		}
		if (attr & 0x80) 
		{
			ibg |= 8;	/* High intensity BG */
		}
		if ((attr & 0x77) == 0)	/* Blank */
		{
			ifg = ibg;
		}
		ull = ((attr & 0x07) == 1) ? 13 : 0xffff;
	} 
	else 
	{
		/* CGA-compatible attributes */
		ull = 0xffff;
		ifg = attr & 0x0F;
		ibg = (attr >> 4) & 0x0F;
	}
	if (incolor->crtc[INCOLOR_CRTC_XMODE] & INCOLOR_XMODE_90COL) 
	{
		elg = 0;
	} 
	else 
	{
		elg = ((chr >= 0xc0) && (chr <= 0xdf));
	}
	fnt = incolor->vram + 0x4000 + 16 * chr + incolor->sc;

	if (blk)
	{
		/* Blinking, draw all background */
		val[0] = val[1] = val[2] = val[3] = 0x000;	
	}
	else if (incolor->sc == ull) 
	{
		/* Underscore, draw all foreground */
		val[0] = val[1] = val[2] = val[3] = 0x1ff;
	}
	else 
	{
		val[0] = fnt[0x00000] << 1;
		val[1] = fnt[0x10000] << 1;
		val[2] = fnt[0x20000] << 1;
		val[3] = fnt[0x30000] << 1;
	
		if (elg) 
		{
			val[0] |= (val[0] >> 1) & 1;
			val[1] |= (val[1] >> 1) & 1;
			val[2] |= (val[2] >> 1) & 1;
			val[3] |= (val[3] >> 1) & 1;
		}
	}
	for (i = 0; i < cw; i++) 
	{
		/* Generate pixel colour */
		cfg = 0;
		pmask = 1;
		for (plane = 0; plane < 4; plane++, pmask = pmask << 1)
		{
			if (val[plane] & 0x100) cfg |= (ifg & pmask);
			else			cfg |= (ibg & pmask);
		}
		/* cfg = colour of foreground pixels */
		if (altattr && (attr & 0x77) == 0) cfg = ibg; /* 'blank' attribute */
		if (palette)
		{
			fg = incolor_rgb[incolor->palette[cfg]];
		} 
		else 
		{
			fg = incolor_rgb[defpal[cfg]];
		}
		
		((uint32_t *)buffer32->line[incolor->displine])[x * cw + i] = fg;
		val[0] = val[0] << 1;
		val[1] = val[1] << 1;
		val[2] = val[2] << 1;
		val[3] = val[3] << 1;
	}
}


static void incolor_draw_char_ram48(incolor_t *incolor, int x, uint8_t chr, uint8_t attr)
{
	unsigned            i;
	int                 elg, blk, ul, ol, bld;
	unsigned            ull, oll, ulc, olc;
	unsigned            val[4];
	unsigned	    ifg, ibg, cfg, pmask, plane;
	const unsigned char *fnt;
	uint32_t	    fg;
	int		    cw = INCOLOR_CW;
	int                 blink   = incolor->ctrl & INCOLOR_CTRL_BLINK;
	int                 altattr = incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_ALTATTR;
	int                 palette = incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_PALETTE;
	int		    font = (attr & 0x0F);

	if (font >= 12) font &= 7;

	blk = 0;
	if (blink && altattr)
	{
		if (attr & 0x40) 
		{
			blk = (incolor->blink & 16);
		}
		attr &= 0x7f;
	}
	if (altattr) 
	{
		/* MDA-compatible attributes */
		if (blink) 
		{
			ibg = (attr & 0x80) ? 8 : 0;
			bld = 0;
			ol  = (attr & 0x20) ? 1 : 0;
			ul  = (attr & 0x10) ? 1 : 0;
		} 
		else 
		{
			bld = (attr & 0x80) ? 1 : 0;
			ibg = (attr & 0x40) ? 0x0F : 0;
			ol  = (attr & 0x20) ? 1 : 0;
			ul  = (attr & 0x10) ? 1 : 0;
		}
	} 
	else 
	{
		/* CGA-compatible attributes */
		ibg = 0;
		ifg = (attr >> 4) & 0x0F;
		ol  = 0;
		ul  = 0;
		bld = 0;
	}
	if (ul) 
	{ 
		ull = incolor->crtc[INCOLOR_CRTC_UNDER] & 0x0F;
		ulc = (incolor->crtc[INCOLOR_CRTC_UNDER] >> 4) & 0x0F;
		if (ulc == 0) ulc = 7;
	} 
	else 
	{
		ull = 0xFFFF;
	}
	if (ol) 
	{ 
		oll = incolor->crtc[INCOLOR_CRTC_OVER] & 0x0F;
		olc = (incolor->crtc[INCOLOR_CRTC_OVER] >> 4) & 0x0F;
		if (olc == 0) olc = 7;
	} 
	else 
	{
		oll = 0xFFFF;
	}

	if (incolor->crtc[INCOLOR_CRTC_XMODE] & INCOLOR_XMODE_90COL) 
	{
		elg = 0;
	} 
	else 
	{
		elg = ((chr >= 0xc0) && (chr <= 0xdf));
	}
	fnt = incolor->vram + 0x4000 + 16 * chr + 4096 * font + incolor->sc;

	if (blk)
	{
		/* Blinking, draw all background */
		val[0] = val[1] = val[2] = val[3] = 0x000;	
	}
	else if (incolor->sc == ull) 
	{
		/* Underscore, draw all foreground */
		val[0] = val[1] = val[2] = val[3] = 0x1ff;
	}
	else 
	{
		val[0] = fnt[0x00000] << 1;
		val[1] = fnt[0x10000] << 1;
		val[2] = fnt[0x20000] << 1;
		val[3] = fnt[0x30000] << 1;
	
		if (elg) 
		{
			val[0] |= (val[0] >> 1) & 1;
			val[1] |= (val[1] >> 1) & 1;
			val[2] |= (val[2] >> 1) & 1;
			val[3] |= (val[3] >> 1) & 1;
		}
		if (bld) 
		{
			val[0] |= (val[0] >> 1);
			val[1] |= (val[1] >> 1);
			val[2] |= (val[2] >> 1);
			val[3] |= (val[3] >> 1);
		}
	}
	for (i = 0; i < cw; i++) 
	{
		/* Generate pixel colour */
		cfg = 0;
		pmask = 1;
		if (incolor->sc == oll)
		{
			cfg = olc ^ ibg;	/* Strikethrough */
		}
		else if (incolor->sc == ull)
		{
			cfg = ulc ^ ibg;	/* Underline */
		}
		else
		{
			for (plane = 0; plane < 4; plane++, pmask = pmask << 1)
			{
				if (val[plane] & 0x100)
				{
					if (altattr)	cfg |= ((~ibg) & pmask);
					else		cfg |= ((~ifg) & pmask);
				}
				else if (altattr)	cfg |= (ibg & pmask);
			}
		}
		if (palette)
		{
			fg = incolor_rgb[incolor->palette[cfg]];
		} 
		else 
		{
			fg = incolor_rgb[defpal[cfg]];
		}
		
		((uint32_t *)buffer32->line[incolor->displine])[x * cw + i] = fg;
		val[0] = val[0] << 1;
		val[1] = val[1] << 1;
		val[2] = val[2] << 1;
		val[3] = val[3] << 1;
	}
}






static void incolor_text_line(incolor_t *incolor, uint16_t ca)
{
        int drawcursor;
	int x, c;
        uint8_t chr, attr;
	uint32_t col;

	for (x = 0; x < incolor->crtc[1]; x++)
	{
		chr  = incolor->vram[(incolor->ma << 1) & 0x3fff];
                attr = incolor->vram[((incolor->ma << 1) + 1) & 0x3fff];

                drawcursor = ((incolor->ma == ca) && incolor->con && incolor->cursoron);

		switch (incolor->crtc[INCOLOR_CRTC_XMODE] & 5)
		{
			case 0:
			case 4:	/* ROM font */
				incolor_draw_char_rom(incolor, x, chr, attr);
				break;
			case 1: /* 4k RAMfont */
				incolor_draw_char_ram4(incolor, x, chr, attr);
				break;
			case 5: /* 48k RAMfont */
				incolor_draw_char_ram48(incolor, x, chr, attr);
				break;

		}
		++incolor->ma;
                if (drawcursor)
                {
			int cw = INCOLOR_CW;
			uint8_t ink = incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_CURSOR;
			if (ink == 0) ink = (attr & 0x08) | 7;

			/* In MDA-compatible mode, cursor brightness comes from 
			 * background */
			if (incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_ALTATTR) 
			{
				ink = (attr & 0x08) | (ink & 7);
			}
			if (incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_PALETTE) 
			{
				col = incolor_rgb[incolor->palette[ink]];
			} 
			else 
			{
				col = incolor_rgb[defpal[ink]];
			}
			for (c = 0; c < cw; c++)
			{
				((uint32_t *)buffer32->line[incolor->displine])[x * cw + c] = col;
			}
		}
	}
}


static void incolor_graphics_line(incolor_t *incolor)
{
	uint8_t mask;
	uint16_t ca;
	int x, c, plane, col;
	uint8_t ink;
	uint16_t val[4];

	/* Graphics mode. */
        ca = (incolor->sc & 3) * 0x2000;
        if ((incolor->ctrl & INCOLOR_CTRL_PAGE1) && (incolor->ctrl2 & INCOLOR_CTRL2_PAGE1))
		ca += 0x8000;

	for (x = 0; x < incolor->crtc[1]; x++)
	{
		mask = incolor->crtc[INCOLOR_CRTC_MASK];	/* Planes to display */
		for (plane = 0; plane < 4; plane++, mask = mask >> 1)
		{
			if (mask & 1) 
				val[plane] = (incolor->vram[((incolor->ma << 1) & 0x1fff) + ca + 0x10000 * plane] << 8) | 
					      incolor->vram[((incolor->ma << 1) & 0x1fff) + ca + 0x10000 * plane + 1];
			else	val[plane] = 0;
		}
		incolor->ma++;
		for (c = 0; c < 16; c++)
		{
			ink = 0;
			for (plane = 0; plane < 4; plane++)
			{
				ink = ink >> 1;
				if (val[plane] & 0x8000) ink |= 8;
				val[plane] = val[plane] << 1;
			}
			/* Is palette in use? */
			if (incolor->crtc[INCOLOR_CRTC_EXCEPT] & INCOLOR_EXCEPT_PALETTE)
				col = incolor->palette[ink];
			else	col = defpal[ink];

			((uint32_t *)buffer32->line[incolor->displine])[(x << 4) + c] = incolor_rgb[col];
		}
	}
}

void incolor_poll(void *p)
{
        incolor_t *incolor = (incolor_t *)p;
        uint16_t ca = (incolor->crtc[15] | (incolor->crtc[14] << 8)) & 0x3fff;
        int x;
        int oldvc;
        int oldsc;

        if (!incolor->linepos)
        {
//                pclog("InColor poll %i %i\n", incolor->vc, incolor->sc);
                incolor->vidtime += incolor->dispofftime;
                incolor->stat |= 1;
                incolor->linepos = 1;
                oldsc = incolor->sc;
                if ((incolor->crtc[8] & 3) == 3) 
                        incolor->sc = (incolor->sc << 1) & 7;
                if (incolor->dispon)
                {
                        if (incolor->displine < incolor->firstline)
                        {
                                incolor->firstline = incolor->displine;
                                video_wait_for_buffer();
                        }
                        incolor->lastline = incolor->displine;
                        if ((incolor->ctrl & INCOLOR_CTRL_GRAPH) && (incolor->ctrl2 & INCOLOR_CTRL2_GRAPH))
                        {
				incolor_graphics_line(incolor);
                        }
                        else
                        {
				incolor_text_line(incolor, ca);
                        }
                }
                incolor->sc = oldsc;
                if (incolor->vc == incolor->crtc[7] && !incolor->sc)
                {
                        incolor->stat |= 8;
//                        printf("VSYNC on %i %i\n",vc,sc);
                }
                incolor->displine++;
                if (incolor->displine >= 500) 
                        incolor->displine = 0;
        }
        else
        {
                incolor->vidtime += incolor->dispontime;
                if (incolor->dispon) 
                        incolor->stat &= ~1;
                incolor->linepos = 0;
                if (incolor->vsynctime)
                {
                        incolor->vsynctime--;
                        if (!incolor->vsynctime)
                        {
                                incolor->stat &= ~8;
//                                printf("VSYNC off %i %i\n",vc,sc);
                        }
                }
                if (incolor->sc == (incolor->crtc[11] & 31) || ((incolor->crtc[8] & 3) == 3 && incolor->sc == ((incolor->crtc[11] & 31) >> 1))) 
                { 
                        incolor->con = 0; 
                        incolor->coff = 1; 
                }
                if (incolor->vadj)
                {
                        incolor->sc++;
                        incolor->sc &= 31;
                        incolor->ma = incolor->maback;
                        incolor->vadj--;
                        if (!incolor->vadj)
                        {
                                incolor->dispon = 1;
                                incolor->ma = incolor->maback = (incolor->crtc[13] | (incolor->crtc[12] << 8)) & 0x3fff;
                                incolor->sc = 0;
                        }
                }
                else if (incolor->sc == incolor->crtc[9] || ((incolor->crtc[8] & 3) == 3 && incolor->sc == (incolor->crtc[9] >> 1)))
                {
                        incolor->maback = incolor->ma;
                        incolor->sc = 0;
                        oldvc = incolor->vc;
                        incolor->vc++;
                        incolor->vc &= 127;
                        if (incolor->vc == incolor->crtc[6]) 
                                incolor->dispon = 0;
                        if (oldvc == incolor->crtc[4])
                        {
//                                printf("Display over at %i\n",displine);
                                incolor->vc = 0;
                                incolor->vadj = incolor->crtc[5];
                                if (!incolor->vadj) incolor->dispon=1;
                                if (!incolor->vadj) incolor->ma = incolor->maback = (incolor->crtc[13] | (incolor->crtc[12] << 8)) & 0x3fff;
                                if ((incolor->crtc[10] & 0x60) == 0x20) incolor->cursoron = 0;
                                else                                     incolor->cursoron = incolor->blink & 16;
                        }
                        if (incolor->vc == incolor->crtc[7])
                        {
                                incolor->dispon = 0;
                                incolor->displine = 0;
                                incolor->vsynctime = 16;//(crtcm[3]>>4)+1;
                                if (incolor->crtc[7])
                                {
//                                        printf("Lastline %i Firstline %i  %i\n",lastline,firstline,lastline-firstline);
                                        if ((incolor->ctrl & INCOLOR_CTRL_GRAPH) && (incolor->ctrl2 & INCOLOR_CTRL2_GRAPH)) 
					{
						x = incolor->crtc[1] << 4;
					}
                                        else
					{
                                               x = incolor->crtc[1] * 9;
					}
                                        incolor->lastline++;
                                        if (x != xsize || (incolor->lastline - incolor->firstline) != ysize)
                                        {
                                                xsize = x;
                                                ysize = incolor->lastline - incolor->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);
                                        }
                                startblit();
					video_blit_memtoscreen(0, incolor->firstline, 0, incolor->lastline - incolor->firstline, xsize, incolor->lastline - incolor->firstline);
                                endblit();
                                        frames++;
                                        if ((incolor->ctrl & INCOLOR_CTRL_GRAPH) && (incolor->ctrl2 & INCOLOR_CTRL2_GRAPH))
                                        {
                                                video_res_x = incolor->crtc[1] * 16;
                                                video_res_y = incolor->crtc[6] * 4;
                                                video_bpp = 1;
                                        }
                                        else
                                        {
                                                video_res_x = incolor->crtc[1];
                                                video_res_y = incolor->crtc[6];
                                                video_bpp = 0;
                                        }
                                }
                                incolor->firstline = 1000;
                                incolor->lastline = 0;
                                incolor->blink++;
                        }
                }
                else
                {
                        incolor->sc++;
                        incolor->sc &= 31;
                        incolor->ma = incolor->maback;
                }
                if ((incolor->sc == (incolor->crtc[10] & 31) || ((incolor->crtc[8] & 3) == 3 && incolor->sc == ((incolor->crtc[10] & 31) >> 1))))
                {
                        incolor->con = 1;
//                        printf("Cursor on - %02X %02X %02X\n",crtcm[8],crtcm[10],crtcm[11]);
                }
        }
}

void *incolor_init()
{
        int c;
        incolor_t *incolor = malloc(sizeof(incolor_t));
        memset(incolor, 0, sizeof(incolor_t));

        incolor->vram = malloc(0x40000);	/* 4 planes of 64k */

        timer_add(incolor_poll, &incolor->vidtime, TIMER_ALWAYS_ENABLED, incolor);
        mem_mapping_add(&incolor->mapping, 0xb0000, 0x08000, incolor_read, NULL, NULL, incolor_write, NULL, NULL,  NULL, 0, incolor);
        io_sethandler(0x03b0, 0x0010, incolor_in, NULL, NULL, incolor_out, NULL, NULL, incolor);

	for (c = 0; c < 64; c++)
	{
		incolor_rgb[c] = makecol32(init_rgb[c][0], init_rgb[c][1], init_rgb[c][2]);
	}

/* Initialise CRTC regs to safe values */
	incolor->crtc[INCOLOR_CRTC_MASK  ] = 0x0F; /* All planes displayed */
	incolor->crtc[INCOLOR_CRTC_RWCTRL] = INCOLOR_RWCTRL_POLARITY;
	incolor->crtc[INCOLOR_CRTC_RWCOL ] = 0x0F; /* White on black */
	incolor->crtc[INCOLOR_CRTC_EXCEPT] = INCOLOR_EXCEPT_ALTATTR;
	for (c = 0; c < 16; c++) 
	{
		incolor->palette[c] = defpal[c];
	}
	incolor->palette_idx = 0;



        return incolor;
}

void incolor_close(void *p)
{
        incolor_t *incolor = (incolor_t *)p;

        free(incolor->vram);
        free(incolor);
}

void incolor_speed_changed(void *p)
{
        incolor_t *incolor = (incolor_t *)p;
        
        incolor_recalctimings(incolor);
}

device_t incolor_device =
{
        "Hercules InColor",
        0,
        incolor_init,
        incolor_close,
        NULL,
        incolor_speed_changed,
        NULL,
        NULL
};
