//===========================================================================//
// File:	btl4pb.cpp                                                          //
// Project: BattleTech           Brick: BattleTech LBE Application            //
// Contents: BTL4PlaybackApplication -- the LBE mission-review / playback      //
//           application, and BTSpoolerTask, the background task that pumps    //
//           recorded NetworkPackets out of a SpoolFile back through the       //
//           network manager so a finished battle can be re-watched.           //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
//                                                                           //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
//                      PROPRIETARY AND CONFIDENTIAL                         //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary BTL4OPT.EXE.  The class declarations
// are VERBATIM from the surviving header BTL4PB.HPP; the method bodies are
// recovered from the Ghidra pseudo-C for the 0x4d3b74-0x4d3f44 cluster
// (all/part_014.c) and cross-checked against the direct Red Planet analog
// RP_L4\RPL4PB.cpp (RPSpoolerTask / RPL4PlaybackApplication), which is a line-
// for-line structural twin.
//
// The object file maps onto these recovered functions:
//
//   BTSpoolerTask --------------------------------- vtable @0051f204
//     @004d3b74  BTSpoolerTask::BTSpoolerTask          (ctor)
//     @004d3bb8  BTSpoolerTask::~BTSpoolerTask         (scalar-deleting dtor)
//     @004d3b90  BTSpoolerTask::DispatchPacket
//     @004d3ba8  BTSpoolerTask::GetTimeBias            (16-byte tail-call thunk)
//
//   BTL4PlaybackApplication ----------------------- vtable @0051f1a4
//     @004d3be4  BTL4PlaybackApplication::BTL4PlaybackApplication (ctor)
//     @004d3ea0  BTL4PlaybackApplication::~BTL4PlaybackApplication (dtor)
//     @004d40d8  BTL4PlaybackApplication::TestInstance (out of OPT vtable)
//     @004d3c14  BTL4PlaybackApplication::MakeNetworkManager
//     @004d3c34  BTL4PlaybackApplication::Initialize
//     @004d3c44  BTL4PlaybackApplication::LoadBackgroundTasks
//     @004d3d40  BTL4PlaybackApplication::ExecuteForeground
//     @004d3ecc  BTL4PlaybackApplication::DispatchPacket
//     @004d3ef8  BTL4PlaybackApplication::RunMissionMessageHandler
//     @004d3f44  BTL4PlaybackApplication::StopMissionMessageHandler
//     (ExecuteBackgroundTask is inherited from L4Application -- the OPT build
//      did not emit a BT override; see note at that method.)
//
// Engine-helper name mapping (FUN_ / DAT_ -> intended symbol):
//     FUN_0047c5d8  SpoolerTask::SpoolerTask              (base ctor)
//     FUN_0047c734  SpoolerTask::~SpoolerTask             (base dtor)
//     FUN_004d34c4  BTL4Application::BTL4Application       (base ctor, btl4app.cpp)
//     FUN_004d350c  BTL4Application::~BTL4Application      (base dtor)
//     FUN_004d3a2c  BTL4Application::RunMissionMessageHandler
//     FUN_004d3a94  BTL4Application::StopMissionMessageHandler
//     FUN_0047b468  L4Application::InitializeTillConsole
//     FUN_0047bfc4  L4PlaybackNetworkManager::L4PlaybackNetworkManager (0x28)
//     FUN_00402298  operator new (Make_Pointer)
//     FUN_004022d0  operator delete
//     FUN_0044de1c  TaskManager::AddTask(task)            (backgroundTasks@+0x64)
//     FUN_0044dd08  ApplicationTask::ApplicationTask(priority)
//     FUN_0041d7f0  NetworkManager::GetNetworkClientPointer(clientID)
//     FUN_00414b60  Now() -> Time*
//     FUN_0044f850  SpoolFile::NextPacket
//     FUN_0044e13c  Application::GetApplicationManager
//     FUN_0044fac0  MissionReviewApplicationManager::ReleaseSpoolFile
//     FUN_00403ad0  Group::FindGroup(root, name)
//     FUN_0042efe4  CameraDirector::FindPlayerByRank(rank)
//     FUN_00421414/00421452  ChainIteratorOf<Node*> ctor/dtor
//     FUN_0049fb74  Mech::Reset(origin, MissionReviewReset)
//     FUN_00419cb8  Entity::FlushEvents
//     FUN_00436668/004364e4  rebuild/replace name bitmap entry
//     FUN_00460840  DPLRenderer::SortAndReloadNameBitmaps
//     FUN_0045a7a8  DPLRenderer::SetViewAngle
//     FUN_0041a1a4  IsDerivedFrom(classDerivations)
//     DAT_004efc94  the global Application*
//     DAT_004efc98  Exit_Code
//     DAT_004fd550  the global mission-review mode (== 2 -> interactive review)
//
// Observed Application sub-object byte offsets (this == Application*):
//     +0x18  spoolFile          (inherited L4Application member; SetSpoolFile)
//     +0x20  networkManager
//     +0x24  entityManager
//     +0x34  updateManager
//     +0x38  rendererManager
//     +0x3c  controlsManager
//     +0x40  intercomManager
//     +0x48  videoRenderer (DPLRenderer)
//     +0x58  secondsRemainingInGame   +0x5c  gameStarted
//     +0x64  backgroundTasks
//     +0x70  executeFrames            +0x88  applicationState
//     +0xc8  currentMission           +0xd4  timeBias (BTL4PlaybackApplication)
//

