#include "ibm.h"
#include "ide.h"
#include "ide_atapi.h"
#include "scsi.h"
#include "scsi_cd.h"
#include "scsi_hd.h"
#include "scsi_zip.h"

#define STATE_IDLE 0
#define STATE_COMMAND 1
#define STATE_COMMANDWAIT 2
#define STATE_DATAIN 3
#define STATE_DATAOUT 4
#define STATE_STATUS 5
#define STATE_MESSAGEIN 6
#define STATE_PHASESEL 7

#define SET_BUS_STATE(bus, state)                                                                                                \
        bus->bus_out = (bus->bus_out & ~(BUS_CD | BUS_IO | BUS_MSG)) | (state & (BUS_CD | BUS_IO | BUS_MSG))

static int cmd_len[8] = {6, 10, 10, 6, 16, 12, 6, 6};

static int get_dev_id(uint8_t data) {
        int c;

        for (c = 0; c < 8; c++) {
                if (data & (1 << c))
                        return c;
        }

        return -1;
}

int scsi_bus_update(scsi_bus_t *bus, int bus_assert) {
        scsi_device_t *dev = NULL;
        void *dev_data = NULL;

        //        pclog("scsi_hd_bus_update: state=%d bus_assert=%04x bus_in=%02x %i %02x %p\n", bus->state, bus_assert,
        //        bus->bus_in, bus->clear_req, bus->bus_out, bus);

        if (bus_assert & BUS_ARB)
                bus->state = STATE_IDLE;

        if (bus->dev_id != -1) {
                dev = bus->devices[bus->dev_id];
                dev_data = bus->device_data[bus->dev_id];
        }

        switch (bus->state) {
        case STATE_IDLE:
                bus->clear_req = bus->change_state_delay = bus->new_req_delay = 0;
                if ((bus_assert & BUS_SEL) && !(bus_assert & BUS_BSY)) {
                        uint8_t sel_data = BUS_GETDATA(bus_assert);

                        // bus->state = STATE_PHASESEL;
                        bus->dev_id = get_dev_id(sel_data);
                        //                        pclog("PHASESEL %02x %2x %i %p\n", sel_data, bus->bus_out, bus->dev_id,
                        //                        bus->devices[bus->dev_id]);
                        if (bus->dev_id != -1 && bus->devices[bus->dev_id]) {
                                bus->bus_out |= BUS_BSY;
                                bus->state = STATE_PHASESEL;
                                //                                pclog("Move to phase sel\n");
                        }
                        break;
                }
                break;
        case STATE_PHASESEL:
                if (!(bus_assert & BUS_SEL)) {
                        if (!(bus_assert & BUS_ATN)) {
                                /*                                uint8_t sel_data = BUS_GETDATA(bus_assert);

                                                                bus->dev_id = get_dev_id(sel_data);*/
                                //                                pclog("STATE_PHASESEL: %i %p\n", bus->dev_id,
                                //                                bus->devices[bus->dev_id]);
                                if (bus->dev_id != -1 && bus->devices[bus->dev_id]) {
                                        bus->state = STATE_COMMAND;
                                        bus->bus_out = BUS_BSY | BUS_REQ;
                                        bus->command_pos = 0;
                                        SET_BUS_STATE(bus, BUS_CD);
                                } else {
                                        bus->state = STATE_IDLE;
                                        bus->bus_out = 0;
                                }
                        } else
                                fatal("dropped sel %x\n", bus_assert & BUS_ATN);
                }
                break;
        case STATE_COMMAND:
                if ((bus_assert & BUS_ACK) && !(bus->bus_in & BUS_ACK)) {
                        //                        pclog("  get data %02x\n", BUS_GETDATA(bus_assert));
                        bus->command[bus->command_pos++] = BUS_GETDATA(bus_assert);
                        bus->clear_req = 3;
                        bus->new_state = bus->bus_out & (BUS_IO | BUS_CD | BUS_MSG);
                        bus->bus_out &= ~BUS_REQ;

                        if (bus->command_pos == (bus->is_atapi ? 12 : cmd_len[bus->command[0] >> 5])) {
                                int new_state;

                                dev->start_command(dev_data);
                                new_state = dev->command(bus->command, dev_data);
                                //                                pclog("COMMAND new_state = %x\n", new_state);
                                if ((new_state & (BUS_IO | BUS_CD | BUS_MSG)) == BUS_CD) {
                                        bus->state = STATE_COMMANDWAIT;
                                        bus->clear_req = 0;
                                } else {
                                        //                                        pclog("Set change_state_delay COMMAND\n");
                                        bus->new_state = new_state;
                                        bus->change_state_delay = 4;
                                }
                        }
                }
                break;

        case STATE_COMMANDWAIT: {
                int new_state;

                //                        pclog("COMMANDWAIT\n");
                new_state = dev->command(bus->command, dev_data);
                //                        pclog("new_state=%x\n", new_state);
                if ((new_state & (BUS_IO | BUS_CD | BUS_MSG)) != BUS_CD) {
                        //                                pclog("Set change_state_delay\n");
                        bus->new_state = new_state;
                        bus->change_state_delay = 4;
                        bus->clear_req = 4;
                }
        } break;

        case STATE_DATAIN:
                if ((bus_assert & BUS_ACK) && !(bus->bus_in & BUS_ACK)) {
                        if (dev->read_complete(dev_data)) {
                                //                                pclog("Read complete\n");
                                bus->bus_out &= ~BUS_REQ;
                                // bus->clear_req = 3;
                                bus->new_state = BUS_CD | BUS_IO;
                                //                                pclog("change_state_delay STATE_DATAIN\n");
                                bus->change_state_delay = 4;
                                bus->new_req_delay = 8;
                                //                                SET_BUS_STATE(data, BUS_CD | BUS_IO);
                        } else {
                                uint8_t val = dev->read(dev_data);

                                //                                pclog("  Read data pos %04x %02x  %x %x\n", bus->data_pos_read,
                                //                                bus->data_in[bus->data_pos_read], bus->data_pos_read,
                                //                                bus->data_pos_write);
                                bus->bus_out = (bus->bus_out & ~BUS_DATAMASK) | BUS_SETDATA(val) | BUS_DBP | BUS_REQ;
                                //                                bus->bus_out |= BUS_REQ;
                                bus->clear_req = 3;
                                bus->bus_out &= ~BUS_REQ;
                                bus->new_state = BUS_IO;
                        }
                        //                        scsi_hd_command(bus->command, data);
                }
                break;

        case STATE_DATAOUT:
                if ((bus_assert & BUS_ACK) && !(bus->bus_in & BUS_ACK)) {
                        dev->write(BUS_GETDATA(bus_assert), dev_data);
                        //                        pclog("Got data %02x\n", BUS_GETDATA(bus_assert));
                        //                        pclog("  Write data pos %04x %02x  %08x %08x\n", bus->data_pos_write-1,
                        //                        bus->data_out[bus->data_pos_write-1], cpu_getd(5), cpu_getd(6));

                        if (dev->write_complete(dev_data)) {
                                int new_state;
                                // pclog("  Write data, command complete\n");
                                bus->bus_out &= ~BUS_REQ;
                                new_state = dev->command(bus->command, dev_data);

                                bus->new_state = new_state;
                                //                                pclog("change_state_delay STATE_DATAOUT\n");
                                bus->change_state_delay = 4;
                                bus->new_req_delay = 8;
                        } else {
                                bus->bus_out |= BUS_REQ;
                        }
                }
                break;

        case STATE_STATUS:
                if ((bus_assert & BUS_ACK) && !(bus->bus_in & BUS_ACK)) {
                        bus->bus_out &= ~BUS_REQ;
                        /*                        bus->clear_req = 3;*/
                        bus->new_state = BUS_CD | BUS_IO | BUS_MSG;
                        //                                pclog("change_state_delay STATE_STATUS\n");
                        bus->change_state_delay = 4;
                        bus->new_req_delay = 8;
                        /*                        bus->state = STATE_MESSAGEIN;*/
                }
                break;

        case STATE_MESSAGEIN:
                if ((bus_assert & BUS_ACK) && !(bus->bus_in & BUS_ACK)) {
                        //                        pclog("Scheduling change to idle\n");
                        bus->bus_out &= ~BUS_REQ;
                        bus->new_state = BUS_IDLE;
                        //                                pclog("change_state_delay STATE_MESSAGEIN\n");
                        bus->change_state_delay = 4;
                        /*                        bus->bus_out &= ~BUS_BSY;
                                                SET_BUS_STATE(bus, BUS_CD | BUS_IO);
                                                bus->state = STATE_IDLE;*/
                }
                break;
        }
        bus->bus_in = bus_assert;

        return bus->bus_out | bus->bus_in;
}

