#include "PListFiles.h"

#include <assert.h>
#include <ctype.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:Menu.h"
#include "WimpLib:Message.h"
#include "WimpLib:Task.h"
#include "WimpLib:Window.h"
#include "WimpLib:Utils.h"

#include "Cmds.h"
#include "DCDswi.h"
#include "DigitalCD.h"
#include "EditFile.h"
#include "EditRadio.h"
#include "FileList.h"
#include "FileTypes.h"
#include "FLTrack.h"
#include "WimpLib:Hourglass.h"
#include "Media.h"
#include "Metadata.h"
#include "Options.h"
#include "Player.h"
#include "PlayList.h"
#include "RndList.h"
#include "Setup.h"
#include "Soundswi.h"
#include "UserEvents.h"

#define PList_ProgramMode      0x00000001
#define PList_FixTrackVolume   0x00000002

struct PListFiles
{
	PlayList        m_PlayList;
	const char*     m_pDriverInfo;
	const char*     m_pTitle;
	Media_LoopMode  m_LoopMode;
	FileList*       m_pEdit;
	FileList*       m_pQueue;
	unsigned int    m_Flags;
	FLTrack*        m_pRefTrack; // Current Track
	FLTrack*        m_pNextTrack;
	MediaObject*    m_pNextObject;
	bool            m_bNextStarted;
	int             m_EditCount;
	RndList*        m_pRndList;
};

static PListFiles* App_pPListFiles = NULL;

static void PListFiles_SetLoopMode(PlayList* pPlayList, Media_LoopMode mode);
static void PListFiles_Stop(PlayList*, bool bInError);
static void PListFiles_OffsetTrackVolume(PListFiles*, int delta);

extern MediaDriver UrlDriver;

#define Icon_ShuffleMode 25
#define Icon_ListMode    27
#define Icon_SetTrack    28
#define Icon_DelX        35
#define Icon_DelFX       36
#define Icon_EditX       37
#define Icon_EditLX      38

#define Shuffle_Track_SortByRaw     0x00000
#define Shuffle_Track_SortByRandom  0x00001
#define Shuffle_Track_SortByNumber  0x00002
#define Shuffle_Track_SortByName    0x00003
#define Shuffle_Track_SortByMask    0x00003
#define Shuffle_GroupBy_Collective  0x00010
#define Shuffle_GroupBy_Artist      0x00020
#define Shuffle_GroupBy_Box         0x00040
#define Shuffle_GroupBy_Album       0x00080
#define Shuffle_GroupBy_Compilation 0x00100
#define Shuffle_GroupBy_CompArtist  0x00200
#define Shuffle_Random_Collective   0x01000
#define Shuffle_Random_Artist       0x02000
#define Shuffle_Random_Box          0x04000

#define Shuffle_SortAlbum_Name      0x00000
#define Shuffle_SortAlbum_Random    0x08000
#define Shuffle_SortAlbum_Date      0x10000
#define Shuffle_SortAlbum_Mask      0x18000

#define Shuffle_DisableAll          0x80000000
#define Shuffle_Mask                0x8001f3f3

static int Compare_Strings(const char* pa, const char* pb)
{
	if (pa)
	{
		if (*pa)
		{
			if (pb && *pb)
			{
				// First compare without taking case and accents into account
				int val = String_Collate(3, pa, pb, -1);

				// If equal, distinguish case and accents
				if (!val) val = String_Collate(0, pa, pb, -1);

				return val;
			}
			return 1;
		}
		else if (pb && !*pb)
			return 0;

		return -1;
	}
	else if (pb)
	{
		if (*pb)
			return -1;

		return 1;
	}

	return 0;
}

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

	if (pPlayer)
	{
		switch(id)
		{
			case Cmd_Ctrl_ToggleShuffle:
			{
				state = ECmdState_Allow;
				if (!(Player_GetShuffleMode(pPlayer) & Shuffle_DisableAll))
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_GroupBy_Collective:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_GroupBy_Collective) != 0)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_GroupBy_Artist:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_GroupBy_Artist) != 0)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_GroupBy_Box:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_GroupBy_Box) != 0)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_GroupBy_Album:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_GroupBy_Album) != 0)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_GroupBy_Compil:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_GroupBy_Compilation) != 0)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_GroupBy_CompilByArtist:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_GroupBy_CompArtist) != 0)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_SortCollective_Random:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_Random_Collective) != 0)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_SortCollective_Name:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_Random_Collective) == 0)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_SortArtist_Random:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_Random_Artist) != 0)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_SortArtist_Name:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_Random_Artist) == 0)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_SortBox_Random:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_Random_Box) != 0)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_SortBox_Name:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_Random_Box) == 0)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_SortAlbum_Random:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_SortAlbum_Mask) == Shuffle_SortAlbum_Random)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_SortAlbum_Name:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_SortAlbum_Mask) == Shuffle_SortAlbum_Name)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_SortAlbum_Date:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_SortAlbum_Mask) == Shuffle_SortAlbum_Date)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_SortBy_Random:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_Track_SortByMask) == Shuffle_Track_SortByRandom)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_SortBy_Name:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_Track_SortByMask) == Shuffle_Track_SortByName)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_SortBy_Number:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_Track_SortByMask) == Shuffle_Track_SortByNumber)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_SortBy_None:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				state = ECmdState_Allow;
				if ((shuffle & Shuffle_Track_SortByMask) == Shuffle_Track_SortByRaw)
					state |= ECmdState_Tick;
			}
			break;
			case Cmd_Ctrl_VolRel_FineUp:
			case Cmd_Ctrl_VolRel_FineDown:
			case Cmd_Ctrl_VolRel_Up:
			case Cmd_Ctrl_VolRel_Down:
			{
				state = ECmdState_Allow;
			}
			break;
			case Cmd_Ctrl_Track_Properties:
			case Cmd_Ctrl_Track_Show:
			case Cmd_Ctrl_File_Delete:
			{
				if (!Options()->Player.bParentalLock
				&&  (DCDUtils_GetFullScreenPlugIn() == -1)
				&&  (This->m_pRefTrack != NULL))
					state = ECmdState_Allow;
			}
			break;
			case Cmd_Ctrl_Track_Remove:
			{
				if (!Options()->Player.bParentalLock
				&&  (This->m_pRefTrack != NULL))
					state = ECmdState_Allow;
			}
			break;
			default:
				state = Player_CheckCommand(pPlayer, id);
		}
	}
	else
		state = App_CheckCommand(pPlayer, id);

	return state;
}

