#include "WimpLib:Task.h"

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

#include "kernel.h"

#include "WimpLib:Clipboard.h"
#include "WimpLib:Desktop.h"
#include "WimpLib:DragDrop.h"
#include "WimpLib:EventSrc.h"
#include "WimpLib:Exception.h"
#include "WimpLib:File.h"
#include "WimpLib:Keyboard.h"
#include "WimpLib:Log.h"
#include "WimpLib:mem.h"
#include "WimpLib:Menu.h"
#include "WimpLib:Message.h"
#include "WimpLib:Sprites.h"
#include "WimpLib:Template.h"
#include "WimpLib:Timer.h"
#include "WimpLib:Utils.h"
#include "WimpLib:Window.h"
#include "WimpLib:Windows.h"

static const _kernel_oserror Task_Error_NoFile = {0x40000001, "NoFile:File '%0' not found."};
static int       Task_PollBuf[256];
static Mode_Info Task_ModeInfo;
static Mouse     Task_MouseInfo;

static struct
{
	int                 m_WimpVersion;
	int                 m_TerritoryNumber;
	char*               m_pTaskName;
	char*               m_pTaskSpriteName;
	char                m_TaskDir[1024]; // cannot dyn. allocate cf logging for mem_dump()
	HTask               m_TaskId;
	unsigned int        m_PollMask;
	int                 m_PollTimeStep;
	EventSource         m_GlobalHandlers;
	EventSource         m_MsgHandlers;
	CSpriteArea*        m_pTaskSprites;
	Event               m_Event;
	JobList*            m_pJobList;
	// Clipboard
	const ClipboardHandler*   m_pClipboardHandler;
	void*               m_pClipboardHandlerData;
	void*               m_pClipboardContents;
	bool                m_bClipboardAllowPaste;
	bool                m_bCaretOwner;
	// Hot keys grabbing
	HWind               m_KeyGrabberwindow;
	_kernel_oserror     m_LastError;
} Task;

//---------------------------------------------------------------------------------------------------------------------------
//- Task constructors and destructor ----------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------------------

//
// Task constructor
//

