/*
 * Wolfenstein: Enemy Territory GPL Source Code
 * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
 *
 * ET: Legacy
 * Copyright (C) 2012-2018 ET:Legacy team <mail@etlegacy.com>
 *
 * This file is part of ET: Legacy - http://www.etlegacy.com
 *
 * ET: Legacy 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.
 *
 * ET: Legacy 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 ET: Legacy. If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, Wolfenstein: Enemy Territory GPL Source Code is also
 * subject to certain additional terms. You should have received a copy
 * of these additional terms immediately following the terms and conditions
 * of the GNU General Public License which accompanied the source code.
 * If not, please request a copy in writing from id Software at the address below.
 *
 * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
 */
/**
 * @file ui_shared.c
 * @brief String allocation/managment
 */

#include "ui_shared.h"
#include "ui_local.h"    // For CS settings/retrieval

scrollInfo_t scrollInfo;

void      (*captureFunc)(void *p) = NULL;
void      *captureData = NULL;
itemDef_t *itemCapture = NULL;   // item that has the mouse captured ( if any )

displayContextDef_t *DC = NULL;

qboolean g_waitingForKey = qfalse;
qboolean g_editingField  = qfalse;

itemDef_t *g_bindItem = NULL;
itemDef_t *g_editItem = NULL;

menuDef_t Menus[MAX_MENUS];      // defined menus
int       menuCount = 0;         // how many

// a stack for modal menus only, stores the menus to come back to
// (an item can be NULL, goes back to main menu / no action required)
menuDef_t *modalMenuStack[MAX_MODAL_MENUS];
int       modalMenuCount = 0;

qboolean debugMode            = qfalse;
int      lastListBoxClickTime = 0;

#ifdef CGAME
#define MEM_POOL_SIZE  128 * 1024
#else
#define MEM_POOL_SIZE  2048 * 1024  // was 1536, 1024
#endif

static char     memoryPool[MEM_POOL_SIZE];
static int      allocPoint;
static qboolean outOfMemory;

/**
 * @brief Convert rectangle-coordinates for use with the current aspectratio.
 * @param[out] rect
 */
void Cui_WideRect(rectDef_t *rect)
{
	rect->x *= DC->xscale;
	rect->y *= DC->yscale;
	rect->w *= DC->xscale;
	rect->h *= DC->yscale;

	if (DC->glconfig.windowAspect > RATIO43 && DC->getCVarValue("r_mode") != 11.f)
	{
		rect->x *= RATIO43 / DC->glconfig.windowAspect;
		rect->w *= RATIO43 / DC->glconfig.windowAspect;
	}
}

/**
 * @brief Convert an x-coordinate for use with the current aspectratio.
 * (if the current aspectratio is 4:3, then leave the x-coordinate unchanged)
 * @param[in] x
 * @return
 */
float Cui_WideX(float x)
{
	return (DC->glconfig.windowAspect <= RATIO43) ? x : x * (DC->glconfig.windowAspect * RPRATIO43); // aspectratio / (4/3)
}

/**
 * @brief The horizontal center of screen pixel-difference of a 4:3 ratio vs. the current aspectratio
 */
float Cui_WideXoffset(void)
{
	return (DC->glconfig.windowAspect <= RATIO43) ? 0.0f : ((640.0f * (DC->glconfig.windowAspect * RPRATIO43)) - 640.0f) * 0.5f;
}

/**
 * @brief UI_Alloc
 * @param[in] size
 * @return
 */
void *UI_Alloc(int size)
{
	char *p;

	if (allocPoint + size > MEM_POOL_SIZE)
	{
		outOfMemory = qtrue;
		if (DC->Print)
		{
			DC->Print(S_COLOR_RED "UI_Alloc: Failure. UI out of memory!\n");
		}

		return NULL;
	}

	p = &memoryPool[allocPoint];

	allocPoint += (size + 15) & ~15;

	return p;
}

/**
 * @brief UI_InitMemory
 */
void UI_InitMemory(void)
{
	allocPoint  = 0;
	outOfMemory = qfalse;
}

/**
 * @brief UI_OutOfMemory
 * @return
 */
qboolean UI_OutOfMemory(void)
{
	return outOfMemory;
}

#define HASH_TABLE_SIZE 2048

/**
 * @brief Return a hash value for the string
 * @param[in] str
 * @return
 */
static long hashForString(const char *str)
{
	int  i    = 0;
	long hash = 0;
	char letter;

	while (str[i] != '\0')
	{
		letter = (char)tolower(str[i]);
		hash  += (long)(letter) * (i + 119);
		i++;
	}

	hash &= (HASH_TABLE_SIZE - 1);
	return hash;
}

typedef struct stringDef_s
{
	struct stringDef_s *next;
	const char *str;
} stringDef_t;

static int  strPoolIndex = 0;
static char strPool[STRING_POOL_SIZE];

static int         strHandleCount = 0;
static stringDef_t *strHandle[HASH_TABLE_SIZE];

/**
 * @brief String_Alloc
 * @param[in] p
 * @return
 */
const char *String_Alloc(const char *p)
{
	int               len;
	long              hash;
	stringDef_t       *str, *last;
	static const char *staticNULL = "";

	if (p == NULL)
	{
		return NULL;
	}

	if (*p == 0)
	{
		return staticNULL;
	}

	hash = hashForString(p);

	str = strHandle[hash];
	while (str)
	{
		if (strcmp(p, str->str) == 0)
		{
			return str->str;
		}

		str = str->next;
	}

	len = strlen(p);
	if (len + strPoolIndex + 1 < STRING_POOL_SIZE)
	{
		int ph = strPoolIndex;

		strcpy(&strPool[strPoolIndex], p);
		strPoolIndex += len + 1;

		str  = strHandle[hash];
		last = str;
		while (str && str->next)
		{
			str  = str->next;
			last = str;
		}

		str       = UI_Alloc(sizeof(stringDef_t));
		str->next = NULL;
		str->str  = &strPool[ph];
		if (last)
		{
			last->next = str;
		}
		else
		{
			strHandle[hash] = str;
		}

		return &strPool[ph];
	}

	return NULL;
}