static bool FilesPlayer_ExecCommand(void* handle, CmdID id)
{
	PListFiles* This = handle;
	Player* pPlayer = This->m_PlayList.m_pPlayer;
	bool bRet = true;

	if (pPlayer)
	{
		switch(id)
		{
			case Cmd_Ctrl_ToggleShuffle:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				Player_SetShuffleMode(pPlayer, shuffle ^ Shuffle_DisableAll);
			}
			break;
			case Cmd_Ctrl_GroupBy_Collective:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				Player_SetShuffleMode(pPlayer, shuffle ^ Shuffle_GroupBy_Collective);
			}
			break;
			case Cmd_Ctrl_GroupBy_Artist:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				Player_SetShuffleMode(pPlayer, shuffle ^ Shuffle_GroupBy_Artist);
			}
			break;
			case Cmd_Ctrl_GroupBy_Box:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				Player_SetShuffleMode(pPlayer, shuffle ^ Shuffle_GroupBy_Box);
			}
			break;
			case Cmd_Ctrl_GroupBy_Album:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				Player_SetShuffleMode(pPlayer, shuffle ^ Shuffle_GroupBy_Album);
			}
			break;
			case Cmd_Ctrl_GroupBy_Compil:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				Player_SetShuffleMode(pPlayer, shuffle ^ Shuffle_GroupBy_Compilation);
			}
			break;
			case Cmd_Ctrl_GroupBy_CompilByArtist:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				Player_SetShuffleMode(pPlayer, shuffle ^ Shuffle_GroupBy_CompArtist);
			}
			break;
			case Cmd_Ctrl_SortCollective_Random:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				Player_SetShuffleMode(pPlayer, shuffle | Shuffle_Random_Collective);
			}
			break;
			case Cmd_Ctrl_SortCollective_Name:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				Player_SetShuffleMode(pPlayer, shuffle & ~Shuffle_Random_Collective);
			}
			break;
			case Cmd_Ctrl_SortArtist_Random:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				Player_SetShuffleMode(pPlayer, shuffle | Shuffle_Random_Artist);
			}
			break;
			case Cmd_Ctrl_SortArtist_Name:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				Player_SetShuffleMode(pPlayer, shuffle & ~Shuffle_Random_Artist);
			}
			break;
			case Cmd_Ctrl_SortBox_Random:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				Player_SetShuffleMode(pPlayer, shuffle | Shuffle_Random_Box);
			}
			break;
			case Cmd_Ctrl_SortBox_Name:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				Player_SetShuffleMode(pPlayer, shuffle & ~Shuffle_Random_Box);
			}
			break;
			case Cmd_Ctrl_SortAlbum_Random:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer) & ~Shuffle_SortAlbum_Mask;

				Player_SetShuffleMode(pPlayer, shuffle | Shuffle_SortAlbum_Random);
			}
			break;
			case Cmd_Ctrl_SortAlbum_Name:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer) & ~Shuffle_SortAlbum_Mask;

				Player_SetShuffleMode(pPlayer, shuffle | Shuffle_SortAlbum_Name);
			}
			break;
			case Cmd_Ctrl_SortAlbum_Date:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer) & ~Shuffle_SortAlbum_Mask;

				Player_SetShuffleMode(pPlayer, shuffle | Shuffle_SortAlbum_Date);
			}
			break;
			case Cmd_Ctrl_SortBy_Random:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				shuffle &= ~Shuffle_Track_SortByMask;
				Player_SetShuffleMode(pPlayer, shuffle | Shuffle_Track_SortByRandom);
			}
			break;
			case Cmd_Ctrl_SortBy_Name:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				shuffle &= ~Shuffle_Track_SortByMask;
				Player_SetShuffleMode(pPlayer, shuffle | Shuffle_Track_SortByName);
			}
			break;
			case Cmd_Ctrl_SortBy_Number:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				shuffle &= ~Shuffle_Track_SortByMask;
				Player_SetShuffleMode(pPlayer, shuffle | Shuffle_Track_SortByNumber);
			}
			break;
			case Cmd_Ctrl_SortBy_None:
			{
				unsigned int shuffle = Player_GetShuffleMode(pPlayer);

				shuffle &= ~Shuffle_Track_SortByMask;
				Player_SetShuffleMode(pPlayer, shuffle | Shuffle_Track_SortByRaw);
			}
			break;
			case Cmd_Ctrl_VolRel_FineUp:
			{
				PListFiles_OffsetTrackVolume(This, 5);
			}
			break;
			case Cmd_Ctrl_VolRel_FineDown:
			{
				PListFiles_OffsetTrackVolume(This, -5);
			}
			break;
			case Cmd_Ctrl_VolRel_Up:
			{
				PListFiles_OffsetTrackVolume(This, 20);
			}
			break;
			case Cmd_Ctrl_VolRel_Down:
			{
				PListFiles_OffsetTrackVolume(This, -20);
			}
			break;
			case Cmd_Ctrl_Track_Properties:
			{
				if (DCDUtils_GetFullScreenPlugIn() == -1)
				{
					List* plist = New_List();

					List_InsertBefore(plist, NULL, This->m_pRefTrack);
					EditFile_Edit(NULL, plist, This->m_pRefTrack);
					Delete_List(plist);
				}
			}
			break;
			case Cmd_Ctrl_Track_Show:
			{
				if (DCDUtils_GetFullScreenPlugIn() == -1)
				{
					FLTrack* pTrack = This->m_pRefTrack;

					if (pTrack)
					{
				  		FileList* pOwner = FLTrack_GetOwner(pTrack);

						if (pOwner)
						{
							FileList_Show(pOwner, -1);
							FileList_Show(pOwner, FileList_FindTrack(pOwner, pTrack));
						}
					}
				}
			}
			break;
			case Cmd_Ctrl_Track_Remove:
			{
				FLTrack* pTrack = This->m_pRefTrack;

				if (pTrack)
				{
					if (Options()->Player.bRemoveAsNeverPlay)
					{
						FLTrack_SetFlags(pTrack, FLTrack_NeverPlay, FLTrack_NeverPlay);
						FLTrack_RefreshViews(pTrack, This, true, false);
						Player_ShowMessage(pPlayer, Msg_Lookup("CmdNeverPlay"));
					}
					else
					{
						FileList* pOwner = FLTrack_GetOwner(pTrack);

						if (pOwner)
						{
							FileList_StartUpdate(pOwner);
							FileList_DelTrack(pOwner, pTrack);
							FileList_EndUpdate(pOwner, false, true);
						}
					}
				}
			}
			break;
			case Cmd_Ctrl_File_Delete:
			{
				FLTrack* pTrack = This->m_pRefTrack;

				if (pTrack)
				{
					if (DCDUtils_GetFullScreenPlugIn() == -1)
					{
						FileList* pOwner = FLTrack_GetOwner(pTrack);

						Menu_Close();

						if (Task_Query(true, "DSDelFile:"))
						{
							// Just in case it changes
							if (pTrack == This->m_pRefTrack)
							{
								PListFiles_Stop(&This->m_PlayList, false);
								throw_FLTrack_DeleteFile(pTrack);

								if (pOwner)
								{
									FileList_StartUpdate(pOwner);
									FileList_DelTrack(pOwner, pTrack);
									FileList_EndUpdate(pOwner, false, true);
								}
							}
						}
					}
				}
			}
			break;
			default:
				bRet = Player_ExecCommand(pPlayer, id);
		}
	}
	else
		bRet = App_ExecCommand(pPlayer, id);

	return bRet;
}

static const CmdHandler FilesPlayer_CmdHandler =
{ FilesPlayer_CheckCommand
, FilesPlayer_ExecCommand
};

static void FilesPlayer_RefreshIcons(Player* pPlayer)
{
	PListFiles* This = (PListFiles*) Player_GetPlayList(pPlayer);
	bool b;

	b = ((FilesPlayer_CheckCommand(This, Cmd_Ctrl_Track_Remove) & ECmdState_Allow) != 0);
	Icon_SetHighlight(Player_GetWindow(pPlayer, 0), Icon_DelX, b);
	Icon_SetHighlight(Player_GetWindow(pPlayer, 1), Icon_DelX, b);

	b = ((FilesPlayer_CheckCommand(This, Cmd_Ctrl_File_Delete) & ECmdState_Allow) != 0);
	Icon_SetHighlight(Player_GetWindow(pPlayer, 0), Icon_DelFX, b);
	Icon_SetHighlight(Player_GetWindow(pPlayer, 1), Icon_DelFX, b);

	b = ((FilesPlayer_CheckCommand(This, Cmd_Ctrl_Track_Properties) & ECmdState_Allow) != 0);
	Icon_SetHighlight(Player_GetWindow(pPlayer, 0), Icon_EditX, b);
	Icon_SetHighlight(Player_GetWindow(pPlayer, 1), Icon_EditX, b);

	b = ((FilesPlayer_CheckCommand(This, Cmd_App_ShowMainList) & ECmdState_Allow) != 0);
	Icon_SetHighlight(Player_GetWindow(pPlayer, 0), Icon_EditLX, b);
	Icon_SetHighlight(Player_GetWindow(pPlayer, 1), Icon_EditLX, b);
}

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

	switch(e->Type)
	{
		case EEvent_Null:
		{
			// Could be due to plug-ins
			FilesPlayer_RefreshIcons(pPlayer);
		}
		break;
		case EEvent_Mouse:
		{
			const Mouse* m = e->pData;

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

					return EListenerAction_StopEvent;
				}
				return EListenerAction_ContinueEvent;
			}

			switch(m->i)
			{
				case Icon_ShuffleMode:
				{
					if (m->but == EBut_Adjust)
					{
						unsigned int shuffle = Player_GetShuffleMode(pPlayer);

						Player_SetShuffleMode(pPlayer, shuffle ^ Shuffle_DisableAll);
					}
					else
					{
						CmdHandler_OpenMenu(&FilesPlayer_CmdHandler, This, "Ctrl_File_Shuffle");
					}

					return EListenerAction_StopEvent;
				}
				break;
				case Icon_DelX:
				{
					CmdHandler_ExecCommand(&FilesPlayer_CmdHandler, This, Cmd_Ctrl_Track_Remove);
					return EListenerAction_StopEvent;
				}
				break;
				case Icon_DelFX:
				{
					CmdHandler_ExecCommand(&FilesPlayer_CmdHandler, This, Cmd_Ctrl_File_Delete);
					return EListenerAction_StopEvent;
				}
				break;
				case Icon_EditX:
				{
					if (m->but == EBut_Adjust)
						CmdHandler_ExecCommand(&FilesPlayer_CmdHandler, This, Cmd_Ctrl_Track_Show);
					else
						CmdHandler_ExecCommand(&FilesPlayer_CmdHandler, This, Cmd_Ctrl_Track_Properties);
					return EListenerAction_StopEvent;
				}
				break;
				case Icon_EditLX:
				{
					if (m->but == EBut_Adjust)
						CmdHandler_ExecCommand(&FilesPlayer_CmdHandler, This, Cmd_App_ShowFileQueue);
					else
						CmdHandler_ExecCommand(&FilesPlayer_CmdHandler, This, Cmd_App_ShowMainList);

					return EListenerAction_StopEvent;
				}
				break;
			}

			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(&FilesPlayer_CmdHandler, This, "Ctrl_File", 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_DelX)
						str = Msg_Lookup("CtDSH001");
					else if (msg->m.i == Icon_DelFX)
						str = Msg_Lookup("CtDSH002");
					else if (msg->m.i == Icon_EditX)
						str = Msg_Lookup("CtDSH003");
					else if (msg->m.i == Icon_EditLX)
						str = Msg_Lookup("CtDSH004");
					else if (msg->m.i == Icon_ListMode)
						str = Msg_Lookup("CtDSH005");
					else return EListenerAction_ContinueEvent;

					Task_HelpReply(msg, str);
					return EListenerAction_StopEvent;
				}
				break;
			}
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

