#include "LBackend.h"
#if defined CC_DISABLE_LAUNCHER
/* e.g. web backend doesn't use the launcher */
#elif defined CC_BUILD_WIN_TEST
/* Testing windows UI backend */
#include "LBackend_Win.c"
#elif defined CC_BUILD_IOS
/* iOS uses custom UI backend */
#else
#include "Launcher.h"
#include "Drawer2D.h"
#include "Window.h"
#include "LWidgets.h"
#include "String.h"
#include "Gui.h"
#include "Drawer2D.h"
#include "Launcher.h"
#include "ExtMath.h"
#include "Window.h"
#include "Funcs.h"
#include "LWeb.h"
#include "Platform.h"
#include "LScreens.h"
#include "Input.h"
#include "Utils.h"
#include "Event.h"
#include "Stream.h"
#include "Logger.h"
#include "Errors.h"

struct FontDesc titleFont, textFont, hintFont, logoFont, rowFont;
/* Contains the pixels that are drawn to the window */
static struct Context2D framebuffer;
/* The area/region of the window that needs to be redrawn and presented to the screen. */
/* If width is 0, means no area needs to be redrawn. */
static Rect2D dirty_rect;
static int pendingFullDraws;

LBackend_DrawHook LBackend_Hooks[4];
static cc_uint8 pendingRedraw;
#define REDRAW_ALL  0x02
#define REDRAW_SOME 0x01

static int xBorder, xBorder2, xBorder3, xBorder4;
static int yBorder, yBorder2, yBorder3, yBorder4;
static int xInputOffset, yInputOffset, inputExpand;
static int caretOffset, caretWidth, caretHeight;
static int scrollbarWidth, dragPad, gridlineWidth, gridlineHeight;
static int hdrYOffset, hdrYPadding, rowYOffset, rowYPadding;
static int cellXOffset, cellXPadding, cellMinWidth;
static int flagXOffset, flagYOffset;

static void HookEvents(void);
void LBackend_Init(void) {
	xBorder = Display_ScaleX(1);
	yBorder = Display_ScaleY(1);

	if (xBorder < 1) { xBorder = 1; }
	if (yBorder < 1) { yBorder = 1; }

	xBorder2 = xBorder * 2; xBorder3 = xBorder * 3; xBorder4 = xBorder * 4;
	yBorder2 = yBorder * 2; yBorder3 = yBorder * 3; yBorder4 = yBorder * 4;

	xInputOffset = Display_ScaleX(5);
	yInputOffset = Display_ScaleY(2);
	inputExpand  = Display_ScaleX(20);

	caretOffset  = Display_ScaleY(5);
	caretWidth   = Display_ScaleX(10);
	caretHeight  = Display_ScaleY(2);

	scrollbarWidth = Display_ScaleX(10);
	dragPad        = Display_ScaleX(8);
	gridlineWidth  = Display_ScaleX(2);
	gridlineHeight = Display_ScaleY(2);

	hdrYOffset   = Display_ScaleY(3);
	hdrYPadding  = Display_ScaleY(5);
	rowYOffset   = Display_ScaleY(3);
	rowYPadding  = Display_ScaleY(1);

	cellXOffset  = Display_ScaleX(6);
	cellXPadding = Display_ScaleX(5);
	cellMinWidth = Display_ScaleX(20);
	flagXOffset  = Display_ScaleX(2);
	flagYOffset  = Display_ScaleY(6);

	Font_Make(&titleFont, 16, FONT_FLAGS_BOLD);
	Font_Make(&textFont,  14, FONT_FLAGS_NONE);
	Font_Make(&hintFont,  12, FONT_FLAGS_NONE);
	HookEvents();
}

void LBackend_Free(void) {
	Font_Free(&titleFont);
	Font_Free(&textFont);
	Font_Free(&hintFont);
	Font_Free(&logoFont);
	Font_Free(&rowFont);
}

void LBackend_UpdateTitleFont(void) {
	Font_Free(&logoFont);
	Launcher_MakeTitleFont(&logoFont);
}
void LBackend_DrawTitle(struct Context2D* ctx, const char* title) {
	Launcher_DrawTitle(&logoFont, title, ctx);
}

/* Scales up flag bitmap if necessary */
static void LBackend_ScaleFlag(struct Bitmap* bmp) {
	struct Bitmap scaled;
	int width  = Display_ScaleX(bmp->width);
	int height = Display_ScaleY(bmp->height);
	/* at default DPI don't need to rescale it */
	if (width == bmp->width && height == bmp->height) return;

	scaled.width  = width;
	scaled.height = height;
	scaled.scan0  = (BitmapCol*)Mem_TryAlloc(width * height, BITMAPCOLOR_SIZE);

	if (!scaled.scan0) {
		Logger_SysWarn(ERR_OUT_OF_MEMORY, "resizing flags bitmap"); return;
	}

	Bitmap_Scale(&scaled, bmp, 0, 0, bmp->width, bmp->height);
	Mem_Free(bmp->scan0);
	*bmp = scaled;
}

void LBackend_DecodeFlag(struct Flag* flag, cc_uint8* data, cc_uint32 len) {
	struct Stream s;
	cc_result res;

	Stream_ReadonlyMemory(&s, data, len);
	res = Png_Decode(&flag->bmp, &s);
	if (res) Logger_SysWarn(res, "decoding flag");
	flag->meta = NULL;

	LBackend_ScaleFlag(&flag->bmp);
}

static void OnPointerMove(void* obj, int idx);
void LBackend_SetScreen(struct LScreen* s) {
	int i;
	/* for hovering over active button etc */
	for (i = 0; i < Pointers_Count; i++) {
		OnPointerMove(s, i);
	}
}

void LBackend_CloseScreen(struct LScreen* s) { }

