/** fMSX: portable MSX emulator ******************************/
/**                                                         **/
/**                         MenuPsp.c                       **/
/**                                                         **/
/** This file contains routines used for menu navigation    **/
/** for the PSP version of fMSX                             **/
/**                                                         **/
/** Copyright (C) Akop Karapetyan 2007                      **/
/**     You are not allowed to distribute this software     **/
/**     commercially. Please, notify me, if you make any    **/
/**     changes to this file.                               **/
/*************************************************************/
#include <string.h>
#include <malloc.h>

#include <time.h>
#include <psptypes.h>
#include <psprtc.h>
#include <pspkernel.h>

#include "MSX.h"
#include "INIFile.h"

#include "psp.h"
#include "ui.h"
#include "fileio.h"
#include "video.h"
#include "ctrl.h"
#include "image.h"

#include "unzip.h"

#include "LibPsp.h"
#include "MenuPsp.h"

#define TAB_QUICKLOAD 0
#define TAB_STATE     1
#define TAB_CONTROL   2
#define TAB_OPTION    3
#define TAB_SYSTEM    4
#define TAB_ABOUT     5
#define TAB_MAX       TAB_SYSTEM

#define SYSTEM_CART_A      2
#define SYSTEM_CART_B      3
#define SYSTEM_DRIVE_A     4
#define SYSTEM_DRIVE_B     5
#define SYSTEM_RETURN      6
#define SYSTEM_SCRNSHOT    7
#define SYSTEM_RESET       8
#define SYSTEM_QUIT        9
#define SYSTEM_CART_A_TYPE 10
#define SYSTEM_CART_B_TYPE 11

#define OPTION_DISPLAY_MODE 1
#define OPTION_SYNC_FREQ    2
#define OPTION_FRAMESKIP    3
#define OPTION_VSYNC        4
#define OPTION_CLOCK_FREQ   5
#define OPTION_SHOW_FPS     6

PspImage *Screen;
int DisplayMode;
int UpdateFreq;
int VSync;
int ClockFreq;
int Frameskip;
int ShowFps;

static PspImage *Background;
static PspImage *NoSaveIcon;

char *ROM[MAXCARTS];
char *Drive[MAXDRIVES];
static char *Quickload;
static int  TabIndex;

static PspMenu *SystemMenu;
static PspMenu *ControlMenu;
static PspMenu *OptionMenu;
static PspMenu *StateMenu;

static PspMenuItem *SelectedSystemItem;
static PspMenuItem *SelectedControlItem;
static PspMenuItem *SelectedOptionItem;
static PspMenuItem *SelectedStateItem;

static const char *EmptySlot = "[EMPTY]";
static const char *CartFilter[] = { "ROM", "ROM.GZ", "ZIP", '\0' };
static const char *DiskFilter[] = { "DSK", "DSK.GZ", "ZIP", '\0' };
static const char *QuickloadFilter[] = { "ROM", "DSK", "ROM.GZ", "DSK.GZ", "ZIP", '\0' };

static const char *ConfigDir = "conf";
static const char *ScreenshotDir = "screens";
static const char *SaveStateDir = "states";
static const char *DefConfigFile = "default";
static const char *BasicConfigFile = "basic";
static const char *OptionsFile = "fmsx.ini";

static char *ScreenshotPath;

static unsigned int DisplayQuickloadTab();
static unsigned int DisplayControlTab();
static unsigned int DisplayStateTab();
static unsigned int DisplaySystemTab();
static unsigned int DisplayOptionTab();
static unsigned int DisplayAboutTab();

static char* GetSystemMenuOptions(PspMenuItem *item, unsigned int ok_btn_mask, unsigned int cancel_btn_mask);
static char* GetControlMenuOptions(PspMenuItem *item, unsigned int ok_btn_mask, unsigned int cancel_btn_mask);
static char* GetSaveStateMenuOptions(PspMenuItem *item, unsigned int ok_btn_mask, unsigned int cancel_btn_mask);
static char* GetOptionsMenuCaption(PspMenuItem *item, unsigned int ok_btn_mask, unsigned int cancel_btn_mask);

static int OnSystemMenuItemChange(const PspMenuItem *item, const PspMenuOption *option);

static void InitGameConfig(struct GameConfig *config);
static int  SaveGameConfig(const char *filename, const struct GameConfig *config);
static int  LoadGameConfig(const char *filename, struct GameConfig *config);

static int LoadFile(char *filename, int slot);
static const char* GetConfigName();
static void LoadOptions();
static void InitOptionDefaults();
static int  SaveOptions();

PspImage* LoadStateIcon(const char *path);
int       LoadState(const char *path);
PspImage* SaveState(const char *path, PspImage *icon);

// TODO: 
#define WIDTH  272
#define HEIGHT 228

#define CART_TYPE_AUTODETECT 8

/* Tab labels */
static const char *TabLabel[] = 
{
  "GAME",
  "STATE", 
  "CONTROLS",
  "OPTIONS",
  "SYSTEM",
  "ABOUT"
};

/* Button labels */
static const char *ButtonLabel[] = 
{
  /* Analog stick: up, down, left, right */
  "\251", "\252", "\253", "\254",
  /* D-pad: up, down, left, right */
  "\245", "\246", "\247", "\250",
  /* Square, Cross,Circle, Triangle */
  "\244", "\241", "\242", "\243",
  /* LTrigger, RTrigger */
  "\255", "\256",
  /* Select, Start */
  "\257\260", "\261\262",
  /* L+R Trigger, Start+Select */
  "\255+\256", "\257\260+\261\262",
  /* Select+LTrigger, Select+RTrigger */
  "\257\260+\255", "\257\260+\256",
  /* End */
  NULL
};

/* Button masks */
const u64 ButtonMask[] = 
{
  PSP_CTRL_LTRIGGER | PSP_CTRL_RTRIGGER, 
  PSP_CTRL_START    | PSP_CTRL_SELECT,
  PSP_CTRL_SELECT   | PSP_CTRL_LTRIGGER,
  PSP_CTRL_SELECT   | PSP_CTRL_RTRIGGER,
  PSP_CTRL_UP,        PSP_CTRL_DOWN,
  PSP_CTRL_LEFT,      PSP_CTRL_RIGHT,
  PSP_CTRL_SQUARE,    PSP_CTRL_CROSS,
  PSP_CTRL_CIRCLE,    PSP_CTRL_TRIANGLE,
  PSP_CTRL_LTRIGGER,  PSP_CTRL_RTRIGGER,
  PSP_CTRL_SELECT,    PSP_CTRL_START,
  0 /* End */
};

/* Button map ID's */
const int ButtonMapId[] = 
{
  MAP_BUTTON_LRTRIGGERS, 
  MAP_BUTTON_STARTSELECT,
  MAP_BUTTON_SELECTLTRIG, 
  MAP_BUTTON_SELECTRTRIG,
  MAP_BUTTON_UP,       MAP_BUTTON_DOWN,
  MAP_BUTTON_LEFT,     MAP_BUTTON_RIGHT,
  MAP_BUTTON_SQUARE,   MAP_BUTTON_CROSS,
  MAP_BUTTON_CIRCLE,   MAP_BUTTON_TRIANGLE,
  MAP_BUTTON_LTRIGGER, MAP_BUTTON_RTRIGGER,
  MAP_BUTTON_SELECT,   MAP_BUTTON_START,
  -1 /* End */
};