static void FilesPlayer_ReloadSkin(Player* pPlayer)
{
	try
	{
		throw_Player_ReloadSkin(pPlayer);
		throw_Window_RegisterEventHandler(Player_GetWindow(pPlayer, 0), FilesPlayer_EventHandler, pPlayer, true);
		throw_Window_RegisterEventHandler(Player_GetWindow(pPlayer, 1), FilesPlayer_EventHandler, pPlayer, true);

		FilesPlayer_RefreshIcons(pPlayer);
	}
	catch
	{
		App_ReportException();
	}
	catch_end
}

static Player* throw_PListFiles_CreatePlayer(PlayList* pPlayList)
{
	Player* pPlayer = throw_New_Player(pPlayList, "", (FPlayer_ReloadSkin) FilesPlayer_ReloadSkin
									, Player_SeparateShuffle | Player_ShuffleMenu | Player_RememberPosition);

	try
	{
		Player_SetCmdHandler(pPlayer, &FilesPlayer_CmdHandler, pPlayList);
		throw_Window_RegisterEventHandler(Player_GetWindow(pPlayer, 0), FilesPlayer_EventHandler, pPlayer, true);
		throw_Window_RegisterEventHandler(Player_GetWindow(pPlayer, 1), FilesPlayer_EventHandler, pPlayer, true);
		throw_Task_AddListener(EEvent_Null, FilesPlayer_EventHandler, pPlayer, false);

		pPlayList->m_pPlayer = pPlayer;

		Player_SetShuffleMode(pPlayer, Player_GetShuffleMode(pPlayer) & Shuffle_Mask);
		Player_Refresh(pPlayer, player_refresh_tracklist);
		FilesPlayer_RefreshIcons(pPlayer);
	}
	catch
	{
		pPlayList->m_pPlayer = NULL;
		Delete_Player(pPlayer);
		throw_current();
	}
	catch_end

	return pPlayer;
}

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

	if (!pPlayer) return;

	Task_RemoveListener(EEvent_Null, FilesPlayer_EventHandler, pPlayer);
	Window_DeRegisterEventHandler(Player_GetWindow(pPlayer, 0), FilesPlayer_EventHandler, pPlayer);
	Window_DeRegisterEventHandler(Player_GetWindow(pPlayer, 1), FilesPlayer_EventHandler, pPlayer);
	Delete_Player(pPlayList->m_pPlayer);
	pPlayList->m_pPlayer = NULL;
}

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

static MediaObject* PListFiles_LoadTrack(FLTrack* pTrack)
{
	const char* filename = NULL;
	MediaObject* pObject = NULL;
	file_type type;

	try
	{
		const CLoadFileType* pLoader = NULL;
		const _kernel_oserror* e = NULL;

		// Build filename
		throw_mem_setstring(&filename, FLTrack_GetFilename(pTrack));

		if ((type = File_GetFileType(filename)) == -2)
			throw_string("DSErr3:%s", filename);

		while(!pObject)
		{
			pLoader = LoadTypes_FindNext(type, pLoader);

			if (!pLoader)
			{
				// Throw last loader error, or no loader
				if (e) throw_os(e);
				throw_string("DSErr5:%d", type);
			}

			e = Media_LoadObject(&pObject, filename, FLTrack_GetTimFlags(pTrack), pLoader->pdriver);
			if (e && (e->errnum != 0x0081b000)) // value is "Format not supported"
				throw_os(e);

			if (!e) throw_MediaObject_MergeMeta(pObject, FLTrack_GetMeta(pTrack));
		}
	}
	catch
	{
		if (memcmp(&exception_current()->error, &Media_ErrorModuleInUse, sizeof(Media_ErrorModuleInUse)))
			App_DescribeError(&exception_current()->error, "DSErr0:%s", filename);
	}
	catch_end

	mem_free(filename);

	return pObject;
}

static void PListFiles_StopCurrentTrack(PListFiles* This, bool bInError)
{
	FLTrack* pRefTrack = This->m_pRefTrack;
	bool bMod = false;

	if (!pRefTrack) return;

	This->m_pRefTrack = NULL;

	// Stop playing, Unload file
	if (This->m_PlayList.m_pObject)
	{
		unsigned int fix;

		if ((This->m_Flags & PList_FixTrackVolume)
		&&  ((fix = Media_GetConfig(This->m_PlayList.m_pObject, Media_CfgVolumeScale)) != 0xffffffff))
		{
			FLTrack_SetVolume(pRefTrack, fix);
			bMod = true;
		}
		Media_Stop(This->m_PlayList.m_pObject);
		Media_UnLoadObject(This->m_PlayList.m_pObject);
		This->m_PlayList.m_pObject = NULL;
	}

	if (bInError)
		bMod |= FLTrack_SetFlags(pRefTrack, FLTrack_FailedToPlay, FLTrack_FailedToPlay);

	// Remove playing mark in playlist
	FLTrack_RefreshViews(pRefTrack, This, bMod, false);

	// Remove driver info
	mem_free(This->m_pDriverInfo);
	This->m_pDriverInfo = NULL;
}

static void PListFiles_StopNextTrack(PListFiles* This)
{
	FLTrack* pNextTrack = This->m_pNextTrack;

	if (!pNextTrack) return;

	This->m_pNextTrack = NULL;

	// Stop playing, Unload file
	if (This->m_pNextObject)
	{
		Media_Stop(This->m_pNextObject);
		Media_UnLoadObject(This->m_pNextObject);
		This->m_pNextObject = NULL;
		This->m_bNextStarted = false;
	}
}

static void PListFiles_OffsetTrackVolume(PListFiles* This, int delta)
{
	if (This->m_pRefTrack)
	{
		FLTrack_SetVolume(This->m_pRefTrack, FLTrack_GetVolume(This->m_pRefTrack) + delta);
		FLTrack_RefreshViews(This->m_pRefTrack, This, true, false);
		PListFiles_RefreshPlayer(This, player_refresh_volume);
		if (This->m_PlayList.m_pPlayer)
			Player_ShowMessage(This->m_PlayList.m_pPlayer
			                 , SPrintf(Msg_Lookup("CmdRelVolume:")
			                 , FLTrack_GetVolume(This->m_pRefTrack)/1.));
	}
}

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

static bool PListFiles_Init(PlayList* pPlayList, bool identify)
{
	PListFiles* This = (PListFiles*) pPlayList;
	const char* title;

	IGNORE(identify);

	// clear all texts
	if (This->m_PlayList.nr_tracks <= 0)
		title = Msg_Lookup("NoDS");
	else
		title = Msg_Lookup("UnkDS");
	mem_setstring(&This->m_pTitle, title);

	return false;
}

