/* MDSI Genius VHR emulation*/
#include <stdlib.h>
#include "ibm.h"
#include "device.h"
#include "io.h"
#include "mem.h"
#include "rom.h"
#include "timer.h"
#include "video.h"
#include "vid_genius.h"

#define GENIUS_XSIZE 728
#define GENIUS_YSIZE 1008

void updatewindowsize(int x, int y);

extern uint8_t fontdat8x12[256][16];	

/* I'm at something of a disadvantage writing this emulation: I don't have an
 * MDSI Genius card, nor do I have the BIOS extension (VHRBIOS.SYS) that came 
 * with it. What I do have are the GEM and Windows 1.04 drivers, plus a driver
 * for a later MCA version of the card. The latter can be found at 
 * <http://files.mpoli.fi/hardware/DISPLAY/GENIUS/> and is necessary if you
 * want the Windows driver to work.
 *
 * This emulation appears to work correctly with:
 * The MCA drivers GMC_ANSI.SYS and INS_ANSI.SYS
 * The GEM driver SDGEN9.VGA
 * The Windows 1.04 driver GENIUS.DRV
 *
 * As far as I can see, the card uses a fixed resolution of 728x1008 pixels. 
 * It has the following modes of operation:
 * 
 * > MDA-compatible:      80x25 text, each character 9x15 pixels.
 * > CGA-compatible:      640x200 mono graphics
 * > Dual:                MDA text in the top half, CGA graphics in the bottom
 * > Native text:         80x66 text, each character 9x15 pixels.
 * > Native graphics:     728x1008 mono graphics.
 *
 * Under the covers, this seems to translate to:
 *  > Text framebuffer.     At B000:0000, 16k. Displayed if enable bit is set 
 *                         in the MDA control register.
 *  > Graphics framebuffer. In native modes goes from A000:0000 to A000:FFFF
 *                         and B800:0000 to B800:FFFF. In CGA-compatible 
 *                         mode only the section at B800:0000 to B800:7FFF
 *                         is visible. Displayed if enable bit is set in the
 *                         CGA control register.
 * 
 * Two card-specific registers control text and graphics display:
 * 
 *  03B0: Control register.
 * 	   Bit 0: Map all graphics framebuffer into memory.
 *         Bit 2: Unknown. Set by GMC /M; cleared by mode set or GMC /T.
 *         Bit 4: Set for CGA-compatible graphics, clear for native graphics.
 *         Bit 5: Set for black on white, clear for white on black.
 *
 *  03B1: Character height register.
 *        Bits 0-1: Character cell height (0 => 15, 1 => 14, 2 => 13, 3 => 12)
 *        Bit  4:   Set to double character cell height (scanlines are doubled)
 *        Bit  7:   Unknown, seems to be set for all modes except 80x66
 *
 *  Not having the card also means I don't have its font. According to the 
 *  card brochure the font is an 8x12 bitmap in a 9x15 character cell. I 
 *  therefore generated it by taking the MDA font, increasing graphics to 
 *  16 pixels in height and reducing the height of characters so they fit
 *  in an 8x12 cell if necessary.
 */

 

typedef struct genius_t
{
        mem_mapping_t mapping;

        uint8_t mda_crtc[32];	/* The 'CRTC' as the host PC sees it */
        int mda_crtcreg;	/* Current CRTC register */
	uint8_t genius_control;	/* Native control register 
				 * I think bit 0 enables the full 
				 * framebuffer. 
				 */
	uint8_t genius_charh;	/* Native character height register: 
				 * 00h => chars are 15 pixels high 
				 * 81h => chars are 14 pixels high
				 * 83h => chars are 12 pixels high 
				 * 90h => chars are 30 pixels high [15 x 2]
				 * 93h => chars are 24 pixels high [12 x 2]
				 */
	uint8_t genius_mode;	/* Current mode (see list at top of file) */
	uint8_t cga_ctrl;	/* Emulated CGA control register */
	uint8_t mda_ctrl;	/* Emulated MDA control register */
	uint8_t cga_colour;	/* Emulated CGA colour register (ignored) */

        uint8_t mda_stat;	/* MDA status (IN 0x3BA) */
        uint8_t cga_stat;	/* CGA status (IN 0x3DA) */

	int font;		/* Current font, 0 or 1 */
	int enabled;		/* Display enabled, 0 or 1 */
	int detach;		/* Detach cursor, 0 or 1 */

        uint64_t dispontime, dispofftime;
        pc_timer_t timer;
        
        int linepos, displine;
        int vc;
        int dispon, blink;
        int vsynctime;

        uint8_t *vram;
} genius_t;

