#include "sysheaders.h"
#include "cdpanel.h"
#include "CompactPlayer.h"

/********************************************************
 * SCSI
 */
 
struct MsgPort *SCSIPort;
struct IOStdReq *SCSIIO;
STRPTR SCSI_Device = "scsi.device";
ULONG SCSI_Unit = 6;
UBYTE *DataBuf;
UBYTE *TOCBuf;
UBYTE *SenseData;
BOOL Ejected = FALSE;
BOOL JustStarted = FALSE;

#define BLOCKSPERSECOND 75	/* CD blocks per second of audio */

typedef struct
{
	UBYTE opcode;
	UBYTE b1;
	UBYTE b2;
	UBYTE b3;
	UBYTE b4;
	UBYTE control;
} SCSICMD6;

typedef struct
{
	UBYTE opcode;
	UBYTE b1;
	UBYTE b2;
	UBYTE b3;
	UBYTE b4;
	UBYTE b5;
	UBYTE b6;
	UBYTE b7;
	UBYTE b8;
	UBYTE control;
} SCSICMD10;

typedef struct
{
	UBYTE opcode;
	UBYTE b1;
	UBYTE b2;
	UBYTE b3;
	UBYTE b4;
	UBYTE b5;
	UBYTE b6;
	UBYTE b7;
	UBYTE b8;
	UBYTE b9;
	UBYTE b10;
	UBYTE control;
} SCSICMD12;

int __stdargs
OpenSCSI(void)
{
	if ((DataBuf 	= AllocVec(DATA_LEN, MEMF_CHIP | MEMF_CLEAR)) &&
		(TOCBuf 	= AllocVec(TOC_LEN, MEMF_CHIP)) &&
		(SenseData 	= AllocVec(SENSE_LEN, MEMF_CHIP | MEMF_CLEAR)) &&
		(SCSIPort 	= CreateMsgPort()) &&
		(SCSIIO 	= CreateIORequest(SCSIPort, sizeof(struct IOStdReq))) &&
		!OpenDevice(SCSI_Device, SCSI_Unit, (struct IORequest *) SCSIIO, NULL))
	{
		SCSIIO->io_Command = HD_SCSICMD;
		if (!CD_IsCD())
		{
			ErrorMsg("Device is not a CD-ROM");
			return -1;
		}
		return 0;
	}

	ErrorMsg("SCSI initialization failed");
	return -1;
}

void __stdargs
CloseSCSI(void)
{
	if (SCSIIO)
	{
		if (SCSIIO->io_Command == HD_SCSICMD)
			CloseDevice((struct IORequest *) SCSIIO);
		DeleteIORequest(SCSIIO);
		SCSIIO = NULL;
	}
	if (SCSIPort)
	{
		DeleteMsgPort(SCSIPort);
		SCSIPort = NULL;
	}
	if (SenseData)
	{
		FreeVec(SenseData);
		SenseData = NULL;
	}
	if (TOCBuf)
	{
		FreeVec(TOCBuf);
		TOCBuf = NULL;
	}
	if (DataBuf)
	{
		FreeVec(DataBuf);
		DataBuf = NULL;
	}
}

static int
DoSCSI( APTR data, int dsize, APTR cmd, int csize, UBYTE flags )
{
	static struct SCSICmd ScsiCmd;
	
	/* send a SCSI Direct command */

	ScsiCmd.scsi_Command = cmd;
	ScsiCmd.scsi_CmdLength = csize;
	ScsiCmd.scsi_Data = data;
	ScsiCmd.scsi_Length = dsize;
	ScsiCmd.scsi_SenseData = SenseData;
	ScsiCmd.scsi_SenseLength = SENSE_LEN;
	ScsiCmd.scsi_SenseActual = 0;
	ScsiCmd.scsi_Flags = flags;
	
	SCSIIO->io_Command = HD_SCSICMD;
	SCSIIO->io_Data = &ScsiCmd;
	SCSIIO->io_Length = sizeof(ScsiCmd);
	
	DoIO((struct IORequest *)SCSIIO);
	
	return SCSIIO->io_Error;
}

void
CD_Eject( UBYTE out )
{
	static SCSICMD6 command = { SCSI_DA_START_STOP_UNIT };
	
	/* Eject/Reload a CD */

	command.b4 = out ? 0x02 : 0x03;
	
	Ejected = out;
	
	DoSCSI( NULL, 0, &command, sizeof(command), (SCSIF_READ|SCSIF_AUTOSENSE) );
}