static void LBackend_LayoutDimensions(struct LWidget* w) {
	const struct LLayout* l = w->layouts + 2;
	while (l->type)
	{
		switch (l->type)
		{
		case LLAYOUT_WIDTH:
			w->width  = Window_Main.Width  - w->x - Display_ScaleX(l->offset);
			w->width  = max(1, w->width);
			break;
		case LLAYOUT_HEIGHT:
			w->height = Window_Main.Height - w->y - Display_ScaleY(l->offset);
			w->height = max(1, w->height);
			break;
		}
		l++;
	}
}

void LBackend_LayoutWidget(struct LWidget* w) {
	const struct LLayout* l = w->layouts;

	w->x = Gui_CalcPos(l[0].type & 0xFF, Display_ScaleX(l[0].offset), w->width,  Window_Main.Width);
	w->y = Gui_CalcPos(l[1].type & 0xFF, Display_ScaleY(l[1].offset), w->height, Window_Main.Height);

	/* e.g. Table widget needs adjusts width/height based on window */
	if (l[1].type & LLAYOUT_EXTRA)
		LBackend_LayoutDimensions(w);

	if (w->type != LWIDGET_TABLE) return;
	LBackend_TableReposition((struct LTable*)w);
}

void LBackend_NeedsRedraw(void* widget) {
	struct LWidget* w = (struct LWidget*)widget;
	pendingRedraw |= REDRAW_SOME;
	w->dirty = true;
}

void LBackend_MarkAllDirty(void) {
	dirty_rect.x = 0; dirty_rect.width  = framebuffer.width;
	dirty_rect.y = 0; dirty_rect.height = framebuffer.height;
}

void LBackend_MarkAreaDirty(int x, int y, int width, int height) {
	int x1, y1, x2, y2;
	if (!Drawer2D_Clamp(&framebuffer, &x, &y, &width, &height)) return;

	/* union with existing dirty area */
	if (dirty_rect.width) {
		x1 = min(x, dirty_rect.x);
		y1 = min(y, dirty_rect.y);

		x2 = max(x +  width, dirty_rect.x + dirty_rect.width);
		y2 = max(y + height, dirty_rect.y + dirty_rect.height);

		x = x1; width  = x2 - x1;
		y = y1; height = y2 - y1;
	}

	dirty_rect.x = x; dirty_rect.width  = width;
	dirty_rect.y = y; dirty_rect.height = height;
}

void LBackend_InitFramebuffer(void) {
	struct Bitmap bmp;
	int width  = max(Window_Main.Width,  1);
	int height = max(Window_Main.Height, 1);

	Window_AllocFramebuffer(&bmp, width, height);
	Context2D_Wrap(&framebuffer, &bmp);
	/* Backing surface may be bigger then valid area */
	framebuffer.width  = width;
	framebuffer.height = height;
}

void LBackend_FreeFramebuffer(void) {
	Window_FreeFramebuffer(&framebuffer.bmp);
}


/*########################################################################################################################*
*------------------------------------------------------Base drawing-------------------------------------------------------*
*#########################################################################################################################*/
static void DrawBoxBounds(BitmapCol color, int x, int y, int width, int height) {
	Context2D_Clear(&framebuffer, color,
		x,                   y, 
		width,               yBorder);
	Context2D_Clear(&framebuffer, color,
		x,                   y + height - yBorder,
		width,               yBorder);
	Context2D_Clear(&framebuffer, color,
		x,                   y, 
		xBorder,             height);
	Context2D_Clear(&framebuffer, color,
		x + width - xBorder, y, 
		xBorder,             height);
}

static CC_NOINLINE void DrawWidget(struct LWidget* w) {
	w->last.x = w->x; w->last.width  = w->width;
	w->last.y = w->y; w->last.height = w->height;

	w->dirty = false;
	w->VTABLE->Draw(w);
	LBackend_MarkAreaDirty(w->x, w->y, w->width, w->height);
}

static CC_NOINLINE void RedrawAll(void) {
	struct LScreen* s = Launcher_Active;
	int i;
	s->DrawBackground(s, &framebuffer);
	
	for (i = 0; i < s->numWidgets; i++) {
		DrawWidget(s->widgets[i]);
	}
	LBackend_MarkAllDirty();
}

static CC_NOINLINE void RedrawDirty(void) {
	struct LScreen* s = Launcher_Active;
	struct LWidget* w;
	int i;
	
	for (i = 0; i < s->numWidgets; i++) {
		w = s->widgets[i];
		if (!w->dirty) continue;

		/* check if widget might need redrawing of background behind */
		if (!w->opaque || w->last.width > w->width || w->last.height > w->height) {
			s->ResetArea(&framebuffer,
						  w->last.x, w->last.y, w->last.width, w->last.height);
			LBackend_MarkAreaDirty(w->last.x, w->last.y, w->last.width, w->last.height);
		}
		DrawWidget(w);
	}
}

static CC_NOINLINE void DoRedraw(void) {
	if (pendingRedraw & REDRAW_ALL) {
		RedrawAll();
		pendingRedraw = 0;
	} else if (pendingRedraw & REDRAW_SOME) {
		RedrawDirty();
		pendingRedraw = 0;
	}
}

void LBackend_Redraw(void) {
	pendingRedraw = REDRAW_ALL;
	LBackend_MarkAllDirty();
}
void LBackend_ThemeChanged(void) { LBackend_Redraw(); }

void LBackend_Tick(void) {
	int i;
	/* Window backend requires always redrawing entire frame - slow */
	/* Most window backends do not require this though */
	if (DisplayInfo.FullRedraw && pendingRedraw) pendingRedraw |= REDRAW_ALL;
	DoRedraw();

	if (pendingFullDraws) {
		pendingFullDraws--;
		LBackend_MarkAllDirty();
	}
	if (!dirty_rect.width) return;

	for (i = 0; i < Array_Elems(LBackend_Hooks); i++)
	{
		if (LBackend_Hooks[i]) LBackend_Hooks[i](&framebuffer);
	}
	Window_DrawFramebuffer(dirty_rect, &framebuffer.bmp);

	dirty_rect.x = 0; dirty_rect.width   = 0;
	dirty_rect.y = 0; dirty_rect.height  = 0;
}

