/*  CD.c
 *  Copyright (C) 2002-2005  PCSX2 Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 *  PCSX2 members can be contacted through their website at www.pcsx2.net.
 */

#include <errno.h> // errno
#include <stddef.h> // NULL
#include <string.h> // strerror()
#include <fcntl.h> // open()
#include <sys/ioctl.h> // ioctl()
#include <sys/stat.h> // open()
#include <sys/types.h> // lseek(), open()
#include <unistd.h> // close(), lseek(), (sleep())

#include <linux/cdrom.h> // CD/DVD based ioctl() and defines.

#include "../convert.h"
#include "logfile.h"
#include "device.h"
#include "CD.h"


// Constants
u8 *playstationcdname = "PLAYSTATION\0";
u8 *ps1name = "CD-XA001\0";

// CD-ROM temp storage structures (see linux/cdrom.h for details)
struct cdrom_tochdr cdheader;
struct cdrom_tocentry cdtrack;
struct cdrom_subchnl subchannel;
u8 cdtempbuffer[2352];

int cdmode; // mode of last CDVDreadTrack call (important for CDs)


// Internal Functions

void InitCDSectorInfo()
{
	cdmode = -1;
} // END InitSectorInfo();


// Function Calls from CDVD.c

void InitCDInfo()
{
	InitCDSectorInfo();
} // END InitDiscType()

s32 CDreadTrack(u32 lsn, int mode, u8 *buffer)
{
	s32 s32result;

#ifdef VERBOSE_FUNCTION
	PrintLog("CDVD driver: CDreadTrack(%i)", lsn);
#endif /* VERBOSE_FUNCTION */

	s32result = 0;

	if (buffer == NULL)  return(-1);

	// The CD way of figuring out where to read.
	LBAtoMSF(lsn, buffer);

	switch (mode)
	{
		case CDVD_MODE_2048:
		case CDVD_MODE_2328:
		case CDVD_MODE_2340:
		case CDVD_MODE_2352:
			errno = 4; // Interrupted system call... (simulated the first time)
			while (errno == 4)
			{
				errno = 0;
				s32result = ioctl(devicehandle, CDROMREADRAW, buffer);
			} // ENDWHILE- Continually being interrupted by the system...
			break;
		case CDVD_MODE_2368: // Unimplemented... as yet.
		default:
#ifdef VERBOSE_WARNINGS
			PrintLog("CDVD driver:   Unknown Mode %i", mode);
#endif /* VERBOSE_WARNINGS */
			return(-1); // Illegal Read Mode? Abort
			break;
	} // ENDSWITCH- Which read mode should we choose?
	if ((s32result == -1) || (errno != 0))
	{
#ifdef VERBOSE_WARNINGS
		PrintLog("CDVD driver:   Error reading CD: %i:%s", errno, strerror(errno));
#endif /* VERBOSE_WARNINGS */
		InitCDSectorInfo();
		return(-1);
	} // ENDIF- Trouble getting a track count?

	cdmode = mode; // Save mode for buffer positioning later.
	return(0); // Call accomplished
} // END CDreadTrack()

s32 CDgetBufferOffset()
{
#ifdef VERBOSE_FUNCTION
	PrintLog("CDVD driver: CDgetBufferOffset()");
#endif /* VERBOSE_FUNCTION */

	switch (cdmode)
	{
		case CDVD_MODE_2048:
			return(0 + 24);
		case CDVD_MODE_2328:
			return(0 + 24);
		case CDVD_MODE_2340:
			return(0 + 12);
		case CDVD_MODE_2352:
			return(0 + 0);
		case CDVD_MODE_2368: // Unimplemented... as yet.
		default:
#ifdef VERBOSE_WARNINGS
			PrintLog("CDVD driver:   Unknown Mode %i", cdmode);
#endif /* VERBOSE_WARNINGS */
			return(0); // Not to worry. for now.
	} // ENDSWITCH- where should we put the buffer pointer?
} // END CDgetBuffer()

