////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 RWS Inc, All Rights Reserved
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as published by
// the Free Software Foundation
//
// 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, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
// menu.CPP
//
// History:
//		11/27/96 JMI	Started.
//
//		12/16/96	JMI	While not complete, this now contains most of the
//							features discussed.
//
//		12/21/96	JMI	Now uses RResMgr for all loaded resources except
//							ms_guiIndicator.m_im since it is not yet practical.
//							Once there is a generic loadable type, such as RFile*
//							or void*, this will be easier.  The possible work
//							around for the meantime could be a func to load an
//							image resource, allocate the gui via Create() to
//							the size of that resource, rspBlit() the image resource
//							into the gui, and Release() the image resource.
//
//		12/21/96	JMI	Now calls ms_msgbox.SetVisible(TRUE) to show and activate
//							ms_msgbox and its children at the end of a successful
//							StartMenu().  Also, calls ms_msgbox.SetVisible(FALSE) to
//							hide and deactivate ms_msgbox and its children during
//							StopMenu().
//
//		01/19/97	JMI	Converted Do() to taking an RInputEvent* instead of a
//							long*.
//
//		01/27/97	JMI	NextItem() was bailing out one too early when it thought
//							it detected that there were no enabled menu items.
//
//		01/27/97	JMI	Now sets palette to background image.
//
//		01/30/97	JMI	Now callback can return non-zero aborting menu right
//							away once started.
//
//		02/02/97	JMI	Now the GUI can be set to use transparent BLiT'ing via
//							the Menu struct.  Now MenuItemCall with -1 for sMenuItem
//							indicates change of menu focus (but no choice yet made).
//
//		02/03/97	JMI	Split DoMenu() into DoMenuInput() and DoMenuOutput() so
//							the input and output for menus can be done at different
//							points in the user loop or whatever.
//
//		02/04/97	JMI	Changed LoadDib() call to Load() (which now supports
//							loading of DIBs).
//
//		02/04/97	JMI	Callback on StopMenu() now occurs after all deallocations.
//
//		02/10/97	JMI	rspReleaseResource() now takes a ptr to a ptr.
//
//		04/08/97	JMI	Now sucks up (and utilizes) Tab and Shift-Tab combos.
//
//		04/11/97	JMI	Now sets the new m_sFontCellHeight field in each GUI
//							that displays text in a menu.
//
//		04/21/97	JMI	Added pmenuBack to MenuAutoItems.
//
//		04/24/97	JMI	Now supports the centering flag for header text and
//							two new flags for shadowing the header and the items.
//							Now colors are specified as RGBA and background font
//							color must be transparent (not offered as an option).
//
//		06/16/97	JMI	Now stores the current composite buffer contents on
//							StartMenu() and restores them on StopMenu().
//
//		07/01/97	JMI	Added sBackItem to MenuAutoItems so when we use pmenuBack
//							we can select the item last selected.
//
//		07/06/97	JMI	Added MenuColumnizeGuis to make the GUIs associated with
//							menu items appear in their own column.
//
//		07/21/97	JMI	Now displays disabled items with shadow and text colors
//							swapped.
//
//		08/10/97	JMI	Made MAX_MENU_ITEMS based on sizeof ami array.
//
//		08/14/97	JMI	MenuItemCall (fnChoice) now returns true to accept menu
//							choice, false otherwise.
//
//		08/22/97	JMI	Now locks the composite buffer before accessing it.
//
//		08/25/97	JMI	Added two new palette info entries for the range of
//							mappable entries.
//
//		08/27/97	JMI	Now the cancel item cannot be disabled...good thing?
//							Probably...otherwise menus cancelled by callbacks and
//							such would have no where to go which was kind of worrying
//							me before.  Also, Useful for having no actual cancel item.
//
//////////////////////////////////////////////////////////////////////////////
//
// This module operates a menu specified by the user via a structure, Menu.
// There are only a few basic calls, StartMenu(), DoMenuInput(),
// DoMenuOutput(), GetCurrentMenu(), and StopMenu().  Communication back to
// the caller is via callbacks.
//
// The user must define a Menu struct and pass a pointer to it to StartMenu().
// At this point, the StopMenu() will automatically be called to destroy any
// existing menu.  Next, the new menu described by the Menu struct is created.
// Now, iteratively, the DoMenuInput() and DoMenuOutput() functions should be
// called to service the menu.  This will process the supplied input, if it
// pertains to the menu, and draw the menu into the supplied image.  Finally,
// when done with the menu, call StopMenu() to clean up the current menu.
// Calls to DoMenu*() when no menu is active will simply return.
//
// Note regarding RResMgr.  The only members of the RResMgr utilized are
// Load(...) and Release(...).  This means that the user of the Menu API is
// resposible for the other stuff, such as OpenSak(), CloseSak(), Purge(),
// etc.
//
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// C Headers -- Must be included before RSPiX.h b/c RSPiX utilizes SHMalloc.
//////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// RSPiX Headers.
// If PATHS_IN_INCLUDES macro is defined, we can utilize relative
// paths to a header file.  In this case we generally go off of our
// RSPiX root directory.  System.h MUST be included before this macro
// is evaluated.  System.h is the header that, based on the current
// platform (or more so in this case on the compiler), defines
// PATHS_IN_INCLUDES.  Blue.h includes system.h so you can include that
// instead.
///////////////////////////////////////////////////////////////////////////////
#include "Blue.h"
#include "input.h"
#ifdef PATHS_IN_INCLUDES
#include "ORANGE/color/colormatch.h"
#include "ORANGE/MsgBox/MsgBox.h"
#include "CYAN/cyan.h"
#else
#include "colormatch.h"
#include "MsgBox.h"
#include "cyan.h"
#endif

//////////////////////////////////////////////////////////////////////////////
// Application headers.
//////////////////////////////////////////////////////////////////////////////
#include "menu.h"

#include "pvr.h"

//////////////////////////////////////////////////////////////////////////////
// Module specific macros.
//////////////////////////////////////////////////////////////////////////////