static uint32_t genius_pal[4];

/* Mapping of attributes to colours, in MDA emulation mode */
static int mdacols[256][2][2];

void genius_recalctimings(genius_t *genius);
void genius_write(uint32_t addr, uint8_t val, void *p);
uint8_t genius_read(uint32_t addr, void *p);


void genius_out(uint16_t addr, uint8_t val, void *p)
{
        genius_t *genius = (genius_t *)p;

        switch (addr)
        {
                case 0x3b0: 	/* Command / control register */
		genius->genius_control = val;
		if (val & 1)
		{
			mem_mapping_set_addr(&genius->mapping, 0xa0000, 0x28000);
		}
		else
		{
			mem_mapping_set_addr(&genius->mapping, 0xb0000, 0x10000);
		}

		break;

		case 0x3b1:
		genius->genius_charh = val;
		break;

		/* Emulated CRTC, register select */
		case 0x3b2: case 0x3b4: case 0x3b6:
		case 0x3d0: case 0x3d2: case 0x3d4: case 0x3d6:
                genius->mda_crtcreg = val & 31;
                break;

		/* Emulated CRTC, value */
                case 0x3b3: case 0x3b5: case 0x3b7:
                case 0x3d1: case 0x3d3: case 0x3d5: case 0x3d7:
	        genius->mda_crtc[genius->mda_crtcreg] = val;
                genius_recalctimings(genius);
	        return;

		/* Emulated MDA control register */
                case 0x3b8: 
             	genius->mda_ctrl = val;
              	return;
		/* Emulated CGA control register */
		case 0x3D8:
             	genius->cga_ctrl = val;
              	return;
		/* Emulated CGA colour register */
                case 0x3D9:
               	genius->cga_colour = val;
              	return;
        }
}

uint8_t genius_in(uint16_t addr, void *p)
{
        genius_t *genius = (genius_t *)p;
        
        switch (addr)
        {
                case 0x3b0: case 0x3b2: case 0x3b4: case 0x3b6:
                case 0x3d0: case 0x3d2: case 0x3d4: case 0x3d6:
	        return genius->mda_crtcreg;
                case 0x3b1: case 0x3b3: case 0x3b5: case 0x3b7:
		case 0x3d1: case 0x3d3: case 0x3d5: case 0x3d7:
	        return genius->mda_crtc[genius->mda_crtcreg];
		case 0x3b8: 
		return genius->mda_ctrl;
		case 0x3d9:
		return genius->cga_colour;
                case 0x3ba: 
	        return genius->mda_stat;
		case 0x3d8:
		return genius->cga_ctrl;
		case 0x3da:
		return genius->cga_stat;
        }
        return 0xff;
}



void genius_write(uint32_t addr, uint8_t val, void *p)
{
        genius_t *genius = (genius_t *)p;
        egawrites++;
        
	if (genius->genius_control & 1)
	{
		addr = addr % 0x28000;
	}
	else
	/* If hi-res memory is disabled, only visible in the B000 segment */
	{
		addr = (addr & 0xFFFF) + 0x10000;
	}
       	genius->vram[addr] = val;
}



uint8_t genius_read(uint32_t addr, void *p)
{
        genius_t *genius = (genius_t *)p;
        egareads++;

	if (genius->genius_control & 1)
	{
		addr = addr % 0x28000;
	}
	else
	/* If hi-res memory is disabled, only visible in the B000 segment */
	{
		addr = (addr & 0xFFFF) + 0x10000;
	}
       	return genius->vram[addr];
}



