#include "ibm.h"
#include "io.h"
#include "mem.h"
#include "vl82c480.h"

static struct {
	int cfg_index;
	uint8_t cfg_regs[256];

	uint8_t port_92;
} vl82c480;

#define CFG_ID     0x00
#define CFG_AAXS   0x0d
#define CFG_BAXS   0x0e
#define CFG_CAXS   0x0f
#define CFG_DAXS   0x10
#define CFG_EAXS   0x11
#define CFG_FAXS   0x12

#define ID_VL82C480 0x90

static void shadow_control(uint32_t addr, uint32_t size, int state) {
//        pclog("shadow_control: addr=%08x size=%04x state=%i\n", addr, size, state);
	switch (state) {
	case 0:mem_set_mem_state(addr, size, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL);
		break;
	case 1:mem_set_mem_state(addr, size, MEM_READ_EXTERNAL | MEM_WRITE_INTERNAL);
		break;
	case 2:mem_set_mem_state(addr, size, MEM_READ_INTERNAL | MEM_WRITE_EXTERNAL);
		break;
	case 3:mem_set_mem_state(addr, size, MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
		break;
	}
	flushmmucache_nopc();
}

void vl82c480_write(uint16_t addr, uint8_t val, void *p) {
//        if (addr != 0x92) pclog("vl82c480_write: addr=%04x val=%02x\n", addr, val);
	switch (addr) {
	case 0x92:
		if ((mem_a20_alt ^ val) & 2) {
			mem_a20_alt = val & 2;
			mem_a20_recalc();
		}
		if ((~vl82c480.port_92 & val) & 1) {
			softresetx86();
			cpu_set_edx();
		}
		vl82c480.port_92 = val;
		break;

	case 0xec:vl82c480.cfg_index = val;
		break;

	case 0xed:
		if (vl82c480.cfg_index >= 0x01 && vl82c480.cfg_index <= 0x24) {
			vl82c480.cfg_regs[vl82c480.cfg_index] = val;
			switch (vl82c480.cfg_index) {
			case CFG_AAXS:shadow_control(0xa0000, 0x4000, val & 3);
				shadow_control(0xa4000, 0x4000, (val >> 2) & 3);
				shadow_control(0xa8000, 0x4000, (val >> 4) & 3);
				shadow_control(0xac000, 0x4000, (val >> 6) & 3);
				break;
			case CFG_BAXS:shadow_control(0xb0000, 0x4000, val & 3);
				shadow_control(0xb4000, 0x4000, (val >> 2) & 3);
				shadow_control(0xb8000, 0x4000, (val >> 4) & 3);
				shadow_control(0xbc000, 0x4000, (val >> 6) & 3);
				break;
			case CFG_CAXS:shadow_control(0xc0000, 0x4000, val & 3);
				shadow_control(0xc4000, 0x4000, (val >> 2) & 3);
				shadow_control(0xc8000, 0x4000, (val >> 4) & 3);
				shadow_control(0xcc000, 0x4000, (val >> 6) & 3);
				break;
			case CFG_DAXS:shadow_control(0xd0000, 0x4000, val & 3);
				shadow_control(0xd4000, 0x4000, (val >> 2) & 3);
				shadow_control(0xd8000, 0x4000, (val >> 4) & 3);
				shadow_control(0xdc000, 0x4000, (val >> 6) & 3);
				break;
			case CFG_EAXS:shadow_control(0xe0000, 0x4000, val & 3);
				shadow_control(0xe4000, 0x4000, (val >> 2) & 3);
				shadow_control(0xe8000, 0x4000, (val >> 4) & 3);
				shadow_control(0xec000, 0x4000, (val >> 6) & 3);
				break;
			case CFG_FAXS:shadow_control(0xf0000, 0x4000, val & 3);
				shadow_control(0xf4000, 0x4000, (val >> 2) & 3);
				shadow_control(0xf8000, 0x4000, (val >> 4) & 3);
				shadow_control(0xfc000, 0x4000, (val >> 6) & 3);
				break;
			}
		}
		break;

	case 0xee:
		if (mem_a20_alt) {
			vl82c480.port_92 &= ~2;
			mem_a20_alt = 0;
			mem_a20_recalc();
		}
		break;
	}
}

uint8_t vl82c480_read(uint16_t addr, void *p) {
	uint8_t ret = 0xff;

	switch (addr) {
	case 0x92:ret = vl82c480.port_92;
		break;

	case 0xec:ret = vl82c480.cfg_index;
		break;

	case 0xed:ret = vl82c480.cfg_regs[vl82c480.cfg_index];
		break;

	case 0xee:
		if (!mem_a20_alt) {
			vl82c480.port_92 |= 2;
			mem_a20_alt = 1;
			mem_a20_recalc();
		}
		break;

	case 0xef:softresetx86();
		cpu_set_edx();
		break;
	}

//        pclog("vl82c480_read: addr=%04x ret=%02x\n", addr, ret);
	return ret;
}

void vl82c480_init(void) {
	memset(&vl82c480, 0, sizeof(vl82c480));
	vl82c480.cfg_regs[CFG_ID] = ID_VL82C480;

	io_sethandler(0x0092, 0x0001, vl82c480_read, NULL, NULL, vl82c480_write, NULL, NULL, NULL);
	io_sethandler(0x00ec, 0x0004, vl82c480_read, NULL, NULL, vl82c480_write, NULL, NULL, NULL);
}