void LBackend_AddDirtyFrames(int frames) {
	pendingFullDraws = frames;
}


/*########################################################################################################################*
*-----------------------------------------------------Event handling------------------------------------------------------*
*#########################################################################################################################*/
static void ReqeustRedraw(void* obj)  { LBackend_Redraw(); }
static void RedrawContents(void* obj) { DoRedraw(); }

CC_NOINLINE static struct LWidget* GetWidgetAt(struct LScreen* s, int idx) {
	struct LWidget* w;
	int i, x = Pointers[idx].x, y = Pointers[idx].y;

	for (i = 0; i < s->numWidgets; i++) {
		w = s->widgets[i];
		if (Gui_Contains(w->x, w->y, w->width, w->height, x, y)) return w;
	}
	return NULL;
}

static void OnPointerDown(void* obj, int idx) {
	struct LScreen* s = Launcher_Active;
	struct LWidget* over;
	struct LWidget* prev;
	if (Window_Main.SoftKeyboardFocus) return;

	if (!s) return;
	over = GetWidgetAt(s, idx);
	prev = s->selectedWidget;

	if (prev && over != prev) LScreen_UnselectWidget(s, idx, prev);
	if (over) LScreen_SelectWidget(s, idx, over, over == prev);
}

static void OnPointerUp(void* obj, int idx) {
	struct LScreen* s = Launcher_Active;
	struct LWidget* over;
	struct LWidget* prev;
	if (Window_Main.SoftKeyboardFocus) return;

	if (!s) return;
	over = GetWidgetAt(s, idx);
	prev = s->selectedWidget;

	/* if user moves mouse away, it doesn't count */
	if (over != prev) {
		LScreen_UnselectWidget(s, idx, prev);
	} else if (over && over->OnClick) {
		over->OnClick(over);
	}
	/* TODO eliminate this hack */
	s->MouseUp(s, idx);
}

static void OnPointerMove(void* obj, int idx) {
	struct LScreen* s = Launcher_Active;
	struct LWidget* over;
	struct LWidget* prev;
	cc_bool overSame;
	if (Window_Main.SoftKeyboardFocus) return;

	if (!s) return;
	over = GetWidgetAt(s, idx);
	prev = s->hoveredWidget;
	overSame = prev == over;

	if (prev && !overSame) {
		prev->hovered    = false;
		s->hoveredWidget = NULL;

		if (prev->OnUnhover) prev->OnUnhover(prev);
		if (prev->VTABLE->MouseLeft) prev->VTABLE->MouseLeft(prev);
	}

	if (over) {
		over->hovered    = true;
		s->hoveredWidget = over;

		if (over->OnHover) over->OnHover(over);
		if (!over->VTABLE->MouseMove) return;
		over->VTABLE->MouseMove(over, idx, overSame);
	}
}

static void OnKeyPress(void* obj, int cp) {
	struct LWidget* selected;
	char c;
	if (!Convert_TryCodepointToCP437(cp, &c)) return;

	selected = Launcher_Active->selectedWidget;
	if (!selected) return;

	if (!selected->VTABLE->KeyPress) return;
	selected->VTABLE->KeyPress(selected, c);
}

static void OnTextChanged(void* obj, const cc_string* str) {
	struct LWidget* selected = Launcher_Active->selectedWidget;
	if (!selected) return;

	if (!selected->VTABLE->TextChanged) return;
	selected->VTABLE->TextChanged(selected, str);
}

static void HookEvents(void) {
	Event_Register_(&PointerEvents.Down,  NULL, OnPointerDown);
	Event_Register_(&PointerEvents.Up,    NULL, OnPointerUp);
	Event_Register_(&PointerEvents.Moved, NULL, OnPointerMove);
	
	Event_Register_(&InputEvents.Press,         NULL, OnKeyPress);
	Event_Register_(&InputEvents.TextChanged,   NULL, OnTextChanged);

	Event_Register_(&WindowEvents.RedrawNeeded, NULL, ReqeustRedraw);
	Event_Register_(&WindowEvents.Redrawing,    NULL, RedrawContents);
}


/*########################################################################################################################*
*------------------------------------------------------ButtonWidget-------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_ButtonInit(struct LButton* w, int width, int height) {	
	w->width  = Display_ScaleX(width);
	w->height = Display_ScaleY(height);
}

void LBackend_ButtonUpdate(struct LButton* w) {
	struct DrawTextArgs args;
	DrawTextArgs_Make(&args, &w->text, &titleFont, true);
	LBackend_NeedsRedraw(w);

	w->_textWidth  = Drawer2D_TextWidth(&args);
	w->_textHeight = Drawer2D_TextHeight(&args);
}

void LBackend_ButtonDraw(struct LButton* w) {
	struct DrawTextArgs args;
	int xOffset, yOffset;
	cc_bool active = w->hovered || w->selected;

	LButton_DrawBackground(&framebuffer, w->x, w->y, w->width, w->height, active);
	xOffset = w->width  - w->_textWidth;
	yOffset = w->height - w->_textHeight;
	DrawTextArgs_Make(&args, &w->text, &titleFont, true);

	if (!active) Drawer2D.Colors['f'] = Drawer2D.Colors['7'];
	Context2D_DrawText(&framebuffer, &args, 
					  w->x + xOffset / 2, w->y + yOffset / 2);

	if (!active) Drawer2D.Colors['f'] = Drawer2D.Colors['F'];
}


/*########################################################################################################################*
*-----------------------------------------------------CheckboxWidget------------------------------------------------------*
*#########################################################################################################################*/
#define CB_SIZE  24
#define CB_OFFSET 8

