/*Paradise VGA emulation

  PC2086, PC3086, GO481 use PVGA1A
  MegaPC uses W90C11A
  */
#include <stdlib.h>
#include "ibm.h"
#include "device.h"
#include "io.h"
#include "mem.h"
#include "rom.h"
#include "video.h"
#include "vid_paradise.h"
#include "vid_svga.h"
#include "vid_svga_render.h"
#include "vid_unk_ramdac.h"
#include "wd76c10.h"

typedef struct paradise_t {
	svga_t svga;

	rom_t bios_rom;

	enum {
		PVGA1A = 0,
		WD90C11
	} type;

	uint32_t read_bank[4], write_bank[4];
} paradise_t;

void paradise_remap(paradise_t *paradise);

void paradise_out(uint16_t addr, uint8_t val, void *p);
uint8_t paradise_in(uint16_t addr, void *p);

void paradise_out(uint16_t addr, uint8_t val, void *p) {
	paradise_t *paradise = (paradise_t *)p;
	svga_t *svga = &paradise->svga;
	uint8_t old;

	if (((addr & 0xfff0) == 0x3d0 || (addr & 0xfff0) == 0x3b0) && !(svga->miscout & 1))
		addr ^= 0x60;
//        output = 3;
//        pclog("Paradise out %04X %02X %04X:%04X\n", addr, val, CS, pc);
	switch (addr) {
	case 0x3c5:
		if (svga->seqaddr > 7) {
			if (paradise->type < WD90C11 || svga->seqregs[6] != 0x48)
				return;
			svga->seqregs[svga->seqaddr & 0x1f] = val;
			if (svga->seqaddr == 0x11)
				paradise_remap(paradise);
			return;
		}
		break;

	case 0x3cf:
		if (svga->gdcaddr >= 0x9 && svga->gdcaddr < 0xf) {
			if ((svga->gdcreg[0xf] & 7) != 5)
				return;
		}
		if (svga->gdcaddr == 6) {
			if ((svga->gdcreg[6] & 0xc) != (val & 0xc)) {
//                                pclog("Write mapping %02X\n", val);
				switch (val & 0xC) {
				case 0x0: /*128k at A0000*/
					mem_mapping_set_addr(&svga->mapping, 0xa0000, 0x20000);
					svga->banked_mask = 0xffff;
					break;
				case 0x4: /*64k at A0000*/
					mem_mapping_set_addr(&svga->mapping, 0xa0000, 0x10000);
					svga->banked_mask = 0xffff;
					break;
				case 0x8: /*32k at B0000*/
					mem_mapping_set_addr(&svga->mapping, 0xb0000, 0x08000);
					svga->banked_mask = 0x7fff;
					break;
				case 0xC: /*32k at B8000*/
					mem_mapping_set_addr(&svga->mapping, 0xb8000, 0x08000);
					svga->banked_mask = 0x7fff;
					break;
				}
			}
			svga->gdcreg[6] = val;
			paradise_remap(paradise);
			return;
		}
		if (svga->gdcaddr == 0x9 || svga->gdcaddr == 0xa) {
			svga->gdcreg[svga->gdcaddr] = val;
			paradise_remap(paradise);
			return;
		}
		if (svga->gdcaddr == 0xb) {
			svga->gdcreg[0xb] = val;
			paradise_remap(paradise);
			return;
		}
		break;

	case 0x3D4:svga->crtcreg = val & 0x3f;
		return;
	case 0x3D5:
		if ((paradise->type == PVGA1A) && (svga->crtcreg & 0x20))
			return;
		if ((svga->crtcreg < 7) && (svga->crtc[0x11] & 0x80))
			return;
		if ((svga->crtcreg == 7) && (svga->crtc[0x11] & 0x80))
			val = (svga->crtc[7] & ~0x10) | (val & 0x10);
		if (svga->crtcreg > 0x29 && (svga->crtc[0x29] & 7) != 5)
			return;
		if (svga->crtcreg >= 0x31 && svga->crtcreg <= 0x37)
			return;
		old = svga->crtc[svga->crtcreg];
		svga->crtc[svga->crtcreg] = val;

		if (old != val) {
			if (svga->crtcreg < 0xe || svga->crtcreg > 0x10) {
				svga->fullchange = changeframecount;
				svga_recalctimings(&paradise->svga);
			}
		}
		break;

	case 0x46e8:io_removehandler(0x03c0, 0x0020, paradise_in, NULL, NULL, paradise_out, NULL, NULL, paradise);
		mem_mapping_disable(&paradise->svga.mapping);
		if (val & 8) {
			io_sethandler(0x03c0, 0x0020, paradise_in, NULL, NULL, paradise_out, NULL, NULL, paradise);
			mem_mapping_enable(&paradise->svga.mapping);
		}
		break;
	}
	svga_out(addr, val, svga);
}

