#include "PListCD.h"

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

#include "WimpLib:Exception.h"
#include "WimpLib:File.h"
#include "WimpLib:Keyboard.h"
#include "WimpLib:mem.h"
#include "WimpLib:Message.h"
#include "WimpLib:Task.h"
#include "WimpLib:Timer.h"
#include "WimpLib:Utils.h"
#include "WimpLib:Window.h"

#include "Cmds.h"
#include "cdswi.h"
#include "CDDesc.h"
#include "CDDriver.h"
#include "DCDswi.h"
#include "DigitalCD.h"
#include "DocEvents.h"
#include "Player.h"
#include "PlayList.h"
#include "UserEvents.h"

struct PListCD
{
	PlayList  m_PlayList;
	CDDesc*   m_pDesc;
	unsigned long  m_CDIdentifier;
	int       m_drive;
	const char* m_pTitle;
	const char* m_pAuthor;
	int       m_TrackCount;
	uint64_t* m_pStarts;
	uint64_t* m_pLens;
	void*     m_CurIndex;
};

#define Icon_ShuffleMode 25
#define Icon_SetTrack    28
#define Icon_EditCD      35
#define Icon_OpenDrawer  37
#define Icon_EditCDMax   38

extern MediaDriver CDDriver;

static int CDPlayer_CheckCommand(void* handle, CmdID id)
{
	PListCD* This = handle;
	Player* pPlayer = This->m_PlayList.m_pPlayer;
	int state = 0;

	switch(id)
	{
		case Cmd_Ctrl_RestartSection:
		case Cmd_Ctrl_PrevSection:
		case Cmd_Ctrl_NextSection:
		{
			state = 0;
		}
		break;
		case Cmd_Ctrl_OpenCDDrawer:
		{
			state = ECmdState_Allow;
		}
		break;
		default:
			if (pPlayer)
				return Player_CheckCommand(pPlayer, id);
			else
				return App_CheckCommand(pPlayer, id);
	}

	return state;
}

static bool CDPlayer_ExecCommand(void* handle, CmdID id)
{
	PListCD* This = handle;
	Player* pPlayer = This->m_PlayList.m_pPlayer;
	bool done = true;

	switch(id)
	{
		case Cmd_App_ShowCDDatabase:
		{
			CDDescList_Show(CDDescList_Get(), This->m_pDesc);
		}
		break;
		case Cmd_Ctrl_OpenCDDrawer:
		{
			Player_ExecCommand(pPlayer, Cmd_Ctrl_Stop);
			if (This->m_PlayList.m_pObject)
				FCD_OpenDrawer((sCDControlBlock*) This->m_PlayList.m_pObject->m_pData);
		}
		break;
		default:
			if (pPlayer)
				done = Player_ExecCommand(pPlayer, id);
			else
				done = App_ExecCommand(NULL, id);
	}

	return done;
}

static const CmdHandler CDPlayer_CmdHandler =
{ CDPlayer_CheckCommand
, CDPlayer_ExecCommand
};