#include <bt.hpp>
#pragma hdrstop

#if !defined(BTL4PB_HPP)
#	include <btl4pb.hpp>
#endif
#if !defined(L4VIDEO_HPP)
#	include <l4video.hpp>
#endif
#if !defined(MISSION_HPP)
#	include <mission.hpp>
#endif
#if !defined(L4NET_HPP)
#	include <l4net.hpp>
#endif
#if !defined(UPDATE_HPP)
#	include <update.hpp>
#endif
#if !defined(RENDERER_HPP)
#	include <renderer.hpp>
#endif
#if !defined(ICOM_HPP)
#	include <icom.hpp>
#endif
#if !defined(DROPZONE_HPP)
#	include <dropzone.hpp>
#endif
#if !defined(DIRECTOR_HPP)
#	include <director.hpp>
#endif
#if !defined(NTTMGR_HPP)
#	include <nttmgr.hpp>		// EntityManager / EntityGroup / FindGroup
#endif
#if !defined(MECH_HPP)
#	include <mech.hpp>
#endif

//#############################################################################
//##########################    BTSpoolerTask    ##############################
//#############################################################################

//
//#############################################################################
// @004d3b74 -- chains to SpoolerTask() (FUN_0047c5d8) then stamps the
// BTSpoolerTask vtable (&0051f204).  Adds no data of its own.
//#############################################################################
//
BTSpoolerTask::BTSpoolerTask()
{
}

//
//#############################################################################
// @004d3bb8 -- scalar-deleting destructor; reinstalls the vtable and chains
// to ~SpoolerTask (FUN_0047c734).
//#############################################################################
//
BTSpoolerTask::~BTSpoolerTask()
{
	Check_Fpu();
}

//
//#############################################################################
// @004d3b90 -- the spooler's per-packet callback simply forwards to the
// running playback application (the global Application*), exactly as the Red
// Planet RPSpoolerTask::DispatchPacket does.
//#############################################################################
//
void
	BTSpoolerTask::DispatchPacket(NetworkPacket *packet)
{
	BTL4PlaybackApplication *playback_app =
		Cast_Object(BTL4PlaybackApplication*, application);
	playback_app->DispatchPacket(packet);
	Check_Fpu();
}

//
//#############################################################################
// @004d3ba8 -- 16-byte tail-call thunk: returns the application's playback
// time bias so the spooler can re-time recorded packets.
//#############################################################################
//
long
	BTSpoolerTask::GetTimeBias()
{
	BTL4PlaybackApplication *playback_app =
		Cast_Object(BTL4PlaybackApplication*, application);
	return playback_app->GetTimeBias();
}


//#############################################################################
//#######################    BTL4PlaybackApplication    #######################
//#############################################################################

//#############################################################################
// Message Support
//
const Receiver::HandlerEntry
	BTL4PlaybackApplication::MessageHandlerEntries[]=
{
	MESSAGE_ENTRY(BTL4PlaybackApplication, RunMission),		// handler @004d3ef8
	MESSAGE_ENTRY(BTL4PlaybackApplication, StopMission)		// handler @004d3f44
};

Receiver::MessageHandlerSet&
	BTL4PlaybackApplication::GetMessageHandlers()
{
	static Receiver::MessageHandlerSet messageHandlers(
		ELEMENTS(BTL4PlaybackApplication::MessageHandlerEntries),
		BTL4PlaybackApplication::MessageHandlerEntries,
		BTL4Application::GetMessageHandlers()			// inherited Application set
	);
	return messageHandlers;
}