/* Game configuration (includes button maps) */
struct GameConfig GameConfig;

/* Default configuration */
struct GameConfig DefaultConfig = 
{
  {
    JST|JST_UP,    /* Analog Up    */
    JST|JST_DOWN,  /* Analog Down  */
    JST|JST_LEFT,  /* Analog Left  */
    JST|JST_RIGHT, /* Analog Right */
    KBD|KBD_UP,    /* D-pad Up     */
    KBD|KBD_DOWN,  /* D-pad Down   */
    KBD|KBD_LEFT,  /* D-pad Left   */
    KBD|KBD_RIGHT, /* D-pad Right  */
    JST|JST_FIREA, /* Square       */
    JST|JST_FIREB, /* Cross        */
    KBD|KBD_SPACE, /* Circle       */
    0,             /* Triangle     */
    0,             /* L Trigger    */
    SPC|SPC_KYBD,  /* R Trigger    */
    0,             /* Select       */
    0,             /* Start        */
    SPC|SPC_MENU,  /* L+R Triggers */
    0,             /* Start+Select */
    0,             /* Select + L   */
    0              /* Select + R   */
  }
};

/* Emulated input mapping */
static const struct InputMethod SysInput[] = 
{
  { "None", 0 },

  /* Special */
  { "Special: Open Menu",       SPC|SPC_MENU },  { "Special: Show keyboard", SPC|SPC_KYBD },
  { "Special: Previous volume", SPC|SPC_PDISK }, { "Special: Next volume",   SPC|SPC_NDISK },

  /* Joystick */
  { "Joystick Up",       JST|JST_UP },    { "Joystick Down",     JST|JST_DOWN },
  { "Joystick Left",     JST|JST_LEFT },  { "Joystick Right",    JST|JST_RIGHT },
  { "Joystick Button A", JST|JST_FIREA }, { "Joystick Button B", JST|JST_FIREB },

  /* Directional */
  { "Keyboard Up",   KBD|KBD_UP },   { "Keyboard Down",  KBD|KBD_DOWN }, 
  { "Keyboard Left", KBD|KBD_LEFT }, { "Keyboard Right", KBD|KBD_RIGHT },

  /* Etc... */
  { "Spacebar",  KBD|KBD_SPACE }, { "Return",    KBD|KBD_ENTER },
  { "Escape", KBD|KBD_ESCAPE },   { "Backspace", KBD|KBD_BS },
  { "Tab",    KBD|KBD_TAB },      { "Select",    KBD|KBD_SELECT },
  { "Home",   KBD|KBD_HOME },     { "Delete",    KBD|KBD_DELETE },
  { "Insert", KBD|KBD_INSERT },   { "Stop",      KBD|KBD_STOP },

  /* Function keys */
  { "F1", KBD|KBD_F1 }, { "F2", KBD|KBD_F2 }, { "F3", KBD|KBD_F3 },
  { "F4", KBD|KBD_F4 }, { "F5", KBD|KBD_F5 },

  /* Numbers */
  { "0", KBD|0x30 }, { "1", KBD|0x31 }, { "2", KBD|0x32 }, { "3", KBD|0x33 },
  { "4", KBD|0x34 }, { "5", KBD|0x35 }, { "6", KBD|0x36 }, { "7", KBD|0x37 },
  { "8", KBD|0x38 }, { "9", KBD|0x39 },

  /* Alphabet */
  { "A", KBD|0x41 }, { "B", KBD|0x42 }, { "C", KBD|0x43 }, { "D", KBD|0x44 },
  { "E", KBD|0x45 }, { "F", KBD|0x46 }, { "G", KBD|0x47 }, { "H", KBD|0x48 },
  { "I", KBD|0x49 }, { "J", KBD|0x4A }, { "K", KBD|0x4B }, { "L", KBD|0x4C },
  { "M", KBD|0x4D }, { "N", KBD|0x4E }, { "O", KBD|0x4F }, { "P", KBD|0x50 },
  { "Q", KBD|0x51 }, { "R", KBD|0x52 }, { "S", KBD|0x53 }, { "T", KBD|0x54 },
  { "U", KBD|0x55 }, { "V", KBD|0x56 }, { "W", KBD|0x57 }, { "X", KBD|0x58 },
  { "Y", KBD|0x59 }, { "Z", KBD|0x5A }, 

  /* Symbols */
  { "' (Single quote)",  KBD|0x27 }, { ", (Comma)",        KBD|0x2C },
  { "- (Minus)",         KBD|0x2D }, { ". (Period)",       KBD|0x2E },
  { "/ (Forward slash)", KBD|0x2F }, { "; (Semicolon)",    KBD|0x3B },
  { "= (Equals)",        KBD|0x3D }, { "[ (Left bracket)", KBD|0x5B },
  { "] (Right bracket)", KBD|0x5D }, { "\\ (Backslash)",   KBD|0x5C },
  { "` (Backquote)",     KBD|0x60 },

  /* Numpad */
  { "Num. pad 0", KBD|KBD_NUMPAD0 }, { "Num. pad 1", KBD|KBD_NUMPAD1 },
  { "Num. pad 2", KBD|KBD_NUMPAD2 }, { "Num. pad 3", KBD|KBD_NUMPAD3 },
  { "Num. pad 4", KBD|KBD_NUMPAD4 }, { "Num. pad 5", KBD|KBD_NUMPAD5 },
  { "Num. pad 6", KBD|KBD_NUMPAD6 }, { "Num. pad 7", KBD|KBD_NUMPAD7 },
  { "Num. pad 8", KBD|KBD_NUMPAD8 }, { "Num. pad 9", KBD|KBD_NUMPAD9 },

  /* State keys */
  { "Shift",   KBD|KBD_SHIFT }, { "Control",   KBD|KBD_CONTROL },
  { "Graph",   KBD|KBD_GRAPH }, { "Caps Lock", KBD|KBD_CAPSLOCK },
  { "Country key", KBD|KBD_COUNTRY },

  /* End */
  { NULL, 0 }
};

