/*
 * 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/>.
 *
 */


#include "textbox.h"
#include "timer.h"


static void textboxDel(WidgetT **widget);
static void textboxFocusEvent(WidgetT *widget, uint8_t focused);
static void textboxMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event);
static void textboxKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt);
static void textboxPaint(WidgetT *widget, uint8_t enabled, RectT pos);


static void textboxDel(WidgetT **widget) {
	TextboxT *t = (TextboxT *)*widget;

	if (t->title) free(t->title);
	if (t->value) free(t->value);
}


static void textboxFocusEvent(WidgetT *widget, uint8_t focused) {
	// Make sure cursor disappears when we lose focus.
	if (!focused) {
		GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY);
	}
}


WidgetT *textboxInit(WidgetT *widget, char *title) {
	TextboxT *t = (TextboxT *)widget;

	t->base.delMethod           = textboxDel;
	t->base.focusMethod         = textboxFocusEvent;
	t->base.keyboardEventMethod = textboxKeyboardEvent;
	t->base.paintMethod         = textboxPaint;
	t->base.mouseEventMethod    = textboxMouseEvent;
	t->title                    = NULL;
	t->maxLength                = 256;
	t->value                    = (char *)malloc(t->maxLength);
	t->caret                    = 0;
	t->offset                   = 0;
	t->password                 = 0;

	if (!t->value) return NULL;
	t->value[0] = 0;

	// Visible is set in textboxSetTitle
	textboxTitleSet(t, title);

	// We need to blink a cursor.
	GUI_SET_FLAG(widget, WIDGET_FLAG_ALWAYS_PAINT);

	return widget;
}


static void textboxKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt) {

	TextboxT *t = (TextboxT *)widget;
	uint16_t  x;
	uint16_t  y;
	char     *temp;

	(void)scancode;
	(void)shift;
	(void)control;
	(void)alt;

	if (extended) {

		switch (ascii) {
			case 71:  // HOME
				t->caret  = 0;
				t->offset = 0;
				break;

			case 79:  // END
				// We cheat and just reset the value.  That moves the offset and cursor.
				temp = strdup(t->value);
				textboxValueSet(t, temp);
				free(temp);
				break;

			case 75:  // LEFT
				// Can we move left in the value?
				if (t->caret + t->offset > 0) {
					// Is the caret on the left edge of the box?
					if (t->caret == 0) {
						// Can we move the string offset?
						if (t->offset > 0) {
							t->offset--;
						}
					} else {
						// Move the caret left.
						t->caret--;
					}
				}
				break;

			case 77:  // RIGHT
				// Can we move right in the value?
				if (t->caret + t->offset < strlen(t->value)) {
					// Is the caret on the right edge of the box?
					if (t->caret == t->visible - 1) {
						// Can we move the string offset?
						if (t->offset < strlen(t->value)) {
							t->offset++;
						}
					} else {
						// Move the caret right.
						t->caret++;
					}
				}
				break;

			case 83:  // DELETE
				// Is there more data to the right in the value?
				if (t->caret + t->offset < strlen(t->value)) {
					// Delete character under caret.
					for (x = t->caret + t->offset; x<strlen(t->value); x++) {
						t->value[x] = t->value[x+1];
					}
				}
				break;

		}  // switch

	} else {  // extended

		switch (ascii) {
			case 8:  // BACKSPACE
				// Can we delete left in the value?
				if (t->caret + t->offset > 0) {
					// Is the caret on the left edge of the box?
					if (t->caret == 0) {
						// Can we move the string offset?
						if (t->offset > 0) {
							t->offset--;
						}
					} else {
						// Move the caret left.
						t->caret--;
					}
					// Delete the character to the left of the caret.
					for (x = t->caret + t->offset; x<strlen(t->value); x++) {
						t->value[x] = t->value[x+1];
					}
				}
				break;

			default:  // Other keys
				if (ascii >= 32 && ascii <= 126) {
					// Remember length so we can zero terminate this after the edit.
					y = strlen(t->value);
					// Insert character, if room.
					if (y < (size_t)t->maxLength - 1) {
						// Move existing characters over, if needed.
						if (t->caret + t->offset < y) {
							for (x=y + 1; x>t->caret + t->offset; x--) {
								t->value[x] = t->value[x-1];
							}
						}
						// Place typed character at caret.
						t->value[t->caret + t->offset] = ascii;
						y++;
						// Fix zero termination.
						t->value[y] = 0;
						// Is the caret on the right edge of the box?
						if (t->caret == t->visible - 1) {
							// Can we move the string offset?
							if (t->offset < y) {
								t->offset++;
							}
						} else {
							// Move the caret right.
							t->caret++;
						}
					}
				}
				break;

		} // switch

	} // extended
}


