#include "CDDriver.h"

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include "swis.h"

#include "cdswi.h"
#include "WimpLib:Exception.h"
#include "WimpLib:Log.h"
#include "Media.h"
#include "Metadata.h"
#include "WimpLib:mem.h"
#include "Options.h"
#include "WimpLib:Utils.h"

void Equalizer_Configure(int swi, EqualizerParams* pParams);

/*
 * CDDriver info:
 *
 * - The device can play objects named "::CDFSx" (a invalid filename)
 *   where x is a drive number.
 * - The data retained by the driver for the object is composed of
 *   1) The Control Block of drive x as required by the CD driver Swis.
 *   2) The end position for playing (required for
 *      CDDriver_Play(start pos, Pos_NoChange)
 * - The positions returned by CDDriver is
 *   MSF position of CD (Minutes, Seconds, Frames)
 *   converted into Frames count (75 frames per second).
 *
 */

extern const MediaDriver CDDriver;

#define BLOCK_SIZE 2352
#undef DISKSAMPLE_EXTERNAL
#undef TEST_FILE

#ifdef DISKSAMPLE_EXTERNAL
typedef struct
{
	int32_t          size;
	volatile int32_t start;
	volatile int32_t free;
	volatile int32_t finishflag;
	int32_t          dummy[12];
} inb_t;

typedef struct
{
	int   filesize;
	char* filetype;
	int   flags;
	struct
	{
		int channels;
		int	samplerate;
		int bitspersample;
		int blocksize;
	} pcm;
} typdesc;

#endif

typedef struct
{
	sCDControlBlock	m_CDBlock;
	uint32_t        m_DriveNr;
	bool            isDefined;
	uint32_t        m_CurTrack;
	uint32_t        m_StartPos;
	uint32_t        m_EndPos;
	uint32_t        m_Tracks;
	uint32_t        m_RangeMin;
	uint32_t        m_RangeMax;
	uint32_t        m_Length;
	uint32_t*       m_pStarts;
	uint32_t*       m_pLens;
	bool            m_HasData;
	// DiskSample
	uint32_t        handle;
	bool            isReady;
	uint32_t        startpos;
	uint32_t        curpos;
	uint32_t        endpos;
	uint32_t        volume;
#ifdef DISKSAMPLE_EXTERNAL
	uint32_t        writepos;
	inb_t*          pInBuffer;
#ifdef TEST_FILE
	int file; // Debug
#endif
	uint8_t         buffer[BLOCK_SIZE];
#endif
} CDObjectData;

#define DiskSample_Version			0x52ec0
#define DiskSample_Configure		0x52ec1
#define DiskSample_FileOpen			0x52ec2
#define DiskSample_StreamClose		0x52ec3
#define DiskSample_StreamCreate		0x52ec4
#define DiskSample_StreamSource		0x52ec5
#define DiskSample_StreamPlay		0x52ec8
#define DiskSample_StreamPause		0x52ec9
#define DiskSample_StreamStop		0x52eca
#define DiskSample_StreamPosition	0x52ecb
#define DiskSample_StreamVolume		0x52ecc
#define DiskSample_StreamIsReady	0x52ecd
#define DiskSample_StreamStatus		0x52ece
#define DiskSample_StreamChain		0x52ecf
#define DiskSample_StreamInfo		0x52ed0
#define DiskSample_StreamTexts		0x52ed1
#define DiskSample_StreamParam		0x52ed2
#define DiskSample_StreamDecoding	0x52ed3

static const char DiskModuleName[] = "DiskSample";

//------------------------------------------------------------------------------

static bool CDDriver_Ensure(MediaObject* pobj)
{
	CDObjectData* pData = pobj->m_pData;

	if (pData->isDefined)
		return true;

	if (FCDFS_ConvertDriveToDevice(pData->m_DriveNr, &pData->m_CDBlock))
		return false;

	pData->isDefined = true;

	return true;
}

void CDDriver_OpenDrawer(MediaObject* pobj)
{
	CDObjectData* pData = pobj->m_pData;

	if (!pData->isDefined)
		return;

	FCD_OpenDrawer(&pData->m_CDBlock);
}

