//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 "battle.h"

#include "battlecontrols.h"
#include "controls.h"
#include "init.h"
#include "element.h"
#include "ship.h"
#include "process.h"
#include "tactrans.h"
		// for flee_preprocess()
#include "intel.h"
#ifdef NETPLAY
#	include "supermelee/netplay/netmelee.h"
#	ifdef NETPLAY_CHECKSUM
#		include "supermelee/netplay/checksum.h"
#	endif
#	include "supermelee/netplay/notifyall.h"
#endif
#include "supermelee/pickmele.h"
#include "resinst.h"
#include "nameref.h"
#include "setup.h"
#include "settings.h"
#include "sounds.h"
#include "libs/graphics/gfx_common.h"
#include "libs/log.h"
#include "libs/mathlib.h"


BYTE battle_counter[NUM_SIDES];
		// The number of ships still available for battle to each side.
		// A ship that has warped out is no longer available.
BOOLEAN instantVictory;
size_t battleInputOrder[NUM_SIDES];
		// Indices of the sides in the order their input is processed.
		// Network sides are last so that the sides will never be waiting
		// on eachother, and games with a 0 frame delay are theoretically
		// possible.
#ifdef NETPLAY
BattleFrameCounter battleFrameCount;
		// Used for synchronisation purposes during netplay.
#endif