static void LCheckbox_OnClick(void* w) {
	struct LCheckbox* cb = (struct LCheckbox*)w;
	LBackend_NeedsRedraw(cb);

	cb->value = !cb->value;
	if (cb->ValueChanged) cb->ValueChanged(cb);
}

void LBackend_CheckboxInit(struct LCheckbox* w) {
	struct DrawTextArgs args;
	DrawTextArgs_Make(&args, &w->text, &textFont, true);

	w->width   = Display_ScaleX(CB_SIZE + CB_OFFSET) + Drawer2D_TextWidth(&args);
	w->height  = Display_ScaleY(CB_SIZE);
	w->OnClick = LCheckbox_OnClick;
}

void LBackend_CheckboxUpdate(struct LCheckbox* w) {
	LBackend_NeedsRedraw(w);
}

/* Based off checkbox from original ClassiCube Launcher */
static const cc_uint8 checkbox_indices[] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x06, 0x07, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x06, 0x09, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x06, 0x0B, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0D, 0x0E, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x0F, 0x06, 0x10, 0x00, 0x11, 0x06, 0x12, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x13, 0x14, 0x15, 0x00, 0x16, 0x17, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x18, 0x06, 0x19, 0x06, 0x1A, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x1B, 0x06, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x1D, 0x06, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const BitmapCol checkbox_palette[] = {
	BitmapCol_Make(0,0,0,0),        BitmapColor_RGB(144, 144, 144),
	BitmapColor_RGB( 61,  61,  61), BitmapColor_RGB( 94,  94,  94),
	BitmapColor_RGB(197, 196, 197), BitmapColor_RGB( 57,  57,  57),
	BitmapColor_RGB( 33,  33,  33), BitmapColor_RGB(177, 177, 177),
	BitmapColor_RGB(189, 189, 189), BitmapColor_RGB( 67,  67,  67),
	BitmapColor_RGB(108, 108, 108), BitmapColor_RGB(171, 171, 171),
	BitmapColor_RGB(220, 220, 220), BitmapColor_RGB( 43,  43,  43),
	BitmapColor_RGB( 63,  63,  63), BitmapColor_RGB(100, 100, 100),
	BitmapColor_RGB(192, 192, 192), BitmapColor_RGB(132, 132, 132),
	BitmapColor_RGB(175, 175, 175), BitmapColor_RGB(217, 217, 217),
	BitmapColor_RGB( 42,  42,  42), BitmapColor_RGB( 86,  86,  86),
	BitmapColor_RGB( 56,  56,  56), BitmapColor_RGB( 76,  76,  76),
	BitmapColor_RGB(139, 139, 139), BitmapColor_RGB(130, 130, 130),
	BitmapColor_RGB(181, 181, 181), BitmapColor_RGB( 62,  62,  62),
	BitmapColor_RGB( 75,  75,  75), BitmapColor_RGB(184, 184, 184),
};

static void DrawIndexed(int size, int x, int y, struct Context2D* ctx) {
	struct Bitmap* bmp = (struct Bitmap*)ctx;
	BitmapCol* row, color;
	int i, xx, yy;

	for (i = 0, yy = 0; yy < size; yy++) {
		if ((y + yy) < 0) { i += size; continue; }
		if ((y + yy) >= bmp->height) break;
		row = Bitmap_GetRow(bmp, y + yy);

		for (xx = 0; xx < size; xx++) {
			color = checkbox_palette[checkbox_indices[i++]];
			if (color == 0) continue; /* transparent pixel */

			if ((x + xx) < 0 || (x + xx) >= bmp->width) continue;
			row[x + xx] = color;
		}
	}
}

void LBackend_CheckboxDraw(struct LCheckbox* w) {
	BitmapCol boxTop    = BitmapColor_RGB(255, 255, 255);
	BitmapCol boxBottom = BitmapColor_RGB(240, 240, 240);
	struct DrawTextArgs args;
	int x, y, width, height;

	width  = Display_ScaleX(CB_SIZE);
	height = Display_ScaleY(CB_SIZE);

	Gradient_Vertical(&framebuffer, boxTop, boxBottom,
						w->x, w->y,              width, height / 2);
	Gradient_Vertical(&framebuffer, boxBottom, boxTop,
						w->x, w->y + height / 2, width, height / 2);

	if (w->value) {
		const int size = 12;
		x = w->x + width  / 2 - size / 2;
		y = w->y + height / 2 - size / 2;
		DrawIndexed(size, x, y, &framebuffer);
	}
	DrawBoxBounds(BITMAPCOLOR_BLACK, w->x, w->y, width, height);

	DrawTextArgs_Make(&args, &w->text, &textFont, true);
	x = w->x + Display_ScaleX(CB_SIZE + CB_OFFSET);
	y = w->y + (height - Drawer2D_TextHeight(&args)) / 2;
	Context2D_DrawText(&framebuffer, &args, x, y);
}


/*########################################################################################################################*
*------------------------------------------------------InputWidget--------------------------------------------------------*
*#########################################################################################################################*/
static cc_uint64 caretStart;
static Rect2D caretRect, lastCaretRect;
#define Rect2D_Equals(a, b) a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height

static void LInput_OpenKeyboard(struct LInput* w) {
	struct OpenKeyboardArgs args;
	OpenKeyboardArgs_Init(&args, &w->text, w->inputType);
	OnscreenKeyboard_Open(&args);
}

static void LInput_OnClick(void* widget) {
	struct LInput* w = (struct LInput*)widget;
	if (DisplayInfo.ShowingSoftKeyboard) return;

	LInput_OpenKeyboard(w);
}

