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

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

//#define SINGLE_TRACK_PLAY 1

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

int __stdargs
Open_SCSI(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(Device, Unit, (struct IORequest *) SCSIIO, NULL))
	{
		SCSIIO->io_Command = HD_SCSICMD;
		
		CD_Eject = Eject_SCSI;
		CD_Stop = Stop_SCSI;
		CD_Play = Play_SCSI;
		CD_PauseResume = PauseResume_SCSI;
		CD_Seek = Seek_SCSI;
		CD_Snoop = Snoop_SCSI;
		CD_ReadTOC = ReadTOC_SCSI;
		CD_IsCD = IsCD_SCSI;
		CD_Close = (void (*)(void))Close_SCSI;
		
		if (CD_IsCD())
			return 0;
		else
			ErrorMsg(GS(DEVICE_FAIL), Device, Unit);
	}
	else
		ErrorMsg(GS(SCSI_FAIL), Device, Unit);
	Close_SCSI();
	return -1;
}

void __stdargs
Close_SCSI(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;
	}

	CD_Eject = (void (*)(UBYTE))Dummy;
	CD_Stop = (void (*)(void))Dummy;
	CD_Play = (void (*)(ULONG))Dummy;
	CD_PauseResume = (void (*)(UBYTE))Dummy;
	CD_Seek = (void (*)(LONG))Dummy;
	CD_Snoop = (ULONG (*)(ULONG *, ULONG *, ULONG *, ULONG))Dummy;
	CD_ReadTOC = (ULONG (*)(void))Dummy;
	CD_IsCD = (BOOL (*)(void))Dummy;
	CD_Close = (void (*)(void))Dummy;
}

static int
DoSCSI( APTR data, int dsize, APTR cmd, int csize, UBYTE flags )
{
	if (SCSIIO)
	{
		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;
	}
	return -1;
}

void
Eject_SCSI( 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
Stop_SCSI(void)
{
	static SCSICMD6 command = { SCSI_DA_START_STOP_UNIT };
	
	/* Stop motor */

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

#ifndef SINGLE_TRACK_PLAY
ULONG EndAddress = ~0;
#endif

void
Play_SCSI(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);
	
#ifdef SINGLE_TRACK_PLAY
	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);
#else
	/* continuous play */
	if (TOCP[0] != ~0)
		EndAddress = TOCL[TOCP[track]] - 1;
	else
		EndAddress = ~0;

	command.b6 = 0xff;
	command.b7 = 0xff;
	command.b8 = 0xff;
	command.b9 = 0xff;
#endif
	
	/* 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
PauseResume_SCSI( 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
Seek_SCSI( 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);
			
#ifdef SINGLE_TRACK_PLAY
			length = end - address - 1;
		
			command.b6 = (length & 0xff000000) >> 24;
			command.b7 = (length & 0x00ff0000) >> 16;
			command.b8 = (length & 0x0000ff00) >> 8;
			command.b9 = (length & 0x000000ff);
#else
			command.b6 = 0xff;
			command.b7 = 0xff;
			command.b8 = 0xff;
			command.b9 = 0xff;
#endif
		
			DoSCSI( DataBuf, DATA_LEN, &command, sizeof(command), (SCSIF_READ|SCSIF_AUTOSENSE) );
		}
	}
}

ULONG
Snoop_SCSI(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;
		
		address = (DataBuf[8] << 24 | DataBuf[9] << 16 | DataBuf[10] << 8 | DataBuf[11]);
		
		if (DataBuf[1] == 0x11)
		{
#ifndef SINGLE_TRACK_PLAY
			if (address >= EndAddress)
			{
				CD_Play(Track + 1); /* end of track, continue program */
				return CD_Snoop(track, tracktime, time, CDP_STOPPED);
			}
#endif
			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];
		
		*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
ReadTOC_SCSI(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], GS(TRACK_NUM), tracks+1);
			tracks++;
		}
		TOCS[--tracks] = NULL;

		if (!GetIndex( tracks, TOCL[2], TOCL[tracks]))
		{
			TITLE[0] = NULL;
			TITLE[1] = NULL;
		}
		
		if (!TITLE[0])
		{
			TITLE[0] = &TitleBuffer[0];
			strcpy(TITLE[0], GS(UNKNOWN_ARTIST));
		}

		if (!TITLE[1])
		{
			TITLE[1] = &TitleBuffer[40];
			strcpy(TITLE[1], GS(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
IsCD_SCSI(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;
}
