#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]);
	}
}