// Determines the number of elements in the passed array at compile time.
#define NUM_ELEMENTS(a) (sizeof(a) / sizeof(a[0]))

#define MAX_MENU_ITEMS short(NUM_ELEMENTS(ms_pmenu->ami) - 1)

// Determines the indicator's Y position based on the supplied menu item index.
#define INDICATOR_POS_Y(sIndex) (ms_asMenuItemPosY[sIndex] - \
								 ms_guiIndicator.heightPvr / 2)

// Determines the indiator's X position based on the supplied menu item index.
#define INDICATOR_POS_X(sIndex) (ms_asMenuItemPosX[sIndex] - \
								 ms_guiIndicator.widthPvr -  \
								 ms_pmenu->menupos.sIndicatorSpacingX)

// Determines an item index from a positive index or a negative offset from
// the number of items.
#define ITEMINDEX(sIndex) (((sIndex) < 0) ? ms_sNumMenuItems + (sIndex) : (sIndex))

#define INVALID_MENU_ITEM 0x7fff

#define SHADOW_X_PIXELS 1
#define SHADOW_Y_PIXELS 1

#ifdef SYS_ENDIAN_LITTLE
#define ALPHA(u32) ((u32 & 0x000000FF) >> 0)
#define RED(u32) ((u32 & 0x0000FF00) >> 8)
#define GREEN(u32) ((u32 & 0x00FF0000) >> 16)
#define BLUE(u32) ((u32 & 0xFF000000) >> 24)
#else
#define ALPHA(u32) ((u32 & 0xFF000000) >> 24)
#define RED(u32) ((u32 & 0x00FF0000) >> 16)
#define GREEN(u32) ((u32 & 0x0000FF00) >> 8)
#define BLUE(u32) ((u32 & 0x000000FF) >> 0)
#endif

//////////////////////////////////////////////////////////////////////////////
// Module specific typedefs.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Exported (extern) variables.
//////////////////////////////////////////////////////////////////////////////

extern PVR::cRenderMgr *g_pvrRenderer;

//////////////////////////////////////////////////////////////////////////////
// Module specific (static) variables / Instantiate class statics.
//////////////////////////////////////////////////////////////////////////////

static Menu *ms_pmenu = NULL; // Pointer to current menu.

static RMsgBox ms_msgbox; // Menu container.

static int16_t ms_sNumMenuItems = 0; // Current number of menu items.

static RFont *ms_pfontItems = NULL;	 // Font for menu items.
static RFont *ms_pfontHeader = NULL; // Font for menu header.

static RPrint ms_printItems;  // Print used for guis.
static RPrint ms_printHeader; // Print used for header.

//static RImage*		ms_pimBackground	= NULL;	// Dlg's background image.

static RGuiItem ms_guiIndicator; // Current menu item indicator.

static RTxt ms_txtHeader; // Header text item.

static int16_t ms_sCurItem = 0;						 // Current menu item index.
static int16_t ms_sNextMenuItem = INVALID_MENU_ITEM; // Item to select on next
													 // StartMenu().

static int16_t ms_asMenuItemPosX[MAX_MENU_ITEMS]; // Array of item X positions.
static int16_t ms_asMenuItemPosY[MAX_MENU_ITEMS]; // Array of item Y positions.

#ifdef MOBILE
static int16_t ms_asMenuItemMouseHeight[MAX_MENU_ITEMS]; // Array of item heights which can be used for mouse
#endif

static int16_t ms_sItemsPerColumn; // Number of items per column

static int16_t ms_sCancel = FALSE; // TRUE to cancel current menu.

static int16_t ms_sItemsX = 0; // X position of menu items.
static int16_t ms_sItemsY = 0; // Y position of menu items.

static RResMgr *ms_presmgr = NULL;

//////////////////////////////////////////////////////////////////////////////
// Module specific (static) protos.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Internal Functions.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// Critical callage for our msgbox.
//
//////////////////////////////////////////////////////////////////////////////
static uint32_t CriticalCall( // Returns item to select or 0.
	RMsgBox * /*pmsgbox*/)	  // MsgBox.
{
	// For now, we expect the caller of this Menu API to call rspDoSystem(),
	// RHot::Do(), etc.

	return 0; // No item preferred.
}

//////////////////////////////////////////////////////////////////////////////
//
// Draws the background for ms_msgbox.
//
//////////////////////////////////////////////////////////////////////////////
static void BackCall( // Returns nothing.
	RGuiItem *pgui,	  // The RGuiItem being composed (this).					  
	RRect *prc)		  // Where to in image.
{

	ASSERT(ms_pmenu != NULL);	
	ASSERT(prc != NULL);
	ASSERT(pgui != NULL);

	// If this is our ms_msgbox . . .
	if (pgui == &ms_msgbox)
	{
		if (ms_pmenu->menuflags & MenuBackTiled)
		{
			// todo: clean up
			uint16_t menuWidth;
			uint16_t menuHeight;

			switch (prc->sW)
			{
			case 280:
				menuWidth = 280;
				menuHeight = 350;
				break;
			case 450:
				menuWidth = 450;
				menuHeight = 460;
				break;
			case 640:
				menuWidth = 640;
				menuHeight = 480;
				break;
			}

			int16_t sOffsetX = pgui->m_sX + ((prc->sW % menuWidth) / 2);
			int16_t sOffsetY = pgui->m_sY + ((prc->sH % menuHeight) / 2);
			int16_t sMaxX = pgui->m_sX + (prc->sW - (prc->sW % menuWidth));
			int16_t sMaxY = pgui->m_sY + (prc->sH - (prc->sH % menuHeight));

			// Tile.
			int16_t sX, sY;
			// Determine amount to offset to center tiled area.

			if (ms_msgbox.backgroundTexture != nullptr)
			{
				for (sY = prc->sY + sOffsetY; sY < sMaxY; sY += menuWidth)
				{
					for (sX = prc->sX + sOffsetX; sX < sMaxX; sX += menuHeight)
					{
						// todo??
						if (g_pvrRenderer->m_szDrawMode == PVR::cRenderMgr::Batched)
						{
							g_pvrRenderer->DrawQuad(
							ms_msgbox.backgroundTexture,
							sX,
							sY,
							menuWidth,
							menuHeight,
							PVR::cRenderMgr::LayerSprite1); 							
						}
						else
						{
							g_pvrRenderer->DrawQuadDirect(
								ms_msgbox.backgroundTexture,
								sX,
								sY,
								menuWidth,
								menuHeight,
								42); // todo: layer								
						}
						
						break;
					}
					break;
				}
			}
		}
		else
		{		
			// Center.
			// todo???			
		}

	}	
}

