/*
 *  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.
 */

#if defined(DEBUG) || defined(USE_DEBUG_KEY)

#include "uqmdebug.h"

#include "build.h"
#include "colors.h"
#include "controls.h"
#include "clock.h"
#include "starmap.h"
#include "element.h"
#include "sis.h"
#include "status.h"
#include "gamestr.h"
#include "gameev.h"
#include "gendef.h"
#include "globdata.h"
#include "planets/lifeform.h"
#include "planets/scan.h"
#include "races.h"
#include "setup.h"
#include "state.h"
#include "libs/mathlib.h"
#include "lua/luadebug.h"

#include <stdio.h>
#include <errno.h>


static void dumpEventCallback (const EVENT *eventPtr, void *arg);

static void starRecurse (STAR_DESC *star, void *arg);
static void planetRecurse (STAR_DESC *star, SOLARSYS_STATE *system,
		PLANET_DESC *planet, void *arg);
static void moonRecurse (STAR_DESC *star, SOLARSYS_STATE *system,
		PLANET_DESC *planet, PLANET_DESC *moon, void *arg);

static void dumpSystemCallback (const STAR_DESC *star,
		const SOLARSYS_STATE *system, void *arg);
static void dumpPlanetCallback (const PLANET_DESC *planet, void *arg);
static void dumpMoonCallback (const PLANET_DESC *moon, void *arg);
static void dumpWorld (FILE *out, const PLANET_DESC *world);

typedef struct TallyResourcesArg TallyResourcesArg;
static void tallySystemPreCallback (const STAR_DESC *star, const
		SOLARSYS_STATE *system, void *arg);
static void tallySystemPostCallback (const STAR_DESC *star, const
		SOLARSYS_STATE *system, void *arg);
static void tallyPlanetCallback (const PLANET_DESC *planet, void *arg);
static void tallyMoonCallback (const PLANET_DESC *moon, void *arg);
static void tallyResourcesWorld (TallyResourcesArg *arg,
		const PLANET_DESC *world);

static void dumpPlanetTypeCallback (int index, const PlanetFrame *planet,
		void *arg);


BOOLEAN instantMove = FALSE;
BOOLEAN disableInteractivity = FALSE;
void (* volatile debugHook) (void) = NULL;


// Must be called on the Starcon2Main thread.
// This function is called synchronously wrt the game logic thread.
void
debugKeyPressedSynchronous (void)
{
	// State modifying:
	equipShip ();
	giveDevices ();

	// Give the player the ships you can't ally with under normal
	// conditions.
	clearEscorts ();
	AddEscortShips (ARILOU_SHIP, 1);
	AddEscortShips (PKUNK_SHIP, 1);
	AddEscortShips (VUX_SHIP, 1);
	AddEscortShips (YEHAT_SHIP, 1);
	AddEscortShips (MELNORME_SHIP, 1);
	AddEscortShips (DRUUGE_SHIP, 1);
	AddEscortShips (ILWRATH_SHIP, 1);
	AddEscortShips (MYCON_SHIP, 1);
	AddEscortShips (SLYLANDRO_SHIP, 1);
	AddEscortShips (UMGAH_SHIP, 1);
	AddEscortShips (URQUAN_SHIP, 1);
	AddEscortShips (BLACK_URQUAN_SHIP, 1);

	resetCrewBattle ();
	resetEnergyBattle ();
	instantMove = !instantMove;
	showSpheres ();
	activateAllShips ();
//	forwardToNextEvent (TRUE);
//	SET_GAME_STATE (MELNORME_CREDIT1, 100);
//	GLOBAL_SIS (ResUnits) = 100000;

	// Informational:
//	dumpEvents (stderr);

	// Graphical and textual:
//	debugContexts();
}

// Can be called on any thread, but usually on main()
// This function is called asynchronously wrt the game logic thread,
// which means locking applies. Use carefully.
// TODO: Once game logic thread is purged of graphics and clock locks,
//   this function may not call graphics and game clock functions at all.
void
debugKeyPressed (void)
{
	// Tests
//	Scale_PerfTest ();

	// Informational:
//	dumpStrings (stdout);
//	dumpPlanetTypes(stderr);
//	debugHook = dumpUniverseToFile;
			// This will cause dumpUniverseToFile to be called from the
			// Starcon2Main loop. Calling it from here would give threading
			// problems.
//	debugHook = tallyResourcesToFile;
			// This will cause tallyResourcesToFile to be called from the
			// Starcon2Main loop. Calling it from here would give threading
			// problems.

	// Interactive:
//	uio_debugInteractive(stdin, stdout, stderr);
//	luaUqm_debug_run();
}

////////////////////////////////////////////////////////////////////////////

// Fast forwards to the next event.
// If skipHEE is set, HYPERSPACE_ENCOUNTER_EVENTs are skipped.
// Must be called from the Starcon2Main thread.
// TODO: LockGameClock may be removed since it is only
//   supposed to be called synchronously wrt the game logic thread.
void
forwardToNextEvent (BOOLEAN skipHEE)
{
	HEVENT hEvent;
	EVENT *EventPtr;
	COUNT year, month, day;
			// time of next event
	BOOLEAN done;

	if (!GameClockRunning ())
		return;

	LockGameClock ();

	done = !skipHEE;
	do {
		hEvent = GetHeadEvent ();
		if (hEvent == 0)
			return;
		LockEvent (hEvent, &EventPtr);
		if (EventPtr->func_index != HYPERSPACE_ENCOUNTER_EVENT)
			done = TRUE;
		year = EventPtr->year_index;
		month = EventPtr->month_index;
		day = EventPtr->day_index;
		UnlockEvent (hEvent);

		for (;;) {
			if (GLOBAL (GameClock.year_index) > year ||
					(GLOBAL (GameClock.year_index) == year &&
					(GLOBAL (GameClock.month_index) > month ||
					(GLOBAL (GameClock.month_index) == month &&
					GLOBAL (GameClock.day_index) >= day))))
				break;

			MoveGameClockDays (1);
		}
	} while (!done);

	UnlockGameClock ();
}

const char *
eventName (BYTE func_index)
{
	switch (func_index) {
	case ARILOU_ENTRANCE_EVENT:
		return "ARILOU_ENTRANCE_EVENT";
	case ARILOU_EXIT_EVENT:
		return "ARILOU_EXIT_EVENT";
	case HYPERSPACE_ENCOUNTER_EVENT:
		return "HYPERSPACE_ENCOUNTER_EVENT";
	case KOHR_AH_VICTORIOUS_EVENT:
		return "KOHR_AH_VICTORIOUS_EVENT";
	case ADVANCE_PKUNK_MISSION:
		return "ADVANCE_PKUNK_MISSION";
	case ADVANCE_THRADD_MISSION:
		return "ADVANCE_THRADD_MISSION";
	case ZOQFOT_DISTRESS_EVENT:
		return "ZOQFOT_DISTRESS";
	case ZOQFOT_DEATH_EVENT:
		return "ZOQFOT_DEATH_EVENT";
	case SHOFIXTI_RETURN_EVENT:
		return "SHOFIXTI_RETURN_EVENT";
	case ADVANCE_UTWIG_SUPOX_MISSION:
		return "ADVANCE_UTWIG_SUPOX_MISSION";
	case KOHR_AH_GENOCIDE_EVENT:
		return "KOHR_AH_GENOCIDE_EVENT";
	case SPATHI_SHIELD_EVENT:
		return "SPATHI_SHIELD_EVENT";
	case ADVANCE_ILWRATH_MISSION:
		return "ADVANCE_ILWRATH_MISSION";
	case ADVANCE_MYCON_MISSION:
		return "ADVANCE_MYCON_MISSION";
	case ARILOU_UMGAH_CHECK:
		return "ARILOU_UMGAH_CHECK";
	case YEHAT_REBEL_EVENT:
		return "YEHAT_REBEL_EVENT";
	case SLYLANDRO_RAMP_UP:
		return "SLYLANDRO_RAMP_UP";
	case SLYLANDRO_RAMP_DOWN:
		return "SLYLANDRO_RAMP_DOWN";
	default:
		// Should not happen
		return "???";
	}
}