uint8_t paradise_in(uint16_t addr, void *p) {
	paradise_t *paradise = (paradise_t *)p;
	svga_t *svga = &paradise->svga;

	if (((addr & 0xfff0) == 0x3d0 || (addr & 0xfff0) == 0x3b0) && !(svga->miscout & 1))
		addr ^= 0x60;

//        if (addr != 0x3da) pclog("Paradise in %04X\n", addr);
	switch (addr) {
	case 0x3c2:return 0x10;

	case 0x3c5:
		if (svga->seqaddr > 7) {
			if (paradise->type < WD90C11 || svga->seqregs[6] != 0x48)
				return 0xff;
			if (svga->seqaddr > 0x12)
				return 0xff;
			return svga->seqregs[svga->seqaddr & 0x1f];
		}
		break;

	case 0x3cf:
		if (svga->gdcaddr >= 0x9 && svga->gdcaddr < 0xf) {
			if (svga->gdcreg[0xf] & 0x10)
				return 0xff;
			switch (svga->gdcaddr) {
			case 0xf:return (svga->gdcreg[0xf] & 0x17) | 0x80;
			}
		}
		break;

	case 0x3D4:return svga->crtcreg;
	case 0x3D5:
		if ((paradise->type == PVGA1A) && (svga->crtcreg & 0x20))
			return 0xff;
		if (svga->crtcreg > 0x29 && svga->crtcreg < 0x30 && (svga->crtc[0x29] & 0x88) != 0x80)
			return 0xff;
		return svga->crtc[svga->crtcreg];
	}
	return svga_in(addr, svga);
}

void paradise_remap(paradise_t *paradise) {
	svga_t *svga = &paradise->svga;

	if (svga->seqregs[0x11] & 0x80) {
//                pclog("Remap 1\n");
		paradise->read_bank[0] = paradise->read_bank[2] = (svga->gdcreg[0x9] & 0x7f) << 12;
		paradise->read_bank[1] = paradise->read_bank[3] = ((svga->gdcreg[0x9] & 0x7f) << 12) + ((svga->gdcreg[6] & 0x08) ? 0 : 0x8000);
		paradise->write_bank[0] = paradise->write_bank[2] = (svga->gdcreg[0xa] & 0x7f) << 12;
		paradise->write_bank[1] = paradise->write_bank[3] = ((svga->gdcreg[0xa] & 0x7f) << 12) + ((svga->gdcreg[6] & 0x08) ? 0 : 0x8000);
	} else if (svga->gdcreg[0xb] & 0x08) {
		if (svga->gdcreg[0x6] & 0xc) {
//                pclog("Remap 2\n");
			paradise->read_bank[0] = paradise->read_bank[2] = (svga->gdcreg[0xa] & 0x7f) << 12;
			paradise->write_bank[0] = paradise->write_bank[2] = (svga->gdcreg[0xa] & 0x7f) << 12;
			paradise->read_bank[1] = paradise->read_bank[3] = ((svga->gdcreg[0x9] & 0x7f) << 12) + ((svga->gdcreg[6] & 0x08) ? 0 : 0x8000);
			paradise->write_bank[1] = paradise->write_bank[3] = ((svga->gdcreg[0x9] & 0x7f) << 12) + ((svga->gdcreg[6] & 0x08) ? 0 : 0x8000);
		} else {
//                pclog("Remap 3\n");
			paradise->read_bank[0] = paradise->write_bank[0] = (svga->gdcreg[0xa] & 0x7f) << 12;
			paradise->read_bank[1] = paradise->write_bank[1] = ((svga->gdcreg[0xa] & 0x7f) << 12) + ((svga->gdcreg[6] & 0x08) ? 0 : 0x8000);
			paradise->read_bank[2] = paradise->write_bank[2] = (svga->gdcreg[0x9] & 0x7f) << 12;
			paradise->read_bank[3] = paradise->write_bank[3] = ((svga->gdcreg[0x9] & 0x7f) << 12) + ((svga->gdcreg[6] & 0x08) ? 0 : 0x8000);
		}
	} else {
		//              pclog("Remap 4\n");
		paradise->read_bank[0] = paradise->read_bank[2] = (svga->gdcreg[0x9] & 0x7f) << 12;
		paradise->read_bank[1] = paradise->read_bank[3] = ((svga->gdcreg[0x9] & 0x7f) << 12) + ((svga->gdcreg[6] & 0x08) ? 0 : 0x8000);
		paradise->write_bank[0] = paradise->write_bank[2] = (svga->gdcreg[0x9] & 0x7f) << 12;
		paradise->write_bank[1] = paradise->write_bank[3] = ((svga->gdcreg[0x9] & 0x7f) << 12) + ((svga->gdcreg[6] & 0x08) ? 0 : 0x8000);
	}
	paradise->read_bank[1] &= 0x7ffff;
	paradise->write_bank[1] &= 0x7ffff;

//        pclog("Remap - %04X %04X  %04x %04x\n", paradise->read_bank[0], paradise->write_bank[0], paradise->read_bank[1], paradise->write_bank[1]);
}

