/*
 * Kangaroo Punch MultiPlayer Game Server Mark II
 * Copyright (C) 2020-2021  Scott Duensing
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */


// Inspired by https://github.com/JMarlin/wsbe


#include "gui.h"
#include "widget.h"
#include "desktop.h"
#include "window.h"


typedef struct PendingEventsS {
	WidgetT        *widget;
	widgetCallback  callback;
} PendingEventsT;


int16_t   _guiMetric[METRIC_COUNT];
ColorT    _guiColor[COLOR_COUNT];
FontT    *_guiFont                 = NULL;
WidgetT  *_guiDragWidget           = NULL;
uint16_t  _guiDragOffsetX          = 0;
uint16_t  _guiDragOffsetY          = 0;
WindowT  *_guiActiveWindow         = NULL;


static DesktopT        *_guiDesktop                = NULL;
static WidgetT         *_guiLastWidgetLeft         = NULL;
static uint8_t          _guiLastWidgetLeftEvent    = MOUSE_EVENT_NONE;
static WidgetT         *_guiFocused                = NULL;
static uint8_t          _guiHasStopped             = 0;      // This starts as 0 even if the GUI hasn't been started.
static WidgetT       ***_guiDeleteList             = NULL;
static uint32_t         _guiTimerValue             = 0;
static uint32_t         _guiTimerLast              = 0;
static uint32_t         _guiTimerHalfSecond        = 0;
static uint32_t         _guiTimerSecond            = 0;
static uint32_t         _guiTimerQuarterSecondTick = 0;
static uint32_t         _guiTimerHalfSecondTick    = 0;
static uint32_t         _guiTimerSecondTick        = 0;
static uint32_t         _guiTimerQuarterSecondOn   = 0;
static uint32_t         _guiTimerHalfSecondOn      = 0;
static uint32_t         _guiTimerSecondOn          = 0;
static PendingEventsT **_guiPendingEvents          = NULL;

// Widget Magic Debug Info.  Don't forget to change MagicE!
static char *_magicDebugNames[MAGIC_COUNT] = {
	"Unknown",
	"Desktop",
	"Window",
	"Button",
	"Label",
	"Checkbox",
	"RadioButton",
	"Picture",
	"Frame",
	"Textbox",
	"Updown",
	"Listbox",
	"Terminal"
};


static void     guiDeleteList(void);
static void     guiDeleteListItem(WidgetT **widget);
static void     guiPaintBoundsGet(WidgetT *widget, RectT *pos);
static void     guiPaintChildren(WidgetT *widget);
static void     guiKeyboardChildrenProcess(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt);
static uint8_t  guiMouseChildrenProcess(WidgetT *widget, MouseT *mouse);
static WindowT *guiWindowFindTopChildrenProcess(WidgetT *widget);


void guiAttach(WidgetT *parent, WidgetT *child) {
	WidgetT *p = NULL;

	// Add us to the child list.
	child->parent = parent;
	arrput(parent->children, child);

	// If this widget is not a window or desktop, find its parent window.
	if (child->magic != MAGIC_WINDOW && child->magic != MAGIC_DESKTOP) {
		p = child;
		while (p != NULL && p->magic != MAGIC_WINDOW) {
			p = p->parent;
		}
		child->window = (WindowT *)p;
	}

	// Caluclate clipping region - this is for the surface on which the widget is drawn, not in screen space.
	if (GUI_GET_FLAG(child, WIDGET_FLAG_OWNS_SURFACE)) {
		child->clip.x = child->margin.x;
		child->clip.y = child->margin.y;
	} else {
		child->clip.x = child->pos.x;
		child->clip.y = child->pos.y;
		p = child;
		while (p != NULL) {
			child->clip.x += p->margin.x;
			child->clip.y += p->margin.y;
			p = p->parent;
		}
	}
	child->clip.w = child->pos.w - child->margin.x - child->margin.w;
	child->clip.h = child->pos.h - child->margin.y - child->margin.h;

	// If this widget does not own a surface, find one for it to draw on.
	if (!GUI_GET_FLAG(child, WIDGET_FLAG_OWNS_SURFACE)) {
		p = child;
		while (p != NULL && !GUI_GET_FLAG(p, WIDGET_FLAG_OWNS_SURFACE)) {
			// Check parent.
			p = p->parent;
		}
		child->surface = p->surface;
	}

	// New windows should be active.
	if (child->magic == MAGIC_WINDOW) {
		windowActiveSet((WindowT *)child);
	}
}


