/*
 * 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 "listbox.h"


static uint16_t _halfFont    = 0;
static uint8_t  _visibleX    = 0;    // How many characters are visible in control
static uint8_t  _visibleY    = 0;    // How many characters are visible in control
static uint16_t _valueWidth  = 0;
static uint16_t _valueHeight = 0;
static uint16_t _valueTop    = 0;
static uint16_t _valueBottom = 0;
static uint16_t _arrowWidth  = 0;
static uint16_t _arrowStart  = 0;


static void listboxDel(WidgetT **widget);
static void listboxMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event);
static void listboxKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt);
static void listboxPaint(WidgetT *widget, uint8_t enabled, RectT pos);
static void listboxScrollDown(ListboxT *listbox);
static void listboxScrollUp(ListboxT *listbox);
static void listboxSizesRecalculate(ListboxT *listbox);


static void listboxDel(WidgetT **widget) {
	ListboxT *l = (ListboxT *)*widget;
	size_t    len = arrlenu(l->values);
	size_t    x;

	if (len > 0) {
		for (x=0; x<len; x++) {
			free(l->values[x]);
		}
	}
	arrfree(l->values);
}


uint16_t listboxIndexGet(ListboxT *listbox) {
	return listbox->selected;
}


void listboxIndexSet(ListboxT *listbox, uint16_t index) {
	if (index < arrlenu(listbox->values)) {
		listbox->selected = index;
		GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
	}
}


WidgetT *listboxInit(WidgetT *widget, char *title) {
	ListboxT *l = (ListboxT *)widget;

	l->base.delMethod           = listboxDel;
	l->base.keyboardEventMethod = listboxKeyboardEvent;
	l->base.paintMethod         = listboxPaint;
	l->base.mouseEventMethod    = listboxMouseEvent;
	l->title                    = NULL;
	l->values                   = NULL;
	l->selected                 = 0;
	l->step                     = 1;
	l->offset                   = 0;

	// Visibles are set in listboxSetTitle
	listboxTitleSet(l, title);

	return widget;
}


void listboxItemAdd(ListboxT *listbox, char *item) {
	arrput(listbox->values, strdup(item));
	GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
}


void listboxItemRemove(ListboxT *listbox, char *item) {
	size_t len = arrlenu(listbox->values);
	size_t x;

	if (len > 0) {
		for (x=0; x<len; x++) {
			if (strcmp(item, listbox->values[x]) == 0) {
				arrdel(listbox->values, x);
				if (listbox->selected > len || listbox->selected > 0) listbox->selected--;
				GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
				break;
			}
		}
	}
}


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

	ListboxT *l   = (ListboxT *)widget;
	int16_t   len = arrlenu(l->values);
	uint16_t  i;

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

	listboxSizesRecalculate(l);

	if (extended) {

		switch (ascii) {
			case 71:  // HOME
				l->selected = 0;
				l->offset   = 0;
				GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY);
				break;

			case 79:  // END
				// Are there more items than visible?
				if (len > _visibleY - 1) {
					// Yep.  Scroll & set selected.
					l->selected = _visibleY - 2;
					l->offset   = len - _visibleY + 1;
				} else {
					// No.  Just set selected.
					l->selected = len - 1;
					l->offset   = 0;
				}
				GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY);
				break;

			case 73:  // PAGEUP
				for (i=0; i<l->step; i++) listboxScrollUp(l);
				break;

			case 81:  // PAGEDOWN
				for (i=0; i<l->step; i++) listboxScrollDown(l);
				break;

			case 72:  // UP
				listboxScrollUp(l);
				break;

			case 80:  // DOWN
				listboxScrollDown(l);
				break;

		} // switch

	} // extended
}


static void listboxMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event) {
	ListboxT *l = (ListboxT *)widget;
	uint16_t  o;
	uint16_t  valueStart;
	int16_t   len;

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

	if (event == MOUSE_EVENT_LEFT_UP) {
		listboxSizesRecalculate(l);

		len = arrlenu(l->values);

		// Clicked item in listbox.
		valueStart = 1 + _guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING];
		if (x > valueStart && x < valueStart + _valueWidth - 1 && y > _valueTop + 1 && y < _valueBottom - 1) {
			o = (y - _valueTop) / fontHeightGet(_guiFont);
			if (o < len) l->selected = o;
			if (l->selected > _visibleY - 2) l->selected = _visibleY - 2;  // Two because 1 is to correct _visibleY and the other 1 is because selected is zero based.
			GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY);
		}

		_arrowStart += 1 + _guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING]; // Left of up arrow
		o = _valueTop + 1 + _guiMetric[METRIC_LISTBOX_VERTICAL_PADDING];  // Top of arrows

		// Did they click the up arrow?
		if (x >= _arrowStart && x <= _arrowStart + fontHeightGet(_guiFont) && y >= o && y <= o + fontHeightGet(_guiFont)) {
			listboxScrollUp(l);
		}

		// Did they click the down arrow?
		o = _valueBottom - 1 - _guiMetric[METRIC_LISTBOX_VERTICAL_PADDING];  // Bottom of down arrow
		if (x >= _arrowStart && x <= _arrowStart + fontHeightGet(_guiFont) && y >= o - fontHeightGet(_guiFont) && y <= o) {
			listboxScrollDown(l);
		}

	} // MOUSE_UP
}


ListboxT *listboxNew(uint16_t x, uint16_t y, uint16_t w, uint16_t h, char *title) {
	ListboxT *listbox = (ListboxT *)malloc(sizeof(ListboxT));
	WidgetT  *widget  = NULL;

	if (!listbox) return NULL;

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

	listbox = (ListboxT *)listboxInit((WidgetT *)listbox, title);

	return listbox;
}


static void listboxPaint(WidgetT *widget, uint8_t enabled, RectT pos) {
	ListboxT  *l = (ListboxT *)widget;
	uint16_t   items;
	uint16_t   o;
	uint16_t   i;
	int16_t    len;
	ColorT     color;

	listboxSizesRecalculate(l);

	// Move a few things into screen space, not widget space.
	_arrowStart  += pos.x;
	_valueTop    += pos.y;
	_valueBottom += pos.y;

	len = arrlenu(l->values);

	// How many items can we draw?
	items = len - l->offset;
	if (items > _visibleY - 1) items = _visibleY - 1;

	// Draw title.
	fontRender(_guiFont, l->title, _guiColor[COLOR_LISTBOX_TEXT],  _guiColor[COLOR_WINDOW_BACKGROUND], pos.x, pos.y);

	// Draw outline of listbox.
	surfaceHighlightFrameDraw(pos.x, _valueTop, pos.x + _valueWidth, _valueBottom, _guiColor[COLOR_LISTBOX_SHADOW], _guiColor[COLOR_LISTBOX_HIGHLIGHT]);

	// Draw background of listbox.
	surfaceRectangleFilledDraw(pos.x + 1, _valueTop + 1, pos.x + _valueWidth - 1, _valueBottom - 1, _guiColor[COLOR_LISTBOX_BACKGROUND]);

	// Draw listbox contents.
	o = _valueTop + 1 + _guiMetric[METRIC_LISTBOX_VERTICAL_PADDING];
	for (i=0; i<items; i++) {
		if (i == l->selected) {
			surfaceRectangleFilledDraw(pos.x + _guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING], o, pos.x + _valueWidth - _guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING], o + fontHeightGet(_guiFont) - 1, _guiColor[COLOR_LISTBOX_SELECTED_BACKGROUND]);
			fontRender(_guiFont, l->values[l->offset + i], _guiColor[COLOR_LISTBOX_SELECTED_TEXT],  _guiColor[COLOR_LISTBOX_SELECTED_BACKGROUND], pos.x + 1 + _guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING], o);
		} else {
			fontRender(_guiFont, l->values[l->offset + i], _guiColor[COLOR_LISTBOX_TEXT],  _guiColor[COLOR_LISTBOX_BACKGROUND], pos.x + 1 + _guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING], o);
		}
		o += fontHeightGet(_guiFont);
	}

	// Draw outline of arrows.
	surfaceHighlightFrameDraw(_arrowStart, _valueTop, _arrowStart + _arrowWidth, _valueBottom, _guiColor[COLOR_LISTBOX_SHADOW], _guiColor[COLOR_LISTBOX_HIGHLIGHT]);

	// Draw background of arrows.
	surfaceRectangleFilledDraw(_arrowStart + 1, _valueTop + 1, _arrowStart + _arrowWidth - 1, _valueBottom - 1, _guiColor[COLOR_LISTBOX_ARROWS_BACKGROUND]);

	// Draw up arrow
	_arrowStart += _halfFont + 1 + _guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING];  // Center of up arrow
	o = _valueTop + 1 + _guiMetric[METRIC_LISTBOX_VERTICAL_PADDING];  // Top of up arrow
	color = l->offset + l->selected > 0 ? _guiColor[COLOR_LISTBOX_ARROWS_ACTIVE] : _guiColor[COLOR_LISTBOX_ARROWS_INACTIVE];
	for (i=0; i<=fontHeightGet(_guiFont); i++) {
		surfaceLineDraw(_arrowStart - i * 0.5, o + i, _arrowStart + i * 0.5, o + i, color);
	}
	surfaceLineDraw(_arrowStart, o, _arrowStart + _halfFont, o + fontHeightGet(_guiFont), _guiColor[COLOR_LISTBOX_SHADOW]);
	surfaceLineDraw(_arrowStart - _halfFont, o + fontHeightGet(_guiFont), _arrowStart + _halfFont, o + fontHeightGet(_guiFont), _guiColor[COLOR_LISTBOX_SHADOW]);
	surfaceLineDraw(_arrowStart, o, _arrowStart - _halfFont, o + fontHeightGet(_guiFont), _guiColor[COLOR_LISTBOX_HIGHLIGHT]);

	// Draw down arrow
	o = _valueBottom - 1 - _guiMetric[METRIC_LISTBOX_VERTICAL_PADDING];  // Bottom of down arrow
	color = l->offset + l->selected < len - 1 ? _guiColor[COLOR_UPDOWN_ARROWS_ACTIVE] : _guiColor[COLOR_UPDOWN_ARROWS_INACTIVE];
	for (i=0; i<=fontHeightGet(_guiFont); i++) {
		surfaceLineDraw(_arrowStart - i * 0.5, o - i, _arrowStart + i * 0.5, o - i, color);
	}
	surfaceLineDraw(_arrowStart, o, _arrowStart + _halfFont, o - fontHeightGet(_guiFont), _guiColor[COLOR_UPDOWN_SHADOW]);
	surfaceLineDraw(_arrowStart - _halfFont, o - fontHeightGet(_guiFont), _arrowStart + _halfFont, o - fontHeightGet(_guiFont), _guiColor[COLOR_UPDOWN_HIGHLIGHT]);
	surfaceLineDraw(_arrowStart, o, _arrowStart - _halfFont, o - fontHeightGet(_guiFont), _guiColor[COLOR_UPDOWN_HIGHLIGHT]);
}


static void listboxScrollDown(ListboxT *listbox) {
	int16_t len = arrlenu(listbox->values);

	// Can we increment?
	if (listbox->selected < _visibleY - 2) {
		// Is it off the end of the list?
		if (listbox->offset + listbox->selected < len - 1) {
			listbox->selected++;
			GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
		}
	} else {
		// Can we scroll the list down?
		if (listbox->offset + listbox->selected < len - 1) {
			listbox->offset++;
			GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
		}
	}
}


static void listboxScrollUp(ListboxT *listbox) {
	// Can we decrement?
	if (listbox->selected > 0) {
		listbox->selected--;
		GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
	} else {
		// Can we scroll the list up?
		if (listbox->offset > 0) {
			listbox->offset--;
			GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
		}
	}
}


static void listboxSizesRecalculate(ListboxT *listbox) {

	// Arrow box width is border + (hpadding*2) + fontHeight
	// Label is drawn at x,y
	// One line horizontal gap between label and border
	// One line vertical gap between value and arrow box
	// Value border is drawn at x+1, y+2 to x+w-2-arrowWidth, y+h-2
	// Value is x+1+hpadding, y+2+vpadding to x+w-1-(hpadding*2)-arrowWidth, y+h-2-(vpadding*2)
	// arrowStart is x+1+valueWidth

	// Set global stuff that isn't actually part of the widget state.
	_halfFont    = fontHeightGet(_guiFont) * 0.5;
	_arrowWidth  = 2 + (_guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING] * 2) + fontHeightGet(_guiFont);  // Arrow width = font height
	_valueWidth  = listbox->base.pos.w - 1 - 1 - (_guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING] * 2) - _arrowWidth;
	_valueHeight = listbox->base.pos.h - 2 - (_guiMetric[METRIC_LISTBOX_VERTICAL_PADDING] * 2);
	_valueTop    = fontHeightGet(_guiFont) + 1;
	_valueBottom = _valueHeight;
	_arrowStart  = 2 + _valueWidth;

	// Figure out how many characters we have room to display.
	_visibleX = (_valueWidth - 2 + (_guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING] * 2)) / fontWidthGet(_guiFont);
	_visibleY = (_valueHeight - 2 + (_guiMetric[METRIC_LISTBOX_VERTICAL_PADDING] * 2)) / fontHeightGet(_guiFont);

}


void listboxStepSet(ListboxT *listbox, int32_t step) {
	listbox->step = step;
}


void listboxTitleSet(ListboxT *listbox, char *title) {
	if (listbox->title) free(listbox->title);
	listbox->title = strdup(title);
	listboxSizesRecalculate(listbox);
	GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
}


char *listboxValueGet(ListboxT *listbox) {
	return listbox->values[listbox->selected];
}


void listboxValueSet(ListboxT *listbox, char *value) {
	size_t len = arrlenu(listbox->values);
	size_t x;

	if (len > 0) {
		for (x=0; x<len; x++) {
			if (strcmp(value, listbox->values[x]) == 0) {
				listbox->selected = x;
				GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
				break;
			}
		}
	}
}