void InitMenu()
{
  int i, j;

  /* Reset variables */
  for (i=0; i<MAXCARTS; i++) ROM[i] = NULL;
  for (i=0; i<MAXDRIVES; i++) Drive[i] = NULL;
  Quickload = NULL;
  TabIndex = TAB_ABOUT;
  Background = NULL;

  PspMenuItem *item;

  /* Initialize paths */
  ScreenshotPath 
    = (char*)malloc(sizeof(char) * (strlen(pspGetAppDirectory()) + strlen(ScreenshotDir) + 2));
  sprintf(ScreenshotPath, "%s%s/", pspGetAppDirectory(), ScreenshotDir);

  /* Initialize system menu */
  SystemMenu=pspMenuCreate();
  item=pspMenuAppendItem(SystemMenu, "Cartridge Slot A", (void*)SYSTEM_CART_A);
  pspMenuAppendOption(item, EmptySlot, NULL, 1);

  char *rom_types[] = 
  {
    "Generic 8kB", 
    "Generic 16kB", 
    "Konami5 8kB", 
    "Konami4 8kB", 
    "ASCII 8kB", 
    "ASCII 16kB",
    "GameMaster2",
    "FMPAC",
    NULL
  };

  item = pspMenuAppendItem(SystemMenu, "Cartridge Type", (void*)SYSTEM_CART_A_TYPE);
  pspMenuAppendOption(item, "Autodetect", (void*)CART_TYPE_AUTODETECT, 1);
  for (i = 0; rom_types[i]; i++)
    pspMenuAppendOption(item, rom_types[i], (void*)i, 0);
                        
  item=pspMenuAppendItem(SystemMenu, "Cartridge Slot B", (void*)SYSTEM_CART_B);
  pspMenuAppendOption(item, EmptySlot, NULL, 1);

  item = pspMenuAppendItem(SystemMenu, "Cartridge Type", (void*)SYSTEM_CART_B_TYPE);
  pspMenuAppendOption(item, "Autodetect", (void*)CART_TYPE_AUTODETECT, 1);
  for (i = 0; rom_types[i]; i++)
    pspMenuAppendOption(item, rom_types[i], (void*)i, 0);
                        
  pspMenuAppendItem(SystemMenu, NULL, NULL);

  item=pspMenuAppendItem(SystemMenu, "Drive A", (void*)SYSTEM_DRIVE_A);
  pspMenuAppendOption(item, EmptySlot, NULL, 1);
  item=pspMenuAppendItem(SystemMenu, "Drive B", (void*)SYSTEM_DRIVE_B);
  pspMenuAppendOption(item, EmptySlot, NULL, 1);
  pspMenuAppendItem(SystemMenu, NULL, NULL);

  pspMenuAppendItem(SystemMenu, "Save screenshot", (void*)SYSTEM_SCRNSHOT);
  pspMenuAppendItem(SystemMenu, NULL, NULL);

  pspMenuAppendItem(SystemMenu, "Reset", (void*)SYSTEM_RESET);
  pspMenuAppendItem(SystemMenu, NULL, NULL);

  pspMenuAppendItem(SystemMenu, "Back to MSX", (void*)SYSTEM_RETURN);
  pspMenuAppendItem(SystemMenu, "Quit", (void*)SYSTEM_QUIT);

  /* Initialize options menu */
  OptionMenu=pspMenuCreate();

  item=pspMenuAppendItem(OptionMenu, "Screen size", (void*)OPTION_DISPLAY_MODE);
  pspMenuAppendOption(item, "Actual size", (void*)DISPLAY_MODE_UNSCALED, 0);
  pspMenuAppendOption(item, "4:3 scaled (fit height)", (void*)DISPLAY_MODE_FIT_HEIGHT, 0);
  pspMenuAppendOption(item, "16:9 scaled (fit screen)", (void*)DISPLAY_MODE_FILL_SCREEN, 0);

  item=pspMenuAppendItem(OptionMenu, "Frame limiter", (void*)OPTION_SYNC_FREQ);
  pspMenuAppendOption(item, "Disabled", (void*)0, 0);
  pspMenuAppendOption(item, "60 fps (NTSC)", (void*)60, 0);
  pspMenuAppendOption(item, "50 fps (PAL)", (void*)50, 0);

  item=pspMenuAppendItem(OptionMenu, "Frame skipping", (void*)OPTION_FRAMESKIP);
  pspMenuAppendOption(item, "No skipping", (void*)0, 0);
  pspMenuAppendOption(item, "Skip 1 frame", (void*)1, 0);
  pspMenuAppendOption(item, "Skip 2 frames", (void*)2, 0);
  pspMenuAppendOption(item, "Skip 3 frames", (void*)3, 0);
  pspMenuAppendOption(item, "Skip 4 frames", (void*)4, 0);
  pspMenuAppendOption(item, "Skip 5 frames", (void*)5, 0);
  pspMenuAppendOption(item, "Skip 6 frames", (void*)6, 0);

  item=pspMenuAppendItem(OptionMenu, "VSync", (void*)OPTION_VSYNC);
  pspMenuAppendOption(item, "Disabled", (void*)0, 0);
  pspMenuAppendOption(item, "Enabled", (void*)1, 0);

  item=pspMenuAppendItem(OptionMenu, "PSP clock frequency", (void*)OPTION_CLOCK_FREQ);
  pspMenuAppendOption(item, "222 MHz", (void*)222, 0);
  pspMenuAppendOption(item, "300 MHz", (void*)300, 0);
  pspMenuAppendOption(item, "333 MHz", (void*)333, 0);

  item=pspMenuAppendItem(OptionMenu, "FPS counter", (void*)OPTION_SHOW_FPS);
  pspMenuAppendOption(item, "Disabled", (void*)0, 0);
  pspMenuAppendOption(item, "Enabled", (void*)1, 0);

  /* Initialize control menu */
  ControlMenu=pspMenuCreate();
  for (i=0; ButtonLabel[i]; i++)
  {
    item=pspMenuAppendItem(ControlMenu, ButtonLabel[i], (void*)i);
    for (j=0; SysInput[j].Description; j++)
      pspMenuAppendOption(item, SysInput[j].Description, (void*)SysInput[j].Code, 0);
  }

  /* Load the background image */
  Background=pspImageLoadPng("background.png");

  /* Init NoSaveState icon image */
  NoSaveIcon=pspImageCreate(136, 114);
  for (i=0;i<15504;i++) NoSaveIcon->Pixels[i]=0xf666;

  /* Initialize state menu */
  StateMenu=pspMenuCreate();
  for (i=0; i<10; i++)
    item=pspMenuAppendItem(StateMenu, NULL, (void*)i);

  /* Load default configuration */
  LoadGameConfig(DefConfigFile, &DefaultConfig);

  /* Initialize configuration */
  InitGameConfig(&GameConfig);

  /* Initialize screen buffer */
  Screen=pspImageCreate(WIDTH, HEIGHT);

  /* Initialize selected menu items */
  SelectedSystemItem=NULL;
  SelectedControlItem=NULL;
  SelectedOptionItem=NULL;
  SelectedStateItem=NULL;

  /* Initialize options */
  LoadOptions();

  /* Initialize UI components */
  UiMetric.Font = &PspStockFont;
}

/* Load options */
static void LoadOptions()
{
  char *path = (char*)malloc(sizeof(char) * (strlen(pspGetAppDirectory()) + strlen(OptionsFile) + 1));
  sprintf(path, "%s%s", pspGetAppDirectory(), OptionsFile);

  /* Initialize INI structure */
  INIFile ini;
  InitINI(&ini);

  /* Read the file */
  if (!LoadINI(&ini, path))
  {
    /* File does not exist; load defaults */
    TrashINI(&ini);
    InitOptionDefaults();
  }
  else
  {
    /* Load values */
    DisplayMode = INIGetInteger(&ini, "Video", "Display Mode", DISPLAY_MODE_UNSCALED);
    UpdateFreq = INIGetInteger(&ini, "Video", "Update Frequency", 60);
    Frameskip = INIGetInteger(&ini, "Video", "Frameskip", 1);
    VSync = INIGetInteger(&ini, "Video", "VSync", 0);
    ClockFreq = INIGetInteger(&ini, "Video", "PSP Clock Frequency", 222);
    ShowFps = INIGetInteger(&ini, "Video", "Show FPS", 0);

    char qlpath[PSP_FILEIO_MAX_PATH_LEN];
    if (INIGetString(&ini, "File", "Game Path", qlpath, PSP_FILEIO_MAX_PATH_LEN))
      Quickload = strdup(qlpath);
  }

  /* Clean up */
  free(path);
  TrashINI(&ini);
}