static void
dumpEventCallback (const EVENT *eventPtr, void *arg)
{
	FILE *out = (FILE *) arg;
	dumpEvent (out, eventPtr);
}

void
dumpEvent (FILE *out, const EVENT *eventPtr)
{
	fprintf (out, "%4u/%02u/%02u: %s\n",
			eventPtr->year_index,
			eventPtr->month_index,
			eventPtr->day_index,
			eventName (eventPtr->func_index));
}

void
dumpEvents (FILE *out)
{
	LockGameClock ();
	ForAllEvents (dumpEventCallback, out);
	UnlockGameClock ();
}

////////////////////////////////////////////////////////////////////////////

// NB: Ship maximum speed and turning rate aren't updated in
// HyperSpace/QuasiSpace or in melee.
void
equipShip (void)
{
	int i;

	// Don't do anything unless in the full game.
	if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE)
		return;

	// Thrusters:
	for (i = 0; i < NUM_DRIVE_SLOTS; i++)
		GLOBAL_SIS (DriveSlots[i]) = FUSION_THRUSTER;

	// Turning jets:
	for (i = 0; i < NUM_JET_SLOTS; i++)
		GLOBAL_SIS (JetSlots[i]) = TURNING_JETS;

	// Shields:
	SET_GAME_STATE (LANDER_SHIELDS,
			(1 << EARTHQUAKE_DISASTER) |
			(1 << BIOLOGICAL_DISASTER) |
			(1 << LIGHTNING_DISASTER) |
			(1 << LAVASPOT_DISASTER));
	// Lander upgrades:
	SET_GAME_STATE (IMPROVED_LANDER_SPEED, 1);
	SET_GAME_STATE (IMPROVED_LANDER_CARGO, 1);
	SET_GAME_STATE (IMPROVED_LANDER_SHOT, 1);

	// Modules:
	if (GET_GAME_STATE (CHMMR_BOMB_STATE) < 2)
	{
		// The Precursor bomb has not been installed.
		// This is the original TFB testing layout.
		i = 0;
		GLOBAL_SIS (ModuleSlots[i++]) = HIGHEFF_FUELSYS;
		GLOBAL_SIS (ModuleSlots[i++]) = HIGHEFF_FUELSYS;
		GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
		GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
		GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
		GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
		GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
		GLOBAL_SIS (ModuleSlots[i++]) = STORAGE_BAY;
		GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
		GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
		GLOBAL_SIS (ModuleSlots[i++]) = DYNAMO_UNIT;
		GLOBAL_SIS (ModuleSlots[i++]) = TRACKING_SYSTEM;
		GLOBAL_SIS (ModuleSlots[i++]) = TRACKING_SYSTEM;
		GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
		GLOBAL_SIS (ModuleSlots[i++]) = CANNON_WEAPON;
		GLOBAL_SIS (ModuleSlots[i++]) = CANNON_WEAPON;
		
		// Landers:
		GLOBAL_SIS (NumLanders) = MAX_LANDERS;
	}
	else
	{
		// The Precursor bomb has been installed.
		i = NUM_BOMB_MODULES;
		GLOBAL_SIS (ModuleSlots[i++]) = HIGHEFF_FUELSYS;
		GLOBAL_SIS (ModuleSlots[i++]) = CREW_POD;
		GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
		GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
		GLOBAL_SIS (ModuleSlots[i++]) = CANNON_WEAPON;
		GLOBAL_SIS (ModuleSlots[i++]) = SHIVA_FURNACE;
	}

	assert (i <= NUM_MODULE_SLOTS);

	// Fill the fuel and crew compartments to the maximum.
	GLOBAL_SIS (FuelOnBoard) = FUEL_RESERVE;
	GLOBAL_SIS (CrewEnlisted) = 0;
	for (i = 0; i < NUM_MODULE_SLOTS; i++)
	{
		switch (GLOBAL_SIS (ModuleSlots[i])) {
			case CREW_POD:
				GLOBAL_SIS (CrewEnlisted) += CREW_POD_CAPACITY;
				break;
			case FUEL_TANK:
				GLOBAL_SIS (FuelOnBoard) += FUEL_TANK_CAPACITY;
				break;
			case HIGHEFF_FUELSYS:
				GLOBAL_SIS (FuelOnBoard) += HEFUEL_TANK_CAPACITY;
				break;
		}
	}

	// Update the maximum speed and turning rate when in interplanetary.
	if (pSolarSysState != NULL)
	{
		// Thrusters:
		pSolarSysState->max_ship_speed = 5 * IP_SHIP_THRUST_INCREMENT;
		for (i = 0; i < NUM_DRIVE_SLOTS; i++)
			if (GLOBAL_SIS (DriveSlots[i] == FUSION_THRUSTER))
				pSolarSysState->max_ship_speed += IP_SHIP_THRUST_INCREMENT;

		// Turning jets:
		pSolarSysState->turn_wait = IP_SHIP_TURN_WAIT;
		for (i = 0; i < NUM_JET_SLOTS; i++)
			if (GLOBAL_SIS (JetSlots[i]) == TURNING_JETS)
				pSolarSysState->turn_wait -= IP_SHIP_TURN_DECREMENT;
	}

	// Make sure everything is redrawn:
	if (inHQSpace () ||
			LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY)
	{
		DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, UNDEFINED_DELTA);
	}
}

////////////////////////////////////////////////////////////////////////////

void
giveDevices (void) {
	SET_GAME_STATE (ROSY_SPHERE_ON_SHIP, 1);
	SET_GAME_STATE (ARTIFACT_2_ON_SHIP, 1);
	SET_GAME_STATE (ARTIFACT_3_ON_SHIP, 1);
	SET_GAME_STATE (SUN_DEVICE_ON_SHIP, 1);
	SET_GAME_STATE (UTWIG_BOMB_ON_SHIP, 1);
	SET_GAME_STATE (ULTRON_CONDITION, 1);
	//SET_GAME_STATE (ULTRON_CONDITION, 2);
	//SET_GAME_STATE (ULTRON_CONDITION, 3);
	//SET_GAME_STATE (ULTRON_CONDITION, 4);
	SET_GAME_STATE (MAIDENS_ON_SHIP, 1);
	SET_GAME_STATE (TALKING_PET_ON_SHIP, 1);
	SET_GAME_STATE (AQUA_HELIX_ON_SHIP, 1);
	SET_GAME_STATE (CLEAR_SPINDLE_ON_SHIP, 1);
	SET_GAME_STATE (UMGAH_BROADCASTERS_ON_SHIP, 1);
	SET_GAME_STATE (TAALO_PROTECTOR_ON_SHIP, 1);
	SET_GAME_STATE (EGG_CASE0_ON_SHIP, 1);
	SET_GAME_STATE (EGG_CASE1_ON_SHIP, 1);
	SET_GAME_STATE (EGG_CASE2_ON_SHIP, 1);
	SET_GAME_STATE (SYREEN_SHUTTLE_ON_SHIP, 1);
	SET_GAME_STATE (VUX_BEAST_ON_SHIP, 1);
	SET_GAME_STATE (PORTAL_SPAWNER_ON_SHIP, 1);
	SET_GAME_STATE (PORTAL_KEY_ON_SHIP, 1);
	SET_GAME_STATE (BURV_BROADCASTERS_ON_SHIP, 1);
	SET_GAME_STATE (MOONBASE_ON_SHIP, 1);
	
	// Not strictly a device (although it originally was one).
	SET_GAME_STATE (DESTRUCT_CODE_ON_SHIP, 1);
}

////////////////////////////////////////////////////////////////////////////