void CDDriver_MountCD(MediaObject* pobj)
{
	CDObjectData*		pData = pobj->m_pData;
	sCDDiscTrackRange	range;
	sCDDiscSize			size;
	int					i;

//	Log("CD Description:\n");

	if (!CDDriver_Ensure(pobj))
	{
		CDDriver_DismountCD(pobj);
		return;
	}

	try
	{
		throw_os(FCD_DiscUsed(&pData->m_CDBlock, 0, &size));

		pData->m_Length = size.size;
//		Log("Total Size: %d blocks\n",pData->m_Length);

		throw_os(FCD_EnquireTrack(&pData->m_CDBlock, 0, &range));

		pData->m_RangeMin = range.range.min;
		pData->m_RangeMax = range.range.max;
		pData->m_Tracks   = range.range.max;
//		Log("First track: %d\n", pData->m_RangeMin);
//		Log("Last  track: %d\n", pData->m_RangeMax);

		pData->m_pStarts = throw_mem_alloc(sizeof(int) * (pData->m_Tracks + 1));
		pData->m_pLens = throw_mem_alloc(sizeof(int) * (pData->m_Tracks + 1));

		for (i = 1; i < pData->m_RangeMin; i++)
		{
			pData->m_pStarts[i] = -1;
			pData->m_pLens[i] = 0;
		}

		for (i = pData->m_RangeMin; i <= pData->m_Tracks; i++)
		{
			pData->m_pStarts[i] = -1;
			throw_os(FCD_EnquireTrack(&pData->m_CDBlock, i, &range));

			pData->m_pStarts[i] = range.track.addr;
			if (!(range.track.flags & 1))
			{
				pData->m_pLens[i] = 1;
//				Log("Audio ");
			}
			else
			{
				pData->m_pLens[i] = 0;
				pData->m_HasData = true;
			}
//			if (!(range.track.flags & 1))
//				Log("Track %d starts at %d\n", i, pData->m_pStarts[i]);
		}

		for (i = pData->m_RangeMin; i < pData->m_Tracks; i++)
		{
			// if audio
			if (pData->m_pLens[i])
				pData->m_pLens[i] = pData->m_pStarts[i+1] - pData->m_pStarts[i];
			else
				pData->m_pStarts[i] = -1;
		}
		if (pData->m_pLens[i])
			pData->m_pLens[i] = pData->m_Length - pData->m_pStarts[i];
		else
			pData->m_pStarts[i] = -1;
	}
	catch
	{
//		const exception* e = exception_current();

//		Log("MountCD Error: %s\n", e->error.errmess);

		CDDriver_DismountCD(pobj);
	}
	catch_end
}

void CDDriver_DismountCD(MediaObject* pobj)
{
	CDObjectData* pData = pobj->m_pData;

	mem_free(pData->m_pStarts);
	mem_free(pData->m_pLens);

	pData->m_RangeMin = 0;
	pData->m_RangeMax = 0;
	pData->m_Tracks   = 0;
	pData->m_Length   = 0;
	pData->m_pStarts  = NULL;
	pData->m_pLens    = NULL;
	pData->m_HasData  = false;
}

static uint32_t CDDriver_GetSection(MediaObject* pobj, uint32_t pos, uint32_t* start, uint32_t* end)
{
	CDObjectData* pData = pobj->m_pData;
	int i;
	uint32_t tstart = 0;
	uint32_t tend = 0;

	for (i = 1; i <= pData->m_Tracks; i++)
	{
		tstart = pData->m_pStarts[i];
		tend = tstart + pData->m_pLens[i];
		if ((tstart <= pos) && (pos < tend))
		{
			*start = tstart;
			*end = tend;
			return i;
		}
	}

	if (pos == tend)
	{
		*start = tstart;
		*end = tend;
		return pData->m_Tracks;
	}

	return -1;
}

static void CDDriver_FindRange(MediaObject* pobj, uint32_t pos, uint32_t* start, uint32_t* end)
{
	CDObjectData* pData = pobj->m_pData;
	int i, j;
	uint32_t tstart = 0;
	uint32_t tend = 0;

	for (i = 1; i <= pData->m_Tracks; i++)
	{
		tstart = pData->m_pStarts[i];
		tend = tstart + pData->m_pLens[i];
		if ((tstart <= pos) && (pos < tend))
		{
			j = i;
			for (i = j - 1; i > 0; i--)
			{
				if (!pData->m_pLens[i])
					break;
				tstart = pData->m_pStarts[i];
			}
			for (i = j + 1; i <= pData->m_Tracks; i++)
			{
				if (!pData->m_pLens[i])
					break;

				tend = pData->m_pStarts[i] + pData->m_pLens[i];
			}
			break;
		}
	}

	*start = tstart;
	*end = tend;
}

static uint32_t CDDriver_FindEndPos(MediaObject* pobj, uint32_t pos)
{
	CDObjectData* pData = pobj->m_pData;
	int i;
	uint32_t end;

	for (i = 1; i <= pData->m_Tracks; i++)
	{
		end = pData->m_pStarts[i] + pData->m_pLens[i];
		if ((pData->m_pStarts[i] <= pos) && (pos < end))
		{
			for (i++; i <= pData->m_Tracks; i++)
			{
				pos = end;
				end = pData->m_pStarts[i] + pData->m_pLens[i];
				if (!pData->m_pLens[i])
					return pos;
			}
			return end;
		}
	}

	return pos;
}