void guiComposite() {
	WidgetT *widget = (WidgetT *)_guiDesktop;
	size_t   len;
	size_t   x;

	// Repaint anyone who needs it.
	guiPaint(widget);

	surfaceSet(NULL);

	// Render us?
	if (GUI_GET_FLAG(widget, WIDGET_FLAG_OWNS_SURFACE)) {
		surfaceBlit(widget->surface, widget->pos.x, widget->pos.y);
	}

	// Now render all surface-containing children to the VBE buffer.
	len = arrlenu(widget->children);
	if (len > 0) {
		for (x=0; x<len; x++) {
			if (GUI_GET_FLAG(widget->children[x], WIDGET_FLAG_OWNS_SURFACE)) {
				surfaceBlit(widget->children[x]->surface, widget->children[x]->pos.x, widget->children[x]->pos.y);
			}
		}
	}
}


void guiDebugAreaShow(WidgetT *widget) {
	RectT r;

	guiWidgetBoundsDrawableOnScreenGet(widget, &r);
	logWrite("%s: Size %dx%d Area %dx%d TL %dx%d BR %dx%d\n",
			_magicDebugNames[widget->magic],
			widget->pos.w, widget->pos.h,
			r.w, r.h,
			widget->margin.x, widget->margin.y,
			widget->margin.w, widget->margin.h);
}


void guiDebugWidgetTreeDump(WidgetT *widget, uint16_t depth) {
	size_t  len = arrlenu(widget->children);
	size_t  x;
	char    line[256];

	for (x=0; x<depth*2; x++) line[x] = '-';
	line[x] = 0;

	logWrite("%s %s P:%dx%d-%dx%d  M:%dx%d-%dx%d  C:%dx%d-%dx%d F:%d\n",
			 line,
			 _magicDebugNames[widget->magic],
			widget->pos.x, widget->pos.y, widget->pos.w, widget->pos.h,
			widget->margin.x, widget->margin.y, widget->margin.w, widget->margin.h,
			widget->clip.x, widget->clip.y, widget->clip.w, widget->clip.h,
			widget->flags);

	// Mark all children.
	if (len > 0) {
		for (x=0; x<len; x++) {
			guiDebugWidgetTreeDump(widget->children[x], depth + 1);
		}
	}
}


void guiDelete(WidgetT **widget) {
	// Since deleting happens in widget events, it's not safe to do it
	// immediately.  Instead, we make a list of what to delete and
	// then process it before painting.

	// This may be redundant now since there is a pending event list
	// that is also handled in the paint event.

	arrput(_guiDeleteList, widget);
}


static void guiDeleteList(void) {
	WidgetT **w       = NULL;
	WindowT  *win     = NULL;

	if (_guiDeleteList) {
		while (arrlen(_guiDeleteList) > 0) {
			w = arrpop(_guiDeleteList);
			guiDeleteListItem(w);
		}

		arrfree(_guiDeleteList);
		_guiDeleteList = NULL;

		// Is the top level window no longer active?  If so, select it.
		win = guiWindowFindTop();
		if (win) {
			if (!GUI_GET_FLAG(W(win), WIDGET_FLAG_ACTIVE)) {
				windowActiveSet(win);
			}
		}
	}
}


static void guiDeleteListItem(WidgetT **widget) {
	WidgetT         *w     = *widget;
	size_t           len   =  arrlenu(w->children);
	size_t           plen  =  arrlen(w->parent != NULL ? w->parent->children : 0);
	size_t           x     =  0;
	uint8_t          nuke  =  1;
	static uint16_t  depth =  0;

	// Delete children.
	if (len > 0) {
		for (x=0; x<len; x++) {
			depth++;
			guiDeleteListItem(&w->children[x]);
			depth--;
		}
	}

	// Remove us from parent children list.
	if (depth == 0) {
		if (plen > 0) {
			for (x=0; x<plen; x++) {
				if (w == w->parent->children[x]) {
					arrdel(w->parent->children, x);
					break;
				}
			}
			// Was this the last child in the list?
			if (plen == 1) {
				// Erase the list.
				arrfree(w->parent->children);
			}
		}
	}

	// If we were involved in a mouse event, get out of it.
	if (w == _guiLastWidgetLeft) _guiLastWidgetLeft = NULL;
	if (w == _guiFocused) _guiFocused = NULL;

	// Delete us.
	if (w->delMethod != NULL) w->delMethod(&w);
	arrfree(w->children);
	if (w->surface != NULL) {
		// Only free the surface if we own it.  If it's our parent's, ignore.
		if (w->parent != NULL) {
			if (w->surface == w->parent->surface) nuke = 0;
		}
		if (nuke) surfaceDestroy(&w->surface);
	}
	free(w);
	w = NULL;
	*widget = w;

	// Make sure we're not drawing into oblivion.
	surfaceSet(NULL);
}


