#include "Gui.h"

#include <SDL.h>

#include "Bitmap.h"
#include "ExMath.h"
#include "Game.h"
#include "Log.h"

#define INTERACTABLE(type) (type == WIDGET_BUTTON  || type == WIDGET_SELECT || type == WIDGET_SLIDER)

static int switchSnd = -1;
static int selectSound = -1;
static int switchTimeout; // Prevents switch sound from playing right after switching to a new menu

int Gui_FirstInteractable(struct GuiMenu *menu)
{
	return Gui_NextInteractable(menu, menu->numWidgets);
}

int Gui_PrevInteractable(struct GuiMenu *menu, int start)
{
	int i;
	for (i = start - 1; i >= 0; i--)
	{
		if (!INTERACTABLE(menu->widgets[i].type))
			continue;
		return i;
	}
	if (i != menu->numWidgets)
		return Gui_PrevInteractable(menu, menu->numWidgets);
	return start;
}

int Gui_NextInteractable(struct GuiMenu *menu, int start)
{
	int i;
	for (i = start + 1; i < menu->numWidgets; i++)
	{
		if (!INTERACTABLE(menu->widgets[i].type))
			continue;
		return i;
	}
	if (i != 0)
		return Gui_NextInteractable(menu, 0);
	return start;
}

void Gui_SelectWidget(struct GuiMenu *menu, int i)
{
	if (i != menu->selected)
	{
		if (switchTimeout)
			switchTimeout = 0;
		else
		{
			if (switchSnd == -1)
				switchSnd = Audio_GetIDForName("SfxMenuSwitch");
			Audio_PlaySound(switchSnd);
		}
	}
	menu->selected = i;
}

void Gui_CloseMenu(struct GuiMenu *menu)
{
	menu->clickProc(GUI_CLOSEMENU);
}

/* Number of pixels of spacing between widgets */
#define GUI_WIDGETSPACING 16

#define GUI_SLIDERWIDTH 64

void Gui_LayoutMenu(struct GuiMenu *menu, Bmp_t *screen)
{
	int i;

	menu->width = 0;
	menu->height = 0;

	Bmp_SetActiveFont(BMPFONT_MINISERIF);

	/* First pass, find the size of each elements as well as the total menu size */
	for (i = 0; i < menu->numWidgets; i++)
	{
		struct GuiWidget *widget = &menu->widgets[i];

		menu->height += GUI_WIDGETSPACING;

		switch (widget->type)
		{
		case WIDGET_TEXT:
			Bmp_TextSize((const char *)widget->data, &widget->w, &widget->h, screen->w);
			widget->y = menu->height;
			break;
		case WIDGET_BUTTON:
			widget->w = 120;
			// TODO: This is half the size that it should be, but increasing it fucks up the layout.
			//         I HATE MENUS AND GUI
			widget->h = 8;
			widget->y = menu->height;
			break;
		case WIDGET_IMAGE: {
			const Bmp_t *widgetImg  = (const Bmp_t *)widget->data;
			widget->w = widgetImg->w;
			widget->h = widgetImg->h;
			widget->y = menu->height;
			break;
		}
		case WIDGET_SLIDER:
			widget->w = GUI_SLIDERWIDTH;
			widget->h = 8;
			widget->y = menu->height;
			break;
		case WIDGET_SELECT:
			/* TODO: This should instead use the width of the longest string in the array */
			widget->w = 140;
			widget->h = 10;
			widget->y = menu->height;
			break;
		default:
			break;
		}

		if (widget->w > menu->width)
			menu->width = widget->w;
		menu->height += widget->h;
	}

	menu->height += GUI_WIDGETSPACING;

	/* Second pass, center each widget within the menu now that we know the total width */
	for (i = 0; i < menu->numWidgets; i++)
	{
		struct GuiWidget *widget = &menu->widgets[i];
		widget->x = (menu->width - widget->w) / 2;
	}

	menu->xOffs = (screen->w - menu->width) / 2;
	menu->yOffs = (screen->h - menu->height) / 3;

	menu->layoutComputed = true;
}

void Gui_HandleLeftRight(struct GuiMenu *menu, int value)
{
	struct GuiWidget *widget = &menu->widgets[menu->selected];
	switch (widget->type)
	{
	case WIDGET_SELECT: {
		struct GuiSelectData *data = (struct GuiSelectData *)widget->data;
		data->selected += value;
		if (data->selected < 0) data->selected = 0;
		if (data->selected >= data->numoptions) data->selected = data->numoptions - 1;
		break;
	}
	case WIDGET_SLIDER: {
		float *sliderValue = (float *)widget->data;
		*sliderValue += value * 0.05f;
		if (*sliderValue < 0.0f) *sliderValue = 0.0f;
		if (*sliderValue > 1.0f) *sliderValue = 1.0f;
		break;
	}
	default:
		break;
	}
}