//#############################################################################
// Shared Data Support
//
Derivation*
	BTL4PlaybackApplication::GetClassDerivations()		// @0051ee2c
{
	static Derivation classDerivations(
		BTL4Application::GetClassDerivations(),
		"BTL4PlaybackApplication"						// @0051f182
	);
	return &classDerivations;
}

BTL4PlaybackApplication::SharedData
	BTL4PlaybackApplication::DefaultData(				// @0051ee5c
		BTL4PlaybackApplication::GetClassDerivations(),
		BTL4PlaybackApplication::GetMessageHandlers()
	);

//
//#############################################################################
// BTL4PlaybackApplication                                          @004d3be4
//#############################################################################
//
// Base call FUN_004d34c4(this, resource, 0x3eb, DefaultData) in the DOS binary:
// BattleTech's BTL4Application ctor there took (ResourceFile*, ClassID,
// SharedData&) -- the DOS build carried no HINSTANCE/HWND.  0x3eb is the
// L4ApplicationClassID.  For the WinTesla port the L4Application base ctor
// requires (HINSTANCE, HWND, ...), so those are threaded through here exactly
// as RPL4PlaybackApplication does.  The spool file is stashed in the inherited
// spoolFile slot (this+0x18).
//
	BTL4PlaybackApplication::BTL4PlaybackApplication(
		HINSTANCE hInstance,
		HWND hWnd,
		ResourceFile *resource_file,
		SpoolFile *spool_file
	):
		BTL4Application(hInstance, hWnd, resource_file, L4ApplicationClassID, DefaultData)
	{
		// vtable installed by the compiler == &PTR_FUN_0051f1a4
		spoolFile = spool_file;			// this[6] == this+0x18
		Check_Fpu();
	}

//
//#############################################################################
// ~BTL4PlaybackApplication                                         @004d3ea0
//#############################################################################
//
	BTL4PlaybackApplication::~BTL4PlaybackApplication()
	{
		// scalar-deleting dtor; reinstalls vtable and chains to ~BTL4Application
		Check_Fpu();
	}

//
//#############################################################################
// MakeNetworkManager                                               @004d3c14
//#############################################################################
//
// new(0x28)/FUN_0047bfc4 -- identical to RP: the playback application drives a
// L4PlaybackNetworkManager rather than a live one.
//
	NetworkManager*
		BTL4PlaybackApplication::MakeNetworkManager()
	{
		Check_Fpu();
		return new L4PlaybackNetworkManager;
	}

//
//#############################################################################
// Initialize                                                       @004d3c34
//#############################################################################
//
	void
		BTL4PlaybackApplication::Initialize()
	{
		Check(this);
		L4Application::InitializeTillConsole();		// FUN_0047b468
	}

//
//#############################################################################
// LoadBackgroundTasks                                              @004d3c44
//#############################################################################
//
// Adds the playback task pipeline.  The first task is our own BTSpoolerTask
// (new/FUN_004d3b74); the remaining four are stock ApplicationTasks built with
// priority 0x48 and the engine task vtables observed in the decomp.  (In the
// shipped binary the trailing tasks are only distinguished by their vtable
// pointers; the RP analog names them ProcessEventTask, GaugeRendererTask,
// CompleteCyclesTask, FryDeathRowTask, AudioRendererTask -- BT emits five of
// these stock tasks plus the spooler.  Names below are best-effort against RP.)
//
	void
		BTL4PlaybackApplication::LoadBackgroundTasks()
	{
		Check(this);

		ApplicationTask *application_task;

		application_task = new BTSpoolerTask;			// FUN_004d3b74
		Register_Object(application_task);
		backgroundTasks->AddTask(application_task);		// FUN_0044de1c(this+0x64,..)

		application_task = new ProcessEventTask;		// vtable &PTR_FUN_004f037c
		Register_Object(application_task);
		backgroundTasks->AddTask(application_task);

		application_task = new GaugeRendererTask;		// vtable &PTR_FUN_004f031c
		Register_Object(application_task);
		backgroundTasks->AddTask(application_task);

		application_task = new CompleteCyclesTask;		// vtable &PTR_FUN_004f0304
		Register_Object(application_task);
		backgroundTasks->AddTask(application_task);

		application_task = new AudioRendererTask;		// vtable &PTR_FUN_004f0364
		Register_Object(application_task);
		backgroundTasks->AddTask(application_task);

		Check_Fpu();
	}

