#include "ibm.h"
#include "io.h"
#include "mem.h"
#include "cpu.h"
#include "x86.h"

#include "headland.h"

static int headland_index;
static uint8_t headland_regs[256];
static uint8_t headland_port_92 = 0xFC, headland_ems_mar = 0, headland_cri = 0;
static uint8_t headland_regs_cr[8] = {0, 0, 0, 0, 0, 0, 0, 0};
static uint16_t headland_ems_mr[64];

static mem_mapping_t headland_low_mapping;
static mem_mapping_t headland_ems_mapping[64];
static mem_mapping_t headland_mid_mapping;
static mem_mapping_t headland_high_mapping;
static mem_mapping_t headland_4000_9FFF_mapping[24];

// TODO - Headland chipset's memory address mapping emulation isn't fully implemented yet, so memory configuration is hardcoded now.
static int headland_mem_conf_cr0[41] = {0x00, 0x00, 0x20, 0x40, 0x60, 0xA0, 0x40, 0xE0,
	0xA0, 0xC0, 0xE0, 0xE0, 0xC0, 0xE0, 0xE0, 0xE0,
	0xE0, 0x20, 0x40, 0x40, 0xA0, 0xC0, 0xE0, 0xE0,
	0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0,
	0x20, 0x40, 0x60, 0x60, 0xC0, 0xE0, 0xE0, 0xE0,
	0xE0};
static int headland_mem_conf_cr1[41] = {0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40,
	0x00, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
	0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
	0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
	0x40};

uint32_t get_headland_addr(uint32_t addr, uint16_t *mr) {
	if (mr && (headland_regs_cr[0] & 2) && (*mr & 0x200)) {
		addr = (addr & 0x3fff) | ((*mr & 0x1F) << 14);

		if (headland_regs_cr[1] & 0x40) {
			if ((headland_regs_cr[4] & 0x80) && (headland_regs_cr[6] & 1)) {
				if (headland_regs_cr[0] & 0x80) {
					addr |= (*mr & 0x60) << 14;
					if (*mr & 0x100)
						addr += ((*mr & 0xC00) << 13) + (((*mr & 0x80) + 0x80) << 15);
					else
						addr += (*mr & 0x80) << 14;
				} else if (*mr & 0x100)
					addr += ((*mr & 0xC00) << 13) + (((*mr & 0x80) + 0x20) << 15);
				else
					addr += (*mr & 0x80) << 12;
			} else if (headland_regs_cr[0] & 0x80)
				addr |= (*mr & 0x100) ? ((*mr & 0x80) + 0x400) << 12 : (*mr & 0xE0) << 14;
			else
				addr |= (*mr & 0x100) ? ((*mr & 0xE0) + 0x40) << 14 : (*mr & 0x80) << 12;
		} else {
			if ((headland_regs_cr[4] & 0x80) && (headland_regs_cr[6] & 1)) {
				if (headland_regs_cr[0] & 0x80) {
					addr |= ((*mr & 0x60) << 14);
					if (*mr & 0x180)
						addr += ((*mr & 0xC00) << 13) + (((*mr & 0x180) - 0x60) << 16);
				} else
					addr |= ((*mr & 0x60) << 14) | ((*mr & 0x180) << 16) | ((*mr & 0xC00) << 13);
			} else if (headland_regs_cr[0] & 0x80)
				addr |= (*mr & 0x1E0) << 14;
			else
				addr |= (*mr & 0x180) << 12;
		}
	} else if (mr == NULL && (headland_regs_cr[0] & 4) == 0 && mem_size >= 1024 && addr >= 0x100000)
		addr -= 0x60000;

	return addr;
}

