#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include "cpu.h"
#include <86box/io.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/plat_unused.h>
#include <86box/chipset.h>

typedef struct rabbit_t {
    uint8_t cur_reg;
    uint8_t tries;
    uint8_t regs[258];
} rabbit_t;

static void
rabbit_recalcmapping(rabbit_t *dev)
{
    uint32_t shread;
    uint32_t shwrite;
    uint32_t shflags = 0;

    shread  = !!(dev->regs[0x101] & 0x40);
    shwrite = !!(dev->regs[0x100] & 0x02);

    shflags = shread ? MEM_READ_INTERNAL : MEM_READ_EXTANY;
    shflags |= shwrite ? MEM_WRITE_INTERNAL : MEM_WRITE_EXTANY;

    shadowbios       = !!shread;
    shadowbios_write = !!shwrite;

#ifdef USE_SHADOW_C0000
    mem_set_mem_state(0x000c0000, 0x00040000, MEM_READ_EXTANY | MEM_WRITE_EXTANY);
#else
    mem_set_mem_state(0x000e0000, 0x00020000, MEM_READ_EXTANY | MEM_WRITE_EXTANY);
#endif

    switch (dev->regs[0x100] & 0x09) {
        case 0x01:
/* The one BIOS we use seems to use something else to control C0000-DFFFF shadow,
   no idea what. */
#ifdef USE_SHADOW_C0000
            /* 64K at 0C0000-0CFFFF */
            mem_set_mem_state(0x000c0000, 0x00010000, shflags);
            /* FALLTHROUGH */
#endif
        case 0x00:
            /* 64K at 0F0000-0FFFFF */
            mem_set_mem_state(0x000f0000, 0x00010000, shflags);
            break;

        case 0x09:
#ifdef USE_SHADOW_C0000
            /* 128K at 0C0000-0DFFFF */
            mem_set_mem_state(0x000c0000, 0x00020000, shflags);
            /* FALLTHROUGH */
#endif
        case 0x08:
            /* 128K at 0E0000-0FFFFF */
            mem_set_mem_state(0x000e0000, 0x00020000, shflags);
            break;
        default:
            break;
    }

    flushmmucache();
}

static void
rabbit_write(uint16_t addr, uint8_t val, void *priv)
{
    rabbit_t *dev = (rabbit_t *) priv;

    switch (addr) {
        case 0x22:
            dev->cur_reg = val;
            dev->tries   = 0;
            break;
        case 0x23:
            if (dev->cur_reg == 0x83) {
                if (dev->tries < 0x02) {
                    dev->regs[dev->tries++ | 0x100] = val;
                    if (dev->tries == 0x02)
                        rabbit_recalcmapping(dev);
                }
            } else
                dev->regs[dev->cur_reg] = val;
            break;
        default:
            break;
    }
}

static uint8_t
rabbit_read(uint16_t addr, void *priv)
{
    uint8_t   ret = 0xff;
    rabbit_t *dev = (rabbit_t *) priv;

    switch (addr) {
        case 0x23:
            if (dev->cur_reg == 0x83) {
                if (dev->tries < 0x02)
                    ret = dev->regs[dev->tries++ | 0x100];
            } else
                ret = dev->regs[dev->cur_reg];
            break;

        default:
            break;
    }

    return ret;
}

static void
rabbit_close(void *priv)
{
    rabbit_t *dev = (rabbit_t *) priv;

    free(dev);
}

static void *
rabbit_init(UNUSED(const device_t *info))
{
    rabbit_t *dev = (rabbit_t *) calloc(1, sizeof(rabbit_t));

    io_sethandler(0x0022, 0x0002, rabbit_read, NULL, NULL, rabbit_write, NULL, NULL, dev);

    return dev;
}

const device_t rabbit_device = {
    .name          = "SiS Rabbit",
    .internal_name = "rabbit",
    .flags         = 0,
    .local         = 0,
    .init          = rabbit_init,
    .close         = rabbit_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};