void LBackend_InputInit(struct LInput* w, int width) {
	w->width    = Display_ScaleX(width);
	w->height   = Display_ScaleY(LINPUT_HEIGHT);
	w->minWidth = w->width;
	
	if (Window_Main.SoftKeyboard)
		w->OnClick  = LInput_OnClick;

	/* Text may end up being wider than minimum width */
	if (w->text.length) LBackend_InputUpdate(w);
}

void LBackend_InputUpdate(struct LInput* w) {
	cc_string text; char textBuffer[STRING_SIZE];
	struct DrawTextArgs args;
	int textWidth;

	String_InitArray(text, textBuffer);
	LInput_UNSAFE_GetText(w, &text);
	LBackend_NeedsRedraw(w);

	DrawTextArgs_Make(&args, &text, &textFont, false);
	textWidth      = Drawer2D_TextWidth(&args);
	w->width       = max(w->minWidth, textWidth + inputExpand);
	w->_textHeight = Drawer2D_TextHeight(&args);
}

static Rect2D LInput_MeasureCaret(struct LInput* w, cc_string* text) {
	struct DrawTextArgs args;
	Rect2D r;
	DrawTextArgs_Make(&args, text, &textFont, true);

	r.x = w->x + xInputOffset;
	r.y = w->y + w->height - caretOffset; r.height = caretHeight;

	if (w->caretPos == -1) {
		r.x += Drawer2D_TextWidth(&args);
		r.width = caretWidth;
	} else {
		args.text = String_UNSAFE_Substring(text, 0, w->caretPos);
		r.x += Drawer2D_TextWidth(&args);

		args.text = String_UNSAFE_Substring(text, w->caretPos, 1);
		r.width   = Drawer2D_TextWidth(&args);
	}
	return r;
}

static void LInput_MoveCaretToCursor(struct LInput* w, int idx) {
	cc_string text; char textBuffer[STRING_SIZE];
	int x = Pointers[idx].x, y = Pointers[idx].y;
	struct DrawTextArgs args;
	int i, charX, charWidth;

	/* Input widget may have been selected by pressing tab */
	/* In which case cursor is completely outside, so ignore */
	if (!Gui_Contains(w->x, w->y, w->width, w->height, x, y)) return;

	String_InitArray(text, textBuffer);
	LInput_UNSAFE_GetText(w, &text);
	x -= w->x; y -= w->y;

	DrawTextArgs_Make(&args, &text, &textFont, true);
	if (x >= Drawer2D_TextWidth(&args)) {
		w->caretPos = -1; return; 
	}

	for (i = 0; i < text.length; i++) {
		args.text = String_UNSAFE_Substring(&text, 0, i);
		charX     = Drawer2D_TextWidth(&args);

		args.text = String_UNSAFE_Substring(&text, i, 1);
		charWidth = Drawer2D_TextWidth(&args);
		if (x >= charX && x < charX + charWidth) {
			w->caretPos = i; return;
		}
	}
}

void LBackend_InputTick(struct LInput* w) {
	int elapsed;
	cc_bool caretShow;
	Rect2D r;

	if (!caretStart) return;
	elapsed = Stopwatch_ElapsedMS(caretStart, Stopwatch_Measure());

	caretShow = (elapsed % 1000) < 500;
	if (caretShow == w->caretShow) return;
	w->caretShow = caretShow;

	LBackend_InputDraw(w);
	r = caretRect;
	
	if (Rect2D_Equals(r, lastCaretRect)) {
		/* Fast path, caret is blinking in same spot */
		LBackend_MarkAreaDirty(r.x, r.y, r.width, r.height);
	} else {
		/* Slow path (new widget, caret moved, etc) */
		LBackend_MarkAreaDirty(w->x, w->y, w->width, w->height);
	}
	lastCaretRect = r;
}

void LBackend_InputSelect(struct LInput* w, int idx, cc_bool wasSelected) {
	caretStart   = Stopwatch_Measure();
	w->caretShow = true;
	LInput_MoveCaretToCursor(w, idx);
	LBackend_NeedsRedraw(w);
	
	if (Window_Main.SoftKeyboardInstant)
		LInput_OpenKeyboard(w);
}

void LBackend_InputUnselect(struct LInput* w) {
	caretStart   = 0;
	w->caretShow = false;
	LBackend_NeedsRedraw(w);
	OnscreenKeyboard_Close();
}


static void LInput_DrawOuterBorder(struct LInput* w) {
	struct LScreen* s     = Launcher_Active;
	struct Context2D* ctx = &framebuffer;
	BitmapCol color       = Launcher_Theme.ButtonBorderColor;

	if (w->selected) {
		DrawBoxBounds(color, w->x, w->y, w->width, w->height);
	} else {
		s->ResetArea(ctx, w->x,                      w->y, 
						  w->width,                  yBorder);
		s->ResetArea(ctx, w->x,                      w->y + w->height - yBorder,
						  w->width,                  yBorder);
		s->ResetArea(ctx, w->x,                      w->y,
						  xBorder,                   w->height);
		s->ResetArea(ctx, w->x + w->width - xBorder, w->y,
						  xBorder,                   w->height);
	}
}

static void LInput_DrawInnerBorder(struct LInput* w) {
	/* e.g. for modern theme: 162,131,186 --> 165,142,168 */
	BitmapCol color = BitmapColor_Offset(Launcher_Theme.ButtonHighlightColor, 3,11,-18);

	Context2D_Clear(&framebuffer, color,
		w->x + xBorder,             w->y + yBorder,
		w->width - xBorder2,        yBorder);
	Context2D_Clear(&framebuffer, color,
		w->x + xBorder,             w->y + w->height - yBorder2,
		w->width - xBorder2,        yBorder);
	Context2D_Clear(&framebuffer, color,
		w->x + xBorder,             w->y + yBorder,
		xBorder,                    w->height - yBorder2);
	Context2D_Clear(&framebuffer, color,
		w->x + w->width - xBorder2, w->y + yBorder,
		xBorder,                    w->height - yBorder2);
}