void headland_set_global_EMS_state(int state) {
	int i;
	uint32_t base_addr, virt_addr;

	for (i = 0; i < 32; i++) {
		base_addr = (i + 16) << 14;
		if (i >= 24)
			base_addr += 0x20000;
		if ((state & 2) && (headland_ems_mr[((state & 1) << 5) | i] & 0x200)) {
			virt_addr = get_headland_addr(base_addr, &headland_ems_mr[((state & 1) << 5) | i]);
			if (i < 24)
				mem_mapping_disable(&headland_4000_9FFF_mapping[i]);
			mem_mapping_disable(&headland_ems_mapping[(((state ^ 1) & 1) << 5) | i]);
			mem_mapping_enable(&headland_ems_mapping[((state & 1) << 5) | i]);
			if (virt_addr < (mem_size << 10))
				mem_mapping_set_exec(&headland_ems_mapping[((state & 1) << 5) | i], ram + virt_addr);
			else
				mem_mapping_set_exec(&headland_ems_mapping[((state & 1) << 5) | i], NULL);
		} else {
			mem_mapping_set_exec(&headland_ems_mapping[((state & 1) << 5) | i], ram + base_addr);
			mem_mapping_disable(&headland_ems_mapping[(((state ^ 1) & 1) << 5) | i]);
			mem_mapping_disable(&headland_ems_mapping[((state & 1) << 5) | i]);

			if (i < 24)
				mem_mapping_enable(&headland_4000_9FFF_mapping[i]);
		}
	}
	flushmmucache();
}

void headland_memmap_state_update() {
	int i;
	uint32_t addr;

	for (i = 0; i < 24; i++) {
		addr = get_headland_addr(0x40000 + (i << 14), NULL);
		mem_mapping_set_exec(&headland_4000_9FFF_mapping[i], addr < (mem_size << 10) ? ram + addr : NULL);
		//pclog("headland_memmap_state_update : Address %06X to %06X\n", 0x40000 + (i << 14), addr);
	}

	mem_set_mem_state(0xA0000, 0x40000, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL);

	if (mem_size > 640) {
		if ((headland_regs_cr[0] & 4) == 0) {
			mem_mapping_set_addr(&headland_mid_mapping, 0x100000, mem_size > 1024 ? 0x60000 : (mem_size - 640) << 10);
			mem_mapping_set_exec(&headland_mid_mapping, ram + 0xA0000);
			if (mem_size > 1024) {
				mem_mapping_set_addr(&headland_high_mapping, 0x160000, (mem_size - 1024) << 10);
				mem_mapping_set_exec(&headland_high_mapping, ram + 0x100000);
			}
		} else {
			mem_mapping_set_addr(&headland_mid_mapping, 0xA0000, mem_size > 1024 ? 0x60000 : (mem_size - 640) << 10);
			mem_mapping_set_exec(&headland_mid_mapping, ram + 0xA0000);
			if (mem_size > 1024) {
				mem_mapping_set_addr(&headland_high_mapping, 0x100000, (mem_size - 1024) << 10);
				mem_mapping_set_exec(&headland_high_mapping, ram + 0x100000);
			}
		}
	}

	headland_set_global_EMS_state(headland_regs_cr[0] & 3);
}