/**
 * @brief String_Report
 */
void String_Report(void)
{
	float f;

	Com_Printf("Memory/String Pool Info\n");
	Com_Printf("----------------\n");
	f  = strPoolIndex;
	f /= STRING_POOL_SIZE;
	f *= 100;
	Com_Printf("String Pool is %.1f%% full, %i bytes out of %i used.\n", (double)f, strPoolIndex, STRING_POOL_SIZE);
	f  = allocPoint;
	f /= MEM_POOL_SIZE;
	f *= 100;
	Com_Printf("Memory Pool is %.1f%% full, %i bytes out of %i used.\n", (double)f, allocPoint, MEM_POOL_SIZE);
}

/**
 * @brief String_Init
 */
void String_Init(void)
{
	int i;

	for (i = 0; i < HASH_TABLE_SIZE; i++)
	{
		strHandle[i] = 0;
	}
	strHandleCount = 0;
	strPoolIndex   = 0;
	menuCount      = 0;
	modalMenuCount = 0;
	UI_InitMemory();
	Item_SetupKeywordHash();
	Menu_SetupKeywordHash();
	if (DC && DC->getBindingBuf)
	{
		Controls_GetConfig();
	}
}

/**
 * @brief Lerp and clamp each component of @p a and @p b into @p c by the fraction @p t
 * @param[in] a
 * @param[in] b
 * @param[in] c
 * @param[in] t
 */
void LerpColor(vec4_t a, vec4_t b, vec4_t c, float t)
{
	int i;

	for (i = 0; i < 4; i++)
	{
		c[i] = a[i] + t * (b[i] - a[i]);
		if (c[i] < 0)
		{
			c[i] = 0;
		}
		else if (c[i] > 1.0f)
		{
			c[i] = 1.0;
		}
	}
}

// display, window, menu, item code

/**
 * @brief Initializes the display with a structure to all the drawing routines
 * @param[in] dc
 */
void Init_Display(displayContextDef_t *dc)
{
	DC = dc;
}

// type and style painting

/**
 * @brief GradientBar_Paint
 * @param[in] rect
 * @param[in] color
 */
void GradientBar_Paint(rectDef_t *rect, vec4_t color)
{
	// gradient bar takes two paints
	DC->setColor(color);
	DC->drawHandlePic(rect->x, rect->y, rect->w, rect->h, DC->Assets.gradientBar);
	DC->setColor(NULL);
}

/**
 * @brief Initializes a window structure ( windowDef_t ) with defaults
 * @param[in,out] w
 */
void Window_Init(Window *w)
{
	Com_Memset(w, 0, sizeof(windowDef_t));
	w->borderSize   = 1;
	w->foreColor[0] = w->foreColor[1] = w->foreColor[2] = w->foreColor[3] = 1.0;
	w->cinematic    = -1;
}

/**
 * @brief Fade
 * @param[in,out] flags
 * @param[in,out] f
 * @param[in] clamp
 * @param[in,out] nextTime
 * @param[in] offsetTime
 * @param[in] bFlags
 * @param[in] fadeAmount
 */
void Fade(int *flags, float *f, float clamp, int *nextTime, int offsetTime, qboolean bFlags, float fadeAmount)
{
	if (*flags & (WINDOW_FADINGOUT | WINDOW_FADINGIN))
	{
		if (DC->realTime > *nextTime)
		{
			*nextTime = DC->realTime + offsetTime;
			if (*flags & WINDOW_FADINGOUT)
			{
				*f -= fadeAmount;
				if (bFlags && *f <= 0.0f)
				{
					*flags &= ~(WINDOW_FADINGOUT | WINDOW_VISIBLE);
				}
			}
			else
			{
				*f += fadeAmount;
				if (*f >= clamp)
				{
					*f = clamp;
					if (bFlags)
					{
						*flags &= ~WINDOW_FADINGIN;
					}
				}
			}
		}
	}
}

/**
 * @brief Window_Paint
 * @param[in,out] w
 * @param[in] fadeAmount
 * @param[in] fadeClamp
 * @param[in] fadeCycle
 */