unsigned long CDDriver_IdentifyVolume(MediaObject* pobj)
{
	CDObjectData* pData = pobj->m_pData;
	unsigned long id = 0;

	if (!pData->isDefined)
		return id;

	id = Options()->Drivers.CDFS.NudgeSize[pData->m_DriveNr];
	FCD_GetIdentifier(&pData->m_CDBlock, &id, pData->m_HasData);

	return id;
}

static uint32_t CDDriver_DiskSamplePos(CDObjectData* pData, uint32_t frame_diff)
{
	uint64_t lpos;
	uint32_t pos;

	_swix(DiskSample_StreamDecoding, _INR(0,2), pData->handle, 6, &lpos);
	pos = (uint32_t) lpos;
	pos *= 75;
	pos /= 1000;
	pos += pData->startpos - frame_diff;

	return pos;
}

#ifdef DISKSAMPLE_EXTERNAL

static const _kernel_oserror* CDDriver_ReadAudio(CDObjectData* pData, int blockid, int nrblocks, uint8_t* pbuf)
{
#ifdef TEST_FILE
	const _kernel_oserror* e;

	if (!pData->file)
	{
		e = _swix(OS_Find, _INR(0,1)|_OUT(0)
				, 0x4f, "ADFS::HardDisc4.$.a/wav"
				, &pData->file);
		if (e) return e;
	}

	if (pData->file)
	{
		e = _swix(OS_Args, _INR(0,2), 1, pData->file, blockid*BLOCK_SIZE);
		if (!e) e = _swix(OS_GBPB, _INR(0,3), 4, pData->file, pbuf, nrblocks*BLOCK_SIZE);
		if (e) memset(pbuf, 0, nrblocks*BLOCK_SIZE);
	}
	else
	{
		memset(pbuf, 0, nrblocks*BLOCK_SIZE);
	}

	return NULL;
#else
	return _swix(0x041266, _INR(0,4)|_IN(7), 0, blockid, nrblocks, pbuf, 0, &pData->m_CDBlock);
#endif
}

static const _kernel_oserror* CDDriver_Fill(MediaObject* pobj)
{
	CDObjectData* pData = pobj->m_pData;
	const _kernel_oserror* e;
	int toread, minread, size, chunksize;
	uint8_t* buf;
	int frame_diff = (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_150FramesBug) ? 150 : 0;

	// if input is not finished (EOF) then we load another chunk of data
	if ((pData->pInBuffer == NULL) || pData->pInBuffer->finishflag)
		return NULL;

	if (pData->writepos >= pData->endpos)
	{
		pData->pInBuffer->finishflag = 1;
		return NULL;
	}

	chunksize = (pData->pInBuffer->size >> 2);

	// load a chunk of data
	toread = pData->pInBuffer->start - pData->pInBuffer->free;
	if (toread <= 0)
		toread += pData->pInBuffer->size;

	if (toread <= chunksize)
		return NULL;

	toread = (chunksize / BLOCK_SIZE);
	if (toread > 255) toread = 255;
	buf = (uint8_t*)(pData->pInBuffer + 1);

	if (toread > (pData->endpos - pData->writepos))
		toread = pData->endpos - pData->writepos;

	if (pData->pInBuffer->start > pData->pInBuffer->free)
		minread = toread;
	else
	{
		minread = pData->pInBuffer->size - pData->pInBuffer->free;
		minread /= BLOCK_SIZE;
		if (minread > toread) minread = toread;
	}

	if (minread)
	{
		e = CDDriver_ReadAudio(pData, pData->writepos + frame_diff, minread, buf + pData->pInBuffer->free);
		if (e) return e;
		pData->writepos += minread;
		pData->pInBuffer->free += minread * BLOCK_SIZE;
		if (pData->pInBuffer->free >= pData->pInBuffer->size)
			pData->pInBuffer->free -= pData->pInBuffer->size;
		toread -= minread;
	}

	if (toread > 0)
	{
		if (pData->pInBuffer->free != 0)
		{
			e = CDDriver_ReadAudio(pData, pData->writepos + frame_diff, 1, &pData->buffer[0]);
			if (e) return e;
			pData->writepos += 1;
			toread -= 1;
			size = pData->pInBuffer->size - pData->pInBuffer->free;
			memcpy(buf + pData->pInBuffer->free, &pData->buffer[0], size);
			memcpy(buf, &pData->buffer[size], BLOCK_SIZE - size);
			pData->pInBuffer->free = BLOCK_SIZE - size;
		}

		if (toread > 0)
		{
			e = CDDriver_ReadAudio(pData, pData->writepos + frame_diff, toread, buf + pData->pInBuffer->free);
			if (e) return e;
			pData->writepos += toread;
			pData->pInBuffer->free += toread * BLOCK_SIZE;
		}
	}

	return NULL;
}

#endif

//------------------------------------------------------------------------------

static const char CDDriver_Name[] = "Compact Disc";