/* Save options */
static int SaveOptions()
{
  char *path = (char*)malloc(sizeof(char) * (strlen(pspGetAppDirectory()) + strlen(OptionsFile) + 1));
  sprintf(path, "%s%s", pspGetAppDirectory(), OptionsFile);

  /* Initialize INI structure */
  INIFile ini;
  InitINI(&ini);

  /* Set values */
  INISetInteger(&ini, "Video", "Display Mode", DisplayMode);
  INISetInteger(&ini, "Video", "Update Frequency", UpdateFreq);
  INISetInteger(&ini, "Video", "Frameskip", Frameskip);
  INISetInteger(&ini, "Video", "VSync", VSync);
  INISetInteger(&ini, "Video", "PSP Clock Frequency", ClockFreq);
  INISetInteger(&ini, "Video", "Show FPS", ShowFps);

  if (Quickload)
  {
    char *qlpath = pspFileIoGetParentDirectory(Quickload);
    INISetString(&ini, "File", "Game Path", qlpath);
    free(qlpath);
  }

  /* Save INI file */
  int status = SaveINI(&ini, path);

  /* Clean up */
  TrashINI(&ini);
  free(path);

  return status;
}

/* Initialize options to system defaults */
static void InitOptionDefaults()
{
  DisplayMode = DISPLAY_MODE_UNSCALED;
  UpdateFreq = 60;
  Frameskip = 1;
  VSync = 0;
  ClockFreq = 222;
  Quickload = NULL;
  ShowFps = 0;
}

/* Returns instructions for selected System menu item */
static char* GetSystemMenuOptions(PspMenuItem *item, unsigned int ok_btn_mask, unsigned int cancel_btn_mask)
{
  switch((int)item->Userdata)
  {
  case SYSTEM_CART_A:
  case SYSTEM_CART_B:
    return "\026\242\020 Browse\t\026\243\020 Eject cart";
  case SYSTEM_DRIVE_A:
  case SYSTEM_DRIVE_B:
    return "\026\242\020 Browse\t\026\243\020 Eject disk";
  case SYSTEM_SCRNSHOT:
    return "\026\242\020 Save screenshot";
  case SYSTEM_RETURN:
    return "\026\242\020 Return to MSX";
  case SYSTEM_RESET:
    return "\026\242\020 Reset MSX";
  case SYSTEM_QUIT:
    return "\026\242\020 Quit program";
  case SYSTEM_CART_A_TYPE:
  case SYSTEM_CART_B_TYPE:
    return "\026\250\020 Select cartridge type";
  }

  return NULL;
}

/* Returns instructions for selected Options menu item */
static char* GetOptionsMenuCaption(PspMenuItem *item, unsigned int ok_btn_mask, unsigned int cancel_btn_mask)
{
  switch((int)item->Userdata)
  {
  case OPTION_DISPLAY_MODE:
    return "\026\250\020 Change MSX screen appearance";
  case OPTION_SYNC_FREQ:
    return "\026\250\020 Change screen update frequency";
  case OPTION_FRAMESKIP:
    return "\026\250\020 Change number of frames skipped per update";
  case OPTION_VSYNC:
    return "\026\250\020 Enable to reduce shearing; disable to increase speed";
  case OPTION_CLOCK_FREQ:
    return "\026\250\020 Change PSP clock frequency (>222 = faster battery depletion)";
  case OPTION_SHOW_FPS:
    return "\026\250\020 Show/hide the frames-per-second counter";
  }

  return NULL;
}

/* Returns instructions for selected Control menu item */
static char* GetControlMenuOptions(PspMenuItem *item, unsigned int ok_btn_mask, unsigned int cancel_btn_mask)
{
  return "\026\250\020 Change mapping\t\026\242\020 Save to \271\t\026\244\020 Set as default\t\026\243\020 Load defaults";
}

/* Returns instructions for selected save state menu item */
static char* GetSaveStateMenuOptions(PspMenuItem *item, unsigned int ok_btn_mask, unsigned int cancel_btn_mask)
{
  return (item->Icon == NoSaveIcon) ? "\026\244\020 Save" : "\026\244\020 Save\t\026\242\020 Load\t\026\243\020 Delete";
}

void DisplayMenu()
{
  int buttons;

  /* Set normal clock frequency */
  pspSetClockFrequency(222);
  /* Set buttons to autorepeat */
  pspCtrlSetPollingMode(PSP_CTRL_AUTOREPEAT);

  /* Menu loop */
  while (!ExitPSP)
  {
    /* Display appropriate tab */
    switch (TabIndex)
    {
    default:
    case TAB_QUICKLOAD:
      buttons = DisplayQuickloadTab(); break;
    case TAB_SYSTEM:
      buttons = DisplaySystemTab();    break;
    case TAB_CONTROL:
      buttons = DisplayControlTab();   break;
    case TAB_STATE:
      buttons = DisplayStateTab();     break;
    case TAB_OPTION:
      buttons = DisplayOptionTab();    break;
    case TAB_ABOUT:
      buttons = DisplayAboutTab();    break;
    }

    /* If L or R are pressed, switch tabs */
    if (buttons & PSP_CTRL_LTRIGGER)
    {
      if (--TabIndex < 0) TabIndex=TAB_MAX;
      continue; 
    }
    else if (buttons & PSP_CTRL_RTRIGGER)
    { 
      if (++TabIndex > TAB_MAX) TabIndex=0; 
      continue;
    }

    /* Any other button - exit the menu loop */
    break;
  }

  if (!ExitPSP)
  {
    /* Set clock frequency during emulation */
    pspSetClockFrequency(ClockFreq);
    /* Set buttons to normal mode */
    pspCtrlSetPollingMode(PSP_CTRL_NORMAL);

    /* Clear the buffers */
    pspVideoClearScreen();
    pspVideoSwapBuffers();
    pspVideoClearScreen();
  }
}

/* Gets configuration name */
static const char* GetConfigName()
{
  if (ROM[0]) return pspFileIoGetFilename(ROM[0]);
  if (Drive[0]) return pspFileIoGetFilename(Drive[0]);

  return BasicConfigFile;
}

/* Initialize game configuration */
static void InitGameConfig(struct GameConfig *config)
{
  /* Initialize to default configuration */
  if (config != &DefaultConfig)
    memcpy(config, &DefaultConfig, sizeof(struct GameConfig));
}

/* Load game configuration */
static int LoadGameConfig(const char *filename, struct GameConfig *config)
{
  char *path;
  if (!(path = (char*)malloc(sizeof(char) * (strlen(pspGetAppDirectory()) + strlen(ConfigDir) + strlen(filename) + 6))))
    return 0;
  sprintf(path, "%s%s/%s.cnf", pspGetAppDirectory(), ConfigDir, filename);

  /* If no configuration, load defaults */
  if (!pspFileIoCheckIfExists(path))
  {
    InitGameConfig(config);
    return 1;
  }

  /* Open file for reading */
  FILE *file = fopen(path, "r");
  free(path);
  if (!file) return 0;

  /* Read contents of struct */
  int nread = fread(config, sizeof(struct GameConfig), 1, file);
  fclose(file);

  if (nread != 1)
  {
    InitGameConfig(config);
    return 0;
  }

  return 1;
}