WidgetT *guiFocusGet(void) {
	return _guiFocused;
}


void guiFocusSet(WidgetT *widget) {
	// Is this widget on the active window?
	if (widget->window) {
		if (!GUI_GET_FLAG(W(widget->window), WIDGET_FLAG_ACTIVE)) {
			// Cancel focus change.
			return;
		}
	}
	// Did the focus change?
	if (widget != _guiFocused) {
		// Remove focus from current control.
		if (_guiFocused) {
			if (_guiFocused->focusMethod) _guiFocused->focusMethod(_guiFocused, 0);
		}
		// Change focus.
		_guiFocused = widget;
		// Tell new control it has focus.
		if (_guiFocused) {
			if (_guiFocused->focusMethod) _guiFocused->focusMethod(_guiFocused, 1);
		}
	}
}


uint8_t guiHasStopped(void) {
	return _guiHasStopped;
}


static void guiKeyboardChildrenProcess(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt) {
	size_t len;
	size_t x;

	// Process children of this widget.
	len = arrlenu(widget->children);
	for (x=0; x<len; x++) {
		guiKeyboardChildrenProcess(widget->children[x], ascii, extended, scancode, shift, control, alt);
	}

	// Does this widget want events?  Check for global keyboard so it doesn't get double events.
	if (widget) {
		if (widget->keyboardEventMethod && GUI_GET_FLAG(widget, WIDGET_FLAG_ALWAYS_RECEIVE_KEYBOARD_EVENTS)) {
			if (!GUI_GET_FLAG(widget, WIDGET_FLAG_DISABLED)) {
				widget->keyboardEventMethod(widget, ascii, extended, scancode, shift, control, alt);
			}
		}
	}
}


void guiKeyboardProcess(uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt) {
	// Does the focused widget want events?  Check for global keyboard so it doesn't get double events.
	if (_guiFocused) {
		if (_guiFocused->keyboardEventMethod && !GUI_GET_FLAG(_guiFocused, WIDGET_FLAG_ALWAYS_RECEIVE_KEYBOARD_EVENTS)) {
			if (!GUI_GET_FLAG(_guiFocused, WIDGET_FLAG_DISABLED)) {
				_guiFocused->keyboardEventMethod(_guiFocused, ascii, extended, scancode, shift, control, alt);
			}
		}
	}

	// Check everyone for global keyboard handling.
	guiKeyboardChildrenProcess((WidgetT *)_guiDesktop, ascii, extended, scancode, shift, control, alt);
}


void guiMousePositionOnWidgetGet(WidgetT *widget, MouseT *mouse, uint16_t *x, uint16_t *y) {
	RectT r;
	guiWidgetPositionOnScreenGet(widget, &r);
	*x = mouse->x - r.x;
	*y = mouse->y - r.y;
}


