#include "common.h"
#ifdef DEBUGMENU
#include "RwHelper.h"
#include "Pad.h"
#include "ControllerConfig.h"
#include "Timer.h"
#include "rtcharse.h"
#include "inttypes.h"
#include "debugmenu.h"
#include <new>

#ifdef _WIN32
#define snprintf _snprintf

#define strdup _strdup
#endif


// Font stuff
struct Pt
{
	int x, y;
};

enum MenuFontStyle
{
	MENUFONT_NORMAL,
	MENUFONT_SEL_ACTIVE,
	MENUFONT_SEL_INACTIVE,
	MENUFONT_MOUSE
};

RtCharset *fontStyles[4];
RtCharsetDesc fontDesc;
int fontscale = 1;	// not supported right now

Pt
fontGetStringSize(const char *s)
{
	Pt sz = { 0, 0 };
	int x;
	char c;
	sz.y = fontDesc.height*fontscale;	// always assume one line;
	x = 0;
	while(c = *s++){
		if(c == '\n'){
			sz.y += fontDesc.height*fontscale;
			if(x > sz.x)
				sz.x = x;
			x = 0;
		}else
			x += fontDesc.width*fontscale;
	}
	if(x > sz.x)
		sz.x = x;
	return sz;
}

Pt
fontPrint(const char *s, float x, float y, int style)
{
	RtCharsetPrintBuffered(fontStyles[style], s, x, y, false);
	return fontGetStringSize(s);
}

int
fontGetLen(int len)
{
	return len*fontDesc.width*fontscale;
}


void
createMenuFont(void)
{
	OpenCharsetSafe();

	RwRGBA fg_normal = { 255, 255, 255, 255 };
	RwRGBA bg_normal = { 255, 255, 255, 0 };
	fontStyles[MENUFONT_NORMAL] = RtCharsetCreate(&fg_normal, &bg_normal);
	assert(fontStyles[MENUFONT_NORMAL]);

	RwRGBA fg_sel_active = { 200, 200, 200, 255 };
	RwRGBA bg_sel_active = { 132, 132, 132, 255 };
	fontStyles[MENUFONT_SEL_ACTIVE] = RtCharsetCreate(&fg_sel_active, &bg_sel_active);
	assert(fontStyles[MENUFONT_SEL_ACTIVE]);

	RwRGBA fg_sel_inactive = { 200, 200, 200, 255 };
	RwRGBA bg_sel_inactive = { 200, 200, 200, 0 };
	fontStyles[MENUFONT_SEL_INACTIVE] = RtCharsetCreate(&fg_sel_inactive, &bg_sel_inactive);
	assert(fontStyles[MENUFONT_SEL_INACTIVE]);

	RwRGBA fg_mouse = { 255, 255, 255, 255 };
	RwRGBA bg_mouse = { 132, 132, 132, 255 };
	fontStyles[MENUFONT_MOUSE] = RtCharsetCreate(&fg_mouse, &bg_mouse);
	assert(fontStyles[MENUFONT_MOUSE]);

	RtCharsetGetDesc(fontStyles[MENUFONT_NORMAL], &fontDesc);
}

void
destroyMenuFont(void)
{
	RtCharsetDestroy(fontStyles[MENUFONT_NORMAL]);
	fontStyles[MENUFONT_NORMAL] = nil;
	RtCharsetDestroy(fontStyles[MENUFONT_SEL_ACTIVE]);
	fontStyles[MENUFONT_SEL_ACTIVE] = nil;
	RtCharsetDestroy(fontStyles[MENUFONT_SEL_INACTIVE]);
	fontStyles[MENUFONT_SEL_INACTIVE] = nil;
	RtCharsetDestroy(fontStyles[MENUFONT_MOUSE]);
	fontStyles[MENUFONT_MOUSE] = nil;
}






enum EntryType
{
	MENUEMPTY = 0,
	MENUSUB,
	MENUVAR,

	MENUVAR_INT,
	MENUVAR_FLOAT,
	MENUVAR_CMD,

	MENUSCROLL	// dummy
};

struct Menu
{
	Menu *parent;
	RwRect r;
	MenuEntry *entries;
	int numEntries;
	int maxNameWidth, maxValWidth;

	MenuEntry *findEntry(const char *entryname);
	void insertEntrySorted(MenuEntry *entry);
	void appendEntry(MenuEntry *entry);

	bool isScrollingUp, isScrollingDown;
	int scrollStart;
	int numVisible;
	RwRect scrollUpR, scrollDownR;
	void scroll(int off);

	int selection;
	MenuEntry *selectedEntry;	// updated by update
	void changeSelection(int newsel);
	void changeSelection(MenuEntry *e);

	void update(void);
	void draw(void);
	Menu(void){ memset(this, 0, sizeof(Menu)); }
	~Menu(void);
};
extern Menu toplevel;

struct MenuEntry_Sub : MenuEntry
{
	Menu *submenu;

	MenuEntry_Sub(const char *name, Menu *menu);
	~MenuEntry_Sub(void) { delete submenu; }
};

struct MenuEntry_Var : MenuEntry
{
	int maxvallen;
	int vartype;
	bool wrapAround;

	virtual void processInput(bool mouseOver, bool selected) = 0;
	int getValWidth(void) { return maxvallen; }
	virtual void getValStr(char *str, int len) = 0;
	MenuEntry_Var(const char *name, int type);
};

