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

#include "WimpLib:BarIcon.h"
#include "WimpLib:Choices.h"
#include "WimpLib:Clipboard.h"
#include "WimpLib:Desktop.h"
#include "WimpLib:DocView.h"
#include "WimpLib:Exception.h"
#include "WimpLib:File.h"
#include "WimpLib:Keyboard.h"
#include "WimpLib:Log.h"
#include "WimpLib:mem.h"
#include "WimpLib:Message.h"
#include "WimpLib:PtrArray.h"
#include "WimpLib:Task.h"
#include "WimpLib:Utils.h"
#include "WimpLib:Window.h"

#include "cdswi.h"
#include "CDDB.h"
#include "CDTreeView.h"
#include "Cmds.h"
#include "CmdList.h"
#include "DCDswi.h"
#include "DigitalCD.h"
#include "DocEvents.h"
#include "EditCDDesc.h"
#include "EditCDTrack.h"
#include "EditDirList.h"
#include "EditFile.h"
#include "EditList.h"
#include "EditRadio.h"
#include "EditYPList.h"
#include "FileTypes.h"
#include "FLClues.h"
#include "FLSearch.h"
#include "FLTreeView.h"
#include "MetaScan.h"
#include "Options.h"
#include "OptEdit.h"
#include "Player.h"
#include "PlayList.h"
#include "Setup.h"
#include "PListCD.h"
#include "PListFiles.h"
#include "PListRadio.h"
#include "PListExt.h"
#include "UserEvents.h"

static EListenerAction App_ModeHandler(void* handle, const Event* e);
static EListenerAction App_Listener(void* handle, const Event* e);
static EListenerAction App_DefaultHandler(void* handle, const Event* e);

static const char ChoicesFile[] = "DigitalCD.Choices";

extern const char ProgramName[];
const char ProgramName[] = "DigitalCD";
static const char ProgramSprite[] = "!DigitalCD";
static const char ProgramDir[] = "<DigitalCD$Dir>";
static const MouseShape ptr_copy = {"ptr_copy", 0, 0};

#define Menu_Info     0
#define Menu_Choices  1
#define Menu_Players  2
#define Menu_PlayList 3
#define Menu_Queue    4
#define Menu_CDList   5
#define Menu_Help     6
#define Menu_Quit     7

CDigitalCD DigitalCD;

static void App_ModeChange(void)
{
	Players_ModeChange();
}

/*------------------------------------------------------------------------------
 * Handling of default files to tranfer to !DCDRes if required.
 *-----------------------------------------------------------------------------*/

static const char DefFile_Equalizer[] = "DigitalCD:Copy.Equalizer";
static const char DefFile_CDs[]       = "DigitalCD:Copy.CDs";
static const char DefFile_Setup[]     = "DigitalCD:Copy.Setup";
static const char DefFile_Clues[]     = "DigitalCD:Copy.Clues";
static const char DefDir_Audio16[]    = "DigitalCD:Copy.Audio16";
static const char DefDir_Default[]    = "DigitalCD:Copy.Default";
static const char DefDir_Oscillo[]    = "DigitalCD:Copy.Oscillo";
static const char DefDir_PowerBars[]  = "DigitalCD:Copy.PowerBars";

static const char ResFile_CDs[]       = "<DigitalCDRes$Dir>.CDs";
static const char ResFile_Setup[]     = "<DigitalCDRes$Dir>.Setup";
static const char ResFile_Clues[]     = "<DigitalCDRes$Dir>.Clues";
static const char ResPath_MetaScan[]  = "<DigitalCDRes$Dir>.MetaScan.";
static const char ResDir_Audio16[]    = "<DigitalCDRes$Dir>.PlugIns.FullScreen.Audio16";
static const char ResDir_Default[]    = "<DigitalCDRes$Dir>.Skins.Default";
static const char ResDir_Oscillo[]    = "<DigitalCDRes$Dir>.PlugIns.Desktop.Oscillo";
static const char ResDir_PowerBars[]  = "<DigitalCDRes$Dir>.PlugIns.Desktop.PowerBars";

/**
 * Shows a short message in plug-ins.
 */

static void App_ShowMessage(const char* pmsg)
{
	DCD_Misc_Message msg = {{sizeof(DCD_Misc_Message), 0, 0, 0, EMsg_DCD_Misc}, 0};

	msg.reason = 5;
	msg.controller = -1;
	strncpy(msg.data.text, pmsg, sizeof(msg.data.text));
	msg.hdr.size = (msg.data.text - (char*) &msg) + strlen(msg.data.text) + 1;
	Task_SendMsg((Msg*) &msg, 0);
}

void App_ReportException(void)
{
	if (DCDUtils_GetFullScreenPlugIn() != -1) // Full screen plug-in active
	{
		App_ShowMessage(exception_current()->error.errmess);
	}
	else
	{
		Task_ReportException();
	}
}