static uint8_t guiMouseChildrenProcess(WidgetT *widget, MouseT *mouse) {
	size_t          len;
	int16_t         x;
	RectT           r;
	uint16_t        mx;
	uint16_t        my;
	uint16_t        sx;
	uint16_t        sy;
	uint8_t         event      = MOUSE_EVENT_NONE;
	static WidgetT *focusDown  = NULL;

	// Get widget and screen coordinates of pointer.
	guiWidgetPositionOnScreenGet(widget, &r);
	sx = r.x;
	sy = r.y;
	guiMousePositionOnWidgetGet(widget, mouse, &mx, &my);

	//logWrite("Mouse %dx%d  Widget %s %dx%d  Screen %dx%d\n", mouse->x, mouse->y, _magicDebugNames[widget->magic], mx, my, sx, sy);

	// Serious hack to make window dragging work better.
	// This is because it's possible to move the mouse faster than METRIC_WINDOW_TITLE_GRAB_HEIGHT pixels
	// which causes it to switch widgets.  So this prevents that.
	if (widget->magic == MAGIC_WINDOW && widget == _guiDragWidget && mouse->buttonLeft && mouse->buttonLeftWasDown) {
		widget->mouseEventMethod(widget, mouse, mx, my, MOUSE_EVENT_LEFT_HOLD);
		return 1;
	}

	// Search children backwards for active widget before checking this widget.
	len = arrlenu(widget->children);
	for (x=len-1; x>=0; --x) {
		if (guiMouseChildrenProcess(widget->children[x], mouse)) {
			return 1;
		}
	}

	// Is the mouse inside this widget?
	if (mouse->x >= sx && mouse->y >= sy && mouse->x < sx + widget->pos.w && mouse->y < sy + widget->pos.h) {

		// Is this the same widget we were over before?
		if (_guiLastWidgetLeft != widget) {

			// Tell previous widget we're moving out.
			if (_guiLastWidgetLeft) {
				if (_guiLastWidgetLeft->mouseEventMethod) _guiLastWidgetLeft->mouseEventMethod(_guiLastWidgetLeft, mouse, mx, my, MOUSE_EVENT_OUT);
			}

			// Tell new widget we've moved in.
			event = MOUSE_EVENT_IN;

		} else {  // Widget Changed.

			// Left button depressed?
			if (_guiLastWidgetLeftEvent == MOUSE_EVENT_NONE && mouse->buttonLeft && !mouse->buttonLeftWasDown) event = MOUSE_EVENT_LEFT_DOWN;

			// Left button held?
			if ((_guiLastWidgetLeftEvent == MOUSE_EVENT_LEFT_DOWN || _guiLastWidgetLeftEvent == MOUSE_EVENT_LEFT_HOLD) && mouse->buttonLeft && mouse->buttonLeftWasDown) event = MOUSE_EVENT_LEFT_HOLD;

			// Left button coming up?
			if ((_guiLastWidgetLeftEvent == MOUSE_EVENT_LEFT_DOWN || _guiLastWidgetLeftEvent == MOUSE_EVENT_LEFT_HOLD) && !mouse->buttonLeft && mouse->buttonLeftWasDown) event = MOUSE_EVENT_LEFT_UP;

		}

		// If this is a left click, change focus to this new widget.
		if (event == MOUSE_EVENT_LEFT_DOWN) focusDown = widget;
		if (event == MOUSE_EVENT_LEFT_UP && widget == focusDown) guiFocusSet(widget);

		_guiLastWidgetLeft      = widget;
		_guiLastWidgetLeftEvent = event;

		// Is there an event?
		if (event != MOUSE_EVENT_NONE) {
			// Does it belong to the active window?  Or does it target a window?  A desktop?
			if (widget->window == _guiActiveWindow || widget->magic == MAGIC_WINDOW || widget->magic == MAGIC_DESKTOP) {
				// Is there a mouse handler?
				if (widget->mouseEventMethod) {
					if (!GUI_GET_FLAG(widget, WIDGET_FLAG_DISABLED) && !GUI_GET_FLAG(widget, WIDGET_FLAG_HIDDEN)) {
						// Ask child to handle event.
						widget->mouseEventMethod(widget, mouse, mx, my, event);
					}
				}
			}
		}

		// Proper child found.
		return 1;
	}

	// Not for this widget.
	return 0;
}


void guiMouseProcess(MouseT *mouse) {
	guiMouseChildrenProcess(W(_guiDesktop), mouse);
}


void guiPaint(WidgetT *widget) {

	// Process any pending events.
	while (arrlen(_guiPendingEvents)) {
		_guiPendingEvents[0]->callback(_guiPendingEvents[0]->widget);
		DEL(_guiPendingEvents[0]);
		arrdel(_guiPendingEvents, 0);
	}
	arrfree(_guiPendingEvents);

	// Process any pending widget deletions.
	guiDeleteList();

	guiPaintChildren(widget);
}