void
clearEscorts (void)
{
	HSHIPFRAG hStarShip, hNextShip;

	for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q));
			hStarShip; hStarShip = hNextShip)
	{
		SHIP_FRAGMENT *StarShipPtr;

		StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
		hNextShip = _GetSuccLink (StarShipPtr);
		UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);

		RemoveQueue (&GLOBAL (built_ship_q), hStarShip);
		FreeShipFrag (&GLOBAL (built_ship_q), hStarShip);
	}

	DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, UNDEFINED_DELTA);
}

////////////////////////////////////////////////////////////////////////////

#if 0
// Not needed anymore, but could be useful in the future.

// Find the HELEMENT belonging to the Flagship.
static HELEMENT
findFlagshipElement (void)
{
	HELEMENT hElement, hNextElement;
	ELEMENT *ElementPtr;

	// Find the ship element.
	for (hElement = GetTailElement (); hElement != 0;
			hElement = hNextElement)
	{
		LockElement (hElement, &ElementPtr);

		if ((ElementPtr->state_flags & PLAYER_SHIP) != 0)
		{
			UnlockElement (hElement);
			return hElement;
		}

		hNextElement = GetPredElement (ElementPtr);
		UnlockElement (hElement);
	}
	return 0;
}
#endif

// Move the Flagship to the destination of the autopilot.
// Should only be called from HyperSpace/QuasiSpace.
// It can be called from debugHook directly after entering HS/QS though.
void
doInstantMove (void)
{
	// Move to the new location:
	if ((GLOBAL (autopilot)).x == ~0 || (GLOBAL (autopilot)).y == ~0)
	{
		// If no destination has been selected, use the current location
		// as the destination.
		(GLOBAL (autopilot)).x = LOGX_TO_UNIVERSE(GLOBAL_SIS (log_x));
		(GLOBAL (autopilot)).y = LOGY_TO_UNIVERSE(GLOBAL_SIS (log_y));
	}
	else
	{
		// A new destination has been selected.
		GLOBAL_SIS (log_x) = UNIVERSE_TO_LOGX((GLOBAL (autopilot)).x);
		GLOBAL_SIS (log_y) = UNIVERSE_TO_LOGY((GLOBAL (autopilot)).y);
	}

	// Check for a solar systems at the destination.
	if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
	{
		// If there's a solar system at the destination, enter it.
		CurStarDescPtr = FindStar (0, &(GLOBAL (autopilot)), 0, 0);
		if (CurStarDescPtr)
		{
			// Leave HyperSpace/QuasiSpace if we're there:
			SET_GAME_STATE (USED_BROADCASTER, 0);
			GLOBAL (CurrentActivity) &= ~IN_BATTLE;

			// Enter IP:
			GLOBAL (ShipFacing) = 0;
			GLOBAL (ip_planet) = 0;
			GLOBAL (in_orbit) = 0;
					// This causes the ship position in IP to be reset.
			GLOBAL (CurrentActivity) |= START_INTERPLANETARY;
		}
	}

	// Turn off the autopilot:
	(GLOBAL (autopilot)).x = ~0;
	(GLOBAL (autopilot)).y = ~0;
}

////////////////////////////////////////////////////////////////////////////

void
showSpheres (void)
{
	HFLEETINFO hStarShip, hNextShip;
	
	for (hStarShip = GetHeadLink (&GLOBAL (avail_race_q));
			hStarShip != NULL; hStarShip = hNextShip)
	{
		FLEET_INFO *FleetPtr;

		FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
		hNextShip = _GetSuccLink (FleetPtr);

		if ((FleetPtr->actual_strength != INFINITE_RADIUS) &&
				(FleetPtr->known_strength != FleetPtr->actual_strength))
		{
			FleetPtr->known_strength = FleetPtr->actual_strength;
			FleetPtr->known_loc = FleetPtr->loc;
		}

		UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
	}
}

////////////////////////////////////////////////////////////////////////////

void
activateAllShips (void)
{
	HFLEETINFO hStarShip, hNextShip;
	
	for (hStarShip = GetHeadLink (&GLOBAL (avail_race_q));
			hStarShip != NULL; hStarShip = hNextShip)
	{
		FLEET_INFO *FleetPtr;

		FleetPtr = LockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
		hNextShip = _GetSuccLink (FleetPtr);

		if (FleetPtr->icons != NULL)
				// Skip the Ur-Quan probe.
		{
			FleetPtr->allied_state = GOOD_GUY;
		}

		UnlockFleetInfo (&GLOBAL (avail_race_q), hStarShip);
	}
}

////////////////////////////////////////////////////////////////////////////

void
forAllStars (void (*callback) (STAR_DESC *, void *), void *arg)
{
	int i;
	extern STAR_DESC starmap_array[];

	for (i = 0; i < NUM_SOLAR_SYSTEMS; i++)
		callback (&starmap_array[i], arg);
}

void
forAllPlanets (STAR_DESC *star, SOLARSYS_STATE *system, void (*callback) (
		STAR_DESC *, SOLARSYS_STATE *, PLANET_DESC *, void *), void *arg)
{
	COUNT i;

	assert(CurStarDescPtr == star);
	assert(pSolarSysState == system);

	for (i = 0; i < system->SunDesc[0].NumPlanets; i++)
		callback (star, system, &system->PlanetDesc[i], arg);
}

void
forAllMoons (STAR_DESC *star, SOLARSYS_STATE *system, PLANET_DESC *planet,
		void (*callback) (STAR_DESC *, SOLARSYS_STATE *, PLANET_DESC *,
		PLANET_DESC *, void *), void *arg)
{
	COUNT i;

	assert(pSolarSysState == system);

	for (i = 0; i < planet->NumPlanets; i++)
		callback (star, system, planet, &system->MoonDesc[i], arg);
}

////////////////////////////////////////////////////////////////////////////

// Must be called from the Starcon2Main thread.
// TODO: LockGameClock may be removed
void
UniverseRecurse (UniverseRecurseArg *universeRecurseArg)
{
	ACTIVITY savedActivity;
	
	if (universeRecurseArg->systemFuncPre == NULL
			&& universeRecurseArg->systemFuncPost == NULL
			&& universeRecurseArg->planetFuncPre == NULL
			&& universeRecurseArg->planetFuncPost == NULL
			&& universeRecurseArg->moonFunc == NULL)
		return;
	
	LockGameClock ();
	//TFB_DEBUG_HALT = 1;
	savedActivity = GLOBAL (CurrentActivity);
	disableInteractivity = TRUE;

	forAllStars (starRecurse, (void *) universeRecurseArg);
	
	disableInteractivity = FALSE;
	GLOBAL (CurrentActivity) = savedActivity;
	UnlockGameClock ();
}

static void
starRecurse (STAR_DESC *star, void *arg)
{
	UniverseRecurseArg *universeRecurseArg = (UniverseRecurseArg *) arg;

	SOLARSYS_STATE SolarSysState;
	SOLARSYS_STATE *oldPSolarSysState = pSolarSysState;
	STAR_DESC *oldStarDescPtr = CurStarDescPtr;
	CurStarDescPtr = star;

	RandomContext_SeedRandom (SysGenRNG, GetRandomSeedForStar (star));

	memset (&SolarSysState, 0, sizeof (SolarSysState));
	SolarSysState.SunDesc[0].pPrevDesc = 0;
	SolarSysState.SunDesc[0].rand_seed = RandomContext_Random (SysGenRNG);
	SolarSysState.SunDesc[0].data_index = STAR_TYPE (star->Type);
	SolarSysState.SunDesc[0].location.x = 0;
	SolarSysState.SunDesc[0].location.y = 0;
	//SolarSysState.SunDesc[0].radius = MIN_ZOOM_RADIUS;
	SolarSysState.genFuncs = getGenerateFunctions (star->Index);

	pSolarSysState = &SolarSysState;
	(*SolarSysState.genFuncs->generatePlanets) (&SolarSysState);

	if (universeRecurseArg->systemFuncPre != NULL)
	{
		(*universeRecurseArg->systemFuncPre) (
				star, &SolarSysState, universeRecurseArg->arg);
	}
	
	if (universeRecurseArg->planetFuncPre != NULL
			|| universeRecurseArg->planetFuncPost != NULL
			|| universeRecurseArg->moonFunc != NULL)
	{
		forAllPlanets (star, &SolarSysState, planetRecurse,
				(void *) universeRecurseArg);
	}

	if (universeRecurseArg->systemFuncPost != NULL)
	{
		(*universeRecurseArg->systemFuncPost) (
				star, &SolarSysState, universeRecurseArg->arg);
	}
	
	pSolarSysState = oldPSolarSysState;
	CurStarDescPtr = oldStarDescPtr;
}

