//Copyright Paul Reiche, Fred Ford. 1992-2002

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "gameopt.h"

#include "build.h"
#include "colors.h"
#include "controls.h"
#include "starmap.h"
#include "menustat.h"
#include "sis.h"
#include "units.h"
#include "gamestr.h"
#include "options.h"
#include "save.h"
#include "settings.h"
#include "setup.h"
#include "sounds.h"
#include "util.h"
#include "libs/graphics/gfx_common.h"

#include <ctype.h>

extern FRAME PlayFrame;

#define MAX_SAVED_GAMES 50
#define SUMMARY_X_OFFS 14
#define SUMMARY_SIDE_OFFS 7
#define SAVES_PER_PAGE 5

#define MAX_NAME_SIZE  SIS_NAME_SIZE

static COUNT lastUsedSlot;

static NamingCallback *namingCB;

void
ConfirmSaveLoad (STAMP *MsgStamp)
{
	RECT r, clip_r;
	TEXT t;

	SetContextFont (StarConFont);
	GetContextClipRect (&clip_r);

	t.baseline.x = clip_r.extent.width >> 1;
	t.baseline.y = (clip_r.extent.height >> 1) + 3;
	t.align = ALIGN_CENTER;
	t.CharCount = (COUNT)~0;
	if (MsgStamp)
		t.pStr = GAME_STRING (SAVEGAME_STRING_BASE + 0);
				// "Saving . . ."
	else
		t.pStr = GAME_STRING (SAVEGAME_STRING_BASE + 1);
				// "Loading . . ."
	TextRect (&t, &r, NULL);
	r.corner.x -= 4;
	r.corner.y -= 4;
	r.extent.width += 8;
	r.extent.height += 8;
	if (MsgStamp)
	{
		*MsgStamp = SaveContextFrame (&r);
	}
	DrawStarConBox (&r, 2,
			BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19),
			BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F),
			TRUE, BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
	SetContextForeGroundColor (
			BUILD_COLOR (MAKE_RGB15 (0x14, 0x14, 0x14), 0x0F));
	font_DrawText (&t);
}

enum
{
	SAVE_GAME = 0,
	LOAD_GAME,
	QUIT_GAME,
	SETTINGS,
	EXIT_GAME_MENU,
};

enum
{
	SOUND_ON_SETTING,
	SOUND_OFF_SETTING,
	MUSIC_ON_SETTING,
	MUSIC_OFF_SETTING,
	CYBORG_OFF_SETTING,
	CYBORG_NORMAL_SETTING,
	CYBORG_DOUBLE_SETTING,
	CYBORG_SUPER_SETTING,
	CHANGE_CAPTAIN_SETTING,
	CHANGE_SHIP_SETTING,
	EXIT_SETTINGS_MENU,
};

static void
FeedbackSetting (BYTE which_setting)
{
	UNICODE buf[128];
	const char *tmpstr;

	buf[0] = '\0';
	// pre-terminate buffer in case snprintf() overflows
	buf[sizeof (buf) - 1] = '\0';

	switch (which_setting)
	{
		case SOUND_ON_SETTING:
		case SOUND_OFF_SETTING:
			snprintf (buf, sizeof (buf) - 1, "%s %s",
					GAME_STRING (OPTION_STRING_BASE + 0),
					GLOBAL (glob_flags) & SOUND_DISABLED
					? GAME_STRING (OPTION_STRING_BASE + 3) :
					GAME_STRING (OPTION_STRING_BASE + 4));
			break;
		case MUSIC_ON_SETTING:
		case MUSIC_OFF_SETTING:
			snprintf (buf, sizeof (buf) - 1, "%s %s",
					GAME_STRING (OPTION_STRING_BASE + 1),
					GLOBAL (glob_flags) & MUSIC_DISABLED
					? GAME_STRING (OPTION_STRING_BASE + 3) :
					GAME_STRING (OPTION_STRING_BASE + 4));
			break;
		case CYBORG_OFF_SETTING:
		case CYBORG_NORMAL_SETTING:
		case CYBORG_DOUBLE_SETTING:
		case CYBORG_SUPER_SETTING:
			if (optWhichMenu == OPT_PC &&
					which_setting > CYBORG_NORMAL_SETTING)
			{
				if (which_setting == CYBORG_DOUBLE_SETTING)
					tmpstr = "+";
				else
					tmpstr = "++";
			}
			else
				tmpstr = "";
			snprintf (buf, sizeof (buf) - 1, "%s %s%s",
					GAME_STRING (OPTION_STRING_BASE + 2),
					!(GLOBAL (glob_flags) & CYBORG_ENABLED)
					? GAME_STRING (OPTION_STRING_BASE + 3) :
					GAME_STRING (OPTION_STRING_BASE + 4),
					tmpstr);
			break;
		case CHANGE_CAPTAIN_SETTING:
		case CHANGE_SHIP_SETTING:
			utf8StringCopy (buf, sizeof (buf),
					GAME_STRING (NAMING_STRING_BASE + 0));
			break;
	}

	DrawStatusMessage (buf);
}

#define DDSHS_NORMAL   0
#define DDSHS_EDIT     1
#define DDSHS_BLOCKCUR 2

static const RECT captainNameRect = {
	/* .corner = */ {
		/* .x = */ 3,
		/* .y = */ 10
	}, /* .extent = */ {
		/* .width = */ SHIP_NAME_WIDTH - 2,
		/* .height = */ SHIP_NAME_HEIGHT
	}
};
static const RECT shipNameRect = {
	/* .corner = */ {
		/* .x = */ 2,
		/* .y = */ 20
	}, /* .extent = */ {
		/* .width = */ SHIP_NAME_WIDTH,
		/* .height = */ SHIP_NAME_HEIGHT
	}
};