static void guiPaintChildren(WidgetT *widget) {
	size_t len;
	size_t x;
	RectT  pos;

	// Paint us.
	if (!GUI_GET_FLAG(widget, WIDGET_FLAG_HIDDEN)) {
		if (GUI_GET_FLAG(widget, WIDGET_FLAG_DIRTY) || GUI_GET_FLAG(widget, WIDGET_FLAG_ALWAYS_PAINT)) {
			if (widget->paintMethod) {
				surfaceSet(widget->surface);
				guiPaintBoundsGet(widget, &pos);
				widget->paintMethod(widget, !GUI_GET_FLAG(widget, WIDGET_FLAG_DISABLED), pos);
				GUI_CLEAR_FLAG(widget, WIDGET_FLAG_DIRTY);
			}
		}
	}

	// Paint all children.
	len = arrlenu(widget->children);
	if (len > 0) {
		for (x=0; x<len; x++) {
			guiPaintChildren(widget->children[x]);
		}
	}
}


static void guiPaintBoundsGet(WidgetT *widget, RectT *pos) {
	if (widget->magic == MAGIC_DESKTOP || widget->magic == MAGIC_WINDOW) {
		pos->x = 0;
		pos->y = 0;
	} else {
		*pos = widget->parent->clip;
		pos->x += widget->pos.x;
		pos->y += widget->pos.y;
	}
	pos->w = widget->pos.w;
	pos->h = widget->pos.h;
}


void guiParentAndChildrenDirtySet(WidgetT *widget) {
	// Does this widget have a parent?
	if (widget->parent) {
		// Mark it and all children dirty.
		guiWidgetAndChildrenDirtySet(widget->parent);
	}
}


void guiPendingEventAdd(WidgetT *widget, void *callback) {
	PendingEventsT *p = NULL;

	NEW(PendingEventsT, p);
	p->callback = callback;
	p->widget   = widget;
	arrput(_guiPendingEvents, p);
}


WidgetT *guiRootGet(void) {
	return (WidgetT *)_guiDesktop;
}


void guiShutdown(void) {
	// Unload fonts.
	fontUnload(&_guiFont);

	// Delete all widgets in GUI tree.
	guiDelete((WidgetT **)&_guiDesktop);
	guiDeleteList();
}