void
CD_Stop(void)
{
	static SCSICMD6 command = { SCSI_DA_START_STOP_UNIT };
	
	/* Stop motor */

	DoSCSI( NULL, 0, &command, sizeof(command), (SCSIF_READ|SCSIF_AUTOSENSE) );
}

void
CD_Play(ULONG track)
{
	static SCSICMD12 command = { SCSI_CD_PLAY_AUDIO_12 };
	ULONG address, length;
	
	/* Play a track. Basic program (random order) support in here, even
	 * though there is no program editor yet. */
	
	if (TOCP[0] != ~0)
	{
		if (TOCP[track] == ~0)
			return;
		address = TOCL[TOCP[track-1]]+1;
	}
	else
	{
		if (track > Tracks)
			return;
		address = TOCL[track-1]+1;
	}
	Track = track;
	
	command.b2 = (address & 0xff000000) >> 24;
	command.b3 = (address & 0x00ff0000) >> 16;
	command.b4 = (address & 0x0000ff00) >> 8;
	command.b5 = (address & 0x000000ff);
	
	if (TOCP[0] != ~0)
		length = TOCL[TOCP[track]] - address - 1;
	else
		length = TOCL[Tracks-1] - address - 1;
	
	command.b6 = (length & 0xff000000) >> 24;
	command.b7 = (length & 0x00ff0000) >> 16;
	command.b8 = (length & 0x0000ff00) >> 8;
	command.b9 = (length & 0x000000ff);
	
	/* because a Toshiba XM3601B can abort a PLAY_AUDIO sent immediately
	 * after media change without any error code, we have a kludge that makes
	 * it restart. */
	JustStarted = TRUE;
	
	DoSCSI( DataBuf, DATA_LEN, &command, sizeof(command), (SCSIF_READ|SCSIF_AUTOSENSE) );
}

void
CD_PauseResume( UBYTE pause )
{
	static SCSICMD10 command = { SCSI_CD_PAUSE_RESUME };
	
	/* Pause/Restart playing */
	
	command.b8 = pause;
	
	DoSCSI( NULL, 0, &command, sizeof(command), (SCSIF_READ|SCSIF_AUTOSENSE) );
}

void
CD_Seek( LONG seconds )
{
	static SCSICMD10 command = { SCSI_CD_READ_SUB_CHANNEL };
	
	command.b2 = 0x40;
	command.b3 = 1;
	command.b6 = 0;
	
	command.b7 = 255;
	command.b8 = 255;
	
	/* Seek forward/backward (rewind/fast forward) */
	
	if (!DoSCSI( DataBuf, DATA_LEN, &command, sizeof(command), (SCSIF_READ|SCSIF_AUTOSENSE) ))
	{
		static SCSICMD12 command = { SCSI_CD_PLAY_AUDIO_12 };
		
		ULONG address = (DataBuf[8] << 24 | DataBuf[9] << 16 | DataBuf[10] << 8 | DataBuf[11]);
		ULONG length, start, end;
		
		address += seconds * BLOCKSPERSECOND;
		
		if (TOCP[0] != ~0)
		{
			start = TOCL[TOCP[Track-1]];
			end = TOCL[TOCP[Track]];
		}
		else
		{
			start = TOCL[Track-1];
			end = TOCL[Track];
		}
		
		if (address >= start && address < end)
		{
			command.b2 = (address & 0xff000000) >> 24;
			command.b3 = (address & 0x00ff0000) >> 16;
			command.b4 = (address & 0x0000ff00) >> 8;
			command.b5 = (address & 0x000000ff);
			
			length = end - address - 1;
		
			command.b6 = (length & 0xff000000) >> 24;
			command.b7 = (length & 0x00ff0000) >> 16;
			command.b8 = (length & 0x0000ff00) >> 8;
			command.b9 = (length & 0x000000ff);
		
			DoSCSI( DataBuf, DATA_LEN, &command, sizeof(command), (SCSIF_READ|SCSIF_AUTOSENSE) );
		}
	}
}