static BOOLEAN
RunAwayAllowed (void)
{
	return (LOBYTE (GLOBAL (CurrentActivity)) == IN_ENCOUNTER
			|| LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
			&& GET_GAME_STATE (STARBASE_AVAILABLE)
			&& !GET_GAME_STATE (BOMB_CARRIER);
}

static void
DoRunAway (STARSHIP *StarShipPtr)
{
	ELEMENT *ElementPtr;

	LockElement (StarShipPtr->hShip, &ElementPtr);
	if (GetPrimType (&DisplayArray[ElementPtr->PrimIndex]) == STAMP_PRIM
			&& ElementPtr->life_span == NORMAL_LIFE
			&& !(ElementPtr->state_flags & FINITE_LIFE)
			&& ElementPtr->mass_points != MAX_SHIP_MASS * 10
			&& !(ElementPtr->state_flags & APPEARING))
	{
		battle_counter[0]--;

		ElementPtr->turn_wait = 3;
		ElementPtr->thrust_wait = 4;
		ElementPtr->colorCycleIndex = 0;
		ElementPtr->preprocess_func = flee_preprocess;
		ElementPtr->mass_points = MAX_SHIP_MASS * 10;
		ZeroVelocityComponents (&ElementPtr->velocity);
		StarShipPtr->cur_status_flags &=
				~(SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED);

		SetPrimColor (&DisplayArray[ElementPtr->PrimIndex],
				BUILD_COLOR (MAKE_RGB15 (0x0B, 0x00, 0x00), 0x2E));
				// XXX: I think this is supposed to be the same as the
				// first entry of the color cycle table in flee_preeprocess,
				// but it is slightly different (0x0A as red value). - SvdB.
		SetPrimType (&DisplayArray[ElementPtr->PrimIndex], STAMPFILL_PRIM);
	
		StarShipPtr->ship_input_state = 0;
	}
	UnlockElement (StarShipPtr->hShip);
}

static void
setupBattleInputOrder(void)
{
	size_t i;

#ifndef NETPLAY
	for (i = 0; i < NUM_SIDES; i++)
		battleInputOrder[i] = i;
#else
	int j;

	i = 0;
	// First put the locally controlled players in the array.
	for (j = 0; j < NUM_SIDES; j++) {
		if (!(PlayerControl[j] & NETWORK_CONTROL)) {
			battleInputOrder[i] = j;
			i++;
		}
	}
	
	// Next put the network controlled players in the array.
	for (j = 0; j < NUM_SIDES; j++) {
		if (PlayerControl[j] & NETWORK_CONTROL) {
			battleInputOrder[i] = j;
			i++;
		}
	}
#endif
}

BATTLE_INPUT_STATE
frameInputHuman (HumanInputContext *context, STARSHIP *StarShipPtr)
{
	(void) StarShipPtr;
	return CurrentInputToBattleInput (context->playerNr);
}

static void
ProcessInput (void)
{
	BOOLEAN CanRunAway;
	size_t sideI;

#ifdef NETPLAY
	netInput ();
#endif

	CanRunAway = RunAwayAllowed ();
		
	for (sideI = 0; sideI < NUM_SIDES; sideI++)
	{
		HSTARSHIP hBattleShip, hNextShip;
		size_t cur_player = battleInputOrder[sideI];

		for (hBattleShip = GetHeadLink (&race_q[cur_player]);
				hBattleShip != 0; hBattleShip = hNextShip)
		{
			BATTLE_INPUT_STATE InputState;
			STARSHIP *StarShipPtr;

			StarShipPtr = LockStarShip (&race_q[cur_player], hBattleShip);
			hNextShip = _GetSuccLink (StarShipPtr);

			if (StarShipPtr->hShip)
			{
				// TODO: review and see if we have to do this every frame, or
				//   if we can do this once somewhere
				StarShipPtr->control = PlayerControl[cur_player];
				
				InputState = PlayerInput[cur_player]->handlers->frameInput (
						PlayerInput[cur_player], StarShipPtr);

#if CREATE_JOURNAL
				JournalInput (InputState);
#endif /* CREATE_JOURNAL */
#ifdef NETPLAY
				if (!(PlayerControl[cur_player] & NETWORK_CONTROL))
				{
					BattleInputBuffer *bib = getBattleInputBuffer(cur_player);
					Netplay_NotifyAll_battleInput (InputState);
					flushPacketQueues ();

					BattleInputBuffer_push (bib, InputState);
							// Add this input to the end of the buffer.
					BattleInputBuffer_pop (bib, &InputState);
							// Get the input from the front of the buffer.
				}
#endif

				StarShipPtr->ship_input_state = 0;
				if (StarShipPtr->RaceDescPtr->ship_info.crew_level)
				{
					if (InputState & BATTLE_LEFT)
						StarShipPtr->ship_input_state |= LEFT;
					else if (InputState & BATTLE_RIGHT)
						StarShipPtr->ship_input_state |= RIGHT;
					if (InputState & BATTLE_THRUST)
						StarShipPtr->ship_input_state |= THRUST;
					if (InputState & BATTLE_WEAPON)
						StarShipPtr->ship_input_state |= WEAPON;
					if (InputState & BATTLE_SPECIAL)
						StarShipPtr->ship_input_state |= SPECIAL;

					if (CanRunAway && cur_player == 0 &&
							(InputState & BATTLE_ESCAPE))
						DoRunAway (StarShipPtr);
				}
			}

			UnlockStarShip (&race_q[cur_player], hBattleShip);
		}
	}
	
#ifdef NETPLAY
	flushPacketQueues ();
#endif

	if (GLOBAL (CurrentActivity) & (CHECK_LOAD | CHECK_ABORT))
		GLOBAL (CurrentActivity) &= ~IN_BATTLE;
}

#if DEMO_MODE || CREATE_JOURNAL
DWORD BattleSeed;
#endif /* DEMO_MODE */

static MUSIC_REF BattleRef;

void
BattleSong (BOOLEAN DoPlay)
{
	if (BattleRef == 0)
	{
		if (LOBYTE (GLOBAL (CurrentActivity)) != IN_HYPERSPACE)
			BattleRef = LoadMusic (BATTLE_MUSIC);
		else if (GET_GAME_STATE (ARILOU_SPACE_SIDE) <= 1)
			BattleRef = LoadMusic (HYPERSPACE_MUSIC);
		else
			BattleRef = LoadMusic (QUASISPACE_MUSIC);
	}

	if (DoPlay)
		PlayMusic (BattleRef, TRUE, 1);
}

void
FreeBattleSong (void)
{
	DestroyMusic (BattleRef);
	BattleRef = 0;
}

static BOOLEAN
DoBattle (BATTLE_STATE *bs)
{
	extern UWORD nth_frame;
	RECT r;
	BYTE battle_speed;

	SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);

#if defined (NETPLAY) && defined (NETPLAY_CHECKSUM)
	if (getNumNetConnections() > 0 &&
			battleFrameCount % NETPLAY_CHECKSUM_INTERVAL == 0)
	{
		crc_State state;
		Checksum checksum;

		crc_init(&state);
		crc_processState (&state);
		checksum = (Checksum) crc_finish (&state);

		Netplay_NotifyAll_checksum ((uint32) battleFrameCount,
				(uint32) checksum);
		flushPacketQueues ();
		addLocalChecksum (battleFrameCount, checksum);
	}
#endif
	ProcessInput ();
			// Also calls NetInput()
#if defined (NETPLAY) && defined (NETPLAY_CHECKSUM)
	if (getNumNetConnections() > 0)
	{
		size_t delay = getBattleInputDelay();

		if (battleFrameCount >= delay
				&& (battleFrameCount - delay) % NETPLAY_CHECKSUM_INTERVAL == 0)
		{
			if (!(GLOBAL (CurrentActivity) & CHECK_ABORT))
			{
				if (!verifyChecksums (battleFrameCount - delay)) {
					GLOBAL(CurrentActivity) |= CHECK_ABORT;
					resetConnections (ResetReason_syncLoss);
				}
			}
		}
	}
#endif

	LockMutex (GraphicsLock);
	if (bs->first_time)
	{
		r.corner.x = SIS_ORG_X;
		r.corner.y = SIS_ORG_Y;
		r.extent.width = SIS_SCREEN_WIDTH;
		r.extent.height = SIS_SCREEN_HEIGHT;
		SetTransitionSource (&r);
	}
	BatchGraphics ();

	// Call the callback function, if set
	if (bs->frame_cb)
		bs->frame_cb ();

	RedrawQueue (TRUE);

	if (bs->first_time)
	{
		bs->first_time = FALSE;
		ScreenTransition (3, &r);
	}
	UnbatchGraphics ();
	UnlockMutex (GraphicsLock);
	if ((!(GLOBAL (CurrentActivity) & IN_BATTLE)) ||
			(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
	{
		return FALSE;
	}

	battle_speed = HIBYTE (nth_frame);
	if (battle_speed == (BYTE)~0)
	{	// maximum speed, nothing rendered at all
		TaskSwitch ();
	}
	else
	{
		SleepThreadUntil (bs->NextTime
				+ BATTLE_FRAME_RATE / (battle_speed + 1));
		bs->NextTime = GetTimeCounter ();
	}

	if ((GLOBAL (CurrentActivity) & IN_BATTLE) == 0)
		return FALSE;

#ifdef NETPLAY
	battleFrameCount++;
#endif
	return TRUE;
}

#ifdef NETPLAY
COUNT
GetPlayerOrder (COUNT i)
{
	// Iff 'myTurn' is set on a connection, the local party will be
	// processed first.
	// If neither is network controlled, the top player (1) is handled
	// first.
	if (((PlayerControl[0] & NETWORK_CONTROL) &&
			!NetConnection_getDiscriminant (netConnections[0])) ||
			((PlayerControl[1] & NETWORK_CONTROL) &&
			NetConnection_getDiscriminant (netConnections[1])))
		return i;
	else
		return 1 - i;
}
#endif

// Let each player pick his ship.
static BOOLEAN
selectAllShips (SIZE num_ships)
{
	if (num_ships == 1) {
		// HyperSpace in full game.
		return GetNextStarShip (NULL, 0);
	}
	
#ifdef NETPLAY
	if ((PlayerControl[0] & NETWORK_CONTROL) &&
			(PlayerControl[1] & NETWORK_CONTROL))
	{
		log_add (log_Error, "Only one side at a time can be network "
				"controlled.\n");
		return FALSE;
	}
#endif

	return GetInitialStarShips ();
}

BOOLEAN
Battle (BattleFrameCallback *callback)
{
	SIZE num_ships;

	LockMutex (GraphicsLock);

#if !(DEMO_MODE || CREATE_JOURNAL)
	if (LOBYTE (GLOBAL (CurrentActivity)) != SUPER_MELEE) {
		// In Supermelee, the RNG is already initialised.
		TFB_SeedRandom (GetTimeCounter ());
	}
#else /* DEMO_MODE */
	if (BattleSeed == 0)
		BattleSeed = TFB_Random ();
	TFB_SeedRandom (BattleSeed);
	BattleSeed = TFB_Random (); /* get next battle seed */
#endif /* DEMO_MODE */

	BattleSong (FALSE);
	
	num_ships = InitShips ();

	if (instantVictory)
	{
		num_ships = 0;
		battle_counter[0] = 1;
		battle_counter[1] = 0;
		instantVictory = FALSE;
	}
	
	if (num_ships)
	{
		BATTLE_STATE bs;

		GLOBAL (CurrentActivity) |= IN_BATTLE;
		battle_counter[0] = CountLinks (&race_q[0]);
		battle_counter[1] = CountLinks (&race_q[1]);

		if (optMeleeScale != TFB_SCALE_STEP)
			SetGraphicScaleMode (optMeleeScale);

		setupBattleInputOrder ();
#ifdef NETPLAY
		initBattleInputBuffers ();
#ifdef NETPLAY_CHECKSUM
		initChecksumBuffers ();
#endif  /* NETPLAY_CHECKSUM */
		battleFrameCount = 0;
		ResetWinnerStarShip ();
		setBattleStateConnections (&bs);
#endif  /* NETPLAY */

		if (!selectAllShips (num_ships)) {
			GLOBAL (CurrentActivity) |= CHECK_ABORT;
			goto AbortBattle;
		}

		BattleSong (TRUE);
		bs.NextTime = 0;
#ifdef NETPLAY
		initBattleStateDataConnections ();
		{
			bool allOk = negotiateReadyConnections (true, NetState_inBattle);
			if (!allOk) {
				GLOBAL (CurrentActivity) |= CHECK_ABORT;
				goto AbortBattle;
			}
		}
#endif  /* NETPLAY */
		bs.InputFunc = DoBattle;
		bs.frame_cb = callback;
		bs.first_time = (BOOLEAN)(LOBYTE (GLOBAL (CurrentActivity)) ==
				IN_HYPERSPACE);

		UnlockMutex (GraphicsLock);
		DoInput (&bs, FALSE);
		LockMutex (GraphicsLock);

AbortBattle:
		if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE)
		{
			if (GLOBAL (CurrentActivity) & CHECK_ABORT)
			{
				// Do not return to the main menu when a game is aborted,
				// (just to the supermelee menu).
#ifdef NETPLAY
				UnlockMutex (GraphicsLock);
				waitResetConnections(NetState_inSetup);
						// A connection may already be in inSetup (set from
						// GetMeleeStarship). This is not a problem, although
						// it will generate a warning in debug mode.
				LockMutex (GraphicsLock);
#endif

				GLOBAL (CurrentActivity) &= ~CHECK_ABORT;
			}
			else
			{
				// Show the result of the battle.
				MeleeGameOver ();
			}
		}

#ifdef NETPLAY
		uninitBattleInputBuffers();
#ifdef NETPLAY_CHECKSUM
		uninitChecksumBuffers ();
#endif  /* NETPLAY_CHECKSUM */
		setBattleStateConnections (NULL);
#endif  /* NETPLAY */

		StopDitty ();
		StopMusic ();
		StopSound ();
	}

	UninitShips ();
	FreeBattleSong ();

	UnlockMutex (GraphicsLock);
	
	return (BOOLEAN) (num_ships < 0);
}