struct MenuEntry_Int : MenuEntry_Var
{
	virtual void setStrings(const char **strings) = 0;
	virtual int findStringLen(void) = 0;
	MenuEntry_Int(const char *name);
};

#define INTTYPES \
	X(Int8, int8, 4, "%4" PRId8) \
	X(Int16, int16, 6, "%6" PRId16) \
	X(Int32, int32, 11, "%11" PRId32) \
	X(Int64, int64, 21, "%21" PRId64) \
	X(UInt8, uint8, 4, "%4" PRIu8) \
	X(UInt16, uint16, 6, "%6" PRIu16) \
	X(UInt32, uint32, 11, "%11" PRIu32) \
	X(UInt64, uint64, 21, "%21" PRIu64)
#define FLOATTYPES \
	X(Float32, float, 11, "%11.3f") \
	X(Float64, double, 11, "%11.3lf")

#define X(NAME, TYPE, MAXLEN, FMT) \
struct MenuEntry_##NAME : MenuEntry_Int														       \
{																		       \
	TYPE *variable;																       \
	TYPE lowerBound, upperBound;														       \
	TYPE step;																       \
	TriggerFunc triggerFunc;														       \
	const char *fmt;															       \
	const char **strings;															       \
																		       \
	void processInput(bool mouseOver, bool selected);														       \
	void getValStr(char *str, int len);													       \
																		       \
	void setStrings(const char **strings);													       \
	int findStringLen(void);														       \
	MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings);     \
};
INTTYPES
#undef X

#define X(NAME, TYPE, MAXLEN, FMT) \
struct MenuEntry_##NAME : MenuEntry_Var												 \
{																 \
	TYPE *variable;														 \
	TYPE lowerBound, upperBound;												 \
	TYPE step;														 \
	TriggerFunc triggerFunc;												 \
	const char *fmt;													 \
																 \
	void processInput(bool mouseOver, bool selected);												 \
	void getValStr(char *str, int len);											 \
																 \
	MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound);	 \
};
FLOATTYPES
#undef X

struct MenuEntry_Cmd : MenuEntry_Var
{
	TriggerFunc triggerFunc;

	void processInput(bool mouseOver, bool selected);
	void getValStr(char *str, int len);

	MenuEntry_Cmd(const char *name, TriggerFunc triggerFunc);
};


Menu *findMenu(const char *name);



#define MUHKEYS \
	X(leftjustdown, rsLEFT) \
	X(rightjustdown, rsRIGHT) \
	X(upjustdown, rsUP) \
	X(downjustdown, rsDOWN) \
	X(pgupjustdown, rsPGUP) \
	X(pgdnjustdown, rsPGDN)

#define MUHBUTTONS \
	X(button1justdown, 1) \
	X(button2justdown, 2) \
	X(button3justdown, 3)

#define REPEATDELAY 700
#define REPEATINTERVAL 50
#define X(var, keycode) static int var;
MUHKEYS
#undef X
static int downtime;
static int repeattime;
static int lastkeydown;
static int *keyptr;

static int buttondown[3];
static int lastbuttondown;
static int *buttonptr;
static int button1justdown, button2justdown, button3justdown;
static float mouseX, mouseY;

static int menuOn;
static int menuInitialized;
static int screenWidth, screenHeight;
static RwRaster *cursor, *arrow;

static int firstBorder = 10;
static int topBorder = 40;	//10;
static int leading = 4;
static int gap = 10;
static int minwidth = 100;

void drawMouse(void);
void drawArrow(RwRect r, int direction, int style);

Menu toplevel;
Menu *activeMenu = &toplevel;
Menu *deepestMenu = &toplevel;
Menu *mouseOverMenu;
MenuEntry *mouseOverEntry;
MenuEntry scrollUpEntry("SCROLLUP"), scrollDownEntry("SCROLLDOWN");	// dummies


#define KEYJUSTDOWN(k) ControlsManager.GetIsKeyboardKeyJustDown((RsKeyCodes)k)
#define KEYDOWN(k) ControlsManager.GetIsKeyboardKeyDown((RsKeyCodes)k)
#define CTRLJUSTDOWN(key) \
	       ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYJUSTDOWN((RsKeyCodes)key) || \
	        (KEYJUSTDOWN(rsLCTRL) || KEYJUSTDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key))
#define CTRLDOWN(key) ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key))


bool
isMouseInRect(RwRect r)
{
	return (mouseX >= r.x && mouseX < r.x+r.w &&
		mouseY >= r.y && mouseY < r.y+r.h);
}

/*
 * MenuEntry
 */

MenuEntry::MenuEntry(const char *name)
{
	this->type = MENUEMPTY;
	this->name = strdup(name);
	this->next = nil;
	this->menu = nil;
}

MenuEntry_Sub::MenuEntry_Sub(const char *name, Menu *menu)
: MenuEntry(name)
{
	this->type = MENUSUB;
	this->submenu = menu;
}

MenuEntry_Var::MenuEntry_Var(const char *name, int vartype)
: MenuEntry(name)
{
	this->type = MENUVAR;
	this->vartype = vartype;
	this->maxvallen = 0;
	this->wrapAround = false;
}

/*
 * *****************************
 * MenuEntry_Int
 * *****************************
 */

MenuEntry_Int::MenuEntry_Int(const char *name)
: MenuEntry_Var(name, MENUVAR_INT)
{
}