void paradise_recalctimings(svga_t *svga) {
	svga->lowres = !(svga->gdcreg[0xe] & 0x01);
	if (svga->bpp == 8 && !svga->lowres)
		svga->render = svga_render_8bpp_highres;
}

static void paradise_write(uint32_t addr, uint8_t val, void *p) {
	paradise_t *paradise = (paradise_t *)p;
//        pclog("paradise_write : %05X %02X  ", addr, val);
	addr = (addr & 0x7fff) + paradise->write_bank[(addr >> 15) & 3];
//        pclog("%08X\n", addr);
	svga_write_linear(addr, val, &paradise->svga);
}
static void paradise_writew(uint32_t addr, uint16_t val, void *p) {
	paradise_t *paradise = (paradise_t *)p;
	addr = (addr & 0x7fff) + paradise->write_bank[(addr >> 15) & 3];
	svga_writew_linear(addr, val, &paradise->svga);
}

static uint8_t paradise_read(uint32_t addr, void *p) {
	paradise_t *paradise = (paradise_t *)p;
//        pclog("paradise_read : %05X ", addr);
	addr = (addr & 0x7fff) + paradise->read_bank[(addr >> 15) & 3];
//        pclog("%08X\n", addr);
	return svga_read_linear(addr, &paradise->svga);
}
static uint16_t paradise_readw(uint32_t addr, void *p) {
	paradise_t *paradise = (paradise_t *)p;
	addr = (addr & 0x7fff) + paradise->read_bank[(addr >> 15) & 3];
	return svga_readw_linear(addr, &paradise->svga);
}

void *paradise_pvga1a_init() {
	paradise_t *paradise = malloc(sizeof(paradise_t));
	svga_t *svga = &paradise->svga;
	memset(paradise, 0, sizeof(paradise_t));

	io_sethandler(0x03c0, 0x0020, paradise_in, NULL, NULL, paradise_out, NULL, NULL, paradise);

	svga_init(&paradise->svga, paradise, 1 << 18, /*256kb*/
		  NULL,
		  paradise_in, paradise_out,
		  NULL,
		  NULL);

	mem_mapping_set_handler(&paradise->svga.mapping, paradise_read, paradise_readw, NULL, paradise_write, paradise_writew, NULL);
	mem_mapping_set_p(&paradise->svga.mapping, paradise);

	svga->crtc[0x31] = 'W';
	svga->crtc[0x32] = 'D';
	svga->crtc[0x33] = '9';
	svga->crtc[0x34] = '0';
	svga->crtc[0x35] = 'C';

	svga->bpp = 8;
	svga->miscout = 1;

	paradise->type = PVGA1A;

	return paradise;
}

