#include "mungal4.h"
#pragma hdrstop

#include "l4splr.h"
#include "..\munga\mission.h"
#include "..\munga\controls.h"
#include "..\munga\appmsg.h"

//#############################################################################
//#######################   SpoolingInterestManager   #########################
//#############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
SpoolingInterestManager::SpoolingInterestManager()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
SpoolingInterestManager::~SpoolingInterestManager()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	SpoolingInterestManager::LoadInterestArenas(Mission *)
{
	Check(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	SpoolingInterestManager::LoadMission(Mission *)
{
	Check(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	SpoolingInterestManager::ReceiveNetworkPacket(
		NetworkPacket *packet,
		Receiver::Message *
	)
{
	//
	//--------------------------------------------------------------------
	// All network messages coming to the interest manager will be spooled
	//--------------------------------------------------------------------
	//
	packet->timeStamp = Now();		// HACK - spoof the clock stuff
	Check(l4_spooling_application);
	SpoolFile *spool = l4_spooling_application->GetSpoolFile();
	Check(spool);
	spool->SpoolPacket(packet);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	SpoolingInterestManager::Shutdown()
{
	Check(this);
}

//#############################################################################
//######################   L4SpoolingNetworkManager   #########################
//#############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
L4SpoolingNetworkManager::L4SpoolingNetworkManager()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	L4SpoolingNetworkManager::StartConnecting(Mission *mission)
{
	//
	//--------------------------------------------------------
	// Let the L4 network manager start the connection process
	//--------------------------------------------------------
	//
	L4NetworkManager::StartConnecting(mission);

	//
	//---------------------------------
	// Local the spool file to write to
	//---------------------------------
	//
	Check(l4_spooling_application);
	SpoolFile *spool = l4_spooling_application->GetSpoolFile();
	Check(spool);

	*(ApplicationID*)spool->GetPointer() = application->GetApplicationID();
	spool->AdvancePointer(sizeof(ApplicationID));

	ResourceFile *res_file = application->GetResourceFile();
	Check(res_file);
	int major_version = res_file->versionArray[1];
	*(int*)spool->GetPointer() = major_version;
	spool->AdvancePointer(sizeof(major_version));

	//
	//-----------------------------------------------------------------
	// Now, write out the host IDs.  Follow each host ID with a logical
	// indicating whether it is local or remote
	//-----------------------------------------------------------------
	//
	HostManager *host_mgr = application->GetHostManager();
	Check(host_mgr);
	Mission::HostIterator mission_host_iterator(mission);
	MissionHostData *mission_host_data;

	while ((mission_host_data = mission_host_iterator.ReadAndNext()) != NULL)
	{
		CString host_name(mission_host_data->GetAddressString());
		SOCKADDR_IN net_address;
		
		ResolveAddress(host_name, &net_address);
		Host *host = host_mgr->FindHost(net_address);
		
		*(Logical*)spool->GetPointer() = (host != host_mgr->GetLocalHost());
		spool->AdvancePointer(sizeof(Logical));
		
		*(HostID*)spool->GetPointer() = host->GetHostID();
		spool->AdvancePointer(sizeof(HostID));
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
L4SpoolingNetworkManager::~L4SpoolingNetworkManager()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	L4SpoolingNetworkManager::ReceiveNetworkPacket(
		NetworkPacket *packet,
		Receiver::Message *message
	)
{
	//
	//------------------------------------------------------
	// Any egg messages will be spooled before being sent on
	//------------------------------------------------------
	//
	if (message->messageID == ReceiveEggFileMessageID)
	{
		packet->timeStamp = Now();		// HACK - spoof the clock stuff
		Check(l4_spooling_application);
		SpoolFile *spool = l4_spooling_application->GetSpoolFile();
		Check(spool);
		spool->SpoolPacket(packet);
	}
	L4NetworkManager::ReceiveNetworkPacket(packet, message);
}

//#############################################################################
//######################   L4PlaybackNetworkManager   #########################
//#############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
L4PlaybackNetworkManager::L4PlaybackNetworkManager():
	NetworkManager(L4PlaybackNetworkManager::DefaultData)
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	L4PlaybackNetworkManager::StartConnecting(Mission *mission)
{
	//
	// Create the host iterator for the mission egg
	//
	Mission::HostIterator mission_host_iterator(mission);
	MissionHostData *mission_host_data;
	Check(application);
	SpoolFile *spool = application->GetSpoolFile();
	Check(spool);

	ApplicationID id = *(ApplicationID*)spool->GetPointer();
	spool->AdvancePointer(sizeof(id));
	int major_rev = *(int*)spool->GetPointer();
	spool->AdvancePointer(sizeof(major_rev));

	if (id != application->GetApplicationID())
	{
		DEBUG_STREAM << "\n\nError - Not a spool file for this application!\n" << std::flush;
		PostQuitMessage(AbortExitCodeID);
	}

	ResourceFile *resource_file = application->GetResourceFile();
	Check(resource_file);

	if (major_rev != resource_file->versionArray[1])
	{
		DEBUG_STREAM << "\n\nError - Spool file major data version should be "
			<< (int)resource_file->versionArray[1] << ", not " << major_rev
			<< "!\n" << std::flush;
		PostQuitMessage(AbortExitCodeID);
	}

	HostManager *host_mgr = application->GetHostManager();
	Check(host_mgr);

	//
	// Iterate through all the host addresses in the egg and create all the host
	// structures, note that there MUST be an entry in the egg for us.
	//
	while ((mission_host_data = mission_host_iterator.ReadAndNext()) != NULL)
	{
		Check(mission_host_data);
		CString host_name(mission_host_data->GetAddressString());

		Logical remote = *(Logical*)spool->GetPointer();
		spool->AdvancePointer(sizeof(Logical));
		HostID host_id = *(HostID*)spool->GetPointer();
		spool->AdvancePointer(sizeof(HostID));

		Host *my_host =
			new L4Host(
				host_id,
				mission_host_data->GetHostType(),
				NULL,
				INVALID_SOCKET,
				host_name
			);
		Register_Object(my_host);
		if (remote)
		{
			host_mgr->AdoptRemoteHost(my_host);
		}
		else
		{
			host_mgr->AdoptLocalHost(my_host);
		}
	}

	//
	// Now, just send the load message
	//
	Check(application);
	Application::Message
		load_message(
			Application::LoadMissionMessageID,
			sizeof(Application::Message)
		);
	application->Post(DefaultEventPriority, application, &load_message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	L4PlaybackNetworkManager::Shutdown()
{
	Check(this);
	NetworkManager::Shutdown();

	Check(application);
	HostManager *host_mgr = application->GetHostManager();
	Check(host_mgr);
	HostManager::RemoteHostIterator remote_hosts(host_mgr);
	Host *my_host;
	while ((my_host = remote_hosts.ReadAndNext()) != NULL)
	{
		Check(my_host);
		host_mgr->OrphanRemoteHost(my_host);
		Unregister_Object(my_host);
		delete my_host;
	}
	return True;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	L4PlaybackNetworkManager::CheckBuffers(NetworkPacket*)
{
	//
	// The buffers are always empty
	//
	return False;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
L4PlaybackNetworkManager::~L4PlaybackNetworkManager()
{
}

//#############################################################################
//######################    L4SpoolingApplication    ##########################
//#############################################################################

L4SpoolingApplication*&
	l4_spooling_application = (L4SpoolingApplication*&)application;

const Receiver::HandlerEntry
	L4SpoolingApplication::MessageHandlerEntries[]=
{
	MESSAGE_ENTRY(L4SpoolingApplication, RunMission),
	MESSAGE_ENTRY(L4SpoolingApplication, StopMission),
	MESSAGE_ENTRY(L4SpoolingApplication, AbortMission),
	MESSAGE_ENTRY(L4SpoolingApplication, LoadMission)
};

Receiver::MessageHandlerSet& L4SpoolingApplication::GetMessageHandlers()
{
	static Receiver::MessageHandlerSet messageHandlers(ELEMENTS(L4SpoolingApplication::MessageHandlerEntries), L4SpoolingApplication::MessageHandlerEntries, L4Application::GetMessageHandlers());
	return messageHandlers;
}

//#############################################################################
// Virtual Data support
//
Derivation* L4SpoolingApplication::GetClassDerivations()
{
	static Derivation classDerivations(L4Application::GetClassDerivations(), "L4SpoolingApplication");
	return &classDerivations;
}

L4SpoolingApplication::SharedData
	L4SpoolingApplication::DefaultData(
		L4SpoolingApplication::GetClassDerivations(),
		L4SpoolingApplication::GetMessageHandlers()
	);

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	L4SpoolingApplication::LoadMissionMessageHandler(Message *)
{
	applicationState.SetState(WaitingForLaunch);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	L4SpoolingApplication::RunMissionMessageHandler(RunMissionMessage *)
{
	Check(this);

	applicationState.SetState(RunningMission);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void L4SpoolingApplication::StopMissionMessageHandler(StopMissionMessage *message)
{
	if (!missionSpooled)
	{
		missionSpooled = True;
		//
		//-----------------------------------------------------------------------
		// Post this message again until the application is running
		//-----------------------------------------------------------------------
		//
		Time post_time = Now();
		post_time += 3.0f;

		Post(DefaultEventPriority, this, message, post_time);
	}
	else
	{
		MissionReviewApplicationManager *app_mgr = GetApplicationManager();
		Check(app_mgr);
		SpoolFile *spool = GetSpoolFile();
		Check(spool);
		app_mgr->StoreSpoolFile(spool);
		spoolFile = NULL;

		L4Application::StopMissionMessageHandler(message);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	L4SpoolingApplication::AbortMissionMessageHandler(
		AbortMissionMessage *message
	)
{
	Check(this);
	Check(spoolFile);
	spoolFile->Rewind();

	L4Application::AbortMissionMessageHandler(message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	L4SpoolingApplication::ReceiveNetworkPacket(
		NetworkPacket *packet,
		Receiver::Message *message
	)
{
	Check(this);

	//
	//------------------------------------------------------
	// Any egg messages will be spooled before being sent on
	//------------------------------------------------------
	//
	switch (message->messageID)
	{
	case StopMissionMessageID:
		if (missionSpooled)
		{
			packet->timeStamp = Now();		// HACK - spoof the clock stuff
			SpoolFile *spool = GetSpoolFile();
			Check(spool);
			spool->SpoolPacket(packet);
			break;
		}

	case RunMissionMessageID:
		packet->timeStamp = Now();		// HACK - spoof the clock stuff
		SpoolFile *spool = GetSpoolFile();
		Check(spool);
		spool->SpoolPacket(packet);
		break;
	}
	L4Application::ReceiveNetworkPacket(packet, message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
L4SpoolingApplication::L4SpoolingApplication(
	HINSTANCE hInstance,
	HWND hWnd,
	ResourceFile *resource_file,
	ApplicationID application_ID
):
	L4Application(
		hInstance,
		hWnd,
		resource_file,
		application_ID,
		L4ApplicationClassID,
		DefaultData
	)
{
	missionSpooled = False;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Registry*
	L4SpoolingApplication::MakeRegistry()
{
	return NULL;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
InterestManager*
	L4SpoolingApplication::MakeInterestManager()
{
	return new SpoolingInterestManager;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
NetworkManager*
	L4SpoolingApplication::MakeNetworkManager()
{
	return new L4SpoolingNetworkManager;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	L4SpoolingApplication::Initialize()
{
	Check(this);
	Application::Initialize();

	//
	// Create the console host and socket
	//
	L4NetworkManager *net_mgr = GetNetworkManager();
	Check(net_mgr);
	net_mgr->CreateConsoleHost();

	//
	// Allocate the spool file
	//
	MissionReviewApplicationManager *app_mgr = GetApplicationManager();
	Check(app_mgr);
	spoolFile = app_mgr->GetEmptySpoolFile();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	L4SpoolingApplication::LoadBackgroundTasks()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
VideoRenderer*
	L4SpoolingApplication::MakeVideoRenderer()
{
	return NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
AudioRenderer*
	L4SpoolingApplication::MakeAudioRenderer()
{
	return NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
GaugeRenderer*
	L4SpoolingApplication::MakeGaugeRenderer(int *secondaryIndex, int *aux1Index, int *aux2Index)
{
	return NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Mission*
	L4SpoolingApplication::MakeMission(
		NotationFile *notation_file,
		ResourceFile *resources
	)
{
	Check(this);
	Check(notation_file);
	Check(resources);

////////////	return new L4Mission(notation_file, resources);
	return new Mission(notation_file, resources);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Entity*
	L4SpoolingApplication::MakeViewpointEntity(Entity__MakeMessage *)
{
	return NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical L4SpoolingApplication::ExecuteForeground(Time, Scalar)
{
	SET_FOREGROUND_PROCESSING();
	Check(this);

	//
	//--------------------------------------------------------------------------
	// Controls Manager
	//
	// Poll all devices, update all control variables.
	//
	// This is executed before the update manager so that the
	// models have valid control values.  It is not necessary for
	// the controls to operate at the frame rate of this loop.  If
	// the controls manager can run run at a lower rate it can
	// throttle itself internally.
	//--------------------------------------------------------------------------
	//
	Check(controlsManager);
	controlsManager->Execute();

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void L4SpoolingApplication::ExecuteBackgroundTask()
{
	Check(this);

	Check(networkManager);
	if (!networkManager->RoutePacket())
	{
		ProcessOneEvent(DefaultEventPriority);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	L4SpoolingApplication::Shutdown(int remainingApps)
{
	Check(this);

	L4Application::Shutdown(remainingApps);
	return !Exit_Code && !missionSpooled;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
L4SpoolingApplication::~L4SpoolingApplication()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	L4SpoolingApplication::TestInstance() const
{
	return IsDerivedFrom(*GetClassDerivations());
}

//#############################################################################
//###########################    SpoolerTask    ###############################
//#############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
SpoolerTask::SpoolerTask()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	SpoolerTask::DispatchPacket(NetworkPacket *)
{
	Fail("SpoolerTask::DispatchPacket - shouldn't be here!");
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
long
	SpoolerTask::GetTimeBias()
{
	Fail("SpoolerTask::GetTimeBias - shouldn't be here!");
	return 0;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	SpoolerTask::Execute()
{
	Check(this);
	Check(application);

	SpoolFile* spool = application->GetSpoolFile();
	if (!spool)
	{
		return;
	}

	//
	//-----------------------------------------------------------------------
	// We have a spool file, so interpret it based upon the application state
	//-----------------------------------------------------------------------
	//
	Check(spool);
	NetworkPacket *packet;
	switch (application->GetApplicationState())
	{
	//
	//-----------------------------------------------------------------------
	// If we are waiting for an egg, post the egg messages to the application
	// from the spool
	//-----------------------------------------------------------------------
	//
	case Application::WaitingForEgg:
		packet = (NetworkPacket*)spool->GetPointer();
		while (packet->clientID == NetworkClient::NetworkManagerClientID)
		{
			spool->NextPacket();
			DispatchPacket(packet);
			packet = (NetworkPacket*)spool->GetPointer();
		}
		break;

	//
	//-------------------------------------------------------------------
	// If the application is loading, then move off the block of interest
	// messages into the interest manager
	//-------------------------------------------------------------------
	//
	case Application::CreatingMission:
   case Application::LoadingMission:
		packet = (NetworkPacket*)spool->GetPointer();
		while (
			packet->clientID != NetworkClient::ApplicationClientID
			 && spool->GetBytesRemaining() > 0
		)
		{
			spool->NextPacket();
			DispatchPacket(packet);
			packet = (NetworkPacket*)spool->GetPointer();
		}
		break;

	//
	//-------------------------------------------------------------------
	// These messages need to be doled out based upon the system time, as
	// modified by when the run message was received
	//-------------------------------------------------------------------
	//

	case Application::LaunchingMission:
	case Application::RunningMission:
		while (spool && spool->GetBytesRemaining() > 0)
		{
			packet = (NetworkPacket*)spool->GetPointer();
			Time when = packet->timeStamp;
			when.ticks += GetTimeBias();
			if (when > Now())
			{
				break;
			}
			spool->NextPacket();
			DispatchPacket(packet);
			spool = application->GetSpoolFile();
		}
		break;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
SpoolerTask::~SpoolerTask()
{
}