void Window_Paint(Window *w, float fadeAmount, float fadeClamp, float fadeCycle)
{
	vec4_t    color = { 0.0, 0.0, 0.0, 0.0 };
	rectDef_t fillRect;

	if (w == NULL)
	{
		return;
	}

	if (debugMode)
	{
		color[0] = color[1] = color[2] = color[3] = 1;
		DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, 1, color);
	}

	if (w->style == 0 && w->border == 0)
	{
		return;
	}

	fillRect = w->rect;

	// FIXME: do right thing for right border type
	if (w->border != 0)
	{
		fillRect.x += w->borderSize;
		fillRect.y += w->borderSize;
		fillRect.w -= 2 * w->borderSize;
		fillRect.h -= 2 * w->borderSize;
	}

	if (w->style == WINDOW_STYLE_FILLED)
	{
		// box, but possible a shader that needs filled
		if (w->background)
		{
			Fade(&w->flags, &w->backColor[3], fadeClamp, &w->nextTime, fadeCycle, qtrue, fadeAmount);
			DC->setColor(w->backColor);
			DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background);
			DC->setColor(NULL);
		}
		else
		{
			DC->fillRect(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->backColor);
		}
	}
	else if (w->style == WINDOW_STYLE_GRADIENT)
	{
		GradientBar_Paint(&fillRect, w->backColor);
		// gradient bar
	}
	else if (w->style == WINDOW_STYLE_SHADER)
	{
		if (w->flags & WINDOW_FORECOLORSET)
		{
			DC->setColor(w->foreColor);
		}

		DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background);
		DC->setColor(NULL);
	}
	else if (w->style == WINDOW_STYLE_TEAMCOLOR)
	{
		if (DC->getTeamColor)
		{
			DC->getTeamColor(&color);
			DC->fillRect(fillRect.x, fillRect.y, fillRect.w, fillRect.h, color);
		}
	}
	else if (w->style == WINDOW_STYLE_CINEMATIC)
	{
		if (w->cinematic == -1)
		{
			w->cinematic = DC->playCinematic(w->cinematicName, fillRect.x, fillRect.y, fillRect.w, fillRect.h);
			if (w->cinematic == -1)
			{
				w->cinematic = -2;
			}
		}
		if (w->cinematic >= 0)
		{
			DC->runCinematicFrame(w->cinematic);
			DC->drawCinematic(w->cinematic, fillRect.x, fillRect.y, fillRect.w, fillRect.h);
		}
	}

	if (w->border == WINDOW_BORDER_FULL)
	{
		// WARNING: full HACK HACK HACK
		if (w->style == WINDOW_STYLE_TEAMCOLOR)
		{
			if (color[0] > 0)
			{
				// red
				color[0] = 1;
				color[1] = color[2] = .5;
			}
			else
			{
				color[2] = 1;
				color[0] = color[1] = .5;
			}

			color[3] = 1;
			DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, color);
		}
		else
		{
			DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, w->borderColor);
		}
	}
	else if (w->border == WINDOW_BORDER_HORZ)
	{
		// top/bottom
		DC->setColor(w->borderColor);
		DC->drawTopBottom(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize);
		DC->setColor(NULL);
	}
	else if (w->border == WINDOW_BORDER_VERT)
	{
		// left right
		DC->setColor(w->borderColor);
		DC->drawSides(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize);
		DC->setColor(NULL);
	}
	else if (w->border == WINDOW_BORDER_KCGRADIENT)
	{
		// this is just two gradient bars along each horz edge
		rectDef_t r = w->rect;

		r.h = w->borderSize;
		GradientBar_Paint(&r, w->borderColor);
		r.y = w->rect.y + w->rect.h - 1;
		GradientBar_Paint(&r, w->borderColor);
	}
}

/**
 * @brief IsVisible
 * @param[in] flags
 * @return
 */
qboolean IsVisible(int flags)
{
	return ((flags & WINDOW_VISIBLE) && !(flags & WINDOW_FADINGOUT));
}

/**
 * @brief FileExists
 * @param filename
 * @return
 */
qboolean FileExists(const char *filename)
{
	fileHandle_t f;

	if (trap_FS_FOpenFile(filename, &f, FS_READ) < 0)
	{
		trap_FS_FCloseFile(f);
		return qfalse;
	}
	else
	{
		trap_FS_FCloseFile(f);
		return qtrue;
	}
}

/**
 * @brief AdjustFrom640
 * @param[out] x
 * @param[out] y
 * @param[out] w
 * @param[out] h
 */
void AdjustFrom640(float *x, float *y, float *w, float *h)
{
	*x *= DC->xscale;
	*y *= DC->yscale;
	*w *= DC->xscale;
	*h *= DC->yscale;

	// adjust screen
	if (DC->glconfig.windowAspect > RATIO43)
	{
		*x *= RATIO43 / DC->glconfig.windowAspect;
		*w *= RATIO43 / DC->glconfig.windowAspect;
	}
}

/**
 * @brief Rect_ContainsPoint
 * @param[in] rect
 * @param[in] x
 * @param[in] y
 * @return
 */
qboolean Rect_ContainsPoint(rectDef_t *rect, float x, float y)
{
	if (rect)
	{
		// correction for widescreen cursor coordinates..
		x = Cui_WideX(x);

		if (x > Cui_WideX(rect->x) && x < Cui_WideX(rect->x + rect->w) && y > rect->y && y < rect->y + rect->h)
		{
			return qtrue;
		}
	}

	return qfalse;
}

/**
 * @brief Rect_ContainsPointN
 * @param[in] rect
 * @param[in] x
 * @param[in] y
 * @return
 */
qboolean Rect_ContainsPointN(rectDef_t *rect, float x, float y)
{
	if (rect)
	{
		if (x > rect->x && x < rect->x + rect->w + 200 && y > rect->y && y < rect->y + rect->h)
		{
			return qtrue;
		}
	}

	return qfalse;
}

/**
 * @brief Window_CloseCinematic
 * @param[in,out] window
 */
void Window_CloseCinematic(windowDef_t *window)
{
	if (window->style == WINDOW_STYLE_CINEMATIC && window->cinematic >= 0)
	{
		DC->stopCinematic(window->cinematic);
		window->cinematic = -1;
	}
}

/**
 * @brief Display_CloseCinematics
 */
void Display_CloseCinematics(void)
{
	int i;

	for (i = 0; i < menuCount; i++)
	{
		Menu_CloseCinematics(&Menus[i]);
	}
}

/**
 * @brief Display_VisibleMenuCount
 * @return
 */
int Display_VisibleMenuCount(void)
{
	int i, count = 0;

	for (i = 0; i < menuCount; i++)
	{
		if (Menus[i].window.flags & (WINDOW_FORCED | WINDOW_VISIBLE))
		{
			count++;
		}
	}

	return count;
}

/**
 * @brief ToWindowCoords
 * @param[out] x
 * @param[out] y
 * @param[in] window
 */
void ToWindowCoords(float *x, float *y, windowDef_t *window)
{
	*x += window->rect.x;
	*y += window->rect.y;
}

/*
 * @brief Rect_ToWindowCoords
 * @param rect
 * @param window
 *
 * @note Unused
void Rect_ToWindowCoords(rectDef_t *rect, windowDef_t *window)
{
    ToWindowCoords(&rect->x, &rect->y, window);
}
*/

typedef struct
{
	char *command;
	int id; // unused
	int defaultbind1_right;
	int defaultbind2_right;
	int defaultbind1_left;
	int defaultbind2_left;
	int bind1;
	int bind2;
} bind_t;

/**
 * @var g_bindings
 * @brief
 * @note These MUST be all lowercase now
 */