// Order of enum must follow sorting order (NC = not Comp, LD = last ditch)
typedef enum
{ Entity_Unknown             = 0x00
, Entity_Comp                = 0x01
, Entity_NC                  = 0x02
, Entity_Comp_Collective     = 0x10
, Entity_NC_Collective       = 0x11
, Entity_LD_Collective       = 0x12
, Entity_Comp_Artist         = 0x20
, Entity_NC_Artist           = 0x21
, Entity_LD_Artist           = 0x22
, Entity_Comp_Date           = 0x30
, Entity_NC_Date             = 0x31
, Entity_Comp_Box            = 0x40
, Entity_NC_Box              = 0x41
, Entity_Comp_Album          = 0x50
, Entity_NC_Album            = 0x51
, Entity_Order               = 0xf0
, Entity_None                = 0xf1
, Entity_Mask_Coarse         = 0xf0
} Entity;

static Entity Track_GetSubEntity(const FLTrack* pTrack, unsigned int shuffle, Entity e, const char** pData)
{
	switch (e)
	{
		case Entity_Unknown:
		{
			if (shuffle & Shuffle_GroupBy_Compilation)
			{
				if (FLTrack_GetFlags(pTrack) & FLTrack_Compilation)
				{
					if (*FLTrack_GetMetaString(pTrack, EMetaId_StreamAlbum)
					||  ((shuffle & Shuffle_GroupBy_Box) && (*FLTrack_GetMetaString(pTrack, EMetaId_StreamBox))))
						return Track_GetSubEntity(pTrack, shuffle, Entity_Comp, pData);
				}
			}

			return Track_GetSubEntity(pTrack, shuffle, Entity_NC, pData);
		}
		break;
	// Entities belonging to a groupment by compilation
		case Entity_Comp:
		{
			*pData = FLTrack_GetMetaString(pTrack, EMetaId_StreamDate);
			if ((shuffle & Shuffle_GroupBy_Album)
			&&  ((shuffle & Shuffle_SortAlbum_Mask) == Shuffle_SortAlbum_Date)
			&& **pData)
				return Entity_Comp_Date;
		}
		// no break;
		case Entity_Comp_Date:
		{
			*pData = FLTrack_GetMetaString(pTrack, EMetaId_StreamBox);
			if ((shuffle & Shuffle_GroupBy_Box) && **pData)
				return Entity_Comp_Box;
		}
		// no break;
		case Entity_Comp_Box:
		{
			*pData = FLTrack_GetMetaString(pTrack, EMetaId_StreamAlbum);
			if (**pData)
				return Entity_Comp_Album;
		}
		// no break;
		case Entity_Comp_Album:
		{
			*pData = FLTrack_GetMetaString(pTrack, EMetaId_StreamCollective);
			if ((shuffle & Shuffle_GroupBy_CompArtist)
			&&  (shuffle & Shuffle_GroupBy_Collective)
			&&  **pData)
				return Entity_Comp_Collective;
		}
		// no break;
		case Entity_Comp_Collective:
		{
			*pData = FLTrack_GetMetaString(pTrack, EMetaId_StreamArtist);
			if ((shuffle & Shuffle_GroupBy_CompArtist) && **pData)
				return Entity_Comp_Artist;
		}
		// no break;
		case Entity_Comp_Artist:
		{
			if (*FLTrack_GetMetaString(pTrack, EMetaId_StreamAlbum))
				return Entity_Order;

			return Entity_None;
		}
		break;
	// Entities not part of a groupment by compilation
		case Entity_NC:
		{
			*pData = FLTrack_GetMetaString(pTrack, EMetaId_StreamCollective);
			if ((shuffle & Shuffle_GroupBy_Collective) && **pData)
				return Entity_NC_Collective;
		}
        // no break;
        case Entity_NC_Collective:
		{
			*pData = FLTrack_GetMetaString(pTrack, EMetaId_StreamArtist);
			if ((shuffle & Shuffle_GroupBy_Artist) && **pData)
				return Entity_NC_Artist;
		}
		// no break;
        case Entity_NC_Artist:
		{
			*pData = FLTrack_GetMetaString(pTrack, EMetaId_StreamDate);
			if ((shuffle & Shuffle_GroupBy_Album)
			&&  ((shuffle & Shuffle_SortAlbum_Mask) == Shuffle_SortAlbum_Date)
			&& **pData)
				return Entity_NC_Date;
		}
		// no break;
		case Entity_NC_Date:
		{
			*pData = FLTrack_GetMetaString(pTrack, EMetaId_StreamBox);
			if ((shuffle & Shuffle_GroupBy_Box) && **pData)
				return Entity_NC_Box;
		}
		// no break;
        case Entity_NC_Box:
		{
			*pData = FLTrack_GetMetaString(pTrack, EMetaId_StreamAlbum);
			if ((shuffle & Shuffle_GroupBy_Album) && **pData)
				return Entity_NC_Album;
		}
		// no break;
        case Entity_NC_Album:
		{
			if (e == Entity_NC_Album)
				return Entity_Order;

			if ((e != Entity_NC)
			||	!(shuffle & Shuffle_GroupBy_Album))
				return Entity_None;

			*pData = FLTrack_GetMetaString(pTrack, EMetaId_StreamCollective);
			if ((shuffle & Shuffle_GroupBy_Collective) && **pData)
				return Entity_LD_Collective;
		}
		case Entity_LD_Collective:
		{
			*pData = FLTrack_GetMetaString(pTrack, EMetaId_StreamArtist);
			if (**pData)
				return Entity_LD_Artist;
		}
		break;
	}

	return Entity_None;
}

static int Compare_Tracks(PListFiles* This, unsigned int shuffle, const FLTrack* pTrackA, const FLTrack* pTrackB)
{
	const char* pDataA;
	const char* pDataB;
	int val;
	Entity entityA = Entity_Unknown;
	Entity entityB = Entity_Unknown;

	while(1)
	{
		entityA = Track_GetSubEntity(pTrackA, shuffle, entityA, &pDataA);
		entityB = Track_GetSubEntity(pTrackB, shuffle, entityB, &pDataB);

		// Coarse entity compare
		val = (entityA & Entity_Mask_Coarse) - (entityB & Entity_Mask_Coarse);
		if (val) return val;

		// They belong to the same broad kind of entity

		// Terminal entities?
		if (entityA >= Entity_Order)
		{
			val = entityA - entityB;
			if (val) return val;
			break;
		}

		// Ordering/shuffling of non-terminal entities
		val = RndList_GetOrder(This->m_pRndList, pDataA)
	    	- RndList_GetOrder(This->m_pRndList, pDataB);
		if (val) return val;

		// Full entity compare
		val = entityA - entityB;
		if (val) return val;

		// Albums/Sets with same name but different artists must be distinguished
		if ((entityA == Entity_LD_Artist)
		||  (entityA == Entity_Comp_Artist)
		||  (entityA == Entity_NC_Artist)
		||  (entityA == Entity_NC_Album)
		||  (entityA == Entity_NC_Box))
		{
			pDataA = FLTrack_GetMetaString(pTrackA, EMetaId_StreamCollective);
			pDataB = FLTrack_GetMetaString(pTrackB, EMetaId_StreamCollective);

			val = RndList_GetOrder(This->m_pRndList, pDataA)
			    - RndList_GetOrder(This->m_pRndList, pDataB);
			if (val) return val;
		}

		if ((entityA == Entity_NC_Album)
		||  (entityA == Entity_NC_Box))
		{
			pDataA = FLTrack_GetMetaString(pTrackA, EMetaId_StreamArtist);
			pDataB = FLTrack_GetMetaString(pTrackB, EMetaId_StreamArtist);

			val = RndList_GetOrder(This->m_pRndList, pDataA)
			    - RndList_GetOrder(This->m_pRndList, pDataB);
			if (val) return val;
		}

		if ((entityA == Entity_Comp_Album)
		||  (entityA == Entity_NC_Album))
		{
			pDataA = FLTrack_GetMetaString(pTrackA, EMetaId_StreamDate);
			pDataB = FLTrack_GetMetaString(pTrackB, EMetaId_StreamDate);

			val = RndList_GetOrder(This->m_pRndList, pDataA)
			    - RndList_GetOrder(This->m_pRndList, pDataB);
			if (val) return val;
		}

		if ((entityA == Entity_Comp_Album)
		||  (entityA == Entity_NC_Album))
		{
			pDataA = FLTrack_GetMetaString(pTrackA, EMetaId_StreamBox);
			pDataB = FLTrack_GetMetaString(pTrackB, EMetaId_StreamBox);

			val = RndList_GetOrder(This->m_pRndList, pDataA)
			    - RndList_GetOrder(This->m_pRndList, pDataB);
			if (val) return val;
		}
	}

	// Ordering/shuffling of terminal entities (tracks)
	switch(shuffle & Shuffle_Track_SortByMask)
	{
		case Shuffle_Track_SortByNumber:
		{
			if (entityA == Entity_Order)
				val = FLTrack_GetOrder(pTrackA) - FLTrack_GetOrder(pTrackB);
		}
		// no break;
		case Shuffle_Track_SortByName:
		{
			if (!val) val = Compare_Strings(FLTrack_GetTrackName(pTrackA), FLTrack_GetTrackName(pTrackB));
			if (!val) val = FLTrack_GetSection(pTrackA) - FLTrack_GetSection(pTrackB);
		}
		break;
		default: // SortByRandom, SortByNone
		{
			val = RndList_GetOrder(This->m_pRndList, pTrackA)
			    - RndList_GetOrder(This->m_pRndList, pTrackB);
		}
		break;
	}

	// Must somehow distinguish them
	if (!val) val = ((char*) pTrackA) - ((char*) pTrackB);

	return val;
}

