/*
 * SASI hard drive emulator class
 */

#include "utils.h"
#include "SASI.h"
#include "DiscEmul.h"

#include "dlc/dlc_register.h"

#include <stdio.h>

#define hdfile the_disc->dfile

SASI::SASI (void): MMIO_Dev (0xfff8, 0xfc40)
{
	addr.reg_mask = 3;
	flags = MMIO_IS_TICKER;
	
	the_disc = new DiscEmul (1, "SASI hard disc");
	the_disc->SelectDriveNum (0);
}

SASI::~SASI (void)
{
	delete the_disc;
}

void SASI::Reset (void)
{
	status = 0;
	next_status = -1;
	en_irq = 0;
	phase = PH_CMD;
	databuf_ptr = databuf_len = 0;
}

byte SASI::MMIO_Read (int addr)
{
	int i;
	
	switch (addr)
	{
		case 0:
		Rel_REQ ();
		if (phase == PH_MSGIN)
		{
			dprintf ("Return message\n");
			if (msgphase++)
			{
				Select (PH_CMD);
				status &= ~STAT_BSY;
			}
			status |= STAT_REQ;
			return msg;
		}
		else if (phase == PH_DATAIN)
		{
			data = databuf[databuf_ptr++];
			if (databuf_ptr >= databuf_len)
			{
				msgphase = 0;
				msg = SC_COMMAND_COMPLETE;
				Select (PH_MSGIN);
				status |= STAT_REQ;
				return data;
			}
			status |= STAT_REQ;
		}
		return data;
		
		case 1:
		i = status;
		if (next_status != -1)
		{
			status = next_status;
			next_status = -1;
		}
		return i;
	}
	return 0xfc;
}

void SASI::MMIO_Write (int addr, byte val)
{
	switch (addr)
	{
		case 0:			// Data
		data = val;
		Rel_REQ ();
		if (status & STAT_BSY)
			DoWrite (val);
		break;
		
		case 2:			// Select
		status |= STAT_BSY | STAT_REQ;
		Select (PH_CMD);
		//DoWrite (data);
		break;
		
		case 3:
		en_irq = val & 1;
		if (en_irq)
			status &= ~STAT_NIRQ;
		else
			status |= STAT_NIRQ;
		break;
	}
}

void SASI::Select (enum phase ph)
{
	phase = ph;
	status &= ~(STAT_MSG | STAT_IO | STAT_CD);
	
	switch (phase)
	{
		case PH_CMD:
		status |= STAT_CD;
		cmdbytes = 0;
		break;
		
		case PH_STAT:
		status |= STAT_IO | STAT_CD;
		break;
		
		case PH_DATAOUT:
		break;
		
		case PH_DATAIN:
		status |= STAT_IO;
		break;
		
		case PH_MSGIN:
		status |= STAT_MSG | STAT_IO | STAT_CD;
		break;
	}
}

void SASI::Rel_REQ (void)
{
	if (status & STAT_REQ)
	{
		status &= ~STAT_REQ;
		if (en_irq)
			SignalInt ();
	}
}

int cmd_lengths[256] = { 0, 1, 0, 0, 0, 0, 0, 0, 6, 0, 6,
			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
			0, 6 };

void SASI::DoWrite (int val)
{
	switch (phase)
	{
		case PH_CMD:
		command[cmdbytes++] = val;
		if (cmdbytes == 1)
		{
			cmd_len = cmd_lengths[command[0]];
			dprintf ("cmd len %d\n", cmd_len);
		}
		if (cmdbytes >= cmd_len)
		{
			ProcessCmd ();
			return;
		}
		next_status = status | STAT_REQ;
		break;
		
		case PH_DATAOUT:
		databuf[databuf_ptr++] = val;
		status |= STAT_REQ;
		if (databuf_ptr >= databuf_len)
		{
			if (hdfile)
			{
				fwrite (databuf, databuf_len, 1, hdfile);
				msgphase = 0;
				msg = SC_COMMAND_COMPLETE;
				Select (PH_MSGIN);
			}
			else
			{
				msgphase = 0;
				msg = SC_DISCONNECT;
				Select (PH_MSGIN);
			}
		}
		break;
		
		default:
		warning ("Write to SASI controller when not expected!");
		break;
	}
}

void SASI::ProcessCmd (void)
{
	struct scsi_g0cdb *g0 = (struct scsi_g0cdb *)(command);
	int blk;
	
	switch (g0->cmd)
	{
		case SC_REZERO_UNIT:
		if (hdfile)
		{
			fseek (hdfile, 0, SEEK_SET);
			Select (PH_CMD);
		}
		else
		{
			msgphase = 0;
			msg = SC_DISCONNECT;
			Select (PH_CMD);
		}
		break;
		
		case SC_READ:
		// Seek the file
		if (hdfile)
		{
			blk = g0->low_addr | (g0->mid_addr << 8) | (g0->high_addr << 16);
			dprintf ("Read blk %d %d sect\n", blk, g0->count);
			fseek (hdfile, blk*256, SEEK_SET);
			fread (databuf, 256, g0->count, hdfile);
			databuf_ptr = 0;
			databuf_len = 256*(g0->count);
			Select (PH_DATAIN);
			next_status = status | STAT_REQ;
		}
		else
		{
			databuf_ptr = 0;
			databuf_len = 256*(g0->count);
			Select (PH_DATAIN);
			next_status = status | STAT_REQ;
		}
		if (g0->link)
			dprintf ("WARNING: linked commands\n");
		break;
		
		case SC_WRITE:
		if (hdfile)
		{
			// Seek the file
			blk = g0->low_addr | (g0->mid_addr << 8) | (g0->high_addr << 16);
			dprintf ("Write blk %d %d sect\n", blk, g0->count);
			fseek (hdfile, blk*256, SEEK_SET);
			databuf_ptr = 0;
			databuf_len = 256*(g0->count);
			Select (PH_DATAOUT);
			next_status = status | STAT_REQ;
		}
		else
		{
			databuf_ptr = 0;
			databuf_len = 256*(g0->count);
			Select (PH_DATAOUT);
			next_status = status | STAT_REQ;
		}
		if (g0->link)
			dprintf ("WARNING: linked commands\n");
		break;
		
		case SC_START:
		dprintf ("SC_START\n");
		msgphase = 0;
		msg = SC_COMMAND_COMPLETE;
		Select (PH_MSGIN);
		break;
		
		default:
		dprintf ("Unknown SCSI command %x\n", g0->cmd);
	}
}

void SASI::AddOK (void)
{
	printf ("SASI device emulation added OK.\n");
}

DLC_Register (SASI);

/* End of file. */