#define X(NAME, TYPE, MAXLEN, FMT) \
int								     \
MenuEntry_##NAME::findStringLen(void){				     \
	TYPE i;							     \
	int len, maxlen = 0;					     \
	for(i = this->lowerBound; i <= this->upperBound; i++){	     \
		len = strlen(this->strings[i-this->lowerBound]);     \
		if(len > maxlen)				     \
			maxlen = len;				     \
	}							     \
	return maxlen;						     \
}								     \
void								     \
MenuEntry_##NAME::processInput(bool mouseOver, bool selected)				     \
{								     \
	TYPE v, oldv;						     \
	int overflow = 0;					     \
	int underflow = 0;					     \
								     \
	v = *this->variable;					     \
	oldv = v;						     \
								     \
	if((selected && leftjustdown) || (mouseOver && button3justdown)){					     \
		v -= this->step;				     \
		if(v > oldv)					     \
			underflow = 1;				     \
	}							     \
	if((selected && rightjustdown) || (mouseOver && button1justdown)){					     \
		v += this->step;				     \
		if(v < oldv)					     \
			overflow = 1;				     \
	}							     \
	if(this->wrapAround){					     \
		if(v > this->upperBound || overflow) v = this->lowerBound;	     \
		if(v < this->lowerBound || underflow) v = this->upperBound;	     \
	}else{									     \
		if(v > this->upperBound || overflow) v = this->upperBound;	     \
		if(v < this->lowerBound || underflow) v = this->lowerBound;	     \
	}							     \
								     \
	*this->variable = v;					     \
	if(oldv != v && this->triggerFunc)			     \
		this->triggerFunc();				     \
}								     \
void											       \
MenuEntry_##NAME::getValStr(char *str, int len)						       \
{											       \
	static char tmp[20];								       \
	if(this->strings){								       \
		snprintf(tmp, 20, "%%%ds", this->maxvallen);				       \
		if(*this->variable < this->lowerBound || *this->variable > this->upperBound){  \
			snprintf(str, len, "ERROR");					       \
			return;								       \
		}									       \
		snprintf(str, len, tmp, this->strings[*this->variable-this->lowerBound]);      \
	}else										       \
		snprintf(str, len, this->fmt, *this->variable);				       \
}											       \
void																			   \
MenuEntry_##NAME::setStrings(const char **strings)													   \
{																			   \
	this->strings = strings;															   \
	if(this->strings)																   \
		this->maxvallen = findStringLen();													   \
}																			   \
MenuEntry_##NAME::MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings)   \
: MenuEntry_Int(name)																	   \
{																			   \
	this->variable = ptr;																   \
	this->step = step;																   \
	this->lowerBound = lowerBound;															   \
	this->upperBound = upperBound;															   \
	this->triggerFunc = triggerFunc;														   \
	this->maxvallen = MAXLEN;															   \
	this->fmt = FMT;																   \
	this->setStrings(strings);																   \
}
INTTYPES
#undef X

/*
 * *****************************
 * MenuEntry_Float
 * *****************************
 */

#define X(NAME, TYPE, MAXLEN, FMT) \
MenuEntry_##NAME::MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound)	     \
: MenuEntry_Var(name, MENUVAR_FLOAT)															     \
{																	     \
	this->variable = ptr;														     \
	this->step = step;														     \
	this->lowerBound = lowerBound;													     \
	this->upperBound = upperBound;													     \
	this->triggerFunc = triggerFunc;												     \
	this->maxvallen = MAXLEN;													     \
	this->fmt = FMT;														     \
}																	     \
void																	     \
MenuEntry_##NAME::getValStr(char *str, int len)												     \
{																	     \
	snprintf(str, len, this->fmt, *this->variable);											     \
}																	     \
void																	     \
MenuEntry_##NAME::processInput(bool mouseOver, bool selected)													     \
{																	     \
	float v, oldv;															     \
	int overflow = 0;					     \
	int underflow = 0;					     \
																	     \
	v = *this->variable;														     \
	oldv = v;															     \
																	     \
	if((selected && leftjustdown) || (mouseOver && button3justdown)){					     \
		v -= this->step;				     \
		if(v > oldv)					     \
			underflow = 1;				     \
	}							     \
	if((selected && rightjustdown) || (mouseOver && button1justdown)){					     \
		v += this->step;				     \
		if(v < oldv)					     \
			overflow = 1;				     \
	}							     \
	if(this->wrapAround){					     \
		if(v > this->upperBound || overflow) v = this->lowerBound;	     \
		if(v < this->lowerBound || underflow) v = this->upperBound;	     \
	}else{									     \
		if(v > this->upperBound || overflow) v = this->upperBound;	     \
		if(v < this->lowerBound || underflow) v = this->lowerBound;	     \
	}							     \
																	     \
	*this->variable = v;														     \
	if(oldv != v && this->triggerFunc)												     \
		this->triggerFunc();													     \
}

FLOATTYPES
#undef X

/*
 * *****************************
 * MenuEntry_Cmd
 * *****************************
 */

void
MenuEntry_Cmd::processInput(bool mouseOver, bool selected)
{
	// Don't execute on button3
	if(this->triggerFunc && (selected && (leftjustdown || rightjustdown) || (mouseOver && button1justdown)))
		this->triggerFunc();
}

void
MenuEntry_Cmd::getValStr(char *str, int len)
{
	strncpy(str, "<", len);
}

MenuEntry_Cmd::MenuEntry_Cmd(const char *name, TriggerFunc triggerFunc)
: MenuEntry_Var(name, MENUVAR_CMD)
{
	this->maxvallen = 1;
	this->triggerFunc = triggerFunc;
}