//////////////////////////////////////////////////////////////////////////////
//
// Copies select attributes from one gui to another.
//
//////////////////////////////////////////////////////////////////////////////
inline void SetGuiAttributes(
	RGuiItem *pguiDst, // Gui to set attributes to.
	RGuiItem *pguiSrc) // Gui to get attributes from.
{
	pguiDst->m_u32TextColor = pguiSrc->m_u32TextColor;
	pguiDst->m_u32BackColor = pguiSrc->m_u32BackColor;
	pguiDst->m_pprint = pguiSrc->m_pprint;
}

//////////////////////////////////////////////////////////////////////////////
//
// Moves the next item in the specified "direction".
//
//////////////////////////////////////////////////////////////////////////////
inline bool NextItem( // Returns true, if new item selected.
	int16_t sDirVert, // Specifies direction (and amount) to move by being
					  // negative or positive.  If 0 when sDirHorz is 0,
					  // the current item is checked and, if disabled,
					  // sDirVert is set to 1.
	int16_t sDirHorz) // Specifies direction (and amount) to move by being
					  // negative or positive.  If 0 when sDirVert is 0,
					  // the current item is checked and, if disabled,
					  // sDirVert is set to 1.
{
	bool bNewSelection = false; // Assume no new selection.

	ASSERT(ms_pmenu != NULL);

	int16_t sNumItemsTraversed = 0;
	int16_t sOldItem = ms_sCurItem;

	do
	{
		// Move current item vertically.
		ms_sCurItem += sDirVert;
		// Move current item horizontally.
		ms_sCurItem += (sDirHorz * ms_sItemsPerColumn);

		// If direction is 0 . . .
		if (sDirVert == 0 && sDirHorz == 0)
		{
			// Make sDir forward 1.
			sDirVert = 1;
			// This feature allows the caller to have the current item checked
			// and, if disabled, the next enabled item will be chosen.
		}
		else
		{
			sNumItemsTraversed++;
		}

		// Check bounds.
		if ((ms_sCurItem > ms_sNumMenuItems) || (ms_sCurItem == ms_sNumMenuItems && sDirHorz))
		{
			if (ms_sItemsPerColumn > 0)
			{
				ms_sCurItem = ms_sCurItem % ms_sItemsPerColumn;
			}
			else
			{
				ms_sCurItem = 0;
			}
		}
		else if (ms_sCurItem == ms_sNumMenuItems)
		{
			ms_sCurItem = 0;
		}
		else if ((ms_sCurItem < -1) || (ms_sCurItem == -1 && sDirHorz))
		{
			if (ms_sItemsPerColumn > 0)
			{

				// Determine location of item in next column.
				int16_t sMaxMenuItem = MAX(ms_sNumMenuItems - 1, 0);
				ms_sCurItem = sMaxMenuItem / ms_sItemsPerColumn * ms_sItemsPerColumn + (ms_sItemsPerColumn + ms_sCurItem);
				// If too large . . .
				if (ms_sCurItem >= ms_sNumMenuItems)
				{
					ASSERT(sDirHorz < 0);
					// Go back another column.
					ms_sCurItem += (sDirHorz * ms_sItemsPerColumn);
				}
			}
			else
			{
				ms_sCurItem = 0;
			}
		}
		else if (ms_sCurItem == -1)
		{
			ms_sCurItem = MAX(ms_sNumMenuItems - 1, 0);
		}

	} while (
		(ms_pmenu->ami[ms_sCurItem].sEnabled == FALSE || 
			(ms_pmenu->ami[ms_sCurItem].sEnabled && strstr("", ms_pmenu->ami[ms_sCurItem].pszText) != NULL))
		&& sNumItemsTraversed <= ms_sNumMenuItems
	);

	// If we've tried all menu items . . .
	if (sNumItemsTraversed > ms_sNumMenuItems)
	{
		// Cancel this menu.
		ms_sCancel = TRUE;
	}
	else
	{
		// If there is a callback . . .
		if (ms_pmenu->menucallbacks.fnChoice != NULL)
		{
			// If there was a change in the selected item . . .
			if (ms_sCurItem != sOldItem)
			{
				bNewSelection = true;
				// Notify caller of change in focused item.
				(*(ms_pmenu->menucallbacks.fnChoice))(ms_pmenu, -1, ms_sCurItem);
			}
		}
	}

	// Set indicator vertical position based on new item.
	// Set indicator horziontal position based on new item.
	ms_guiIndicator.Move(
		INDICATOR_POS_X(ms_sCurItem),
		INDICATOR_POS_Y(ms_sCurItem));

	// Set focus to this item's gui or to no item, if none.
	ms_msgbox.SetFocus(ms_pmenu->ami[ms_sCurItem].pgui);

	return bNewSelection;
}
//////////////////////////////////////////////////////////////////////////////
// External Functions.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// Sets up the menu described to be run via DoMenu*() calls.
//
//////////////////////////////////////////////////////////////////////////////
extern int16_t StartMenu( // Returns 0 on success.
	Menu *pmenu,		  // In:  Pointer to Menu describing menu.
	RResMgr *presmgr	  // In:  Resource manager ptr.
	)					  // In:  Src for erase data.  Erase data is
						  // updated back over the menu on StopMenu()
						  // calls.
{
	int16_t sRes = 0; // Assume success.
	PVR::cTexture *menuBg;
	// End any existing menu.		
	if (g_pvrRenderer->m_szDrawMode == PVR::cRenderMgr::Batched)
	{
		g_pvrRenderer->FlushBatchedDrawQueues();
	}
	
	StopMenu();

	// If there is a new menu . . .
	if (pmenu != NULL)
	{		
		U32 u32TextColor;
		U32 u32TextShadowColor;
		int16_t sMapStartIndex;
		int16_t sMapNumEntries;

		// Set new menu.
		ms_pmenu = pmenu;

		ms_sCancel = FALSE;

		// If there is a callback . . .
		if (ms_pmenu->menucallbacks.fnInit != NULL)
		{
			if ((*(ms_pmenu->menucallbacks.fnInit))(ms_pmenu, TRUE) == 0)
			{
			}
			else
			{
				TRACE("StartMenu(): StartMenu() aborted by callback.\n");
				// Abort right away.
				ms_sCancel = TRUE;
			}
		}

		ASSERT(presmgr != NULL);
		// Store pointer to desired res mgr.
		ms_presmgr = presmgr;

		// Determine number of menu items.
		ms_sNumMenuItems = 0;
		while (pmenu->ami[ms_sNumMenuItems].pszText != NULL)
		{
			ms_sNumMenuItems++;
		}

		ms_sNumMenuItems = MIN(ms_sNumMenuItems, MAX_MENU_ITEMS);

		// Set GUI settings.
		ms_msgbox.m_sTransparent = ms_pmenu->menugui.sTransparent;

		// If there is a desired font for menu header . . .
		if (ms_pmenu->menuheader.pszFontFile != NULL)
		{
			ms_printHeader.SetFont(ms_pmenu->menuheader.sHeight, &g_fontPostal);
		}		

		if (ms_pmenu->menuflags & MenuHeaderTextShadow)
		{
			ms_txtHeader.m_sTextShadowOffsetX = SHADOW_X_PIXELS;
			ms_txtHeader.m_sTextShadowOffsetY = SHADOW_Y_PIXELS;
			ms_txtHeader.m_sTextEffects = RGuiItem::Shadow;
		}
		else
		{
			ms_txtHeader.m_sTextEffects = 0;
		}

		// If there is a desired font for menu items . . .
		if (ms_pmenu->menuitemsfont.pszFile != NULL)
		{
			ms_printItems.SetFont(ms_pmenu->menuitemsfont.sHeight, &g_fontPostal);
		}		

		// Set dlg's attributes based on ms_pmenu.
		// Set dlg's attributes that are not based on ms_pmenu.
		ms_msgbox.m_sBorderThickness = 0; // No border.
		
		// Use our print.
		ms_msgbox.m_pprint = &ms_printItems;
		// Use this text height.
		ms_msgbox.m_sFontCellHeight = ms_pmenu->menuitemsfont.sHeight;

		// Set callback for iterative calls.
		ms_msgbox.m_mbcUser = CriticalCall;

		// Compute position of dialog.
		int16_t sX;
		int16_t sY;
		int16_t sW;
		int16_t sH;

		if (ms_pmenu->menuflags & MenuPosCenter)
		{
			// Centered menu.
			sW = ms_pmenu->menupos.sW;
			sH = ms_pmenu->menupos.sH;
			sX = 640 / 2 - sW / 2;
			sY = 480 / 2 - sH / 2;
		}
		else
		{
			if (ms_pmenu->menuflags & MenuPosFullscreen)
			{
				// Full screen menu.
				sX = 0;
				sY = 0;
				sW = 640;
				sH = 480;
			}
			else
			{
				// Menu at specified location.
				sX = ms_pmenu->menupos.sX;
				sY = ms_pmenu->menupos.sY;
				sW = ms_pmenu->menupos.sW;
				sH = ms_pmenu->menupos.sH;
			}
		}

		// If there is a background . . .				
		menuBg = g_pvrRenderer->m_TextureRepository.Get(PVR_TEXTURE_MENU_BG);

		// Get color indices.
		U8 au8Red[256];
		U8 au8Green[256];
		U8 au8Blue[256];
		rspGetPaletteEntries(
			0,		   // Palette entry to start copying to (has no effect on source!)
			256,	   // Number of palette entries to do
			au8Red,	   // Pointer to first red component to copy to
			au8Green,  // Pointer to first green component to copy to
			au8Blue,   // Pointer to first blue component to copy to
			sizeof(U8) // Number of bytes by which to increment pointers after each copy
		);

		sMapStartIndex = ms_pmenu->menuback.sMapStartIndex;
		sMapNumEntries = ms_pmenu->menuback.sMapNumEntries;
		if (sMapNumEntries == 0)
		{
			sMapNumEntries = 256;
		}

		u32TextColor = rspMatchColorRGB(				 // Out: Matched index.
			RED(ms_pmenu->menuitemsfont.u32ForeColor),	 // In:  Pixel's red value.
			GREEN(ms_pmenu->menuitemsfont.u32ForeColor), // In:  Pixel's green value.
			BLUE(ms_pmenu->menuitemsfont.u32ForeColor),	 // In:  Pixel's blue value.
			sMapStartIndex,								 // In:  Min mappable index (affects source).
			sMapNumEntries,								 // In:  Num mappable indices (affects source).
			au8Red,										 // In:  Beginning of red color table.
			au8Green,									 // In:  Beginning of green color table.
			au8Blue,									 // In:  Beginning of blue color table.
			sizeof(U8)									 // In:  Size to increment between each index in each table.
		);	

		u32TextShadowColor = rspMatchColorRGB(			   // Out: Matched index.
			RED(ms_pmenu->menuitemsfont.u32ShadowColor),   // In:  Pixel's red value.
			GREEN(ms_pmenu->menuitemsfont.u32ShadowColor), // In:  Pixel's green value.
			BLUE(ms_pmenu->menuitemsfont.u32ShadowColor),  // In:  Pixel's blue value.
			sMapStartIndex,								   // In:  Min mappable index (affects source).
			sMapNumEntries,								   // In:  Num mappable indices (affects source).
			au8Red,										   // In:  Beginning of red color table.
			au8Green,									   // In:  Beginning of green color table.
			au8Blue,									   // In:  Beginning of blue color table.
			sizeof(U8)									   // In:  Size to increment between each index in each table.
		);

		ms_msgbox.m_u32BackColor = 0;

		ms_txtHeader.m_u32TextColor = rspMatchColorRGB( // Out: Matched index.
			RED(ms_pmenu->menuheader.u32ForeColor),		// In:  Pixel's red value.
			GREEN(ms_pmenu->menuheader.u32ForeColor),	// In:  Pixel's green value.
			BLUE(ms_pmenu->menuheader.u32ForeColor),	// In:  Pixel's blue value.
			sMapStartIndex,								// In:  Min mappable index (affects source).
			sMapNumEntries,								// In:  Num mappable indices (affects source).
			au8Red,										// In:  Beginning of red color table.
			au8Green,									// In:  Beginning of green color table.
			au8Blue,									// In:  Beginning of blue color table.
			sizeof(U8)									// In:  Size to increment between each index in each table.
		);

		ms_txtHeader.m_u32TextShadowColor = rspMatchColorRGB( // Out: Matched index.
			RED(ms_pmenu->menuheader.u32ShadowColor),		  // In:  Pixel's red value.
			GREEN(ms_pmenu->menuheader.u32ShadowColor),		  // In:  Pixel's green value.
			BLUE(ms_pmenu->menuheader.u32ShadowColor),		  // In:  Pixel's blue value.
			sMapStartIndex,									  // In:  Min mappable index (affects source).
			sMapNumEntries,									  // In:  Num mappable indices (affects source).
			au8Red,											  // In:  Beginning of red color table.
			au8Green,										  // In:  Beginning of green color table.
			au8Blue,										  // In:  Beginning of blue color table.
			sizeof(U8)										  // In:  Size to increment between each index in each table.
		);

		ms_txtHeader.m_u32BackColor = 0;

		// Create dialog . . .
		if (ms_msgbox.Create(sX, sY, sW, sH) == 0)
		{
			// Successfully created dialog.			
		}
		else
		{			
			TRACE("StartMenu(): Unable to create ms_msgbox.\n");
			sRes = -5;

			goto Done;
		}

		ms_sItemsX = ms_pmenu->menupos.sItemX;
		ms_sItemsY = ms_pmenu->menupos.sItemY;

		// If negative . . .
		if (ms_sItemsX < 0)
		{
			// Offset from center.
			ms_sItemsX += ms_msgbox.widthPvr / 2;
		}

		// If negative . . .
		if (ms_sItemsY < 0)
		{
			// Offset from center.
			ms_sItemsY += ms_msgbox.heightPvr / 2;
		}

		// If there is menu header text . . .
		if (ms_pmenu->menuheader.pszHeaderText[0] != '\0')
		{
			int16_t sPosX = ms_pmenu->menupos.sHeaderX;
			int16_t sPosY = ms_pmenu->menupos.sHeaderY;

			if (sPosX < 0)
			{
				// Offset from center.
				sPosX += ms_msgbox.widthPvr / 2;
			}

			if (sPosY < 0)
			{
				// Offset from center.
				sPosY += ms_msgbox.heightPvr / 2;
			}

			ms_txtHeader.m_pprint = &ms_printHeader;
			ms_txtHeader.m_sBorderThickness = 0;
			ms_txtHeader.m_sFontCellHeight = ms_pmenu->menuheader.sHeight;

			// Determine width for header . . .
			// Store text width.
			int16_t sTextWidth = ms_txtHeader.m_pprint->GetWidth(ms_pmenu->menuheader.pszHeaderText);
			// Active when visible.
			ms_txtHeader.m_sActive = TRUE;
			ms_txtHeader.SetVisible(TRUE);

			// If centering specified . . .
			if (ms_pmenu->menuflags & MenuHeaderTextCenter)
			{
				sPosX = ms_msgbox.widthPvr / 2 - sTextWidth / 2;
			}

			// Update header item.
			ms_txtHeader.SetText(ms_pmenu->menuheader.pszHeaderText);
			ms_txtHeader.m_sX = sPosX;
			ms_txtHeader.m_sY = sPosY;

			// Reserve space for borders, if there are any.
			int16_t sTotalBorderThickness = ms_txtHeader.GetTopLeftBorderThickness() + ms_txtHeader.GetBottomRightBorderThickness();

			// todo?			
			ms_txtHeader.offsetPvrX = sPosX + sX;
			ms_txtHeader.offsetPvrY = sPosY + sY;

			// Create item . . .
			if (ms_txtHeader.Create(
					sPosX,
					sPosY,
					sTextWidth + sTotalBorderThickness + SHADOW_X_PIXELS,
					ms_txtHeader.m_sFontCellHeight + sTotalBorderThickness + SHADOW_Y_PIXELS) == 0)
			{
				// Successfully created header item.
				ms_txtHeader.m_sTransparent = TRUE;
				ms_txtHeader.m_u32TransparentColor = 0;
				// Add to msg box . . .
				ms_txtHeader.SetParent(&ms_msgbox);
			}
			else
			{
				TRACE("StartMenu(): ms_txtHeader.Create() failed.\n");
				sRes = -11;

				goto Done;
			}
		}

		ms_sItemsPerColumn = 0;

		if (ms_sNumMenuItems > 0)
		{
			int16_t sMaxItemX = ms_pmenu->menupos.sMaxItemX;
			int16_t sMaxItemY = ms_pmenu->menupos.sMaxItemY;

			// If less than 1 . . .
			if (sMaxItemX < 1)
			{
				sMaxItemX += ms_msgbox.widthPvr;
			}

			// If less than 1 . . .
			if (sMaxItemY < 1)
			{
				sMaxItemY += ms_msgbox.heightPvr;
			}

			int16_t sPosX = ms_sItemsX;
			int16_t sIndex = 0;

			while (sIndex < ms_sNumMenuItems && sPosX < sMaxItemX)
			{
				int16_t sPosY = ms_sItemsY;
				int16_t sMaxGuiPosX = sPosX;
				int16_t sMaxItemExtentX = sPosX;
				int16_t sStartIndex = sIndex; // First index this column.

				RTxt *ptxt;
				for (; sIndex < ms_sNumMenuItems; sIndex++)
				{
					// Color is based on whether the item is enabled or not.
					if (ms_pmenu->ami[sIndex].sEnabled)
					{
						ms_msgbox.m_u32TextColor = u32TextColor;
						ms_msgbox.m_u32TextShadowColor = u32TextShadowColor;
					}
					else
					{
						ms_msgbox.m_u32TextColor = u32TextShadowColor;
						ms_msgbox.m_u32TextShadowColor = u32TextColor;
					}

					// Add a text item and get a pointer to it.
					// Note that the item is stored and maintained by RMsgBox.

					ptxt = ms_msgbox.AddText(
						ms_pmenu->ami[sIndex].pszText,
						sPosX, sPosY,
						sIndex,
						SHADOW_X_PIXELS,
						SHADOW_Y_PIXELS,
						sPosX + sX,
						sPosY + sY,
						(ms_pmenu->menuflags & MenuItemTextShadow));

					if (ptxt != NULL)
					{

						// Remember pos of vertical center of this item.
						ms_asMenuItemPosY[sIndex] = sPosY + ptxt->heightPvr / 2;
						// Remember x position of this item.
						ms_asMenuItemPosX[sIndex] = sPosX;

						// If there is a gui . . .
						if (ms_pmenu->ami[sIndex].pgui != NULL)
						{
							// Put gui item just to right of text and
							// centered vertically with text.
							ms_pmenu->ami[sIndex].pgui->Move(
								sPosX + ptxt->widthPvr + ms_pmenu->menupos.sIndicatorSpacingX,
								sPosY + ptxt->heightPvr / 2 - ms_pmenu->ami[sIndex].pgui->heightPvr / 2);

							// Remember the position of the furthest GUI.
							sMaxGuiPosX = MAX(sMaxGuiPosX, ms_pmenu->ami[sIndex].pgui->m_sX);

							ms_pmenu->ami[sIndex].pgui->SetParent(&ms_msgbox);
							ms_pmenu->ami[sIndex].pgui->SetVisible(TRUE);

							// Remember the extent of the furthest item.
							sMaxItemExtentX = MAX(
								sMaxItemExtentX,
								int16_t(ms_pmenu->ami[sIndex].pgui->m_sX + ms_pmenu->ami[sIndex].pgui->widthPvr));
						}
						else
						{
							// Remember the extent of the furthest item.
							sMaxItemExtentX = MAX(
								sMaxItemExtentX,
								int16_t(ptxt->m_sX + ptxt->widthPvr));
						}

						ptxt->m_sTransparent = true;
						ptxt->m_u32TransparentColor = 0;

						// Move to pos for next item.
						sPosY += ptxt->heightPvr + ms_pmenu->menupos.sItemSpacingY;

						// If this next position is too far . . .
						if (sPosY + ptxt->heightPvr > sMaxItemY)
						{
							sIndex++;
							break;
						}
					}
					else
					{
						TRACE("StartMenu(): ms_msgbox.AddText() failed for item %s.\n",
							  ms_pmenu->ami[sIndex].pszText);
						sRes = -10;

						goto Done;
					}
				}

				if (ms_sItemsPerColumn == 0)
				{
					ms_sItemsPerColumn = sIndex;
				}

				// If columnize GUIs specified . . .
				if (ms_pmenu->menuflags & MenuColumnizeGuis)
				{
					// End index EXCLUSIVE is either an entire column
					// or a partial (for the last column).
					int16_t sEndIndex = MIN(sIndex, int16_t(sStartIndex + ms_sItemsPerColumn));

					for (sIndex = sStartIndex; sIndex < sEndIndex; sIndex++)
					{
						// If there is a gui . . .
						if (ms_pmenu->ami[sIndex].pgui != NULL)
						{
							// Shift GUI over to furthest GUI position.
							ms_pmenu->ami[sIndex].pgui->Move(
								sMaxGuiPosX,
								ms_pmenu->ami[sIndex].pgui->m_sY);
						}
					}
				}

				// Next column.
				//sPosX = sMaxItemExtentX + ms_pmenu->menupos.sIndicatorSpacingX + ms_guiIndicator.m_im.m_sWidth;
				sPosX = sMaxItemExtentX + ms_pmenu->menupos.sIndicatorSpacingX + ms_guiIndicator.widthPvr;
			}

			// Store number of menu items that fit.
			ms_sNumMenuItems = sIndex;
		}

		// If we went back to this menu . . .
		if (ms_sNextMenuItem != INVALID_MENU_ITEM)
		{
			// We don't need to use the ITEMINDEX macro if we set this
			// value, but just in case a callback decides to use the
			// extended indexing . . ..
			ms_sCurItem = ITEMINDEX(ms_sNextMenuItem);
			// Clear back item.
			ms_sNextMenuItem = INVALID_MENU_ITEM;
		}
		else
		{
			// Start at default item.
			ms_sCurItem = ITEMINDEX(ms_pmenu->menuautoitems.sDefaultItem);
		}

		// If there is a menu indicator . . .
		if (ms_pmenu->menuindicator.pszFile != NULL)
		{			
			// Set indicator position.	
			PVR::cTexture* tex = g_pvrRenderer->m_TextureRepository.Get(PVR_TEXTURE_MENU_BULLET);		
			ms_guiIndicator.isPvr = true;			
			ms_guiIndicator.widthPvr = tex->GetImageWidth();
			ms_guiIndicator.heightPvr = tex->GetImageHeight();
			ms_guiIndicator.tex = tex;

			// Make indicator a child of ms_msgbox.
			ms_guiIndicator.SetParent(&ms_msgbox);

			ms_guiIndicator.m_sX = ms_sItemsX - ms_guiIndicator.widthPvr - ms_pmenu->menupos.sIndicatorSpacingX;			

			// This will set ms_guiIndicator.m_sY and will skip to the first enabled entry
			// which starting with ms_sCurItem.
			NextItem(0, 0);			
		}
		else
		{
			// Make indicator top level (so it won't show up when ms_msgbox is drawn).
			// Note we could make the indicator not visible, instead.
			ms_guiIndicator.SetParent(NULL);
		}
				
	Done:

		// If any errors occurred . . .
		if (sRes != 0)
		{
			StopMenu();

			TRACE("StartMenu(): Menu not started due to start up error.\n");
		}
		else
		{
			// Show and activate msg box and children.
			ms_msgbox.SetVisible(TRUE);
		}
	}
	
	return sRes;
}