static void
planetRecurse (STAR_DESC *star, SOLARSYS_STATE *system, PLANET_DESC *planet,
		void *arg)
{
	UniverseRecurseArg *universeRecurseArg = (UniverseRecurseArg *) arg;
	
	assert(CurStarDescPtr == star);
	assert(pSolarSysState == system);

	planet->pPrevDesc = &system->SunDesc[0];

	if (universeRecurseArg->planetFuncPre != NULL)
	{
		system->pOrbitalDesc = planet;
		DoPlanetaryAnalysis (&system->SysInfo, planet);
				// When GenerateDefaultFunctions is used as genFuncs,
				// generateOrbital will also call DoPlanetaryAnalysis,
				// but with other GenerateFunctions this is not guaranteed.
		(*system->genFuncs->generateOrbital) (system, planet);
		(*universeRecurseArg->planetFuncPre) (
				planet, universeRecurseArg->arg);
	}

	if (universeRecurseArg->moonFunc != NULL)
	{
		RandomContext_SeedRandom (SysGenRNG, planet->rand_seed);
		
		(*system->genFuncs->generateMoons) (system, planet);

		forAllMoons (star, system, planet, moonRecurse,
				(void *) universeRecurseArg);
	}
	
	if (universeRecurseArg->planetFuncPost != NULL)
	{
		system->pOrbitalDesc = planet;
		DoPlanetaryAnalysis (&system->SysInfo, planet);
				// When GenerateDefaultFunctions is used as genFuncs,
				// generateOrbital will also call DoPlanetaryAnalysis,
				// but with other GenerateFunctions this is not guaranteed.
		(*system->genFuncs->generateOrbital) (system, planet);
		(*universeRecurseArg->planetFuncPost) (
				planet, universeRecurseArg->arg);
	}
}

static void
moonRecurse (STAR_DESC *star, SOLARSYS_STATE *system, PLANET_DESC *planet,
		PLANET_DESC *moon, void *arg)
{
	UniverseRecurseArg *universeRecurseArg = (UniverseRecurseArg *) arg;
	
	assert(CurStarDescPtr == star);
	assert(pSolarSysState == system);
	
	moon->pPrevDesc = planet;

	if (universeRecurseArg->moonFunc != NULL)
	{
		system->pOrbitalDesc = moon;
		if (moon->data_index != HIERARCHY_STARBASE && moon->data_index != SA_MATRA)
		{
			DoPlanetaryAnalysis (&system->SysInfo, moon);
				// When GenerateDefaultFunctions is used as genFuncs,
				// generateOrbital will also call DoPlanetaryAnalysis,
				// but with other GenerateFunctions this is not guaranteed.
		}
		(*system->genFuncs->generateOrbital) (system, moon);
		(*universeRecurseArg->moonFunc) (
				moon, universeRecurseArg->arg);
	}
}

////////////////////////////////////////////////////////////////////////////

typedef struct
{
	FILE *out;
} DumpUniverseArg;

// Must be called from the Starcon2Main thread.
void
dumpUniverse (FILE *out)
{
	DumpUniverseArg dumpUniverseArg;
	UniverseRecurseArg universeRecurseArg;
	
	dumpUniverseArg.out = out;

	universeRecurseArg.systemFuncPre = dumpSystemCallback;
	universeRecurseArg.systemFuncPost = NULL;
	universeRecurseArg.planetFuncPre = dumpPlanetCallback;
	universeRecurseArg.planetFuncPost = NULL;
	universeRecurseArg.moonFunc = dumpMoonCallback;
	universeRecurseArg.arg = (void *) &dumpUniverseArg;

	UniverseRecurse (&universeRecurseArg);
}

// Must be called from the Starcon2Main thread.
void
dumpUniverseToFile (void)
{
	FILE *out;

#	define UNIVERSE_DUMP_FILE "PlanetInfo"
	out = fopen(UNIVERSE_DUMP_FILE, "w");
	if (out == NULL)
	{
		fprintf(stderr, "Error: Could not open file '%s' for "
				"writing: %s\n", UNIVERSE_DUMP_FILE, strerror(errno));
		return;
	}

	dumpUniverse (out);
	
	fclose(out);

	fprintf(stdout, "*** Star dump complete. The game may be in an "
			"undefined state.\n");
			// Data generation may have changed the game state,
			// in particular for special planet generation.
}

static void
dumpSystemCallback (const STAR_DESC *star, const SOLARSYS_STATE *system,
		void *arg)
{
	FILE *out = ((DumpUniverseArg *) arg)->out;
	dumpSystem (out, star, system);
}

void
dumpSystem (FILE *out, const STAR_DESC *star, const SOLARSYS_STATE *system)
{
	UNICODE name[256];
	UNICODE buf[40];

	GetClusterName (star, name);
	snprintf (buf, sizeof buf, "%s %s",
			bodyColorString (STAR_COLOR(star->Type)),
			starTypeString (STAR_TYPE(star->Type)));
	fprintf (out, "%-22s  (%3d.%1d, %3d.%1d) %-19s  %s\n",
			name,
			star->star_pt.x / 10, star->star_pt.x % 10,
			star->star_pt.y / 10, star->star_pt.y % 10,
			buf,
			starPresenceString (star->Index));

	(void) system;  /* satisfy compiler */
}

const char *
bodyColorString (BYTE col)
{
	switch (col) {
		case BLUE_BODY:
			return "blue";
		case GREEN_BODY:
			return "green";
		case ORANGE_BODY:
			return "orange";
		case RED_BODY:
			return "red";
		case WHITE_BODY:
			return "white";
		case YELLOW_BODY:
			return "yellow";
		case CYAN_BODY:
			return "cyan";
		case PURPLE_BODY:
			return "purple";
		case VIOLET_BODY:
			return "violet";
		default:
			// Should not happen
			return "???";
	}
}

const char *
starTypeString (BYTE type)
{
	switch (type) {
		case DWARF_STAR:
			return "dwarf";
		case GIANT_STAR:
			return "giant";
		case SUPER_GIANT_STAR:
			return "super giant";
		default:
			// Should not happen
			return "???";
	}
}