DesktopT *guiStartup(void) {

	_guiMetric[METRIC_BUTTON_BEZEL_SIZE]          = 2;
	_guiMetric[METRIC_BUTTON_HORIZONTAL_PADDING]  = 8;
	_guiMetric[METRIC_BUTTON_VERTICAL_PADDING]    = 2;

	_guiMetric[METRIC_WINDOW_BORDER_WIDTH]        = 4;   // Does not include highlight or shadow lines.
	_guiMetric[METRIC_WINDOW_TITLE_HEIGHT]        = 17;  // Does not include highlight or shadow lines.
	_guiMetric[METRIC_WINDOW_TITLE_GRAB_HEIGHT]   = _guiMetric[METRIC_WINDOW_BORDER_WIDTH] + _guiMetric[METRIC_WINDOW_TITLE_HEIGHT] + 5;  // Border, highlights, titlebar.

	_guiMetric[METRIC_CHECKBOX_PADDING]           = 6;   // Makes the 10 wide checkbox fill two character cells by padding it out to 16.

	_guiMetric[METRIC_RADIOBUTTON_PADDING]        = 6;   // Makes the 10 wide radio button fill two character cells by padding it out to 16.

	_guiMetric[METRIC_TEXTBOX_HORIZONTAL_PADDING] = 2;
	_guiMetric[METRIC_TEXTBOX_VERTICAL_PADDING]   = 2;
	_guiMetric[METRIC_TEXTBOX_PADDING]            = 6;   // Matches other label / widget padding.

	_guiMetric[METRIC_UPDOWN_HORIZONTAL_PADDING]  = 2;
	_guiMetric[METRIC_UPDOWN_VERTICAL_PADDING]    = 2;
	_guiMetric[METRIC_UPDOWN_PADDING]             = 6;   // Matches other label / widget padding.
	_guiMetric[METRIC_UPDOWN_ARROW_PADDING]       = 2;

	_guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING] = 2;
	_guiMetric[METRIC_LISTBOX_VERTICAL_PADDING]   = 2;


	_guiColor[COLOR_BUTTON_BACKGROUND]              = vbeColorMake(168, 168, 168);
	_guiColor[COLOR_BUTTON_HIGHLIGHT]               = vbeColorMake(248, 252, 248);
	_guiColor[COLOR_BUTTON_SHADOW]                  = vbeColorMake( 80,  84,  80);
	_guiColor[COLOR_BUTTON_TEXT]                    = vbeColorMake(  0,   0,   0);

	_guiColor[COLOR_BUTTON_BACKGROUND_DISABLED]     = vbeColorMake(124, 126, 124);
	_guiColor[COLOR_BUTTON_HIGHLIGHT_DISABLED]      = vbeColorMake(200, 200, 200);
	_guiColor[COLOR_BUTTON_SHADOW_DISABLED]         = vbeColorMake( 80,  84,  80);
	_guiColor[COLOR_BUTTON_TEXT_DISABLED]           = vbeColorMake( 84,  84,  84);

	_guiColor[COLOR_DESKTOP]                        = vbeColorMake( 51, 153, 255);

	_guiColor[COLOR_WINDOW_BACKGROUND]              = vbeColorMake(168, 168, 168);
	_guiColor[COLOR_WINDOW_HIGHLIGHT]               = vbeColorMake(248, 252, 248);
	_guiColor[COLOR_WINDOW_SHADOW]                  = vbeColorMake( 80,  84,  80);
	_guiColor[COLOR_WINDOW_TITLE_ACTIVE]            = vbeColorMake( 80,  84,  80);
	_guiColor[COLOR_WINDOW_TITLE_INACTIVE]          = vbeColorMake(168, 168, 168);
	_guiColor[COLOR_WINDOW_TITLE_TEXT_ACTIVE]       = vbeColorMake(248, 252, 248);
	_guiColor[COLOR_WINDOW_TITLE_TEXT_INACTIVE]     = vbeColorMake(  0,   0,   0);

	_guiColor[COLOR_LABEL_TEXT_INACTIVE]            = vbeColorMake(248, 252, 248);
	_guiColor[COLOR_LABEL_TEXT_INACTIVE]            = vbeColorMake(  0,   0,   0);

	_guiColor[COLOR_CHECKBOX_HIGHLIGHT]             = vbeColorMake(248, 252, 248);
	_guiColor[COLOR_CHECKBOX_SHADOW]                = vbeColorMake(  0,   0,   0);
	_guiColor[COLOR_CHECKBOX_ACTIVE]                = vbeColorMake( 80,  84,  80);
	_guiColor[COLOR_CHECKBOX_INACTIVE]              = vbeColorMake(168, 168, 168);
	_guiColor[COLOR_CHECKBOX_TEXT]                  = vbeColorMake(  0,   0,   0);
	_guiColor[COLOR_CHECKBOX_HIGHLIGHT_DISABLED]    = vbeColorMake(200, 200, 200);
	_guiColor[COLOR_CHECKBOX_SHADOW_DISABLED]       = vbeColorMake( 80,  84,  80);
	_guiColor[COLOR_CHECKBOX_ACTIVE_DISABLED]       = vbeColorMake( 80,  84,  80);
	_guiColor[COLOR_CHECKBOX_INACTIVE_DISABLED]     = vbeColorMake(124, 126, 124);
	_guiColor[COLOR_CHECKBOX_TEXT_DISABLED]         = vbeColorMake( 84,  84,  84);

	_guiColor[COLOR_RADIOBUTTON_HIGHLIGHT]          = vbeColorMake(248, 252, 248);
	_guiColor[COLOR_RADIOBUTTON_SHADOW]             = vbeColorMake(  0,   0,   0);
	_guiColor[COLOR_RADIOBUTTON_ACTIVE]             = vbeColorMake( 80,  84,  80);
	_guiColor[COLOR_RADIOBUTTON_INACTIVE]           = vbeColorMake(168, 168, 168);
	_guiColor[COLOR_RADIOBUTTON_TEXT]               = vbeColorMake(  0,   0,   0);
	_guiColor[COLOR_RADIOBUTTON_HIGHLIGHT_DISABLED] = vbeColorMake(200, 200, 200);
	_guiColor[COLOR_RADIOBUTTON_SHADOW_DISABLED]    = vbeColorMake( 80,  84,  80);
	_guiColor[COLOR_RADIOBUTTON_ACTIVE_DISABLED]    = vbeColorMake( 80,  84,  80);
	_guiColor[COLOR_RADIOBUTTON_INACTIVE_DISABLED]  = vbeColorMake(124, 126, 124);
	_guiColor[COLOR_RADIOBUTTON_TEXT_DISABLED]      = vbeColorMake( 84,  84,  84);

	_guiColor[COLOR_FRAME_HIGHLIGHT]                = vbeColorMake(248, 252, 248);
	_guiColor[COLOR_FRAME_SHADOW]                   = vbeColorMake(  0,   0,   0);
	_guiColor[COLOR_FRAME_TEXT]                     = vbeColorMake(  0,   0,   0);

	_guiColor[COLOR_TEXTBOX_HIGHLIGHT]              = vbeColorMake(248, 252, 248);
	_guiColor[COLOR_TEXTBOX_SHADOW]                 = vbeColorMake(  0,   0,   0);
	_guiColor[COLOR_TEXTBOX_TEXT]                   = vbeColorMake(  0,   0,   0);
	_guiColor[COLOR_TEXTBOX_BACKGROUND]             = vbeColorMake(248, 252, 248);

	_guiColor[COLOR_UPDOWN_HIGHLIGHT]               = vbeColorMake(248, 252, 248);
	_guiColor[COLOR_UPDOWN_SHADOW]                  = vbeColorMake(  0,   0,   0);
	_guiColor[COLOR_UPDOWN_TEXT]                    = vbeColorMake(  0,   0,   0);
	_guiColor[COLOR_UPDOWN_BACKGROUND]              = vbeColorMake(248, 252, 248);
	_guiColor[COLOR_UPDOWN_ARROWS_BACKGROUND]       = vbeColorMake(124, 126, 124);
	_guiColor[COLOR_UPDOWN_ARROWS_ACTIVE]           = vbeColorMake(168, 168, 168);
	_guiColor[COLOR_UPDOWN_ARROWS_INACTIVE]         = vbeColorMake( 80,  84,  80);

	_guiColor[COLOR_LISTBOX_HIGHLIGHT]              = vbeColorMake(248, 252, 248);
	_guiColor[COLOR_LISTBOX_SHADOW]                 = vbeColorMake(  0,   0,   0);
	_guiColor[COLOR_LISTBOX_TEXT]                   = vbeColorMake(  0,   0,   0);
	_guiColor[COLOR_LISTBOX_BACKGROUND]             = vbeColorMake(248, 252, 248);
	_guiColor[COLOR_LISTBOX_SELECTED_TEXT]          = vbeColorMake(248, 252, 248);
	_guiColor[COLOR_LISTBOX_SELECTED_BACKGROUND]    = vbeColorMake(  0,   0,   0);
	_guiColor[COLOR_LISTBOX_ARROWS_BACKGROUND]      = vbeColorMake(124, 126, 124);
	_guiColor[COLOR_LISTBOX_ARROWS_ACTIVE]          = vbeColorMake(168, 168, 168);
	_guiColor[COLOR_LISTBOX_ARROWS_INACTIVE]        = vbeColorMake( 80,  84,  80);

	// Load font.  ***TODO*** This should probably be provided by an API call to remove it from in here.
	_guiFont = fontLoad(cacheFilenameGet("system:vga8x14"));

	// Create desktop and return it.  Remember it for later.
	_guiDesktop = desktopNew();

	// First thing the mouse is over is the desktop.
	_guiLastWidgetLeft      = W(_guiDesktop);
	_guiLastWidgetLeftEvent = MOUSE_EVENT_NONE;
	// Synthesize a MOUSE_EVENT_IN for the desktop.
	//***TODO*** Uncomment this after removing 'mouse' from the parameter list.
	//if (_guiDesktop->base.mouseEventMethod) _guiDesktop->base.mouseEventMethod(W(_guiDesktop), 0, 0, MOUSE_EVENT_IN);

	return _guiDesktop;
}