/*
 * *****************************
 * Menu
 * *****************************
 */

void
Menu::scroll(int off) {
	if(isScrollingUp && off < 0)
		scrollStart += off;
	if(isScrollingDown && off > 0)
		scrollStart += off;
	if(scrollStart < 0) scrollStart = 0;
	if(scrollStart > numEntries-numVisible) scrollStart = numEntries-numVisible;
}

void
Menu::changeSelection(int newsel){
	selection = newsel;
	if(selection < 0) selection = 0;
	if(selection >= numEntries) selection = numEntries-1;
	if(selection < scrollStart) scrollStart = selection;
	if(selection >= scrollStart+numVisible) scrollStart = selection-numVisible+1;
}

void
Menu::changeSelection(MenuEntry *sel)
{
	MenuEntry *e;
	int i = 0;
	for(e = this->entries; e; e = e->next){
		if(e == sel){
			this->selection = i;
			this->selectedEntry = sel;
			break;
		}
		i++;
	}
}



MenuEntry*
Menu::findEntry(const char *entryname)
{
	MenuEntry *m;
	for(m = this->entries; m; m = m->next)
		if(strcmp(entryname, m->name) == 0)
			return m;
	return nil;
}

void
Menu::insertEntrySorted(MenuEntry *entry)
{
	MenuEntry **mp;
	int cmp;
	for(mp = &this->entries; *mp; mp = &(*mp)->next){
		cmp = strcmp(entry->name, (*mp)->name);
		if(cmp == 0)
			return;
		if(cmp < 0)
			break;
	}
	entry->next = *mp;
	*mp = entry;
	entry->menu = this;
	this->numEntries++;
}

void
Menu::appendEntry(MenuEntry *entry)
{
	MenuEntry **mp;
	for(mp = &this->entries; *mp; mp = &(*mp)->next);
	entry->next = *mp;
	*mp = entry;
	entry->menu = this;
	this->numEntries++;
}

void
Menu::update(void)
{
	int i;
	int x, y;
	Pt sz;
	MenuEntry *e;
	int onscreen;
	x = this->r.x;
	y = this->r.y + 18;
	int end = this->r.y+this->r.h - 18;
	this->numVisible = 0;

	deepestMenu = this;

	int bottomy = end;
	onscreen = 1;
	i = 0;
	this->maxNameWidth = 0;
	this->maxValWidth = 0;
	this->isScrollingUp = this->scrollStart > 0;
	this->isScrollingDown = false;
	this->selectedEntry = nil;
	for(e = this->entries; e; e = e->next){
		sz = fontGetStringSize(e->name);
		e->r.x = x;
		e->r.y = y;
		e->r.w = sz.x;
		e->r.h = sz.y;

		if(i == this->selection)
			this->selectedEntry = e;

		if(i >= this->scrollStart)
			y += sz.y + leading*fontscale;
		if(y >= end){
			this->isScrollingDown = true;
			onscreen = 0;
		}else
			bottomy = y;
		if(i >= this->scrollStart && onscreen)
			this->numVisible++;

		if(e->type == MENUVAR){
			int valwidth = fontGetLen(((MenuEntry_Var*)e)->getValWidth());
			if(valwidth > maxValWidth)
				maxValWidth = valwidth;
		}
		if(e->r.w > maxNameWidth)
			maxNameWidth = e->r.w;
		i++;
	}
	if(this->r.w < maxNameWidth + maxValWidth + gap*fontscale)
		this->r.w = maxNameWidth + maxValWidth + gap*fontscale;

	this->scrollUpR = this->r;
	this->scrollUpR.h = 16;
	this->scrollDownR = this->scrollUpR;
	this->scrollDownR.y = bottomy;

	// Update active submenu
	if(this->selectedEntry && this->selectedEntry->type == MENUSUB){
		Menu *submenu = ((MenuEntry_Sub*)this->selectedEntry)->submenu;
		submenu->r.x = this->r.x+this->r.w + 10;
		submenu->r.y = this->r.y;
		submenu->r.w = minwidth;	// update menu will expand
		submenu->r.h = this->r.h;
		submenu->update();
	}
}

void
Menu::draw(void)
{
	static char val[100];
	int i;
	MenuEntry *e;
	i = 0;
	for(e = this->entries; e; e = e->next){
		if(i >= this->scrollStart+this->numVisible)
			break;
		if(i >= this->scrollStart){
			int style = MENUFONT_NORMAL;
			if(i == this->selection)
				style = this == activeMenu ? MENUFONT_SEL_ACTIVE : MENUFONT_SEL_INACTIVE;
			if(style != MENUFONT_SEL_ACTIVE && e == mouseOverEntry)
				style = MENUFONT_MOUSE;
			fontPrint(e->name, e->r.x, e->r.y, style);
			if(e->type == MENUVAR){
				int valw = fontGetLen(((MenuEntry_Var*)e)->getValWidth());
				((MenuEntry_Var*)e)->getValStr(val, 100);
				fontPrint(val, e->r.x+this->r.w-valw, e->r.y, style);
			}
		}
		i++;
	}

	if(this->isScrollingUp)
		drawArrow(this->scrollUpR, -1, isMouseInRect(this->scrollUpR));
	if(this->isScrollingDown)
		drawArrow(this->scrollDownR, 1, isMouseInRect(this->scrollDownR));

	if(this->selectedEntry && this->selectedEntry->type == MENUSUB)
		((MenuEntry_Sub*)this->selectedEntry)->submenu->draw();
}