static bind_t g_bindings[] =
{
	{ "+forward",         'w',             -1,  K_UPARROW,       -1,  -1, -1, -1 },
	{ "+back",            's',             -1,  K_DOWNARROW,     -1,  -1, -1, -1 },
	{ "+moveleft",        'a',             -1,  K_LEFTARROW,     -1,  -1, -1, -1 },
	{ "+moveright",       'd',             -1,  K_RIGHTARROW,    -1,  -1, -1, -1 },
	{ "+moveup",          K_SPACE,         -1,  K_KP_INS,        -1,  -1, -1, -1 },
	{ "+movedown",        'c',             -1,  K_LCTRL,         -1,  -1, -1, -1 },
	{ "+leanright",       'e',             -1,  K_PGDN,          -1,  -1, -1, -1 },
	{ "+leanleft",        'q',             -1,  K_DEL,           -1,  -1, -1, -1 },
	{ "+prone",           'x',             -1,  K_LSHIFT,        -1,  -1, -1, -1 },
	{ "+attack",          K_MOUSE1,        -1,  K_MOUSE1,        -1,  -1, -1, -1 },
	{ "weapalt",          K_MOUSE2,        -1,  K_MOUSE2,        -1,  -1, -1, -1 },
	{ "weapprev",         K_MWHEELDOWN,    -1,  K_MWHEELDOWN,    -1,  -1, -1, -1 },
	{ "weapnext",         K_MWHEELUP,      -1,  K_MWHEELUP,      -1,  -1, -1, -1 },
	{ "weaponbank 10",    '0',             -1,  '0',             -1,  -1, -1, -1 },
	{ "weaponbank 1",     '1',             -1,  '1',             -1,  -1, -1, -1 },
	{ "weaponbank 2",     '2',             -1,  '2',             -1,  -1, -1, -1 },
	{ "weaponbank 3",     '3',             -1,  '3',             -1,  -1, -1, -1 },
	{ "weaponbank 4",     '4',             -1,  '4',             -1,  -1, -1, -1 },
	{ "weaponbank 5",     '5',             -1,  '5',             -1,  -1, -1, -1 },
	{ "weaponbank 6",     '6',             -1,  '6',             -1,  -1, -1, -1 },
	{ "weaponbank 7",     '7',             -1,  '7',             -1,  -1, -1, -1 },
	{ "weaponbank 8",     '8',             -1,  '8',             -1,  -1, -1, -1 },
	{ "weaponbank 9",     '9',             -1,  '9',             -1,  -1, -1, -1 },
	{ "+sprint",          K_LSHIFT,        -1,  K_MOUSE3,        -1,  -1, -1, -1 },
	{ "+speed",           K_CAPSLOCK,      -1,  K_CAPSLOCK,      -1,  -1, -1, -1 },
	{ "+activate",        'f',             -1,  K_ENTER,         -1,  -1, -1, -1 },
	{ "+zoom",            'b',             -1,  'b',             -1,  -1, -1, -1 },
	{ "+mapexpand",       'g',             -1,  '#',             -1,  -1, -1, -1 },
	{ "+reload",          'r',             -1,  K_END,           -1,  -1, -1, -1 },
	{ "kill",             'k',             -1,  'k',             -1,  -1, -1, -1 },
	{ "+scores",          K_TAB,           -1,  K_TAB,           -1,  -1, -1, -1 },
	{ "+stats",           K_LALT,          -1,  K_F9,            -1,  -1, -1, -1 },
	{ "+topshots",        K_LCTRL,         -1,  K_F10,           -1,  -1, -1, -1 },
	{ "+objectives",      'o',             -1,  'o',             -1,  -1, -1, -1 },
	{ "toggleconsole",    '`',             '~', '`',             '~', -1, -1, -1 },
	{ "togglemenu",       K_ESCAPE,        -1,  K_ESCAPE,        -1,  -1, -1, -1 },
	{ "openlimbomenu",    'l',             -1,  'l',             -1,  -1, -1, -1 },
#ifdef FEATURE_MULTIVIEW
	{ "mvactivate",       'm',             -1,  'm',             -1,  -1, -1, -1 },
	{ "spechelp",         K_BACKSPACE,     -1,  K_BACKSPACE,     -1,  -1, -1, -1 },
#endif
	{ "mapzoomout",       ',',             -1,  '[',             -1,  -1, -1, -1 },
	{ "mapzoomin",        '.',             -1,  ']',             -1,  -1, -1, -1 },
	{ "zoomin",           '=',             -1,  '-',             -1,  -1, -1, -1 },
	{ "zoomout",          '-',             -1,  '=',             -1,  -1, -1, -1 },
	{ "classmenu",        'p',             -1,  'p',             -1,  -1, -1, -1 },
	{ "messagemode",      't',             -1,  't',             -1,  -1, -1, -1 },
	{ "messagemode2",     'y',             -1,  'y',             -1,  -1, -1, -1 },
	{ "messagemode3",     'u',             -1,  'u',             -1,  -1, -1, -1 },
	{ "mp_quickmessage",  'v',             -1,  'v',             -1,  -1, -1, -1 },
	{ "mp_fireteammsg",   'z',             -1,  'c',             -1,  -1, -1, -1 },
	{ "vote yes",         K_F1,            -1,  K_F1,            -1,  -1, -1, -1 },
	{ "vote no",          K_F2,            -1,  K_F2,            -1,  -1, -1, -1 },
	{ "ready",            K_F3,            -1,  K_F3,            -1,  -1, -1, -1 },
	{ "notready",         K_F4,            -1,  K_F4,            -1,  -1, -1, -1 },
	{ "autoscreenshot",   K_F11,           -1,  K_F11,           -1,  -1, -1, -1 },
	{ "autoRecord",       K_F12,           -1,  K_F12,           -1,  -1, -1, -1 },
	{ "mp_fireteamadmin", K_KP_ENTER,      -1,  K_KP_ENTER,      -1,  -1, -1, -1 },
	{ "selectbuddy -1",   K_KP_PLUS,       -1,  K_KP_PLUS,       -1,  -1, -1, -1 },
	{ "selectbuddy 0",    K_KP_END,        -1,  K_KP_END,        -1,  -1, -1, -1 },
	{ "selectbuddy 1",    K_KP_DOWNARROW,  -1,  K_KP_DOWNARROW,  -1,  -1, -1, -1 },
	{ "selectbuddy 2",    K_KP_PGDN,       -1,  K_KP_PGDN,       -1,  -1, -1, -1 },
	{ "selectbuddy 3",    K_KP_LEFTARROW,  -1,  K_KP_LEFTARROW,  -1,  -1, -1, -1 },
	{ "selectbuddy 4",    K_KP_5,          -1,  K_KP_5,          -1,  -1, -1, -1 },
	{ "selectbuddy 5",    K_KP_RIGHTARROW, -1,  K_KP_RIGHTARROW, -1,  -1, -1, -1 },
	{ "selectbuddy 6",    K_KP_HOME,       -1,  K_KP_HOME,       -1,  -1, -1, -1 },
	{ "selectbuddy 7",    K_KP_UPARROW,    -1,  K_KP_UPARROW,    -1,  -1, -1, -1 },
	{ "selectbuddy -2",   K_KP_MINUS,      -1,  K_KP_MINUS,      -1,  -1, -1, -1 },
};