static void QSort_Tracks(PListFiles* This, unsigned int shuffle, const void** a, int n)
{
	// insertion sort on small arrays
	if (n < 7)
	{
		const void** pl;
		const void** pm;
		const void** pn = a + n;
		const void* pt;

		for (pm = a + 1; pm < pn; pm++)
		{
			for (pl = pm; (pl > a) && (Compare_Tracks(This, shuffle, pl[-1], pl[0]) > 0); pl--)
			{
				pt = pl[0];
				pl[0] = pl[-1];
				pl[-1] = pt;
			}
		}

		return;
	}

	const void* pv = a[n/2];
	const void* pt;

	int i = 0, j = n;

	a[n/2] = a[0];
	a[0] = pv;

	for (;;)
	{
		do i++; while ((i < j) && (Compare_Tracks(This, shuffle, a[i], pv) < 0));
		do j--; while ((i <= j) && (Compare_Tracks(This, shuffle, pv, a[j]) < 0));
		if (j < i) break;
		pt = a[i];
		a[i] = a[j];
		a[j] = pt;
	}
	pt = a[0];
	a[0] = a[j];
	a[j] = pt;
	QSort_Tracks(This, shuffle, a, j);
	QSort_Tracks(This, shuffle, a + j + 1, n - j - 1);
}

static int PListFiles_GetTrackList(PlayList* pPlayList, const void*** pptracks, bool list, unsigned int shuffle)
{
	PListFiles* This = (PListFiles*) pPlayList;
	const void** ptracks;

	if ((shuffle & (Shuffle_GroupBy_Artist|Shuffle_GroupBy_Album)) == Shuffle_GroupBy_Album)
		shuffle |= Shuffle_GroupBy_Compilation;

	if (shuffle & (Shuffle_GroupBy_Artist|Shuffle_GroupBy_Album)
	&& ((shuffle & Shuffle_Track_SortByMask) == Shuffle_Track_SortByRaw))
	{
		shuffle &= ~Shuffle_Track_SortByMask;
		shuffle |= Shuffle_Track_SortByName;
	}

	if (list)
		 This->m_Flags |= PList_ProgramMode;
	else This->m_Flags &= ~PList_ProgramMode;

	This->m_PlayList.nr_tracks = 0;

	if (This->m_pRndList)
	{
		Delete_RndList(This->m_pRndList);
		This->m_pRndList = NULL;
	}

	if ((This->m_Flags & PList_ProgramMode) && This->m_pQueue)
		This->m_PlayList.nr_tracks += FileList_CountTracksToPlay(This->m_pQueue, FLTrack_Type_File);

	if (!(This->m_Flags & PList_ProgramMode) && This->m_pEdit)
		This->m_PlayList.nr_tracks += FileList_CountTracksToPlay(This->m_pEdit, FLTrack_Type_File);

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

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

	if ((This->m_Flags & PList_ProgramMode) && This->m_pQueue)
		ptracks = FileList_ListTracksToPlay(This->m_pQueue, ptracks, FLTrack_Type_File);

	if (!(This->m_Flags & PList_ProgramMode) && This->m_pEdit)
		ptracks = FileList_ListTracksToPlay(This->m_pEdit, ptracks, FLTrack_Type_File);

	throw_assert(*pptracks + This->m_PlayList.nr_tracks == ptracks);

	ptracks = *pptracks;

	try
	{
		This->m_pRndList = throw_New_RndList();
		// Determine an Collectives order
		throw_RndList_AddSet(This->m_pRndList, (const OrderedSet*) DigitalCD.pCollectivesCol, (shuffle & Shuffle_Random_Collective) != 0);
		// Artists follows
		throw_RndList_AddSet(This->m_pRndList, (const OrderedSet*) DigitalCD.pArtistsCol, (shuffle & Shuffle_Random_Artist) != 0);
		// Dates follows
		if ((shuffle & Shuffle_SortAlbum_Mask) == Shuffle_SortAlbum_Date)
			throw_RndList_AddSet(This->m_pRndList, (const OrderedSet*) DigitalCD.pDatesCol, 0);
		// Boxs follows
		throw_RndList_AddSet(This->m_pRndList, (const OrderedSet*) DigitalCD.pBoxesCol, (shuffle & Shuffle_Random_Box) != 0);
		// Albums follows
		throw_RndList_AddSet(This->m_pRndList, (const OrderedSet*) DigitalCD.pAlbumsCol, (shuffle & Shuffle_SortAlbum_Mask) == Shuffle_SortAlbum_Random);
		// Tracks follows
		throw_RndList_AddArray
			( This->m_pRndList
			, ptracks
			, This->m_PlayList.nr_tracks
			, (shuffle & Shuffle_Track_SortByMask) == Shuffle_Track_SortByRandom
			);
		QSort_Tracks(This, shuffle, ptracks, This->m_PlayList.nr_tracks);
	}
	catch
	{
		// The list just remains unsorted
	}
	catch_end

	return This->m_PlayList.nr_tracks;
}

static const char* PListFiles_GetText(PlayList* pPlayList, void* ptrack, EMetaId id)
{
	PListFiles* This = (PListFiles*) pPlayList;
	FLTrack* pTrack = ptrack;
	const char* text = NULL;

	if (pTrack == NULL)
	{
		switch(id)
		{
			case EMetaId_StreamType:
			{
				text = This->m_pTitle;
			}
			break;
		}
	}
	else if (id == EMetaId_StreamName)
	{
		text = FLTrack_GetTrackName(pTrack);
	}
	else
	{
		const MetaData* meta;

		// Playing info may be richer than static info
		if (pPlayList->m_pObject && (This->m_pRefTrack == pTrack))
		{
			meta = MediaObject_FindMeta(pPlayList->m_pObject, id);

			if (meta && meta->data)
				text = meta->data;
		}

		if (!text)
		{
			meta = MetaList_Find(FLTrack_GetMeta(pTrack), id);

			if (meta && meta->data)
				text = meta->data;
		}
	}

	if (!text) text = &nil;

	if ((pTrack != NULL)
	&&  (  (id == EMetaId_StreamName)
	    || (id == EMetaId_StreamTitle)
	    )
	   )
	{
		unsigned int section = FLTrack_GetSection(pTrack);
		if (section > 0)
			text = SPrintf("%s [%02d]", text, section);
	}

	return text;
}

static void PListFiles_StartPlay(MediaObject* pObject, FLTrack* pTrack)
{
	uint64_t startpos;

	Media_SetConfig(pObject, Media_CfgFrequency, &Options()->Music_Files.Frequency);
	Media_SetConfig(pObject, Media_CfgInterpol, &Options()->Music_Files.bInterpol);
	Media_SetConfig(pObject, Media_CfgBalance, &Options()->Music_Files.Balance);
	Media_SetConfig(pObject, Media_CfgStereoSeparation, &Options()->Music_Files.StereoSeparation);
	Media_SetConfig(pObject, Media_CfgVolumeRamping, &Options()->Music_Files.bVolumeRamping);
	Media_SetConfig(pObject, Media_CfgEqualizer, Options());

	startpos = Media_GetSectionPos(pObject, FLTrack_GetSection(pTrack));
	Media_Play(pObject, startpos);
	Media_SetConfig(pObject, Media_CfgDMASize, &Options()->Music_Files.DMASize);
}