static BOOLEAN
DrawNameString (bool nameCaptain, UNICODE *Str, COUNT CursorPos,
		COUNT state)
{
	RECT r;
	TEXT lf;
	Color BackGround, ForeGround;
	FONT Font;

	{
		if (nameCaptain)
		{	// Naming the captain
			Font = TinyFont;
			r = captainNameRect;
			lf.baseline.x = r.corner.x + (r.extent.width >> 1) - 1;

			BackGround = BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09);
			ForeGround = BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B);
		}
		else
		{	// Naming the flagship
			Font = StarConFont;
			r = shipNameRect;
			lf.baseline.x = r.corner.x + (r.extent.width >> 1);

			BackGround = BUILD_COLOR (MAKE_RGB15 (0x0F, 0x00, 0x00), 0x2D);
			ForeGround = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x00), 0x7D);
		}

		lf.baseline.y = r.corner.y + r.extent.height - 1;
		lf.align = ALIGN_CENTER;
	}

	SetContext (StatusContext);
	SetContextFont (Font);
	lf.pStr = Str;
	lf.CharCount = (COUNT)~0;

	if (!(state & DDSHS_EDIT))
	{	// normal state
		if (nameCaptain)
			DrawCaptainsName ();
		else
			DrawFlagshipName (TRUE);
	}
	else
	{	// editing state
		COUNT i;
		RECT text_r;
		BYTE char_deltas[MAX_NAME_SIZE];
		BYTE *pchar_deltas;

		TextRect (&lf, &text_r, char_deltas);
		if ((text_r.extent.width + 2) >= r.extent.width)
		{	// the text does not fit the input box size and so
			// will not fit when displayed later
			// disallow the change
			return (FALSE);
		}

		PreUpdateFlashRect ();

		SetContextForeGroundColor (BackGround);
		DrawFilledRectangle (&r);

		pchar_deltas = char_deltas;
		for (i = CursorPos; i > 0; --i)
			text_r.corner.x += *pchar_deltas++;
		if (CursorPos < lf.CharCount) /* end of line */
			--text_r.corner.x;

		if (state & DDSHS_BLOCKCUR)
		{	// Use block cursor for keyboardless systems
			if (CursorPos == lf.CharCount)
			{	// cursor at end-line -- use insertion point
				text_r.extent.width = 1;
			}
			else if (CursorPos + 1 == lf.CharCount)
			{	// extra pixel for last char margin
				text_r.extent.width = (SIZE)*pchar_deltas + 2;
			}
			else
			{	// normal mid-line char
				text_r.extent.width = (SIZE)*pchar_deltas + 1;
			}
		}
		else
		{	// Insertion point cursor
			text_r.extent.width = 1;
		}

		text_r.corner.y = r.corner.y;
		text_r.extent.height = r.extent.height;
		SetContextForeGroundColor (BLACK_COLOR);
		DrawFilledRectangle (&text_r);

		SetContextForeGroundColor (ForeGround);
		font_DrawText (&lf);

		PostUpdateFlashRect ();
	}

	return (TRUE);
}

static BOOLEAN
OnNameChange (TEXTENTRY_STATE *pTES)
{
	bool nameCaptain = (bool) pTES->CbParam;
	COUNT hl = DDSHS_EDIT;

	if (pTES->JoystickMode)
		hl |= DDSHS_BLOCKCUR;

	return DrawNameString (nameCaptain, pTES->BaseStr, pTES->CursorPos, hl);
}

static void
NameCaptainOrShip (bool nameCaptain)
{
	UNICODE buf[MAX_NAME_SIZE] = "";
	TEXTENTRY_STATE tes;
	UNICODE *Setting;

	SetContext (StatusContext);
	SetFlashRect (nameCaptain ? &captainNameRect : &shipNameRect);

	DrawNameString (nameCaptain, buf, 0, DDSHS_EDIT);

	DrawStatusMessage (GAME_STRING (NAMING_STRING_BASE + 0));

	if (nameCaptain)
	{
		Setting = GLOBAL_SIS (CommanderName);
		tes.MaxSize = sizeof (GLOBAL_SIS (CommanderName));
	}
	else
	{
		Setting = GLOBAL_SIS (ShipName);
		tes.MaxSize = sizeof (GLOBAL_SIS (ShipName));
	}

	// text entry setup
	tes.Initialized = FALSE;
	tes.BaseStr = buf;
	tes.CursorPos = 0;
	tes.CbParam = (void*) nameCaptain;
	tes.ChangeCallback = OnNameChange;
	tes.FrameCallback = 0;

	if (DoTextEntry (&tes))
		utf8StringCopy (Setting, tes.MaxSize, buf);
	else
		utf8StringCopy (buf, sizeof (buf), Setting);

	SetFlashRect (SFR_MENU_3DO);

	DrawNameString (nameCaptain, buf, 0, DDSHS_NORMAL);

	if (namingCB)
		namingCB ();
}