static EListenerAction CDPlayer_EventHandler(void* handle, const Event* e)
{
	Player* pPlayer = handle;
	PListCD* This = (PListCD*) Player_GetPlayList(pPlayer);

	switch(e->Type)
	{
		case EEvent_Mouse:
		{
			const Mouse* m = e->pData;

			if (m->but & EBut_Menu)
			{
				if  (m->i != Icon_SetTrack)
				{
					CmdHandler_OpenMenu(&CDPlayer_CmdHandler, This, "Ctrl_CD");

					return EListenerAction_StopEvent;
				}
				return EListenerAction_ContinueEvent;
			}

			switch(m->i)
			{
				case Icon_ShuffleMode:
				{
					Player_SetShuffleMode(pPlayer, Player_GetShuffleMode(pPlayer) ^ 1);
					return EListenerAction_StopEvent;
				}
				break;
				case Icon_OpenDrawer:
				{
					CmdHandler_ExecCommand(&CDPlayer_CmdHandler, This, Cmd_Ctrl_OpenCDDrawer);
					return EListenerAction_StopEvent;
				}
				break;
				default:
				{
					if ((m->i >= Icon_EditCD)
					&&  (m->i <= Icon_EditCDMax))
					{
						CmdHandler_ExecCommand(&CDPlayer_CmdHandler, This, Cmd_App_ShowCDDatabase);
						return EListenerAction_StopEvent;
					}
					return EListenerAction_ContinueEvent;
				}
			}
		}
		break;
		case EEvent_Key:
		{
			const Event_Key* key = e->pData;

			// Ignore key presses if shortcuts are disabled
			// unless a full screen plug-in forwards them to use
			if (!Player_AllowsShortcuts(pPlayer)
			&&  (DCDUtils_GetFullScreenPlugIn() == -1))
				return EListenerAction_ContinueEvent;

			if (CmdHandler_KeyPressed(&CDPlayer_CmdHandler, This, "Ctrl_CD", key->code))
				return EListenerAction_StopEvent;

			return EListenerAction_ContinueEvent;
		}
		break;
		case EEvent_Message:
		case EEvent_MessageWantAck:
		{
			const Msg_HelpRequest* msg = e->pData;

			switch(msg->hdr.action)
			{
				case EMsg_HelpRequest:
				{
					const char* str;

					if (msg->m.i == Icon_OpenDrawer)
					{
						str = Msg_Lookup("CtCDH001");
						Task_HelpReply(msg, str);
						return EListenerAction_StopEvent;
					}
					else if ((msg->m.i >= Icon_EditCD)
					&&  (msg->m.i <= Icon_EditCDMax))
					{
						str = Msg_Lookup("CtCDH");
						Task_HelpReply(msg, str);
						return EListenerAction_StopEvent;
					}
				}
				break;
			}
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

static void CDPlayer_ReloadSkin(Player* pPlayer)
{
	try
	{
		throw_Player_ReloadSkin(pPlayer);
		throw_Window_RegisterEventHandler(Player_GetWindow(pPlayer, 0), CDPlayer_EventHandler, pPlayer, true);
		throw_Window_RegisterEventHandler(Player_GetWindow(pPlayer, 1), CDPlayer_EventHandler, pPlayer, true);
	}
	catch
	{
		App_ReportException();
	}
	catch_end
}

static EListenerAction CDPlayer_Listener(void* handle, const Event* e)
{
	Player* pPlayer = handle;
	PlayList* pPlayList = Player_GetPlayList(pPlayer);

	switch(e->Type)
	{
		case EEvent_OptionHideCDDriveNumber:
		{
			Player_Refresh(pPlayer, player_refresh_listtitle);
		}
		break;
		case EEvent_OptionMixingParameters:
		{
			if (pPlayList->m_pObject)
			{
				Media_SetConfig(pPlayList->m_pObject, Media_CfgFrequency, &Options()->Music_Files.Frequency);
				Media_SetConfig(pPlayList->m_pObject, Media_CfgInterpol, &Options()->Music_Files.bInterpol);
				Media_SetConfig(pPlayList->m_pObject, Media_CfgVolumeRamping, &Options()->Music_Files.bVolumeRamping);
				Media_SetConfig(pPlayList->m_pObject, Media_CfgBalance, &Options()->Music_Files.Balance);
				Media_SetConfig(pPlayList->m_pObject, Media_CfgStereoSeparation, &Options()->Music_Files.StereoSeparation);
				Media_SetConfig(pPlayList->m_pObject, Media_CfgEqualizer, Options());
			}
		}
		break;
		case EEvent_OptionMixingFrequency:
		{
			if (pPlayList->m_pObject)
				Media_SetConfig(pPlayList->m_pObject, Media_CfgFrequency, e->pData);
		}
		break;
		case EEvent_OptionMixingInterpol:
		{
			if (pPlayList->m_pObject)
				Media_SetConfig(pPlayList->m_pObject, Media_CfgInterpol, e->pData);
		}
		break;
		case EEvent_OptionMixingVolRamping:
		{
			if (pPlayList->m_pObject)
				Media_SetConfig(pPlayList->m_pObject, Media_CfgVolumeRamping, e->pData);
		}
		break;
		case EEvent_OptionMixingBalance:
		{
			if (pPlayList->m_pObject)
				Media_SetConfig(pPlayList->m_pObject, Media_CfgBalance, e->pData);
		}
		break;
		case EEvent_OptionMixingStereo:
		{
			if (pPlayList->m_pObject)
				Media_SetConfig(pPlayList->m_pObject, Media_CfgStereoSeparation, e->pData);
		}
		break;
		case EEvent_OptionMixingEqualizer:
		{
			if (pPlayList->m_pObject)
				Media_SetConfig(pPlayList->m_pObject, Media_CfgEqualizer, e->pData);
		}
		break;
		case EEvent_OptionRescaleVolume:
		{
			Player_Refresh(pPlayer, player_refresh_volume);
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

static Player* throw_PListCD_CreatePlayer(PlayList* pPlayList)
{
	Player* pPlayer = throw_New_Player(pPlayList, "CD", (FPlayer_ReloadSkin) CDPlayer_ReloadSkin, Player_SlowPolling);

	try
	{
		Player_SetCmdHandler(pPlayer, &CDPlayer_CmdHandler, pPlayList);
		throw_Window_RegisterEventHandler(Player_GetWindow(pPlayer, 0), CDPlayer_EventHandler, pPlayer, true);
		throw_Window_RegisterEventHandler(Player_GetWindow(pPlayer, 1), CDPlayer_EventHandler, pPlayer, true);
		throw_Task_AddListener(EEvent_OptionHideCDDriveNumber, CDPlayer_Listener, pPlayer, false);
		throw_Task_AddListener(EEvent_OptionMixingParameters, CDPlayer_Listener, pPlayer, false);
		throw_Task_AddListener(EEvent_OptionMixingFrequency, CDPlayer_Listener, pPlayer, false);
		throw_Task_AddListener(EEvent_OptionMixingInterpol, CDPlayer_Listener, pPlayer, false);
		throw_Task_AddListener(EEvent_OptionMixingVolRamping, CDPlayer_Listener, pPlayer, false);
		throw_Task_AddListener(EEvent_OptionMixingBalance, CDPlayer_Listener, pPlayer, false);
		throw_Task_AddListener(EEvent_OptionMixingStereo, CDPlayer_Listener, pPlayer, false);
		throw_Task_AddListener(EEvent_OptionMixingEqualizer, CDPlayer_Listener, pPlayer, false);
		throw_Task_AddListener(EEvent_OptionRescaleVolume, CDPlayer_Listener, pPlayer, false);

		pPlayList->m_pPlayer = pPlayer;
		Player_Refresh(pPlayer, player_refresh_tracklist);
	}
	catch
	{
		pPlayList->m_pPlayer = NULL;
		Delete_Player(pPlayer);
		throw_current();
	}
	catch_end

	return pPlayer;
}

static void PListCD_DeletePlayer(PlayList* pPlayList)
{
	Player* pPlayer = pPlayList->m_pPlayer;

	if (!pPlayer) return;

	Window_DeRegisterEventHandler(Player_GetWindow(pPlayer, 0), CDPlayer_EventHandler, pPlayer);
	Window_DeRegisterEventHandler(Player_GetWindow(pPlayer, 1), CDPlayer_EventHandler, pPlayer);
	Task_RemoveListener(EEvent_OptionHideCDDriveNumber, CDPlayer_Listener, pPlayer);
	Task_RemoveListener(EEvent_OptionMixingParameters, CDPlayer_Listener, pPlayer);
	Task_RemoveListener(EEvent_OptionMixingFrequency, CDPlayer_Listener, pPlayer);
	Task_RemoveListener(EEvent_OptionMixingInterpol, CDPlayer_Listener, pPlayer);
	Task_RemoveListener(EEvent_OptionMixingVolRamping, CDPlayer_Listener, pPlayer);
	Task_RemoveListener(EEvent_OptionMixingBalance, CDPlayer_Listener, pPlayer);
	Task_RemoveListener(EEvent_OptionMixingStereo, CDPlayer_Listener, pPlayer);
	Task_RemoveListener(EEvent_OptionMixingEqualizer, CDPlayer_Listener, pPlayer);
	Task_RemoveListener(EEvent_OptionRescaleVolume, CDPlayer_Listener, pPlayer);
	Delete_Player(pPlayer);
	pPlayList->m_pPlayer = NULL;
}

/*-------------------------------------------------------------------------------*/

static void throw_PListCD_BuildListText(PListCD* This)
{
	// give a default identification to the disc
	{
		const char* author = (This->m_pDesc == NULL) ? &nil
							: CDDesc_GetMetaString(This->m_pDesc, EMetaId_StreamArtist);
		const char* title = (This->m_pDesc == NULL) ? &nil
							: CDDesc_GetMetaString(This->m_pDesc, EMetaId_StreamAlbum);

		if (author[0])
		{
			throw_mem_setstring(&This->m_pTitle, title);

			if (Options()->Drivers.CDFS.bHideDriveNumber)
				throw_mem_setstring(&This->m_pAuthor, author);
			else
				throw_mem_setstring(&This->m_pAuthor, SPrintf("<%1d> %s", This->m_drive, author));
		}
		else if (title[0])
		{
			if (Options()->Drivers.CDFS.bHideDriveNumber)
				throw_mem_setstring(&This->m_pTitle, title);
			else
				throw_mem_setstring(&This->m_pTitle, SPrintf("<%1d> %s", This->m_drive, title));

			throw_mem_setstring(&This->m_pAuthor, NULL);
		}
		else
		{
			throw_mem_setstring(&This->m_pTitle
				, Msg_CLookup("UnkCD2: <%1d> Audio CD %lu"
				, This->m_drive
				, (This->m_pDesc == NULL) ? This->m_CDIdentifier : CDDesc_GetId(This->m_pDesc)));
			throw_mem_setstring(&This->m_pAuthor, NULL);
		}
	}
}

/*-------------------------------------------------------------------------------*/

static bool PListCD_Init(PlayList* pPlayList, bool identify)
{
	char cdfs[] = "::CDFSx";
	int i;
	PListCD* This = (PListCD*) pPlayList;

	// clear tracks
	pPlayList->nr_tracks = 0;

	if (pPlayList->m_pObject == NULL)
	{
		snprintf(cdfs, sizeof(cdfs), "::CDFS%d", This->m_drive);
		Media_LoadObject(&pPlayList->m_pObject, cdfs, 0, &CDDriver);
		if (!pPlayList->m_pObject)
			__throw_string(__FILE__, __LINE__, "CDErr0: %d", This->m_drive);
	}

	CDDriver_DismountCD(pPlayList->m_pObject);
	CDDriver_MountCD(pPlayList->m_pObject);
	Media_GetPosition(pPlayList->m_pObject, &pPlayList->m_Position);

	This->m_pDesc = NULL;
	This->m_CDIdentifier = 0;
	This->m_TrackCount = pPlayList->m_Position.volume.sections;

	try
	{
		mem_free(This->m_pStarts);
		mem_free(This->m_pLens);
		This->m_pStarts = NULL;
		This->m_pLens = NULL;
		This->m_pStarts = throw_mem_alloc(This->m_TrackCount * sizeof(*This->m_pStarts));
		This->m_pLens = throw_mem_alloc(This->m_TrackCount * sizeof(*This->m_pLens));
	}
	catch
	{
		This->m_TrackCount = 0;
		App_ReportException();
	}
	catch_end

	for (i = 0; i < This->m_TrackCount; i++)
	{
		CDDriver_GetSectionInfo(pPlayList->m_pObject, i + 1, &This->m_pStarts[i], &This->m_pLens[i]);
		if (This->m_pLens[i])
			pPlayList->nr_tracks++;
	}

	if (!identify || (pPlayList->nr_tracks <= 0))
	{
		const char* title;

		if (pPlayList->nr_tracks <= 0)
		{
			if (This->m_TrackCount == 0)
				title = "NoCD";
			else
				title = "NotAudCD";
		}
		else
			title = "UnkCD";

		try
		{
			throw_mem_setstring(&This->m_pTitle, Msg_CLookup(title, This->m_drive));
		}
		catch
		{
			App_ReportException();
		}
		catch_end

		return true;
	}

	try
	{
		This->m_CDIdentifier = CDDriver_IdentifyVolume(pPlayList->m_pObject);
		This->m_pDesc = throw_CDDescList_GetWorkDesc
							( CDDescList_Get()
							, -1
							, This->m_CDIdentifier
							);
		if (This->m_pDesc != NULL)
			throw_CDDesc_FixCDSize(This->m_pDesc, This->m_TrackCount);
	}
	catch
	{
		This->m_pDesc = NULL;
		App_ReportException();
	}
	catch_end

	// identify the CD
	if (This->m_pDesc != NULL)
		CDDesc_Identify(This->m_pDesc, This->m_drive);

	throw_PListCD_BuildListText(This);

	return true;
}

static int PListCD_GetTrackList(PlayList* pPlayList, const void*** pptracks, bool bList, unsigned int shuffle)
{
	int i;
	int j;
	int count;
	const void** ptracks;
	PListCD* This = (PListCD*) pPlayList;
	Array* pPrefList = NULL;

	if (pPlayList->nr_tracks <= 0)
	{
		*pptracks = ptracks = mem_alloc(sizeof(void*));
		if (!ptracks) App_ReportError("NoMem");
		return 0;
	}

	if (This->m_pDesc != NULL)
	{
		pPrefList = CDDesc_GetPrefList(This->m_pDesc);
		if (Array_Count(pPrefList) <= 0) bList = false;
	}
	else bList = false;

	if (bList)
	{
		*pptracks = ptracks = mem_alloc(Array_Count(pPrefList) * sizeof(void*));
		if (!ptracks)
		{
			App_ReportError("NoMem");
			return 0;
		}

		for (i = 0, j = 0; i < Array_Count(pPrefList); i++)
		{
			int k = *(int*) Array_Get(pPrefList, i);

			if (This->m_pLens[k])
			{
				ptracks[j] = (void*) (1 + k);
				j++;
			}
		}

		count = j;
	}
	else
	{
		*pptracks = ptracks = mem_alloc(pPlayList->nr_tracks * sizeof(void*));
		if (!ptracks)
		{
			App_ReportError("NoMem");
			return 0;
		}

		for (i = 0, j = 0; i < This->m_TrackCount; i++)
		{
			if (This->m_pLens[i])
			{
				ptracks[j] = (void*) (1 + i);
				j++;
			}
		}

		count = pPlayList->nr_tracks;
	}

	if (shuffle)
	{
		for (i = 0; i < count; i++)
		{
			float jj = ((float) rand()) / RAND_MAX;
			int j = (int) (jj * count);
			const void* k = ptracks[i];
			if (j >= count) j = count - 1;
			ptracks[i] = ptracks[j];
			ptracks[j] = k;
		}
	}

	return count;
}

static const char* PListCD_GetText(PlayList* pPlayList, void* ptrack, EMetaId id)
{
	PListCD* This = (PListCD*) pPlayList;
	const char* text = NULL;

	if (ptrack)
	{
		const CDTrack* pTrack = (This->m_pDesc != NULL)
								? CDDesc_GetTrack(This->m_pDesc, ((int) ptrack) - 1)
								: NULL;

		switch(id)
		{
			case EMetaId_StreamName:
			case EMetaId_StreamTitle:
			{
				text = (pTrack != NULL)
						? CDTrack_GetLabel(pTrack)
						: SPrintf(Msg_Lookup("UnkTrack: Track %d"), ((int) ptrack));
			}
			break;
			case EMetaId_ModuleName:
			{
				const MetaData* meta = MediaObject_FindMeta(pPlayList->m_pObject, id);

				if (meta && meta->data)
					text = meta->data;
			}
			break;
			default:
			{
				text = (pTrack != NULL)
						? CDTrack_GetMetaString(pTrack, id)
						: &nil;
			}
		}
	}
	else
	{
		switch(id)
		{
			case EMetaId_StreamAlbum:
			{
				text = (This->m_pDesc != NULL)
						? CDDesc_GetMetaString(This->m_pDesc, EMetaId_StreamAlbum)
						: &nil;
				if (!text[0]) text = This->m_pTitle;
			}
			break;
			default:
			{
				text = (This->m_pDesc != NULL)
						? CDDesc_GetMetaString(This->m_pDesc, id)
						: &nil;
			}
		}
	}

	if (!text) text = &nil;

	return text;
}

static int PListCD_GetTrackNummer(PlayList* pPlayList, void* ptrack)
{
	PListCD* This = (PListCD*) pPlayList;

	if (pPlayList->m_pObject == NULL) return 0;

	if (ptrack == NULL) return 0;

	if (This->m_pDesc != NULL)
	{
		const CDTrack* pTrack = CDDesc_GetTrack(This->m_pDesc, ((int) ptrack) - 1);
		return CDTrack_GetOrder(pTrack);
	}

	return (int) ptrack;
}

static bool PListCD_PlayTrack(PlayList* pPlayList, void* ptrack)
{
	PListCD* This = (PListCD*) pPlayList;
	Player* pPlayer = pPlayList->m_pPlayer;
	int index = PListCD_GetTrackNummer(pPlayList, ptrack) - 1;
	uint64_t curpos;

	if (!pPlayer || !This->m_pLens[index] || (pPlayList->m_pObject == NULL))
		return false;

	Media_GetPosition(pPlayList->m_pObject, &pPlayList->m_Position);
	curpos = pPlayList->m_Position.driver.pos;
	if ((Media_GetStatus(pPlayList->m_pObject) != status_play)
	||  (This->m_pStarts[index] > (unsigned int) curpos)
	||  ((This->m_pStarts[index] + This->m_pLens[index]) < (unsigned int) curpos))
		curpos = (uint64_t) This->m_pStarts[index];

	Media_Play(pPlayList->m_pObject, curpos);
	Player_SetHardwareType(pPlayer, PlayList_GetHardwareType(pPlayList));
	This->m_CurIndex = ptrack;

	return true;
}

static Media_Status PListCD_GetStatus(PlayList* pPlayList)
{
	if (pPlayList->m_pObject == NULL) return status_empty;

	return Media_GetStatus(pPlayList->m_pObject);
}

static void PListCD_SetLoopMode(PlayList* pPlayList, Media_LoopMode mode)
{
	// nothing to do
	IGNORE(pPlayList);
	IGNORE(mode);
}

static void PListCD_Play(PlayList* pPlayList, uint64_t pos)
{
	if (pPlayList->m_pObject)
		Media_Play(pPlayList->m_pObject, pos);
}

static void PListCD_Stop(PlayList* pPlayList, bool bInError)
{
	PListCD* This = (PListCD*) pPlayList;
	IGNORE(bInError);

	This->m_CurIndex = NULL;
	if (This->m_PlayList.m_pObject)
		Media_Stop(pPlayList->m_pObject);
}

static void PListCD_GetPosition(PlayList* pPlayList, void* ptrack, MediaPosition* newpos)
{
	MediaPosition* pos = &pPlayList->m_Position;

	IGNORE(ptrack);

	if (pPlayList->m_pObject)
		Media_GetPosition(pPlayList->m_pObject, pos);

	*newpos = *pos;
	return;
}

static int PListCD_GetModuleHandle(PlayList* pPlayList, void* ptrack)
{
	PListCD* This = (PListCD*) pPlayList;
	IGNORE(ptrack);

	if (pPlayList->m_pObject)
	{
		const MetaData* meta = MediaObject_FindMeta(pPlayList->m_pObject, EMetaId_ModuleHandle);

		if (meta)
			return (int) meta->data;
	}

	return This->m_drive;
}

static void PListCD_SetConfig(PlayList* pPlayList, Media_Config type, const void* value)
{
	Player* pPlayer = pPlayList->m_pPlayer;

	if ((pPlayList->m_pObject == NULL)
	||  !pPlayer)
		return;

	if (type == Media_CfgVolume)
	{
		MediaVolume* pvol = (MediaVolume*) value;
		unsigned int mainval = pvol->sysscaled;
		unsigned int val = pvol->raw;

		pvol->raw /= 100;
		pvol->sysscaled /= 100;
		Media_SetConfig(pPlayList->m_pObject, type, pvol);

		if (Media_GetConfig(pPlayList->m_pObject, Media_CfgHardwareType) == Media_HType_File)
			val = mainval;

		DCDUtils_SetIntInfo(Player_GetDCDUtilsHandle(pPlayer), DCD_Info_MainVolume, val >> 8);
		DCDUtils_SetIntInfo(Player_GetDCDUtilsHandle(pPlayer), DCD_Info_ScaleVolume, val >> 8);
	}
	else
		Media_SetConfig(pPlayList->m_pObject, type, value);

	return;
}

static unsigned int PListCD_GetConfig(PlayList* pPlayList, Media_Config type)
{
	switch(type)
	{
		case PlayList_CfgAutoStart:
		{
			return Options()->Player.bAutoStartNewCDs;
		}
		break;
		case PlayList_CfgSeekable:
		{
			return true;
		}
		break;
		case Media_CfgVolume:
		{
			if (pPlayList->m_pObject != NULL)
			{
				return Media_GetConfig(pPlayList->m_pObject, Media_CfgVolume)*100;
			}
			else
			{
				return pPlayList->m_Volume;
			}
		}
		break;
		default:
		{
			if (pPlayList->m_pObject != NULL)
				return Media_GetConfig(pPlayList->m_pObject, (Media_Config) type);

			return 0xffffffff;
		}
		break;
	}
}

static EListenerAction PListCD_OnDocEvent(void* pListener, const Event* pEvent)
{
	PListCD* This = pListener;
	const DocEvent* pe = pEvent->pData;

	if (pe->pContainer != CDDescList_Get())
		return EListenerAction_ContinueEvent;

	switch(pe->event)
	{
		case EDocEvent_InsertElement:
		{
			if (This->m_pDesc == NULL)
			{
				This->m_pDesc = CDTrack_GetCDDesc(pe->pElement);
				if (CDDesc_GetId(This->m_pDesc) != This->m_CDIdentifier)
					This->m_pDesc = NULL;
				else
				{
					Player* pPlayer = This->m_PlayList.m_pPlayer;
					throw_PListCD_BuildListText(This);
					if (pPlayer) Player_Refresh(pPlayer, player_refresh_track | player_refresh_tracklist);
				}
			}
		}
		break;
		case EDocEvent_UpdateElement:
		{
			if (This->m_pDesc != NULL)
			{
				Player* pPlayer = This->m_PlayList.m_pPlayer;
				const CDTrack* pTrack = CDDesc_GetTrack(This->m_pDesc, (int) This->m_CurIndex - 1);

				if (pe->pElement == This->m_pDesc)
				{
					throw_PListCD_BuildListText(This);
					if (pPlayer) Player_Refresh(pPlayer, player_refresh_track | player_refresh_tracklist);
				}
				else if ((pTrack != NULL) && (pe->pElement == pTrack))
				{
					if (pPlayer) Player_Refresh(pPlayer, player_refresh_track);
				}
			}
		}
		break;
		case EDocEvent_RemoveElement:
		{
			if (pe->pElement == This->m_pDesc)
			{
				Player* pPlayer = This->m_PlayList.m_pPlayer;
				This->m_pDesc = NULL;
				throw_PListCD_BuildListText(This);
				if (pPlayer) Player_Refresh(pPlayer, player_refresh_track | player_refresh_tracklist);
			}
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

/*-------------------------------------------------------------------------------*/

static const PlayList_Cmds PListCD_Cmds =
{
	  PListCD_Init
	, PListCD_GetTrackList
	, PListCD_GetText
	, NULL
	, PListCD_PlayTrack
	, PListCD_GetStatus
	, PListCD_SetLoopMode
	, PListCD_Play
	, PListCD_Stop
	, PListCD_GetPosition
	, PListCD_GetTrackNummer
	, PListCD_GetModuleHandle
	, PListCD_SetConfig
	, PListCD_GetConfig
	, PlayList_GetUpdates
	, throw_PListCD_CreatePlayer
	, PListCD_DeletePlayer
};

static void throw_PListCD_PListCD(PListCD* This, int drive)
{
	char string[255];

	This->m_CDIdentifier = 0;
	This->m_pDesc = NULL;
	This->m_drive = drive;
	This->m_pTitle = throw_mem_allocstring(NULL);
	This->m_pAuthor = throw_mem_allocstring(NULL);
	This->m_pStarts = NULL;
	This->m_pLens = NULL;
	snprintf(string, sizeof(string), Msg_Lookup("Play1"), This->m_drive);

	throw_PlayList_PlayList(&This->m_PlayList, &PListCD_Cmds, string, NULL);
	throw_DocEvents_AddListener(NULL, This, PListCD_OnDocEvent);
}

static void PListCD_NotPListCD(PListCD* This)
{
	DocEvents_RemoveListener(NULL, This, PListCD_OnDocEvent);
	if (This->m_PlayList.m_pObject)
	{
		Media_UnLoadObject(This->m_PlayList.m_pObject);
		This->m_PlayList.m_pObject = NULL;
	}
	PlayList_NotPlayList(&This->m_PlayList);
	This->m_pDesc = NULL;
	mem_free(This->m_pTitle);
	This->m_pTitle = NULL;
	mem_free(This->m_pAuthor);
	This->m_pAuthor = NULL;
	mem_free(This->m_pStarts);
	This->m_pStarts = NULL;
	mem_free(This->m_pLens);
	This->m_pLens = NULL;
}

static PListCD* New_PListCD(int drive)
{
	PListCD* This = throw_mem_calloc(sizeof(*This), 1);

	try
	{
		throw_PListCD_PListCD(This, drive);
	}
	catch
	{
		PListCD_NotPListCD(This);
		mem_free(This);
		App_ReportException();
		return NULL;
	}
	catch_end

	return This;
}

static void Delete_PListCD(PListCD* This)
{
	if (This)
	{
		PListCD_NotPListCD(This);
		mem_free(This);
	}
}

CDDesc* PListCD_GetDesc(PListCD* This)
{
	if (((PlayList*) This)->nr_tracks == 0)
		PListCD_Init((PlayList*) This, true);
	return This->m_pDesc;
}

static struct
{
	int      FirstPlayListIndex;
} PListCDs;

void PListCDs_PListCDs(void)
{
	PListCDs.FirstPlayListIndex = PlayLists_Count();

	for (int i = 0; i < DigitalCD.Hardware.NrOfCDDrives; i++)
	{
		PListCD* plist = New_PListCD(i);

		if (!plist)
		{
			App_ReportError("CDErr0:%d", i);
			DigitalCD.Hardware.NrOfCDDrives = i;
			continue;
		}
	}
}

void PListCDs_NotPListCDs(void)
{
	for (int i = 0; i < DigitalCD.Hardware.NrOfCDDrives; i++)
	{
		PListCD* plist = PListCDs_Get(i);

		Delete_PListCD(plist);
	}
}

PListCD* PListCDs_Get(int drive)
{
	return (PListCD*) PlayLists_Get(PListCDs.FirstPlayListIndex + drive);
}