//////////////////////////////////////////////////////////////////////////////
//
// Performs one iteration of menu input logic.  Call this repeatedly in
// conjunction with DoMenuOutput() to run the menu.
//
//////////////////////////////////////////////////////////////////////////////
extern void DoMenuInput( // Returns nothing.
	RInputEvent *pie	 // In:  Most recent user input event.
	)
// Out: pie->sUsed = TRUE, if used.
{
	// If there is a current menu . . .
	if (ms_pmenu != NULL)
	{
		int16_t sChooseCurrent = FALSE;						
		for (int8_t i = 0; i < MAPLE_CONTROLLER_MAX; i++)		
		{
			cMapleController *pController = rspGetJoy(i);			
			bool bExtended = pController->IsExtended();
			CInputSettings *pInputSettings = bExtended ? &g_InputSettings[CONTROL_SCHEME_TWINSTICK] : &g_InputSettings[CONTROL_SCHEME_STANDARD];	
			MapleInputState mis = {}; // Current MapleInput state	
					
			if (pController->Status() != MAPLE::cMapleController::ControllerDisconnected)
			{			
				GetMapleInputState(&mis, pController->Id());			
				// Menu up.
				if (mis.ButtonState[MAPLEINPUT_BUTTON_MENUUP] == MapleInputState::Press || mis.AxisState[MAPLEINPUT_AXIS_MENUUP] == MapleInputState::Press || (bExtended && mis.AxisState[MAPLEINPUT_AXIS_MENUUP2] == MapleInputState::Press))
				{										
					if (g_sActivePlayers & g_sPlayerFlags[i] || !g_bLimitMenuInput)
					{						
						g_sActiveMenuController = i;						
						NextItem(-1, 0);
					}					
				}

				// Menu down.
				if (mis.ButtonState[MAPLEINPUT_BUTTON_MENUDOWN] == MapleInputState::Press || mis.AxisState[MAPLEINPUT_AXIS_MENUDOWN] == MapleInputState::Press || (bExtended && mis.AxisState[MAPLEINPUT_AXIS_MENUDOWN2] == MapleInputState::Press))
				{									
					if (g_sActivePlayers & g_sPlayerFlags[i] || !g_bLimitMenuInput)
					{
						g_sActiveMenuController = i;						
						NextItem(1, 0);
					}				
				}
				
				// Menu left.
				if (mis.ButtonState[MAPLEINPUT_BUTTON_MENULEFT] == MapleInputState::Press || mis.AxisState[MAPLEINPUT_AXIS_MENULEFT] == MapleInputState::Press || (bExtended && mis.AxisState[MAPLEINPUT_AXIS_MENULEFT2] == MapleInputState::Press))
				{					
					if (g_sActivePlayers & g_sPlayerFlags[i] || !g_bLimitMenuInput)
					{
						g_sActiveMenuController = i;
						NextItem(0, -1);
						//!! HACK
						pie->lKey = RSP_GK_LEFT;
						pie->sUsed = 0;
						pie->type = RInputEvent::Key;
					}				
				}

				// todo input
				// Menu right.
				if (mis.ButtonState[MAPLEINPUT_BUTTON_MENURIGHT] == MapleInputState::Press || mis.AxisState[MAPLEINPUT_AXIS_MENURIGHT] == MapleInputState::Press || (bExtended && mis.AxisState[MAPLEINPUT_AXIS_MENURIGHT2] == MapleInputState::Press))
				{					
					if (g_sActivePlayers & g_sPlayerFlags[i] || !g_bLimitMenuInput)
					{
						g_sActiveMenuController = i;
						NextItem(0, 1);
						//!! HACK
						pie->lKey = RSP_GK_RIGHT;
						pie->sUsed = 0;
						pie->type = RInputEvent::Key;
					}
				}

				// Cancel/goback.
				if (mis.ButtonState[MAPLEINPUT_BUTTON_BACK] == MapleInputState::Press || mis.ButtonState[MAPLEINPUT_BUTTON_BACK2] == MapleInputState::Press || (bExtended && mis.ButtonState[MAPLEINPUT_BUTTON_BACK3] == MapleInputState::Press))
				{
					if (g_sActivePlayers & g_sPlayerFlags[i] || !g_bLimitMenuInput)
					{
						g_sActiveMenuController = i;
						ms_sCancel = TRUE;
					}									
				}

				// Confirm.
				if (mis.ButtonState[MAPLEINPUT_BUTTON_CONFIRM] == MapleInputState::Press || (bExtended && mis.ButtonState[MAPLEINPUT_BUTTON_CONFIRM2] == MapleInputState::Press) || mis.ButtonState[MAPLEINPUT_BUTTON_START] == MapleInputState::Press)
				{
					g_sActiveMenuController = i;
					sChooseCurrent = TRUE;
				}
			}
		}
					
		// If cancelled . . .
		if (ms_sCancel != FALSE)
		{
			int16_t sCancelItem = ITEMINDEX(ms_pmenu->menuautoitems.sCancelItem);
			// If enabled . . .
			if (ms_pmenu->ami[sCancelItem].sEnabled != FALSE)
			{
				// Choose cancel item.
				ms_sCurItem = sCancelItem;
				sChooseCurrent = TRUE;
			}

			// Reset cancel flag.
			ms_sCancel = FALSE;
		}

		if (sChooseCurrent != FALSE)
		{
			bool bAcceptChoice = true;

			// If there is a callback . . .
			if (ms_pmenu->menucallbacks.fnChoice != NULL)
			{
				// See if callback will allow this choice . . .
				bAcceptChoice = (*(ms_pmenu->menucallbacks.fnChoice))(ms_pmenu, ms_sCurItem, ms_sCurItem);
			}

			// If choice is still okay . . .
			if (bAcceptChoice == true)
			{
				// If there is still a current menu . . .
				if (ms_pmenu != NULL)
				{
					Menu *pmenuNext = ms_pmenu->ami[ms_sCurItem].pmenu;
					// If this is the cancel item . . .
					if (ms_sCurItem == ITEMINDEX(ms_pmenu->menuautoitems.sCancelItem))
					{
						// If there is a go back menu . . .
						if (ms_pmenu->menuautoitems.pmenuBack != NULL)
						{
							// Start menu to go back to.
							pmenuNext = ms_pmenu->menuautoitems.pmenuBack;
							// Should we clear the go back menu item (pmenuBack)
							// here? ***
							// Set selection for new menu.
							ms_sNextMenuItem = ms_pmenu->menuautoitems.sBackItem;
						}
					}
					else
					{
						if (pmenuNext != NULL)
						{
							// Store menu to go back to.
							pmenuNext->menuautoitems.pmenuBack = ms_pmenu;
							// Store item to go back to.
							pmenuNext->menuautoitems.sBackItem = ms_sCurItem;
						}
					}

					// If there is a menu . . .
					if (pmenuNext != NULL)
					{
						// Start the new menu.
						StartMenu(pmenuNext, ms_presmgr);					
					}
				}
			}
		}

		// Make sure menu is still active (even after callbacks) . . .
		if (ms_pmenu != NULL)
		{
			// Call msgbox.
			ms_msgbox.DoModeless(pie);			
		}
	}
}