static bool throw_CDDriver_LoadObject(MediaObject* pobj, const char* pObjectName)
{
	static const char	cd[] = "::CDFS";
	CDObjectData* volatile	pData = NULL;
	int			i;
	int			drive;

	// Check if object name is "::CDFSx" with x as drive nr
	if (strlen(pObjectName) != (strlen(cd) + 1))
		return false;

	for (i = 0; i < strlen(cd); i++)
	{
		if (toupper(pObjectName[i]) != cd[i])
		{
			return false;
		}
	}

	if (!isdigit(pObjectName[i]))
		return false;

	/*
	 * Allocate memory for object data
	 * get control block of drive x
	 * Register new object to Media interface
	 *
	 */
	drive = atoi(pObjectName + i);

	pData = throw_mem_alloc(sizeof(*pData));
	memset(pData, 0, sizeof(*pData));
	pData->m_DriveNr = drive;
	pData->handle = -1;
	pobj->m_Status = status_undef;

	if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_ReadAudioSWI)
		Media_Attach(pobj, pData, Media_HType_File);
	else
		Media_Attach(pobj, pData, (Media_HType) (Media_HType_CD0 + drive));

	CDDriver_Ensure(pobj);

	throw_MediaObject_SetText(pobj, EMetaId_StreamType, EMetaOrigin_Stream, CDDriver_Name);
	throw_MediaObject_SetText(pobj, EMetaId_ModuleName, EMetaOrigin_Stream, "CDFS");
	throw_MediaObject_SetMeta(pobj, EMetaId_ModuleHandle, EMetaOrigin_Stream, (void*) drive, 0);

	return true;
}

static void CDDriver_UnLoadObject(MediaObject* pobj)
{
	CDObjectData* pData = pobj->m_pData;

	if (pData)
	{
#ifdef TEST_FILE
		if (pData->file) _swi(OS_Find, _INR(0,1), 0, pData->file);
#endif
		CDDriver_Stop(pobj);
		if ((Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_ReadAudioSWI)
		&&  (Options()->Music_Files.bUnloadModules))
			RMKill(DiskModuleName);
		CDDriver_DismountCD(pobj);
		mem_free(pData);
		pobj->m_pData = NULL;
	}
}