static BOOLEAN
DrawSaveNameString (UNICODE *Str, COUNT CursorPos, COUNT state, COUNT gameIndex)
{
	RECT r;
	TEXT lf;
	Color BackGround, ForeGround;
	FONT Font;
	UNICODE fullStr[256], dateStr[80];

	DateToString (dateStr, sizeof dateStr, GLOBAL(GameClock.month_index),
			GLOBAL(GameClock.day_index), GLOBAL(GameClock.year_index));
	strncat (dateStr, ": ", sizeof(dateStr) - strlen(dateStr) -1);
	snprintf (fullStr, sizeof fullStr, "%s%s", dateStr, Str);

	SetContextForeGroundColor (BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33));
	r.extent.width = 15;
	if (MAX_SAVED_GAMES > 99)
		r.extent.width += 5;
	r.extent.height = 11;
	r.corner.x = 8;
	r.corner.y = (160 + ((gameIndex % SAVES_PER_PAGE) * 13));
	DrawRectangle (&r);

	r.extent.width = (204 - SAFE_X);
	r.corner.x = (30 + SAFE_X);
	DrawRectangle (&r);

	Font = TinyFont;
	lf.baseline.x = r.corner.x + 3;
	lf.baseline.y = r.corner.y + 8;

	BackGround = BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33);
	ForeGround = BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01);

	lf.align = ALIGN_LEFT;

	SetContextFont (Font);
	lf.pStr = fullStr;
	lf.CharCount = (COUNT)~0;

	if (!(state & DDSHS_EDIT))
	{
		TEXT t;

		SetContextForeGroundColor (BLACK_COLOR);
		DrawFilledRectangle (&r);

		t.baseline.x = r.corner.x + 3;
		t.baseline.y = r.corner.y + 8;
		t.align = ALIGN_LEFT;
		t.pStr = Str;
		t.CharCount = (COUNT)~0;
		SetContextForeGroundColor (CAPTAIN_NAME_TEXT_COLOR);
		font_DrawText (&lf);
	}
	else
	{	// editing state
		COUNT i, FullCursorPos;
		RECT text_r;
		BYTE char_deltas[256];
		BYTE *pchar_deltas;

		TextRect (&lf, &text_r, char_deltas);
		if ((text_r.extent.width + 2) >= r.extent.width)
		{	// the text does not fit the input box size and so
			// will not fit when displayed later
			// disallow the change
			return (FALSE);
		}

		PreUpdateFlashRect ();

		SetContextForeGroundColor (BackGround);
		DrawFilledRectangle (&r);

		pchar_deltas = char_deltas;

		FullCursorPos = CursorPos + strlen(dateStr) - 1;
		for (i = FullCursorPos; i > 0; --i)
			text_r.corner.x += *pchar_deltas++;

		if (FullCursorPos < lf.CharCount) /* end of line */
			--text_r.corner.x;

		if (state & DDSHS_BLOCKCUR)
		{	// Use block cursor for keyboardless systems
			if (FullCursorPos == lf.CharCount)
			{	// cursor at end-line -- use insertion point
				text_r.extent.width = 1;
			}
			else if (FullCursorPos + 1 == lf.CharCount)
			{	// extra pixel for last char margin
				text_r.extent.width = (SIZE)*pchar_deltas + 2;
			}
			else
			{	// normal mid-line char
				text_r.extent.width = (SIZE)*pchar_deltas + 1;
			}
		}
		else
		{	// Insertion point cursor
			text_r.extent.width = 1;
		}

		text_r.corner.y = r.corner.y;
		text_r.extent.height = r.extent.height;
		SetContextForeGroundColor (BLACK_COLOR);
		DrawFilledRectangle (&text_r);

		SetContextForeGroundColor (ForeGround);
		font_DrawText (&lf);
		PostUpdateFlashRect ();
	}

	return (TRUE);
}

static BOOLEAN
OnSaveNameChange (TEXTENTRY_STATE *pTES)
{
	COUNT hl = DDSHS_EDIT;
	COUNT *gameIndex = pTES->CbParam;

	if (pTES->JoystickMode)
		hl |= DDSHS_BLOCKCUR;

	return DrawSaveNameString (pTES->BaseStr, pTES->CursorPos, hl, *gameIndex);
}

static BOOLEAN
NameSaveGame (COUNT gameIndex, UNICODE *buf)
{
	TEXTENTRY_STATE tes;
	COUNT CursPos = strlen(buf);
	COUNT *gIndex = HMalloc (sizeof (COUNT));
	RECT r;
	*gIndex = gameIndex;

	DrawSaveNameString (buf, CursPos, DDSHS_EDIT, gameIndex);

	tes.MaxSize = SAVE_NAME_SIZE;

	// text entry setup
	tes.Initialized = FALSE;
	tes.BaseStr = buf;
	tes.CursorPos = CursPos;
	tes.CbParam = gIndex;
	tes.ChangeCallback = OnSaveNameChange;
	tes.FrameCallback = 0;
	r.extent.width = (204 - SAFE_X);
	r.extent.height = 11;
	r.corner.x = (30 + SAFE_X);
	r.corner.y = (160 + ((gameIndex % SAVES_PER_PAGE) * 13));
	SetFlashRect (&r);

	if (!DoTextEntry (&tes))
		buf[0] = 0;

	SetFlashRect(NULL);

	DrawSaveNameString (buf, CursPos, DDSHS_NORMAL, gameIndex);

	if (namingCB)
		namingCB ();

	SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);

	HFree (gIndex);

	SetFlashRect (NULL);

	if (tes.Success)
		return (TRUE);
	else
		return (FALSE);
}

void
SetNamingCallback (NamingCallback *callback)
{
	namingCB = callback;
}

