#include "ibm.h"

#include "fdc.h"
#include "fdd.h"
#include "io.h"
#include "lpt.h"
#include "serial.h"
#include "fdc37c93x.h"

typedef struct fdc37c93x_t {
        int index;

        uint8_t global_regs[0x30];
        uint8_t fdc_regs[0x100];
        uint8_t ide1_regs[0x100];
        uint8_t ide2_regs[0x100];
        uint8_t lpt_regs[0x100];
        uint8_t com1_regs[0x100];
        uint8_t com2_regs[0x100];
        uint8_t rtc_regs[0x100];
        uint8_t kbd_regs[0x100];
        uint8_t auxio_regs[0x100];
        uint8_t accessbus_regs[0x100];

        uint8_t lock[2];
} fdc37c93x_t;

static fdc37c93x_t fdc37c93x_global;

static void write_lock(fdc37c93x_t *fdc37c93x, uint8_t val) {
        if (val == 0x55 && fdc37c93x->lock[1] == 0x55)
                fdc_3f1_enable(0);
        if (fdc37c93x->lock[0] == 0x55 && fdc37c93x->lock[1] == 0x55 && val != 0x55)
                fdc_3f1_enable(1);

        fdc37c93x->lock[0] = fdc37c93x->lock[1];
        fdc37c93x->lock[1] = val;
}

void fdc37c93x_write(uint16_t port, uint8_t val, void *p) {
        fdc37c93x_t *fdc37c93x = (fdc37c93x_t *)p;

        //        pclog("Write SuperIO %04x %02x\n", port, val);
        if (fdc37c93x->lock[0] == 0x55 && fdc37c93x->lock[1] == 0x55) {
                if (port == 0x3f0) {
                        if (val == 0xaa)
                                write_lock(fdc37c93x, val);
                        else
                                fdc37c93x->index = val;
                } else {
                        uint16_t addr;
                        int irq;

                        //                        pclog("fdc37c93x_write: index=%02x dev=%i val=%02x\n", fdc37c93x->index,
                        //                        fdc37c93x->global_regs[7], val);
                        if (fdc37c93x->index < 0x30) {
                                if (fdc37c93x->index != 0x20 && fdc37c93x->index != 0x21)
                                        fdc37c93x->global_regs[fdc37c93x->index] = val;
                        } else
                                switch (fdc37c93x->global_regs[7]) {
                                case 0:
                                        fdc37c93x->fdc_regs[fdc37c93x->index] = val;
                                        break;
                                case 1:
                                        fdc37c93x->ide1_regs[fdc37c93x->index] = val;
                                        break;
                                case 2:
                                        fdc37c93x->ide2_regs[fdc37c93x->index] = val;
                                        break;
                                case 3:
                                        fdc37c93x->lpt_regs[fdc37c93x->index] = val;
                                        break;
                                case 4:
                                        fdc37c93x->com1_regs[fdc37c93x->index] = val;
                                        break;
                                case 5:
                                        fdc37c93x->com2_regs[fdc37c93x->index] = val;
                                        break;
                                case 6:
                                        fdc37c93x->rtc_regs[fdc37c93x->index] = val;
                                        break;
                                case 7:
                                        fdc37c93x->kbd_regs[fdc37c93x->index] = val;
                                        break;
                                case 8:
                                        fdc37c93x->auxio_regs[fdc37c93x->index] = val;
                                        break;
                                case 9:
                                        fdc37c93x->accessbus_regs[fdc37c93x->index] = val;
                                        break;
                                }

                        fdc_remove();
                        if ((fdc37c93x->global_regs[0x22] & 0x01) && (fdc37c93x->fdc_regs[0x30] & 0x01)) {
                                //                                pclog("FDC enabled\n");
                                fdc_add();
                        }
                        /*                        else
                                                        pclog("FDC disabled\n");*/

                        lpt1_remove();
                        lpt2_remove();
                        addr = (fdc37c93x->lpt_regs[0x61] & 0xfc) | ((fdc37c93x->lpt_regs[0x60] & 3) << 8);
                        if ((fdc37c93x->global_regs[0x22] & (1 << 3)) && (fdc37c93x->lpt_regs[0x30] & 0x01)) {
                                //                                pclog("LPT addr = %04x\n", addr);
                                lpt1_init(addr);
                        }
                        /*                        else
                                                        pclog("LPT disabled\n");*/

                        serial1_remove();
                        addr = (fdc37c93x->com1_regs[0x61] & 0xf8) | ((fdc37c93x->com1_regs[0x60] & 3) << 8);
                        irq = fdc37c93x->com1_regs[0x70] & 0xf;
                        if ((fdc37c93x->global_regs[0x22] & (1 << 4)) && (fdc37c93x->com1_regs[0x30] & 0x01)) {
                                //                                pclog("COM1 addr = %04x, IRQ = %i\n", addr, irq);
                                serial1_set(addr, irq);
                        }
                        /*                        else
                                                        pclog("COM1 disabled\n");*/

                        serial2_remove();
                        addr = (fdc37c93x->com2_regs[0x61] & 0xf8) | ((fdc37c93x->com2_regs[0x60] & 3) << 8);
                        irq = fdc37c93x->com2_regs[0x70] & 0xf;
                        if ((fdc37c93x->global_regs[0x22] & (1 << 5)) && (fdc37c93x->com2_regs[0x30] & 0x01)) {
                                //                                pclog("COM2 addr = %04x, IRQ = %i\n", addr, irq);
                                serial2_set(addr, irq);
                        }
                        /*                        else
                                                        pclog("COM2 disabled\n");*/

                        fdc_update_enh_mode(fdc37c93x->fdc_regs[0xf0] & 1);

                        fdc_update_densel_force((fdc37c93x->fdc_regs[0xf1] & 0x0c) >> 2);
                        fdd_swap = (fdc37c93x->fdc_regs[0xf0] & (1 << 4)) ? 1 : 0;
                }
        } else {
                if (port == 0x3f0)
                        write_lock(fdc37c93x, val);
        }
}