void textboxLengthMaxSet(TextboxT *textbox, uint16_t length) {
	char *temp = strdup(textbox->value);

	free(textbox->value);
	textbox->maxLength = length + 1;
	textbox->value = (char *)malloc(textbox->maxLength);
	textboxValueSet(textbox, temp);
	free(temp);
}


static void textboxMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event) {
	TextboxT *t = (TextboxT *)widget;
	RectT     textArea;

	(void)x;
	(void)y;
	(void)mouse;

	// Allow dragging/positioning text cursor with mouse.
	if (event == MOUSE_EVENT_LEFT_HOLD) {
		// Where's the text display?
		textArea.x = (strlen(t->title) * fontWidthGet(_guiFont)) + _guiMetric[METRIC_TEXTBOX_PADDING] + 2 + _guiMetric[METRIC_TEXTBOX_HORIZONTAL_PADDING];
		textArea.y = 2 + _guiMetric[METRIC_TEXTBOX_VERTICAL_PADDING];
		textArea.w = textArea.x + t->visible * fontWidthGet(_guiFont);
		textArea.h = textArea.y + fontHeightGet(_guiFont);
		// Is the pointer over it?
		if (x >= textArea.x && x < textArea.w && y >= textArea.y && y < textArea.h) {
			// Move caret.
			t->caret = (x - textArea.x) / fontWidthGet(_guiFont);
			// Did we go too far?
			if (t->caret + t->offset > strlen(t->value)) {
				t->caret = strlen(t->value) - t->offset;
			}
			// Ensure we redraw.
			GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY);
		}
	}
}


TextboxT *textboxNew(uint16_t x, uint16_t y, uint16_t w, char *title) {
	TextboxT *textbox = (TextboxT *)malloc(sizeof(TextboxT));
	WidgetT  *widget  = NULL;
	uint16_t  h       = fontHeightGet(_guiFont) + 4 + (_guiMetric[METRIC_TEXTBOX_VERTICAL_PADDING] * 2);

	if (!textbox) return NULL;

	widget = widgetInit(W(textbox), MAGIC_TEXTBOX, x, y, w, h, 0, 0, 0, 0);
	if (!widget) {
		free(textbox);
		return NULL;
	}

	textbox = (TextboxT *)textboxInit((WidgetT *)textbox, title);

	return textbox;
}