Menu::~Menu(void)
{
	MenuEntry *e, *next;
	for(e = entries; e; e = next){
		next = e->next;
		delete e;
	}
	memset(this, 0, sizeof(Menu));
}

Menu*
findMenu(const char *name)
{
	Menu *m;
	MenuEntry *e;
	char *tmppath = strdup(name);
	char *next, *curname;

	curname = tmppath;
	next = curname;

	m = &toplevel;
	while(*next){
		curname = next;
		while(*next){
			if(*next == '|'){
				*next++ = '\0';
				break;
			}
			next++;
		}
		e = m->findEntry(curname);
		if(e){
			// return an error if the entry exists but isn't a menu
			if(e->type != MENUSUB){
				free(tmppath);
				return nil;
			}
			m = ((MenuEntry_Sub*)e)->submenu;
		}else{
			// Create submenus that don't exist yet
			Menu *submenu = new Menu();
			submenu->parent = m;
			MenuEntry *me = new MenuEntry_Sub(curname, submenu);
			// Don't sort submenus outside the toplevel menu
			if(m == &toplevel)
				m->insertEntrySorted(me);
			else
				m->appendEntry(me);
			m = submenu;
		}
	}

	free(tmppath);
	return m;
}

/*
 * ****************
 * debug menu
 * ****************
 */

static uint8 cursorPx[] = {
#include "cursor.inc"
};

static uint8 arrowPx[] = {
#include "arrow.inc"
};

void
DebugMenuInit(void)
{
	createMenuFont();

	RwInt32 w, h, d, flags;
	RwImage *img = RwImageCreate(16, 16, 32);
	assert(img);
	RwImageSetPixels(img, cursorPx);
	RwImageSetStride(img, RwImageGetWidth(img)*4);
	RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE, &w, &h, &d, &flags);
	cursor = RwRasterCreate(w, h, d, flags);
	cursor = RwRasterSetFromImage(cursor, img);
	assert(cursor);
	RwImageDestroy(img);

	img = RwImageCreate(32, 16, 32);
	assert(img);
	RwImageSetPixels(img, arrowPx);
	RwImageSetStride(img, RwImageGetWidth(img)*4);
	RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE, &w, &h, &d, &flags);
	arrow = RwRasterCreate(w, h, d, flags);
	arrow = RwRasterSetFromImage(arrow, img);
	assert(arrow);
	RwImageDestroy(img);


	menuInitialized = true;
}

void
DebugMenuShutdown(void)
{
	if(menuInitialized){
		destroyMenuFont();
		RwRasterDestroy(cursor);
		cursor = nil;
		RwRasterDestroy(arrow);
		arrow = nil;

		toplevel.~Menu();
		new (&toplevel) Menu();

		activeMenu = &toplevel;
		deepestMenu = &toplevel;
		mouseOverMenu = nil;
		mouseOverEntry = nil;
	}
	menuInitialized = false;
}