void guiStop(void) {
	_guiHasStopped = 1;
}


uint32_t guiTimerGet(void) {
	return _guiTimerValue;
}


uint8_t guiTimerHalfSecondOn(void) {
	return _guiTimerHalfSecondOn;
}


uint8_t guiTimerHalfSecondTick(void) {
	return _guiTimerHalfSecondTick;
}


void guiTimerProcess(uint32_t rawClock) {
	uint32_t delta;

	_guiTimerValue = rawClock;

	// Reset ticks.
	_guiTimerQuarterSecondTick = 0;
	_guiTimerHalfSecondTick    = 0;
	_guiTimerSecondTick        = 0;

	// Ensure we haven't rolled past midnight between calls.
	if (_guiTimerValue >= _guiTimerLast) {
		delta = _guiTimerValue - _guiTimerLast;
	} else {
		// Compensate for midnight rollover.
		delta = (_guiTimerValue + TICKS_PER_DAY) - _guiTimerLast;
	}

	// Everything ticks off the quarter second.
	if (delta > TICKS_PER_SECOND * 0.25) {
		_guiTimerLast = _guiTimerValue;

		// Quarter Second timer.
		_guiTimerQuarterSecondOn = !_guiTimerQuarterSecondOn;
		_guiTimerQuarterSecondTick = 1;

		// Half Second timer.
		if (--_guiTimerHalfSecond == 0) {
			_guiTimerHalfSecond = 2;
			_guiTimerHalfSecondOn = !_guiTimerHalfSecondOn;
			_guiTimerHalfSecondTick = 1;

			// Second timer
			if (--_guiTimerSecond == 0) {
				_guiTimerSecond = 2;
				_guiTimerSecondOn = !_guiTimerSecondOn;
				_guiTimerSecondTick = 1;
			} // Second.

		} // Half Second.

	} // Quarter Second.
}