void throw_Task_Task
( bool bcpp
, int Version
, const char* pTaskName
, const char* pTaskSpriteName
, const char* pTaskDir
, int* pMsgList
)
{
	_kernel_swi_regs regs;
	const _kernel_oserror* err;
	char var[2];
	int options;
	int mem_flags = 0;

	memset(&Task, 0, sizeof(Task));

	// XOS_ReadVarVal
	regs.r[0] = (int) SPrintf("%s$CatchSignals", pTaskName);
	regs.r[1] = (int) var;
	regs.r[2] = 2;
	regs.r[3] = 0;
	if (_kernel_swi(0x020023, &regs, &regs))
		var[0] = '1';

	if (var[0] == '2')
	{
		regs.r[0] = 1;
		_kernel_swi(0x55, &regs, &regs);
		_kernel_swi(0x40, &regs, &regs);
		regs.r[0] = 2;
		_kernel_swi(0x55, &regs, &regs);
		_kernel_swi(0x40, &regs, &regs);
		regs.r[0] = 3;
		_kernel_swi(0x55, &regs, &regs);
		_kernel_swi(0x40, &regs, &regs);
		regs.r[0] = 4;
		_kernel_swi(0x55, &regs, &regs);
		_kernel_swi(0x40, &regs, &regs);
		regs.r[0] = 6;
		_kernel_swi(0x55, &regs, &regs);
		_kernel_swi(0x40, &regs, &regs);
		regs.r[0] = 9;
		_kernel_swi(0x55, &regs, &regs);
		_kernel_swi(0x40, &regs, &regs);
	}
	else if (var[0] != '0')
		exception_initialise();

	snprintf(Task.m_TaskDir, sizeof(Task.m_TaskDir), "%s", pTaskDir);

	SetLoggingOptions(Log_Logging);

	// Wimp initialise
	regs.r[0] = Version;
	regs.r[1] = 0x4b534154;
	regs.r[2] = (int) pTaskName;
	regs.r[3] = (int) pMsgList;
	if ((err = _kernel_swi(0x0600c0, &regs, &regs)) != NULL)
	{
		regs.r[0] = (int) err;
		regs.r[1] = 1;
		regs.r[2] = (int) pTaskName;
		_kernel_swi(0x600df, &regs, &regs);

		exit(EXIT_FAILURE);
	}

	Task.m_WimpVersion = regs.r[0];
	Task.m_TaskId      = regs.r[1];

	Task.m_KeyGrabberwindow = HWind_None;

	Task.m_TerritoryNumber = 1;
	if (!_kernel_swi(0x63040, &regs, &regs))
		Task.m_TerritoryNumber = regs.r[0];

	try
	{
		const char* filename;
		char        sprname[12];
		FILE*       file;

		// Initialise heap
		if (bcpp) mem_flags = EMem_CPP;

		// XOS_ReadVarVal
		regs.r[0] = (int) SPrintf("%s$MemMalloc", pTaskName);
		regs.r[1] = (int) var;
		regs.r[2] = 2;
		regs.r[3] = 0;
		if (_kernel_swi(0x020023, &regs, &regs) == NULL)
		{
			if (var[0] == '1')
				mem_flags |= EMem_CPP;
		}

		// XOS_ReadVarVal
		regs.r[0] = (int) SPrintf("%s$MemLog", pTaskName);
		regs.r[1] = (int) var;
		regs.r[2] = 2;
		regs.r[3] = 0;
		if (_kernel_swi(0x020023, &regs, &regs) == NULL)
		{
			if (var[0] == '1')
				mem_flags |= EMem_Log;
		}

		// XOS_ReadVarVal
		regs.r[0] = (int) SPrintf("%s$MemCheck", pTaskName);
		regs.r[1] = (int) var;
		regs.r[2] = 2;
		regs.r[3] = 0;
		if (_kernel_swi(0x020023, &regs, &regs) == NULL)
		{
			if (var[0] == '1')
				mem_flags |= EMem_Check;
		}

		// XOS_ReadVarVal
		regs.r[0] = (int) SPrintf("%s$MemDump", pTaskName);
		regs.r[1] = (int) var;
		regs.r[2] = 2;
		regs.r[3] = 0;
		if (_kernel_swi(0x020023, &regs, &regs) == NULL)
		{
			if (var[0] == '1')
				mem_flags |= EMem_Dump;
		}

		mem_init(mem_flags);
		throw_EventSource_EventSource(&Task.m_GlobalHandlers);
		throw_EventSource_EventSource(&Task.m_MsgHandlers);

		throw_Windows_Windows();

		Task.m_pTaskName = throw_mem_allocstring(pTaskName);

		// Convert Dir to cannonical path using OS_FSControl 37
		{
		  char* path = throw_File_CanonicalPath(pTaskDir);
		  snprintf(Task.m_TaskDir, sizeof(Task.m_TaskDir), "%s", path);
		  mem_free(path);
		}

		Task_SetModeInfo();

		throw_Msg_Msg();

		// Get sprites file suffix
		regs.r[0] = 2;
		throw_os(_kernel_swi(0x0600f2, &regs, &regs));

		// Open sprite file
		snprintf(sprname, sizeof(sprname), "Sprites%s", (char*) regs.r[0]);
		filename = throw_File_FromRes(sprname);
		file = fopen(filename, "rb");
		if (file == NULL)
		{
			filename = throw_File_FromRes("Sprites");
			file = fopen(filename, "rb");
		}

		if (file == NULL)
		{
			regs.r[0] = (int) &Task_Error_NoFile;
			regs.r[1] = 0;
			regs.r[2] = 0;
			regs.r[3] = 0;
			regs.r[4] = (int) filename;
			regs.r[5] = 0;
			regs.r[6] = 0;
			regs.r[7] = 0;
			_kernel_swi(0x41506, &regs, &regs);
			throw_os((const _kernel_oserror*) regs.r[0]);
		}
		fclose(file);

		Task.m_pTaskSprites = throw_Sprites_LoadFile(filename);

		// Load Templates
		throw_Templates_Templates();

		Task.m_pTaskSpriteName = throw_mem_allocstring(pTaskSpriteName);

		Task.m_pJobList = New_JobList();
	}
	catch
	{
		regs.r[0] = (int) &exception_current()->error;
		regs.r[1] = 1;
		regs.r[2] = (int) pTaskName;
		_kernel_swi(0x600df, &regs, &regs);

		Delete_JobList(Task.m_pJobList);

		Templates_NotTemplates();

		EventSource_NotEventSource(&Task.m_GlobalHandlers);
		EventSource_NotEventSource(&Task.m_MsgHandlers);
		Windows_NotWindows();

		Msg_NotMsg();

		mem_free(Task.m_pTaskSprites);
		mem_free(Task.m_pTaskSpriteName);
		mem_free(Task.m_pTaskName);

		// Wimp CloseDown
		regs.r[0] = Task.m_TaskId;
		regs.r[1] = 0x4b534154;
		_kernel_swi(0x0600dd, &regs, &regs);

		exit(EXIT_FAILURE);
	}
	catch_end

	options = 0;
	// XOS_ReadVarVal
	regs.r[0] = (int) SPrintf("%s$Logging", pTaskName);
	regs.r[1] = (int) var;
	regs.r[2] = 2;
	regs.r[3] = 0;
	if (_kernel_swi(0x020023, &regs, &regs) == NULL)
	{
		if (var[0] == '1')
			options |= Log_Logging;
		if (var[0] == '2')
			options |= Log_Logging | Log_SysLog;
	}

	// XOS_ReadVarVal
	regs.r[0] = (int) SPrintf("%s$Trace", pTaskName);
	regs.r[1] = (int) var;
	regs.r[2] = 2;
	regs.r[3] = 0;
	if (_kernel_swi(0x020023, &regs, &regs) == NULL)
	{
		if (var[0] == '1')
			options |= Log_Trace;
	}

	SetLoggingOptions(options);
}

//
// Task destructor
//

void  Task_NotTask(int retval)
{
	_kernel_swi_regs regs;

	if (retval != EXIT_SUCCESS)
		mem_removechecks();

	Clipboard_Release();

	Delete_JobList(Task.m_pJobList);

	if (Task.m_KeyGrabberwindow != HWind_None)
		Window_Delete(Task.m_KeyGrabberwindow);

	Templates_NotTemplates();

	mem_free(Task.m_pTaskSprites);

	EventSource_NotEventSource(&Task.m_GlobalHandlers);
	EventSource_NotEventSource(&Task.m_MsgHandlers);
	Windows_NotWindows();

	Msg_NotMsg();

	mem_free(Task.m_pTaskSpriteName);
	mem_free(Task.m_pTaskName);

	mem_dump();

	// Wimp CloseDown
	regs.r[0] = Task.m_TaskId;
	regs.r[1] = 0x4b534154;
	_kernel_swi(0x0600dd, &regs, &regs);

	exit(retval);
}

//---------------------------------------------------------------------------------------------------------------------------
//- Wimp_PollIdle handling --------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------------------

//
// Task main loop
//

void Task_MainLoop(unsigned int mask, int time_step)
{
	Task.m_PollMask = mask;
	Task.m_PollTimeStep = time_step;

	try
	{
		while (true)
			Task_ProcessEvent(Task_PollIdle(Task.m_PollMask, Task.m_PollTimeStep));
	}
	catch
	{
		Task_ReportException();
		Log("Program terminated by untrapped exception.\n");
		Task_NotTask(EXIT_FAILURE);
	}
	catch_end
}

//
// Task call to wimp poll
//