void
processInput(void)
{
	int shift = KEYDOWN(rsRSHIFT) || KEYDOWN(rsLSHIFT);
#define X(var, keycode) var = KEYJUSTDOWN(keycode);
	MUHKEYS
#undef X

	// Implement auto-repeat
#define X(var, keycode) \
	if(var){ \
		repeattime = downtime = CTimer::GetTimeInMilliseconds(); \
		lastkeydown = keycode; \
		keyptr = &var; \
	}
	MUHKEYS
#undef X
	if(lastkeydown){
		if(KEYDOWN(lastkeydown)){
			int curtime = CTimer::GetTimeInMilliseconds();
			if(curtime - downtime > REPEATDELAY){
				if(curtime - repeattime > REPEATINTERVAL){
					repeattime = curtime;
					*keyptr = 1;
				}
			}
		}else{
			lastkeydown = 0;
		}
	}

	// Also for mouse buttons
#define X(var, num)							  \
	if(var){							  \
		repeattime = downtime = CTimer::GetTimeInMilliseconds();	  \
		lastbuttondown = num;					  \
		buttonptr = &var;					  \
	}
	MUHBUTTONS
#undef X
	if(lastbuttondown){
		if(buttondown[lastbuttondown-1]){
			int curtime = CTimer::GetTimeInMilliseconds();
			if(curtime - downtime > REPEATDELAY){
				if(curtime - repeattime > REPEATINTERVAL){
					repeattime = curtime;
					*buttonptr = 1;
				}
			}
		}else{
			lastbuttondown = 0;
		}
	}

	// Walk through all visible menus and figure out which one the mouse is over
	mouseOverMenu = nil;
	mouseOverEntry = nil;
	Menu *menu;
	for(menu = deepestMenu; menu; menu = menu->parent)
		if(isMouseInRect(menu->r)){
			mouseOverMenu = menu;
			break;
		}
	if(mouseOverMenu){
		// Walk all visibile entries and figure out which one the mouse is over
		MenuEntry *e;
		int i = 0;
		for(e = mouseOverMenu->entries; e; e = e->next){
			if(i >= mouseOverMenu->scrollStart+mouseOverMenu->numVisible)
				break;
			if(i >= mouseOverMenu->scrollStart){
				RwRect r = e->r;
				r.w = mouseOverMenu->r.w;	// span the whole menu
				if(isMouseInRect(r)){
					mouseOverEntry = e;
					break;
				}
			}
			i++;
		}
		if(mouseOverMenu->isScrollingUp && isMouseInRect(mouseOverMenu->scrollUpR)){
			mouseOverEntry = &scrollUpEntry;
			mouseOverEntry->r = mouseOverMenu->scrollUpR;
			mouseOverEntry->menu = mouseOverMenu;
			mouseOverEntry->type = MENUSCROLL;
		}
		if(mouseOverMenu->isScrollingDown && isMouseInRect(mouseOverMenu->scrollDownR)){
			mouseOverEntry = &scrollDownEntry;
			mouseOverEntry->r = mouseOverMenu->scrollDownR;
			mouseOverEntry->menu = mouseOverMenu;
			mouseOverEntry->type = MENUSCROLL;
		}
	}

	if(pgupjustdown)
		activeMenu->scroll(shift ? -5 : -1);
	if(pgdnjustdown)
		activeMenu->scroll(shift ? 5 : 1);
	if(downjustdown)
		activeMenu->changeSelection(activeMenu->selection + (shift ? 5 : 1));
	if(upjustdown)
		activeMenu->changeSelection(activeMenu->selection - (shift ? 5 : 1));

	if(CPad::NewMouseControllerState.WHEELUP){
		if(mouseOverMenu)
			activeMenu = mouseOverMenu;
		activeMenu->scroll(shift ? -5 : -1);
	}
	if(CPad::NewMouseControllerState.WHEELDN){
		if(mouseOverMenu)
			activeMenu = mouseOverMenu;
		activeMenu->scroll(shift ? 5 : 1);
	}

	if(mouseOverEntry == &scrollUpEntry){
		if(button1justdown){
			activeMenu = mouseOverEntry->menu;
			activeMenu->scroll(shift ? -5 : -1);
		}
	}
	if(mouseOverEntry == &scrollDownEntry){
		if(button1justdown){
			activeMenu = mouseOverEntry->menu;
			activeMenu->scroll(shift ? 5 : 1);
		}
	}

	// Have to call this before processInput below because menu entry can change
	if((button1justdown || button3justdown) && mouseOverEntry){
		activeMenu = mouseOverEntry->menu;
		activeMenu->changeSelection(mouseOverEntry);
	}
	if(KEYJUSTDOWN(rsENTER)){
		if(activeMenu->selectedEntry && activeMenu->selectedEntry->type == MENUSUB)
			activeMenu = ((MenuEntry_Sub*)activeMenu->selectedEntry)->submenu;
	}else if(KEYJUSTDOWN(rsBACKSP)){
		if(activeMenu->parent)
			activeMenu = activeMenu->parent;
	}else{
		if(mouseOverEntry && mouseOverEntry->type == MENUVAR)
			((MenuEntry_Var*)mouseOverEntry)->processInput(true, mouseOverEntry == activeMenu->selectedEntry);
		if(activeMenu->selectedEntry && activeMenu->selectedEntry->type == MENUVAR &&
		   mouseOverEntry != activeMenu->selectedEntry)
			((MenuEntry_Var*)activeMenu->selectedEntry)->processInput(false, true);
	}
}

void
updateMouse(void)
{
	CPad *pad = CPad::GetPad(0);
	int dirX = 1;
	int dirY = 1;

	if(MousePointerStateHelper.bInvertHorizontally) dirX = -1;
	if(MousePointerStateHelper.bInvertVertically) dirY = -1;

	mouseX += pad->NewMouseControllerState.x*dirX;
	mouseY += pad->NewMouseControllerState.y*dirY;

	if(mouseX < 0.0f) mouseX = 0.0f;
	if(mouseY < 0.0f) mouseY = 0.0f;
	if(mouseX >= screenWidth) mouseX = screenWidth;
	if(mouseY >= screenHeight) mouseY = screenHeight;

	button1justdown = pad->NewMouseControllerState.LMB && !pad->OldMouseControllerState.LMB;
	button2justdown = pad->NewMouseControllerState.MMB && !pad->OldMouseControllerState.MMB;
	button3justdown = pad->NewMouseControllerState.RMB && !pad->OldMouseControllerState.RMB;
	buttondown[0] = pad->NewMouseControllerState.LMB;
	buttondown[1] = pad->NewMouseControllerState.MMB;
	buttondown[2] = pad->NewMouseControllerState.RMB;

	// Zero the mouse position so the camera won't move
	pad->NewMouseControllerState.x = 0.0f;
	pad->NewMouseControllerState.y = 0.0f;
}

void
DebugMenuProcess(void)
{
	// We only process some input here

	CPad *pad = CPad::GetPad(0);
	if(CTRLJUSTDOWN('M'))
		menuOn = !menuOn;
	if(!menuOn)
		return;

	pad->DisablePlayerControls = 1;
	// TODO: this could happen earlier
	if(!menuInitialized)
		DebugMenuInit();
	updateMouse();

}

void
DebugMenuRender(void)
{
	if(!menuOn)
		return;

	RwRenderStateSet(rwRENDERSTATEZTESTENABLE, 0);
	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, 0);
	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
	RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
	RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
	RwRenderStateSet(rwRENDERSTATEFOGENABLE, 0);
	RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE);

	RwCamera *cam = RwCameraGetCurrentCamera();
	screenWidth = RwRasterGetWidth(RwCameraGetRaster(cam));
	screenHeight = RwRasterGetHeight(RwCameraGetRaster(cam));