static BOOLEAN
DoSettings (MENU_STATE *pMS)
{
	BYTE cur_speed;

	if (GLOBAL (CurrentActivity) & CHECK_ABORT)
		return FALSE;

	cur_speed = (GLOBAL (glob_flags) & COMBAT_SPEED_MASK) >> COMBAT_SPEED_SHIFT;

	if (PulsedInputState.menu[KEY_MENU_CANCEL]
			|| (PulsedInputState.menu[KEY_MENU_SELECT]
			&& pMS->CurState == EXIT_SETTINGS_MENU))
	{
		return FALSE;
	}
	else if (PulsedInputState.menu[KEY_MENU_SELECT])
	{
		switch (pMS->CurState)
		{
			case SOUND_ON_SETTING:
			case SOUND_OFF_SETTING:
				ToggleSoundEffect ();
				pMS->CurState ^= 1;
				DrawMenuStateStrings (PM_SOUND_ON, pMS->CurState);
				break;
			case MUSIC_ON_SETTING:
			case MUSIC_OFF_SETTING:
				ToggleMusic ();
				pMS->CurState ^= 1;
				DrawMenuStateStrings (PM_SOUND_ON, pMS->CurState);
				break;
			case CHANGE_CAPTAIN_SETTING:
			case CHANGE_SHIP_SETTING:
				NameCaptainOrShip (pMS->CurState == CHANGE_CAPTAIN_SETTING);
				break;
			default:
				if (cur_speed++ < NUM_COMBAT_SPEEDS - 1)
					GLOBAL (glob_flags) |= CYBORG_ENABLED;
				else
				{
					cur_speed = 0;
					GLOBAL (glob_flags) &= ~CYBORG_ENABLED;
				}
				GLOBAL (glob_flags) =
						((GLOBAL (glob_flags) & ~COMBAT_SPEED_MASK)
						| (cur_speed << COMBAT_SPEED_SHIFT));
				pMS->CurState = CYBORG_OFF_SETTING + cur_speed;
				DrawMenuStateStrings (PM_SOUND_ON, pMS->CurState);
		}

		FeedbackSetting (pMS->CurState);
	}
	else if (DoMenuChooser (pMS, PM_SOUND_ON))
		FeedbackSetting (pMS->CurState);

	return TRUE;
}

static void
SettingsMenu (void)
{
	MENU_STATE MenuState;

	memset (&MenuState, 0, sizeof MenuState);
	MenuState.CurState = SOUND_ON_SETTING;

	DrawMenuStateStrings (PM_SOUND_ON, MenuState.CurState);
	FeedbackSetting (MenuState.CurState);

	MenuState.InputFunc = DoSettings;
	DoInput (&MenuState, FALSE);

	DrawStatusMessage (NULL);
}

typedef struct
{
	SUMMARY_DESC summary[MAX_SAVED_GAMES];
	BOOLEAN saving;
			// TRUE when saving, FALSE when loading
	BOOLEAN success;
			// TRUE when load/save succeeded
	FRAME SummaryFrame;

} PICK_GAME_STATE;

static void
DrawBlankSavegameDisplay (PICK_GAME_STATE *pickState)
{
	STAMP s;

	s.origin.x = 0;
	s.origin.y = 0;
	s.frame = SetAbsFrameIndex (pickState->SummaryFrame,
			GetFrameCount (pickState->SummaryFrame) - 1);
	DrawStamp (&s);
}

static void
DrawSaveLoad (PICK_GAME_STATE *pickState)
{
	STAMP s;

	s.origin.x = SUMMARY_X_OFFS + 1;
	s.origin.y = 0;
	s.frame = SetAbsFrameIndex (pickState->SummaryFrame,
			GetFrameCount (pickState->SummaryFrame) - 2);
	if (pickState->saving)
		s.frame = DecFrameIndex (s.frame);
	DrawStamp (&s);
}

static void
DrawSavegameCargo (SIS_STATE *sisState)
{
	COUNT i;
	STAMP s;
	TEXT t;
	UNICODE buf[40];
	static const Color cargo_color[] =
	{
		BUILD_COLOR (MAKE_RGB15_INIT (0x02, 0x0E, 0x13), 0x00),
		BUILD_COLOR (MAKE_RGB15_INIT (0x19, 0x00, 0x00), 0x00),
		BUILD_COLOR (MAKE_RGB15_INIT (0x10, 0x10, 0x10), 0x00),
		BUILD_COLOR (MAKE_RGB15_INIT (0x03, 0x05, 0x1E), 0x00),
		BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x18, 0x00), 0x00),
		BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x1B, 0x00), 0x00),
		BUILD_COLOR (MAKE_RGB15_INIT (0x1E, 0x0D, 0x00), 0x00),
		BUILD_COLOR (MAKE_RGB15_INIT (0x14, 0x00, 0x14), 0x05),
		BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x19), 0x00),
	};