const char *
starPresenceString (BYTE index)
{
	switch (index) {
		case 0:
			// nothing
			return "";
		case SOL_DEFINED:
			return "Sol";
		case SHOFIXTI_DEFINED:
			return "Shofixti male";
		case MAIDENS_DEFINED:
			return "Shofixti maidens";
		case START_COLONY_DEFINED:
			return "Starting colony";
		case SPATHI_DEFINED:
			return "Spathi home";
		case ZOQFOT_DEFINED:
			return "Zoq-Fot-Pik home";
		case MELNORME0_DEFINED:
			return "Melnorme trader #0";
		case MELNORME1_DEFINED:
			return "Melnorme trader #1";
		case MELNORME2_DEFINED:
			return "Melnorme trader #2";
		case MELNORME3_DEFINED:
			return "Melnorme trader #3";
		case MELNORME4_DEFINED:
			return "Melnorme trader #4";
		case MELNORME5_DEFINED:
			return "Melnorme trader #5";
		case MELNORME6_DEFINED:
			return "Melnorme trader #6";
		case MELNORME7_DEFINED:
			return "Melnorme trader #7";
		case MELNORME8_DEFINED:
			return "Melnorme trader #8";
		case TALKING_PET_DEFINED:
			return "Talking Pet";
		case CHMMR_DEFINED:
			return "Chmmr home";
		case SYREEN_DEFINED:
			return "Syreen home";
		case BURVIXESE_DEFINED:
			return "Burvixese ruins";
		case SLYLANDRO_DEFINED:
			return "Slylandro home";
		case DRUUGE_DEFINED:
			return "Druuge home";
		case BOMB_DEFINED:
			return "Precursor bomb";
		case AQUA_HELIX_DEFINED:
			return "Aqua Helix";
		case SUN_DEVICE_DEFINED:
			return "Sun Device";
		case TAALO_PROTECTOR_DEFINED:
			return "Taalo Shield";
		case SHIP_VAULT_DEFINED:
			return "Syreen ship vault";
		case URQUAN_WRECK_DEFINED:
			return "Ur-Quan ship wreck";
		case VUX_BEAST_DEFINED:
			return "Zex' beauty";
		case SAMATRA_DEFINED:
			return "Sa-Matra";
		case ZOQ_SCOUT_DEFINED:
			return "Zoq-Fot-Pik scout";
		case MYCON_DEFINED:
			return "Mycon home";
		case EGG_CASE0_DEFINED:
			return "Mycon egg shell #0";
		case EGG_CASE1_DEFINED:
			return "Mycon egg shell #1";
		case EGG_CASE2_DEFINED:
			return "Mycon egg shell #2";
		case PKUNK_DEFINED:
			return "Pkunk home";
		case UTWIG_DEFINED:
			return "Utwig home";
		case SUPOX_DEFINED:
			return "Supox home";
		case YEHAT_DEFINED:
			return "Yehat home";
		case VUX_DEFINED:
			return "Vux home";
		case ORZ_DEFINED:
			return "Orz home";
		case THRADD_DEFINED:
			return "Thraddash home";
		case RAINBOW_DEFINED:
			return "Rainbow world";
		case ILWRATH_DEFINED:
			return "Ilwrath home";
		case ANDROSYNTH_DEFINED:
			return "Androsynth ruins";
		case MYCON_TRAP_DEFINED:
			return "Mycon trap";
		default:
			// Should not happen
			return "???";
	}
}

static void
dumpPlanetCallback (const PLANET_DESC *planet, void *arg)
{
	FILE *out = ((DumpUniverseArg *) arg)->out;
	dumpPlanet (out, planet);
}

void
dumpPlanet (FILE *out, const PLANET_DESC *planet)
{
	(*pSolarSysState->genFuncs->generateName) (pSolarSysState, planet);
	fprintf (out, "- %-37s  %s\n", GLOBAL_SIS (PlanetName),
			planetTypeString (planet->data_index & ~PLANET_SHIELDED));
	dumpWorld (out, planet);
}

static void
dumpMoonCallback (const PLANET_DESC *moon, void *arg)
{
	FILE *out = ((DumpUniverseArg *) arg)->out;
	dumpMoon (out, moon);
}

void
dumpMoon (FILE *out, const PLANET_DESC *moon)
{
	const char *typeStr;
	
	if (moon->data_index == HIERARCHY_STARBASE)
	{
		typeStr = "StarBase";
	}
	else if (moon->data_index == SA_MATRA)
	{
		typeStr = "Sa-Matra";
	}
	else
	{
		typeStr = planetTypeString (moon->data_index & ~PLANET_SHIELDED);
	}
	fprintf (out, "  - Moon %-30c  %s\n",
			'a' + (moon - &pSolarSysState->MoonDesc[0]), typeStr);

	dumpWorld (out, moon);
}

static void
dumpWorld (FILE *out, const PLANET_DESC *world)
{
	PLANET_INFO *info;
	
	if (world->data_index == HIERARCHY_STARBASE) {
		return;
	}
	
	if (world->data_index == SA_MATRA) {
		return;
	}

	info = &pSolarSysState->SysInfo.PlanetInfo;
	fprintf(out, "          AxialTilt:  %d\n", info->AxialTilt);
	fprintf(out, "          Tectonics:  %d\n", info->Tectonics);
	fprintf(out, "          Weather:    %d\n", info->Weather);
	fprintf(out, "          Density:    %d\n", info->PlanetDensity);
	fprintf(out, "          Radius:     %d\n", info->PlanetRadius);
	fprintf(out, "          Gravity:    %d\n", info->SurfaceGravity);
	fprintf(out, "          Temp:       %d\n", info->SurfaceTemperature);
	fprintf(out, "          Day:        %d\n", info->RotationPeriod);
	fprintf(out, "          Atmosphere: %d\n", info->AtmoDensity);
	fprintf(out, "          LifeChance: %d\n", info->LifeChance);
	fprintf(out, "          DistToSun:  %d\n", info->PlanetToSunDist);

	if (world->data_index & PLANET_SHIELDED) {
		// Slave-shielded planet
		return;
	}

	fprintf (out, "          Bio: %4d    Min: %4d\n",
			calculateBioValue (pSolarSysState, world),
			calculateMineralValue (pSolarSysState, world));
}

COUNT
calculateBioValue (const SOLARSYS_STATE *system, const PLANET_DESC *world)
{
	COUNT result;
	COUNT numBio;
	COUNT i;

	assert (system->pOrbitalDesc == world);
	
	numBio = callGenerateForScanType (system, world, GENERATE_ALL,
			BIOLOGICAL_SCAN, NULL);

	result = 0;
	for (i = 0; i < numBio; i++)
	{
		NODE_INFO info;
		callGenerateForScanType (system, world, i, BIOLOGICAL_SCAN, &info);
		result += BIO_CREDIT_VALUE *
				LONIBBLE (CreatureData[info.type].ValueAndHitPoints);
	}
	return result;
}

void
generateBioIndex(const SOLARSYS_STATE *system, const PLANET_DESC *world,
		COUNT bio[])
{
	COUNT numBio;
	COUNT i;

	assert (system->pOrbitalDesc == world);
	
	numBio = callGenerateForScanType (system, world, GENERATE_ALL,
			BIOLOGICAL_SCAN, NULL);

	for (i = 0; i < NUM_CREATURE_TYPES + NUM_SPECIAL_CREATURE_TYPES; i++)
		bio[i] = 0;
	
	for (i = 0; i < numBio; i++)
	{
		NODE_INFO info;
		callGenerateForScanType (system, world, i, BIOLOGICAL_SCAN, &info);
		bio[info.type]++;
	}
}

COUNT
calculateMineralValue (const SOLARSYS_STATE *system, const PLANET_DESC *world)
{
	COUNT result;
	COUNT numDeposits;
	COUNT i;

	assert (system->pOrbitalDesc == world);
	
	numDeposits = callGenerateForScanType (system, world, GENERATE_ALL,
			MINERAL_SCAN, NULL);

	result = 0;
	for (i = 0; i < numDeposits; i++)
	{
		NODE_INFO info;
		callGenerateForScanType (system, world, i, MINERAL_SCAN, &info);
		result += HIBYTE (info.density) *
				GLOBAL (ElementWorth[ElementCategory (info.type)]);
	}
	return result;
}

void
generateMineralIndex(const SOLARSYS_STATE *system, const PLANET_DESC *world,
		COUNT minerals[])
{
	COUNT numDeposits;
	COUNT i;

	assert (system->pOrbitalDesc == world);
	
	numDeposits = callGenerateForScanType (system, world, GENERATE_ALL,
			MINERAL_SCAN, NULL);

	for (i = 0; i < NUM_ELEMENT_CATEGORIES; i++)
		minerals[i] = 0;
	
	for (i = 0; i < numDeposits; i++)
	{
		NODE_INFO info;
		callGenerateForScanType (system, world, i, MINERAL_SCAN, &info);
		minerals[ElementCategory (info.type)] += HIBYTE (info.density);
	}
}

////////////////////////////////////////////////////////////////////////////

struct TallyResourcesArg
{
	FILE *out;
	COUNT mineralCount;
	COUNT bioCount;
};