static bool PListFiles_PlayTrack(PlayList* pPlayList, void* ptrack, bool bPrevInError)
{
	PListFiles* This = (PListFiles*) pPlayList;
	Player* pPlayer = This->m_PlayList.m_pPlayer;
	FLTrack* pTrack = ptrack;
	bool bMod = false;

	if (!pPlayer) return false;

	//
	// Step 1: Stop currently playing track
	//

	PListFiles_StopCurrentTrack(This, bPrevInError);

	//
	// Step 2: Load new track, extract info and play it
	//

	if (This->m_pNextTrack == ptrack)
	{
		// Already pre-loaded
		pPlayList->m_pObject = This->m_pNextObject;
		This->m_pNextTrack = NULL;
		This->m_pNextObject = NULL;
	}
	else
	{
		// Wrong one or none pre-loaded
		PListFiles_StopNextTrack(This);
		// Load
		pPlayList->m_pObject = PListFiles_LoadTrack(pTrack);
	}

	if (pPlayList->m_pObject == NULL)
	{
		if (FLTrack_SetFlags(pTrack, FLTrack_FailedToPlay, FLTrack_FailedToPlay))
			FLTrack_RefreshViews(pTrack, This, true, false);

		FilesPlayer_RefreshIcons(pPlayer);

		return false;
	}

	const MetaData* meta = MediaObject_FindMeta(pPlayList->m_pObject, EMetaId_StreamType);
	mem_setstring(&This->m_pDriverInfo, (meta && meta->data) ? meta->data : NULL);

	Player_SetHardwareType(pPlayer, PlayList_GetHardwareType(pPlayList));

	// New track is ok
	This->m_pRefTrack = pTrack;

	// Setup playing parameters (cannot be done before m_pRefTrack is set) and start playing
	bMod = FLTrack_SetFlags(pTrack, FLTrack_Played | FLTrack_PlayedToday
	                              , FLTrack_Played | FLTrack_PlayedToday | FLTrack_Invalidated | FLTrack_FailedToPlay);
	// Force refresh to at least show play mark
	FLTrack_RefreshViews(pTrack, This, bMod, false);

	PListFiles_RefreshPlayer(This, player_refresh_track | player_refresh_volume);

	PListFiles_SetLoopMode(pPlayList, This->m_LoopMode);

	if (!This->m_bNextStarted)
	{
		PlayList_SetVolume(pPlayList, pPlayList->m_Volume);
		PListFiles_StartPlay(pPlayList->m_pObject, pTrack);
	}
	This->m_bNextStarted = false;

	if (Options()->Player.bFixTrackVolume)
		 This->m_Flags |= PList_FixTrackVolume;
	else This->m_Flags &= ~PList_FixTrackVolume;

	FilesPlayer_RefreshIcons(pPlayer);

	return true;
}

static bool PListFiles_PrepTrack(PlayList* pPlayList, void* ptrack)
{
	PListFiles* This = (PListFiles*) pPlayList;
	FLTrack* pTrack = ptrack;

	if (This->m_pNextTrack == ptrack) return false;
	if (This->m_pRefTrack == ptrack) return false;

	// Wrong one or none pre-loaded
	PListFiles_StopNextTrack(This);
	// Load
	This->m_pNextObject = PListFiles_LoadTrack(pTrack);
	if (This->m_pNextObject == NULL)
		return false;

	This->m_pNextTrack = ptrack;

	// Ensure it doesn't start at 100%
	PlayList_SetVolume(pPlayList, pPlayList->m_Volume);

	Media_Chain(pPlayList->m_pObject, This->m_pNextObject);

	return true;
}

static Media_Status PListFiles_GetStatus(PlayList* pPlayList)
{
	PListFiles* This = (PListFiles*) pPlayList;

	if (This->m_PlayList.nr_tracks <= 0) return status_empty;
	if (pPlayList->m_pObject == NULL) return status_stop;

	Media_Status status = Media_GetStatus(pPlayList->m_pObject);
	// Already start next?
	if ((status == status_stop) && This->m_pNextTrack)
	{
		PlayList_SetVolume(pPlayList, pPlayList->m_Volume);
		PListFiles_StartPlay(This->m_pNextObject, This->m_pNextTrack);
		This->m_bNextStarted = true;
	}

	return status;
}

static void PListFiles_SetLoopMode(PlayList* pPlayList, Media_LoopMode mode)
{
	PListFiles* This = (PListFiles*) pPlayList;

	This->m_LoopMode = mode;

	if (pPlayList->m_pObject)
	{
		// List with 1 track, translate loop on list to avoid reloads
		if ((mode == loop_volume_loop) && (This->m_PlayList.nr_tracks == 1))
			mode = loop_section_loop;

		// Translate into loop mode at file level
		if (mode != loop_section_loop)
			mode = loop_section_play;

		// Translate back into volume level if play all sections is present
		if (FLTrack_GetFlags(This->m_pRefTrack) & FLTrack_PlayAllSections)
		{
			if (mode == loop_section_loop)
				mode = loop_volume_loop;
			else
				mode = loop_volume_play;
		}

		Media_SetLoopMode(pPlayList->m_pObject, mode);
	}
}

static void PListFiles_Play(PlayList* pPlayList, uint64_t current)
{
	PListFiles* This = (PListFiles*) pPlayList;

	PListFiles_SetLoopMode(pPlayList, This->m_LoopMode);
	Media_Play(pPlayList->m_pObject, current);
	Media_SetConfig(pPlayList->m_pObject, Media_CfgDMASize, &Options()->Music_Files.DMASize);

	This->m_Flags &= ~PList_FixTrackVolume;
}

static void PListFiles_Stop(PlayList* pPlayList, bool bInError)
{
	PListFiles* This = (PListFiles*) pPlayList;
	Player* pPlayer = This->m_PlayList.m_pPlayer;

	if (!pPlayer) return;

	PListFiles_StopCurrentTrack(This, bInError);
	PListFiles_StopNextTrack(This);

	FilesPlayer_RefreshIcons(pPlayer);
}

static void PListFiles_GetPosition(PlayList* pPlayList, void* pTrack, MediaPosition* newpos)
{
	PListFiles* This = (PListFiles*) pPlayList;
	MediaPosition* pos = &pPlayList->m_Position;

	if (pPlayList->m_pObject == NULL)
		memset(newpos, 0, sizeof(*newpos));
	else if (This->m_pRefTrack == pTrack)
	{
		Media_GetPosition(pPlayList->m_pObject, newpos);
		if (pos->section.nr != newpos->section.nr)
			pPlayList->m_pObject->m_Updates |= Media_Updated_Length;

		if (FLTrack_GetFlags(pTrack) & FLTrack_PlayAllSections)
		{
			newpos->section.pos = newpos->volume.pos;
			newpos->section.len = newpos->volume.len;
		}
	}

	pPlayList->m_Position = *newpos;
}

static int PListFiles_GetTrackNummer(PlayList* pPlayList, void* ptrack)
{
	IGNORE(pPlayList);
	IGNORE(ptrack);

	return 0; // Use the player track index
}