static void LInput_BlendBoxTop(struct LInput* w) {
	BitmapCol color = BitmapColor_RGB(0, 0, 0);

	Gradient_Blend(&framebuffer, color, 75,
		w->x + xBorder,      w->y + yBorder, 
		w->width - xBorder2, yBorder);
	Gradient_Blend(&framebuffer, color, 50,
		w->x + xBorder,      w->y + yBorder2,
		w->width - xBorder2, yBorder);
	Gradient_Blend(&framebuffer, color, 25,
		w->x + xBorder,      w->y + yBorder3, 
		w->width - xBorder2, yBorder);
}

static void LInput_DrawText(struct LInput* w, struct DrawTextArgs* args) {
	int y, hintHeight;

	if (w->text.length || !w->hintText) {
		y = w->y + (w->height - w->_textHeight) / 2;
		Context2D_DrawText(&framebuffer, args, 
							w->x + xInputOffset, y + yInputOffset);
	} else {
		args->text = String_FromReadonly(w->hintText);
		args->font = &hintFont;

		hintHeight = Drawer2D_TextHeight(args);
		y = w->y + (w->height - hintHeight) / 2;

		Drawer2D.Colors['f'] = BitmapColor_RGB(125, 125, 125);
		Context2D_DrawText(&framebuffer, args, 
							w->x + xInputOffset, y);
		Drawer2D.Colors['f'] = BITMAPCOLOR_WHITE;
	}
}

void LBackend_InputDraw(struct LInput* w) {
	cc_string text; char textBuffer[STRING_SIZE];
	struct DrawTextArgs args;

	String_InitArray(text, textBuffer);
	LInput_UNSAFE_GetText(w, &text);
	DrawTextArgs_Make(&args, &text, &textFont, false);

	LInput_DrawOuterBorder(w);
	LInput_DrawInnerBorder(w);
	Context2D_Clear(&framebuffer, BITMAPCOLOR_WHITE,
		w->x + xBorder2,     w->y + yBorder2,
		w->width - xBorder4, w->height - yBorder4);
	LInput_BlendBoxTop(w);

	Drawer2D.Colors['f'] = Drawer2D.Colors['0'];
	LInput_DrawText(w, &args);
	Drawer2D.Colors['f'] = Drawer2D.Colors['F'];

	caretRect = LInput_MeasureCaret(w, &text);
	if (!w->caretShow) return;
	Context2D_Clear(&framebuffer, BITMAPCOLOR_BLACK,
					caretRect.x, caretRect.y, caretRect.width, caretRect.height);
}


/*########################################################################################################################*
*------------------------------------------------------LabelWidget--------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_LabelInit(struct LLabel* w) { }
#define LLabel_GetFont(w) (w->small ? &hintFont : &textFont)

void LBackend_LabelUpdate(struct LLabel* w) {
	struct DrawTextArgs args;
	DrawTextArgs_Make(&args, &w->text, LLabel_GetFont(w), true);
	LBackend_NeedsRedraw(w);

	w->width  = Drawer2D_TextWidth(&args);
	w->height = Drawer2D_TextHeight(&args);
}

void LBackend_LabelDraw(struct LLabel* w) {
	struct DrawTextArgs args;
	DrawTextArgs_Make(&args, &w->text, LLabel_GetFont(w), true);
	Context2D_DrawText(&framebuffer, &args, w->x, w->y);
}


/*########################################################################################################################*
*-------------------------------------------------------LineWidget--------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_LineInit(struct LLine* w, int width) {
	w->width  = Display_ScaleX(width);
	w->height = Display_ScaleY(LLINE_HEIGHT);
}

void LBackend_LineDraw(struct LLine* w) {
	BitmapCol color = LLine_GetColor();
	Gradient_Blend(&framebuffer, color, 128, w->x, w->y, w->width, w->height);
}


/*########################################################################################################################*
*------------------------------------------------------SliderWidget-------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_SliderInit(struct LSlider* w, int width, int height) {
	w->width  = Display_ScaleX(width); 
	w->height = Display_ScaleY(height);
}

void LBackend_SliderUpdate(struct LSlider* w) {
	LBackend_NeedsRedraw(w);
}

static void LSlider_DrawBoxBounds(struct LSlider* w) {
	BitmapCol boundsTop    = BitmapColor_RGB(119, 100, 132);
	BitmapCol boundsBottom = BitmapColor_RGB(150, 130, 165);

	/* TODO: Check these are actually right */
	Context2D_Clear(&framebuffer, boundsTop,
				  w->x,     w->y,
				  w->width, yBorder);
	Context2D_Clear(&framebuffer, boundsBottom,
				  w->x,	    w->y + w->height - yBorder,
				  w->width, yBorder);

	Gradient_Vertical(&framebuffer, boundsTop, boundsBottom,
					 w->x,                      w->y,
					 xBorder,                   w->height);
	Gradient_Vertical(&framebuffer, boundsTop, boundsBottom,
					 w->x + w->width - xBorder, w->y,
					 xBorder,				    w->height);
}

static void LSlider_DrawBox(struct LSlider* w) {
	BitmapCol progTop    = BitmapColor_RGB(220, 204, 233);
	BitmapCol progBottom = BitmapColor_RGB(207, 181, 216);
	int halfHeight = (w->height - yBorder2) / 2;

	Gradient_Vertical(&framebuffer, progTop, progBottom,
					  w->x + xBorder,	   w->y + yBorder, 
					  w->width - xBorder2, halfHeight);
	Gradient_Vertical(&framebuffer, progBottom, progTop,
					  w->x + xBorder,	   w->y + yBorder + halfHeight, 
		              w->width - xBorder2, halfHeight);
}