// Must be called from the Starcon2Main thread.
void
tallyResources (FILE *out)
{
	TallyResourcesArg tallyResourcesArg;
	UniverseRecurseArg universeRecurseArg;
	
	tallyResourcesArg.out = out;

	universeRecurseArg.systemFuncPre = tallySystemPreCallback;
	universeRecurseArg.systemFuncPost = tallySystemPostCallback;
	universeRecurseArg.planetFuncPre = tallyPlanetCallback;
	universeRecurseArg.planetFuncPost = NULL;
	universeRecurseArg.moonFunc = tallyMoonCallback;
	universeRecurseArg.arg = (void *) &tallyResourcesArg;

	UniverseRecurse (&universeRecurseArg);
}

// Must be called from the Starcon2Main thread.
void
tallyResourcesToFile (void)
{
	FILE *out;

#	define RESOURCE_TALLY_FILE "ResourceTally"
	out = fopen(RESOURCE_TALLY_FILE, "w");
	if (out == NULL)
	{
		fprintf(stderr, "Error: Could not open file '%s' for "
				"writing: %s\n", RESOURCE_TALLY_FILE, strerror(errno));
		return;
	}

	tallyResources (out);
	
	fclose(out);

	fprintf(stdout, "*** Resource tally complete. The game may be in an "
			"undefined state.\n");
			// Data generation may have changed the game state,
			// in particular for special planet generation.
}

static void
tallySystemPreCallback (const STAR_DESC *star, const SOLARSYS_STATE *system,
		void *arg)
{
	TallyResourcesArg *tallyResourcesArg = (TallyResourcesArg *) arg;
	tallyResourcesArg->mineralCount = 0;
	tallyResourcesArg->bioCount = 0;
	
	(void) star;  /* satisfy compiler */
	(void) system;  /* satisfy compiler */
}

static void
tallySystemPostCallback (const STAR_DESC *star, const SOLARSYS_STATE *system,
		void *arg)
{
	UNICODE name[256];
	TallyResourcesArg *tallyResourcesArg = (TallyResourcesArg *) arg;
	FILE *out = tallyResourcesArg->out;

	GetClusterName (star, name);
	fprintf (out, "%s\t%d\t%d\n", name, tallyResourcesArg->mineralCount,
			tallyResourcesArg->bioCount);

	(void) star;  /* satisfy compiler */
	(void) system;  /* satisfy compiler */
}

static void
tallyPlanetCallback (const PLANET_DESC *planet, void *arg)
{
	tallyResourcesWorld ((TallyResourcesArg *) arg, planet);
}

static void
tallyMoonCallback (const PLANET_DESC *moon, void *arg)
{
	tallyResourcesWorld ((TallyResourcesArg *) arg, moon);
}

static void
tallyResourcesWorld (TallyResourcesArg *arg, const PLANET_DESC *world)
{
	if (world->data_index == HIERARCHY_STARBASE) {
		return;
	}
	
	if (world->data_index == SA_MATRA) {
		return;
	}

	arg->bioCount += calculateBioValue (pSolarSysState, world),
	arg->mineralCount += calculateMineralValue (pSolarSysState, world);
}

////////////////////////////////////////////////////////////////////////////

void
forAllPlanetTypes (void (*callback) (int, const PlanetFrame *, void *),
		void *arg)
{
	int i;
	extern const PlanetFrame planet_array[];

	for (i = 0; i < NUMBER_OF_PLANET_TYPES; i++)
		callback (i, &planet_array[i], arg);
}
	
typedef struct
{
	FILE *out;
} DumpPlanetTypesArg;

void
dumpPlanetTypes (FILE *out)
{
	DumpPlanetTypesArg dumpPlanetTypesArg;
	dumpPlanetTypesArg.out = out;

	forAllPlanetTypes (dumpPlanetTypeCallback, (void *) &dumpPlanetTypesArg);
}

static void
dumpPlanetTypeCallback (int index, const PlanetFrame *planetType, void *arg)
{
	DumpPlanetTypesArg *dumpPlanetTypesArg = (DumpPlanetTypesArg *) arg;

	dumpPlanetType(dumpPlanetTypesArg->out, index, planetType);
}

void
dumpPlanetType (FILE *out, int index, const PlanetFrame *planetType)
{
	int i;
	fprintf (out,
			"%s\n"
			"\tType: %s\n"
			"\tColor: %s\n"
			"\tSurface generation algoritm: %s\n"
			"\tTectonics: %s\n"
			"\tAtmosphere: %s\n"
			"\tDensity: %s\n"
			"\tElements:\n",
			planetTypeString (index),
			worldSizeString (PLANSIZE (planetType->Type)),
			bodyColorString (PLANCOLOR (planetType->Type)),
			worldGenAlgoString (PLANALGO (planetType->Type)),
			tectonicsString (planetType->BaseTectonics),
			atmosphereString (HINIBBLE (planetType->AtmoAndDensity)),
			densityString (LONIBBLE (planetType->AtmoAndDensity))
			);
	for (i = 0; i < NUM_USEFUL_ELEMENTS; i++)
	{
		const ELEMENT_ENTRY *entry;
		entry = &planetType->UsefulElements[i];
		if (entry->Density == 0)
			continue;
		fprintf(out, "\t\t0 to %d %s-quality (+%d) deposits of %s (%s)\n",
				DEPOSIT_QUANTITY (entry->Density),
				depositQualityString (DEPOSIT_QUALITY (entry->Density)),
				DEPOSIT_QUALITY (entry->Density) * 5,
				GAME_STRING (ELEMENTS_STRING_BASE + entry->ElementType),
				GAME_STRING (CARGO_STRING_BASE + 2 + ElementCategory (
				entry->ElementType))
			);
	}
	fprintf (out, "\n");
}

const char *
planetTypeString (int typeIndex)
{
	static UNICODE typeStr[40];

	if (typeIndex >= FIRST_GAS_GIANT)
	{
		// "Gas Giant"
		snprintf(typeStr, sizeof typeStr, "%s",
				GAME_STRING (SCAN_STRING_BASE + 4 + 51));
	}
	else
	{
		// "<type> World" (eg. "Water World")
		snprintf(typeStr, sizeof typeStr, "%s %s",
				GAME_STRING (SCAN_STRING_BASE + 4 + typeIndex),
				GAME_STRING (SCAN_STRING_BASE + 4 + 50));
	}
	return typeStr;
}

// size is what you get from PLANSIZE (planetFrame.Type)
const char *
worldSizeString (BYTE size)
{
	switch (size)
	{
		case SMALL_ROCKY_WORLD:
			return "small rocky world";
		case LARGE_ROCKY_WORLD:
			return "large rocky world";
		case GAS_GIANT:
			return "gas giant";
		default:
			// Should not happen
			return "???";
	}
}

// algo is what you get from PLANALGO (planetFrame.Type)
const char *
worldGenAlgoString (BYTE algo)
{
	switch (algo)
	{
		case TOPO_ALGO:
			return "TOPO_ALGO";
		case CRATERED_ALGO:
			return "CRATERED_ALGO";
		case GAS_GIANT_ALGO:
			return "GAS_GIANT_ALGO";
		default:
			// Should not happen
			return "???";
	}
}

// tectonics is what you get from planetFrame.BaseTechtonics
// not reentrant
const char *
tectonicsString (BYTE tectonics)
{
	static char buf[sizeof "-127"];
	switch (tectonics)
	{
		case NO_TECTONICS:
			return "none";
		case LOW_TECTONICS:
			return "low";
		case MED_TECTONICS:
			return "medium";
		case HIGH_TECTONICS:
			return "high";
		case SUPER_TECTONICS:
			return "super";
		default:
			snprintf (buf, sizeof buf, "%d", tectonics);
			return buf;
	}
}

// atmosphere is what you get from HINIBBLE (planetFrame.AtmoAndDensity)
const char *
atmosphereString (BYTE atmosphere)
{
	switch (atmosphere)
	{
		case LIGHT:
			return "thin";
		case MEDIUM:
			return "normal";
		case HEAVY:
			return "thick";
		default:
			return "super thick";
	}
}