Event* Task_PollIdle(unsigned int mask, int time_step)
{
	_kernel_swi_regs regs;
	int poll;
	int time = 0;

	// Compact memory
	mem_pack();

	if (mask & EPollMask_Null) time_step = 0;

	if (time_step > 0)
		time = Timer_GetTime() + time_step;

	// Wimp poll
	regs.r[0] = mask;
	regs.r[1] = (int) Task_PollBuf;
	regs.r[2] = time;
	poll = (time_step > 0) ? 0x400e1 : 0x400c7;
	_kernel_swi(poll, &regs, &regs);

	//
	// We use internal events with top bit set
	// which we should normally never receive
	// but in who knows in risc os 43.50 ?
	//
	if (regs.r[0] < 0) regs.r[0] = 0;

	// Dispatch event
	Task.m_Event.Type = regs.r[0];
	Task.m_Event.pData = &Task_PollBuf;

	// Get mouse info
	regs.r[1] = (int) &Task_MouseInfo;
	_kernel_swi(0x400cf, &regs, &regs);

	return &Task.m_Event;
}

//
// This call processes a wimp event
//

void Task_ProcessEvent(const Event* e)
{
	_kernel_swi_regs regs;
	HWind w = HWind_None;

	//
	// First, we try to find the window handle of events we know to be window specific.
	// We also process all Menus event and preprocess some other events.
	//

	if (Menu_ProcessEvent(e))
		return;

	switch(e->Type)
	{
		case EEvent_Key:
		{
			Event_Key* key = (Event_Key*) e->pData;

			w = key->caret.pos.w;

			// Extending codes to more or less match Zap
			switch (key->code)
			{
				case 0x01:case 0x02:case 0x03:case 0x04:case 0x05:case 0x06:case 0x07:
				case 0x08:case 0x09:case 0x0A:case 0x0B:case 0x0C:
				case 0x0E:case 0x0F:case 0x10:case 0x11:case 0x12:case 0x13:case 0x14:
				case 0x15:case 0x16:case 0x17:case 0x18:case 0x19:case 0x1A:
				{
					if (Keyboard_PollShift())
						key->code +=  0x100;
				}
				break;
				case 0x0D:
				case 0x1B:case 0x1C:case 0x1D:case 0x1E:
				{
					if (Keyboard_PollCtrl())
					{
						if (Keyboard_PollShift())
							key->code += 0x140;
						else
							key->code += 0x120;
					}
					else if (Keyboard_PollShift())
						key->code += 0x100;
				}
			    break;
			    case 0x20:
			    {
					if (Keyboard_PollCtrl())
						key->code = 0;
					if (Keyboard_PollShift())
						key->code += 0x100;
			    }
			    break;
			    case 0x7f:
			    {
			    	if (Keyboard_PollCtrl())
						key->code = 0x1f;
					if (Keyboard_PollShift())
						key->code += 0x100;
			    }
			    break;
			}
		}
		break;
		case EEvent_WindowRedraw:
		case EEvent_WindowOpen:
		case EEvent_WindowClose:
		case EEvent_WindowPtrLeave:
		case EEvent_WindowPtrEnter:
		case EEvent_WindowScroll:
		case EEvent_WindowLoseCaret:
		case EEvent_WindowGainCaret:
			w = * (int*) e->pData;
		break;
		case EEvent_Mouse:
			w = ((Mouse*) e->pData)->w;
		break;
		case EEvent_Message:
		case EEvent_MessageWantAck:
		{
			switch(((Msg*) e->pData)->hdr.action)
			{
				case EMsg_Dragging:
					// If a call-back is installed call it first
					if (DragDrop_CallDraggingCallback(e->pData))
						return;
					w = ((Msg_Dragging*) e->pData)->pos.w;
				break;
				case EMsg_HelpRequest:
					w = ((Msg_HelpRequest*) e->pData)->m.w;
				break;
				case EMsg_DataLoad:
				case EMsg_DataSave:
				{
					const Msg_FileData* msg = e->pData;

					// If a call-back for dragging is installed
					// call it to clean up things like ghost caret,
					// then remove it
					//
					DragDrop_SetOnDraggingCallback(NULL, NULL, NULL);
					w = msg->pos.w;
					// If on system icons, ignore
					if (msg->pos.i < -1) w = HWind_None;

					// If the stated ref is the one of our last
					// DragClaim with ptr_nodrop we don't want it
					if (DragDrop_IsNoDropRef(msg->hdr.ref))
						return;
				}
				break;
				case EMsg_ModeChange:
				{
					// Preprocessing
					Task_SetModeInfo();
				}
				break;
			}
		}
		break;
	}

	//
	// First call preproccess event handler
	// Note that this loop type allows a preprocessor to remove itself
	//

	if (EventSource_PreProcessEvent(&Task.m_GlobalHandlers, e))
		return;

	//
	// In the case messages we know to be window specific
	// we call the related event handler if one is defined
	//

	if (w != HWind_None)
	{
		if (Window_ProcessEvent(w, e))
			return;
	}

	//
	// If Help request message  not processed, it could be
	// help on a menu.
	// Dispatch messages to listeners
	//

	if (Menu_PostProcessEvent(e))
		return;

	switch(e->Type)
	{
		case EEvent_Null:
		{
			JobList_Exec(Task.m_pJobList);
		}
		break;
		case EEvent_Message:
		case EEvent_MessageWantAck:
		{
			Event ecopy;

			// Dispatch messages to listeners
			ecopy.Type = ((Msg*) e->pData)->hdr.action;
			ecopy.pData = e->pData;
			if (EventSource_ProcessEvent(&Task.m_MsgHandlers, &ecopy))
				return;
		}
		break;
	}

	//
	// If the message was not processed by a window event handler
	// we call a default event handler if one is defined
	//

	if (EventSource_PostProcessEvent(&Task.m_GlobalHandlers, e))
		return;

	//
	// If the message was still not processed
	// we call or own default event handler
	//

	switch(e->Type)
	{
		case EEvent_WindowRedraw:
		{
			CWindRedraw r;

			r.w = * (int*) e->pData;
			regs.r[1] = (int) &r;
			// Wimp_RedrawWindow
			_kernel_swi(0x600c8, &regs, &regs);

			while (regs.r[0])
			{
				// Wimp_GetRectangle
				_kernel_swi(0x600ca, &regs, &regs);
			}
		}
		break;
		case EEvent_WindowOpen:
		case EEvent_WindowScroll:
		{
			regs.r[1] = (int) e->pData;
			_kernel_swi(0x600c5, &regs, &regs);
		}
		break;
		case EEvent_WindowClose:
		{
			regs.r[1] = (int) e->pData;
			_kernel_swi(0x600c6, &regs, &regs);
		}
		break;
		case EEvent_Key:
		{
			// Key not used, forward it
			regs.r[0] = ((Event_Key*) e->pData)->code;
			_kernel_swi(0x600dc, &regs, &regs);
		}
		break;
		case EEvent_Message:
		case EEvent_MessageWantAck:
		{
			switch(((Msg*) e->pData)->hdr.action)
			{
				case EMsg_Quit:
				{
					_kernel_swi(0x600dd, &regs, &regs);
					exit(EXIT_SUCCESS);
				}
				break;
				case EMsg_ClaimEntity:
				{
					const Msg_ClaimEntity* pmsg = e->pData;

					// do nothing if message came from us
					if (Task_GetId() == pmsg->hdr.task) return;

					// if clipboard is claimed, release it
					if (pmsg->flags & 0x4)
					{
						Clipboard_Clear();
						Clipboard_UpdateUI();
					}

					if (pmsg->flags & 0x3)
						Task.m_bCaretOwner = false;
				}
				break;
				case EMsg_DataRequest: // Clipboard data request
				{
					Msg_DataRequest* pmsg = (Msg_DataRequest*) e->pData;

					if (Task.m_pClipboardHandler == NULL) return;
					if (!(pmsg->flags & 0x4)) return;

					// Ensure list is limited
					pmsg->types[53] = -1;

					XFer_Send(pmsg->hdr.id
							, pmsg->hdr.task
							, pmsg->pos.w
							, pmsg->pos.i
							, pmsg->pos.pt.x
							, pmsg->pos.pt.y
							, 0
							, Task.m_pClipboardHandler->m_pSetFileType(Task.m_pClipboardContents, pmsg->types)
							, "Clipboard"
							, Task.m_pClipboardHandler->m_pTransferContents
							, NULL
							, NULL
							, NULL
							, NULL
							, Task.m_pClipboardHandler->m_bAsFileOnly
							, Task.m_pClipboardContents
							);
				}
				break;
				case EMsg_Dragging: // Drag & drop Message_Dragging
				{
					DragDrop_ReplyNoDrop(e->pData);
				}
				break;
			}
		}
		break;
	}
}