#define LSLIDER_MAXVALUE 100
void LBackend_SliderDraw(struct LSlider* w) {
	int curWidth;
	LSlider_DrawBoxBounds(w);
	LSlider_DrawBox(w);

	curWidth = (int)((w->width - xBorder2) * w->value / LSLIDER_MAXVALUE);
	Context2D_Clear(&framebuffer, w->color,
				   w->x + xBorder, w->y + yBorder, 
				   curWidth,       w->height - yBorder2);
}


/*########################################################################################################################*
*-------------------------------------------------------TableWidget-------------------------------------------------------*
*#########################################################################################################################*/
static void InitRowFont(void) {
	if (rowFont.handle) return;
	Font_Make(&rowFont, 11, FONT_FLAGS_NONE);
}

void LBackend_TableInit(struct LTable* w) { }
void LBackend_TableUpdate(struct LTable* w) { }

void LBackend_TableReposition(struct LTable* w) {
	int rowsHeight;
	InitRowFont();
	w->hdrHeight = Font_CalcHeight(&textFont, true) + hdrYPadding;
	w->rowHeight = Font_CalcHeight(&rowFont,  true) + rowYPadding;

	w->rowsBegY = w->y + w->hdrHeight + gridlineHeight;
	w->rowsEndY = w->y + w->height;
	rowsHeight  = w->height - (w->rowsBegY - w->y);

	w->visibleRows = rowsHeight / w->rowHeight;
	LTable_ClampTopRow(w);
}

void LBackend_TableFlagAdded(struct LTable* w) {
	/* TODO: Only redraw flags */
	LBackend_NeedsRedraw(w);
}

/* Works out top and height of the scrollbar */
static void LTable_GetScrollbarCoords(struct LTable* w, int* y, int* height) {
	float scale;
	if (!w->rowsCount) { *y = 0; *height = 0; return; }

	scale   = w->height / (float)w->rowsCount;
	*y      = Math_Ceil(w->topRow * scale);
	*height = Math_Ceil(w->visibleRows * scale);
	*height = min(*y + *height, w->height) - *y;
}

/* Draws background behind column headers */
static void LTable_DrawHeaderBackground(struct LTable* w) {
	BitmapCol gridColor = BitmapColor_RGB(20, 20, 10);

	if (!Launcher_Theme.ClassicBackground) {
		Context2D_Clear(&framebuffer, gridColor,
						w->x, w->y, w->width, w->hdrHeight);
	} else {
		Launcher_DrawBackground(&framebuffer,
						w->x, w->y, w->width, w->hdrHeight);
	}
}

static BitmapCol LBackend_TableRowColor(struct LTable* w, int row) {
	struct ServerInfo* entry = row < w->rowsCount ? LTable_Get(row) : NULL;
	cc_bool selected         = entry && String_Equals(&entry->hash, w->selectedHash);
	cc_bool featured         = entry && entry->featured;

	return LTable_RowColor(row, selected, featured);
}

/* Draws background behind each row in the table */
static void LTable_DrawRowsBackground(struct LTable* w) {
	int y, height, row;
	BitmapCol color;

	y = w->rowsBegY;
	for (row = w->topRow; ; row++, y += w->rowHeight) {
		color = LBackend_TableRowColor(w, row);

		/* last row may get chopped off */
		height = min(y + w->rowHeight, w->rowsEndY) - y;
		/* hit the end of the table */
		if (height < 0) break;

		if (color) {
			Context2D_Clear(&framebuffer, color,
							w->x, y, w->width, height);
		} else {
			Launcher_DrawBackground(&framebuffer, 
							w->x, y, w->width, height);
		}
	}
}

/* Draws a gridline below column headers and gridlines after each column */
static void LTable_DrawGridlines(struct LTable* w) {
	int i, x;
	if (Launcher_Theme.ClassicBackground) return;

	x = w->x;
	Context2D_Clear(&framebuffer, Launcher_Theme.BackgroundColor,
				   x, w->y + w->hdrHeight, w->width, gridlineHeight);

	for (i = 0; i < w->numColumns; i++) {
		x += w->columns[i].width;
		if (!w->columns[i].hasGridline) continue;
			
		Context2D_Clear(&framebuffer, Launcher_Theme.BackgroundColor,
					   x, w->y, gridlineWidth, w->height);
		x += gridlineWidth;
	}
}

/* Draws the entire background of the table */
static void LTable_DrawBackground(struct LTable* w) {
	LTable_DrawHeaderBackground(w);
	LTable_DrawRowsBackground(w);
	LTable_DrawGridlines(w);
}

/* Draws title of each column at top of the table */
static void LTable_DrawHeaders(struct LTable* w) {
	struct DrawTextArgs args;
	int i, x, y;

	DrawTextArgs_MakeEmpty(&args, &textFont, true);
	x = w->x; y = w->y;

	for (i = 0; i < w->numColumns; i++) {
		args.text = String_FromReadonly(w->columns[i].name);
		Drawer2D_DrawClippedText(&framebuffer, &args, 
								x + cellXOffset, y + hdrYOffset, 
								w->columns[i].width - cellXPadding);

		x += w->columns[i].width;
		if (w->columns[i].hasGridline) x += gridlineWidth;
	}
}