// density is what you get from LONIBBLE (planetFrame.AtmoAndDensity)
const char *
densityString (BYTE density)
{
	switch (density)
	{
		case GAS_DENSITY:
			return "gaseous";
		case LIGHT_DENSITY:
			return "light";
		case LOW_DENSITY:
			return "low";
		case NORMAL_DENSITY:
			return "normal";
		case HIGH_DENSITY:
			return "high";
		case SUPER_DENSITY:
			return "super high";
		default:
			// Should not happen
			return "???";
	}
}

// quality is what you get from DEPOSIT_QUALITY (elementEntry.Density)
const char *
depositQualityString (BYTE quality)
{
	switch (quality)
	{
		case LIGHT:
			return "low";
		case MEDIUM:
			return "medium";
		case HEAVY:
			return "high";
		default:
			// Should not happen
			return "???";
	}
}

////////////////////////////////////////////////////////////////////////////

// playerNr should be 0 or 1
STARSHIP*
findPlayerShip (SIZE playerNr)
{
	HELEMENT hElement, hNextElement;

	for (hElement = GetHeadElement (); hElement; hElement = hNextElement)
	{
		ELEMENT *ElementPtr;

		LockElement (hElement, &ElementPtr);
		hNextElement = GetSuccElement (ElementPtr);
					
		if ((ElementPtr->state_flags & PLAYER_SHIP)	&&
				ElementPtr->playerNr == playerNr)
		{
			STARSHIP *StarShipPtr;
			GetElementStarShip (ElementPtr, &StarShipPtr);
			UnlockElement (hElement);
			return StarShipPtr;
		}
		
		UnlockElement (hElement);
	}
	return NULL;
}

////////////////////////////////////////////////////////////////////////////

void
resetCrewBattle (void)
{
	STARSHIP *StarShipPtr;
	COUNT delta;
	CONTEXT OldContext;
	
	if (!(GLOBAL (CurrentActivity) & IN_BATTLE) ||
			(inHQSpace ()))
		return;
	
	StarShipPtr = findPlayerShip (RPG_PLAYER_NUM);
	if (StarShipPtr == NULL || StarShipPtr->RaceDescPtr == NULL)
		return;

	delta = StarShipPtr->RaceDescPtr->ship_info.max_crew -
			StarShipPtr->RaceDescPtr->ship_info.crew_level;

	OldContext = SetContext (StatusContext);
	DeltaCrew (StarShipPtr->hShip, delta);
	SetContext (OldContext);
}

void
resetEnergyBattle (void)
{
	STARSHIP *StarShipPtr;
	COUNT delta;
	CONTEXT OldContext;
	
	if (!(GLOBAL (CurrentActivity) & IN_BATTLE) ||
			(inHQSpace ()))
		return;
	
	StarShipPtr = findPlayerShip (RPG_PLAYER_NUM);
	if (StarShipPtr == NULL || StarShipPtr->RaceDescPtr == NULL)
		return;

	delta = StarShipPtr->RaceDescPtr->ship_info.max_energy -
			StarShipPtr->RaceDescPtr->ship_info.energy_level;

	OldContext = SetContext (StatusContext);
	DeltaEnergy (StarShipPtr->hShip, delta);
	SetContext (OldContext);
}

////////////////////////////////////////////////////////////////////////////