static const int g_bindCount = sizeof(g_bindings) / sizeof(bind_t);

/**
 * @brief Controls_GetConfig
 */
void Controls_GetConfig(void)
{
	int i;

	// iterate each command, get its numeric binding
	for (i = 0; i < g_bindCount; i++)
	{
		DC->getKeysForBinding(g_bindings[i].command, &g_bindings[i].bind1, &g_bindings[i].bind2);
	}
}

/**
 * @brief Controls_SetConfig
 * @param[in] restart
 */
void Controls_SetConfig(qboolean restart)
{
	int i;

	// iterate each command, get its numeric binding
	for (i = 0; i < g_bindCount; i++)
	{
		if (g_bindings[i].bind1 != -1)
		{
			DC->setBinding(g_bindings[i].bind1, g_bindings[i].command);

			if (g_bindings[i].bind2 != -1)
			{
				DC->setBinding(g_bindings[i].bind2, g_bindings[i].command);
			}
		}
	}

	if (restart)
	{
		DC->executeText(EXEC_APPEND, "in_restart\n");
	}
}

/**
 * @brief Controls_SetDefaults
 * @param[in] lefthanded
 */
void Controls_SetDefaults(qboolean lefthanded)
{
	int i;

	// iterate each command, set its default binding
	for (i = 0; i < g_bindCount; i++)
	{
		g_bindings[i].bind1 = lefthanded ? g_bindings[i].defaultbind1_left : g_bindings[i].defaultbind1_right;
		g_bindings[i].bind2 = lefthanded ? g_bindings[i].defaultbind2_left : g_bindings[i].defaultbind2_right;
	}
}

/**
 * @brief Binding_IDFromName
 * @param[in] name
 * @return
 */
int Binding_IDFromName(const char *name)
{
	int i;

	for (i = 0; i < g_bindCount; i++)
	{
		if (!Q_stricmp(name, g_bindings[i].command))
		{
			return i;
		}
	}

	return -1;
}

/**
 * @brief Binding_Set
 * @param[in] id
 * @param[in] b1
 * @param[in] b2
 */
void Binding_Set(int id, int b1, int b2)
{
	if (id != -1)
	{
		if (b1 != -2)
		{
			int key1;
			int key2;

			// Set the current value of bind1 to bind2
			DC->getKeysForBinding(g_bindings[id].command, &key1, &key2);
			Binding_Set(id, -2, key1);

			Binding_Unset(id, 1);
			g_bindings[id].bind1 = b1;
		}

		if (b2 != -2)
		{
			Binding_Unset(id, 2);
			g_bindings[id].bind2 = b2;
		}
	}
}

/**
 * @brief Binding_Unset
 * @param[in] id
 * @param[in] index
 */
void Binding_Unset(int id, int index)
{
	int key1;
	int key2;

	// Find the keys binded to action
	DC->getKeysForBinding(g_bindings[id].command, &key1, &key2);

	// Unbind the key1 or key2 according to index
	switch (index)
	{
	case 1:
		DC->setBinding(key1, "");
		break;
	case 2:
		DC->setBinding(key2, "");
		break;
	default:
		break;
	}
}

/**
 * @brief Binding_Check
 * @param[in] id
 * @param[in] b1
 * @param[in] key
 * @return
 */
qboolean Binding_Check(int id, qboolean b1, int key)
{
	if (id != -1)
	{
		if (b1)
		{
			if (g_bindings[id].bind1 == key)
			{
				return qtrue;
			}
		}
		else
		{
			if (g_bindings[id].bind2 == key)
			{
				return qtrue;
			}
		}
	}

	return qfalse;
}

/**
 * @brief Binding_Get
 * @param[in] id
 * @param[in] b1
 * @return
 */
int Binding_Get(int id, qboolean b1)
{
	if (id != -1)
	{
		if (b1)
		{
			return g_bindings[id].bind1;
		}
		else
		{
			return g_bindings[id].bind2;
		}
	}

	return -1;
}

/**
 * @brief Binding_Count
 * @return
 */
int Binding_Count(void)
{
	return g_bindCount;
}

char g_nameBind1[32];
char g_nameBind2[32];

/**
 * @brief Binding_FromName
 * @param[in] cvar
 * @return
 */
char *Binding_FromName(const char *cvar)
{
	int b1, b2;

	DC->getKeysForBinding(cvar, &b1, &b2);

	if (b1 != -1)
	{
		DC->keynumToStringBuf(b1, g_nameBind1, 32);
		Q_strupr(g_nameBind1);

		if (b2 != -1)
		{
			DC->keynumToStringBuf(b2, g_nameBind2, 32);
			Q_strupr(g_nameBind2);
			Q_strcat(g_nameBind1, 32, DC->translateString(" or "));
			Q_strcat(g_nameBind1, 32, g_nameBind2);
		}
	}
	else
	{
		Q_strncpyz(g_nameBind1, "(?" "?" "?)", 32);
	}

	return g_nameBind1;
}