uint8_t guiTimerQuarterSecondOn(void) {
	return _guiTimerQuarterSecondOn;
}


uint8_t guiTimerQuarterSecondTick(void) {
	return _guiTimerQuarterSecondTick;
}


uint8_t guiTimerSecondOn(void) {
	return _guiTimerSecondOn;
}


uint8_t guiTimerSecondTick(void) {
	return _guiTimerSecondTick;
}


void *guiUserDataGet(WidgetT *widget) {
	return widget->userData;
}


void guiUserDataSet(WidgetT *widget, void *userData) {
	widget->userData = userData;
}


void guiWidgetAndChildrenDirtySet(WidgetT *widget) {
	size_t len = arrlenu(widget->children);
	size_t x;

	// Mark us dirty.
	GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY);

	// Mark all children.
	if (len > 0) {
		for (x=0; x<len; x++) {
			guiWidgetAndChildrenDirtySet(widget->children[x]);
		}
	}
}


void guiWidgetBoundsDrawableOnScreenGet(WidgetT *widget, RectT *bounds) {
	guiPaintBoundsGet(widget, bounds);
	if (GUI_GET_FLAG(widget, WIDGET_FLAG_OWNS_SURFACE)) {
		bounds->x += widget->pos.x;
		bounds->y += widget->pos.y;
	} else {
		bounds->x += widget->window->base.pos.x;
		bounds->y += widget->window->base.pos.y;
	}
	bounds->x += widget->margin.x;
	bounds->y += widget->margin.y;
	bounds->w -= (widget->margin.x + widget->margin.w);
	bounds->h -= (widget->margin.y + widget->margin.h);
}


void guiWidgetPositionOnScreenGet(WidgetT *widget, RectT *pos) {
	WidgetT *p = widget->parent;

	if (GUI_GET_FLAG(widget, WIDGET_FLAG_OWNS_SURFACE)) {
		*pos = widget->pos;
	} else {
		*pos = p->clip;
		pos->x += widget->pos.x + widget->window->base.pos.x;
		pos->y += widget->pos.y + widget->window->base.pos.y;
		pos->w = widget->pos.w;
		pos->h = widget->pos.h;
	}

}


WindowT *guiWindowFindTop(void) {
	if (guiRootGet() == NULL) return NULL;
	return guiWindowFindTopChildrenProcess(guiRootGet());
}


static WindowT *guiWindowFindTopChildrenProcess(WidgetT *widget) {
	size_t   len = arrlenu(widget->children);
	size_t   x;
	WindowT *top = NULL;
	WindowT *r   = NULL;

	// Is this a window?
	if (widget->magic == MAGIC_WINDOW) {
		top = (WindowT *)widget;
	}

	// Check children.
	if (len > 0) {
		for (x=0; x<len; x++) {
			r = guiWindowFindTopChildrenProcess(widget->children[x]);
			if (r) top = r;
		}
	}

	return top;
}