static void textboxPaint(WidgetT *widget, uint8_t enabled, RectT pos) {
	TextboxT *t          = (TextboxT *)widget;
	char     *draw       = NULL;
	uint16_t  labelWidth;
	uint16_t  valueWidth;
	uint16_t  caretPos;
	uint16_t  textX;
	uint16_t  textY;
	char      cursor[2] = { 0xb1, 0 };


	if (GUI_GET_FLAG(widget, WIDGET_FLAG_DIRTY) || guiFocusGet() == widget) {
		labelWidth = (strlen(t->title) * fontWidthGet(_guiFont)) + _guiMetric[METRIC_TEXTBOX_PADDING];
		valueWidth = (t->visible * fontWidthGet(_guiFont)) + (_guiMetric[METRIC_TEXTBOX_HORIZONTAL_PADDING] * 2);

		// Draw title.
		fontRender(_guiFont, t->title, _guiColor[COLOR_TEXTBOX_TEXT],  _guiColor[COLOR_WINDOW_BACKGROUND], pos.x, pos.y + 2 + _guiMetric[METRIC_TEXTBOX_VERTICAL_PADDING]);

		// Draw outline of textbox.
		surfaceHighlightFrameDraw( pos.x + labelWidth,     pos.y,     pos.x + labelWidth + valueWidth + 2, pos.y + pos.h,     _guiColor[COLOR_TEXTBOX_SHADOW], _guiColor[COLOR_TEXTBOX_HIGHLIGHT]);
		surfaceRectangleDraw(      pos.x + labelWidth + 1, pos.y + 1, pos.x + labelWidth + valueWidth + 1, pos.y + pos.h - 1, _guiColor[COLOR_WINDOW_BACKGROUND]);

		// Draw background.
		surfaceRectangleFilledDraw(pos.x + labelWidth + 2, pos.y + 2, pos.x + labelWidth + valueWidth,     pos.y + pos.h - 2, _guiColor[COLOR_TEXTBOX_BACKGROUND]);

		// Where's the text display start?
		textX = pos.x + labelWidth + 2 + _guiMetric[METRIC_TEXTBOX_HORIZONTAL_PADDING];
		textY = pos.y + 2 + _guiMetric[METRIC_TEXTBOX_VERTICAL_PADDING];

		// Draw value.
		draw = strdup(&t->value[t->offset]);
		if (strlen(draw) > t->visible) draw[t->visible] = 0;
		if (t->password != 0) memset(draw, t->password, strlen(draw));
		fontRender(_guiFont, draw, _guiColor[COLOR_TEXTBOX_TEXT], _guiColor[COLOR_TEXTBOX_BACKGROUND], textX, textY);
		free(draw);

		// Draw cursor.
		if (guiFocusGet() == widget && guiTimerQuarterSecondOn()) {
			caretPos = textX + fontWidthGet(_guiFont) * t->caret;
			fontRender(_guiFont, cursor, _guiColor[COLOR_TEXTBOX_TEXT], _guiColor[COLOR_TEXTBOX_BACKGROUND], caretPos, textY);
		}

		GUI_CLEAR_FLAG(widget, WIDGET_FLAG_DIRTY);
	}
}


void textboxPasswordCharacterSet(TextboxT *textbox, char c) {
	textbox->password = c;
}


void textboxTitleSet(TextboxT *textbox, char *title) {
	if (textbox->title) free(textbox->title);
	textbox->title = strdup(title);

	// Figure out how many characters we have room to display.
	textbox->visible = (textbox->base.pos.w - ((strlen(title) * fontWidthGet(_guiFont)) + _guiMetric[METRIC_TEXTBOX_PADDING] + 4 + (_guiMetric[METRIC_TEXTBOX_HORIZONTAL_PADDING] * 2))) / fontWidthGet(_guiFont);

	GUI_SET_FLAG((WidgetT *)textbox, WIDGET_FLAG_DIRTY);
}


char *textboxValueGet(TextboxT *textbox) {
	return textbox->value;
}


void textboxValueSet(TextboxT *textbox, char *value) {
	// Copy it & truncate if needed.
	strncpy(textbox->value, value, textbox->maxLength - 1);

	// Is this longer than the area we have to display it in?
	if (strlen(textbox->value) > (size_t)(textbox->visible - 1)) {
		textbox->offset = strlen(textbox->value) - (textbox->visible - 1);
	}

	// Set caret position.
	textbox->caret = strlen(&textbox->value[textbox->offset]);

	GUI_SET_FLAG((WidgetT *)textbox, WIDGET_FLAG_DIRTY);
}
