/*
 * This file is subject to the terms of the GFX License. If a copy of
 * the license was not distributed with this file, you can obtain one at:
 *
 *              http://ugfx.org/license.html
 */

/**
 * @file	src/gwin/gwin_textedit.c
 * @brief	GWIN TextEdit widget header file
 */

#include "../../gfx.h"

#if GFX_USE_GWIN && GWIN_NEED_TEXTEDIT

#include "gwin_class.h"
#include <string.h>

// Some settings
#define TEXT_PADDING_LEFT		4
#define CURSOR_PADDING_LEFT		0
#define CURSOR_EXTRA_HEIGHT		1

// Macros to assist in data type conversions
#define gh2obj ((GTexteditObject *)gh)
#define gw2obj ((GTexteditObject *)gw)

static bool_t resizeText(GWidgetObject* gw, size_t pos, int32_t diff) {
	char	*p, *q;
	size_t	sz;

	p = (char *)gw->text;
	sz = strlen(p)+1;
	if (diff < 0)
		memcpy(p+pos, p+pos-diff, sz-pos+diff);
	if (!(p = gfxRealloc(p, sz, sz+diff)))
		return FALSE;
	gw->text = p;
	if (diff > 0) {
		q = p + sz;
		p += pos;
		while(--q >= p)
			q[diff] = q[0];
	}
	return TRUE;
}

// Function that allows to set the cursor to any position in the string
// This should be optimized. Currently it is an O(n^2) problem and therefore very
// slow. An optimized version would copy the behavior of mf_get_string_width()
// and do the comparation directly inside of that loop so we only iterate
// the string once.
static void TextEditMouseDown(GWidgetObject* gw, coord_t x, coord_t y) {
	uint16_t i = 0;

	(void)y;

	// Directly jump to the end of the string
	if (x > gdispGetStringWidth(gw->text, gw->g.font)) {
		gw2obj->cursorPos = strlen(gw->text);

	// Otherwise iterate through each character and get the size in pixels to compare
	} else {
		i = 1;
		while (gdispGetStringWidthCount(gw->text, gw->g.font, i) < x) {
			i++;
		}

		gw2obj->cursorPos = i-1;
	}

	_gwinUpdate((GHandle)gw);
}

#if (GFX_USE_GINPUT && GINPUT_NEED_KEYBOARD) || GWIN_NEED_KEYBOARD
	static void TextEditKeyboard(GWidgetObject* gw, GEventKeyboard* pke) {
		// Only react on KEYDOWN events. Ignore KEYUP events.
		if ((pke->keystate & GKEYSTATE_KEYUP) || !pke->bytecount)
			return;

		// Is it a special key?
		if (pke->keystate & GKEYSTATE_SPECIAL) {

			// Arrow keys to move the cursor
			switch ((uint8_t)pke->c[0]) {
			case GKEY_LEFT:
				if (!gw2obj->cursorPos)
					return;
				gw2obj->cursorPos--;
				break;
			case GKEY_RIGHT:
				if (!gw->text[gw2obj->cursorPos])
					return;
				gw2obj->cursorPos++;
				break;
			case GKEY_HOME:
				if (!gw2obj->cursorPos)
					return;
				gw2obj->cursorPos = 0;
				break;
			case GKEY_END:
				if (!gw->text[gw2obj->cursorPos])
					return;
				gw2obj->cursorPos = strlen(gw->text);
				break;
			default:
				return;
			}

		} else {

			// Normal key press
			switch((uint8_t)pke->c[0]) {
			case GKEY_BACKSPACE:
				// Backspace
				if (!gw2obj->cursorPos)
					return;
				gw2obj->cursorPos--;
				resizeText(gw, gw2obj->cursorPos, -1);
				break;
			case GKEY_TAB:
			case GKEY_LF:
			case GKEY_CR:
				// Move to the next field
				_gwinMoveFocus();
				return;
			case GKEY_DEL:
				// Delete
				if (!gw->text[gw2obj->cursorPos])
					return;
				resizeText(gw, gw2obj->cursorPos, -1);
				break;
			default:
				// Ignore any other control characters
				if ((uint8_t)pke->c[0] < GKEY_SPACE)
					return;

				// Keep the edit length to less than the maximum
				if (gw2obj->maxSize && gw2obj->cursorPos+pke->bytecount > gw2obj->maxSize)
					return;

				// Make space
				resizeText(gw, gw2obj->cursorPos, pke->bytecount);

				// Insert the character
				memcpy((char *)gw->text+gw2obj->cursorPos, pke->c, pke->bytecount);
				gw2obj->cursorPos += pke->bytecount;
				break;
			}
		}

		_gwinUpdate((GHandle)gw);
	}
#endif