//	if(screenHeight > 1080)
//		fontscale = 2;
//	else
		fontscale = 1;

	Pt sz;
	sz = fontPrint("Debug Menu", firstBorder*fontscale+30, topBorder, 0);

	toplevel.r.x = firstBorder*fontscale;
	toplevel.r.y = topBorder + sz.y + 10;
	toplevel.r.w = minwidth;	// update menu will expand
	toplevel.r.h = screenHeight - 10 - toplevel.r.y;
	toplevel.update();
	toplevel.draw();
	processInput();
	RtCharsetBufferFlush();

	drawMouse();
}



void
drawArrow(RwRect r, int direction, int style)
{
	static RwImVertexIndex indices[] = { 0, 1, 2, 2, 1, 3 };
	static RwIm2DVertex arrowVerts[4];

	RwCamera *cam = RwCameraGetCurrentCamera();
	float recipz = 1.0f/RwCameraGetNearClipPlane(cam);

	int width = RwRasterGetWidth(arrow);
	int height = RwRasterGetHeight(arrow);

	int left = r.x + (r.w - width)/2;
	int right = left + width;
	int top = r.y;
	int bottom = r.y+r.h;

	float umin = HALFPX / width;
	float vmin = HALFPX / height;
	float umax = (width + HALFPX) / width;
	float vmax = (height + HALFPX) / height;
	if(direction < 0){
		vmin = (height - HALFPX) / height;
		vmax = -HALFPX / height;
	}

	if(style){
		RwIm2DVertexSetScreenX(&arrowVerts[0], r.x);
		RwIm2DVertexSetScreenY(&arrowVerts[0], r.y-1);
		RwIm2DVertexSetScreenZ(&arrowVerts[0], RwIm2DGetNearScreenZ());
		RwIm2DVertexSetCameraZ(&arrowVerts[0], RwCameraGetNearClipPlane(cam));
		RwIm2DVertexSetRecipCameraZ(&arrowVerts[0], recipz);
		RwIm2DVertexSetIntRGBA(&arrowVerts[0], 132, 132, 132, 255);

		RwIm2DVertexSetScreenX(&arrowVerts[1], r.x+r.w);
		RwIm2DVertexSetScreenY(&arrowVerts[1], r.y-1);
		RwIm2DVertexSetScreenZ(&arrowVerts[1], RwIm2DGetNearScreenZ());
		RwIm2DVertexSetCameraZ(&arrowVerts[1], RwCameraGetNearClipPlane(cam));
		RwIm2DVertexSetRecipCameraZ(&arrowVerts[1], recipz);
		RwIm2DVertexSetIntRGBA(&arrowVerts[1], 132, 132, 132, 255);

		RwIm2DVertexSetScreenX(&arrowVerts[2], r.x);
		RwIm2DVertexSetScreenY(&arrowVerts[2], r.y+r.h+1);
		RwIm2DVertexSetScreenZ(&arrowVerts[2], RwIm2DGetNearScreenZ());
		RwIm2DVertexSetCameraZ(&arrowVerts[2], RwCameraGetNearClipPlane(cam));
		RwIm2DVertexSetRecipCameraZ(&arrowVerts[2], recipz);
		RwIm2DVertexSetIntRGBA(&arrowVerts[2], 132, 132, 132, 255);

		RwIm2DVertexSetScreenX(&arrowVerts[3], r.x+r.w);
		RwIm2DVertexSetScreenY(&arrowVerts[3], r.y+r.h+1);
		RwIm2DVertexSetScreenZ(&arrowVerts[3], RwIm2DGetNearScreenZ());
		RwIm2DVertexSetCameraZ(&arrowVerts[3], RwCameraGetNearClipPlane(cam));
		RwIm2DVertexSetRecipCameraZ(&arrowVerts[3], recipz);
		RwIm2DVertexSetIntRGBA(&arrowVerts[3], 132, 132, 132, 255);

		RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil);
		RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR);
		RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, arrowVerts, 4, indices, 6);
	}


	RwIm2DVertexSetScreenX(&arrowVerts[0], left);
	RwIm2DVertexSetScreenY(&arrowVerts[0], top);
	RwIm2DVertexSetScreenZ(&arrowVerts[0], RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(&arrowVerts[0], RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(&arrowVerts[0], recipz);
	RwIm2DVertexSetIntRGBA(&arrowVerts[0], 255, 255, 255, 255);
	RwIm2DVertexSetU(&arrowVerts[0], umin, recipz);
	RwIm2DVertexSetV(&arrowVerts[0], vmin, recipz);

	RwIm2DVertexSetScreenX(&arrowVerts[1], right);
	RwIm2DVertexSetScreenY(&arrowVerts[1], top);
	RwIm2DVertexSetScreenZ(&arrowVerts[1], RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(&arrowVerts[1], RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(&arrowVerts[1], recipz);
	RwIm2DVertexSetIntRGBA(&arrowVerts[1], 255, 255, 255, 255);
	RwIm2DVertexSetU(&arrowVerts[1], umax, recipz);
	RwIm2DVertexSetV(&arrowVerts[1], vmin, recipz);

	RwIm2DVertexSetScreenX(&arrowVerts[2], left);
	RwIm2DVertexSetScreenY(&arrowVerts[2], bottom);
	RwIm2DVertexSetScreenZ(&arrowVerts[2], RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(&arrowVerts[2], RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(&arrowVerts[2], recipz);
	RwIm2DVertexSetIntRGBA(&arrowVerts[2], 255, 255, 255, 255);
	RwIm2DVertexSetU(&arrowVerts[2], umin, recipz);
	RwIm2DVertexSetV(&arrowVerts[2], vmax, recipz);

	RwIm2DVertexSetScreenX(&arrowVerts[3], right);
	RwIm2DVertexSetScreenY(&arrowVerts[3], bottom);
	RwIm2DVertexSetScreenZ(&arrowVerts[3], RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(&arrowVerts[3], RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(&arrowVerts[3], recipz);
	RwIm2DVertexSetIntRGBA(&arrowVerts[3], 255, 255, 255, 255);
	RwIm2DVertexSetU(&arrowVerts[3], umax, recipz);
	RwIm2DVertexSetV(&arrowVerts[3], vmax, recipz);

	RwRenderStateSet(rwRENDERSTATETEXTURERASTER, arrow);
	RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR);
	RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, arrowVerts, 4, indices, 6);
}

void
drawMouse(void)
{
	static RwImVertexIndex indices[] = { 0, 1, 2, 2, 1, 3 };
	static RwIm2DVertex vertices[4];
	RwIm2DVertex *vert;
	RwCamera *cam;
	cam = RwCameraGetCurrentCamera();
	float x = mouseX;
	float y = mouseY;
	float w = RwRasterGetWidth(cursor);
	float h = RwRasterGetHeight(cursor);
	float recipz = 1.0f/RwCameraGetNearClipPlane(cam);

	float umin = HALFPX / w;
	float vmin = HALFPX / h;
	float umax = (w + HALFPX) / w;
	float vmax = (h + HALFPX) / h;

	vert = vertices;
	RwIm2DVertexSetScreenX(vert, x);
	RwIm2DVertexSetScreenY(vert, y);
	RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(vert, recipz);
	RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255);
	RwIm2DVertexSetU(vert, umin, recipz);
	RwIm2DVertexSetV(vert, vmin, recipz);
	vert++;

	RwIm2DVertexSetScreenX(vert, x+w);
	RwIm2DVertexSetScreenY(vert, y);
	RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(vert, recipz);
	RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255);
	RwIm2DVertexSetU(vert, umax, recipz);
	RwIm2DVertexSetV(vert, vmin, recipz);
	vert++;

	RwIm2DVertexSetScreenX(vert, x);
	RwIm2DVertexSetScreenY(vert, y+h);
	RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(vert, recipz);
	RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255);
	RwIm2DVertexSetU(vert, umin, recipz);
	RwIm2DVertexSetV(vert, vmax, recipz);
	vert++;

	RwIm2DVertexSetScreenX(vert, x+w);
	RwIm2DVertexSetScreenY(vert, y+h);
	RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(vert, recipz);
	RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255);
	RwIm2DVertexSetU(vert, umax, recipz);
	RwIm2DVertexSetV(vert, vmax, recipz);
	vert++;

	RwRenderStateSet(rwRENDERSTATETEXTURERASTER, cursor);
	RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR);
	RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, vertices, 4, indices, 6);
}