//////////////////////////////////////////////////////////////////////////////
//
// Performs one iteration of menu output logic.  Call this repeatedly in
// conjunction with DoMenuInput() to run the menu.
//
//////////////////////////////////////////////////////////////////////////////
extern void DoMenuOutput()
{
	// If there is a current menu . . .
	if (ms_pmenu != NULL)
	{
		// Draw msgbox.
		// todo?
		//PVR::cTexture *tex = g_pvrRenderer->m_TextureRepository.Get(string(ms_pmenu->menuback.pszFile));
		PVR::cTexture *tex = g_pvrRenderer->m_TextureRepository.Get(PVR_TEXTURE_MENU_BG);
		
		if (tex != nullptr)
		{
			ms_msgbox.backgroundTexture = tex;
		}
		else
		{
			ms_msgbox.backgroundTexture = nullptr;
		}
		
		RRect rc;
		ms_msgbox.GetClient(&rc.sX, &rc.sY, &rc.sW, &rc.sH);
		BackCall(&ms_msgbox, &rc);			
		ms_msgbox.DrawPVR();			
	}
}

//////////////////////////////////////////////////////////////////////////////
//
// Get the current menu.
//
//////////////////////////////////////////////////////////////////////////////
extern Menu *GetCurrentMenu(void) // Returns the a pointer to the current
								  // menu or NULL if there is none.
{
	return ms_pmenu;
}