static int PListFiles_GetModuleHandle(PlayList* pPlayList, void* ptrack)
{
	PListFiles* This = (PListFiles*) pPlayList;
	FLTrack* pTrack = ptrack;

	if (pTrack == NULL)
		return 0;
	else
	{
		const MetaData* meta;

		// Playing info may be richer than static info
		if (pPlayList->m_pObject && (This->m_pRefTrack == pTrack))
		{
			meta = MediaObject_FindMeta(pPlayList->m_pObject, EMetaId_ModuleHandle);

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

	return 0;
}

#define MaxVolume (100<<16)

static void PListFiles_SetConfig(PlayList* pPlayList, Media_Config type, const void* value)
{
	PListFiles* This = (PListFiles*) pPlayList;
	Player* pPlayer = This->m_PlayList.m_pPlayer;

	if (!pPlayer) return;

	switch(type)
	{
		case Media_CfgVolume:
		{
			MediaVolume* pvol = (MediaVolume*) value;
			unsigned int val;
			unsigned int mainval = pvol->sysscaled;

			if (This->m_pNextTrack)
			{
				MediaVolume vol = *pvol;
				vol.raw = ((vol.raw/100) * FLTrack_GetVolume(This->m_pNextTrack)) / 100;
				vol.sysscaled = ((vol.sysscaled/100) * FLTrack_GetVolume(This->m_pNextTrack)) / 100;
				Media_SetConfig(This->m_pNextObject, Media_CfgVolume, &vol);
			}
			if (This->m_pRefTrack)
			{
				pvol->raw = ((pvol->raw/100) * FLTrack_GetVolume(This->m_pRefTrack)) / 100;
				pvol->sysscaled = ((pvol->sysscaled/100) * FLTrack_GetVolume(This->m_pRefTrack)) / 100;
				Media_SetConfig(pPlayList->m_pObject, Media_CfgVolume, pvol);
				if (Options()->PlugIns.bRescaleWithTrackVolume == false)
					val = mainval;
				else
					val= pvol->sysscaled * 100;

				// Fix value for DCDUtils
				if (val > MaxVolume) val = MaxVolume;
			}
			else
			{
				val = mainval = MaxVolume;
			}

			DCDUtils_SetIntInfo(Player_GetDCDUtilsHandle(pPlayer), DCD_Info_MainVolume, mainval >> 8);
			DCDUtils_SetIntInfo(Player_GetDCDUtilsHandle(pPlayer), DCD_Info_ScaleVolume, val >> 8);
		}
		break;
		case Media_CfgParams:
		{
			if (This->m_pRefTrack)
			{
				unsigned int val = FLTrack_GetTimFlags(This->m_pRefTrack);
				Media_SetConfig(pPlayList->m_pObject, type, &val);
			}
		}
		break;
		default:
		{
			if (This->m_pRefTrack)
			{
				Media_SetConfig(pPlayList->m_pObject, type, value);
				This->m_Flags &= ~PList_FixTrackVolume;
			}
		}
	}
}

static unsigned int PListFiles_GetConfig(PlayList* pPlayList, Media_Config type)
{
	switch(type)
	{
		case PlayList_CfgAutoStart:
		{
			return false;
		}
		break;
		case PlayList_CfgSeekable:
		{
			if (pPlayList->m_pObject)
				return Media_GetSeekable(pPlayList->m_pObject);

			return false;
		}
		break;
		case Media_CfgVolume:
		{
			return pPlayList->m_Volume;
		}
		break;
	}

	return 0xffffffff;
}

static unsigned int PListFiles_GetUpdates(PlayList* pPlayList)
{
	PListFiles* This = (PListFiles*) pPlayList;
	FLTrack* pTrack = This->m_pRefTrack;
	unsigned int updates = 0;
	bool bModified = false;
	const MetaData* meta;

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

	updates = Media_GetUpdates(pPlayList->m_pObject);

	// Some of the metadata updates are taken back as track info
	if (updates & Media_Updated_Meta)
	{
		int i;

		for (i = 0; i < MediaObject_CountMeta(pPlayList->m_pObject); i++)
		{
			meta = MediaObject_GetMeta(pPlayList->m_pObject, i);

			if (meta->data)
			{
				switch (meta->id)
				{
					case EMetaId_StreamTitle:
					case EMetaId_StreamAlbum:
					case EMetaId_StreamArtist:
					case EMetaId_StreamDate:
						bModified |= FLTrack_SetMetaText(pTrack, meta->id, EMetaOrigin_Stream, meta->data);
					break;
					case EMetaId_StreamTrackNumber:
						bModified |= FLTrack_SetOrder(pTrack, EMetaOrigin_Stream, (unsigned int) meta->data);
					break;
				}
			}
		}

		if (bModified)
			FLTrack_RefreshViews(pTrack, This, true, false);
	}

	return updates;
}

static EListenerAction PListFiles_Listener(void* handle, const Event* e)
{
	PListFiles* This = handle;
	Player* pPlayer = This->m_PlayList.m_pPlayer;

	switch(e->Type)
	{
		case EEvent_OptionShowFileAsTitle:
		{
			if (pPlayer) Player_Refresh(pPlayer, player_refresh_tracklist);
		}
		break;
		case EEvent_OptionMixingParameters:
		{
			PListFiles_SetConfig(&This->m_PlayList, Media_CfgFrequency, &Options()->Music_Files.Frequency);
			PListFiles_SetConfig(&This->m_PlayList, Media_CfgInterpol, &Options()->Music_Files.bInterpol);
			PListFiles_SetConfig(&This->m_PlayList, Media_CfgVolumeRamping, &Options()->Music_Files.bVolumeRamping);
			PListFiles_SetConfig(&This->m_PlayList, Media_CfgBalance, &Options()->Music_Files.Balance);
			PListFiles_SetConfig(&This->m_PlayList, Media_CfgStereoSeparation, &Options()->Music_Files.StereoSeparation);
			PListFiles_SetConfig(&This->m_PlayList, Media_CfgEqualizer, Options());
		}
		break;
		case EEvent_OptionMixingFrequency:
		{
			PListFiles_SetConfig(&This->m_PlayList, Media_CfgFrequency, e->pData);
		}
		break;
		case EEvent_OptionMixingInterpol:
		{
			PListFiles_SetConfig(&This->m_PlayList, Media_CfgInterpol, e->pData);
		}
		break;
		case EEvent_OptionMixingVolRamping:
		{
			PListFiles_SetConfig(&This->m_PlayList, Media_CfgVolumeRamping, e->pData);
		}
		break;
		case EEvent_OptionMixingBalance:
		{
			PListFiles_SetConfig(&This->m_PlayList, Media_CfgBalance, e->pData);
		}
		break;
		case EEvent_OptionMixingStereo:
		{
			PListFiles_SetConfig(&This->m_PlayList, Media_CfgStereoSeparation, e->pData);
		}
		break;
		case EEvent_OptionMixingEqualizer:
		{
			PListFiles_SetConfig(&This->m_PlayList, Media_CfgEqualizer, e->pData);
		}
		break;
		case EEvent_OptionParentalLock:
		{
			if (pPlayer) FilesPlayer_RefreshIcons(pPlayer);
		}
		break;
		case EEvent_OptionRescaleVolume:
		{
			PListFiles_RefreshPlayer(This, player_refresh_volume);
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

static EJobAction Job_New_PListFiles_RefreshTrackList(void* pOwner, EJobAction action, void* pParams)
{
	PListFiles* This = pOwner;
	IGNORE(pParams);

	try
	{
		if (action == EJobAction_Continue)
		{
			Player* pPlayer = This->m_PlayList.m_pPlayer;
			MediaObject* pObject = This->m_PlayList.m_pObject;
			bool off = !WLib_Hourglass_IsOn();

			if (off) WLib_Hourglass_On();
			PListFiles_Init(&This->m_PlayList, false);

			if (pObject)
			{
				const MetaData* meta = MediaObject_FindMeta(pObject, EMetaId_StreamType);
				mem_setstring(&This->m_pDriverInfo, (meta && meta->data) ? meta->data : NULL);
			}

			if (pPlayer && This->m_pEdit) Player_Refresh(pPlayer, player_refresh_tracklist | player_refresh_volume);

			PListFiles_RefreshPlayer(This, player_refresh_track | player_refresh_volume);

			// Ensure loop on list is not translated anymore into loop on track if more than one track
			PListFiles_SetLoopMode(&This->m_PlayList, This->m_LoopMode);

			if (off) WLib_Hourglass_Off();
		}

		action = EJobAction_Stop;
	}
	catch
	{
		App_ReportException();
		action = EJobAction_Stop;
	}
	catch_end

	return action;
}

void PListFiles_RefreshTrackList(PListFiles* This, const FileList* pCause)
{
	if (pCause != NULL)
	{
		if ((This->m_pQueue == pCause) && !(This->m_Flags & PList_ProgramMode))
			return;
		if ((This->m_pQueue != pCause) && (This->m_Flags & PList_ProgramMode))
			return;
	}

	JobList_Add(Task_GetJobList(), true, This, NULL, Job_New_PListFiles_RefreshTrackList);
}

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

static const PlayList_Cmds PListFiles_Cmds =
{
	  PListFiles_Init
	, PListFiles_GetTrackList
	, PListFiles_GetText
	, PListFiles_PrepTrack
	, PListFiles_PlayTrack
	, PListFiles_GetStatus
	, PListFiles_SetLoopMode
	, PListFiles_Play
	, PListFiles_Stop
	, PListFiles_GetPosition
	, PListFiles_GetTrackNummer
	, PListFiles_GetModuleHandle
	, PListFiles_SetConfig
	, PListFiles_GetConfig
	, PListFiles_GetUpdates
	, throw_PListFiles_CreatePlayer
	, PListFiles_DeletePlayer
};

static void Delete_PListFiles(PListFiles* This)
{
	if (This)
	{
		Delete_FileList(This->m_pQueue);

		Delete_RndList(This->m_pRndList);

		Task_RemoveListener(EEvent_OptionShowFileAsTitle, PListFiles_Listener, This);
		Task_RemoveListener(EEvent_OptionMixingParameters, PListFiles_Listener, This);
		Task_RemoveListener(EEvent_OptionMixingFrequency, PListFiles_Listener, This);
		Task_RemoveListener(EEvent_OptionMixingInterpol, PListFiles_Listener, This);
		Task_RemoveListener(EEvent_OptionMixingVolRamping, PListFiles_Listener, This);
		Task_RemoveListener(EEvent_OptionMixingBalance, PListFiles_Listener, This);
		Task_RemoveListener(EEvent_OptionMixingStereo, PListFiles_Listener, This);
		Task_RemoveListener(EEvent_OptionMixingEqualizer, PListFiles_Listener, This);
		Task_RemoveListener(EEvent_OptionParentalLock, PListFiles_Listener, This);
		Task_RemoveListener(EEvent_OptionRescaleVolume, PListFiles_Listener, This);

		PlayList_NotPlayList(&This->m_PlayList);

		mem_free(This->m_pTitle);
		mem_free(This->m_pDriverInfo);

		mem_free(This);
	}
}

static PListFiles* throw_New_PListFiles(void)
{
	PListFiles* This = throw_mem_calloc(1, sizeof(*This));

	try
	{
		throw_PlayList_PlayList(&This->m_PlayList, &PListFiles_Cmds, Msg_Lookup("Play0"), NULL);

		throw_Task_AddListener(EEvent_OptionShowFileAsTitle, PListFiles_Listener, This, false);
		throw_Task_AddListener(EEvent_OptionMixingParameters, PListFiles_Listener, This, false);
		throw_Task_AddListener(EEvent_OptionMixingFrequency, PListFiles_Listener, This, false);
		throw_Task_AddListener(EEvent_OptionMixingInterpol, PListFiles_Listener, This, false);
		throw_Task_AddListener(EEvent_OptionMixingVolRamping, PListFiles_Listener, This, false);
		throw_Task_AddListener(EEvent_OptionMixingBalance, PListFiles_Listener, This, false);
		throw_Task_AddListener(EEvent_OptionMixingStereo, PListFiles_Listener, This, false);
		throw_Task_AddListener(EEvent_OptionMixingEqualizer, PListFiles_Listener, This, false);
		throw_Task_AddListener(EEvent_OptionParentalLock, PListFiles_Listener, This, false);
		throw_Task_AddListener(EEvent_OptionRescaleVolume, PListFiles_Listener, This, false);

		PListFiles_RefreshTrackList(This, NULL);
	}
	catch
	{
		Delete_PListFiles(This);
		throw_current();
	}
	catch_end

	return This;
}

PListFiles* PListFiles_Get(void)
{
	if (!App_pPListFiles) App_pPListFiles = throw_New_PListFiles();

	return App_pPListFiles;
}

void PListFiles_Release(void)
{
	if (App_pPListFiles)
	{
		Delete_PListFiles(App_pPListFiles);
		App_pPListFiles = NULL;
	}
}

/*-------------------------------------------------------------------------*
 *--- SSList calls --------------------------------------------------------*
 *-------------------------------------------------------------------------*/

const FLTrack* PListFiles_GetPlayedTrack(PListFiles* This)
{
	return This->m_pRefTrack;
}

void PListFiles_RemoveTrack(PListFiles* This, const FLTrack* pTrack)
{
	Player* pPlayer = This->m_PlayList.m_pPlayer;

	if (This->m_pNextTrack == pTrack)
		PListFiles_StopNextTrack(This);

	if (pPlayer)
		Player_RemoveTrack(pPlayer, pTrack);
}

void PListFiles_StopTrack(PListFiles* This, const FLTrack* pTrack)
{
	Player* pPlayer = This->m_PlayList.m_pPlayer;

	if (This->m_pNextTrack == pTrack)
		PListFiles_StopNextTrack(This);

	if (pPlayer
	&&  (This->m_pRefTrack == pTrack))
		CmdHandler_ExecCommand(&FilesPlayer_CmdHandler, This, Cmd_Ctrl_Stop);
}

void PListFiles_RefreshPlayer(PListFiles* This, unsigned int flags)
{
	Player* pPlayer = This->m_PlayList.m_pPlayer;

	if ((flags & player_refresh_track)
	&&  This->m_PlayList.m_pObject)
	{
		try
		{
			throw_MediaObject_MergeMeta(This->m_PlayList.m_pObject, FLTrack_GetMeta(This->m_pRefTrack));
		}
		catch
		{
		}
		catch_end
	}

	if (pPlayer) Player_Refresh(pPlayer, flags);
}

void PListFiles_ReceiveFile(PListFiles* This, const Msg_FileData* rcv)
{
	bool bSendReply = false;

	if (rcv->type < 0x1000)
	{
		// Do nothing if we don't handle that kind of file
		if (!FileList_AcceptSourceFileType(rcv->type)
		&&  (LoadTypes_FindNext(rcv->type, NULL) == NULL))
			return;
	}
	else
	{
	 	// Don't handle opening (double-click) on folders/applications
		if (rcv->hdr.action == EMsg_DataOpen)
			return;
	}

	if (FileList_AcceptSourceFileType(rcv->type))
	{
		// Load new playlist
		if (!Options()->Player.bParentalLock)
		{
			if ((rcv->pos.w == HWind_IconBar)
			||  (Task_GetId() != Window_GetTask(rcv->pos.w, rcv->pos.i)))
			{
				try
				{
					FileList* pEdit = throw_New_FileList_Receive(This->m_pEdit, rcv);
					FileList_Show(pEdit, -1);
					bSendReply = false; // done already
				}
				catch
				{
					App_ReportException();
				}
				catch_end
			}
		}
	}
	else
	{
		FileList* pInsertList = NULL;
		bool bPlay = false;

		// Load file/directory? Check appropriate action
		ELoadAction action = ELoadAction_Ignore;

		if (rcv->hdr.action == EMsg_DataOpen)
			action = Options()->Music_Files.DoubleClickAction;
		else if (rcv->hdr.action == EMsg_DataLoad)
		{
			Player* pPlayer = Players_Get(0);

			if (pPlayer && (rcv->pos.w == Player_GetWindow(pPlayer, -1)))
				action = Options()->Music_Files.DropOnPlayerAction;
			else if (rcv->pos.w == HWind_IconBar)
				action = Options()->Music_Files.DropOnIconBarAction;
		}

		switch(action)
		{
			case ELoadAction_QueueAndPlay:
				bPlay = true;
			// nobreak;
			case ELoadAction_Queue:
			{
				pInsertList = This->m_pQueue;
			}
			break;
			case ELoadAction_AddToPlaylistAndPlay:
				bPlay = true;
			// nobreak;
			case ELoadAction_AddToPlaylist:
			{
				if (Options()->Player.bParentalLock)
					pInsertList = This->m_pQueue;
				else
					pInsertList = This->m_pEdit;
			}
			break;
		}

		// Insert file/directory?
		if (pInsertList)
		{
			int flags = 0;

			if (Options()->Music_Files.bAutoOpenPlaylist)
				flags |= FileList_Add_Show;

			if (bPlay && (This->m_PlayList.m_pObject == NULL))
				flags |= FileList_Add_Play;

			FileList_AddFile(pInsertList, -1, rcv->name, flags);

			bSendReply = true;
		}
	}

	if (bSendReply)
	{
		if ((rcv->hdr.action == EMsg_DataOpen)
		||  (rcv->hdr.action == EMsg_DataLoad))
		{
			Msg msg;

			// Send a DataLoadAck message to the sender task
			msg = *((Msg*) rcv);
			msg.hdr.ref = rcv->hdr.id;
			msg.hdr.action = EMsg_DataLoadAck;

			Task_SendMsg(&msg, rcv->hdr.task);
		}
	}
}

void PListFiles_AttachFileList(PListFiles* This, FileList* pList)
{
	CmdHandler_ExecCommand(&FilesPlayer_CmdHandler, This, Cmd_Ctrl_Stop);

	This->m_pEdit = pList;

	PListFiles_RefreshTrackList(This, pList);
}

void PListFiles_AttachQueue(PListFiles* This, FileList* pList)
{
	CmdHandler_ExecCommand(&FilesPlayer_CmdHandler, This, Cmd_Ctrl_Stop);

	This->m_pQueue = pList;

	PListFiles_RefreshTrackList(This, pList);
}

FileList* PListFiles_GetQueue(const PListFiles* This)
{
	return This->m_pQueue;
}