/**
 * @brief Display_KeyBindPending
 * @return
 */
qboolean Display_KeyBindPending(void)
{
	return g_waitingForKey;
}

/**
 * @brief Display_GetContext
 * @return
 *
 * @note Unused
 */
displayContextDef_t *Display_GetContext()
{
	return DC;
}

/**
 * @brief Display_CaptureItem
 * @param[in] x
 * @param[in] y
 * @return
 */
void *Display_CaptureItem(int x, int y)
{
	int i;

	for (i = 0; i < menuCount; i++)
	{
		// turn off focus each item
		// menu->items[i].window.flags &= ~WINDOW_HASFOCUS;
		if (Rect_ContainsPoint(&Menus[i].window.rect, x, y))
		{
			return &Menus[i];
		}
	}

	return NULL;
}

/**
 * @brief Display_MouseMove
 * @param[in,out] p
 * @param[in] x
 * @param[in] y
 * @return
 */
qboolean Display_MouseMove(void *p, int x, int y)
{
	menuDef_t *menu = p;

	if (menu == NULL)
	{
		int i;

		menu = Menu_GetFocused();
		if (menu)
		{
			if (menu->window.flags & WINDOW_POPUP)
			{
				Menu_HandleMouseMove(menu, x, y);
				return qtrue;
			}
		}

		for (i = 0; i < menuCount; i++)
		{
			Menu_HandleMouseMove(&Menus[i], x, y);
		}
	}
	else
	{
		menu->window.rect.x += x;
		menu->window.rect.y += y;
		Menu_UpdatePosition(menu);
	}

	return qtrue;
}

/**
 * @brief Display_CursorType
 * @param[in] x
 * @param[in] y
 * @return
 *
 * @note Unused
 */
int Display_CursorType(int x, int y)
{
	rectDef_t r2;
	int       i;

	for (i = 0; i < menuCount; i++)
	{
		r2.x = Menus[i].window.rect.x - 3;
		r2.y = Menus[i].window.rect.y - 3;
		r2.w = r2.h = 7;
		if (Rect_ContainsPoint(&r2, x, y))
		{
			return CURSOR_SIZER;
		}
	}

	return CURSOR_ARROW;
}

/**
 * @brief Display_HandleKey
 * @param[in] key
 * @param[in] down
 * @param[in] x
 * @param[in] y
 *
 * @note Unused
 */
void Display_HandleKey(int key, qboolean down, int x, int y)
{
	menuDef_t *menu = Display_CaptureItem(x, y);

	if (menu == NULL)
	{
		menu = Menu_GetFocused();
	}

	if (menu)
	{
		Menu_HandleKey(menu, key, down);
	}
}

/**
 * @brief Window_CacheContents
 * @param[in] window
 */
static void Window_CacheContents(windowDef_t *window)
{
	if (window)
	{
		if (window->cinematicName)
		{
			int cin = DC->playCinematic(window->cinematicName, 0, 0, 0, 0);
			DC->stopCinematic(cin);
		}
	}
}

/**
 * @brief Item_CacheContents
 * @param[in] item
 */
static void Item_CacheContents(itemDef_t *item)
{
	if (item)
	{
		Window_CacheContents(&item->window);
	}
}

/**
 * @brief Menu_CacheContents
 * @param[in] menu
 */
static void Menu_CacheContents(menuDef_t *menu)
{
	if (menu)
	{
		int i;

		Window_CacheContents(&menu->window);

		for (i = 0; i < menu->itemCount; i++)
		{
			Item_CacheContents(menu->items[i]);
		}

		if (menu->soundName && *menu->soundName)
		{
			DC->registerSound(menu->soundName, qtrue);
		}
	}
}

/**
 * @brief Display_CacheAll
 */
void Display_CacheAll(void)
{
	int i;

	for (i = 0; i < menuCount; i++)
	{
		Menu_CacheContents(&Menus[i]);
	}
}

/*
 * @brief Translates string
 * @param[in] handle
 * @param[out] out
 * @return
 *
 * @note Unused
qboolean PC_String_Parse_Trans(int handle, const char **out)
{
	pc_token_t token;

	if (!trap_PC_ReadToken(handle, &token))
	{
		return qfalse;
	}

	*(out) = String_Alloc(DC->translateString(token.string));
	return qtrue;
}
*/

/**
 * @brief PC_Rect_Parse
 * @param[in] handle
 * @param[in] r
 * @return
 */
qboolean PC_Rect_Parse(int handle, rectDef_t *r)
{
	if (PC_Float_Parse(handle, &r->x))
	{
		if (PC_Float_Parse(handle, &r->y))
		{
			if (PC_Float_Parse(handle, &r->w))
			{
				if (PC_Float_Parse(handle, &r->h))
				{
					return qtrue;
				}
			}
		}
	}

	return qfalse;
}

// Panel Handling
// ======================================================
panel_button_t *bg_focusButton;

/**
 * @brief BG_RectContainsPoint
 * @param[in] x
 * @param[in] y
 * @param[in] w
 * @param[in] h
 * @param[in] px
 * @param[in] py
 * @return
 */
qboolean BG_RectContainsPoint(float x, float y, float w, float h, float px, float py)
{
	if (px > x && px < x + w && py > y && py < y + h)
	{
		return qtrue;
	}

	return qfalse;
}

/**
 * @brief BG_CursorInRect
 * @param[in] rect
 * @return
 */
qboolean BG_CursorInRect(rectDef_t *rect)
{
	return BG_RectContainsPoint(rect->x, rect->y, rect->w, rect->h, DC->cursorx, DC->cursory);
}

/**
 * @brief BG_PanelButton_RenderEdit
 * @param[in] button
 */