// This function should help in making sure that gamestr.h matches
// gamestrings.txt.
void
dumpStrings (FILE *out)
{
#define STRINGIZE(a) #a
#define MAKE_STRING_CATEGORY(prefix) \
		{ \
			/* .name  = */ STRINGIZE(prefix ## _BASE), \
			/* .base  = */ prefix ## _BASE, \
			/* .count = */ prefix ## _COUNT \
		}
	struct {
		const char *name;
		size_t base;
		size_t count;
	} categories[] = {
		{ "0", 0, 0 },
		MAKE_STRING_CATEGORY(STAR_STRING),
		MAKE_STRING_CATEGORY(DEVICE_STRING),
		MAKE_STRING_CATEGORY(CARGO_STRING),
		MAKE_STRING_CATEGORY(ELEMENTS_STRING),
		MAKE_STRING_CATEGORY(SCAN_STRING),
		MAKE_STRING_CATEGORY(STAR_NUMBER),
		MAKE_STRING_CATEGORY(PLANET_NUMBER),
		MAKE_STRING_CATEGORY(MONTHS_STRING),
		MAKE_STRING_CATEGORY(FEEDBACK_STRING),
		MAKE_STRING_CATEGORY(STARBASE_STRING),
		MAKE_STRING_CATEGORY(ENCOUNTER_STRING),
		MAKE_STRING_CATEGORY(NAVIGATION_STRING),
		MAKE_STRING_CATEGORY(NAMING_STRING),
		MAKE_STRING_CATEGORY(MELEE_STRING),
		MAKE_STRING_CATEGORY(SAVEGAME_STRING),
		MAKE_STRING_CATEGORY(OPTION_STRING),
		MAKE_STRING_CATEGORY(QUITMENU_STRING),
		MAKE_STRING_CATEGORY(STATUS_STRING),
		MAKE_STRING_CATEGORY(FLAGSHIP_STRING),
		MAKE_STRING_CATEGORY(ORBITSCAN_STRING),
		MAKE_STRING_CATEGORY(MAINMENU_STRING),
		MAKE_STRING_CATEGORY(NETMELEE_STRING),
		{ "GAMESTR_COUNT", GAMESTR_COUNT, (size_t) -1 }
	};
	size_t numCategories = sizeof categories / sizeof categories[0];
	size_t numStrings = GetStringTableCount (GameStrings);
	size_t stringI;
	size_t categoryI = 0;

	// Start with a sanity check to see if gamestr.h has been changed but
	// not this file.
	for (categoryI = 0; categoryI < numCategories - 1; categoryI++) {
		if (categories[categoryI].base + categories[categoryI].count !=
				categories[categoryI + 1].base) {
			fprintf(stderr, "Error: String category list in dumpStrings() is "
					"not up to date.\n");
			return;
		}
	}
	
	if (GAMESTR_COUNT != numStrings) {
		fprintf(stderr, "Warning: GAMESTR_COUNT is %d, but GameStrings "
				"contains %d strings.\n", GAMESTR_COUNT, numStrings);
	}

	categoryI = 0;
	for (stringI = 0; stringI < numStrings; stringI++) {
		while (categoryI < numCategories &&
				stringI >= categories[categoryI + 1].base)
			categoryI++;
		fprintf(out, "[ %s + %d ]  %s\n", categories[categoryI].name,
				stringI - categories[categoryI].base, GAME_STRING(stringI));
	}
}

////////////////////////////////////////////////////////////////////////////


static Color
hsvaToRgba (double hue, double sat, double val, BYTE alpha)
{
	unsigned int hi = (int) (hue / 60.0);
	double f = (hue / 60.0) - ((int) (hue / 60.0));
	double p = val * (1.0 - sat);
	double q = val * (1.0 - f * sat);
	double t = val * (1.0 - (1.0 - f * sat));

	// Convert p, q, t, and v from [0..1] to [0..255]
	BYTE pb = (BYTE) (p * 255.0 + 0.5);
	BYTE qb = (BYTE) (q * 255.0 + 0.5);
	BYTE tb = (BYTE) (t * 255.0 + 0.5);
	BYTE vb = (BYTE) (val * 255.0 + 0.5);

	assert (hue >= 0.0 && hue < 360.0);
	assert (sat >= 0 && sat <= 1.0);
	assert (val >= 0 && val <= 1.0);
	/*fprintf(stderr, "hsva = (%.1f, %.2f, %.2f, %.2d)\n",
			hue, sat, val, alpha);*/
	
	assert (hi < 6);
	switch (hi) {
		case 0: return BUILD_COLOR_RGBA (vb, tb, pb, alpha);
		case 1: return BUILD_COLOR_RGBA (qb, vb, pb, alpha);
		case 2: return BUILD_COLOR_RGBA (pb, vb, tb, alpha);
		case 3: return BUILD_COLOR_RGBA (pb, qb, vb, alpha);
		case 4: return BUILD_COLOR_RGBA (tb, pb, vb, alpha);
		case 5: return BUILD_COLOR_RGBA (vb, pb, qb, alpha);
	}

	// Should not happen.
	return BUILD_COLOR_RGBA (0, 0, 0, alpha);
}

// Returns true iff this context has a visible FRAME.
static bool
isContextVisible (CONTEXT context)
{
	FRAME contextFrame;

	// Save the original context.
	CONTEXT oldContext = SetContext (context);
	
	// Get the frame of the specified context.
	contextFrame = GetContextFGFrame ();

	// Restore the original context.
	SetContext (oldContext);

	return contextFrame == Screen;
}

static size_t
countVisibleContexts (void)
{
	size_t contextCount;
	CONTEXT context;

	contextCount = 0;
	for (context = GetFirstContext (); context != NULL;
			context = GetNextContext (context))
	{
		if (!isContextVisible (context))
			continue;
		
		contextCount++;
	}

	return contextCount;
}

static void
drawContext (CONTEXT context, double hue /* no pun intended */)
{
	FRAME drawFrame;
	CONTEXT oldContext;
	FONT oldFont;
	DrawMode oldMode;
	Color oldFgCol;
	Color rectCol;
	Color lineCol;
	Color textCol;
	bool haveClippingRect;
	RECT rect;
	LINE line;
	TEXT text;
	POINT p1, p2, p3, p4;

	drawFrame = GetContextFGFrame ();
	rectCol = hsvaToRgba (hue, 1.0, 0.5, 100);
	lineCol = hsvaToRgba (hue, 1.0, 1.0, 90);
	textCol = lineCol;

	// Save the original context.
	oldContext = SetContext (context);
	
	// Get the clipping rectangle of the specified context.
	haveClippingRect = GetContextClipRect (&rect);

	// Switch back the old context; we're going to draw in it.
	(void) SetContext (oldContext);

	p1 = rect.corner;
	p2.x = rect.corner.x + rect.extent.width - 1;
	p2.y = rect.corner.y;
	p3.x = rect.corner.x;
	p3.y = rect.corner.y + rect.extent.height - 1;
	p4.x = rect.corner.x + rect.extent.width - 1;
	p4.y = rect.corner.y + rect.extent.height - 1;

	oldFgCol = SetContextForeGroundColor (rectCol);
	DrawFilledRectangle (&rect);

	SetContextForeGroundColor (lineCol);
	line.first = p1; line.second = p2; DrawLine (&line);
	line.first = p2; line.second = p4; DrawLine (&line);
	line.first = p1; line.second = p3; DrawLine (&line);
	line.first = p3; line.second = p4; DrawLine (&line);
	line.first = p1; line.second = p4; DrawLine (&line);
	line.first = p2; line.second = p3; DrawLine (&line);
	// Gimme C'99! So I can do:
	//     DrawLine ((LINE) { .first = p1, .second = p2 })

	oldFont = SetContextFont (TinyFont);
	SetContextForeGroundColor (textCol);
	// Text prim does not yet support alpha via Color.a
	oldMode = SetContextDrawMode (MAKE_DRAW_MODE (DRAW_ALPHA, textCol.a));
	text.baseline.x = (p1.x + (p2.x + 1)) / 2;
	text.baseline.y = p1.y + 8;
	text.pStr = GetContextName (context);
	text.align = ALIGN_CENTER;
	text.CharCount = (COUNT) ~0;
	font_DrawText (&text);
	(void) SetContextDrawMode (oldMode);

	(void) SetContextForeGroundColor (oldFgCol);
	(void) SetContextFont (oldFont);
}

static void
describeContext (FILE *out, const CONTEXT context) {
	RECT rect;
	CONTEXT oldContext = SetContext (context);
	
	GetContextClipRect (&rect);
	fprintf(out, "Context '%s':\n"
			"\tClipRect = (%d, %d)-(%d, %d)  (%d x %d)\n",
		   GetContextName (context),
		   rect.corner.x, rect.corner.y,
		   rect.corner.x + rect.extent.width,
		   rect.corner.y + rect.extent.height,
		   rect.extent.width, rect.extent.height);
	
	SetContext (oldContext);
}


typedef struct wait_state
{
	// standard state required by DoInput
	BOOLEAN (*InputFunc) (struct wait_state *self);
} WAIT_STATE;

		
// Maybe move to elsewhere, where it can be reused?
static BOOLEAN
waitForKey (struct wait_state *self) {
	if (PulsedInputState.menu[KEY_MENU_SELECT] ||
			PulsedInputState.menu[KEY_MENU_CANCEL])
		return FALSE;

	SleepThread (ONE_SECOND / 20);
	
	(void) self;
	return TRUE;
}

// Maybe move to elsewhere, where it can be reused?
static FRAME
getScreen (void)
{
	CONTEXT oldContext = SetContext (ScreenContext);
	FRAME savedFrame;
	RECT screenRect;

	screenRect.corner.x = 0;
	screenRect.corner.y = 0;
	screenRect.extent.width = ScreenWidth;
	screenRect.extent.height = ScreenHeight;
	savedFrame = CaptureDrawable (LoadDisplayPixmap (&screenRect, (FRAME) 0));

	(void) SetContext (oldContext);
	return savedFrame;
}

static void
putScreen (FRAME savedFrame) {
	STAMP stamp;
	
	CONTEXT oldContext = SetContext (ScreenContext);

	stamp.origin.x = 0;
	stamp.origin.y = 0;
	stamp.frame = savedFrame;
	DrawStamp (&stamp);

	(void) SetContext (oldContext);
}

// Show the contexts on the screen.
// Must be called from the main thread.
void
debugContexts (void)
{
	static volatile bool inDebugContexts = false;
			// Prevent this function from being called from within itself.
	
	CONTEXT orgContext;
	CONTEXT debugDrawContext;
			// We're going to use this context to draw in.
	FRAME debugDrawFrame;
	double hueIncrement;
	size_t visibleContextI;
	CONTEXT context;
	size_t contextCount;
	FRAME savedScreen;

	// Prevent this function from being called from within itself.
	if (inDebugContexts)
		return;
	inDebugContexts = true;

	contextCount = countVisibleContexts ();
	if (contextCount == 0)
	{
		goto out;
	}
	
	savedScreen = getScreen ();
	FlushGraphics ();
			// Make sure that the screen has actually been captured,
			// before we use the frame.

	// Create a new frame to draw on.
	debugDrawContext = CreateContext ("debugDrawContext");
	// New work frame is a copy of the original.
	debugDrawFrame = CaptureDrawable (CloneFrame (savedScreen));
	orgContext = SetContext (debugDrawContext);
	SetContextFGFrame (debugDrawFrame);

	hueIncrement = 360.0 / contextCount;

	visibleContextI = 0;
	for (context = GetFirstContext (); context != NULL;
			context = GetNextContext (context))
	{
		if (context == debugDrawContext) {
			// Skip our own context.
			continue;
		}
	
		if (isContextVisible (context))
		{
			// Only draw the visible contexts.
			drawContext (context, visibleContextI * hueIncrement);
			visibleContextI++;
		}

		describeContext (stderr, context);
	}

	// Blit the final debugging frame to the screen.
	putScreen (debugDrawFrame);

	// Wait for a key:
	{
		WAIT_STATE state;
		state.InputFunc = waitForKey;
		DoInput(&state, TRUE);
	}

	SetContext (orgContext);

	// Destroy the debugging frame and context.
	DestroyContext (debugDrawContext);
			// This does nothing with the drawable set with
			// SetContextFGFrame().
	DestroyDrawable (ReleaseDrawable (debugDrawFrame));
	
	putScreen (savedScreen);

	DestroyDrawable (ReleaseDrawable (savedScreen));

out:
	inDebugContexts = false;
}

#endif  /* DEBUG */