//
//#############################################################################
// ExecuteForeground                                                @004d3d40
//#############################################################################
//
// The mission-review frame loop.  Structurally the RP ExecuteForeground:
// when idle/not executing it merely services the video renderer; otherwise it
// runs the controls manager (only in interactive review, DAT_004fd550==2), the
// update manager, the interest/renderer managers, the intercom, and -- when
// actually running a mission (state==5) -- recomputes the remaining game time.
//
	Logical
		BTL4PlaybackApplication::ExecuteForeground(
			Time start_of_frame,
			Scalar frame_duration
		)
	{
		SET_FOREGROUND_PROCESSING();

		Check(this);

		//
		// executeFrames(+0x70)==0, or state(+0x88)==WaitingForEgg(1): idle.
		//
		if (!executeFrames || GetApplicationState() == WaitingForEgg)
		{
			videoRenderer->ExecuteIdle();
			CLEAR_FOREGROUND_PROCESSING();
			return executeFrames && !Exit_Code;			// DAT_004efc98
		}

		Time frame_ticks;
		frame_ticks = frame_duration;					// Time::operator=(Scalar)

		//
		// Controls only matter in interactive ("scrub") review mode.
		//
		if (GetMissionReviewMode() == 2)				// DAT_004fd550
		{
			Check(controlsManager);
			controlsManager->Execute();					// vtbl+0x18
		}

		//
		// Update manager -> interest manager -> renderer manager -> intercom.
		//
		SET_UPDATE_MANAGER();
		Check(updateManager);
		updateManager->Execute(start_of_frame);			// FUN_00435794 / FUN_004363ec
		CLEAR_UPDATE_MANAGER();

		Check(rendererManager);
		rendererManager->UpdateInterestOrigins(start_of_frame);	// FUN_00434a08
		Check(interestManager);
		interestManager->Execute();						// FUN_0043649c

		SET_RENDERER_MANAGER();
		Check(rendererManager);
		rendererManager->Execute(start_of_frame, frame_ticks, frame_ticks);	// FUN_00436440
		CLEAR_RENDERER_MANAGER();

		Check(intercomManager);
		intercomManager->Execute();						// (this+0x40)->vtbl+0x24

		//
		// Running a recorded mission: keep the on-screen game clock current.
		//
		if (GetApplicationState() == RunningMission)	// state(+0x88)==5
		{
			// The decomp showed "(Now()-gameStarted)/TicksPerSecond": that
			// division reflects the DOS Time-in-ticks representation.  In the
			// ported engine Time::operator-(Time) already yields Scalar seconds
			// (time.h Time_To_Float), so the value is used directly here -- the
			// same net result, matching the RP twin.
			secondsRemainingInGame =					// +0x58
				currentMission->GetGameLength()			// (this+0xc8)+0xe0
					- (Now() - gameStarted);			// +0x5c
		}

		CLEAR_FOREGROUND_PROCESSING();
		Check_Fpu();
		return executeFrames && !Exit_Code;
	}

//
//#############################################################################
// ExecuteBackgroundTask
//#############################################################################
//
// NOTE: the OPT build did not emit a BT-specific override for this slot -- the
// playback application inherits L4Application::ExecuteBackgroundTask unchanged
// (the RP twin's body -- "if (ProcessOneEvent(HighEventPriority)) return; else
// backgroundTasks->Execute();" -- already lives in the L4 base here).  Declared
// in the header for completeness; reconstructed best-effort to match RP.
//
	void
		BTL4PlaybackApplication::ExecuteBackgroundTask()
	{
		Check(this);

		if (ProcessOneEvent(HighEventPriority))
		{
			return;
		}

		Check(backgroundTasks);
		backgroundTasks->Execute();
	}

//
//#############################################################################
// DispatchPacket                                                   @004d3ecc
//#############################################################################
//
// Route a recorded packet to its client.  packet->clientID is the first word
// of the NetworkPacket; messageData lives at packet+0x10.
//
	void
		BTL4PlaybackApplication::DispatchPacket(NetworkPacket *packet)
	{
		Check(this);

		NetworkManager *net_mgr = GetNetworkManager();		// this+0x20
		Check(net_mgr);
		NetworkClient *client =
			net_mgr->GetNetworkClientPointer(packet->clientID);	// FUN_0041d7f0
		Check(client);
		client->ReceiveNetworkPacket(packet, &packet->messageData);	// vtbl+0x14
		Check_Fpu();
	}