#define ELEMENT_ORG_Y      17
#define ELEMENT_SPACING_Y  12
#define ELEMENT_SPACING_X  36

	SetContext (SpaceContext);
	BatchGraphics ();
	SetContextFont (StarConFont);

	// setup element icons
	s.frame = SetAbsFrameIndex (MiscDataFrame,
			(NUM_SCANDOT_TRANSITIONS << 1) + 3);
	s.origin.x = 7 + SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS + 3;
	s.origin.y = ELEMENT_ORG_Y;
	// setup element amounts
	t.baseline.x = 33 + SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS + 3;
	t.baseline.y = ELEMENT_ORG_Y + 3;
	t.align = ALIGN_RIGHT;
	t.pStr = buf;

	// draw element icons and amounts
	for (i = 0; i < NUM_ELEMENT_CATEGORIES; ++i)
	{
		if (i == NUM_ELEMENT_CATEGORIES / 2)
		{
			s.origin.x += ELEMENT_SPACING_X;
			s.origin.y = ELEMENT_ORG_Y;
			t.baseline.x += ELEMENT_SPACING_X;
			t.baseline.y = ELEMENT_ORG_Y + 3;
		}
		// draw element icon
		DrawStamp (&s);
		s.frame = SetRelFrameIndex (s.frame, 5);
		s.origin.y += ELEMENT_SPACING_Y;
		// print element amount
		SetContextForeGroundColor (cargo_color[i]);
		snprintf (buf, sizeof buf, "%u", sisState->ElementAmounts[i]);
		t.CharCount = (COUNT)~0;
		font_DrawText (&t);
		t.baseline.y += ELEMENT_SPACING_Y;
	}

	// draw Bio icon
	s.origin.x = 24 + SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS;
	s.origin.y = 68;
	s.frame = SetAbsFrameIndex (s.frame, 68);
	DrawStamp (&s);
	// print Bio amount
	t.baseline.x = 50 + SUMMARY_X_OFFS;
	t.baseline.y = s.origin.y + 3;
	SetContextForeGroundColor (cargo_color[i]);
	snprintf (buf, sizeof buf, "%u", sisState->TotalBioMass);
	t.CharCount = (COUNT)~0;
	font_DrawText (&t);

	UnbatchGraphics ();
}

static void
DrawSavegameSummary (PICK_GAME_STATE *pickState, COUNT gameIndex)
{
	SUMMARY_DESC *pSD = pickState->summary + gameIndex;
	RECT r;
	STAMP s;

	BatchGraphics ();

	if (pSD->year_index == 0)
	{
		// Unused save slot, draw 'Empty Game' message.
		s.origin.x = 0;
		s.origin.y = 0;
		s.frame = SetAbsFrameIndex (pickState->SummaryFrame,
				GetFrameCount (pickState->SummaryFrame) - 4);
		DrawStamp (&s);
	}
	else
	{
		// Game slot used, draw information about save game.
		COUNT i;
		RECT OldRect;
		TEXT t;
		QUEUE player_q;
		CONTEXT OldContext;
		SIS_STATE SaveSS;
		UNICODE buf[256];
		POINT starPt;

		// Save the states because we will hack them
		SaveSS = GlobData.SIS_state;
		player_q = GLOBAL (built_ship_q);

		OldContext = SetContext (StatusContext);
		// Hack StatusContext so we can use standard SIS display funcs
		GetContextClipRect (&OldRect);
		r.corner.x = SIS_ORG_X + ((SIS_SCREEN_WIDTH - STATUS_WIDTH) >> 1) +
				SAFE_X - 16 + SUMMARY_X_OFFS;
//		r.corner.x = SIS_ORG_X + ((SIS_SCREEN_WIDTH - STATUS_WIDTH) >> 1);
		r.corner.y = SIS_ORG_Y;
		r.extent.width = STATUS_WIDTH;
		r.extent.height = STATUS_HEIGHT;
		SetContextClipRect (&r);

		// Hack the states so that we can use standard SIS display funcs
		GlobData.SIS_state = pSD->SS;
		InitQueue (&GLOBAL (built_ship_q),
				MAX_BUILT_SHIPS, sizeof (SHIP_FRAGMENT));
		for (i = 0; i < pSD->NumShips; ++i)
			CloneShipFragment (pSD->ShipList[i], &GLOBAL (built_ship_q), 0);
		DateToString (buf, sizeof buf,
				pSD->month_index, pSD->day_index, pSD->year_index),
		ClearSISRect (DRAW_SIS_DISPLAY);
		DrawStatusMessage (buf);
		UninitQueue (&GLOBAL (built_ship_q));

		SetContextClipRect (&OldRect);

		SetContext (SpaceContext);
		// draw devices
		s.origin.y = 13;
		for (i = 0; i < 4; ++i)
		{
			COUNT j;

			s.origin.x = 140 + SUMMARY_X_OFFS + SUMMARY_SIDE_OFFS;
			for (j = 0; j < 4; ++j)
			{
				COUNT devIndex = (i * 4) + j;
				if (devIndex < pSD->NumDevices)
				{
					s.frame = SetAbsFrameIndex (MiscDataFrame, 77
							+ pSD->DeviceList[devIndex]);
					DrawStamp (&s);
				}
				s.origin.x += 18;
			}
			s.origin.y += 18;
		}

		SetContextFont (StarConFont);
		t.baseline.x = 173 + SUMMARY_X_OFFS + SUMMARY_SIDE_OFFS;
		t.align = ALIGN_CENTER;
		t.CharCount = (COUNT)~0;
		t.pStr = buf;
		if (pSD->Flags & AFTER_BOMB_INSTALLED)
		{
			// draw the bomb and the escape pod
			s.origin.x = SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS + 6;
			s.origin.y = 0;
			s.frame = SetRelFrameIndex (pickState->SummaryFrame, 0);
			DrawStamp (&s);
			// draw RU "NO LIMIT" 
			s.origin.x = SUMMARY_X_OFFS + SUMMARY_SIDE_OFFS;
			s.frame = IncFrameIndex (s.frame);
			DrawStamp (&s);
		}
		else
		{
			DrawSavegameCargo (&pSD->SS);

			SetContext (RadarContext);
			// Hack RadarContext so we can use standard Lander display funcs
			GetContextClipRect (&OldRect);
			r.corner.x = SIS_ORG_X + 10 + SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS;
			r.corner.y = SIS_ORG_Y + 84;
			r.extent = OldRect.extent;
			SetContextClipRect (&r);
			// draw the lander with upgrades
			InitLander (pSD->Flags | OVERRIDE_LANDER_FLAGS);
			SetContextClipRect (&OldRect);
			SetContext (SpaceContext);

			snprintf (buf, sizeof buf, "%u", pSD->SS.ResUnits);
			t.baseline.y = 102;
			SetContextForeGroundColor (
					BUILD_COLOR (MAKE_RGB15 (0x10, 0x00, 0x10), 0x01));
			font_DrawText (&t);
			t.CharCount = (COUNT)~0;
		}
		t.baseline.y = 126;
		snprintf (buf, sizeof buf, "%u",
				MAKE_WORD (pSD->MCreditLo, pSD->MCreditHi));
		SetContextForeGroundColor (
				BUILD_COLOR (MAKE_RGB15 (0x10, 0x00, 0x10), 0x01));
		font_DrawText (&t);

		// print the location
		t.baseline.x = 6;
		t.baseline.y = 139 + 6;
		t.align = ALIGN_LEFT;
		t.pStr = buf;
		starPt.x = LOGX_TO_UNIVERSE (pSD->SS.log_x);
		starPt.y = LOGY_TO_UNIVERSE (pSD->SS.log_y);
		switch (pSD->Activity)
		{
			case IN_LAST_BATTLE:
			case IN_INTERPLANETARY:
			case IN_PLANET_ORBIT:
			case IN_STARBASE:
			{
				BYTE QuasiState;
				STAR_DESC *SDPtr;

				QuasiState = GET_GAME_STATE (ARILOU_SPACE_SIDE);
				SET_GAME_STATE (ARILOU_SPACE_SIDE, 0);
				SDPtr = FindStar (NULL, &starPt, 1, 1);
				SET_GAME_STATE (ARILOU_SPACE_SIDE, QuasiState);
				if (SDPtr)
				{
					GetClusterName (SDPtr, buf);
					starPt = SDPtr->star_pt;
					break;
				}
			}
			default:
				buf[0] = '\0';
				break;
			case IN_HYPERSPACE:
				utf8StringCopy (buf, sizeof (buf),
						GAME_STRING (NAVIGATION_STRING_BASE + 0));
				break;
			case IN_QUASISPACE:
				utf8StringCopy (buf, sizeof (buf),
						GAME_STRING (NAVIGATION_STRING_BASE + 1));
				break;
		}

		SetContextFont (TinyFont);
		SetContextForeGroundColor (
				BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33));
		t.CharCount = (COUNT)~0;
		font_DrawText (&t);
		t.align = ALIGN_CENTER;
		t.baseline.x = SIS_SCREEN_WIDTH - SIS_TITLE_BOX_WIDTH - 4
				+ (SIS_TITLE_WIDTH >> 1);
		switch (pSD->Activity)
		{
			case IN_STARBASE:
				utf8StringCopy (buf, sizeof (buf), // Starbase
						GAME_STRING (STARBASE_STRING_BASE));
				break;
			case IN_LAST_BATTLE:
				utf8StringCopy (buf, sizeof (buf), // Sa-Matra
						GAME_STRING (PLANET_NUMBER_BASE + 32));
				break;
			case IN_PLANET_ORBIT:
				utf8StringCopy (buf, sizeof (buf), pSD->SS.PlanetName);
				break;
			default:
				snprintf (buf, sizeof buf, "%03u.%01u : %03u.%01u",
						starPt.x / 10, starPt.x % 10,
						starPt.y / 10, starPt.y % 10);
		}
		t.CharCount = (COUNT)~0;
		font_DrawText (&t);

		SetContext (OldContext);

		// Restore the states because we hacked them
		GLOBAL (built_ship_q) = player_q;
		GlobData.SIS_state = SaveSS;
	}

	UnbatchGraphics ();
}

