#include "WimpLib:Window.h"

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

#include "swis.h"

#include "WimpLib:Coords.h"
#include "WimpLib:Exception.h"
#include "WimpLib:EventSrc.h"
#include "WimpLib:Keyboard.h"
#include "WimpLib:List.h"
#include "WimpLib:mem.h"
#include "WimpLib:Message.h"
#include "WimpLib:PtrArray.h"
#include "WimpLib:Task.h"
#include "WimpLib:Template.h"
#include "WimpLib:Timer.h"
#include "WimpLib:Windows.h"
#include "WimpLib:Utils.h"

static List* TheWindows = NULL;

typedef struct win_info
{
	HWind            id;
	CTemplate*       t;
	EventSource      m_Handlers;
	struct win_info* owner;
	PtrArray*        Panes;
	const CWindOpen* pOpen;
	char             help[12];
} win_info;

void throw_Windows_Windows(void)
{
	TheWindows = New_List();
}

void Windows_NotWindows(void)
{
	Delete_List(TheWindows);
	TheWindows = NULL;
}

static EListenerAction Windows_ProcessEvent(win_info* info, const Event* e)
{
	if (EventSource_ProcessEvent(&info->m_Handlers, e))
		return EListenerAction_StopEvent;

	switch(e->Type)
	{
		case EEvent_WindowOpen:
		case EEvent_WindowScroll:
		{
			int i;
			CWindOpen* po = (CWindOpen*) e->pData;
			Event      ep;
			CWindOpen  st;
			win_info*  ppane = NULL;
			HWind      behind;
			HWind      after;

			// Open panes first, to avoid flashing
			ep.Type = EEvent_WindowOpen;
			ep.pData = &st;

			info->pOpen = po;
			behind = po->behind;
			after = behind;

			for (i = 0; i < PtrArray_Count(info->Panes); i++)
			{
				ppane = PtrArray_Get(info->Panes, i);
				st = Window_GetState(ppane->id).o;
				st.behind = after;
				after = ppane->id;
				Windows_ProcessEvent(ppane, &ep);
			}

			// Open window after pane
			if (ppane)
			{
				po->behind = after;
				st = *po;
			}

			_swix(Wimp_OpenWindow, _IN(1), po);

			// If window did not open at expected pos, replot panes
			if (ppane)
			{
				po->cvt = Window_GetPosInfo(info->id);

				if (memcmp(&po->cvt, &st.cvt, sizeof(CWindCvt)))
				{
					after = behind;

					for (i = 0; i < PtrArray_Count(info->Panes); i++)
					{
						ppane = PtrArray_Get(info->Panes, i);
						st = Window_GetState(ppane->id).o;
						st.behind = after;
						after = ppane->id;
						Windows_ProcessEvent(ppane, &ep);
					}

					// Open window after pane
					po->behind = after;

					_swix(Wimp_OpenWindow, _IN(1), po);
				}

			}

			info->pOpen = NULL;

			return EListenerAction_StopEvent;
		}
		break;
		case EEvent_WindowClose:
		{
			int i;
			Event ep;
			win_info* ppane;

			// Close panes
			ep.Type = EEvent_WindowClose;

			for (i = 0; i < PtrArray_Count(info->Panes); i++)
			{
				ppane = PtrArray_Get(info->Panes, i);
				ep.pData = &(ppane->id);
				Windows_ProcessEvent(ppane, &ep);
			}

			// Close window
			_swix(Wimp_CloseWindow, _IN(1), e->pData);

			return EListenerAction_StopEvent;
		}
		break;
		case EEvent_Message:
		case EEvent_MessageWantAck:
		{
			switch(((Msg*) e->pData)->hdr.action)
			{
				case EMsg_HelpRequest:
				{
					const Msg_HelpRequest* msg = e->pData;
					const char* str;

					if (info->help[0])
					{
						char token[15];
						snprintf(token, sizeof(token), "%s%03d", info->help, msg->m.i);
						str = Msg_Lookup(token);
						if (strcmp(str, token))
						{
							Task_HelpReply(msg, str);
							return EListenerAction_StopEvent;
						}
					}

					// Eventually get default help from parent
					while (info)
					{
						if (info->help[0])
						{
							str = Msg_Lookup(info->help);
							if (strcmp(str, info->help))
							{
								Task_HelpReply(msg, str);
								return EListenerAction_StopEvent;
							}
						}
						info = info->owner;
					}
					// If Help request message not processed,
					// return as processed cf help on a menu code.
					return EListenerAction_StopEvent;
				}
				break;
			}
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

/*
 * This call tries dispatch an event to the correct window handler
 * return true if the event was processed
 */

HWind Windows_GetAny(void)
{
	win_info* info = List_Get(TheWindows, 0);

	if (info)
		return info->id;
	else
		return HWind_IconBar;
}

static win_info* Windows_Find(HWind id)
{
	ListNode* pNode = NULL;
	win_info* info;

	while ((pNode = List_GetSuccessor(TheWindows, pNode)) != NULL)
	{
		info = List_GetNodeData(TheWindows, pNode);

		if (info->id == id)
			return info;
	}

	return NULL;
}

bool Window_ProcessEvent(HWind id, const Event* e)
{
	win_info* info = Windows_Find(id);

	if (info) return Windows_ProcessEvent(info, e);

	return false;
}

/*-----------------------*
 *- Window registration -*
 *-----------------------*/

static win_info* throw_Windows_AddWindow(HWind id, CTemplate* t, const char* phelpid)
{
	win_info* pinfo = throw_mem_calloc(sizeof(*pinfo), 1);

	try
	{
		pinfo->id = id;
		pinfo->t = t;
		pinfo->owner = NULL;
		pinfo->pOpen = NULL;
		snprintf(pinfo->help, sizeof(pinfo->help), "%s", phelpid ? phelpid : &nil);
		pinfo->Panes = New_PtrArray(4);
		throw_EventSource_EventSource(&pinfo->m_Handlers);
		List_InsertBefore(TheWindows, NULL, pinfo);
	}
	catch
	{
		EventSource_NotEventSource(&pinfo->m_Handlers);
		Delete_PtrArray(pinfo->Panes);
		mem_free(pinfo);
		throw_current();
	}
	catch_end

	return pinfo;
}

static void Windows_RemoveWindow(HWind id)
{
	win_info* info;
	ListNode* pNode = NULL;

	while((pNode = List_GetSuccessor(TheWindows, pNode)) != NULL)
	{
		info = List_GetNodeData(TheWindows, pNode);

		if (info->id == id)
		{
			EventSource_NotEventSource(&info->m_Handlers);
			// detach panes from us
			for (int i = PtrArray_Count(info->Panes) - 1; i >= 0; i--)
			{
				win_info* ppane = PtrArray_Get(info->Panes, i);
				ppane->owner = NULL;
			}
			Delete_PtrArray(info->Panes);

			if (info->owner)
				PtrArray_Remove(info->owner->Panes, PtrArray_Find(info->owner->Panes, 0, info));

			List_RemoveNode(TheWindows, pNode);
			if (info->t != NULL) Templates_Remove(info->t);
			mem_free(info);
			break;
		}
	}
}

void throw_Window_AddPane(HWind id, HWind pane)
{
	win_info* pinfo = Windows_Find(id);
	win_info* ppane = Windows_Find(pane);
	CWindState state = Window_GetState(id);

	throw_assert(pinfo || ppane);

	PtrArray_Insert(pinfo->Panes, -1, ppane);

	ppane->owner = pinfo;

	// Force a refresh to make the pane appear correctly
	if (state.flags & EWind_IsOpen)
		Window_OpenAt(id, &state.o);
}

void Window_RemovePane(HWind id, HWind pane)
{
	win_info* pinfo = Windows_Find(id);
	win_info* ppane = Windows_Find(pane);

	throw_assert(pinfo || ppane);

	PtrArray_Remove(pinfo->Panes, PtrArray_Find(pinfo->Panes, 0, ppane));
	ppane->owner = NULL;
}

void Window_SetHelp(HWind w, const char* phelpid)
{
	win_info* pinfo = Windows_Find(w);

	throw_assert(pinfo);
	snprintf(pinfo->help, sizeof(pinfo->help), "%s", phelpid ? phelpid : &nil);
}

CWindOpen Window_GetOwnerInfo(HWind id)
{
	win_info* pinfo = Windows_Find(id);

	throw_assert(pinfo && pinfo->owner);
	if (pinfo->owner->pOpen) return *pinfo->owner->pOpen;

	return Window_GetState(pinfo->owner->id).o;
}

/*
 * Registers a user defined event handler for a window
 */

void throw_Window_RegisterEventHandler(HWind id, Listener_FOnEvent pEventHandler, void *pHandle, bool bFirst)
{
	win_info* info = Windows_Find(id);

	if (!info) throw_string("Window not found for handler %p", pEventHandler);

	throw_EventSource_AddGlobalListener(&info->m_Handlers, pEventHandler, pHandle, bFirst);
}

/*
 * DeRegisters a user defined event handler for a window
 */

void Window_DeRegisterEventHandler(HWind id, Listener_FOnEvent pEventHandler, void *pHandle)
{
	win_info* info = Windows_Find(id);

	if (info)
		EventSource_RemoveGlobalListener(&info->m_Handlers, pEventHandler, pHandle);
}

static int scroll_time = 0;

/*
 * Create a window from an in memory definition
 */

HWind throw_Window_CreateCustom(CTemplate* t, CSpriteArea* pSpriteArea, const char* phelpid)
{
	CWind* pwindow = Template_GetWindow(t);
	HWind  w;
	CIcon* pi;
	int i;

	pi = (CIcon*) (pwindow + 1);
	for (i = 0; i < pwindow->nicons; i++, pi++)
	{
		if ((pi->flags & 0x0103) == 0x0102)
			pi->data.is.sprite_area = pSpriteArea;
	}

	pwindow->sprite_area = pSpriteArea;

	throw_os(_swix(Wimp_CreateWindow, _IN(1)|_OUT(0), &pwindow->o.o.cvt, &w));

	try
	{
		throw_Windows_AddWindow(w, t, phelpid);
	}
	catch
	{
		_swi(Wimp_DeleteWindow, _IN(1), w);
		throw_current();
	}
	catch_end

	return w;
}

/*
 * Create a window from an in memory definition
 */

HWind throw_Window_Create(CTemplate* t, const char* phelpid)
{
	return throw_Window_CreateCustom(t, Task_GetSpriteArea(), phelpid);
}

/*
 * Create a window from a template
 */

HWind throw_Window_CreateFrom(const char* pid, const char* phelpid)
{
	CTemplate* t = throw_Templates_Copy(pid);
	HWind id = HWind_None;

	try
	{
		id = throw_Window_Create(t, phelpid);
	}
	catch
	{
		Templates_Remove(t);
		throw_current();
	}
	catch_end

	return id;
}

void Window_Delete(HWind id)
{
	if (id == HWind_None) return;

	Windows_RemoveWindow(id);

	_swix(Wimp_CloseWindow, _IN(1), &id);

	throw_runtimeos(_swix(Wimp_DeleteWindow, _IN(1), &id));
}

void Window_Open(HWind id)
{
	CWindState state;

	state.o.w = id;
	throw_runtimeos(_swix(Wimp_GetWindowState, _IN(1), &state));

	state.o.behind = HWind_None;

	Window_OpenAt(id, &state.o);
}

void Window_OpenBehind(HWind id, HWind behind)
{
	CWindState state;

	state.o.w = id;
	throw_runtimeos(_swix(Wimp_GetWindowState, _IN(1), &state));

	state.o.behind = behind;

	Window_OpenAt(id, &state.o);
}

void Window_OpenAt(HWind id, CWindOpen* o)
{
	Event e;

	o->w = id;

	e.Type = EEvent_WindowOpen;
	e.pData = o;

	Task_ProcessEvent(&e);
}

bool Window_ScrollToRect(CWindOpen* o, const CRect* prct)
{
	CSize size;
	CSize s = o->cvt.s;

	size.cx = o->cvt.box.x1 - o->cvt.box.x0;
	size.cy = o->cvt.box.y1 - o->cvt.box.y0;

	if ((o->cvt.s.cx + size.cx) < prct->x1)
		o->cvt.s.cx = prct->x1 - size.cx;
	if (o->cvt.s.cx > prct->x0)
		o->cvt.s.cx = prct->x0;

	if ((o->cvt.s.cy - size.cy) > prct->y0)
		o->cvt.s.cy = prct->y0 + size.cy;
	if (o->cvt.s.cy < prct->y1)
		o->cvt.s.cy = prct->y1;

	return (o->cvt.s.cx != s.cx) || (o->cvt.s.cy != s.cy);
}

void Window_OpenPane(CWindOpen* o, const CWindOpen* mo, const CRect* pbox, unsigned int flags)
{
	CWindRedraw pane;
	CWindState  state;
	CRect box;

	// Convert box to external coords
	box = RectToScreen(pbox, &mo->cvt);

	// Get window work area and outline as work area given in parameter does not correspond to outline
	state.o.w = o->w;
	throw_runtimeos(_swix(Wimp_GetWindowState, _IN(1), &state));

	if (!(state.flags & EWind_IsOpen))
	{
		// Convert box to external coords
		o->cvt.box = box;
		o->cvt.box.y0 += 40;
		o->cvt.box.x1 -= 40;
		// Open at +- correct pos
		Window_WimpOpen(o);
		throw_runtimeos(_swix(Wimp_GetWindowState, _IN(1), &state));
	}

	pane.w = o->w;
	throw_runtimeos(_swix(Wimp_GetWindowOutline, _IN(1), &pane));

	// Update work area to fit outline in rect
	if (flags & pane_fit_hgadgets)
	{
		o->cvt.box.x0 = box.x0 + state.o.cvt.box.x0 - pane.cvt.box.x0;
		o->cvt.box.x1 = box.x1 + state.o.cvt.box.x1 - pane.cvt.box.x1;
	}
	else
	{
		o->cvt.box.x0 = box.x0;
		o->cvt.box.x1 = box.x1;
	}
	if (flags & pane_fit_vgadgets)
	{
		o->cvt.box.y0 = box.y0 + state.o.cvt.box.y0 - pane.cvt.box.y0;
		o->cvt.box.y1 = box.y1 + state.o.cvt.box.y1 - pane.cvt.box.y1;
	}
	else
	{
		o->cvt.box.y0 = box.y0;
		o->cvt.box.y1 = box.y1;
	}

	// Open pane on front of master window
/*	o->behind = mo->behind;
*/
}

bool Window_IsOpen(HWind id)
{
	CWindState state;

	state.o.w = id;
	if (_swix(Wimp_GetWindowState, _IN(1), &state)) return false;

	if (state.flags & EWind_IsOpen) return true;

	return false;
}

void Window_WimpClose(HWind id)
{
	_swix(Wimp_CloseWindow, _IN(1), &id);
}

void Window_Close(HWind id)
{
	Event e;

	e.Type = EEvent_WindowClose;
	e.pData = &id;

	Task_ProcessEvent(&e);
}

CWindCvt Window_GetPosInfo(HWind id)
{
	CWindState state;

	state.o.w = id;
	throw_runtimeos(_swix(Wimp_GetWindowState, _IN(1), &state));

	return state.o.cvt;
}

CWindState Window_GetState(HWind id)
{
	CWindState state;

	state.o.w = id;
	throw_runtimeos(_swix(Wimp_GetWindowState, _IN(1), &state));

	return state;
}

CWind Window_GetInfo(HWind id)
{
	CWind info;

	info.o.o.w = id;
	throw_runtimeos(_swix(Wimp_GetWindowInfo, _IN(1), 1 | ((int) &info)));

	return info;
}

CRect Window_GetOutline(HWind id)
{
	struct
	{
		HWind w;
		CRect pos;
	} outline;

	outline.w = id;
	throw_runtimeos(_swix(Wimp_GetWindowOutline, _IN(1), &outline));

	return outline.pos;
}

CRect Window_GetExpectedOutline(CWindOpen* po)
{
	CWindState  state;
	struct
	{
		HWind w;
		CRect pos;
	} outline;

	// Get window work area and outline as work area given in parameter does not correspond to outline
	state.o.w = po->w;
	throw_runtimeos(_swix(Wimp_GetWindowState, _IN(1), &state));

	if (!(state.flags & EWind_IsOpen))
	{
		// Open at correct pos
		Window_WimpOpen(po);
		// Retrive correct pos
		throw_runtimeos(_swix(Wimp_GetWindowState, _IN(1), &state));
	}

	outline.w = po->w;
	throw_runtimeos(_swix(Wimp_GetWindowOutline, _IN(1), &outline));

	// Adapt outline to new position (in case of resize
	outline.pos.x0 += po->cvt.box.x0 - state.o.cvt.box.x0;
	outline.pos.x1 += po->cvt.box.x1 - state.o.cvt.box.x1;
	outline.pos.y0 += po->cvt.box.y0 - state.o.cvt.box.y0;
	outline.pos.y1 += po->cvt.box.y1 - state.o.cvt.box.y1;

	return outline.pos;
}

void Window_FitInExtent(HWind id, CWindOpen* po)
{
	CRect ex = Window_GetInfo(id).ex;

	// Reopen the window
	if ((po->cvt.box.x1 - po->cvt.box.x0) > (ex.x1 - ex.x0))
		po->cvt.box.x1 = ex.x1 - ex.x0 + po->cvt.box.x0;
	if ((po->cvt.box.y1 - po->cvt.box.y0) > (ex.y1 - ex.y0))
		po->cvt.box.y0 = ex.y0 - ex.y1 + po->cvt.box.y1;
}

void Window_SetExtent(HWind id, const CRect* box, bool fix_display)
{
	CWind  info = Window_GetInfo(id);
	CCaret old_caret;
	CRect  rct = *box;
	bool   bOpen = (info.o.flags & EWind_IsOpen) != 0;

	// Check if change is required
	if (!memcmp(box, &info.ex, sizeof(CRect)))
		return;

	RoundUpRect(&rct);

	throw_runtimeos(_swix(Wimp_SetExtent, _INR(0,1), id, &rct));

	// Visible part of window can become greater than extent
	if (fix_display)
	{
		if (!bOpen) info.o.o.behind = -3;

		// Save caret pos, because we will close the window
		Caret_Get(&old_caret);

		// Reopen the window
		Window_FitInExtent(id, &info.o.o);
		Window_OpenAt(id, &info.o.o);

		// Restore the caret if required
		if (old_caret.pos.w == id)
			Caret_Set(&old_caret);

		// Close directly, circumvent event handlers here
		if (!bOpen) Window_WimpClose(id);
	}
}

void Window_SetTitle(HWind id, const char* pformat, ...)
{
	va_list arg;
	va_start(arg, pformat);
	Window_VSetTitle(id, pformat, arg);
	va_end(arg);
}

void Window_VSetTitle(HWind id, const char* pformat, va_list arg)
{
	CWind info;
	CWindRedraw pane;
	char* pTitle;

	info = Window_GetInfo(id);

	if (!(info.title_flags & 0x0100)) return;

	pTitle = VSPrintf(pformat, arg);

	if (!strcmp(info.title_data.it.buffer, pTitle)) return;

	snprintf(info.title_data.it.buffer, info.title_data.it.buf_size, "%s", pTitle);

	if (Window_IsOpen(id))
	{
		if (Task_GetWimpVersion() >= 380)
			_swix(Wimp_ForceRedraw, _INR(0,2), id, 0x4B534154,3);
		else
		{
			pane.w = id;
			if (_swix(Wimp_GetWindowOutline, _IN(1), &pane)) return;

			// Update work area to fit outline in rect
			pane.cvt.box.y0 = info.o.o.cvt.box.y1;

			Window_Invalidate(HWind_None, &pane.cvt.box);
		}
	}
}

bool Window_ScrollIfMouseOnBorder(HWind id, int dx, int dy)
{
	const Mouse* m = Mouse_Get();
	CWindOpen o = Window_GetState(id).o;
	int x = 0;
	int y = 0;
	int step = 0;
	int time = Timer_GetTime();

	if (time < scroll_time) return false;

	if ((m->pt.x >= o.cvt.box.x0) && (m->pt.x < o.cvt.box.x0 + 10)) x = -dx;
	if ((m->pt.y >= o.cvt.box.y0) && (m->pt.y < o.cvt.box.y0 + 10)) y = -dy;
	if ((m->pt.x <= o.cvt.box.x1) && (m->pt.x > o.cvt.box.x1 - 10)) x = dx;
	if ((m->pt.y <= o.cvt.box.y1) && (m->pt.y > o.cvt.box.y1 - 10)) y = dy;

	if (x || y)
	{
		o.cvt.s.cx += x;
		o.cvt.s.cy += y;

		Window_OpenAt(id, &o);

		step = Keyboard_GetRepeatRate();
		if (step < 5) step = 5;
		if (step > 25) step = 25;
		scroll_time = time + step;

		return true;
	}

	return false;
}

void Window_Invalidate(HWind w, const CRect* pbox)
{
	CRect rct = *pbox;

	RoundUpRect(&rct);

	throw_runtimeos(_swix(Wimp_ForceRedraw, _INR(0,4), w, rct.x0, rct.y0, rct.x1, rct.y1));
}

void Window_Update(HWind w, const CRect* pbox, Window_Drawer pdraw, void* handle)
{
	CWindRedraw r;
	int more;

	r.w = w;
	r.cvt.box = *pbox;
	RoundUpRect(&r.cvt.box);

	throw_runtimeos(_swix(Wimp_UpdateWindow, _IN(1)|_OUT(0), &r, &more));

	while (more)
	{
		if (pdraw) pdraw(handle, &r);

		throw_runtimeos(_swix(Wimp_GetRectangle, _IN(1)|_OUT(0), &r, &more));
	}
}

void Window_Redraw(HWind w, Window_Drawer pdraw, void* handle)
{
	CWindRedraw r;
	int more;

	r.w = w;

	throw_runtimeos(_swix(Wimp_RedrawWindow, _IN(1)|_OUT(0), &r, &more));

	while (more)
	{
		if (pdraw) pdraw(handle, &r);

		throw_runtimeos(_swix(Wimp_GetRectangle, _IN(1)|_OUT(0), &r, &more));
	}
}

void Window_WimpOpen(CWindOpen* o)
{
	throw_runtimeos(_swix(Wimp_OpenWindow, _IN(1), o));
}

HTask Window_GetTask(HWind w, HIcon i)
{
	HTask task;
	Msg_Hdr msg;

	msg.size = sizeof(Msg_Hdr);
	msg.ref = 0;
	msg.action = 0;

	// Send Ack with no ref
	if (_swix(Wimp_SendMessage, _INR(0,3)|_OUT(2), 19, &msg, w, i, &task))
		return HTask_None;

	return task;
}

HIcon throw_Icon_Create(HWind id, CIcon* picon)
{
	HIcon iid;
	struct
	{
		HWind w;
		CIcon i;
	} icon;

	icon.w = id;
	icon.i = *picon;

	throw_os(_swix(Wimp_CreateIcon, _INR(0,1)|_OUT(0), 0, &icon, &iid));

	return iid;
}

void Icon_Delete(HWind id, HIcon icon)
{
	int i[2];

	if (icon == HIcon_None) return;

	i[0] = id;
	i[1] = icon;

	throw_runtimeos(_swix(Wimp_DeleteIcon, _IN(1), &i[0]));
}

bool Icon_GetInfo(HWind id, HIcon icon, CIcon* picon)
{
	struct
	{
		HWind w;
		HIcon i;
		CIcon def;
	} ricon;

	ricon.w = id;
	ricon.i = icon;
	if (_swix(Wimp_GetIconState, _IN(1), &ricon))
		return false;

	if (picon) *picon = ricon.def;

	return ((ricon.def.flags & EIcon_Deleted) == 0);
}

void Icon_Plot(const CIcon* picon)
{
	throw_runtimeos(_swix(Wimp_PlotIcon, _IN(1), picon));
}

void Icon_Redraw(HWind id, HIcon icon)
{
	int i[4];

	i[0] = id;
	i[1] = icon;
	i[2] = 0;
	i[3] = 0;

	throw_runtimeos(_swix(Wimp_SetIconState, _IN(1), &i[0]));
}

void Icon_ForceRedraw(HWind id, HIcon icon)
{
	CIcon info;

	Icon_GetInfo(id, icon, &info);
	Window_Invalidate(id, &info.box);
}

HIcon throw_Icon_Move(HWind id, HIcon icon, const CRect* pbox)
{
	CWind info = Window_GetInfo(id);

	// bug in ResizeIcon if icon == nicons => wimp memory corrupted
	if (icon < info.nicons)
	{
		if (_swix(Wimp_ResizeIcon, _INR(0,5), id, icon, pbox->x0, pbox->y0, pbox->x1, pbox->y1))
		{
			CIcon i;

			Icon_GetInfo(id, icon, &i);
			Icon_Delete(id, icon);
			i.box = *pbox;
			return throw_Icon_Create(id, &i);
		}
	}
	else
		throw_string("Invalid Icon %d", icon);

	return icon;
}

bool Icon_SetData(HWind id, HIcon icon, const char* pdata)
{
	CIcon info;
	CCaret caret;

	Icon_GetInfo(id, icon, &info);

	if (!(info.flags & 0x0100)) return false;

	if (!pdata) pdata = &nil;
	if (!strcmp(info.data.it.buffer, pdata)) return false;

	Caret_Get(&caret);

	snprintf(info.data.it.buffer, info.data.it.buf_size, "%s", pdata);
	if((caret.pos.w == id) && (caret.pos.i == icon))
	{
		int len = strlen(info.data.it.buffer);
		if (caret.index > len) caret.index = len;
		Caret_Set(&caret);
	}
	Icon_Redraw(id, icon);

	return true;
}

void Icon_Printf(HWind id, HIcon icon, const char* pformat, ...)
{
	va_list arg;
	va_start(arg, pformat);
	Icon_SetData(id, icon, VSPrintf(pformat, arg));
	va_end(arg);
}

void Icon_VPrintf(HWind id, HIcon icon, const char* pformat, va_list arg)
{
	Icon_SetData(id, icon, VSPrintf(pformat, arg));
}

char* Icon_GetData(HWind id, HIcon icon)
{
	CIcon info;

	Icon_GetInfo(id, icon, &info);

	if (!(info.flags & 0x0100)) return NULL;

	return info.data.it.buffer;
}

void Icon_SetValidation(HWind id, HIcon icon, const char* pvalid)
{
	CIcon info;

	Icon_GetInfo(id, icon, &info);

	if ((info.flags & 0x0101) != 0x0101) return;

	if (!pvalid) pvalid = &nil;
	if (!strcmp(info.data.it.validation, pvalid)) return;

	strcpy(info.data.it.validation, pvalid);
	Icon_Redraw(id, icon);
}

void Icon_PrintfValidation(HWind id, HIcon icon, const char* pformat, ...)
{
	va_list arg;
	va_start(arg, pformat);
	Icon_VPrintfValidation(id, icon, pformat, arg);
	va_end(arg);
}

void Icon_VPrintfValidation(HWind id, HIcon icon, const char* pformat, va_list arg)
{
	CIcon info;
	char* pValid;

	Icon_GetInfo(id, icon, &info);

	if ((info.flags & 0x0101) != 0x0101) return;

	pValid = VSPrintf(pformat, arg);
	if (!strcmp(info.data.it.validation, pValid)) return;

	strcpy(info.data.it.validation, pValid);
	Icon_Redraw(id, icon);
}

void Icon_SetHighlight(HWind id, HIcon icon, bool on)
{
	int i[4];

	i[2] = (on) ? EIcon_Selected : 0;
	if (i[2] == Icon_GetState(id, icon, EIcon_Selected))
		return;

	i[0] = id;
	i[1] = icon;
	i[3] = EIcon_Selected;

	throw_runtimeos(_swix(Wimp_SetIconState, _IN(1), &i[0]));
}

void Icon_SetDimmed(HWind id, HIcon icon, bool on)
{
	int i[4];

	i[2] = (on) ? EIcon_Dimmed : 0;
	if (i[2] == Icon_GetState(id, icon, EIcon_Dimmed))
		return;

	i[0] = id;
	i[1] = icon;
	i[3] = EIcon_Dimmed;

	throw_runtimeos(_swix(Wimp_SetIconState, _IN(1), &i[0]));
}

void Icon_PushControl(HWind w, HIcon icon, int time)
{
	int timer;
	Icon_SetHighlight(w, icon, true);
	timer = Timer_GetTime();
	while (Timer_GetTime() - timer < time)
		;
	Icon_SetHighlight(w, icon, false);
}

void Icon_SetState(HWind id, HIcon icon, unsigned int state, unsigned int mask)
{
	int i[4];

	i[0] = id;
	i[1] = icon;
	i[2] = state;
	i[3] = mask;

	throw_runtimeos(_swix(Wimp_SetIconState, _IN(1), &i[0]));
}

unsigned int Icon_GetState(HWind id, HIcon icon, unsigned int mask)
{
	CIcon info;

	Icon_GetInfo(id, icon, &info);

	return (mask & (int) info.flags);
}

void Icon_GetScreenBox(HWind id, HIcon icon, CRect* box)
{
	CWindCvt cvt = Window_GetPosInfo(id);
	CIcon info;

	Icon_GetInfo(id, icon, &info);
	*box = RectToScreen(&info.box, &cvt);
}

void Icon_SetFocus(HWind id, HIcon icon, int index)
{
	CCaret caret;
	CIcon info;
	int len;

	if (icon != HIcon_None)
	{
		Icon_GetInfo(id, icon, &info);

		len = strlen(info.data.it.buffer);
		if ((index > len) || (index < 0)) index = len;
	}

	caret.pos.w = id;
	caret.pos.i = icon;
	caret.height = -1;
	caret.index = index;
	Caret_Set(&caret);
}