int scsi_bus_read(scsi_bus_t *bus) {
        scsi_device_t *dev = NULL;
        void *dev_data = NULL;

        if (bus->dev_id != -1) {
                dev = bus->devices[bus->dev_id];
                dev_data = bus->device_data[bus->dev_id];
        }

        //        pclog("scsi_hd_bus_read: bus_out=%02x bus_in=%02x  %i %i %i\n", bus->bus_out, bus->bus_in, bus->clear_req,
        //        bus->change_state_delay, bus->new_req_delay);

        if (bus->clear_req) {
                bus->clear_req--;
                if (!bus->clear_req) {
                        SET_BUS_STATE(bus, bus->new_state);
                        //                        pclog("clear_req\n");
                        bus->bus_out |= BUS_REQ;
                }
        }

        if (bus->change_state_delay) {
                bus->change_state_delay--;
                if (!bus->change_state_delay) {
                        uint8_t val;

                        //                        pclog("change_state_delay %08x\n", bus->bus_out & (BUS_IO | BUS_CD | BUS_MSG |
                        //                        BUS_IDLE));
                        SET_BUS_STATE(bus, bus->new_state);

                        switch (bus->bus_out & (BUS_IO | BUS_CD | BUS_MSG)) {
                        case BUS_IO:
                                bus->state = STATE_DATAIN;
                                val = dev->read(dev_data);
                                bus->bus_out = (bus->bus_out & ~BUS_DATAMASK) | BUS_SETDATA(val) | BUS_DBP;
                                break;

                        case 0:
                                if (bus->new_state & BUS_IDLE) {
                                        bus->state = STATE_IDLE;
                                        bus->bus_out &= ~BUS_BSY;
                                } else
                                        bus->state = STATE_DATAOUT;
                                break;

                        case (BUS_IO | BUS_CD):
                                bus->state = STATE_STATUS;
                                bus->bus_out = (bus->bus_out & ~BUS_DATAMASK) | BUS_SETDATA(dev->get_status(dev_data)) | BUS_DBP;
                                break;

                        case (BUS_CD | BUS_IO | BUS_MSG):
                                bus->state = STATE_MESSAGEIN;
                                bus->bus_out = (bus->bus_out & ~BUS_DATAMASK) | BUS_SETDATA(0) | BUS_DBP;
                                break;

                        default:
                                fatal("change_state_delay bad state %x\n", bus->bus_out);
                        }
                }
        }
        if (bus->new_req_delay) {
                bus->new_req_delay--;
                if (!bus->new_req_delay) {
                        //                        pclog("new_req_delay\n");
                        bus->bus_out |= BUS_REQ;
                }
        }

        //        pclog("  bus_out now %02x\n", bus->bus_out);
        return bus->bus_out; // | bus->bus_in;
}