static void
DrawGameSelection (PICK_GAME_STATE *pickState, COUNT selSlot)
{
	RECT r;
	TEXT t;
	COUNT i;
	COUNT curSlot;
	UNICODE buf[256];
	UNICODE buf2[80];

	BatchGraphics ();

	SetContextFont (TinyFont);

	// Erase the selection menu
	r.extent.width = 240;
	r.extent.height = 65;
	r.corner.x = 1;
	r.corner.y = 160;
	SetContextForeGroundColor (BLACK_COLOR);
	DrawFilledRectangle (&r);

	t.CharCount = (COUNT)~0;
	t.pStr = buf;
	t.align = ALIGN_LEFT;

	// Draw savegame slots info
	curSlot = selSlot - (selSlot % SAVES_PER_PAGE);
	for (i = 0; i < SAVES_PER_PAGE && curSlot < MAX_SAVED_GAMES;
			++i, ++curSlot)
	{
		SUMMARY_DESC *desc = &pickState->summary[curSlot];

		SetContextForeGroundColor ((curSlot == selSlot) ?
				(BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33)):
				(BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)));
		r.extent.width = 15;
		if (MAX_SAVED_GAMES > 99)
			r.extent.width += 5;
		r.extent.height = 11;
		r.corner.x = 8;
		r.corner.y = 160 + (i * 13);
		DrawRectangle (&r);

		t.baseline.x = r.corner.x + 3;
		t.baseline.y = r.corner.y + 8;
		snprintf (buf, sizeof buf, (MAX_SAVED_GAMES > 99) ? "%03u" : "%02u",
				curSlot);
		font_DrawText (&t);

		r.extent.width = 204 - SAFE_X;
		r.corner.x = 30 + SAFE_X;
		DrawRectangle (&r);

		t.baseline.x = r.corner.x + 3;
		if (desc->year_index == 0)
		{
			utf8StringCopy (buf, sizeof buf,
					GAME_STRING (SAVEGAME_STRING_BASE + 3)); // "Empty Slot"
		}
		else
		{
			DateToString (buf2, sizeof buf2, desc->month_index,
					desc->day_index, desc->year_index);
			snprintf (buf, sizeof buf, "%s: %s", buf2, desc->SaveName[0] ? desc->SaveName : GAME_STRING (SAVEGAME_STRING_BASE + 4));
		}
		font_DrawText (&t);
	}

	UnbatchGraphics ();
}