//
//#############################################################################
// RunMissionMessageHandler                                         @004d3ef8
//#############################################################################
//
// On the very first launch (state==WaitingForLaunch, 3) synchronise the
// playback clock: timeBias = Now - (timestamp of the spooled RunMission
// packet), then advance the spool one packet.  Then chain to the base.
//
	void
		BTL4PlaybackApplication::RunMissionMessageHandler(
			RunMissionMessage *message
		)
	{
		Check(this);

		if (GetApplicationState() == WaitingForLaunch)		// state(+0x88)==3
		{
			SpoolFile *spool = GetSpoolFile();				// this+0x18
			Check(spool);
			NetworkPacket *packet = (NetworkPacket*)spool->GetPointer();	// vtbl slot0
			timeBias =										// this+0xd4
				Now().ticks - packet->timeStamp.ticks;		// FUN_00414b60, packet+0xc
			spool->NextPacket();							// FUN_0044f850
		}

		BTL4Application::RunMissionMessageHandler(message);	// FUN_004d3a2c
		Check_Fpu();
	}

//
//#############################################################################
// StopMissionMessageHandler                                        @004d3f44
//#############################################################################
//
char winners_spot[]="win?";			// @0051ee63 (literal "win?\0")

	void
		BTL4PlaybackApplication::StopMissionMessageHandler(
			StopMissionMessage *message
		)
	{
		MissionReviewApplicationManager *app_mgr = GetApplicationManager();	// FUN_0044e13c
		Check(app_mgr);

		SpoolFile *spool = GetSpoolFile();					// this+0x18
		if (spool)
		{
			app_mgr->ReleaseSpoolFile(spool);				// FUN_0044fac0
			spoolFile = NULL;

			//
			// Move every player into the dropzone matching its finishing rank
			// (the "winners circle").
			//
			EntityManager *entity_manager = application->GetEntityManager();	// DAT_004efc94+0x24
			EntityGroup *dropzones = entity_manager->FindGroup("DropZones");	// FUN_00403ad0
			char *place = winners_spot + 3;					// the trailing '?'

			Player *p;
			for (
				int rank = 0;
				(p = CameraDirector::FindPlayerByRank(rank)) != NULL;	// FUN_0042efe4
				++rank
			)
			{
				*place = (char)('1' + rank);				// "win1".."winN"

				ChainIteratorOf<Node*> iterator(dropzones->groupMembers);	// FUN_00421414
				DropZone *dropzone;
				while ((dropzone = (DropZone*)iterator.ReadAndNext()) != NULL)
				{
					if (!strcmp(dropzone->GetDropZoneName(), winners_spot))	// name@+0x1c8
					{
						break;
					}
				}
				if (!dropzone)
				{
					continue;
				}

				//
				// Park the player's Mech on that dropzone for the freeze-frame.
				//
				Mech *mech = (Mech*)p->GetPlayerVehicle();	// p+0x1fc
				if (mech->GetClassID() == MechClassID)		// classID(+4)==0xbb9
				{
					mech->Reset(dropzone->localOrigin, Mech::MissionReviewReset);	// FUN_0049fb74, +0x100, 0
					mech->SetPerformance(&Mech::DoNothing);	// mech[7..9]=&PTR_0051ee69..
					mech->FlushEvents();					// FUN_00419cb8

					//
					// Refresh this player's name bitmap entry.
					//
					// RECONSTRUCTION NOTE: the shipped BTL4OPT.EXE here built a
					// per-entity NameBitmapEntry (FUN_00436668) and handed it to
					// RendererManager::ReplaceNameBitmap (FUN_004364e4).  That
					// per-entry name-bitmap API belonged to the original Division
					// IG image-generator renderer and was removed wholesale in the
					// WinTesla port (L4D3D); RendererManager / NameBitmapEntry no
					// longer expose it.  The bulk reload below
					// (SortAndReloadNameBitmaps) -- which the port retained and the
					// RP twin RPL4PlaybackApplication::StopMissionMessageHandler
					// relies on alone -- performs the equivalent refresh, so the
					// per-entry rebuild is intentionally folded into it here.
				}
			}

			//
			// Re-sort the name bitmaps and tilt the camera up for the circle.
			//
			DPLRenderer *dpl_renderer = GetVideoRenderer();	// this+0x48
			Check(dpl_renderer);
			dpl_renderer->SortAndReloadNameBitmaps();		// FUN_00460840
			dpl_renderer->SetViewAngle(Degree(45.0f));		// FUN_0045a7a8(.,4); cf. literal @0051ee66
		}

		//
		// Let the application stop normally.
		//
		BTL4Application::StopMissionMessageHandler(message);	// FUN_004d3a94
		Check_Fpu();
	}

//
//#############################################################################
// TestInstance                                                     @004d40d8
//#############################################################################
//
	Logical
		BTL4PlaybackApplication::TestInstance() const
	{
		return IsDerivedFrom(*GetClassDerivations());		// FUN_0041a1a4(.,0x51ee2c)
	}