int scsi_bus_match(scsi_bus_t *bus, int bus_assert) {
        //        pclog("bus_match  %02x %02x\n", bus_assert & (BUS_CD | BUS_IO | BUS_MSG), bus->bus_out & (BUS_CD | BUS_IO |
        //        BUS_MSG));

        return (bus_assert & (BUS_CD | BUS_IO | BUS_MSG)) == (bus->bus_out & (BUS_CD | BUS_IO | BUS_MSG));
}

void scsi_bus_kick(scsi_bus_t *bus) {
        //        pclog("scsi_bus_kick\n");
        scsi_bus_update(bus, 0);
}

void scsi_bus_atapi_init(scsi_bus_t *bus, scsi_device_t *device, int id, atapi_device_t *atapi_dev) {
        memset(bus->devices, 0, sizeof(bus->devices));
        memset(bus->device_data, 0, sizeof(bus->device_data));

        bus->devices[0] = device;
        bus->device_data[0] = bus->devices[0]->atapi_init(bus, id, atapi_dev);
        if (!bus->device_data[0])
                bus->devices[0] = NULL;

        bus->is_atapi = 1;
}

void scsi_bus_init(scsi_bus_t *bus) {
        int c;

        memset(bus->devices, 0, sizeof(bus->devices));
        memset(bus->device_data, 0, sizeof(bus->device_data));

        for (c = 0; c < 7; c++) {
                if (cdrom_channel == c)
                        bus->devices[c] = &scsi_cd;
                else if (zip_channel == c)
                        bus->devices[c] = &scsi_zip;
                else
                        bus->devices[c] = &scsi_hd;

                bus->device_data[c] = bus->devices[c]->init(bus, c);
                if (!bus->device_data[c])
                        bus->devices[c] = NULL;
        }

        bus->is_atapi = 0;
}

void scsi_bus_close(scsi_bus_t *bus) {
        int c;

        memset(bus->devices, 0, sizeof(bus->devices));
        memset(bus->device_data, 0, sizeof(bus->device_data));

        for (c = 0; c < 8; c++) {
                if (bus->device_data[c])
                        bus->devices[c]->close(bus->device_data[c]);
        }
}

void scsi_bus_reset(scsi_bus_t *bus) {
        int c;

        bus->state = STATE_IDLE;
        bus->clear_req = 0;
        bus->change_state_delay = 0;
        bus->new_req_delay = 0;
        bus->bus_in = bus->bus_out = 0;
        bus->command_pos = 0;

        for (c = 0; c < 8; c++) {
                if (bus->device_data[c] && bus->devices[c]->reset)
                        bus->devices[c]->reset(bus->device_data[c]);
        }
}