//////////////////////////////////////////////////////////////////////////////
//
// Call this to release all memory being used by the menu system
// It should be safe to call this function even if StartMenu()
// failed, and it should be safe to call it multiple times -- in
// both cases, it shouldn't do anything.
//
//////////////////////////////////////////////////////////////////////////////
extern int16_t StopMenu(void) // Returns 0 on success.
{
	int16_t sRes = 0; // Assume success.
		
	// If we have a current menu . . .
	if (ms_pmenu != NULL)
	{
		// Clean up.

		// Destroy any dynamic dlg data.
		ms_msgbox.Destroy();

		// Remove all child items created via ms_msgbox.
		ms_msgbox.RemoveAll();

		// Hide and deactivate msg box and remaining children.
		ms_msgbox.SetVisible(FALSE);

		int16_t sIndex;
		for (sIndex = 0; sIndex < ms_sNumMenuItems; sIndex++)
		{
			// If there is a gui . . .
			if (ms_pmenu->ami[sIndex].pgui != NULL)
			{
				ms_pmenu->ami[sIndex].pgui->SetParent(NULL);
			}
		}

		// Destroy indicator data.
		ms_guiIndicator.Destroy();

		// Delete fonts.
		if (ms_pfontItems != NULL)
		{			
		//	ms_presmgr->Release(ms_pfontItems);
		//	ms_pfontItems = NULL;
		}

		if (ms_pfontHeader != NULL)
		{
			
		//	ms_presmgr->Release(ms_pfontHeader);
		//	ms_pfontHeader = NULL;
		}

		ms_sNumMenuItems = 0;

		// If there's a callback . . .
		if (ms_pmenu->menucallbacks.fnInit != NULL)
		{			
			// Let the callback know we're cleaning up.
			(*ms_pmenu->menucallbacks.fnInit)(ms_pmenu, FALSE);
		}

		// Clear current menu pointer.
		ms_pmenu = NULL;			
	}	

	return sRes;
}

//////////////////////////////////////////////////////////////////////////////
//
// Get the Menu's background image.
//
//////////////////////////////////////////////////////////////////////////////
extern RImage *GetCurrentMenuBackground(void) // Returns a pointer to the
											  // current background image
											  // or NULL, if none.
{
	//todo
	return nullptr;
	//return ms_pimBackground;
}

//////////////////////////////////////////////////////////////////////////////
//
// Get the Menu's GUI.
//
//////////////////////////////////////////////////////////////////////////////
extern RGuiItem *GetCurrentMenuBox(void) // Returns a pointer to the current
										 // menu GUI or NULL, if none.
{
	return (RGuiItem *)&ms_msgbox;
}

///////////////////////////////////////////////////////////////////////////////
// EOF
///////////////////////////////////////////////////////////////////////////////