void genius_recalctimings(genius_t *genius)
{
        double disptime;
	double _dispontime, _dispofftime;

	disptime = 0x31;
	_dispontime = 0x28;
        _dispofftime = disptime - _dispontime;
        _dispontime  *= MDACONST;
        _dispofftime *= MDACONST;
	genius->dispontime  = (uint64_t)_dispontime;
	genius->dispofftime = (uint64_t)_dispofftime;
}


/* Draw a single line of the screen in either text mode */
void genius_textline(genius_t *genius, uint8_t background)
{
	int x;
	int w  = 80;	/* 80 characters across */
	int cw = 9;	/* Each character is 9 pixels wide */
	uint8_t chr, attr;
	uint8_t bitmap[2];
        int blink, c, row;
        int drawcursor, cursorline;
	uint16_t addr;
	uint8_t sc;
	int charh;
	uint16_t ma = (genius->mda_crtc[13] | (genius->mda_crtc[12] << 8)) & 0x3fff;
	uint16_t ca = (genius->mda_crtc[15] | (genius->mda_crtc[14] << 8)) & 0x3fff;
	unsigned char *framebuf = genius->vram + 0x10000;
	uint32_t col;

	/* Character height is 12-15 */
	charh = 15 - (genius->genius_charh & 3);
	if (genius->genius_charh & 0x10)
	{
		row = ((genius->displine >> 1) / charh);	
		sc  = ((genius->displine >> 1) % charh);	
	}
	else
	{
		row = (genius->displine / charh);	
		sc  = (genius->displine % charh);	
	}
	addr = ((ma & ~1) + row * w) * 2;

	ma += (row * w);
	
	if ((genius->mda_crtc[10] & 0x60) == 0x20)
	{
		cursorline = 0;
	}
	else
	{
		cursorline = ((genius->mda_crtc[10] & 0x1F) <= sc) &&
			     ((genius->mda_crtc[11] & 0x1F) >= sc);
	}

	for (x = 0; x < w; x++)
	{
		chr  = framebuf[(addr + 2 * x) & 0x3FFF];
		attr = framebuf[(addr + 2 * x + 1) & 0x3FFF];
		drawcursor = ((ma == ca) && cursorline && genius->enabled &&
			(genius->mda_ctrl & 8));

		switch (genius->mda_crtc[10] & 0x60)
		{
			case 0x00: drawcursor = drawcursor && (genius->blink & 16); break;
			case 0x60: drawcursor = drawcursor && (genius->blink & 32); break;
		}
		blink = ((genius->blink & 16) && 
			(genius->mda_ctrl & 0x20) && 
			(attr & 0x80) && !drawcursor);

		if (genius->mda_ctrl & 0x20) attr &= 0x7F;
		/* MDA underline */
		if (sc == charh && ((attr & 7) == 1))
		{
			col = mdacols[attr][blink][1];

			if (genius->genius_control & 0x20)
			{
				col ^= 0xffffff;
			}

			for (c = 0; c < cw; c++)
			{
				if (col != background) 
					((uint32_t *)buffer32->line[genius->displine])[(x * cw) + c] = col;
			}
		}
		else	/* Draw 8 pixels of character */
		{
			bitmap[0] = fontdat8x12[chr][sc];
			for (c = 0; c < 8; c++)
			{
				col = mdacols[attr][blink][(bitmap[0] & (1 << (c ^ 7))) ? 1 : 0];
				if (!(genius->enabled) || !(genius->mda_ctrl & 8))
					col = mdacols[0][0][0];
	
				if (genius->genius_control & 0x20)
				{
					col ^= 0xffffff;
				}
				if (col != background)
				{
					((uint32_t *)buffer32->line[genius->displine])[(x * cw) + c] = col;
				}
			}
			/* The ninth pixel column... */
			if ((chr & ~0x1f) == 0xc0) 
			{
				/* Echo column 8 for the graphics chars */
				col = ((uint32_t *)buffer32->line[genius->displine])[(x * cw) + 7];
				if (col != background)
                                        ((uint32_t *)buffer32->line[genius->displine])[(x * cw) + 8] = col;
			}
			else	/* Otherwise fill with background */	
			{
				col = mdacols[attr][blink][0];
				if (genius->genius_control & 0x20)
				{
					col ^= 0xffffff;
				}
				if (col != background)
                                        ((uint32_t *)buffer32->line[genius->displine])[(x * cw) + 8] = col;
			}
                        if (drawcursor)
                        {
                        	for (c = 0; c < cw; c++)
                                	((uint32_t *)buffer32->line[genius->displine])[(x * cw) + c] ^= mdacols[attr][0][1];
                        }
			++ma;
		}
	}
}