/* Save game configuration */
static int SaveGameConfig(const char *filename, const struct GameConfig *config)
{
  char *path;
  if (!(path = (char*)malloc(sizeof(char) * (strlen(pspGetAppDirectory()) + strlen(ConfigDir) + strlen(filename) + 6))))
    return 0;
  sprintf(path, "%s%s/%s.cnf", pspGetAppDirectory(), ConfigDir, filename);

  /* Open file for writing */
  FILE *file = fopen(path, "w");
  free(path);
  if (!file) return 0;

  /* Write contents of struct */
  int nwritten = fwrite(config, sizeof(struct GameConfig), 1, file);
  fclose(file);

  return (nwritten == 1);
}

/* Handles drawing of generic items */
void DisplayPspStatus()
{
  static int percentiles[] = { 60, 30, 12, 0 };
  static char caption[32];
  static int percent_left, time_left, i, width;
  pspTime time;

  time_left = pspGetBatteryTime();
  sceRtcGetCurrentClockLocalTime(&time);
  percent_left = pspGetBatteryPercent();

  /* Determine appropriate charge icon */
  for (i = 0; i < 4; i++)
    if (percent_left >= percentiles[i])
      break;

  /* Display PSP status (time, battery, etc..) */
  sprintf(caption, "\270%2i/%2i %02i%c%02i %c%3i%% (%02i:%02i) ", 
    time.month, time.day, 
    time.hour, (time.microseconds > 500000) ? ':' : ' ', time.minutes, 
    (percent_left > 6) 
      ? 0263 + i 
      : 0263 + i + (time.microseconds > 500000) ? 0 : 1, 
    percent_left, 
    time_left / 60, time_left % 60);

  width = pspFontGetTextWidth(UiMetric.Font, caption);
  pspVideoPrint(UiMetric.Font, SCR_WIDTH - width, 0, caption, PSP_VIDEO_WHITE);

  /* Draw tabs */
  int height = pspFontGetLineHeight(UiMetric.Font);
  int x, max;

  max = (TabIndex <= TAB_MAX) ? TAB_MAX : TabIndex;
  for (i = 0, x = 5; i <= max; i++, x += width + 10)
  {
    /* Determine width of text */
    width = pspFontGetTextWidth(UiMetric.Font, TabLabel[i]);

    /* Draw background of active tab */
    if (i == TabIndex) pspVideoFillRect(x - 5, 0, x + width + 5, height + 1, 0xff959595);

    /* Draw name of tab */
    pspVideoPrint(UiMetric.Font, x, 0, TabLabel[i], PSP_VIDEO_WHITE);
  }
}

/* Handles any special drawing for the system menu */
void SystemMenuCustomDraw()
{
  int w, h, x, y;
  w = WIDTH / 2;
  h = HEIGHT / 2;
  x = SCR_WIDTH - w - 8;
  y = SCR_HEIGHT - h - 56;

  if (Screen->Pixels[0])
  {
    /* Draw a small representation of the screen */
    pspVideoShadowRect(x, y, x + w - 1, y + h - 1, PSP_VIDEO_BLACK, 3);
    pspVideoPutImage(Screen, x, y, w, h);
  }

  DisplayPspStatus();
}

/* 'About' menu navigation */
static unsigned int DisplayAboutTab()
{
  int fh, i, x, y, height;
  SceCtrlData pad;
  unsigned int cncl_btn_mask = PSP_CTRL_LTRIGGER | PSP_CTRL_RTRIGGER | PSP_CTRL_CROSS;
  char *lines[] = 
  { 
    "fMSX-PSP version 3.1.3", 
    "\026www.akop.org/psp/fmsx",
    " ",
    "2007 Akop Karapetyan",
    "1994-2007 Marat Fayzullin",
    NULL
  };

  for (i = 0; lines[i]; i++);
  fh = pspFontGetLineHeight(UiMetric.Font);
  height = fh * (i - 1);

  while (!ExitPSP)
  {
    if (!pspCtrlPollControls(&pad))
      continue;

    if (pad.Buttons & cncl_btn_mask)
      break;

    /* Clear screen */
    if (Background) pspVideoPutImage(Background, 0, 0, Background->Width, Background->Height);
    else pspVideoClearScreen();

    /* Render lines */
    for (i = 0, y = SCR_HEIGHT / 2 - height / 2; lines[i]; i++, y += fh)
    {
      x = SCR_WIDTH / 2 - pspFontGetTextWidth(UiMetric.Font, lines[i]) / 2;
      pspVideoPrint(UiMetric.Font, x, y, lines[i], PSP_VIDEO_GRAY);
    }

    /* Instructions */
    pspVideoPrint(UiMetric.Font, 0, SCR_HEIGHT - fh, "\026\255\256\020 Switch tabs", PSP_VIDEO_GRAY);

    /* Render PSP status */
    DisplayPspStatus();

    /* Swap buffers */
    sceKernelDcacheWritebackAll();
    pspVideoWaitVSync();
    pspVideoSwapBuffers();
  }

  return pad.Buttons;
}

/* Called when a system menu item changes */
static int OnSystemMenuItemChange(const PspMenuItem *item, const PspMenuOption *option)
{
  switch((int)item->Userdata)
  {
  case SYSTEM_CART_A_TYPE:
  case SYSTEM_CART_B_TYPE:
    SETROMTYPE((int)item->Userdata - SYSTEM_CART_A_TYPE, (int)option->Value);
    break;
  }

  return 1;
}