void CDDriver_Play(MediaObject* pobj, uint64_t currentpos)
{
	CDObjectData* pData = pobj->m_pData;
	uint32_t  pos = (uint32_t) currentpos;
	const _kernel_oserror*	err = NULL;
	int frame_diff = (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_150FramesBug) ? 150 : 0;

	if (!pData->isDefined)
		return;

	pData->m_CurTrack = CDDriver_GetSection(pobj, pos, &pData->m_StartPos, &pData->m_EndPos);
//	Log("Try to play track %d, pos %d with frame correction %d\n", pData->m_CurTrack, pos, frame_diff);

	if (pData->m_CurTrack < 1) return;

	if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_ReadAudioSWI)
		Media_Attach(pobj, pData, Media_HType_File);
	else
		Media_Attach(pobj, pData, (Media_HType) (Media_HType_CD0 + pData->m_DriveNr));

	if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_ReadAudioSWI)
	{
		uint32_t startpos, endpos;
		CDDriver_FindRange(pobj, pos, &startpos, &endpos);
		startpos += frame_diff;
		endpos += frame_diff;
		pData->curpos = pos;

		// Check if range valid
		if (pData->handle != -1)
		{
			if ((startpos != pData->startpos)
			||  (endpos != pData->endpos))
			{
				_swix(DiskSample_StreamClose, _IN(0), pData->handle);
				pData->handle = -1;
			}
		}

		if (pData->handle == -1)
		{
			int insize = Options()->Drivers.DiskSample.InputBufferSize;
			throw_os(RMEnsure(DiskModuleName, 51, "System:Modules.Audio.DiskSample"));
			_swix(DiskSample_Configure, _INR(0,1),   8, Options()->Drivers.DiskSample.bFreeVolume);
			_swix(DiskSample_Configure, _INR(0,1),  10, Options()->Drivers.DiskSample.bEnableInterrupts);
			_swix(DiskSample_Configure, _INR(0,1), 256, Options()->Drivers.DiskSample.InputBufferSize);
			_swix(DiskSample_Configure, _INR(0,1), 257, Options()->Drivers.DiskSample.OutputBufferSize);
			err = _swix(DiskSample_StreamCreate, _IN(1)|_OUT(0)
			          , (insize > 256) ? insize : 256
			          , &pData->handle);
			if (err) goto error;

			pData->startpos = startpos;
			pData->endpos = endpos;
			pData->isReady = false;

#ifdef DISKSAMPLE_EXTERNAL
			pData->writepos = startpos;
			static const typdesc desc = {0, "PCM",  0x1, {2, 44100, 16, 4}};

			err = _swix(DiskSample_StreamSource, _INR(0,2)|_OUT(1)
						, pData->handle, 0, &desc
						, &pData->pInBuffer);
#else
			err = _swix(DiskSample_StreamSource, _INR(0,4), pData->handle, 4
			          , pData->m_DriveNr, startpos, endpos - 1);
#endif
			if (err)
			{
				_swix(DiskSample_StreamClose, _IN(0), pData->handle);
				pData->handle = -1;
				goto error;
			}
//			Log("Open from %d to %d, play %d\n", startpos, endpos, pData->curpos);
			// No loop
			_swix(DiskSample_StreamStatus, _IN(0)|_IN(1)|_IN(2), pData->handle, 0x8, 0x8);
			throw_MediaObject_SetMeta(pobj, EMetaId_ModuleName, EMetaOrigin_Stream, DiskModuleName, 0);
			throw_MediaObject_SetMeta(pobj, EMetaId_ModuleHandle, EMetaOrigin_Stream, (void*) pData->handle, 0);
#ifdef DISKSAMPLE_EXTERNAL
			CDDriver_Fill(pobj);
#endif
		}
		// if pos = start, song is either started or restarted
		if (pData->curpos == pData->startpos)
		{
			_swix(DiskSample_StreamPause, _IN(0), pData->handle);
			CDDriver_SetConfig(pobj, Media_CfgFrequency, &pobj->m_iSpeed);
			CDDriver_SetConfig(pobj, Media_CfgDMASize, &Options()->Music_Files.DMASize);
		}
		if (pobj->m_Status == status_play)
		{
			pos = CDDriver_DiskSamplePos(pData, frame_diff);
//			Log("Play pos %d\n", pData->curpos);
			if (abs(pos - pData->curpos) > 7)
			{
#ifdef DISKSAMPLE_EXTERNAL
				pData->writepos = pData->curpos;
#endif
				pos = pData->curpos - (pData->startpos - frame_diff);
				_swix(DiskSample_StreamPosition, _IN(0)|_IN(1), pData->handle, (pos * 1000) / 75);
				_swix(DiskSample_StreamPlay, _IN(0), pData->handle);
			}
		}
		else
		{
			// delayed till stream is ready
			pobj->m_Status = status_play;
			CDDriver_GetStatus(pobj);
		}
	}
	else if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_PlayTrackSWI)
	{
		err = FCD_PlayTrack(&pData->m_CDBlock, pData->m_CurTrack, 255);

		pobj->m_Status = status_play;
	}
	else if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_TrackByTrack)
	{
//		Log(" from %d to %d\n", pos, pData->m_EndPos - 1);
		err = FCD_PlayAudio(&pData->m_CDBlock, 0, pos + frame_diff, pData->m_EndPos + frame_diff - 1);

		pobj->m_Status = status_play;
	}
	else
	{
		uint32_t pos1 = 0;
		FCD_EnquireAddress(&pData->m_CDBlock, 0, &pos1);
		if ((pobj->m_Status != status_play)
		||  (abs(pos - pos1) > 7))
		{
			uint32_t endpos = CDDriver_FindEndPos(pobj, pos);
//			Log(" from %d to %d\n", pos, endpos - 1);
			err = FCD_PlayAudio(&pData->m_CDBlock, 0, pos + frame_diff, endpos + frame_diff - 1);
		}

		pobj->m_Status = status_play;
	}

error:
	if (err)
		throw_os(err);
	else
		FCD_EjectButton(&pData->m_CDBlock, true);
}

void CDDriver_Freeze(MediaObject* pobj, bool on)
{
	CDObjectData*	pData = pobj->m_pData;
	int		frame_diff = (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_150FramesBug) ? 150 : 0;

	if (!pData->isDefined)
		return;

	if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_ReadAudioSWI)
	{
		if (pData->handle == -1) return;
		if (on)
		{
			_swix(DiskSample_StreamPause, _IN(0), pData->handle);
			pobj->m_Status = status_pause;
		}
		else
		{
			_swix(DiskSample_StreamPlay, _IN(0), pData->handle);
			pobj->m_Status = status_play;
			CDDriver_SetConfig(pobj, Media_CfgFrequency, &pobj->m_iSpeed);
			CDDriver_SetConfig(pobj, Media_CfgDMASize, &Options()->Music_Files.DMASize);
		}
	}
	else
	{
		FCD_AudioPause(&pData->m_CDBlock, on);

		if (!(Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_PlayTrackSWI)
		&&  !on)
		{
			uint32_t pos = 0;
			uint32_t endpos;
			FCD_EnquireAddress(&pData->m_CDBlock, 0, &pos);

			if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_TrackByTrack)
				endpos = pData->m_EndPos;
			else
				endpos = CDDriver_FindEndPos(pobj, pos);

//			Log(" from %d to %d\n", pos, endpos - 1);
			FCD_PlayAudio(&pData->m_CDBlock, 0, pos + frame_diff, endpos + frame_diff - 1);
			pobj->m_Status = status_play;
		}
	}
}