/* Draw a line in the CGA 640x200 mode */
void genius_cgaline(genius_t *genius)
{
	int x, c;
	uint32_t dat;
	uint32_t ink;
	uint32_t addr;

	ink = (genius->genius_control & 0x20) ? genius_pal[0] : genius_pal[3];
	/* We draw the CGA at row 600 */
	if (genius->displine < 600) 
	{
		return;
	}
	addr = 0x18000 + 80 * ((genius->displine - 600) >> 2);
	if ((genius->displine - 600) & 2)
	{
		addr += 0x2000;
	}

	for (x = 0; x < 80; x++)
	{
		dat =  genius->vram[addr];
		addr++;

		for (c = 0; c < 8; c++)
		{
			if (dat & 0x80)
			{
				((uint32_t *)buffer32->line[genius->displine])[x*8 + c] = ink;
			}
			dat = dat << 1;
		}
	}
}




/* Draw a line in the native high-resolution mode */
void genius_hiresline(genius_t *genius)
{
	int x, c;
	uint32_t dat;
	uint32_t ink;
	uint32_t addr;
        
	ink = (genius->genius_control & 0x20) ? genius_pal[0] : genius_pal[3];
	/* The first 512 lines live at A0000 */
	if (genius->displine < 512) 
	{
		addr = 128 * genius->displine;
	}
	else	/* The second 496 live at B8000 */
	{
		addr = 0x18000 + 128 * (genius->displine - 512);
	}

	for (x = 0; x < 91; x++)
	{
		dat =  genius->vram[addr];
		addr++;

		for (c = 0; c < 8; c++)
		{
			if (dat & 0x80)
			{
				((uint32_t *)buffer32->line[genius->displine])[x*8 + c] = ink;
			}
			dat = dat << 1;
		}
	}
}




void genius_poll(void *p)
{
        genius_t *genius = (genius_t *)p;
        int x;
        uint32_t background;

        if (!genius->linepos)
        {
                timer_advance_u64(&genius->timer, genius->dispofftime);
                genius->cga_stat |= 1;
                genius->mda_stat |= 1;
                genius->linepos = 1;
                if (genius->dispon)
                {
			if (genius->genius_control & 0x20)
			{
				background = genius_pal[3];
			}
			else
			{
				background = genius_pal[0];
			}
                        if (genius->displine == 0)
                        {
                                video_wait_for_buffer();
                        }
			/* Start off with a blank line */
			for (x = 0; x < GENIUS_XSIZE; x++)
			{
				((uint32_t *)buffer32->line[genius->displine])[x] = background;
			}
			/* If graphics display enabled, draw graphics on top
			 * of the blanked line */
			if (genius->cga_ctrl & 8)
			{
				if (genius->genius_control & 8)
				{
					genius_cgaline(genius);
				}
				else
				{
					genius_hiresline(genius);
				}
			}
			/* If MDA display is enabled, draw MDA text on top
			 * of the lot */
			if (genius->mda_ctrl & 8)
			{	
				genius_textline(genius, background);
			}
                }
                genius->displine++;
		/* Hardcode a fixed refresh rate and VSYNC timing */
                if (genius->displine == 1008) /* Start of VSYNC */
                {
                        genius->cga_stat |= 8;
			genius->dispon = 0;
                }
		if (genius->displine == 1040) /* End of VSYNC */
		{
                        genius->displine = 0;
                        genius->cga_stat &= ~8;
			genius->dispon = 1;
		}
        }
        else
        {
		if (genius->dispon)
		{
                	genius->cga_stat &= ~1;
                	genius->mda_stat &= ~1;
		}
                timer_advance_u64(&genius->timer, genius->dispontime);
                genius->linepos = 0;

		if (genius->displine == 1008)
                {
/* Hardcode GENIUS_XSIZE * GENIUS_YSIZE window size */
			if (GENIUS_XSIZE != xsize || GENIUS_YSIZE != ysize)
			{
                                xsize = GENIUS_XSIZE;
                                ysize = GENIUS_YSIZE;
                                if (xsize < 64) xsize = 656;
                                if (ysize < 32) ysize = 200;
                                updatewindowsize(xsize, ysize);
                        }
                        video_blit_memtoscreen(0, 0, 0, ysize, xsize, ysize);

                        frames++;
			/* Fixed 728x1008 resolution */
			video_res_x = GENIUS_XSIZE;
			video_res_y = GENIUS_YSIZE;
			video_bpp = 1;
                	genius->blink++;
                }
        }
}