/* Keep track of the slider currently being dragged, so it can continue to be updated once the mouse leaves its bounding box. */
static struct GuiWidget *draggingSlider = NULL;

void Gui_HandleMouseMove(struct GuiMenu *menu, bool buttonDown, int mouseX, int mouseY)
{
	int i;
	for (i = 0; i < menu->numWidgets; i++)
	{
		struct GuiWidget *widget = &menu->widgets[i];
		int xx = widget->x + menu->xOffs;
		int yy = widget->y + menu->yOffs;

	// fix for button layout garbage
		int crap = widget->w;
		if (widget->type == WIDGET_BUTTON)
			crap = 16;

		if ((mouseX >= xx && mouseX <= (xx + widget->w))
			&& (mouseY >= yy && mouseY <= (yy + crap))
			&& INTERACTABLE(widget->type))
		{
			Gui_SelectWidget(menu, i);

			if (!draggingSlider && buttonDown && widget->type == WIDGET_SLIDER)
			{
				draggingSlider = widget;
			}
		}
	}

	if (draggingSlider)
	{
		if (!buttonDown)
		{
			draggingSlider = NULL;
		}
		else
		{
			float dragValue = (float)(mouseX - (draggingSlider->x + menu->xOffs)) / draggingSlider->w;
			if (dragValue < 0.0f)
				dragValue = 0.0f;
			if (dragValue > 1.0f)
				dragValue = 1.0f;
			*(float *)draggingSlider->data = dragValue;
		}
	}
}

#define GUI_COLOUR_DARKBG    0xa0000000
#define GUI_COLOUR_FILLBG    0xff000000
#define GUI_COLOUR_TEXT      0xffe0e0e0
#define GUI_COLOUR_BUTTON    0xff100000
#define GUI_COLOUR_BUTTONHVR 0xffffffff

static Bmp_t buttonGfx0;
static Bmp_t buttonGfx1;
void Gui_LoadButtonGraphicsRemoveThisFunctionLaterPleaseAndThankYou(const RPKFile *rpk)
{
	buttonGfx0 = Bmp_CreateFromPAKEntry(rpk, "button0");
	buttonGfx1 = Bmp_CreateFromPAKEntry(rpk, "button1");
	// TODO  MEMORY LEAK! 
}

static char sliderPercentStr[5];