void CDDriver_Stop(MediaObject* pobj)
{
	CDObjectData* pData = pobj->m_pData;

	if (pobj->m_Status == status_undef)
		return;

	if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_ReadAudioSWI)
	{
		pobj->m_Status = status_stop;
		if (pData->handle != -1)
		{
			_swix(DiskSample_StreamStop, _IN(0), pData->handle);
			_swix(DiskSample_StreamClose, _IN(0), pData->handle);
			pData->handle = -1;
		}
	}
	else
	{
		// Do a check to prevent the CD to spin into the drive, when not necessary
		if (pobj->m_Status != status_stop)
		{
			FCD_StopDisc(&pData->m_CDBlock);
			pobj->m_Status = status_stop;
		}
	}

	// Always do it, because if the CD stopped without a user action, the eject button is still locked
	FCD_EjectButton(&pData->m_CDBlock, false);
}

void CDDriver_Seek(MediaObject* pobj, EMedia_Seek set)
{
	CDObjectData* pData = pobj->m_pData;
	int frame_diff = (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_150FramesBug) ? 150 : 0;

	if (!pData->isDefined)
		return;

	if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_ReadAudioSWI)
	{
		if (pData->handle == -1) return;
		switch(set)
		{
			case EMedia_Seek_PreviousPos:
			{
				uint32_t pos = pData->curpos - 75;

				if (pos < pData->startpos)
				{
					_swix(DiskSample_StreamStop, _IN(0), pData->handle);
					pobj->m_Status = status_stop;
				}
				else
				{
#ifdef DISKSAMPLE_EXTERNAL
					pData->writepos = pos;
#endif
					pos -= pData->startpos - frame_diff;
					_swix(DiskSample_StreamPosition, _IN(0)|_IN(1), pData->handle, (pos * 1000) / 75);
				}
			}
			break;
			case EMedia_Seek_NextPos:
			{
				uint32_t pos = pData->curpos + 75;

				if (pos >= pData->endpos)
				{
					_swix(DiskSample_StreamStop, _IN(0), pData->handle);
					pobj->m_Status = status_stop;
				}
				else
				{
#ifdef DISKSAMPLE_EXTERNAL
					pData->writepos = pos;
#endif
					pos -= pData->startpos - frame_diff;
					_swix(DiskSample_StreamPosition, _IN(0)|_IN(1), pData->handle, (pos * 1000) / 75);
				}
			}
			break;
			default: ;
		}
	}
	else
	{
		switch(set)
		{
			case EMedia_Seek_PreviousPos:
			{
				uint32_t	pos = 0;
				uint32_t	endpos;

				FCD_EnquireAddress(&pData->m_CDBlock, 0, &pos);

				if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_PlayTrackSWI)
					return;

				if (pos < (pData->m_StartPos + 75))
				{
					FCD_StopDisc(&pData->m_CDBlock);
					pobj->m_Status = status_stop;
				}
				else
				{
					pos -= 75;
					if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_TrackByTrack)
						endpos = pData->m_EndPos;
					else
						endpos = CDDriver_FindEndPos(pobj, pos);

//					Log(" from %d to %d\n", pos, endpos - 1);
					FCD_PlayAudio(&pData->m_CDBlock, 0, pos + frame_diff, endpos + frame_diff - 1);
				}

				return;
			}
			break;
			case EMedia_Seek_NextPos:
			{
				uint32_t	pos = 0;
				uint32_t	endpos;

				FCD_EnquireAddress(&pData->m_CDBlock, 0, &pos);
				pos += 75;

				if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_PlayTrackSWI)
					return;

				if (pos >= pData->m_EndPos)
				{
					FCD_StopDisc(&pData->m_CDBlock);
					pobj->m_Status = status_stop;
				}
				else
				{
					if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_TrackByTrack)
						endpos = pData->m_EndPos;
					else
						endpos = CDDriver_FindEndPos(pobj, pos);

//					Log(" from %d to %d\n", pos, endpos - 1);
					FCD_PlayAudio(&pData->m_CDBlock, 0, pos + frame_diff, endpos + frame_diff - 1);
				}

				return;
			}
			break;
			default: ;
		}
	}
}

void CDDriver_SetLoopMode(MediaObject* pobj, Media_LoopMode mode)
{
	pobj->m_LoopMode = mode;
}

Media_LoopMode CDDriver_GetLoopMode(MediaObject* pobj)
{
	return pobj->m_LoopMode;
}