// I, personally, don't see the big deal with SubQ
// However, sooner or later I'll incorporate it into the Cache Buffer system
// (backward compatibility, and all that)
s32 CDreadSubQ(u32 lsn, cdvdSubQ *subq)
{
	int tempmode;
	s32 s32result;

	s32result = 0;

#ifdef VERBOSE_FUNCTION
	PrintLog("CDVD driver: CDreadSubQ()");
#endif /* VERBOSE_FUNCTION */

	tempmode = cdmode;
	if (tempmode == -1)  tempmode = CDVD_MODE_2352;
	CDreadTrack(lsn, tempmode, cdtempbuffer);
	if ((s32result == -1) || (errno != 0))
	{
#ifdef VERBOSE_WARNINGS
		PrintLog("CDVD driver:   Error prepping CD SubQ: %i:%s", errno, strerror(errno));
#endif /* VERBOSE_WARNINGS */
		return(s32result);
	} // ENDIF- Trouble?

	subchannel.cdsc_format = CDROM_MSF;
	s32result = ioctl(devicehandle, CDROMSUBCHNL, &subchannel);
	if ((s32result == -1) || (errno != 0))
	{
#ifdef VERBOSE_WARNINGS
		PrintLog("CDVD driver:   Error reading CD SubQ: %i:%s", errno, strerror(errno));
#endif /* VERBOSE_WARNINGS */
		return(s32result);
	} // ENDIF- Trouble?

	if (subq != NULL)
	{
		subq->mode = subchannel.cdsc_adr;
		subq->ctrl = subchannel.cdsc_ctrl;
		subq->trackNum = subchannel.cdsc_trk;
		subq->trackIndex = subchannel.cdsc_ind;
		subq->trackM = subchannel.cdsc_reladdr.msf.minute;
		subq->trackS = subchannel.cdsc_reladdr.msf.second;
		subq->trackF = subchannel.cdsc_reladdr.msf.frame;
		subq->discM = subchannel.cdsc_absaddr.msf.minute;
		subq->discS = subchannel.cdsc_absaddr.msf.second;
		subq->discF = subchannel.cdsc_absaddr.msf.frame;
	} // ENDIF- Did the caller want all this data?

	return(0);
} // END CDVDreadSubQ()

s32 CDgetTN(cdvdTN *cdvdtn)
{
#ifdef VERBOSE_FUNCTION
	PrintLog("CDVD driver: CDgetTN()");
#endif /* VERBOSE_FUNCTION */

	if (cdvdtn != NULL)
	{
		cdvdtn->strack = cdheader.cdth_trk0;
		cdvdtn->etrack = cdheader.cdth_trk1;
	} // ENDIF- programmer actually WANTS this info?

	return(0); // Call accomplished
} // END CDVDgetTN()

s32 CDgetTD(u8 newtrack, cdvdTD *cdvdtd)
{
	u8 j;
	u16 k;
	char temptime[3];

#ifdef VERBOSE_FUNCTION
	PrintLog("CDVD driver: CDgetTD()");
#endif /* VERBOSE_FUNCTION */

	j = newtrack;
	if (j == CDROM_LEADOUT)  j = 0;

	if (j == 0)
	{
		k = 27;
	}
	else
	{
		k = j * 10 + 37;
	} // ENDIF- Where to start hunting for this number?

	if (cdvdtd != NULL)
	{
		cdvdtd->type = tocbuffer[j*10 + 30];

		temptime[0] = BCDTOHEX(tocbuffer[k]);
		temptime[1] = BCDTOHEX(tocbuffer[k + 1]);
		temptime[2] = BCDTOHEX(tocbuffer[k + 2]);
		cdvdtd->lsn = MSFtoLBA(temptime);
	} // ENDIF- Does the caller REALLY want this data?

	return(0); // Call accomplished
} // END CDVDgetTD()