void App_ReportError(const char* pmsg, ...)
{
	va_list arg;
	va_start(arg, pmsg);

	if (DCDUtils_GetFullScreenPlugIn() != -1) // Full screen plug-in active
	{
		_kernel_oserror  e;

		e.errnum = 1;
		vsnprintf(e.errmess, sizeof(e.errmess), Msg_Lookup(pmsg), arg);

		App_ShowMessage(e.errmess);
	}
	else
	{
		Task_ReportVMsg(0x501, pmsg, arg);
	}
	va_end(arg);
}

void App_DescribeError(const _kernel_oserror* e, const char* pmsg, ...)
{
	va_list arg;
	va_start(arg, pmsg);

	if (DCDUtils_GetFullScreenPlugIn() != -1) // Full screen plug-in active
	{
		_kernel_oserror  e;

		e.errnum = 1;
		vsnprintf(e.errmess, sizeof(e.errmess), Msg_Lookup(pmsg), arg);

		App_ShowMessage(e.errmess);
	}
	else
	{
		Task_DescribeVMsg(0x501, e, pmsg, arg);
	}
	va_end(arg);
}

const _kernel_oserror* App_ReportOSError(const _kernel_oserror* e)
{
	if (DCDUtils_GetFullScreenPlugIn() != -1) // Full screen plug-in active
	{
		App_ShowMessage(e->errmess);
	}
	else
	{
		Task_ReportOSError(e);
	}

	return e;
}

/**
 * Inserts filename into a collection of files to copy
 *
 * @param  list   collection of files to copy
 * @param  value  filename to add to the collection
 */
static void App_FileListInsert(PtrArray* list, const char* value) throws(mem)
{
	char* pname = throw_mem_allocstring(value);
	int i;

	for (i = 0 ; i < PtrArray_Count(list); i++)
	{
		if (strcmp(pname, PtrArray_Get(list, i)) < 0)
			break;
	}

	try
	{
		PtrArray_Insert(list, i, pname);
	}
	catch
	{
		mem_free(pname);
		throw_current();
	}
	catch_end
}

/**
 * Inserts recursively the whole content of a directory a list of files to copy
 * and a list of directories to create.
 *
 * @param  list     collection of files to copy
 * @param  dlist    collection of directories to create
 * @param  path     path of the directory whose content is to be added to the collections
 * @param  skiplen  length of the start of the filename/directory that should be dropped
 *                  before inclusion in the lists
 */
static void App_AddFileList(PtrArray* flist, PtrArray* dlist, const char* path, int skiplen) throws(mem)
{
	int entry = 0;
	int pathlen = strlen(path) + 1;
	char* pnewfile = throw_mem_alloc(MAXPATH);
	char* pbuffer = pnewfile + pathlen;
	file_type Type;

	try
	{
		snprintf(pnewfile, MAXPATH, "%s", path);
		pbuffer[-1] = FILE_FOLDER_CHAR;

		while(entry != -1)
		{
			entry = Dir_ReadEntry(path, entry, pbuffer);
			if (pbuffer[0])
			{
				Type = File_GetFileType(pnewfile);
				if ((Type >= 0) && (Type < 0x1000))
					App_FileListInsert(flist, pnewfile + skiplen);
				else if (Type >= 0x1000)
				{
					App_FileListInsert(dlist, pnewfile + skiplen);
					App_AddFileList(flist, dlist, pnewfile, skiplen);
				}
			}
		}
	}
	catch
	{
		mem_free(pnewfile);
		throw_current();
	}
	catch_end

	mem_free(pnewfile);
}

/**
 * Copies the content of one directory to a destination directory.
 * Creates the destination directory and any subfolder as required,
 * copies new of modified files to the destination,
 * deletes from the destination files which don't exist in the source.
 *
 * @param from  full pathname of the source directory.
 * @param to    full pathname of the destination directory.
 */