//
// This call processes a wimp event, knowing there is a modal dialog box
//

void Task_ProcessEventModal(const Event* e, HWind id)
{
	switch (e->Type)
	{
		case EEvent_WindowOpen:
		case EEvent_WindowScroll:
		{
			CWindOpen* open = (CWindOpen*) e->pData;

			if (open->w != id)
			{
				if (open->behind == HWind_None)
				{
					Window_Open(id);

					open->behind = id;
				}
			}
		}
		break;
		case EEvent_WindowClose:
		{
			const HWind* pid = e->pData;

			if (*pid != id)
			{
				// Force caret to dialog box
				Caret_SetInvisible(id);
				// Open modal dialog on top
				Window_Open(id);
				// Ignore
				return;
			}
		}
		break;
		case EEvent_Key:
		{
			_kernel_swi_regs regs;
			// Other keys are not processed
			regs.r[0] = ((Event_Key*) e->pData)->code;
			_kernel_swi(0x600dc, &regs, &regs);
			return;
		}
		break;
		case EEvent_Mouse:
		{
			const Mouse* m = e->pData;

			if (m->w != id)
			{
				// Force caret to dialog box
				Caret_SetInvisible(id);
				// Open modal dialog on top
				Window_Open(id);
				// Ignore button clicks
				return;
			}
		}
		break;
		case EEvent_WindowGainCaret:
		{
			const CCaret* pc = e->pData;

			if (pc->pos.w != id)
			{
				// Force caret to dialog box
				Caret_SetInvisible(id);
				// Open modal dialog on top
				Window_Open(id);

				return;
			}
		}
		break;
	}

	// Normal processing
	Task_ProcessEvent(e);
}

//---------------------------------------------------------------------------------------------------------------------------
//- Register Event Handlers: task specific and window specific --------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------------------

//
// Task loop while PreEventHandler exists
// Note: You should not have more than one active at once
//

void Task_SubLoop(unsigned int mask, int time_step, Listener_FOnEvent pEventHandler, void *pHandle)
{
	int PollMask = Task.m_PollMask;
	int PollTimeStep = Task.m_PollTimeStep;

	try
	{
		throw_Task_RegisterEventHandler(pEventHandler, pHandle, true);

		Task.m_PollMask = mask;
		Task.m_PollTimeStep = time_step;

		while (EventSource_FindGlobalListener(&Task.m_GlobalHandlers, pEventHandler, pHandle))
			Task_ProcessEvent(Task_PollIdle(Task.m_PollMask, Task.m_PollTimeStep));
	}
	catch
	{
		Task_ReportException();
	}
	catch_end

	Task.m_PollMask = PollMask;
	Task.m_PollTimeStep = PollTimeStep;
}

//
// Post a message and wait for reply
//

static EListenerAction Task_WaitReply_Handler(void* pHandle, const Event* e)
{
	const Msg** preply = pHandle;
	const Msg*  pmsg = e->pData;

	// We are only interested in replies or acks to our messages
	switch (e->Type)
	{
		case EEvent_Message:
		case EEvent_MessageWantAck:
		{
			if (pmsg->hdr.ref != (*preply)->hdr.id) // reply
				return EListenerAction_ContinueEvent;
		}
		break;
		case EEvent_MessageAck:
		{
			if ((pmsg->hdr.id != (*preply)->hdr.id)   // msg bouncing
			&&  (pmsg->hdr.ref != (*preply)->hdr.id)) // reply
				return EListenerAction_ContinueEvent;
		}
		break;
		default:
			return EListenerAction_ContinueEvent;
	}

	Task_DeRegisterEventHandler(Task_WaitReply_Handler, pHandle);

	if (pmsg->hdr.id == (*preply)->hdr.id)
	{
		// msg bouncing
		*preply = NULL;
	}
	else
		*preply = pmsg;

	return EListenerAction_StopEvent;
}