void BG_PanelButton_RenderEdit(panel_button_t *button)
{
	qboolean useCvar = button->data[0] ? qfalse : qtrue;
	int      offset  = -1;

	if (useCvar)
	{
		char buffer[MAX_EDITFIELD + 1];

		trap_Cvar_VariableStringBuffer(button->text, buffer, sizeof(buffer));

		if (BG_PanelButtons_GetFocusButton() == button && ((DC->realTime / 1000) % 2))
		{
			if (trap_Key_GetOverstrikeMode())
			{
				Q_strcat(buffer, sizeof(buffer), "^0|");
			}
			else
			{
				Q_strcat(buffer, sizeof(buffer), "^0_");
			}
		}
		else
		{
			Q_strcat(buffer, sizeof(buffer), " ");
		}

		do
		{
			offset++;
			if (buffer[offset] == '\0')
			{
				break;
			}
		}
		while (DC->textWidthExt(buffer + offset, button->font->scalex, 0, button->font->font) > button->rect.w);

		DC->drawTextExt(button->rect.x, button->rect.y + button->rect.h, button->font->scalex, button->font->scaley, button->font->colour, va("^7%s", buffer + offset), 0, 0, button->font->style, button->font->font);
	}
	else
	{
		char *s;

		if (BG_PanelButtons_GetFocusButton() == button && ((DC->realTime / 1000) % 2))
		{
			if (DC->getOverstrikeMode())
			{
				s = va("^7%s^0|", button->text);
			}
			else
			{
				s = va("^7%s^0_", button->text);
			}
		}
		else
		{
			s = va("^7%s ", button->text);   // space hack to make the text not blink
		}

		do
		{
			offset++;
			if (s[offset] == '\0')
			{
				break;
			}
		}
		while (DC->textWidthExt(s + offset, button->font->scalex, 0, button->font->font) > button->rect.w);

		DC->drawTextExt(button->rect.x, button->rect.y + button->rect.h, button->font->scalex, button->font->scaley, button->font->colour, s + offset, 0, 0, button->font->style, button->font->font);
	}
}

/**
 * @brief BG_PanelButton_EditClick
 * @param[in] button
 * @param[in] key
 * @return
 */
qboolean BG_PanelButton_EditClick(panel_button_t *button, int key)
{
	if (key == K_MOUSE1)
	{
		if (!BG_CursorInRect(&button->rect) && BG_PanelButtons_GetFocusButton() == button)
		{
			BG_PanelButtons_SetFocusButton(NULL);
			if (button->onFinish)
			{
				button->onFinish(button);
			}

			return qfalse;
		}
		else
		{
			BG_PanelButtons_SetFocusButton(button);
			return qtrue;
		}
	}
	else if (BG_PanelButtons_GetFocusButton() != button)
	{
		return qfalse;
	}
	else
	{
		char     buffer[256];
		char     *s = NULL;
		size_t   len, maxlen;
		qboolean useCvar = button->data[0] ? qfalse : qtrue;

		if (useCvar)
		{
			maxlen = sizeof(buffer);
			DC->getCVarString(button->text, buffer, sizeof(buffer));
			len = strlen(buffer);
		}
		else
		{
			maxlen = (size_t)button->data[0];
			s      = button->text;
			len    = strlen(s);
		}

		if (key & K_CHAR_FLAG)
		{
			key &= ~K_CHAR_FLAG;

			if (key == 'h' - 'a' + 1)          // ctrl-h is backspace
			{
				if (len)
				{
					if (useCvar)
					{
						buffer[len - 1] = '\0';
						DC->setCVar(button->text, buffer);
					}
					else
					{
						s[len - 1] = '\0';
					}
				}

				return qtrue;
			}

			if (key < 32)
			{
				return qtrue;
			}

			if (button->data[1])
			{
				if (key < '0' || key > '9')
				{
					if (button->data[1] == 2)
					{
						return qtrue;
					}
					else if (!(len == 0 && key == '-'))
					{
						return qtrue;
					}
				}
			}

			if (len >= maxlen - 1)
			{
				return qtrue;
			}

			if (useCvar)
			{
				buffer[len]     = (char)key;
				buffer[len + 1] = '\0';
				trap_Cvar_Set(button->text, buffer);
			}
			else
			{
				s[len]     = (char)key;
				s[len + 1] = '\0';
			}

			return qtrue;
		}
		else
		{
			if (key == K_ENTER || key == K_KP_ENTER)
			{
				if (button->onFinish)
				{
					button->onFinish(button);
				}

				BG_PanelButtons_SetFocusButton(NULL);
				return qfalse;
			}
		}
	}

	return qtrue;
}

/**
 * @brief BG_PanelButtonsKeyEvent
 * @param[in] key
 * @param[in] down
 * @param[in] buttons
 * @return
 */
qboolean BG_PanelButtonsKeyEvent(int key, qboolean down, panel_button_t **buttons)
{
	panel_button_t *button;

	if (BG_PanelButtons_GetFocusButton())
	{
		for ( ; *buttons; buttons++)
		{
			button = (*buttons);

			if (button == BG_PanelButtons_GetFocusButton())
			{
				if (button->onKeyDown && down)
				{
					if (!button->onKeyDown(button, key))
					{
						if (BG_PanelButtons_GetFocusButton())
						{
							return qfalse;
						}
					}
					else
					{
						return qtrue;
					}
				}

				if (button->onKeyUp && !down)
				{
					if (!button->onKeyUp(button, key))
					{
						if (BG_PanelButtons_GetFocusButton())
						{
							return qfalse;
						}
					}
					else
					{
						return qtrue;
					}
				}
			}
		}
	}

	if (down)
	{
		for ( ; *buttons; buttons++)
		{
			button = (*buttons);

			if (button->onKeyDown)
			{
				if (BG_CursorInRect(&button->rect))
				{
					if (button->onKeyDown(button, key))
					{
						return qtrue;
					}
				}
			}
		}
	}
	else
	{
		for ( ; *buttons; buttons++)
		{
			button = (*buttons);

			if (button->onKeyUp && BG_CursorInRect(&button->rect))
			{
				if (button->onKeyUp(button, key))
				{
					return qtrue;
				}
			}
		}
	}

	return qfalse;
}