/* Draws contents of the currently visible rows in the table */
static void LTable_DrawRows(struct LTable* w) {
	cc_string str; char strBuffer[STRING_SIZE];
	struct ServerInfo* entry;
	struct DrawTextArgs args;
	struct LTableCell cell;
	int i, x, y, row, end;

	InitRowFont();
	String_InitArray(str, strBuffer);
	DrawTextArgs_Make(&args, &str, &rowFont, true);
	cell.table = w;
	y   = w->rowsBegY;
	end = w->topRow + w->visibleRows;

	for (row = w->topRow; row < end; row++, y += w->rowHeight) {
		x = w->x;

		if (row >= w->rowsCount)            break;
		if (y + w->rowHeight > w->rowsEndY) break;
		entry = LTable_Get(row);

		for (i = 0; i < w->numColumns; i++) {
			args.text  = str; cell.x = x; cell.y = y;
			cell.width = w->columns[i].width;
			w->columns[i].DrawRow(entry, &args, &cell, &framebuffer);

			if (args.text.length) {
				Drawer2D_DrawClippedText(&framebuffer, &args, 
										x + cellXOffset, y + rowYOffset, 
										cell.width - cellXPadding);
			}

			x += w->columns[i].width;
			if (w->columns[i].hasGridline) x += gridlineWidth;
		}
	}
}

/* Draws scrollbar on the right edge of the table */
static void LTable_DrawScrollbar(struct LTable* w) {
	BitmapCol classicBack   = BitmapColor_RGB( 80,  80,  80);
	BitmapCol classicScroll = BitmapColor_RGB(160, 160, 160);
	BitmapCol backCol   = Launcher_Theme.ClassicBackground ? classicBack   : Launcher_Theme.ButtonBorderColor;
	BitmapCol scrollCol = Launcher_Theme.ClassicBackground ? classicScroll : Launcher_Theme.ButtonForeActiveColor;

	int x, y, height;
	x = w->x + w->width - scrollbarWidth;
	LTable_GetScrollbarCoords(w, &y, &height);

	Context2D_Clear(&framebuffer, backCol,
					x, w->y,     scrollbarWidth, w->height);		
	Context2D_Clear(&framebuffer, scrollCol, 
					x, w->y + y, scrollbarWidth, height);
}

void LBackend_TableDraw(struct LTable* w) {
	LTable_DrawBackground(w);
	LTable_DrawHeaders(w);
	LTable_DrawRows(w);
	LTable_DrawScrollbar(w);
	LBackend_MarkAllDirty();
}


static void LTable_RowsClick(struct LTable* w, int idx) {
	int mouseY = Pointers[idx].y - w->rowsBegY;
	int row    = w->topRow + mouseY / w->rowHeight;
	LTable_RowClick(w, row);
}

/* Handles clicking on column headers (either resizes a column or sort rows) */
static void LTable_HeadersClick(struct LTable* w, int idx) {
	int x, i, mouseX = Pointers[idx].x;

	for (i = 0, x = w->x; i < w->numColumns; i++) {
		/* clicked on gridline, begin dragging */
		if (mouseX >= (x - dragPad) && mouseX < (x + dragPad) && w->columns[i].draggable) {
			w->draggingColumn = i - 1;
			w->dragXStart = (mouseX - w->x) - w->columns[i - 1].width;
			return;
		}

		x += w->columns[i].width;
		if (w->columns[i].hasGridline) x += gridlineWidth;
	}

	for (i = 0, x = w->x; i < w->numColumns; i++) {
		if (mouseX >= x && mouseX < (x + w->columns[i].width) && w->columns[i].sortable) {
			w->sortingCol = i;
			w->columns[i].invertSort = !w->columns[i].invertSort;
			LTable_Sort(w);
			return;
		}

		x += w->columns[i].width;
		if (w->columns[i].hasGridline) x += gridlineWidth;
	}
}

/* Handles clicking on the scrollbar on right edge of table */
static void LTable_ScrollbarClick(struct LTable* w, int idx) {
	int y, height, mouseY = Pointers[idx].y - w->y;
	LTable_GetScrollbarCoords(w, &y, &height);

	if (mouseY < y) {
		w->topRow -= w->visibleRows;
	} else if (mouseY >= y + height) {
		w->topRow += w->visibleRows;
	} else {
		w->dragYOffset = mouseY - y;
	}

	w->draggingScrollbar = true;
	LTable_ClampTopRow(w);
}

void LBackend_TableMouseDown(struct LTable* w, int idx) {
	if (Pointers[idx].x >= Window_Main.Width - scrollbarWidth) {
		LTable_ScrollbarClick(w, idx);
		w->_lastRow = -1;
	} else if (Pointers[idx].y < w->rowsBegY) {
		LTable_HeadersClick(w, idx);
		w->_lastRow = -1;
	} else {
		LTable_RowsClick(w, idx);
	}
	LBackend_NeedsRedraw(w);
}

void LBackend_TableMouseMove(struct LTable* w, int idx) {
	int x = Pointers[idx].x - w->x, y = Pointers[idx].y - w->y;
	int i, col, width, maxW;

	if (w->draggingScrollbar) {
		float scale = w->height / (float)w->rowsCount;
		int row     = (int)((y - w->dragYOffset) / scale);
		/* avoid expensive redraw when possible */
		if (w->topRow == row) return;

		w->topRow = row;
		LTable_ClampTopRow(w);
		LBackend_NeedsRedraw(w);
	} else if (w->draggingColumn >= 0) {
		col   = w->draggingColumn;
		width = x - w->dragXStart;

		/* Ensure this column doesn't expand past right side of table */
		maxW = w->width;
		for (i = 0; i < col; i++) maxW -= w->columns[i].width;

		Math_Clamp(width, cellMinWidth, maxW - cellMinWidth);
		if (width == w->columns[col].width) return;
		w->columns[col].width = width;
		LBackend_NeedsRedraw(w);
	}
}

/* Stops an in-progress dragging of resizing column. */
void LBackend_TableMouseUp(struct LTable* w, int idx) {
	w->draggingColumn    = -1;
	w->draggingScrollbar = false;
	w->dragYOffset       = 0;
}
#endif