void headland_write(uint16_t addr, uint8_t val, void *priv) {
	uint8_t old_val, index;
	uint32_t base_addr, virt_addr;

	switch (addr) {
	case 0x22:headland_index = val;
		break;

	case 0x23:old_val = headland_regs[headland_index];

		if (headland_index == 0xc1 && !is486)
			val = 0;
		headland_regs[headland_index] = val;
		//pclog("Headland write %02X %02X at %04X:%04X\n",headland_index,val,CS,cpu_state.oldpc);
		if (headland_index == 0x82) {
			if (val & 0x10)
				mem_set_mem_state(0xf0000, 0x10000, MEM_READ_INTERNAL | MEM_WRITE_DISABLED);
			else
				mem_set_mem_state(0xf0000, 0x10000, MEM_READ_EXTERNAL | MEM_WRITE_INTERNAL);
		} else if (headland_index == 0x87) {
			if ((val & 1) && !(old_val & 1))
				softresetx86();
		}
		break;

	case 0x92:
		if ((mem_a20_alt ^ val) & 2) {
			mem_a20_alt = val & 2;
			mem_a20_recalc();
		}
		if ((~headland_port_92 & val) & 1) {
			softresetx86();
			cpu_set_edx();
		}
		headland_port_92 = val | 0xFC;
		break;

	case 0x1EC:
		//pclog("Set EMS Map Register to %02X(actually FF%02X) at %04X:%04X\n", val, val, CS, cpu_state.oldpc);
		headland_ems_mr[headland_ems_mar & 0x3F] = val | 0xFF00;
		index = headland_ems_mar & 0x1F;
		base_addr = (index + 16) << 14;
		if (index >= 24)
			base_addr += 0x20000;
		if ((headland_regs_cr[0] & 2) && ((headland_regs_cr[0] & 1) == ((headland_ems_mar & 0x20) >> 5))) {
			virt_addr = get_headland_addr(base_addr, &headland_ems_mr[headland_ems_mar & 0x3F]);
			if (index < 24)
				mem_mapping_disable(&headland_4000_9FFF_mapping[index]);
			if (virt_addr < (mem_size << 10))
				mem_mapping_set_exec(&headland_ems_mapping[headland_ems_mar & 0x3F], ram + virt_addr);
			else
				mem_mapping_set_exec(&headland_ems_mapping[headland_ems_mar & 0x3F], NULL);
			mem_mapping_enable(&headland_ems_mapping[headland_ems_mar & 0x3F]);
			pclog("Map page %d(address %05X) to address %06X\n", index, base_addr, virt_addr);
			flushmmucache();
		}
		if (headland_ems_mar & 0x80)
			headland_ems_mar++;
		break;

	case 0x1ED:
		//pclog("Set Control Register Index to %02X at %04X:%04X\n", val, CS, cpu_state.oldpc);
		headland_cri = val;
		break;

	case 0x1EE:
		//pclog("Set EMS Map Address Register to %02X at %04X:%04X\n", val, CS, cpu_state.oldpc);
		headland_ems_mar = val;
		break;

	case 0x1EF:
		//pclog("Set Control Register %1X to %02X at %04X:%04X\n", headland_cri, val, CS, cpu_state.oldpc);
		old_val = headland_regs_cr[headland_cri];
		switch (headland_cri) {
		case 0:headland_regs_cr[0] = (val & 0x1F) | headland_mem_conf_cr0[(mem_size > 640 ? mem_size : mem_size - 128) >> 9];
			mem_set_mem_state(0xE0000, 0x10000, (val & 8 ? MEM_READ_INTERNAL : MEM_READ_EXTERNAL) | MEM_WRITE_DISABLED);
			mem_set_mem_state(0xF0000, 0x10000, (val & 0x10 ? MEM_READ_INTERNAL : MEM_READ_EXTERNAL) | MEM_WRITE_DISABLED);
			headland_memmap_state_update();
			break;
		case 1:headland_regs_cr[1] = (val & 0xBF) | headland_mem_conf_cr1[(mem_size > 640 ? mem_size : mem_size - 128) >> 9];
			headland_memmap_state_update();
			break;
		case 2:
		case 3:
		case 5:headland_regs_cr[headland_cri] = val;
			headland_memmap_state_update();
			break;
		case 4:headland_regs_cr[4] = (headland_regs_cr[4] & 0xF0) | (val & 0x0F);
			if (val & 1) {
				mem_mapping_disable(&bios_mapping[0]);
				mem_mapping_disable(&bios_mapping[1]);
				mem_mapping_disable(&bios_mapping[2]);
				mem_mapping_disable(&bios_mapping[3]);
			} else {
				mem_mapping_enable(&bios_mapping[0]);
				mem_mapping_enable(&bios_mapping[1]);
				mem_mapping_enable(&bios_mapping[2]);
				mem_mapping_enable(&bios_mapping[3]);
			}
			break;
		case 6:
			if (headland_regs_cr[4] & 0x80) {
				headland_regs_cr[headland_cri] = (val & 0xFE) | (mem_size > 8192 ? 1 : 0);
				headland_memmap_state_update();
			}
			break;
		default:break;
		}
		break;

	default:break;
	}
}

void headland_writew(uint16_t addr, uint16_t val, void *priv) {
	uint8_t index;
	uint32_t base_addr, virt_addr;

	switch (addr) {
	case 0x1EC:
		//pclog("Set EMS Map Register to %04X at %04X:%04X\n", val, CS, cpu_state.oldpc);
		headland_ems_mr[headland_ems_mar & 0x3F] = val;
		index = headland_ems_mar & 0x1F;
		base_addr = (index + 16) << 14;
		if (index >= 24)
			base_addr += 0x20000;
		if ((headland_regs_cr[0] & 2) && (headland_regs_cr[0] & 1) == ((headland_ems_mar & 0x20) >> 5)) {
			if (val & 0x200) {
				virt_addr = get_headland_addr(base_addr, &headland_ems_mr[headland_ems_mar & 0x3F]);
				if (index < 24)
					mem_mapping_disable(&headland_4000_9FFF_mapping[index]);
				if (virt_addr < (mem_size << 10))
					mem_mapping_set_exec(&headland_ems_mapping[headland_ems_mar & 0x3F], ram + virt_addr);
				else
					mem_mapping_set_exec(&headland_ems_mapping[headland_ems_mar & 0x3F], NULL);
				mem_mapping_enable(&headland_ems_mapping[headland_ems_mar & 0x3F]);
				//pclog("Map page %d(address %05X) to address %07X(val=%04X)\n", index, base_addr, virt_addr, val);
			} else {
				mem_mapping_set_exec(&headland_ems_mapping[headland_ems_mar & 0x3F], ram + base_addr);
				mem_mapping_disable(&headland_ems_mapping[headland_ems_mar & 0x3F]);
				if (index < 24)
					mem_mapping_enable(&headland_4000_9FFF_mapping[index]);
				//pclog("Unmap page %d(address %05X)\n", index, base_addr);
			}
			flushmmucache();
		}
		if (headland_ems_mar & 0x80)
			headland_ems_mar++;
		break;

	default:break;
	}
}