void *genius_init()
{
        int c;
        genius_t *genius = malloc(sizeof(genius_t));
        memset(genius, 0, sizeof(genius_t));

	/* 160k video RAM */
        genius->vram = malloc(0x28000);

        timer_add(&genius->timer, genius_poll, genius, 1);

	/* Occupy memory between 0xB0000 and 0xBFFFF (moves to 0xA0000 in
	 * high-resolution modes)  */
        mem_mapping_add(&genius->mapping, 0xb0000, 0x10000, genius_read, NULL, NULL, genius_write, NULL, NULL,  NULL, 0, genius);
	/* Respond to both MDA and CGA I/O ports */
        io_sethandler(0x03b0, 0x000C, genius_in, NULL, NULL, genius_out, NULL, NULL, genius);
        io_sethandler(0x03d0, 0x0010, genius_in, NULL, NULL, genius_out, NULL, NULL, genius);

        genius_pal[0] = makecol(0x00, 0x00, 0x00);
        genius_pal[1] = makecol(0x55, 0x55, 0x55);
        genius_pal[2] = makecol(0xaa, 0xaa, 0xaa);
        genius_pal[3] = makecol(0xff, 0xff, 0xff);

	/* MDA attributes */
	/* I don't know if the Genius's MDA emulation actually does 
	 * emulate bright / non-bright. For the time being pretend it does. */
        for (c = 0; c < 256; c++)
        {
                mdacols[c][0][0] = mdacols[c][1][0] = mdacols[c][1][1] = genius_pal[0];
                if (c & 8) mdacols[c][0][1] = genius_pal[3];
                else       mdacols[c][0][1] = genius_pal[2];
        }
        mdacols[0x70][0][1] = genius_pal[0];
        mdacols[0x70][0][0] = mdacols[0x70][1][0] = mdacols[0x70][1][1] = genius_pal[3];
        mdacols[0xF0][0][1] = genius_pal[0];
        mdacols[0xF0][0][0] = mdacols[0xF0][1][0] = mdacols[0xF0][1][1] = genius_pal[3];
        mdacols[0x78][0][1] = genius_pal[2];
        mdacols[0x78][0][0] = mdacols[0x78][1][0] = mdacols[0x78][1][1] = genius_pal[3];
        mdacols[0xF8][0][1] = genius_pal[2];
        mdacols[0xF8][0][0] = mdacols[0xF8][1][0] = mdacols[0xF8][1][1] = genius_pal[3];
        mdacols[0x00][0][1] = mdacols[0x00][1][1] = genius_pal[0];
        mdacols[0x08][0][1] = mdacols[0x08][1][1] = genius_pal[0];
        mdacols[0x80][0][1] = mdacols[0x80][1][1] = genius_pal[0];
        mdacols[0x88][0][1] = mdacols[0x88][1][1] = genius_pal[0];

/* Start off in 80x25 text mode */
        genius->cga_stat   = 0xF4;
	genius->genius_mode = 2;
	genius->enabled    = 1;
	genius->genius_charh = 0x90; /* Native character height register */
        return genius;
}

void genius_close(void *p)
{
        genius_t *genius = (genius_t *)p;

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

static int genius_available()
{
        return rom_present("8x12.bin");
}

void genius_speed_changed(void *p)
{
        genius_t *genius = (genius_t *)p;
        
        genius_recalctimings(genius);
}

device_t genius_device =
{
        "Genius VHR",
        0,
        genius_init,
        genius_close,
        genius_available,
        genius_speed_changed,
        NULL,
        NULL
};