Msg* Task_PostAndWaitForReply(Msg* msg, HTask task)
{
	Msg* reply = msg;

	if (Task_PostMsg(msg, task)) return NULL;

	Task_SubLoop(Task.m_PollMask, Task.m_PollTimeStep, Task_WaitReply_Handler, &reply);

	return reply;
}

Msg* Task_WPostAndWaitForReply(Msg* msg, HWind w, HIcon i)
{
	Msg* reply = msg;

	if (Task_WPostMsg(msg, w, i)) return NULL;

	Task_SubLoop(Task.m_PollMask, Task.m_PollTimeStep, Task_WaitReply_Handler, &reply);

	return reply;
}

//
// (De)Registers a user defined task event handler
//

void throw_Task_RegisterEventHandler(Listener_FOnEvent pEventHandler, void* pHandle, bool bFirst)
{
	throw_EventSource_AddGlobalListener(&Task.m_GlobalHandlers, pEventHandler, pHandle, bFirst);
}

void Task_DeRegisterEventHandler(Listener_FOnEvent pEventHandler, void *pHandle)
{
	EventSource_RemoveGlobalListener(&Task.m_GlobalHandlers, pEventHandler, pHandle);
}

//
// (De)Registers a user defined specific event handler
//

void throw_Task_AddListener(int event, Listener_FOnEvent pEventHandler, void *pHandle, bool bFirst)
{
	throw_EventSource_AddListener(&Task.m_GlobalHandlers, event, pEventHandler, pHandle, bFirst);
}

void Task_RemoveListener(int event, Listener_FOnEvent pEventHandler, void *pHandle)
{
//	tmp(&Task.m_GlobalHandlers);
	EventSource_RemoveListener(&Task.m_GlobalHandlers, event, pEventHandler, pHandle);
}

//
// (De)Registers a user defined specific message handler
//

void throw_Task_AddMsgListener(int msgtype, Listener_FOnEvent pEventHandler, void *pHandle, bool bFirst)
{
	throw_EventSource_AddListener(&Task.m_MsgHandlers, msgtype, pEventHandler, pHandle, bFirst);
}

void Task_RemoveMsgListener(int msgtype, Listener_FOnEvent pEventHandler, void *pHandle)
{
//	tmp(&Task.m_GlobalHandlers);
	EventSource_RemoveListener(&Task.m_MsgHandlers, msgtype, pEventHandler, pHandle);
}

//---------------------------------------------------------------------------------------------------------------------------
//- Task information --------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------------------

//
// Task properties
//

HTask Task_GetId(void)
{
	return Task.m_TaskId;
}

int Task_GetWimpVersion(void)
{
	return Task.m_WimpVersion;
}

int Task_GetTerritoryNumber(void)
{
	return Task.m_TerritoryNumber;
}

unsigned int Task_GetPollMask(void)
{
	return Task.m_PollMask;
}

int Task_GetPollTimeStep(void)
{
	return Task.m_PollTimeStep;
}

void Task_SetPollTimeStep(int step)
{
	Task.m_PollTimeStep = step;
}

const char* Task_GetName(void)
{
	return Task.m_pTaskName;
}

const char* Task_GetDir(void)
{
	return Task.m_TaskDir;
}

const char* Task_GetSpriteName(void)
{
	return Task.m_pTaskSpriteName;
}

CSpriteArea* Task_GetSpriteArea(void)
{
	return Task.m_pTaskSprites;
}

//
// Screen properties
//

const Mode_Info* Task_GetModeInfo(void)
{
	return &Task_ModeInfo;
}

const Mouse* Mouse_Get(void)
{
	return &Task_MouseInfo;
}

void Mouse_Set(int x, int y)
{
	_kernel_swi_regs regs;
	char block[5];

	block[0] = 3;
	block[1] = (x & 0xff);
	block[2] = (x & 0xff00) >> 8;
	block[3] = (y & 0xff);
	block[4] = (y & 0xff00) >> 8;
	regs.r[0] = 21;
	regs.r[1] = (int) &block;
	_kernel_swi(0x20007, &regs, &regs);
}

void Task_SetModeInfo(void)
{
	_kernel_swi_regs regs;
	int inv[6];
	int outv[6];

	inv[0] = 4;
	inv[1] = 5;
	inv[2] = 11;
	inv[3] = 12;
	inv[4] = 9;
	inv[5] = -1;

	// OS_ReadVduVariables
	regs.r[0] = (int) inv;
	regs.r[1] = (int) outv;
	_kernel_swi(0x20031, &regs, &regs);

	Task_ModeInfo.shiftx = outv[0];
	Task_ModeInfo.shifty = outv[1];
	Task_ModeInfo.dx = 1 << outv[0];
	Task_ModeInfo.dy = 1 << outv[1];
	Task_ModeInfo.bpp = 1 << outv[4];
	Task_ModeInfo.resx = 180 >> outv[0];
	Task_ModeInfo.resy = 180 >> outv[1];
	Task_ModeInfo.millix = 400;
	Task_ModeInfo.milliy = 400;
	Task_ModeInfo.box.x0 = 0;
	Task_ModeInfo.box.y0 = 0;
	Task_ModeInfo.box.x1 = (outv[2] + 1) << outv[0];
	Task_ModeInfo.box.y1 = (outv[3] + 1) << outv[1];
}

//---------------------------------------------------------------------------------------------------------------------------
//- Task message reporting --------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------------------

const _kernel_oserror* Task_LastOSError(void)
{
	// take a safe copy
	Task.m_LastError = *_kernel_last_oserror();
	return &Task.m_LastError;
}

void Task_ReportException(void)
{

	Task_ReportOSError(&exception_current()->error);
}

void Task_ReportError(const char* pmsg, ...)
{
	va_list arg;
	va_start(arg, pmsg);
	Task_ReportVMsg(0x501, pmsg, arg);
	va_end(arg);
}