ULONG
CD_Snoop(ULONG *track, ULONG *tracktime, ULONG *time, ULONG ostat)
{
	static SCSICMD10 command = { SCSI_CD_READ_SUB_CHANNEL };
	
	command.b2 = 0x40;
	command.b3 = 1;
	command.b6 = 0;
	
	command.b7 = 255;
	command.b8 = 255;
	
	/* snoop and update current status */
	
	if (!DoSCSI( DataBuf, DATA_LEN, &command, sizeof(command), (SCSIF_READ|SCSIF_AUTOSENSE) ))
	{
		ULONG status;
		ULONG address;
		
		if (DataBuf[1] == 0x11)
			status = CDP_PLAYING;
		else
		if (DataBuf[1] == 0x12)
			status = CDP_PAUSED;
		else
		{
			if (JustStarted && !(DataBuf[5] & 0x04 /* signifies a data track */))
			{
				/* kludge for aborted PLAY_AUDIO (needed with Toshiba XM3601B) */
				CD_Play(Track); /* restart track */
				return CD_Snoop(track, tracktime, time, CDP_STOPPED);
			}
			else
			if (ostat == CDP_PLAYING)
			{
				CD_Play(Track + 1); /* end of track, continue */
				return CD_Snoop(track, tracktime, time, CDP_STOPPED);
			}
			JustStarted = FALSE;
			*track = Track = 0;
			return CDP_STOPPED;
		}
		
		Track = *track = DataBuf[6];
		
		address = (DataBuf[8] << 24 | DataBuf[9] << 16 | DataBuf[10] << 8 | DataBuf[11]);
		
		*time = address / BLOCKSPERSECOND;
		
		address = (DataBuf[12] << 24 | DataBuf[13] << 16 | DataBuf[14] << 8 | DataBuf[15]);
		
		*tracktime = address / BLOCKSPERSECOND;
		
		if (*tracktime > 1)			/* if the player managed to stay on for longer than */
			JustStarted = FALSE;	/* one second, we can assume it will do so. */
		
		return status;
	}
	return (ULONG)(Ejected ? CDP_EJECTED : CDP_EMPTY);
}

ULONG
CD_ReadTOC(void)
{
	static SCSICMD10 command = { SCSI_CD_READ_TOC };

	command.b7 = 0x03;
	command.b8 = 0x24;
	
	/* Read track lengths from the CD Table Of Contents */
	
	if (!DoSCSI( TOCBuf, TOC_LEN, &command, sizeof(command),  (SCSIF_READ|SCSIF_AUTOSENSE) ))
	{
		ULONG tocsize = (TOCBuf[0] << 8) | TOCBuf[1];
		ULONG tracks = 0;
		UBYTE *toc;
		
		if (tocsize > 2) tocsize -= 2;
		for (toc = &TOCBuf[4] ; toc < (&TOCBuf[4] + tocsize) ; toc += 8 )
		{
			TOCL[tracks] = (toc[4] << 24) | (toc[5] << 16) | (toc[6] << 8) | (toc[7]);
			TOCT[tracks] = TOCL[tracks] / BLOCKSPERSECOND;
			TOCF[tracks] = (toc[1] & 0x04) ? 1 : 0;
			TOCS[tracks] = &TitleBuffer[(tracks+2)*40];
			Sprintf(TOCS[tracks], "Track %-2ld", tracks+1);
			tracks++;
		}
		TOCS[--tracks] = NULL;

		GetIndex( tracks, TOCL[2], TOCL[tracks]);
		
		if (!TITLE[0])
		{
			TITLE[0] = &TitleBuffer[0];
			strcpy(TITLE[0], "Unknown artist");
		}

		if (!TITLE[1])
		{
			TITLE[1] = &TitleBuffer[40];
			strcpy(TITLE[1], "Unknown title");
		}

		return tracks;
	}
	if (SenseData[2] == 0x06 && /* media changed */
		SenseData[7] >= 3 && 	/* enough sense data */
		SenseData[12] == 0x28)	/* media changed */
	{
		Delay(10);
		return CD_ReadTOC();
	}
	return 0;
}

BOOL
CD_IsCD(void)
{
	static SCSICMD6 command = { SCSI_INQUIRY };
	
	/* Test if a SCSI target is a CD-ROM device */
	
	command.b4 = 40;
	
	if (!DoSCSI( DataBuf, DATA_LEN, &command, sizeof(command), (SCSIF_READ|SCSIF_AUTOSENSE) ))
	{
		return (BOOL)(((DataBuf[0] & 0x1f) == 0x05) ? TRUE : FALSE); /* is a CD-ROM */
	}
	return FALSE;
}