uint8_t headland_read(uint16_t addr, void *priv) {
	uint8_t val;

	switch (addr) {
	case 0x22:val = headland_index;
		break;

	case 0x23:
		if ((headland_index >= 0xc0 || headland_index == 0x20) && cpu_iscyrix)
			val = 0xff; /*Don't conflict with Cyrix config registers*/
		else
			val = headland_regs[headland_index];
		break;

	case 0x92:val = headland_port_92 | 0xFC;
		break;

	case 0x1EC:val = headland_ems_mr[headland_ems_mar & 0x3F];
		if (headland_ems_mar & 0x80)
			headland_ems_mar++;
		break;

	case 0x1ED:val = headland_cri;
		break;

	case 0x1EE:val = headland_ems_mar;
		break;

	case 0x1EF:
		switch (headland_cri) {
		case 0:val = (headland_regs_cr[0] & 0x1F) | headland_mem_conf_cr0[(mem_size > 640 ? mem_size : mem_size - 128) >> 9];
			break;
		case 1:val = (headland_regs_cr[1] & 0xBF) | headland_mem_conf_cr1[(mem_size > 640 ? mem_size : mem_size - 128) >> 9];
			break;
		case 6:
			if (headland_regs_cr[4] & 0x80)
				val = (headland_regs_cr[6] & 0xFE) | (mem_size > 8192 ? 1 : 0);
			else
				val = 0;
			break;
		default:val = headland_regs_cr[headland_cri];
			break;
		}
		break;

	default:val = 0xFF;
		break;
	}

	return val;
}

uint16_t headland_readw(uint16_t addr, void *priv) {
	uint16_t val;

	switch (addr) {
	case 0x1EC:val = headland_ems_mr[headland_ems_mar & 0x3F] | ((headland_regs_cr[4] & 0x80) ? 0xF000 : 0xFC00);
		if (headland_ems_mar & 0x80)
			headland_ems_mar++;
		break;

	default:val = 0xFFFF;
		break;
	}

	return val;
}

uint8_t mem_read_headlandb(uint32_t addr, void *priv) {
	uint8_t val = 0xff;

	addr = get_headland_addr(addr, priv);
	if (addr < (mem_size << 10))
		val = ram[addr];

	return val;
}

void mem_write_headlandb(uint32_t addr, uint8_t val, void *priv) {
	addr = get_headland_addr(addr, priv);
	if (addr < (mem_size << 10))
		ram[addr] = val;
}

uint16_t mem_read_headlandw(uint32_t addr, void *priv) {
	uint16_t val = 0xffff;

	if ((addr & 0x3FFF) == 0x3FFF)
		pclog("mem_read_headlandw(%08X, %p) called.\n", addr, priv);
	addr = get_headland_addr(addr, priv);
	if (addr < (mem_size << 10))
		val = *(uint16_t *)&ram[addr];

	return val;
}

void mem_write_headlandw(uint32_t addr, uint16_t val, void *priv) {
	if ((addr & 0x3FFF) == 0x3FFF)
		pclog("mem_write_headlandw(%08X, %04X, %p) called.\n", addr, val, priv);
	addr = get_headland_addr(addr, priv);
	if (addr < (mem_size << 10))
		*(uint16_t *)&ram[addr] = val;
}

uint32_t mem_read_headlandl(uint32_t addr, void *priv) {
	uint32_t val = 0xffffffff;

	if ((addr & 0x3FFF) > 0x3FFC)
		pclog("mem_read_headlandl(%08X, %p) called.\n", addr, priv);
	addr = get_headland_addr(addr, priv);
	if (addr < (mem_size << 10))
		val = *(uint32_t *)&ram[addr];

	return val;
}