static void
RedrawPickDisplay (PICK_GAME_STATE *pickState, COUNT selSlot)
{
	BatchGraphics ();
	DrawBlankSavegameDisplay (pickState);
	DrawSavegameSummary (pickState, selSlot);
	DrawGameSelection (pickState, selSlot);
	UnbatchGraphics ();
}

static void
LoadGameDescriptions (SUMMARY_DESC *pSD)
{
	COUNT i;

	for (i = 0; i < MAX_SAVED_GAMES; ++i, ++pSD)
	{
		if (!LoadGame (i, pSD))
			pSD->year_index = 0;
	}
}

static BOOLEAN
DoPickGame (MENU_STATE *pMS)
{
	PICK_GAME_STATE *pickState = pMS->privData;
	BYTE NewState;
	SUMMARY_DESC *pSD;
	DWORD TimeIn = GetTimeCounter ();

	if (GLOBAL (CurrentActivity) & CHECK_ABORT)
		return FALSE;

	if (PulsedInputState.menu[KEY_MENU_CANCEL])
	{
		pickState->success = FALSE;
		return FALSE;
	}
	else if (PulsedInputState.menu[KEY_MENU_SELECT])
	{
		pSD = &pickState->summary[pMS->CurState];
		if (pickState->saving || pSD->year_index)
		{	// valid slot
			PlayMenuSound (MENU_SOUND_SUCCESS);
			pickState->success = TRUE;
			return FALSE;
		}
		PlayMenuSound (MENU_SOUND_FAILURE);
	}
	else
	{
		NewState = pMS->CurState;
		if (PulsedInputState.menu[KEY_MENU_LEFT]
				|| PulsedInputState.menu[KEY_MENU_PAGE_UP])
		{
			if (NewState == 0)
				NewState = MAX_SAVED_GAMES - 1;
			else if ((NewState - SAVES_PER_PAGE) > 0)
				NewState -= SAVES_PER_PAGE;
			else 
				NewState = 0;
		}
		else if (PulsedInputState.menu[KEY_MENU_RIGHT]
				|| PulsedInputState.menu[KEY_MENU_PAGE_DOWN])
		{
			if (NewState == MAX_SAVED_GAMES - 1)
				NewState = 0;
			else if ((NewState + SAVES_PER_PAGE) < MAX_SAVED_GAMES - 1)
				NewState += SAVES_PER_PAGE;
			else 
				NewState = MAX_SAVED_GAMES - 1;
		}
		else if (PulsedInputState.menu[KEY_MENU_UP])
		{
			if (NewState == 0)
				NewState = MAX_SAVED_GAMES - 1;
			else
				NewState--;
		}
		else if (PulsedInputState.menu[KEY_MENU_DOWN])
		{
			if (NewState == MAX_SAVED_GAMES - 1)
				NewState = 0;
			else
				NewState++;
		}

		if (NewState != pMS->CurState)
		{
			pMS->CurState = NewState;
			SetContext (SpaceContext);
			RedrawPickDisplay (pickState, pMS->CurState);
		}

		SleepThreadUntil (TimeIn + ONE_SECOND / 30);
	}

	return TRUE;
}

static BOOLEAN
SaveLoadGame (PICK_GAME_STATE *pickState, COUNT gameIndex, BOOLEAN *canceled_by_user)
{
	SUMMARY_DESC *desc = pickState->summary + gameIndex;
	UNICODE nameBuf[256];
	STAMP saveStamp;
	BOOLEAN success;

	saveStamp.frame = NULL;

	if (pickState->saving)
	{
		// Initialize the save name with whatever name is there already
		// SAVE_NAME_SIZE is less than 256, so this is safe.
		strncpy(nameBuf, desc->SaveName, SAVE_NAME_SIZE);
		nameBuf[SAVE_NAME_SIZE] = 0;
		if (NameSaveGame (gameIndex, nameBuf))
		{
			PlayMenuSound (MENU_SOUND_SUCCESS);
			ConfirmSaveLoad (pickState->saving ? &saveStamp : NULL);
			success = SaveGame (gameIndex, desc, nameBuf);
		}
		else
		{
			success = FALSE;
			*canceled_by_user = TRUE;
		}
	}
	else
	{
		ConfirmSaveLoad (pickState->saving ? &saveStamp : NULL);
		success = LoadGame (gameIndex, NULL);
	}

	// TODO: the same should be done for both save and load if we also
	//   display a load problem message
	if (pickState->saving)
	{	// restore the screen under "SAVING..." message
		DrawStamp (&saveStamp);
	}

	DestroyDrawable (ReleaseDrawable (saveStamp.frame));

	return success;
}