void Task_ReportInfo(const char* pmsg, ...)
{
	va_list arg;
	va_start(arg, pmsg);
	Task_ReportVMsg(0x301, pmsg, arg);
	va_end(arg);
}

bool Task_Query(bool invert, const char* pmsg, ...)
{
	bool ret;

	va_list arg;
	va_start(arg, pmsg);
	if (invert)
		ret =  (Task_ReportVMsg(0x907, pmsg, arg) == 1);
	else
		ret =  (Task_ReportVMsg(0x903, pmsg, arg) == 1);
	va_end(arg);
	return ret;
}

int Task_ReportVMsg(unsigned int flags, const char* pmsg, va_list arg)
{
	_kernel_swi_regs regs;
	_kernel_oserror  e;

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

	regs.r[0] = (int) &e;
	regs.r[1] = flags;
	regs.r[2] = (int) Task_GetName();
	regs.r[3] = (int) Task.m_pTaskSpriteName;
	regs.r[4] = (int) Task.m_pTaskSprites;
	regs.r[5] = 0;
	if (_kernel_swi(0x600df, &regs, &regs) != NULL) return 0;

	return regs.r[1];
}

void Task_DescribeError(const _kernel_oserror* e, const char* pmsg, ...)
{
	va_list arg;
	va_start(arg, pmsg);
	Task_DescribeVMsg(0x501, e, pmsg, arg);
	va_end(arg);
}

void Task_DescribeVMsg(unsigned int flags, const _kernel_oserror* pe, const char* pmsg, va_list arg)
{
	_kernel_swi_regs regs;
	_kernel_oserror  e;

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

	regs.r[0] = (int) &e;
	regs.r[1] = flags;
	regs.r[2] = (int) Task_GetName();
	regs.r[3] = (int) Task.m_pTaskSpriteName;
	regs.r[4] = (int) Task.m_pTaskSprites;
	if (pe)
	{
		// Description is present
		if (Task.m_WimpVersion < 350)
		{
			// On Old Wimp show both in successtion
			regs.r[0] = (int) pe;
			regs.r[5] = 0;
			if (_kernel_swi(0x600df, &regs, &regs) != NULL) return;

			regs.r[0] = (int) &e;
			regs.r[1] = flags;
			regs.r[2] = (int) Task_GetName();
			regs.r[3] = (int) Task.m_pTaskSpriteName;
			regs.r[4] = (int) Task.m_pTaskSprites;
			regs.r[5] = 0;
			_kernel_swi(0x600df, &regs, &regs);
		}
		else
		{
			// Show main one, with a describe button
			regs.r[5] = (int) Msg_Lookup("ErrDescribe:Describe");
			if (_kernel_swi(0x600df, &regs, &regs) != NULL) return;
			// If button is pressed, show description
			if (regs.r[1] == 3)
			{
				// try to suppress extra decribe level of serious errors
				e = *pe;
				e.errnum &= 0x7fffffff;
				regs.r[0] = (int) &e;
				regs.r[1] = flags;
				regs.r[2] = (int) Task_GetName();
				regs.r[3] = (int) Task.m_pTaskSpriteName;
				regs.r[4] = (int) Task.m_pTaskSprites;
				regs.r[5] = 0;
				_kernel_swi(0x600df, &regs, &regs);
			}
		}
	}
	else
	{
		regs.r[5] = 0;
		_kernel_swi(0x600df, &regs, &regs);
	}

	return;
}

const _kernel_oserror* Task_ReportOSError(const _kernel_oserror* e)
{
	_kernel_swi_regs regs;

	// Report message if there is an error
	if (!e) return e;

	regs.r[0] = (int) Msg_ErrorLookup(e);
	regs.r[1] = 0x501;
	regs.r[2] = (int) Task_GetName();
	regs.r[3] = (int) Task.m_pTaskSpriteName;
	regs.r[4] = (int) Task.m_pTaskSprites;
	regs.r[5] = 0;
	_kernel_swi(0x600df, &regs, &regs);

	return e;
}

void Task_NoError(const _kernel_oserror* e)
{
	_kernel_swi_regs regs;

	// Report message if there is an error
	if (!e) return;

//	regs.r[0] = (int) Msg_ErrorLookup(e);
	regs.r[0] = (int) e;
	regs.r[1] = 0x501;
	regs.r[2] = (int) Task_GetName();
	regs.r[3] = (int) Task.m_pTaskSpriteName;
	regs.r[4] = (int) Task.m_pTaskSprites;
	regs.r[5] = 0;
	_kernel_swi(0x600df, &regs, &regs);

	exit(EXIT_FAILURE);
}

//---------------------------------------------------------------------------------------------------------------------------
//- Message reply -----------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------------------

const _kernel_oserror* Task_SendMsg(Msg* pmsg, HTask task)
{
	_kernel_swi_regs regs;

	if (pmsg->hdr.size % 4)
		pmsg->hdr.size -= (pmsg->hdr.size % 4) - 4;

	// Wimp_SendMessage
	regs.r[0] = EEvent_Message;
	regs.r[1] = (int) pmsg;
	regs.r[2] = task;
	return _kernel_swi(0x0600e7, &regs, &regs);
}

const _kernel_oserror* Task_WSendMsg(Msg* pmsg, HWind w, HIcon i)
{
	_kernel_swi_regs regs;

	if (pmsg->hdr.size % 4)
		pmsg->hdr.size -= (pmsg->hdr.size % 4) - 4;

	// Wimp_SendMessage
	regs.r[0] = EEvent_Message;
	regs.r[1] = (int) pmsg;
	regs.r[2] = w;
	regs.r[3] = i;
	return _kernel_swi(0x0600e7, &regs, &regs);
}

const _kernel_oserror* Task_PostMsg(Msg* pmsg, HTask task)
{
	_kernel_swi_regs regs;

	if (pmsg->hdr.size % 4)
		pmsg->hdr.size -= (pmsg->hdr.size % 4) - 4;

	// Wimp_SendMessage
	regs.r[0] = EEvent_MessageWantAck;
	regs.r[1] = (int) pmsg;
	regs.r[2] = task;
	return _kernel_swi(0x0600e7, &regs, &regs);
}