Media_Status CDDriver_GetStatus(MediaObject* pobj)
{
	CDObjectData*		pData = pobj->m_pData;
	const _kernel_oserror*	e = NULL;
	int frame_diff = (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_150FramesBug) ? 150 : 0;
	int	status;

	if (!CDDriver_Ensure(pobj))
	{
		pobj->m_Status = status_undef;
		return pobj->m_Status;
	}

	if (pData->handle != -1)
	{
		e = _swix(DiskSample_StreamStatus, _IN(0)|_IN(1)|_IN(2)|_OUT(1), pData->handle, 0, 0, &status);
		if (e) goto error;

#ifdef DISKSAMPLE_EXTERNAL
		e = CDDriver_Fill(pobj);
		if (e) goto error;
#endif

		if (!(status & 1))
		{
			// Not ready
			pobj->m_Status = status_busy;
		}
		else if (!pData->isReady)
		{
			// Ready for the first time
			pData->isReady = true;
			pobj->m_Status = status_play;
			CDDriver_SetConfig(pobj, Media_CfgFrequency, &pobj->m_iSpeed);
			uint32_t pos = pData->curpos;
//			Log("Play from %d\n", pos);
#ifdef DISKSAMPLE_EXTERNAL
			pData->writepos = pData->startpos;
#endif
			pos -= pData->startpos - frame_diff;
			_swix(DiskSample_StreamPosition, _IN(0)|_IN(1), pData->handle, (pos * 1000) / 75);
			_swix(DiskSample_StreamPlay, _IN(0), pData->handle);
		}
		else if (status & 2)
		{
			// Playing
			pobj->m_Status = status_play;

			uint32_t pos = CDDriver_DiskSamplePos(pData, frame_diff);
			if ((pData->m_EndPos <= pos)
			&&  (pos <= pData->m_Length))
			{
				// no pobj->m_Status = here
				// drive is still play we just let the player notice a change of track
//				Log("Stopped by pos %d >= than  end pos %d\n", pos, pData->m_EndPos);
				return status_stop;
			}
		}
		else if (status & 4)
		{
			// Paused
			// force pause?
			if (pobj->m_Status != status_pause)
			{
				pobj->m_Status = status_stop;
				pData->isReady = false;
			}
		}
		else
		{
			pobj->m_Status = status_stop;
			pData->isReady = false;
		}
	}
	else
	{
		if ((e = FCD_AudioStatus(&pData->m_CDBlock, &status)) != NULL)
		{
			pobj->m_Status = status_empty;
//			Log("GetStatus Error: %s\n", e->errmess);
		}
		else
		{
			switch(status)
			{
				case 0:
				{
					pobj->m_Status = status_play;

					if (!(Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_TrackByTrack))
					{
						uint32_t pos = 0;
						FCD_EnquireAddress(&pData->m_CDBlock, 0, &pos);

						if ((pData->m_EndPos <= pos)
						&&  (pos <= pData->m_Length))
						{
							// no pobj->m_Status = here
							// drive is still play we just let the player notice a change of track
//							Log("Stopped by pos %d >= than  end pos %d\n", pos, pData->m_EndPos);
							return status_stop;
						}
					}
				}
				break;
				case 1:
				{
					pobj->m_Status = status_pause;
				}
				break;
				case 3:
				case 4:
				case 5:
				{
					pobj->m_Status = status_stop;
//					Log("Stopped by status code %d\n", status);
				}
				break;
				default:
				{
					if (pobj->m_Status == status_empty)
						pobj->m_Status = status_stop;
				}
			}
		}
	}

	return pobj->m_Status;

error:
	pobj->m_Err = *e;
	pobj->m_Status = status_error;

	return pobj->m_Status;
}

void CDDriver_GetPosition(MediaObject* pobj, MediaPosition* pos)
{
	CDObjectData* pData = pobj->m_pData;
	uint32_t      pos1 = 0;
	int           frame_diff = (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_150FramesBug) ? 150 : 0;


	if (pData->isDefined)
	{
		if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_ReadAudioSWI)
		{
			if (pData->handle != -1)
			{
				if (pobj->m_Status == status_play)
				{
					pos1 = CDDriver_DiskSamplePos(pData, frame_diff);
					pos1 += pData->startpos - frame_diff;
					pData->curpos = pos1;
				}
				else
					pos1 = pData->curpos;
//				Log("Pos %d -> frames %d\n", (uint32_t) pos->driver.pos, pos1);
			}
		}
		else
		{
			if (FCD_EnquireAddress(&pData->m_CDBlock, 0, &pos1))
				pos1 = 0;
		}
	}

	pos->driver.start = pData->m_StartPos;
	pos->driver.pos = (uint64_t) pos1;
	pos->driver.end = pData->m_EndPos;
	pos->volume.sections = pData->m_Tracks;
	pos->volume.pos = ((pos1 + frame_diff) * 4) / 3;
	pos->volume.len = ((pData->m_Length + frame_diff) * 4) / 3;
	pos->section.pos = ((pos1 - pData->m_StartPos) * 4) / 3;
	pos->section.len = ((pData->m_EndPos - pData->m_StartPos) * 4) / 3;
	pos->section.nr = pData->m_CurTrack;
}