uint8_t fdc37c93x_read(uint16_t port, void *p) {
        fdc37c93x_t *fdc37c93x = (fdc37c93x_t *)p;

        //        pclog("Read SuperIO %04x %02x\n", port, fdc37c665_curreg);
        if (fdc37c93x->lock[0] == 0x55 && fdc37c93x->lock[1] == 0x55) {
                if (port == 0x3f1) {
                        if (fdc37c93x->index < 0x30)
                                return fdc37c93x->global_regs[fdc37c93x->index];
                        else
                                switch (fdc37c93x->global_regs[7]) {
                                case 0:
                                        return fdc37c93x->fdc_regs[fdc37c93x->index];
                                case 1:
                                        return fdc37c93x->ide1_regs[fdc37c93x->index];
                                case 2:
                                        return fdc37c93x->ide2_regs[fdc37c93x->index];
                                case 3:
                                        return fdc37c93x->lpt_regs[fdc37c93x->index];
                                case 4:
                                        return fdc37c93x->com1_regs[fdc37c93x->index];
                                case 5:
                                        return fdc37c93x->com2_regs[fdc37c93x->index];
                                case 6:
                                        return fdc37c93x->rtc_regs[fdc37c93x->index];
                                case 7:
                                        return fdc37c93x->kbd_regs[fdc37c93x->index];
                                case 8:
                                        return fdc37c93x->auxio_regs[fdc37c93x->index];
                                case 9:
                                        return fdc37c93x->accessbus_regs[fdc37c93x->index];
                                }
                }
        }
        return 0xff;
}

void fdc37c932fr_init() {
        fdc37c93x_t *fdc37c93x = &fdc37c93x_global;
        int c;

        io_sethandler(0x03f0, 0x0002, fdc37c93x_read, NULL, NULL, fdc37c93x_write, NULL, NULL, fdc37c93x);

        fdc_update_is_nsc(0);

        memset(&fdc37c93x_global, 0, sizeof(fdc37c93x_global));

        fdc37c93x->global_regs[0x03] = 0x03;
        fdc37c93x->global_regs[0x20] = 0x03;
        fdc37c93x->global_regs[0x21] = 0x01;
        fdc37c93x->global_regs[0x24] = 0x04;
        fdc37c93x->global_regs[0x26] = 0xf0;
        fdc37c93x->global_regs[0x27] = 0x03;

        fdc37c93x->fdc_regs[0x60] = 0x03;
        fdc37c93x->fdc_regs[0x61] = 0xf0;
        fdc37c93x->fdc_regs[0x70] = 0x06;
        fdc37c93x->fdc_regs[0x74] = 0x02;
        fdc37c93x->fdc_regs[0xf0] = 0x0e;
        fdc37c93x->fdc_regs[0xf2] = 0xff;

        fdc37c93x->ide1_regs[0x60] = 0x01;
        fdc37c93x->ide1_regs[0x61] = 0xf0;
        fdc37c93x->ide1_regs[0x62] = 0x03;
        fdc37c93x->ide1_regs[0x63] = 0xf6;
        fdc37c93x->ide1_regs[0x70] = 0x0e;
        fdc37c93x->ide1_regs[0xf0] = 0x0c;
        fdc37c93x->ide1_regs[0xf1] = 0x0d;

        fdc37c93x->lpt_regs[0x74] = 0x04;
        fdc37c93x->lpt_regs[0xf0] = 0x3c;

        fdc37c93x->com2_regs[0x74] = 0x04;
        fdc37c93x->com2_regs[0xf1] = 0x02;
        fdc37c93x->com2_regs[0xf2] = 0x03;

        fdc37c93x->rtc_regs[0x63] = 0x70;
        fdc37c93x->rtc_regs[0xf4] = 0x03;

        fdc37c93x->auxio_regs[0xb1] = 0x80;
        fdc37c93x->auxio_regs[0xc0] = 0x01;
        fdc37c93x->auxio_regs[0xc1] = 0x01;
        fdc37c93x->auxio_regs[0xc5] = 0x01;
        fdc37c93x->auxio_regs[0xc6] = 0x01;
        fdc37c93x->auxio_regs[0xc7] = 0x01;
        fdc37c93x->auxio_regs[0xc8] = 0x01;
        fdc37c93x->auxio_regs[0xc9] = 0x80;
        for (c = 0xcb; c < 0xee; c++)
                fdc37c93x->auxio_regs[c] = 0x01;

        fdc_update_densel_polarity(1);
        fdc_update_densel_force(0);
        fdd_swap = 0;
}