s32 CALLBACK CDgetDiskType(s32 ioctldisktype)
{
	s32 offset;
	s32 s32result;
	int i;
	u8 j;
	int tempdisctype;

	offset = 0;
	errno = 0;
	i = 0;
	j = 0;
	tempdisctype = CDVD_TYPE_UNKNOWN;

#ifdef VERBOSE_FUNCTION
	PrintLog("CDVD driver: CDgetDiskType()");
#endif /* VERBOSE_FUNCTION */

	s32result = CDreadTrack(16, CDVD_MODE_2352, cdtempbuffer);
	if ((s32result != 0) || (errno != 0))
	{
		return(-1);
	} // ENDIF- Cannot read the CD's ISO9660 volume sector? Abort
	disctype = CDVD_TYPE_DETCTCD;

	switch (ioctldisktype)
	{
		case CDS_AUDIO:
#ifdef VERBOSE_DISC_TYPE
			PrintLog("CDVD driver: Detected CDDA Audio disc.");
#endif /* VERBOSE_DISC_TYPE */
			tempdisctype = CDVD_TYPE_CDDA;
			tocbuffer[0] = 0x01;
			break;

		case CDS_DATA_1:
		case CDS_MIXED:
#ifdef VERBOSE_DISC_TYPE
			PrintLog("CDVD driver: Detected CD disc.");
#endif /* VERBOSE_DISC_TYPE */
			tocbuffer[0] = 0x41;

			CDreadTrack(16, CDVD_MODE_2048, cdtempbuffer);
			offset = CDgetBufferOffset();
			i = 0;
			while ((*(playstationcdname + i) != 0) &&
			        (*(playstationcdname + i) == cdtempbuffer[offset + 8 + i]))  i++;
			if (*(playstationcdname + i) == 0)
			{
				i = 0;
				while ((*(ps1name + i) != 0) &&
				        (*(ps1name + i) == cdtempbuffer[offset + 1024 + i]))  i++;
				if (*(ps1name + i) == 0)
				{
#ifdef VERBOSE_DISC_TYPE
					PrintLog("CDVD driver: Detected Playstation CD disc.");
#endif /* VERBOSE_DISC_TYPE */
					tempdisctype = CDVD_TYPE_PSCD;
				}
				else
				{
#ifdef VERBOSE_DISC_TYPE
					PrintLog("CDVD driver: Detected Playstation 2 CD disc.");
#endif /* VERBOSE_DISC_TYPE */
					tempdisctype = CDVD_TYPE_PS2CD;
				} // ENDIF- Did we find the CD ident? (For Playstation 1 CDs)
			}
			else
			{
				tempdisctype = CDVD_TYPE_UNKNOWN;
			} // ENDIF- Did we find the Playstation name?
			break;

		default:
			return(-1);
	} // ENDSWITCH- What has ioctl disc type come up with?

	// Collect TN data
	cdheader.cdth_trk0 = 0;
	cdheader.cdth_trk1 = 0;

	s32result = ioctl(devicehandle, CDROMREADTOCHDR, &cdheader);
	if ((s32result == -1) || (errno != 0))
	{
#ifdef VERBOSE_WARNINGS
		PrintLog("CDVD driver:   Error reading TN: (%i) %i:%s",
		         s32result, errno, strerror(errno));
#endif /* VERBOSE_WARNINGS */
		cdheader.cdth_trk0 = 1;
		cdheader.cdth_trk1 = 1;
	} // ENDIF- Failed to read in track count? Assume 1 track.
#ifdef VERBOSE_DISC_INFO
	PrintLog("CDVD driver: Track Number Range: %i-%i",
	         cdheader.cdth_trk0, cdheader.cdth_trk1);
#endif /* VERBOSE_DISC_INFO */
	tocbuffer[2] = 0xA0;
	tocbuffer[7] = HEXTOBCD(cdheader.cdth_trk0);
	tocbuffer[12] = 0xA1;
	tocbuffer[17] = HEXTOBCD(cdheader.cdth_trk1);

	// Collect disc TD data
	cdtrack.cdte_track = CDROM_LEADOUT;
	cdtrack.cdte_format = CDROM_LBA;
	s32result = ioctl(devicehandle, CDROMREADTOCENTRY, &cdtrack);
	if ((s32result == -1) || (errno != 0))
	{
#ifdef VERBOSE_WARNINGS
		PrintLog("CDVD driver:   Error reading TD for disc: (%i) %i:%s",
		         s32result, errno, strerror(errno));
#endif /* VERBOSE_WARNINGS */
		return(-1);
	} // ENDIF- Trouble getting a track count?

	LBAtoMSF(cdtrack.cdte_addr.lba, &tocbuffer[27]);
#ifdef VERBOSE_DISC_INFO
	PrintLog("CDVD driver: Total Time: %i:%i",
	         tocbuffer[27], tocbuffer[28]);
#endif /* VERBOSE_DISC_INFO */
	tocbuffer[27] = HEXTOBCD(tocbuffer[27]);
	tocbuffer[28] = HEXTOBCD(tocbuffer[28]);
	tocbuffer[29] = HEXTOBCD(tocbuffer[29]);

	// Collect track TD data
	for (j = cdheader.cdth_trk0; j <= cdheader.cdth_trk1; j++)
	{
		cdtrack.cdte_track = j; // j-1?
		cdtrack.cdte_format = CDROM_LBA;
		s32result = ioctl(devicehandle, CDROMREADTOCENTRY, &cdtrack);
		if ((s32result == -1) || (errno != 0))
		{
#ifdef VERBOSE_WARNINGS
			PrintLog("CDVD driver:   Error reading TD for track %i: (%i) %i:%s",
			         j, s32result, errno, strerror(errno));
#endif /* VERBOSE_WARNINGS */
			// No more here...

		}
		else
		{
			LBAtoMSF(cdtrack.cdte_addr.lba, &tocbuffer[j*10 + 37]);
#ifdef VERBOSE_DISC_INFO
			PrintLog("CDVD driver: Track %i:  Data Mode %i   Disc Start Time:%i:%i.%i\n",
			         j,
			         cdtrack.cdte_datamode,
			         tocbuffer[j*10+37],
			         tocbuffer[j*10+38],
			         tocbuffer[j*10+39]);
#endif /* VERBOSE_DISC_INFO */
			tocbuffer[j*10 + 30] = cdtrack.cdte_datamode;
			tocbuffer[j*10 + 32] = HEXTOBCD(j);
			tocbuffer[j*10 + 37] = HEXTOBCD(tocbuffer[j*10 + 37]);
			tocbuffer[j*10 + 38] = HEXTOBCD(tocbuffer[j*10 + 38]);
			tocbuffer[j*10 + 39] = HEXTOBCD(tocbuffer[j*10 + 39]);
		} // ENDIF- Trouble getting a track count?
	} // NEXT j- Reading each track's info in turn

	errno = 0;
	disctype = tempdisctype; // Trigger the fact we have the info (finally)
	return(disctype);
} // END CDVDgetDiskType()