static const gwidgetVMT texteditVMT = {
	{
		"TextEdit",					// The class name
		sizeof(GTexteditObject),	// The object size
		_gwidgetDestroy,			// The destroy routine
		_gwidgetRedraw, 			// The redraw routine
		0,							// The after-clear routine
	},
	gwinTexteditDefaultDraw,		// default drawing routine
	#if GINPUT_NEED_MOUSE
		{
			TextEditMouseDown,		// Process mouse down events (NOT USED)
			0,						// Process mouse up events (NOT USED)
			0,						// Process mouse move events (NOT USED)
		},
	#endif
	#if (GFX_USE_GINPUT && GINPUT_NEED_KEYBOARD) || GWIN_NEED_KEYBOARD
		{
			TextEditKeyboard		// Process keyboard key down events
		},
	#endif
	#if GINPUT_NEED_TOGGLE
		{
			0,						// No toggle role
			0,						// Assign Toggles (NOT USED)
			0,						// Get Toggles (NOT USED)
			0,						// Process toggle off event (NOT USED)
			0,						// Process toggle on event (NOT USED)
		},
	#endif
	#if GINPUT_NEED_DIAL
		{
			0,						// No dial roles
			0,						// Assign Dials (NOT USED)
			0, 						// Get Dials (NOT USED)
			0,						// Procees dial move events (NOT USED)
		},
	#endif
};

GHandle gwinGTexteditCreate(GDisplay* g, GTexteditObject* wt, GWidgetInit* pInit, size_t maxSize)
{
	char	*p;

	// Create the underlying widget
	if (!(wt = (GTexteditObject*)_gwidgetCreate(g, &wt->w, pInit, &texteditVMT)))
		return 0;

	wt->maxSize = maxSize;

	// Reallocate the text (if necessary)
	if (!(wt->w.g.flags & GWIN_FLG_ALLOCTXT)) {
		if (!(p = gfxAlloc(wt->maxSize+1)))
			return 0;
		strncpy(p, wt->w.text, wt->maxSize);
		wt->w.text = p;
		wt->w.g.flags |= GWIN_FLG_ALLOCTXT;
	}

	// Set text and cursor position
	wt->cursorPos = strlen(wt->w.text);

	gwinSetVisible(&wt->w.g, pInit->g.show);

	return (GHandle)wt;
}

void gwinTexteditDefaultDraw(GWidgetObject* gw, void* param)
{
	const char*			p;
	coord_t				cpos, tpos;
	const GColorSet*	pcol;

	(void)param;

	// Is it a valid handle?
	if (gw->g.vmt != (gwinVMT*)&texteditVMT)
		return;

	// Retrieve colors
	if ((gw->g.flags & GWIN_FLG_SYSENABLED))
		pcol = &gw->pstyle->enabled;
	else
		pcol = &gw->pstyle->disabled;

	// Adjust the text position so the cursor fits in the window
	p = gw->text;
	if (!gw2obj->cursorPos)
		tpos = 0;
	else {
		for(cpos = gw2obj->cursorPos; ; p++, cpos--) {
			tpos = gdispGetStringWidthCount(p, gw->g.font, cpos);
			if (tpos < gw->g.width-(TEXT_PADDING_LEFT+CURSOR_PADDING_LEFT))
				break;
		}
	}

	// Render background and string
	#if TEXT_PADDING_LEFT
		gdispGFillArea(gw->g.display, gw->g.x, gw->g.y, TEXT_PADDING_LEFT, gw->g.height, pcol->fill);
	#endif
	gdispGFillStringBox(gw->g.display, gw->g.x + TEXT_PADDING_LEFT, gw->g.y, gw->g.width-TEXT_PADDING_LEFT, gw->g.height, p, gw->g.font, pcol->text, pcol->fill, justifyLeft);

	// Render cursor (if focused)
	if (gwinGetFocus() == (GHandle)gw) {
		// Calculate cursor stuff

		// Draw cursor
		tpos += gw->g.x + CURSOR_PADDING_LEFT + TEXT_PADDING_LEFT + gdispGetFontMetric(gw->g.font, fontBaselineX)/2;
		cpos = (gw->g.height - gdispGetFontMetric(gw->g.font, fontHeight))/2 - CURSOR_EXTRA_HEIGHT;
		gdispGDrawLine(gw->g.display, tpos, gw->g.y + cpos, tpos, gw->g.y + gw->g.height - cpos, pcol->edge);
	}

	// Render border
	gdispGDrawBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, pcol->edge);

	// Render highlighted border if focused
	_gwidgetDrawFocusRect(gw, 0, 0, gw->g.width, gw->g.height);

}

#undef gh2obj
#undef gw2obj

#endif // GFX_USE_GWIN && GWIN_NEED_TEXTEDIT