/* Handles navigation within the system tab */
static unsigned int DisplaySystemTab()
{
  PspMenuItem* item;
  unsigned int buttons=0;
  int opt, slot;

  for (;!ExitPSP;)
  {
    buttons = pspUiMenu(Background,
      SystemMenu, &SelectedSystemItem,
      PSP_CTRL_TRIANGLE | PSP_CTRL_CIRCLE, 
      PSP_CTRL_CROSS | PSP_CTRL_LTRIGGER | PSP_CTRL_RTRIGGER,
      GetSystemMenuOptions,
      SystemMenuCustomDraw,
      OnSystemMenuItemChange);

    if (buttons & PSP_CTRL_CIRCLE)
    {
      unsigned int rom_but;
      opt = (int)SelectedSystemItem->Userdata;

      switch (opt)
      {
      case SYSTEM_CART_A:
      case SYSTEM_CART_B:

        /* Load cartridge */
        slot = opt - SYSTEM_CART_A;
        rom_but = pspUiFileBrowser(Background,
          ROM[slot] ? ROM[slot] : pspGetAppDirectory(), 
          CartFilter, &ROM[slot], 
          PSP_CTRL_CIRCLE, PSP_CTRL_CROSS,
          DisplayPspStatus);

        if (rom_but & PSP_CTRL_CIRCLE)
        {
          if (!LoadFile(ROM[slot], slot))
            break;
        }

        /* Load game configuration */
        if (!LoadGameConfig(GetConfigName(), &GameConfig))
          ; //pspUiAlert("ERROR: Configuration not loaded");

        break;

      case SYSTEM_DRIVE_A:
      case SYSTEM_DRIVE_B:

        /* Load disk */
        slot = opt - SYSTEM_DRIVE_A;
        rom_but = pspUiFileBrowser(Background,
          Drive[slot] ? Drive[slot] : pspGetAppDirectory(),
          DiskFilter, &Drive[slot], 
          PSP_CTRL_CIRCLE, PSP_CTRL_CROSS,
          DisplayPspStatus);

        if (rom_but & PSP_CTRL_CIRCLE) 
        {
          if (!LoadFile(Drive[slot], slot))
            break;
        }

        /* Load game configuration */
        if (!LoadGameConfig(GetConfigName(), &GameConfig))
          ; //pspUiAlert("ERROR: Configuration not loaded");

        break;

      case SYSTEM_RESET:

        /* Reset system */
        if (pspUiConfirm("Reset MSX?"))
        {
          ResetMSX(Mode,RAMPages,VRAMPages);
          goto exit_menu;
        }

        break;

      case SYSTEM_QUIT:

        /* Exit */
        if (pspUiConfirm("Quit fMSX-PSP?"))
        {
          ExitNow=1;
          ExitPSP=1;
        }
        break;

      case SYSTEM_SCRNSHOT:

        /* Save screenshot */
        if (!SaveScreenshot(ScreenshotPath, GetConfigName(), Screen))
          pspUiAlert("ERROR: Screenshot not saved");
        else
          pspUiAlert("Screenshot saved successfully");

        break;

      default:
        break;

      case SYSTEM_RETURN:

        /* Return to emulation */
        goto exit_menu;
      }
    }
    else if (buttons & PSP_CTRL_TRIANGLE)
    {
      opt=(int)SelectedSystemItem->Userdata;

      switch (opt)
      {
      case SYSTEM_CART_A:
      case SYSTEM_CART_B:

        /* Eject cartridge */
        slot=opt-SYSTEM_CART_A;
        if (ROM[slot]) { free(ROM[slot]); ROM[slot] = NULL; }
        pspMenuModifyOption(SelectedSystemItem->Options, EmptySlot, NULL);

        /* Eject cart */
        LoadCart(NULL, slot);

        /* Reset cartridge type */
        item = pspMenuFindItemByUserdata(SystemMenu, (void*)SYSTEM_CART_A_TYPE + slot);
        pspMenuSelectOptionByValue(item, (void*)CART_TYPE_AUTODETECT);

        /* Load game configuration */
        if (!LoadGameConfig(GetConfigName(), &GameConfig))
          ; //pspUiAlert("ERROR: Configuration not loaded");

        break;

      case SYSTEM_DRIVE_A:
      case SYSTEM_DRIVE_B:

        /* Eject disk */
        slot=opt-SYSTEM_DRIVE_A;
        if (Drive[slot]) { free(Drive[slot]); Drive[slot] = NULL; }
        pspMenuModifyOption(SelectedSystemItem->Options, EmptySlot, NULL);

        /* Eject disk */
        ChangeDisk(slot, NULL);

        /* Load game configuration */
        if (!LoadGameConfig(GetConfigName(), &GameConfig))
          ; //pspUiAlert("ERROR: Configuration not loaded");

        break;
      }
    }
    else if (buttons & (PSP_CTRL_CROSS | PSP_CTRL_LTRIGGER | PSP_CTRL_RTRIGGER))
      break;
  }

exit_menu:
  return buttons;
}

/* Loads a cartridge or disk image into specified slot */
static int LoadFile(char *filename, int slot)
{
  PspMenuItem *item = NULL;
  char *file_to_load = NULL;

  /* Check if it's a zip file */
  if (pspFileIoEndsWith(filename, "ZIP"))
  {
    unzFile zip;
    unz_file_info fi;
    unz_global_info gi;
    char compressed_file[245];
    char *found_file;
    int i;

    /* Open archive for reading */
    if (!(zip = unzOpen(filename)))
    {
      pspUiAlert("Error opening compressed file");
      return 0;
    }

    /* Get global ZIP file information */
    if (unzGetGlobalInfo(zip, &gi) != UNZ_OK)
    {
      pspUiAlert("Error reading compressed file information");
      unzClose(zip);
      return 0;
    }

    found_file = NULL;
    int min_dsk = 11, dsk;

    for (i = 0; i < gi.number_entry; i++)
    {
      /* Get name of the archived file */
      if (unzGetCurrentFileInfo(zip, &fi, compressed_file, 
        sizeof(compressed_file), NULL, 0, NULL, 0) != UNZ_OK)
      {
        pspUiAlert("Error reading compressed file information");
        unzClose(zip);
        return 0;
      }

      /* For ROM's, just load the first available one */
      if (pspFileIoEndsWith(compressed_file, "ROM"))
      {
        found_file = compressed_file;
        break;
      }
      else if (pspFileIoEndsWith(compressed_file, "DSK"))
      {
        /* Check the pre-extension char */
        char digit = compressed_file[strlen(compressed_file) - 5];

        if (digit >= '0' && digit <= '9')
        {
          /* Find the file with the lowest sequential number */
          /* Treat zero as #10 */
          dsk = (digit == 0) ? 10 : (int)digit - '0';
          if (dsk < min_dsk)
          {
            min_dsk = dsk;
            found_file = compressed_file;
          }
        }
        else
        {
          /* Non-sequential DSK - load it immediately */
          found_file = compressed_file;
          break;
        }
      }

      /* Go to the next file in the archive */
      if (i + 1 < gi.number_entry)
      {
        if (unzGoToNextFile(zip) != UNZ_OK)
        {
          pspUiAlert("Error parsing compressed files");
          unzClose(zip);
          return 0;
        }
      }
    }

    /* Done reading */
    unzClose(zip);

    if (!found_file)
    {
      pspUiAlert("No loadable files found in the archive");
      return 0;
    }

    /* Build the virtual compressed filename */
    if (!(file_to_load = malloc(sizeof(char) * (strlen(filename) + strlen(found_file) + 2))))
      return 0;

    sprintf(file_to_load, "%s/%s", filename, found_file);
  }
  else
  {
    file_to_load = strdup(filename);
  }

  /* Load disk image */
  if (pspFileIoEndsWith(file_to_load, "DSK") || pspFileIoEndsWith(file_to_load, "DSK.GZ")) 
  {
    if (!ChangeDisk(slot, file_to_load))
    {
      free(file_to_load);
      pspUiAlert("Error loading disk");
      return 0;
    }

    free(Drive[slot]);
    Drive[slot] = file_to_load;
    item = pspMenuFindItemByUserdata(SystemMenu, (void*)SYSTEM_DRIVE_A + slot);
  }
  /* Load cartridge */
  else if (pspFileIoEndsWith(file_to_load, "ROM") || pspFileIoEndsWith(file_to_load, "ROM.GZ"))
  {
    if (!LoadCart(file_to_load, slot))
    {
      free(file_to_load);
      pspUiAlert("Error loading cartridge");
      return 0;
    }

    /* Reset cartridge type */
    item = pspMenuFindItemByUserdata(SystemMenu, (void*)SYSTEM_CART_A_TYPE + slot);
    pspMenuSelectOptionByValue(item, (void*)CART_TYPE_AUTODETECT);

    free(ROM[slot]);
    ROM[slot] = file_to_load;
    item = pspMenuFindItemByUserdata(SystemMenu, (void*)SYSTEM_CART_A + slot);
  }
  else
  {
    free(file_to_load);
  }

  /* Update menu item */
  if (item) 
    pspMenuModifyOption(item->Options, pspFileIoGetFilename(filename), NULL);

  return 1;
}