static BOOLEAN
PickGame (BOOLEAN saving, BOOLEAN fromMainMenu)
{
	CONTEXT OldContext;
	MENU_STATE MenuState;
	PICK_GAME_STATE pickState;
	RECT DlgRect;
	STAMP DlgStamp;
	TimeCount TimeOut;
	InputFrameCallback *oldCallback;

	memset (&pickState, 0, sizeof pickState);
	pickState.saving = saving;
	pickState.SummaryFrame = SetAbsFrameIndex (PlayFrame, 39);

	memset (&MenuState, 0, sizeof MenuState);
	MenuState.privData = &pickState;
	// select the last used slot
	MenuState.CurState = lastUsedSlot;

	TimeOut = FadeMusic (0, ONE_SECOND / 2);

	// Deactivate any background drawing, like planet rotation
	oldCallback = SetInputCallback (NULL);

	LoadGameDescriptions (pickState.summary);

	OldContext = SetContext (SpaceContext);
	// Save the current state of the screen for later restoration
	DlgStamp = SaveContextFrame (NULL);
	GetContextClipRect (&DlgRect);

	SleepThreadUntil (TimeOut);
	PauseMusic ();
	StopSound ();
	FadeMusic (NORMAL_VOLUME, 0);

	// draw the current savegame and fade in
	SetTransitionSource (NULL);
	BatchGraphics ();

	SetContextBackGroundColor (BLACK_COLOR);
	ClearDrawable ();
	RedrawPickDisplay (&pickState, MenuState.CurState);
	DrawSaveLoad (&pickState);

	if (fromMainMenu)
	{
		UnbatchGraphics ();
		FadeScreen (FadeAllToColor, ONE_SECOND / 2);
	}
	else
	{
		RECT ctxRect;

		GetContextClipRect (&ctxRect);
		ScreenTransition (3, &ctxRect);
		UnbatchGraphics ();
	}

	SetMenuSounds (MENU_SOUND_ARROWS | MENU_SOUND_PAGEUP | MENU_SOUND_PAGEDOWN,
			0);
	MenuState.InputFunc = DoPickGame;

	// Save/load retry loop
	while (1)
	{
		BOOLEAN canceled_by_user = FALSE;

		pickState.success = FALSE;
		DoInput (&MenuState, TRUE);
		if (!pickState.success)
			break; // canceled

		lastUsedSlot = MenuState.CurState;

		if (SaveLoadGame (&pickState, MenuState.CurState, &canceled_by_user))
			break; // all good

		// something broke
		if (saving && !canceled_by_user)
			SaveProblem ();
		// TODO: Shouldn't we have a Problem() equivalent for Load too?

		// reload and redraw everything
		LoadGameDescriptions (pickState.summary);
		RedrawPickDisplay (&pickState, MenuState.CurState);
	}

	SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);

	if (pickState.success && !saving)
	{	// Load succeeded, signal up the chain
		GLOBAL (CurrentActivity) |= CHECK_LOAD;
	}

	if (!(GLOBAL (CurrentActivity) & CHECK_ABORT) &&
			(saving || (!pickState.success && !fromMainMenu)))
	{	// Restore previous screen
		SetTransitionSource (&DlgRect);
		BatchGraphics ();
		DrawStamp (&DlgStamp);
		ScreenTransition (3, &DlgRect);
		UnbatchGraphics ();
	}

	DestroyDrawable (ReleaseDrawable (DlgStamp.frame));

	SetContext (OldContext);

	ResumeMusic ();

	// Reactivate any background drawing, like planet rotation
	SetInputCallback (oldCallback);

	return pickState.success;
}

static BOOLEAN
DoGameOptions (MENU_STATE *pMS)
{
	if (GLOBAL (CurrentActivity) & CHECK_ABORT)
		return FALSE;

	if (PulsedInputState.menu[KEY_MENU_CANCEL]
			|| (PulsedInputState.menu[KEY_MENU_SELECT]
			&& pMS->CurState == EXIT_GAME_MENU))
	{
		return FALSE;
	}
	else if (PulsedInputState.menu[KEY_MENU_SELECT])
	{
		switch (pMS->CurState)
		{
			case SAVE_GAME:
			case LOAD_GAME:
				SetFlashRect (NULL);
				if (PickGame (pMS->CurState == SAVE_GAME, FALSE))
					return FALSE;
				SetFlashRect (SFR_MENU_3DO);
				break;
			case QUIT_GAME:
				if (ConfirmExit ())
					return FALSE;
				break;
			case SETTINGS:
				SettingsMenu ();
				DrawMenuStateStrings (PM_SAVE_GAME, pMS->CurState);
				break;
		}
	}
	else
		DoMenuChooser (pMS, PM_SAVE_GAME);

	return TRUE;
}

// Returns TRUE when the owner menu should continue
BOOLEAN
GameOptions (void)
{
	MENU_STATE MenuState;

	memset (&MenuState, 0, sizeof MenuState);

	if (LastActivity == CHECK_LOAD)
	{	// Selected LOAD from main menu
		BOOLEAN success;

		DrawMenuStateStrings (PM_SAVE_GAME, LOAD_GAME);
		success	= PickGame (FALSE, TRUE);
		if (!success)
		{	// Selected LOAD from main menu, and canceled
			GLOBAL (CurrentActivity) |= CHECK_ABORT;
		}
		return FALSE;
	}

	MenuState.CurState = SAVE_GAME;
	DrawMenuStateStrings (PM_SAVE_GAME, MenuState.CurState);

	SetFlashRect (SFR_MENU_3DO);

	SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
	MenuState.InputFunc = DoGameOptions;
	DoInput (&MenuState, TRUE);

	SetFlashRect (NULL);

	return !(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD));
}
