#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "podule_api.h"
#include "scsi.h"
#include "scsi_cd.h"
#include "scsi_hd.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 const int cmd_len[8] = {6, 10, 10, 6, 16, 12, 6, 6};
/*Toshiba CD-ROM vendor commands are 10 bytes long*/
static const int specific_cmd_len[256] =
{
	[0xc0] = 10,
	[0xc1] = 10,
	[0xc2] = 10,
	[0xc3] = 10,
	[0xc4] = 10,
	[0xc6] = 10,
	[0xc7] = 10,
	[0xc8] = 10
};

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;

//        scsi_log("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);
//                        scsi_log("bus->dev_id=%i device=%p\n", bus->dev_id, (bus->dev_id != -1) ? bus->devices[bus->dev_id] : 0);
//                        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
				scsi_fatal("dropped sel %x\n", bus_assert & BUS_ATN);
		}
		break;
		case STATE_COMMAND:
		if ((bus_assert & BUS_ACK) && !(bus->bus_in & BUS_ACK))
		{
			int len;

//                        scsi_log("  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->is_atapi)
				len = 12;
			else if (specific_cmd_len[bus->command[0]])
				len = specific_cmd_len[bus->command[0]];
			else
				len = cmd_len[bus->command[0] >> 5];

			if (bus->command_pos == len)
			{
				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);

				if ((new_state & (BUS_IO | BUS_CD | BUS_MSG)) == BUS_CD)
				{
					bus->state = STATE_COMMANDWAIT;
					bus->clear_req = 0;
				}
				else
				{
					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:
				scsi_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, struct 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, podule_t *podule, const podule_callbacks_t *podule_callbacks)
{
	int c;

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

	for (c = 0; c < 7; c++)
	{
		char config_name[20];
		const char *type;

		sprintf(config_name, "device%i_type", c);
		type = podule_callbacks->config_get_string(podule, config_name, "none");

		if (!strcmp(type, "hd"))
			bus->devices[c] = &scsi_hd;
		else if (!strcmp(type, "cd"))
			bus->devices[c] = &scsi_cd;
		else
			bus->devices[c] = NULL;

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

	bus->is_atapi = 0;
}

void scsi_bus_close(scsi_bus_t *bus)
{
	int c;

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

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

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

void scsi_bus_timer_run(scsi_bus_t *bus, int time_us)
{
	int c;

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

void scsi_bus_device_set_timer(scsi_bus_t *bus, int id, int timer_us)
{
	bus->device_timer_us[id] = timer_us;
}