/* Handles navigation within the quickload tab */
static unsigned int DisplayQuickloadTab()
{
  unsigned int buttons=0;
  int i;

  for (;!ExitPSP;)
  {
    buttons = pspUiFileBrowser(Background,
      Quickload ? Quickload : pspGetAppDirectory(), 
      QuickloadFilter, &Quickload, 
      PSP_CTRL_CIRCLE, PSP_CTRL_CROSS | PSP_CTRL_LTRIGGER | PSP_CTRL_RTRIGGER,
      DisplayPspStatus);

    if (buttons & PSP_CTRL_CIRCLE)
    {
      /* Trash any ROM or disk selections */
      for (i=0; i<MAXCARTS; i++)  if (ROM[i]) { free(ROM[i]); ROM[i] = NULL; }
      for (i=0; i<MAXDRIVES; i++) if (Drive[i]) { free(Drive[i]); Drive[i] = NULL; }

      /* Eject all cartridges and disks */
      for (i = 0; i < MAXCARTS; i++) LoadCart(NULL, i);
      for (i = 0; i < MAXDRIVES; i++) ChangeDisk(i, NULL);

      /* Reinitialize menu */
      int userdata[] = { SYSTEM_CART_A, SYSTEM_CART_B, SYSTEM_DRIVE_A, SYSTEM_DRIVE_B, 0 };
      PspMenuItem *item;
      for (i = 0; userdata[i]; i++)
      {
        item = pspMenuFindItemByUserdata(SystemMenu, (void*)userdata[i]);
        pspMenuModifyOption(item->Options, EmptySlot, NULL);
      }

      /* Load disk or cartridge */
      char *fn_copy = strdup(Quickload);
      if (!LoadFile(fn_copy, 0))
      {
        free(fn_copy);
        continue;
      }

      /* Load game configuration */
      if (!LoadGameConfig(GetConfigName(), &GameConfig))
        ;//pspUiAlert("ERROR: Configuration not loaded");

      /* Restart MSX */
      ResetMSX(Mode,RAMPages,VRAMPages);
      break;
    }
    else if (buttons & (PSP_CTRL_CROSS | PSP_CTRL_LTRIGGER | PSP_CTRL_RTRIGGER))
      break;
  }

  return buttons;
}

/* Handles navigation within the control tab */
static unsigned int DisplayControlTab()
{
  unsigned int buttons=0;
  PspMenuItem *item;
  int i;

  /* Load current button mapping */
  for (item=ControlMenu->First, i=0; item; item=item->Next, i++)
    pspMenuSelectOptionByValue(item, (void*)GameConfig.ButtonMap[i]);

  for (;!ExitPSP;)
  {
    buttons = pspUiMenu(Background,
      ControlMenu, &SelectedControlItem,
      PSP_CTRL_CIRCLE | PSP_CTRL_SQUARE, 
      PSP_CTRL_CROSS | PSP_CTRL_LTRIGGER | PSP_CTRL_RTRIGGER | PSP_CTRL_TRIANGLE,
      GetControlMenuOptions, 
      DisplayPspStatus,
      NULL);

    if (!(buttons & PSP_CTRL_TRIANGLE))
    {
      /* Commit changes to memory */
      for (item=ControlMenu->First, i=0; item; item=item->Next, i++)
        GameConfig.ButtonMap[i]=(unsigned int)item->Selected->Value;
    }

    if (buttons & (PSP_CTRL_CROSS | PSP_CTRL_LTRIGGER | PSP_CTRL_RTRIGGER))
      break;
    else if (buttons & PSP_CTRL_CIRCLE)
    {
      /* Save mapping to memory stick */
      if (SaveGameConfig(GetConfigName(), &GameConfig))
        pspUiAlert("Changes saved");
      else
        pspUiAlert("ERROR: Changes not saved");
    }
    else if (buttons & PSP_CTRL_SQUARE)
    {
      /* Save mapping to memory stick */
      if (SaveGameConfig(DefConfigFile, &DefaultConfig))
      {
        /* Modify in-memory defaults */
        memcpy(&DefaultConfig, &GameConfig, sizeof(struct GameConfig));
        pspUiAlert("Changes saved");
      }
      else
        pspUiAlert("ERROR: Changes not saved");
    }
    else if (buttons & PSP_CTRL_TRIANGLE)
    {
      /* Load default button mapping */
      for (item=ControlMenu->First, i=0; item; item=item->Next, i++)
        pspMenuSelectOptionByValue(item, (void*)DefaultConfig.ButtonMap[i]);
    }
  }

  return buttons;
}

/* Load state icon */
PspImage* LoadStateIcon(const char *path)
{
  /* Open file for reading */
  FILE *f = fopen(path, "r");
  if (!f) return NULL;

  /* Position pointer at the end and read the position of the image */
  long pos;
  fseek(f, -sizeof(long), SEEK_END);
  fread(&pos, sizeof(long), 1, f);

  /* Reposition to start of image */
  if (fseek(f, pos, SEEK_SET) != 0)
  {
    fclose(f);
    return 0;
  }

  /* Load image */
  PspImage *image = pspImageLoadPngOpen(f);
  fclose(f);

  return image;
}

/* Load state */
int LoadState(const char *path)
{
  return LoadSTA(path);
}

/* Save state */
PspImage* SaveState(const char *path, PspImage *icon)
{
  /* Save state */
  if (!SaveSTA(path))
    return NULL;

  /* Create thumbnail */
  PspImage *thumb = pspImageCreateThumbnail(icon);
  if (!thumb) return NULL;

  /* Reopen file in append mode */
  FILE *f = fopen(path, "a");
  if (!f)
  {
    pspImageDestroy(thumb);
    return NULL;
  }

  long pos = ftell(f);

  /* Write the thumbnail */
  if (!pspImageSavePngOpen(f, thumb))
  {
    pspImageDestroy(thumb);
    fclose(f);
    return NULL;
  }

  /* Write the position of the icon */
  fwrite(&pos, sizeof(long), 1, f);

  fclose(f);
  return thumb;
}