/*
 * Generate interfaces
 */


#define X(NAME, TYPE, unused1, unused2) \
MenuEntry*																			\
DebugMenuAdd##NAME(const char *path, const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings)	\
{																				\
	Menu *m = findMenu(path);																\
	if(m == nil)																		\
		return nil;																		\
	MenuEntry *e = new MenuEntry_##NAME(name, ptr, triggerFunc, step, lowerBound, upperBound, strings);							\
	m->appendEntry(e);							\
	return e;																		\
}
INTTYPES
#undef X

#define X(NAME, TYPE, unused1, unused2) \
MenuEntry*																			\
DebugMenuAdd##NAME(const char *path, const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound)	\
{																				\
	Menu *m = findMenu(path);																\
	if(m == nil)																		\
		return nil;																		\
	MenuEntry *e = new MenuEntry_##NAME(name, ptr, triggerFunc, step, lowerBound, upperBound);								\
	m->appendEntry(e);							\
	return e;																		\
}
FLOATTYPES
#undef X

MenuEntry*																			\
DebugMenuAddCmd(const char *path, const char *name, TriggerFunc triggerFunc)
{
	Menu *m = findMenu(path);
	if(m == nil)
		return nil;
	MenuEntry *e = new MenuEntry_Cmd(name, triggerFunc);
	m->appendEntry(e);
	return e;
}

void
DebugMenuEntrySetWrap(MenuEntry *e, bool wrap)
{
	if(e && e->type == MENUVAR)
		((MenuEntry_Var*)e)->wrapAround = wrap;
}

void
DebugMenuEntrySetStrings(MenuEntry *e, const char **strings)
{
	if(e && e->type == MENUVAR_INT)
		((MenuEntry_Int*)e)->setStrings(strings);
}

void
DebugMenuEntrySetAddress(MenuEntry *e, void *addr)
{
	if(e && e->type == MENUVAR){
		MenuEntry_Var *ev = (MenuEntry_Var*)e;
		// HACK - we know the variable field is at the same address
		// for all int/float classes. let's hope it stays that way.
		if(ev->vartype = MENUVAR_INT)
			((MenuEntry_Int32*)e)->variable = (int32*)addr;
		else if(ev->vartype = MENUVAR_FLOAT)
			((MenuEntry_Float32*)e)->variable = (float*)addr;
	}
}
#endif
src/extras/debugmenu.cpp