const _kernel_oserror* Task_WPostMsg(Msg* pmsg, HWind w, HIcon i)
{
	_kernel_swi_regs regs;

	if (pmsg->hdr.size % 4)
		pmsg->hdr.size -= (pmsg->hdr.size % 4) - 4;

	// Wimp_SendMessage
	regs.r[0] = EEvent_MessageWantAck;
	regs.r[1] = (int) pmsg;
	regs.r[2] = w;
	regs.r[3] = i;
	return _kernel_swi(0x0600e7, &regs, &regs);
}

//
// Send an acknowledge
//

const _kernel_oserror* Task_AcknowledgeMsg(const Msg* pmsg)
{
	_kernel_swi_regs regs;
	_kernel_oserror* err;
	Msg_Hdr hdr;
	Msg* preply = (Msg*) pmsg;

	hdr = pmsg->hdr;
	preply->hdr.ref = hdr.id;

	// Wimp_SendMessage
	regs.r[0] = EEvent_MessageAck;
	regs.r[1] = (int) pmsg;
	regs.r[2] = hdr.task;
	err= _kernel_swi(0x0600e7, &regs, &regs);

	preply->hdr = hdr;

	return err;
}

//
// Send an help reply
//

const _kernel_oserror* Task_HelpReply(const Msg_HelpRequest* pmsg, const char* phelp)
{
	_kernel_swi_regs regs;
	Msg_HelpReply h;
	int len;

	h.hdr = pmsg->hdr;
	h.hdr.action = EMsg_HelpReply;
	h.hdr.ref = pmsg->hdr.id;
	snprintf(h.text, sizeof(h.text), "%s", phelp);
	len = strlen(h.text);
	h.hdr.size = sizeof(Msg_Hdr) + len + 1;
	if (h.hdr.size % 4)
		h.hdr.size -= (h.hdr.size % 4) - 4;

	// Wimp_SendMessage
	regs.r[0] = EEvent_Message;
	regs.r[1] = (int) &h;
	regs.r[2] = h.hdr.task;
	return _kernel_swi(0x0600e7, &regs, &regs);
}

//
// Send an help reply
//

bool Task_HelpReply_FromMenu(const Msg_HelpRequest* pmsg, const char* ptkitem, const char* ptksub)
{
	_kernel_swi_regs regs;
	Msg_HelpReply h;
	int len;
	const char* pitem = ptkitem ? Msg_Lookup(ptkitem) : NULL;
	const char* psub = ptksub ? Msg_Lookup(ptksub) : NULL;

	h.hdr = pmsg->hdr;
	h.hdr.action = EMsg_HelpReply;
	h.hdr.ref = pmsg->hdr.id;
	if (pitem)
	{
		// Main help text found?
		if (!strcmp(pitem, ptkitem))
			return false;

		if (psub)
			snprintf(h.text, sizeof(h.text), "%s %s.|M%s %s."
			       , Msg_Lookup("HlpMenu"), pitem, Msg_Lookup("HlpSubMenu"), psub);
		else
			snprintf(h.text, sizeof(h.text), "%s %s."
			       , Msg_Lookup("HlpMenu"), pitem);
	}
	else
	{
		snprintf(h.text, sizeof(h.text), "%s %s."
		       , Msg_Lookup("HlpSubMenu"), psub);
	}
	len = strlen(h.text);
	h.hdr.size = sizeof(Msg_Hdr) + len + 1;
	if (h.hdr.size % 4)
		h.hdr.size -= (h.hdr.size % 4) - 4;

	// Wimp_SendMessage
	regs.r[0] = EEvent_Message;
	regs.r[1] = (int) &h;
	regs.r[2] = h.hdr.task;
	_kernel_swi(0x0600e7, &regs, &regs);

	return true;
}

JobList* Task_GetJobList(void)
{
	return Task.m_pJobList;
}

//---------------------------------------------------------------------------------------------------------------------------
//- Clipboard Management ----------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------------------

void Clipboard_UpdateUI(void)
{
	Event e;

	e.Type = EEvent_ClipboardUpdate;
	e.pData = NULL;

	Task_ProcessEvent(&e);
}

void Clipboard_Claim(const ClipboardHandler* pHandler, void* pHandlerData)
{
	_kernel_swi_regs regs;
	Msg_ClaimEntity  msg;

	// Release actual content of the clipboard
	Clipboard_Clear();

	// Send Message_ClaimEntity
	msg.hdr.size = 24;
	msg.hdr.ref = 0;
	msg.hdr.action = EMsg_ClaimEntity;
	msg.flags = EEntity_Clipboard; // claim clipboard

	// Wimp_SendMessage
	regs.r[0] = EEvent_Message;
	regs.r[1] = (int) &msg;
	regs.r[2] = 0;
	_kernel_swi(0x0600e7, &regs, &regs);

	Task.m_pClipboardHandler = pHandler;
	Task.m_pClipboardHandlerData = pHandlerData;

	Task.m_pClipboardContents = Task.m_pClipboardHandler->m_pFillContents(pHandlerData);

	Clipboard_UpdateUI();
}

void Clipboard_Release(void)
{
	Msg_ClaimEntity  msg;
	Msg_DataRequest* pmsg;

	if (Task.m_pClipboardHandler == NULL) return;

	// Send Message_ReleaseEntity
	msg.hdr.size = 24;
	msg.hdr.ref = 0;
	msg.hdr.action = EMsg_ReleaseEntity;
	msg.flags = EEntity_Clipboard; // release clipboard

	pmsg = (Msg_DataRequest*) Task_PostAndWaitForReply((Msg*) &msg, (HTask) 0);

	if (pmsg
	&&  (pmsg->hdr.action == EMsg_DataRequest)
	&&  (pmsg->flags & 0x4))
	{
		// Ensure list is limited
		pmsg->types[53] = -1;

		XFer_Send(        pmsg->hdr.id
				, pmsg->hdr.task
				, pmsg->pos.w
				, pmsg->pos.i
				, pmsg->pos.pt.x
				, pmsg->pos.pt.y
				, 0
				, Task.m_pClipboardHandler->m_pSetFileType(Task.m_pClipboardContents, pmsg->types)
				, "Clipboard"
				, Task.m_pClipboardHandler->m_pTransferContents
				, NULL
				, NULL
				, NULL
				, NULL
				, Task.m_pClipboardHandler->m_bAsFileOnly
				, Task.m_pClipboardContents
				);
	}

	Clipboard_Clear();
}