static unsigned int DisplayStateTab()
{
  unsigned int buttons=0;
  PspMenuItem *item;
  PspImage *icon;
  SceIoStat stat;
  char caption[20];

  // TODO: length checking
  char path[PSP_FILEIO_MAX_PATH_LEN];

  /* Initialize icons */
  for (item=StateMenu->First; item; item=item->Next)
  {
    sprintf(path, "%s%s/%s_%02i.sta", pspGetAppDirectory(), SaveStateDir, GetConfigName(), (int)item->Userdata);
    if (pspFileIoCheckIfExists(path))
    {
      if (sceIoGetstat(path, &stat) < 0)
        sprintf(caption, "ERROR");
      else
        sprintf(caption, "%02i/%02i/%02i %02i:%02i", 
          stat.st_mtime.month,
          stat.st_mtime.day,
          stat.st_mtime.year - (stat.st_mtime.year / 100) * 100,
          stat.st_mtime.hour,
          stat.st_mtime.minute);

      pspMenuSetCaption(item, caption);
      item->Icon = LoadStateIcon(path);
    }
    else
    {
      pspMenuSetCaption(item, "Empty");
      item->Icon = NoSaveIcon;
    }
  }

  for (;!ExitPSP;)
  {
    buttons = pspUiGallery(Background,
      StateMenu, &SelectedStateItem,
      PSP_CTRL_CIRCLE | PSP_CTRL_SQUARE | PSP_CTRL_TRIANGLE, 
      PSP_CTRL_CROSS | PSP_CTRL_LTRIGGER | PSP_CTRL_RTRIGGER,
      GetSaveStateMenuOptions,
      DisplayPspStatus);

    if (buttons & (PSP_CTRL_CROSS | PSP_CTRL_LTRIGGER | PSP_CTRL_RTRIGGER))
      break;
    else if (buttons & PSP_CTRL_SQUARE)
    {
      /* Save state */
      sprintf(path, "%s%s/%s_%02i.sta", pspGetAppDirectory(), SaveStateDir, GetConfigName(), (int)SelectedStateItem->Userdata);
      if (pspFileIoCheckIfExists(path) && !pspUiConfirm("Overwrite existing state?"))
          continue;

      pspUiFlashMessage("Saving, please wait ...");

      if ((icon = SaveState(path, Screen)))
      {
        /* Trash the old icon (if any) */
        if (SelectedStateItem->Icon && SelectedStateItem->Icon != NoSaveIcon)
          pspImageDestroy((PspImage*)SelectedStateItem->Icon);

        /* Set the new icon */
        SelectedStateItem->Icon=icon;

        /* Get file modification time/date */
        if (sceIoGetstat(path, &stat) < 0)
          sprintf(caption, "ERROR");
        else
          sprintf(caption, "%02i/%02i/%02i %02i:%02i", 
            stat.st_mtime.month,
            stat.st_mtime.day,
            stat.st_mtime.year - (stat.st_mtime.year / 100) * 100,
            stat.st_mtime.hour,
            stat.st_mtime.minute);

        pspMenuSetCaption(SelectedStateItem, caption);
      }
      else 
        pspUiAlert("ERROR: State not saved");
    }
    else if (buttons & PSP_CTRL_CIRCLE)
    {
      /* Load state */
      sprintf(path, "%s%s/%s_%02i.sta", pspGetAppDirectory(), SaveStateDir, GetConfigName(), (int)SelectedStateItem->Userdata);
      if (pspFileIoCheckIfExists(path) && pspUiConfirm("Load state?"))
      {
        if (LoadState(path))
          break;

        pspUiAlert("ERROR: State not loaded");
      }
    }
    else if (buttons & PSP_CTRL_TRIANGLE)
    {
      /* Delete state */
      sprintf(path, "%s%s/%s_%02i.sta", pspGetAppDirectory(), SaveStateDir, GetConfigName(), (int)SelectedStateItem->Userdata);
      if (pspFileIoCheckIfExists(path))
      {
        if (pspUiConfirm("Delete state?"))
        {
          if (pspFileIoDelete(path))
          {
            /* Trash the old icon (if any) */
            if (SelectedStateItem->Icon && SelectedStateItem->Icon != NoSaveIcon)
              pspImageDestroy((PspImage*)SelectedStateItem->Icon);

            /* Update icon, caption */
            SelectedStateItem->Icon = NoSaveIcon;
            pspMenuSetCaption(SelectedStateItem, "Empty");
          }
          else pspUiAlert("ERROR: State not deleted");
        }
      }
    }
  }

  /* Destroy any icons */
  for (item=StateMenu->First; item; item=item->Next)
    if (item->Icon != NULL && item->Icon != NoSaveIcon)
      pspImageDestroy((PspImage*)item->Icon);

  return buttons;
}

/* Handles navigation within the Option tab */
static unsigned int DisplayOptionTab()
{
  unsigned int buttons=0;
  PspMenuItem *item;

  item = pspMenuFindItemByUserdata(OptionMenu, (void*)OPTION_DISPLAY_MODE);
  pspMenuSelectOptionByValue(item, (void*)DisplayMode);
  item = pspMenuFindItemByUserdata(OptionMenu, (void*)OPTION_SYNC_FREQ);
  pspMenuSelectOptionByValue(item, (void*)UpdateFreq);
  item = pspMenuFindItemByUserdata(OptionMenu, (void*)OPTION_FRAMESKIP);
  pspMenuSelectOptionByValue(item, (void*)(int)Frameskip);
  item = pspMenuFindItemByUserdata(OptionMenu, (void*)OPTION_VSYNC);
  pspMenuSelectOptionByValue(item, (void*)VSync);
  item = pspMenuFindItemByUserdata(OptionMenu, (void*)OPTION_CLOCK_FREQ);
  pspMenuSelectOptionByValue(item, (void*)ClockFreq);
  item = pspMenuFindItemByUserdata(OptionMenu, (void*)OPTION_SHOW_FPS);
  pspMenuSelectOptionByValue(item, (void*)ShowFps);

  for (;!ExitPSP;)
  {
    buttons = pspUiMenu(Background,
      OptionMenu, &SelectedOptionItem,
      PSP_CTRL_CIRCLE, 
      PSP_CTRL_CROSS | PSP_CTRL_LTRIGGER | PSP_CTRL_RTRIGGER,
      GetOptionsMenuCaption, 
      DisplayPspStatus,
      NULL);

    if (buttons & (PSP_CTRL_CROSS | PSP_CTRL_LTRIGGER | PSP_CTRL_RTRIGGER))
      break;
  }

  item = pspMenuFindItemByUserdata(OptionMenu, (void*)OPTION_DISPLAY_MODE);
  DisplayMode = (int)item->Selected->Value;
  item = pspMenuFindItemByUserdata(OptionMenu, (void*)OPTION_SYNC_FREQ);
  UpdateFreq = (int)item->Selected->Value;
  item = pspMenuFindItemByUserdata(OptionMenu, (void*)OPTION_FRAMESKIP);
  Frameskip = (byte)(int)item->Selected->Value;
  item = pspMenuFindItemByUserdata(OptionMenu, (void*)OPTION_VSYNC);
  VSync = (byte)(int)item->Selected->Value;
  item = pspMenuFindItemByUserdata(OptionMenu, (void*)OPTION_CLOCK_FREQ);
  ClockFreq = (int)item->Selected->Value;
  item = pspMenuFindItemByUserdata(OptionMenu, (void*)OPTION_SHOW_FPS);
  ShowFps = (int)item->Selected->Value;

  return buttons;
}

/* Clean up any used resources */
void TrashMenu()
{
  int i;

  /* Save options */
  SaveOptions();

  /* Free memory */
  for (i=0; i<MAXCARTS; i++) if (ROM[i]) free(ROM[i]);
  for (i=0; i<MAXDRIVES; i++) if (Drive[i]) free(Drive[i]);
  if (Quickload) free(Quickload);

  /* Trash menus */
  pspMenuDestroy(SystemMenu);
  pspMenuDestroy(OptionMenu);
  pspMenuDestroy(ControlMenu);
  pspMenuDestroy(StateMenu);

  /* Trash images */
  if (Background) pspImageDestroy(Background);
  if (NoSaveIcon) pspImageDestroy(NoSaveIcon);
  if (Screen) pspImageDestroy(Screen);

  if (ScreenshotPath) free(ScreenshotPath);
}