void CDDriver_GetSectionInfo(MediaObject* pobj, uint32_t section, uint64_t* pos, uint64_t* length)
{
	CDObjectData* pData = pobj->m_pData;

	*pos = pData->m_pStarts[section];
	*length = pData->m_pLens[section];
}

uint64_t CDDriver_GetSectionPos(MediaObject* pobj, uint32_t section)
{
	CDObjectData* pData = pobj->m_pData;

	return pData->m_pStarts[section];
}

void CDDriver_SetConfig(MediaObject* pobj, Media_Config type, const void* value)
{
	CDObjectData* pData = pobj->m_pData;
	uint32_t dummy;

	if (!pData->isDefined)
		return;

	if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_ReadAudioSWI)
	{
		if (type == Media_CfgVolume)
		{
			MediaVolume* pvol = (MediaVolume*) value;
			pData->volume = pvol->raw;
			uint32_t val = pvol->sysscaled;

			if (pData->handle != -1)
			{
				if (val > 0x40000) val = 0x40000;
				_swix(DiskSample_StreamVolume, _IN(0)|_IN(1), pData->handle, val >> 8);
			}
			return;
		}

		if (pData->handle == -1) return;
		switch(type)
		{
			case Media_CfgFrequency:
			{
				pobj->m_iSpeed = *(uint32_t*) value;
				if (pData->isReady)
				{
					int freq = pobj->m_iSpeed;
					_swix(DiskSample_StreamParam, _INR(0,1)|_OUT(2), pData->handle, 1, &freq);
					freq = Options_MixFrequency(Options(), freq);
					// Is 16-bit disabled ?
					_swix(DiskSample_Configure, _IN(0)|_IN(1)|_OUT(1), 4, -1, &dummy);
					if (dummy)
						_swix(DiskSample_Configure, _IN(0)|_IN(1), 0, Quality_FromFrequency(freq));
					else
						_swix(DiskSample_Configure, _IN(0)|_IN(1), 5, freq);
				}
			}
			break;
			case Media_CfgInterpol:
			{
				bool val = *(bool*) value;
				_swix(DiskSample_Configure, _IN(0)|_IN(1), 1, (val != 0));
			}
			break;
			case Media_CfgBalance:
			{
				uint32_t val = *(uint32_t*) value;
				_swix(DiskSample_Configure, _IN(0)|_IN(1), 2, val);
			}
			break;
			case Media_CfgStereoSeparation:
			{
				uint32_t val = *(uint32_t*) value;
				_swix(DiskSample_Configure, _IN(0)|_IN(1), 3, val);
			}
			break;
			case Media_CfgEqualizer:
			{
				COptions* popt = (COptions*) value;
				if (popt->Equalizer.bActive)
					Equalizer_Configure(0x072ec1, &popt->Equalizer.Params);
				else
					Equalizer_Configure(0x072ec1, NULL);
			}
			break;
			case Media_CfgDMASize:
			{
				uint32_t val = *(uint32_t*) value;
				// Sound_Configure
				if (val) _swix(0x40140, _INR(0,4), 0, val, 0, 0, 0);
			}
			break;
			default: ;
		}
	}
	else
	{
		switch(type)
		{
			case Media_CfgVolume:
			{
				MediaVolume* pvol = (MediaVolume*) value;
				uint32_t val = pvol->raw;
				int	vol[2];

				if (val > 0xffff) val = 0xffff;
				vol[0] = vol[1] = val;

				FCD_SetAudioParams(&pData->m_CDBlock, vol);
			}
			break;
			default: ;
		}
	}
}

uint32_t CDDriver_GetConfig(MediaObject* pobj, Media_Config type)
{
	CDObjectData* pData = pobj->m_pData;
	uint32_t val = 0xffffffff;

	if (Options()->Drivers.CDFS.PlayMode[pData->m_DriveNr] & ECDMode_ReadAudioSWI)
	{
		switch(type)
		{
			case Media_CfgVolume:
			{
				return pData->volume;
			}
			break;
			default: ;
		}
	}
	else
	{
		switch(type)
		{
			case Media_CfgVolume:
			{
				int	volume[3];

				if (FCD_GetAudioParams(&pData->m_CDBlock, volume))
					val = 0xffff;
				else
					// Return mean value of left & right volumes (range 0 - 0xffff)
					val = (volume[0] + volume[1]) >> 1;
			}
			break;
			default: ;
		}
	}

	return val;
}

//------------------------------------------------------------------------------

const MediaDriver CDDriver =
{
	  CDDriver_Name
	, throw_CDDriver_LoadObject
	, CDDriver_UnLoadObject
	, NULL
	, CDDriver_Play
	, CDDriver_Freeze
	, CDDriver_Stop
	, CDDriver_Seek
	, CDDriver_SetLoopMode
	, CDDriver_GetLoopMode
	, CDDriver_GetStatus
	, CDDriver_GetPosition
	, CDDriver_GetSectionPos
	, CDDriver_SetConfig
	, CDDriver_GetConfig
};