static void App_DirCopy(const char* from, const char* to)
{
	PtrArray* volatile FilesFrom = NULL;
	PtrArray* volatile FilesTo = NULL;
	PtrArray* volatile DirsFrom = NULL;
	PtrArray* volatile DirsTo = NULL;
	int i, j, res;
	const char* FileFrom;
	const char* FileTo;
	static char filet[MAXPATH];
	static char filef[MAXPATH];
	File_Info InfoFrom;
	File_Info InfoTo;

	try
	{
		FilesFrom = New_PtrArray(10);
		FilesTo = New_PtrArray(10);
		DirsFrom = New_PtrArray(10);
		DirsTo = New_PtrArray(10);

		Path_Ensure(to);

		// Get a sorted list of files and dirs
		App_AddFileList(FilesFrom, DirsFrom, from, strlen(from) + 1);
		App_AddFileList(FilesTo, DirsTo, to, strlen(to) + 1);

		// Compare files list
		for (i = PtrArray_Count(FilesFrom) - 1, j = PtrArray_Count(FilesTo) - 1; true;)
		{
			FileFrom = (i >= 0) ? PtrArray_Get(FilesFrom, i) : NULL;
			FileTo = (j >= 0) ? PtrArray_Get(FilesTo, j) : NULL;

			if (!FileFrom)
			{
				if (!FileTo) break; // exit from loop
				res = -1;
			}
			else if (!FileTo) res = 1;
			else res = strcmp(FileFrom, FileTo);

			if (res > 0)
			{
				// Only in source
				snprintf(filef, sizeof(filef), "%s.%s", from, FileFrom);
				snprintf(filet, sizeof(filet), "%s.%s", to, FileFrom);
				File_EnsurePath(filet);
				File_Copy(filef, filet);
				i--;
			}
			else if (res < 0)
			{
				// Only in destination
				snprintf(filet, sizeof(filet), "%s.%s", to, FileTo);
				File_Delete(filet);
				j--;
			}
			else
			{
				// Found in both
				snprintf(filef, sizeof(filef), "%s.%s", from, FileFrom);
				snprintf(filet, sizeof(filet), "%s.%s", to, FileFrom);

				InfoFrom = File_GetInfo(filef);
				InfoTo   = File_GetInfo(filet);

				if (memcmp(&InfoFrom, &InfoTo, sizeof(InfoFrom)))
				{
					// Files differs
					File_Delete(filet);
					File_Copy(filef, filet);
				}
				i--;
				j--;
			}
		}

		// Remove directories found only in destination
		for (i = PtrArray_Count(DirsFrom) - 1,  j = PtrArray_Count(DirsTo) - 1; j >= 0; )
		{
			FileTo = PtrArray_Get(DirsTo, j);
			if (i >= 0)
			{
				FileFrom = PtrArray_Get(DirsFrom, i);
				res = strcmp(FileFrom, FileTo);
			}
			else res = -1;

			if (res > 0)
			{
				// Only in source
				i--;
			}
			else if (res < 0)
			{
				// Only in destination
				snprintf(filet, sizeof(filet), "%s.%s", to, FileTo);
				File_Delete(filet);
				j--;
			}
			else
			{
				// Found in both
				i--;
				j--;
			}
		}
	}
	catch
	{
		Task_ReportException();
	}
	catch_end

	if (FilesFrom)
	{
		for (i = 0; i < PtrArray_Count(FilesFrom); i++)
			mem_free(PtrArray_Get(FilesFrom, i));
		Delete_PtrArray(FilesFrom);
	}
	if (FilesTo)
	{
		for (i = 0; i < PtrArray_Count(FilesTo); i++)
			mem_free(PtrArray_Get(FilesTo, i));
		Delete_PtrArray(FilesTo);
	}
	if (DirsFrom)
	{
		for (i = 0; i < PtrArray_Count(DirsFrom); i++)
			mem_free(PtrArray_Get(DirsFrom, i));
		Delete_PtrArray(DirsFrom);
	}
	if (DirsTo)
	{
		for (i = 0; i < PtrArray_Count(DirsTo); i++)
			mem_free(PtrArray_Get(DirsTo, i));
		Delete_PtrArray(DirsTo);
	}
}