void *paradise_wd90c11_init() {
	paradise_t *paradise = malloc(sizeof(paradise_t));
	svga_t *svga = &paradise->svga;
	memset(paradise, 0, sizeof(paradise_t));

	io_sethandler(0x03c0, 0x0020, paradise_in, NULL, NULL, paradise_out, NULL, NULL, paradise);

	svga_init(&paradise->svga, paradise, 1 << 19, /*512kb*/
		  paradise_recalctimings,
		  paradise_in, paradise_out,
		  NULL,
		  NULL);

	mem_mapping_set_handler(&paradise->svga.mapping, paradise_read, paradise_readw, NULL, paradise_write, paradise_writew, NULL);
	mem_mapping_set_p(&paradise->svga.mapping, paradise);

	svga->crtc[0x31] = 'W';
	svga->crtc[0x32] = 'D';
	svga->crtc[0x33] = '9';
	svga->crtc[0x34] = '0';
	svga->crtc[0x35] = 'C';
	svga->crtc[0x36] = '1';
	svga->crtc[0x37] = '1';

	svga->bpp = 8;
	svga->miscout = 1;

	paradise->type = WD90C11;

	return paradise;
}

static void *paradise_pvga1a_pc2086_init() {
	paradise_t *paradise = paradise_pvga1a_init();

	if (paradise)
		rom_init(&paradise->bios_rom, "pc2086/40186.ic171", 0xc0000, 0x8000, 0x7fff, 0, MEM_MAPPING_EXTERNAL);

	return paradise;
}
static void *paradise_pvga1a_pc3086_init() {
	paradise_t *paradise = paradise_pvga1a_init();

	if (paradise)
		rom_init(&paradise->bios_rom, "pc3086/c000.bin", 0xc0000, 0x8000, 0x7fff, 0, MEM_MAPPING_EXTERNAL);

	return paradise;
}

static void *paradise_wd90c11_megapc_init() {
	paradise_t *paradise = paradise_wd90c11_init();

	if (paradise)
		rom_init_interleaved(&paradise->bios_rom,
				     "megapc/41651-bios lo.u18",
				     "megapc/211253-bios hi.u19",
				     0xc0000, 0x8000, 0x7fff, 0, MEM_MAPPING_EXTERNAL);

	return paradise;
}

static void *paradise_pvga1a_oli_go481_init() {
	paradise_t *paradise = paradise_pvga1a_init();

	if (paradise)
		rom_init_interleaved(&paradise->bios_rom,
				     "oli_go481_lo.bin",
				     "oli_go481_hi.bin",
				     0xc0000, 0x8000, 0x7fff, 0, MEM_MAPPING_EXTERNAL);

	io_sethandler(0x46e8, 0x0001, paradise_in, NULL, NULL, paradise_out, NULL, NULL, paradise);

	return paradise;
}

void paradise_close(void *p) {
	paradise_t *paradise = (paradise_t *)p;

	svga_close(&paradise->svga);

	free(paradise);
}

void paradise_speed_changed(void *p) {
	paradise_t *paradise = (paradise_t *)p;

	svga_recalctimings(&paradise->svga);
}

void paradise_force_redraw(void *p) {
	paradise_t *paradise = (paradise_t *)p;

	paradise->svga.fullchange = changeframecount;
}

void paradise_add_status_info(char *s, int max_len, void *p) {
	paradise_t *paradise = (paradise_t *)p;

	svga_add_status_info(s, max_len, &paradise->svga);
}

static int oli_go481_available() {
	return (rom_present("oli_go481_lo.bin") && rom_present("oli_go481_hi.bin"));
}

device_t paradise_pvga1a_pc2086_device =
	{
		"Paradise PVGA1A (Amstrad PC2086)",
		0,
		paradise_pvga1a_pc2086_init,
		paradise_close,
		NULL,
		paradise_speed_changed,
		paradise_force_redraw,
		paradise_add_status_info
	};
device_t paradise_pvga1a_pc3086_device =
	{
		"Paradise PVGA1A (Amstrad PC3086)",
		0,
		paradise_pvga1a_pc3086_init,
		paradise_close,
		NULL,
		paradise_speed_changed,
		paradise_force_redraw,
		paradise_add_status_info
	};
device_t paradise_wd90c11_megapc_device =
	{
		"Paradise WD90C11 (Amstrad MegaPC)",
		0,
		paradise_wd90c11_megapc_init,
		paradise_close,
		NULL,
		paradise_speed_changed,
		paradise_force_redraw,
		paradise_add_status_info
	};
device_t paradise_pvga1a_oli_go481_device =
	{
		"Paradise PVGA1A (Olivetti GO481)",
		0,
		paradise_pvga1a_oli_go481_init,
		paradise_close,
		oli_go481_available,
		paradise_speed_changed,
		paradise_force_redraw,
		paradise_add_status_info
	};