void Clipboard_Clear(void)
{
	if (Task.m_pClipboardHandler == NULL) return;

	// Release data
	if (Task.m_pClipboardContents != NULL)
		Task.m_pClipboardHandler->m_pFreeContents(Task.m_pClipboardContents);

	Task.m_pClipboardHandler = NULL;
	Task.m_pClipboardHandlerData = NULL;
	Task.m_pClipboardContents = NULL;
}

int Clipboard_Paste(HWind w, int handle, int x, int y, const file_type* file_types)
{
	_kernel_swi_regs regs;
	Msg_DataRequest  msg;

	msg.hdr.size = 44;
	msg.hdr.ref = 0;
	msg.hdr.action = EMsg_DataRequest;
	msg.pos.w = w;
	msg.pos.i = handle;
	msg.pos.pt.x = x;
	msg.pos.pt.y = y;
	msg.flags = 0x04;
	if (file_types)
	{
		int i;
		for (i = 0; file_types[i] != -1; i++)
		{
			msg.types[i] = file_types[i];
			msg.hdr.size += 4;
		}
		msg.types[i] = -1;
	}

	// Wimp_SendMessage
	regs.r[0] = EEvent_MessageWantAck;
	regs.r[1] = (int) &msg;
	regs.r[2] = 0;
	_kernel_swi(0x0600e7, &regs, &regs);

	return msg.hdr.id;
}

bool Clipboard_AllowPaste(const file_type* file_types, bool send)
{
	int type, i;

	if (Task.m_pClipboardHandler)
	{
		Task.m_bClipboardAllowPaste = false;

		type = Task.m_pClipboardHandler->m_pSetFileType(Task.m_pClipboardContents, file_types);

		for (i = 0; file_types[i] != -1; i++)
		{
			if (file_types[i] == type)
				Task.m_bClipboardAllowPaste = true;
		}
	}
	else if (send)
	{
		Msg_DataRequest msg;
		Msg_FileData*   rcv;

		Task.m_bClipboardAllowPaste = false;

		msg.hdr.size = 44;
		msg.hdr.ref = 0;
		msg.hdr.action = EMsg_DataRequest;
		msg.pos.w = Windows_GetAny();
		msg.pos.i = 0;
		msg.pos.pt.x = 0;
		msg.pos.pt.y = 0;
		msg.flags = 0x04;
		for (i = 0; file_types[i] != -1; i++)
		{
			msg.types[i] = file_types[i];
			msg.hdr.size += 4;
		}
		msg.types[i] = -1;

		rcv = (Msg_FileData*) Task_PostAndWaitForReply((Msg*) &msg, (HTask) 0);

		if (rcv && (rcv->hdr.action == EMsg_DataSave))
		{
			for (i = 0; file_types[i] != -1; i++)
			{
				if (file_types[i] == rcv->type)
					Task.m_bClipboardAllowPaste = true;
			}
		}
	}

	return Task.m_bClipboardAllowPaste;
}

void Clipboard_ClaimCaret(void)
{
	_kernel_swi_regs regs;
	Msg_ClaimEntity  msg;

	if (Task.m_bCaretOwner) return;

	msg.hdr.size = 24;
	msg.hdr.ref = 0;
	msg.hdr.action = EMsg_ClaimEntity;
	msg.flags = 0x1;

	// Wimp_SendMessage
	regs.r[0] = EEvent_Message;
	regs.r[1] = (int) &msg;
	regs.r[2] = 0;
	_kernel_swi(0x0600e7, &regs, &regs);
}

void Clipboard_ClaimSelection(void)
{
	_kernel_swi_regs regs;
	Msg_ClaimEntity  msg;

	if (Task.m_bCaretOwner) return;

	msg.hdr.size = 24;
	msg.hdr.ref = 0;
	msg.hdr.action = EMsg_ClaimEntity;
	msg.flags = 0x2;

	// Wimp_SendMessage
	regs.r[0] = EEvent_Message;
	regs.r[1] = (int) &msg;
	regs.r[2] = 0;
	_kernel_swi(0x0600e7, &regs, &regs);
}

//---------------------------------------------------------------------------------------------------------------------------
//- Misc --------------------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------------------

void Task_WimpProcessKey(int code)
{
	_kernel_swi_regs regs;

	regs.r[0] = code;
	_kernel_swi(0x600dc, &regs, &regs);
}

void KeyGrabber_Activate(void)
{
	CCaret old_caret;

	if (Task.m_KeyGrabberwindow == HWind_None)
	{
		CTemplate* volatile t = NULL;

		try
		{
			t = throw_Templates_Blank(0, 0x80000150, 0, 0);
			t->window->o.o.cvt.box.x0 = -104;
			t->window->o.o.cvt.box.x1 = -100;
			Task.m_KeyGrabberwindow = throw_Window_Create(t, NULL);
		}
		catch
		{
			Templates_Remove(t);
			Task_ReportException();
			return;
		}
		catch_end
	}

	Window_OpenBehind(Task.m_KeyGrabberwindow, HWind_None);

	Caret_Get(&old_caret);

	if (Task.m_TaskId != Window_GetTask(old_caret.pos.w, old_caret.pos.i))
		Caret_SetInvisible(Task.m_KeyGrabberwindow);
	else
		Caret_SetInvisible(old_caret.pos.w);
}

void KeyGrabber_Deactivate(void)
{
	if (Task.m_KeyGrabberwindow != HWind_None)
		Window_Close(Task.m_KeyGrabberwindow);
}