void Gui_DrawMenu(struct GuiMenu *menu, Bmp_t *screen)
{
	int i;

	if (!menu->layoutComputed)
		menu->layoutProc(menu, screen);

	switch (menu->bgType)
	{
	case GUIBG_RECT:
		Bmp_FillRegion(screen, GUI_COLOUR_DARKBG, menu->xOffs, menu->yOffs, menu->width, menu->height);
		break;
	case GUIBG_DARKENSCREEN:
		Bmp_FillRegion(screen, GUI_COLOUR_DARKBG, 0, 0, screen->w, screen->h);
		break;
	case GUIBG_FILLBG:
		Bmp_Clear(screen, GUI_COLOUR_FILLBG);
		break;
	case GUIBG_NONE:
	default:
		break;
	}

	Bmp_SetActiveFont(BMPFONT_MINISERIF);
	for (i = 0; i < menu->numWidgets; i++)
	{
		const struct GuiWidget *widget = &menu->widgets[i];
		int xx = widget->x + menu->xOffs;
		int yy = widget->y + menu->yOffs;

		bool isSelected= menu->selected == i;
		switch (widget->type)
		{
		case WIDGET_TEXT:
			Bmp_DrawText(screen, (const char *)widget->data, GUI_COLOUR_TEXT, xx, yy, screen->w);
			break;
		case WIDGET_BUTTON:
		// CRAN BUTTON SPECIFICATIONS:
		// -the first and last 8 pixels are for the border and the middle gets looped as a 16x16 block
		// -button0 turns to button1 when hovered on
		// -normal text colour is black or dark red and the hover colour is either white or the current pink
		// -buttons transluscent by 50% or something so you can peep the fire and stuff from behind it
			{
			Bmp_t *btn = isSelected ? &buttonGfx1 : &buttonGfx0;
			int remainingWidth = widget->w - 16;
			int xoff = xx + 8;
			Bmp_BlitRegion(screen, btn, xx, yy, 0, 0, 8, btn->h);
			while (remainingWidth > 0) {
				int pieceWidth = 16;
				if (pieceWidth > remainingWidth)
					pieceWidth = remainingWidth;
				Bmp_BlitRegion(screen, btn, xoff, yy, 8, 0, pieceWidth, btn->h);
				xoff += pieceWidth;
				remainingWidth -= pieceWidth;
			}
			Bmp_BlitRegion(screen, btn, xoff, yy, 24, 0, 8, btn->h);
			}
			{
			int tw, th, xcenter;
			Bmp_TextSize((const char *)widget->data, &tw, &th, widget->w);
			xcenter  = (widget->w - tw) / 2;
			Bmp_DrawText(screen, (const char *)widget->data, isSelected ? GUI_COLOUR_BUTTONHVR : GUI_COLOUR_BUTTON, xx + xcenter, yy + 4, widget->w);
			}
			break;
		case WIDGET_IMAGE:
			Bmp_Blit(screen, (Bmp_t *)widget->data, xx, yy);
			break;
		case WIDGET_SLIDER: {
			int nubOffs = (int)(*(float*)widget->data * (float)widget->w);
			Bmp_FillRegion(screen, isSelected ? 0xff404040 : 0xff202020, xx - 1, yy - 1, widget->w + 2, widget->h + 2);
			Bmp_FillRegion(screen, 0xff000000, xx, yy, widget->w, widget->h);
			Bmp_FillRegion(screen, isSelected ? GUI_COLOUR_TEXT : 0xff7f7f7f, xx + nubOffs - 3, yy - 1, 6, widget->h + 2);
			SDL_snprintf(sliderPercentStr, 5, "%d%%", (int)(*(float *)widget->data * 100.0f));
			Bmp_SetActiveFont(BMPFONT_TINY);
			Bmp_DrawText(screen, sliderPercentStr, GUI_COLOUR_TEXT, xx + widget->w + 6, yy + 1, screen->w);
			Bmp_SetActiveFont(BMPFONT_MINISERIF);
			break;
		}
		case WIDGET_SELECT: {
			struct GuiSelectData *data = (struct GuiSelectData *)widget->data;
			Bmp_SetActiveFont(BMPFONT_TINY);
			Bmp_DrawText(screen, data->options[data->selected], isSelected ? GUI_COLOUR_BUTTONHVR : GUI_COLOUR_TEXT, xx, yy, screen->w);
			if (isSelected && data->selected > 0)
				Bmp_DrawGlyph(screen, '<', GUI_COLOUR_BUTTONHVR, xx - 8, yy);
			if (isSelected && data->selected < data->numoptions - 1)
				Bmp_DrawGlyph(screen, '>', GUI_COLOUR_BUTTONHVR, xx + widget->w + 2, yy);
			Bmp_SetActiveFont(BMPFONT_MINISERIF);
			break;
		}
		default:
			break;
		}
	}
}

void Gui_ClickMenu(const struct GuiMenu *menu, int selected)
{
	if (selectSound == -1)
		selectSound = Audio_GetIDForName("SfxMenuSelect");
	Audio_PlaySound(selectSound);
	switchTimeout = 1;
	menu->clickProc(Game.activeMenu->selected);
}

void Gui_HandleClick(const struct GuiMenu *menu, int mouseX, int mouseY)
{
	int i;

	if (!menu->layoutComputed)
	{
		Msg_Warning("Attempted to handle click on menu without layout.");
		return;
	}

	for (i = 0; i < menu->numWidgets; i++)
	{
		const struct GuiWidget *widget = &menu->widgets[i];
		int x0 = widget->x + menu->xOffs;
		int y0 = widget->y + menu->yOffs;
		//int x1 = widget->x + widget->w + menu->xOffs;
		// THE STUPID WAY
		int x1 = widget->x + widget->w + menu->xOffs;
		int y1 = widget->y + (widget->type == WIDGET_BUTTON ? 16 : widget->h) + menu->yOffs;
		bool isWidgetClicked = (mouseX >= x0 && mouseX <= x1) && (mouseY >= y0 && mouseY <= y1);
		if (isWidgetClicked)
		{
			switch (widget->type)
			{
			case WIDGET_SLIDER:
				*(float *)widget->data = (float)(mouseX - x0) / widget->w;
				break;
			case WIDGET_SELECT: {
				struct GuiSelectData *data = (struct GuiSelectData *)widget->data;
				data->selected = (data->selected + 1) % data->numoptions;
				break;
			}
			default:
				break;
			}
			
			Gui_ClickMenu(menu, i);
			return;
		}
	}
}