/**
 * @brief BG_PanelButtonsSetup
 * @param[in,out] buttons
 */
void BG_PanelButtonsSetup(panel_button_t **buttons)
{
	panel_button_t *button;

	for ( ; *buttons; buttons++)
	{
		button = (*buttons);

		if (button->shaderNormal)
		{
			button->hShaderNormal = trap_R_RegisterShaderNoMip(button->shaderNormal);
		}
	}
}

/**
 * @brief BG_PanelButtonsGetHighlightButton
 * @param[in] buttons
 * @return
 */
panel_button_t *BG_PanelButtonsGetHighlightButton(panel_button_t **buttons)
{
	panel_button_t *button;

	for ( ; *buttons; buttons++)
	{
		button = (*buttons);

		if (button->onKeyDown && BG_CursorInRect(&button->rect))
		{
			return button;
		}
	}

	return NULL;
}

/**
 * @brief BG_PanelButtonsRender
 * @param[in] buttons
 */
void BG_PanelButtonsRender(panel_button_t **buttons)
{
	panel_button_t *button;

	for ( ; *buttons; buttons++)
	{
		button = (*buttons);

		if (button->onDraw)
		{
			button->onDraw(button);
		}
	}
}

/**
 * @brief BG_PanelButtonsRender_TextExt
 * @param[in] button
 * @param[in] text
 */
void BG_PanelButtonsRender_TextExt(panel_button_t *button, const char *text)
{
	float x = button->rect.x;

	if (!button->font)
	{
		return;
	}

	if (button->font->align == ITEM_ALIGN_CENTER)
	{
		x += ((button->rect.w - DC->textWidthExt(text, button->font->scalex, 0, button->font->font)) * 0.5f);
	}
	else if (button->font->align == ITEM_ALIGN_RIGHT)
	{
		x += button->rect.w - DC->textWidthExt(text, button->font->scalex, 0, button->font->font);
	}

	if (button->data[1])
	{
		vec4_t clrBdr = { 0.5f, 0.5f, 0.5f, 1.f };
		vec4_t clrBck = { 0.f, 0.f, 0.f, 0.8f };

		DC->fillRect(button->rect.x, button->rect.y, button->rect.w, button->rect.h, clrBck);
		DC->drawRect(button->rect.x, button->rect.y, button->rect.w, button->rect.h, 1, clrBdr);
	}

	DC->drawTextExt(x, button->rect.y + button->data[0], button->font->scalex, button->font->scaley, button->font->colour, text, 0, 0, button->font->style, button->font->font);
}

/**
 * @brief BG_PanelButtonsRender_Text
 * @param[in] button
 */
void BG_PanelButtonsRender_Text(panel_button_t *button)
{
	BG_PanelButtonsRender_TextExt(button, button->text);
}

/**
 * @brief BG_PanelButtonsRender_Img
 * @param[in] button
 */
void BG_PanelButtonsRender_Img(panel_button_t *button)
{
	vec4_t clr = { 1.f, 1.f, 1.f, 1.f };

	if (button->data[0])
	{
		clr[0] = button->data[1] / 255.f;
		clr[1] = button->data[2] / 255.f;
		clr[2] = button->data[3] / 255.f;
		clr[3] = button->data[4] / 255.f;

		trap_R_SetColor(clr);
	}

	if (button->data[5])
	{
		DC->drawRect(button->rect.x, button->rect.y, button->rect.w, button->rect.h, 1, clr);
	}
	else
	{
		DC->drawHandlePic(button->rect.x, button->rect.y, button->rect.w, button->rect.h, button->hShaderNormal);
	}

	if (button->data[0])
	{
		trap_R_SetColor(NULL);
	}
}

/**
 * @brief BG_PanelButtons_GetFocusButton
 * @return
 */
panel_button_t *BG_PanelButtons_GetFocusButton(void)
{
	return bg_focusButton;
}

/**
 * @brief BG_PanelButtons_SetFocusButton
 * @param button
 */
void BG_PanelButtons_SetFocusButton(panel_button_t *button)
{
	bg_focusButton = button;
}

/**
 * @brief BG_FitTextToWidth_Ext
 * @param[in,out] instr
 * @param[in] scale
 * @param[in] w
 * @param[in] size
 * @param[in] font
 */
void BG_FitTextToWidth_Ext(char *instr, float scale, float w, size_t size, fontHelper_t *font)
{
	char buffer[1024];
	char *s, *p, *c, *ls = NULL;

	Q_strncpyz(buffer, instr, 1024);
	Com_Memset(instr, 0, size);

	c = s = instr;
	p = buffer;

	while (*p)
	{
		*c = *p++;

		if (*c == ' ')
		{
			ls = c;
		} // store last space, to try not to break mid word

		c++;

		if (*p == '\n')
		{
			s = c + 1;
		}
		else if (DC->textWidthExt(s, scale, 0, font) > w)
		{
			if (ls)
			{
				*ls = '\n';
				s   = ls + 1;
			}
			else
			{
				*c       = *(c - 1);
				*(c - 1) = '\n';
				s        = c++;
			}

			ls = NULL;
		}
	}

	if (c != buffer && (*(c - 1) != '\n'))
	{
		*c++ = '\n';
	}

	*c = '\0';
}

/**
 * @brief Adjusting panel coordinates so it is horizontally centered.
 * @param[in,out] buttons
 * @param[in] xoffset
 */
void C_PanelButtonsSetup(panel_button_t **buttons, float xoffset)
{
	panel_button_t *button;

	if (xoffset == 0.0f)
	{
		return;
	}

	for ( ; *buttons; buttons++)
	{
		button          = (*buttons);
		button->rect.x += xoffset;
	}
}