void mem_write_headlandl(uint32_t addr, uint32_t val, void *priv) {
	if ((addr & 0x3FFF) > 0x3FFC)
		pclog("mem_write_headland(%08X, %08X, %p) called.\n", addr, val, priv);
	addr = get_headland_addr(addr, priv);
	if (addr < (mem_size << 10))
		*(uint32_t *)&ram[addr] = val;
}

void headland_init() {
	int i;

	for (i = 0; i < 8; i++)
		headland_regs_cr[i] = 0;
	headland_regs_cr[0] = 4;

	switch (romset) {
	case ROM_AMI386SX:
		// Remark - Previously distributed AMI386 BIOS doesn't seem to be for the Headland chipset.
		io_sethandler(0x0022, 0x0002, headland_read, NULL, NULL, headland_write, NULL, NULL, NULL);
		break;
	case ROM_AMA932J:headland_regs_cr[4] = 0x20;
		io_sethandler(0x0092, 0x0001, headland_read, NULL, NULL, headland_write, NULL, NULL, NULL);
		break;
	default:headland_regs_cr[4] = 0;
		break;
	}

	io_sethandler(0x01EC, 0x0001, headland_read, headland_readw, NULL, headland_write, headland_writew, NULL, NULL);
	io_sethandler(0x01ED, 0x0003, headland_read, NULL, NULL, headland_write, NULL, NULL, NULL);

	for (i = 0; i < 64; i++)
		headland_ems_mr[i] = 0;

	mem_mapping_disable(&ram_low_mapping);
	mem_mapping_disable(&ram_mid_mapping);
	mem_mapping_disable(&ram_high_mapping);

	mem_mapping_add(&headland_low_mapping,
			0,
			0x40000,
			mem_read_headlandb,
			mem_read_headlandw,
			mem_read_headlandl,
			mem_write_headlandb,
			mem_write_headlandw,
			mem_write_headlandl,
			ram,
			MEM_MAPPING_INTERNAL,
			NULL);

	if (mem_size > 640) {
		mem_mapping_add(&headland_mid_mapping,
				0xA0000,
				0x60000,
				mem_read_headlandb,
				mem_read_headlandw,
				mem_read_headlandl,
				mem_write_headlandb,
				mem_write_headlandw,
				mem_write_headlandl,
				ram + 0xA0000,
				MEM_MAPPING_INTERNAL,
				NULL);
		mem_mapping_enable(&headland_mid_mapping);
	}

	if (mem_size > 1024) {
		mem_mapping_add(&headland_high_mapping,
				0x100000,
				((mem_size - 1024) * 1024),
				mem_read_headlandb,
				mem_read_headlandw,
				mem_read_headlandl,
				mem_write_headlandb,
				mem_write_headlandw,
				mem_write_headlandl,
				ram + 0x100000,
				MEM_MAPPING_INTERNAL,
				NULL);
		mem_mapping_enable(&headland_high_mapping);
	}

	for (i = 0; i < 24; i++) {
		mem_mapping_add(&headland_4000_9FFF_mapping[i],
				0x40000 + (i << 14),
				0x4000,
				mem_read_headlandb,
				mem_read_headlandw,
				mem_read_headlandl,
				mem_write_headlandb,
				mem_write_headlandw,
				mem_write_headlandl,
				mem_size > 256 + (i << 4) ? ram + 0x40000 + (i << 14) : NULL,
				MEM_MAPPING_INTERNAL,
				NULL);
		mem_mapping_enable(&headland_4000_9FFF_mapping[i]);
	}

	for (i = 0; i < 64; i++) {
		headland_ems_mr[i] = 0;
		mem_mapping_add(&headland_ems_mapping[i],
				((i & 31) + ((i & 31) >= 24 ? 24 : 16)) << 14,
				0x04000,
				mem_read_headlandb,
				mem_read_headlandw,
				mem_read_headlandl,
				mem_write_headlandb,
				mem_write_headlandw,
				mem_write_headlandl,
				ram + (((i & 31) + ((i & 31) >= 24 ? 24 : 16)) << 14),
				0,
				&headland_ems_mr[i]);
		mem_mapping_disable(&headland_ems_mapping[i]);
	}

	headland_memmap_state_update();
}
