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

static struct {
        int reg_idx;
        uint8_t regs[256];
        uint8_t port_62;
} acc2168;

static void acc2168_shadow_recalc() {
        if (acc2168.regs[0x02] & 8) {
                switch (acc2168.regs[0x02] & 0x30) {
                case 0x00:
                        mem_set_mem_state(0xf0000, 0x10000, MEM_READ_EXTERNAL | MEM_WRITE_INTERNAL);
                        break;
                case 0x10:
                        mem_set_mem_state(0xf0000, 0x10000, MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                        break;
                case 0x20:
                        mem_set_mem_state(0xf0000, 0x10000, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL);
                        break;
                case 0x30:
                        mem_set_mem_state(0xf0000, 0x10000, MEM_READ_INTERNAL | MEM_WRITE_EXTERNAL);
                        break;
                }
        } else
                mem_set_mem_state(0xf0000, 0x10000, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL);

        if (acc2168.regs[0x02] & 4) {
                switch (acc2168.regs[0x02] & 0x30) {
                case 0x00:
                        mem_set_mem_state(0xe0000, 0x10000, MEM_READ_EXTERNAL | MEM_WRITE_INTERNAL);
                        break;
                case 0x10:
                        mem_set_mem_state(0xe0000, 0x10000, MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                        break;
                case 0x20:
                        mem_set_mem_state(0xe0000, 0x10000, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL);
                        break;
                case 0x30:
                        mem_set_mem_state(0xe0000, 0x10000, MEM_READ_INTERNAL | MEM_WRITE_EXTERNAL);
                        break;
                }
        } else
                mem_set_mem_state(0xe0000, 0x10000, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL);
}

static void acc2168_write(uint16_t addr, uint8_t val, void *p) {
        //        pclog("acc2168_write: addr=%04x val=%02x\n", addr, val);
        if (!(addr & 1))
                acc2168.reg_idx = val;
        else {
                acc2168.regs[acc2168.reg_idx] = val;
                //                pclog("acc2168_write: idx=%02x val=%02x %04x:%04x\n", acc2168.reg_idx, val, CS,cpu_state.pc);

                switch (acc2168.reg_idx) {
                case 0x02:
                        acc2168_shadow_recalc();
                        break;
                }
        }
}

static uint8_t acc2168_read(uint16_t addr, void *p) {
        if (!(addr & 1))
                return acc2168.reg_idx;

        //        pclog("acc2168_read: idx=%02x val=%02x\n", acc2168.reg_idx, acc2168.regs[acc2168.reg_idx]);
        return acc2168.regs[acc2168.reg_idx];
}

static uint8_t port78_read(uint16_t addr, void *p) { return 0; }

static uint8_t port92_read(uint16_t addr, void *p) { return acc2168.port_62 | 0xfc; }

static void port92_write(uint16_t addr, uint8_t val, void *p) {
        if ((mem_a20_alt ^ val) & 2) {
                mem_a20_alt = val & 2;
                mem_a20_recalc();
        }
        if ((~acc2168.port_62 & val) & 1) {
                softresetx86();
                cpu_set_edx();
        }
        acc2168.port_62 = val | 0xfC;
}

void acc2168_init() {
        memset(&acc2168, 0, sizeof(acc2168));
        io_sethandler(0x00f2, 0x0002, acc2168_read, NULL, NULL, acc2168_write, NULL, NULL, NULL);
        io_sethandler(0x0078, 0x0001, port78_read, NULL, NULL, NULL, NULL, NULL, NULL);
        io_sethandler(0x0092, 0x0001, port92_read, NULL, NULL, port92_write, NULL, NULL, NULL);
}