static EJobAction Job_AutoStartPlayList(void* pOwner, EJobAction action, void* pParams)
{
	try
	{
		if (action == EJobAction_Continue)
		{
			// Delay action if necessary
			if (JobList_Count(Task_GetJobList()) > 1)
				JobList_Add(Task_GetJobList(), false, pOwner, pParams, Job_AutoStartPlayList);
			else
			{
				if (Options()->Player.bAutoStartPlaylist)
				{
					Player* pPlayer = throw_PlayList_EnsurePlayer(PlayLists_Get(0), false);
					Player_RemoteControl(pPlayer, ECmd_Type_Player_Ctrl, 0);
				}
			}

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

	return action;
}

static void throw_App_Init(int argc, char* argv[])
{
	int arg, i;
	bool bAutostart;

	DigitalCD.bExiting = false;

	DigitalCD.Hardware.NrOfCDDrives = 0;
	FCDFS_GetNumberOfDrives(&DigitalCD.Hardware.NrOfCDDrives);
	DigitalCD.Hardware.Capabilities = check_hardware();

	// Ensure DCDUtils module is loaded
	throw_os(RMEnsure("DCDUtils", 27, "<DigitalCD$Dir>.RMStore.DCDUtils"));

	// Ensure path for plug-ins
	File_EnsurePath("<Choices$Write>.DigitalCD.PlugIns.");

	// init rnd
	srand(time(NULL));

	CmdList* pCmds = /*throw*/CmdList_Get();
	throw_CmdList_Parse(pCmds);

	DigitalCD.pPathsCol = throw_New_StrCol();
	DigitalCD.pCollectivesCol = throw_New_StrCol();
	DigitalCD.pArtistsCol = throw_New_StrCol();
	DigitalCD.pDatesCol = throw_New_StrCol();
	DigitalCD.pBoxesCol = throw_New_StrCol();
	DigitalCD.pAlbumsCol = throw_New_StrCol();
	DigitalCD.pBroadcastersCol = throw_New_StrCol();
	DigitalCD.pMimeTypesCol = throw_New_StrCol();

	// Check <DigitalCDRes$Dir> dir exists
	if (File_GetFileType("<DigitalCDRes$Dir>") < 0x1000)
		throw_string(Msg_Lookup("ReErr0:"));
	// Ensure some files are in Choices:
	if (File_GetFileType(EquWriteStyles) == -2)
		File_Copy(DefFile_Equalizer, EquWriteStyles);
	// Ensure some files are in DigitalCDRes:
	if (File_GetFileType(ResFile_CDs) == -2)
		File_Copy(DefFile_CDs, ResFile_CDs);
	if (File_GetFileType(ResFile_Setup) == -2)
		File_Copy(DefFile_Setup, ResFile_Setup);
	if (File_GetFileType(ResFile_Clues) == -2)
		File_Copy(DefFile_Clues, ResFile_Clues);
	File_EnsurePath(ResPath_MetaScan);
	App_DirCopy(DefDir_Default, ResDir_Default);
	App_DirCopy(DefDir_Audio16, ResDir_Audio16);
	App_DirCopy(DefDir_Oscillo, ResDir_Oscillo);
	App_DirCopy(DefDir_PowerBars, ResDir_PowerBars);

	DigitalCD.Clues = throw_Sprites_LoadFile(ResFile_Clues);
	throw_FLClues_FLClues(&DigitalCD.m_ClueList, DigitalCD.Clues);
	MetaScanList_Build();

	throw_Task_RegisterEventHandler(App_DefaultHandler, NULL, false);
	throw_Task_AddMsgListener(EMsg_ModeChange, App_ModeHandler, NULL, false);
	throw_Task_AddListener(EEvent_OptionIconbarActions, App_Listener, NULL, false);

	DigitalCD.IconBar = BarIcon_Create(NULL, 0, ProgramSprite, bar_PRight, 0);

	DigitalCD.WindInfo = throw_Window_CreateFrom("Info", NULL);

	CDTreeViews_CDTreeViews();
	FLTreeViews_FLTreeViews();

	throw_Players_Players();
	throw_PlayLists_PlayLists();
	throw_Choices_Choices(ChoicesFile);
	throw_Media_Media();
	throw_LoadTypes_LoadTypes();
	/*throw_*/OptEdit_Get();
	throw_Players_LoadSkins();
	App_ModeChange();
	throw_DocEvents_DocEvents();
	/*throw_*/PListFiles_Get();
	/*throw_*/CDDescList_Get();
	PListCDs_PListCDs();
	/*throw_*/PListRadio_Get();
	/*throw_*/PListExt_Get();
	FileList* pMainList = throw_FileList_GetMain();
	PListFiles_AttachFileList(PListFiles_Get(), pMainList);
	PListRadio_AttachFileList(PListRadio_Get(), pMainList);
	PListFiles_AttachQueue(PListFiles_Get(), throw_New_FileList_Queue());


	DigitalCD.MenuOpen = throw_New_Menu_Empty(Msg_Lookup("OpenT"));
	throw_Menu_InsertItem(DigitalCD.MenuOpen, Msg_Lookup("Open255"), -1);
	Menu_ItemSetSubMenu(DigitalCD.MenuOpen, 0, DigitalCD.MenuPlayers);
	throw_Menu_InsertItem(DigitalCD.MenuOpen, Msg_Lookup("Open256"), -1);
	throw_Menu_InsertItem(DigitalCD.MenuOpen, Msg_Lookup("Open257"), -1);
	throw_Menu_InsertItem(DigitalCD.MenuOpen, Msg_Lookup("Open258"), -1);
	throw_Menu_InsertItem(DigitalCD.MenuOpen, Msg_Lookup("Open259"), -1);

	// Reload options, cf icon bar clicks
	Options_Load(&DigitalCD.Options);
	bAutostart = Options()->Player.bAutoStartPlaylist;

	if (Options()->Player.bSinglePanelMode)
		throw_PlayList_EnsurePlayer(NULL, true);
	else
	{
		for (i = 0; i < PlayLists_Count(); i++)
			throw_PlayList_EnsurePlayer(PlayLists_Get(i), true);
	}

	for (arg = 1; arg < argc; arg++)
	{
		Log("Param %d: %s\n", arg, argv[arg]);
	}

	for (arg = 1; arg < argc; arg++)
	{
		if (argv[arg][0] == '-')
		{
			if ((arg + 1) >= argc) break;

			if (!strcmp(argv[arg], "-drive"))
			{
				PlayList* pList = (PlayList*) PListCDs_Get(atoi(argv[arg+1]));

				Log("Start CD drive %d, %p\n", atoi(argv[arg+1]), (void*) pList);
				if (pList)
				{
					try
					{
						Player* pPlayer = throw_PlayList_EnsurePlayer(pList, true);

						Player_ShowControls(pPlayer, -1);
						Player_SetStatus(pPlayer, status_stop);
						Player_RemoteControl(pPlayer, ECmd_Type_Player_Ctrl, 0);
						bAutostart = false;
					}
					catch
					{
						App_ReportException();
					}
					catch_end
				}
			}

			arg++;
		}
		else
		{
			Msg_FileData msg;

			msg.pos.w = HWind_None;
			msg.pos.i = HIcon_None;
			msg.pos.pt.x = 0;
			msg.pos.pt.y = 0;
			msg.size = 0;
			msg.type = File_GetFileType(argv[arg]);
			strncpy(msg.name, argv[arg], 211);
			msg.name[211] = '\0';
			msg.hdr.action = EMsg_DataOpen;
			msg.hdr.ref = 0;
			msg.hdr.size = 45 + strlen(msg.name);

			Task_SendMsg((Msg*) &msg, Task_GetId());
		}
	}

	if (bAutostart)
		JobList_Add(Task_GetJobList(), false, NULL, NULL, Job_AutoStartPlayList);

	App_BroadcastPrimaryController();
}

static bool App_MayClose(void)
{
	if (throw_FileList_GetMain())
		FileList_SetModifiedFlag(throw_FileList_GetMain(), false);

	DocList* pDocList = throw_DocList_Get();
	if (!DocList_IsSafe(pDocList))
	{
		if (!Task_Query(false, Msg_Lookup("AskQuit:")))
		{
			int i;

			for (i = 0; i < DocList_Count(pDocList); i++)
			{
				Document* pDoc = DocList_GetDocument(pDocList, i);
				if (Document_IsModified(pDoc))
				{
					DocEvent e = {EDocEvent_ShowView, NULL, pDoc};

					try
					{
						DocEvents_NotifyListeners(pDoc, &e);
					}
					catch
					{
						Task_ReportException();
					}
					catch_end
				}
			}

			return false;
		}
	}

	return true;
}

static void App_Close(void)
{
	DocList* pDocList = throw_DocList_Get();

	// Prepare documents autosave list
	DigitalCD.bExiting = true;

	// Must do it explicitely before we release collections or handlers
	// called by the clipboard data destructors
	Clipboard_Release();

	EditCDDesc_NotEdit();
	EditCDTrack_NotEdit();
	EditDirList_NotEdit();
	EditFile_NotEdit();
	EditList_NotEdit();
	EditRadio_NotEdit();
	EditYPList_NotEdit();
	FLSearch_NotFLSearch();

	if (throw_FileList_GetMain())
	{
		FileList_SetModifiedFlag(throw_FileList_GetMain(), true);
		FileList_OnSaveDocument(throw_FileList_GetMain());
	}

	PListRadio_AttachFileList(PListRadio_Get(), NULL);
	PListFiles_AttachFileList(PListFiles_Get(), NULL);
	PListFiles_AttachQueue(PListFiles_Get(), NULL);

	// Remove before CD database document is removed
	PListCDs_NotPListCDs();

	// Remove documents with links first to speed up process
	for (int j = DocList_Count(pDocList) - 1; j >= 0; j--)
	{
		Document* pDoc = DocList_GetDocument(pDocList, j);
		if (!FileList_IsFileList(pDoc))
			continue;

		FileList* pList = (FileList*) pDoc;

		switch (FileList_GetType(pList))
		{
			case FileList_TypeSearch:
			case FileList_TypeQueue:
			{
				Delete_Document((Document*) pList);
			}
			break;
		}
	}

	DocList_RemoveAllDocuments(pDocList);
	Delete_DocList(pDocList);

	Task_RemoveListener(EEvent_OptionIconbarActions, App_Listener, NULL);
	Task_RemoveMsgListener(EMsg_ModeChange, App_ModeHandler, NULL);
	Task_DeRegisterEventHandler(App_DefaultHandler, NULL);

	PListExt_Release();
	PListRadio_Release();
	PListFiles_Release();
	CDDescList_Release();
	DocEvents_NotDocEvents();

	LoadTypes_NotLoadTypes();
	Media_NotMedia();
	Window_Delete(DigitalCD.WindInfo);
	Delete_Menu(DigitalCD.MenuOpen, false);
	Delete_Menu(DigitalCD.MenuPlayers, false);
	BarIcon_Delete(DigitalCD.IconBar);
	OptEdit_Release();
	PlayLists_NotPlayLists();
	Players_NotPlayers();

	CDTreeViews_NotCDTreeViews();
	FLTreeViews_NotFLTreeViews();


	CmdList_Release();

	MetaScanList_Release();
	FLClues_NotFLClues(&DigitalCD.m_ClueList);
	mem_free(DigitalCD.Clues);

	Delete_StrCol(DigitalCD.pPathsCol);
	Delete_StrCol(DigitalCD.pCollectivesCol);
	Delete_StrCol(DigitalCD.pArtistsCol);
	Delete_StrCol(DigitalCD.pDatesCol);
	Delete_StrCol(DigitalCD.pBoxesCol);
	Delete_StrCol(DigitalCD.pAlbumsCol);
	Delete_StrCol(DigitalCD.pBroadcastersCol);
	Delete_StrCol(DigitalCD.pMimeTypesCol);

	Choices_NotChoices();

	RMKill("DigitalCDUtils");

	// Will not return from this call
	Task_NotTask(EXIT_SUCCESS);
}

static file_type App_Load_FileTypes[] =
{
	FileType_PlayList,
	-1
};

static bool App_OnDragging(void* handle, const Msg_Dragging* rcv)
{
	IGNORE(handle);

	if ((rcv->flags & DragFlags_DoNotClaim)
	||  (DragDrop_GetFileType(rcv->types, App_Load_FileTypes) == -1))
		return false;

	if ((rcv->pos.w != HWind_IconBar) || (rcv->pos.i != DigitalCD.IconBar))
		return false;

	Desktop_SetPointer(&ptr_copy);

	// Reply to message
	DragDrop_ReplyToDragging(rcv, ClaimFlags_PointerChange, App_Load_FileTypes, NULL, NULL);

	return true;
}

void App_StrongHelp(const char* token)
{
	Msg msg;

	snprintf(msg.data.chars, 235, "Help_Word DigitalCDIRes:DigitalCD %s", token);
	msg.data.chars[235] = '\0';
	msg.hdr.action = EMsg_StrongHelp;
	msg.hdr.ref = 0;
	msg.hdr.size = 45 + strlen(msg.data.chars);

	Msg* reply = Task_PostAndWaitForReply(&msg, 0);
	if (reply == NULL)
	{
//		App_ExecCommand(NULL, Cmd_App_ShowHelp);
		HTask dummy;

		StartTask(&dummy, SPrintf("Obey DigitalCD:StrongHelp %s", token));
	}
}

void App_Open(int cmd)
{
	if (cmd < PlayLists_Count())
	{
		try
		{
			Player* pPlayer = throw_PlayList_EnsurePlayer(PlayLists_Get(cmd), true);
			Player_ShowControls(pPlayer, -1);
		}
		catch
		{
			App_ReportException();
		}
		catch_end

		return;
	}

	switch(cmd)
	{
		case Cmd_OpenChoices:
		{
			OptEdit_Show(OptEdit_Get());
		}
		break;
		case Cmd_OpenPlayList:
		{
			FileList_Show(throw_FileList_GetMain(), -1);
		}
		break;
		case Cmd_OpenQueue:
		{
			FileList_Show(PListFiles_GetQueue(PListFiles_Get()), -1);
		}
		break;
		case Cmd_OpenCDList:
		{
			CDDescList_Show(CDDescList_Get(), NULL);
		}
		break;
	}
}

int App_CheckCommand(void* handle, uint32_t id)
{
	IGNORE(handle);

	if (DCDUtils_GetFullScreenPlugIn() != -1)
		return 0;

	switch(id)
	{
		case Cmd_App_ShowInfo:
		case Cmd_App_ShowHelp:
		case Cmd_App_ShowWebsite:
		case Cmd_App_ListPlayers:
		case Cmd_App_ListWindows:
		case Cmd_App_ShowChoices:
		case Cmd_App_ShowFileQueue:
		case Cmd_App_Quit:
			return ECmdState_Allow;
		break;
		case Cmd_App_ShowMainList:
		case Cmd_App_ShowCDDatabase:
			if (Options()->Player.bParentalLock == false)
				return ECmdState_Allow;
		break;
	}

	return 0;
}

static const CmdHandler App_CmdHandler =
{ App_CheckCommand
, App_ExecCommand
};

bool App_ExecCommand(void* handle, uint32_t id)
{
	IGNORE(handle);

	switch(id)
	{
		case Cmd_App_Quit:
		{
			if (App_MayClose())
				App_Close();
		}
		break;
		case Cmd_App_ShowInfo:
		{
			const CmdContext* ctx = CmdHandler_GetContext(&App_CmdHandler);

			switch(ctx->e->Type)
			{
				case EEvent_Message:
				case EEvent_MessageWantAck:
				{
					switch(((Msg*) ctx->e->pData)->hdr.action)
					{
						case EMsg_MenuWarning:
						{
							Menu_OpenSubWindow(DigitalCD.WindInfo);

							return true;
						}
						break;
					}
				}
				break;
			}

			Window_Open(DigitalCD.WindInfo);
		}
		break;
		case Cmd_App_ShowHelp:
		{
			HTask dummy;

			StartTask(&dummy, "Filer_Run DigitalCD:!Help");
		}
		break;
		case Cmd_App_ShowWebsite:
		{
		}
		break;
		case Cmd_App_ListPlayers:
		{
			const CmdContext* ctx = CmdHandler_GetContext(&App_CmdHandler);

			switch(ctx->e->Type)
			{
				case EEvent_Menu:
				{
					if (ctx->hit[0] >= 0)
						App_Open(ctx->hit[0]);
				}
				break;
				case EEvent_Message:
				case EEvent_MessageWantAck:
				{
					switch(((Msg*) ctx->e->pData)->hdr.action)
					{
						case EMsg_MenuWarning:
						{
							Menu_OpenSubMenu(DigitalCD.MenuPlayers);
						}
						break;
						case EMsg_HelpRequest:
						{
							Task_HelpReply(ctx->e->pData, Msg_Lookup("PlayH"));
						}
						break;
					}
				}
				break;
			}
		}
		break;
		case Cmd_App_ListWindows:
		{
			const CmdContext* ctx = CmdHandler_GetContext(&App_CmdHandler);

			switch(ctx->e->Type)
			{
				case EEvent_Menu:
				{
					if (ctx->hit[0] > 0)
						App_Open(Cmd_OpenChoices + ctx->hit[0] - 1);
					if (ctx->hit[1] >= 0)
						App_Open(ctx->hit[1]);
				}
				break;
				case EEvent_Message:
				case EEvent_MessageWantAck:
				{
					switch(((Msg*) ctx->e->pData)->hdr.action)
					{
						case EMsg_MenuWarning:
						{
							if (ctx->hit[0] == -1)
								Menu_OpenSubMenu(DigitalCD.MenuOpen);
							else
								Menu_OpenDefaultSubMenu();
						}
						break;
						case EMsg_HelpRequest:
						{
							if (ctx->hit[0] > 0)
								Task_HelpReply(ctx->e->pData, Msg_Lookup("OpenH"));
							else
								Task_HelpReply(ctx->e->pData, Msg_Lookup("PlayH"));
						}
						break;
					}
				}
				break;
			}
		}
		break;
		case Cmd_App_ShowChoices:
		{
			App_Open(Cmd_OpenChoices);
		}
		break;
		case Cmd_App_ShowMainList:
		{
			App_Open(Cmd_OpenPlayList);
		}
		break;
		case Cmd_App_ShowFileQueue:
		{
			App_Open(Cmd_OpenQueue);
		}
		break;
		case Cmd_App_ShowCDDatabase:
		{
			App_Open(Cmd_OpenCDList);
		}
		break;
		default:
			return false;
	}

	return true;
}

static EListenerAction App_Listener(void* handle, const Event* e)
{
	IGNORE(handle);

	switch(e->Type)
	{
		case EEvent_OptionIconbarActions:
		{
			App_BroadcastPrimaryController();
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

static EListenerAction App_ModeHandler(void* handle, const Event* e)
{
	IGNORE(handle);
	IGNORE(e);

	App_ModeChange();

	return EListenerAction_ContinueEvent;
}

static void App_CheckSystemVolume(void)
{
	static int oldsysvol = -1;
	_kernel_swi_regs regs;

	// Get configured volume with Sound_Volume
	regs.r[0] = 0;
	if (_kernel_swi(0x060180, &regs, &regs))
		return;

	if ((oldsysvol >= 0) && (oldsysvol != regs.r[0]))
	{
		Event e = {NULL, EEvent_OptionRescaleVolume, NULL};
		Task_ProcessEvent(&e);
	}

	oldsysvol = regs.r[0];
}

static EListenerAction App_DefaultHandler(void* handle, const Event* e)
{
	IGNORE(handle);

	switch(e->Type)
	{
		case EEvent_Null:
		{
			Choices_FlushPendingWrites();
			App_CheckSystemVolume();
			return EListenerAction_ContinueEvent;
		}
		break;
		case EEvent_Mouse:
		{
			const Mouse* m = e->pData;

			if (m->w == HWind_IconBar)
			{
				if (m->but & EBut_Menu)
				{
					CmdHandler_OpenMenu(&App_CmdHandler, handle, "Iconbar");
				}
				else if (m->but & EBut_Select)
				{
					if (Keyboard_PollShift())
						App_Open(Options()->Iconbar.Select[1]);
					else if (Keyboard_PollCtrl())
						App_Open(Options()->Iconbar.Select[2]);
					else
						App_Open(Options()->Iconbar.Select[0]);
				}
				else if (m->but & EBut_Adjust)
				{
					if (Keyboard_PollShift())
						App_Open(Options()->Iconbar.Adjust[1]);
					else if (Keyboard_PollCtrl())
						App_Open(Options()->Iconbar.Adjust[2]);
					else
						App_Open(Options()->Iconbar.Adjust[0]);
				}
			}
		}
		break;
		case EEvent_Message:
		case EEvent_MessageWantAck:
		{
			switch(((Msg*) e->pData)->hdr.action)
			{
				case EMsg_Quit:
				{
					App_Close();
				}
				break;
				case EMsg_PreQuit:
				{
					if (!App_MayClose())
						Task_AcknowledgeMsg(e->pData);
				}
				break;
				case EMsg_Dragging:
				{
					/* If we accept the message, it adds a PreEventHandler
					 * to ensure that only our OnDragging handles the
					 * following Message_Dragging messages.
					 */
					if (DragDrop_SetOnDraggingCallback(e->pData, App_OnDragging, NULL))
						return EListenerAction_StopEvent;
				}
				break;
				case EMsg_DataOpen:
				case EMsg_DataLoad:
				case EMsg_DataSave:
				{
					PListFiles_ReceiveFile(PListFiles_Get(), e->pData);
					return EListenerAction_StopEvent;
				}
				break;
				case EMsg_HelpRequest:
				{
					const Msg_HelpRequest* pmsg = e->pData;

					if ((pmsg->m.w == HWind_IconBar)
					&&  (pmsg->m.i == DigitalCD.IconBar))
					{
						const char* str = Msg_Lookup("IconH");
						Task_HelpReply(pmsg, str);
						return EListenerAction_StopEvent;
					}
				}
				break;
				case EMsg_SaveDesktop:
				{
					const Msg_SaveDesktop* pmsg = e->pData;
					int i;
					const char* ptr = Task_GetDir();
					const char* name = NULL;
					int handle = pmsg->file;

					try
					{
						if (!_kernel_osbput('R', handle)
						||  !_kernel_osbput('u', handle)
						||  !_kernel_osbput('n', handle)
						||  !_kernel_osbput(' ', handle))
							throw_last_oserror();

						for (i = 0; ptr[i]; i++)
						{
							if (!_kernel_osbput(ptr[i], handle))
								throw_last_oserror();
						}

						if (name)
						{
							if (!_kernel_osbput(' ' , handle))
								throw_last_oserror();
							for (i = 0; name[i]; i++)
							{
								if (!_kernel_osbput(name[i], handle))
									throw_last_oserror();
							}
						}

						if (!_kernel_osbput(10, handle))
							throw_last_oserror();
					}
					catch
					{
						App_ReportException();
						Task_AcknowledgeMsg(e->pData);
					}
					catch_end

					return EListenerAction_StopEvent;
				}
				break;
				case EMsg_DCD_Misc:
				{
					const DCD_Misc_Message* pmsg = e->pData;
					DCD_Misc_Message msg = {{sizeof(DCD_Misc_Message), 0, 0, 0, EMsg_DCD_Misc}, 0};
					Player* pPlayer = NULL;

					switch(pmsg->reason)
					{
						case 1: msg.controller = pmsg->controller; break;
						case 2: msg.controller = Options()->Iconbar.Select[0]; break;
						case 3: msg.controller = Options()->Iconbar.Adjust[0]; break;
						case 4:
						{
							msg.controller = pmsg->controller;

							try
							{
								if (Options()->Player.bSinglePanelMode)
									pPlayer = throw_PlayList_EnsurePlayer(NULL, true);
								else
									pPlayer = throw_PlayList_EnsurePlayer(PlayLists_Get(pmsg->controller), true);

								Player_RemoteControl(pPlayer, (ECmd_Type_Player) pmsg->data.cmd.cmd, pmsg->data.cmd.value);
							}
							catch
							{
								App_ReportException();
							}
							catch_end

							return EListenerAction_StopEvent;
						}
						break;
						default:
						{
							return EListenerAction_StopEvent;
						}
					}

					pPlayer = PlayLists_GetPlayer(msg.controller);

					if (pPlayer)
					{
						msg.reason = 0;
						msg.data.info.handle = Player_GetDCDUtilsHandle(pPlayer);
						msg.data.info.window = Player_GetWindow(pPlayer, -1);
						msg.hdr.ref = pmsg->hdr.id;
						Task_SendMsg((Msg*) &msg, pmsg->hdr.task);
					}
				}
				break;
			}
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

static int messages[] =
{
	EMsg_DataSave,
	EMsg_DataSaveAck,
	EMsg_DataLoad,
	EMsg_DataLoadAck,
	EMsg_DataOpen,
	EMsg_RAMFetch,
	EMsg_RAMTransmit,
	EMsg_PreQuit,
	EMsg_SaveDesktop,
	EMsg_ClaimEntity,
	EMsg_ReleaseEntity,
	EMsg_DataRequest,
	EMsg_Dragging,
	EMsg_DragClaim,
	EMsg_HelpRequest,
	EMsg_MenuWarning,
	EMsg_ModeChange,
	EMsg_MenusDeleted,
	EMsg_FontChanged,
	EMsg_StrongHelp,
	CDDB_Query_MessageNum,
	CDDB_QueryResults_MessageNum,
	EMsg_DCD_Misc,
	0
};

void App_BroadcastPrimaryController(void)
{
	DCD_Misc_Message msg = {{sizeof(DCD_Misc_Message), 0, 0, 0, EMsg_DCD_Misc}, 0};
	Player* pPlayer = NULL;

	// Request primary player ?
	try
	{
		pPlayer = throw_PlayList_EnsurePlayer(NULL, false);
	}
	catch
	{
		App_ReportException();
		return;
	}
	catch_end

	msg.controller = Player_GetNr(pPlayer);
	msg.data.info.handle = Player_GetDCDUtilsHandle(pPlayer);
	msg.data.info.window = Player_GetWindow(pPlayer, -1);
	msg.hdr.size = sizeof(msg.data.info) + (((char*) &msg.data.info) - ((char*) &msg));

	Task_SendMsg((Msg*) &msg, 0);
}

int main(int argc, char* argv[])
{
	// Wimp initialisations
	throw_Task_Task(false, 310, ProgramName, ProgramSprite, ProgramDir, messages);

	try
	{
		throw_App_Init(argc, argv);
	}
	catch
	{
		Task_ReportException();
		return EXIT_FAILURE;
	}
	catch_end

	Task_MainLoop(0, 2);

	return EXIT_SUCCESS;
}
