#include "rpl4.h"
#pragma hdrstop

#include "rpl4app.h"
#include "rpl4vid.h"
#include "rpl4arnd.h"
#include "rpl4grnd.h"
#include "rpl4mssn.h"
#include "..\munga_l4\l4gauge.h"
#include "..\rp\rpreg.h"
#include "..\munga_l4\l4ctrl.h"
#include "..\munga_l4\l4mppr.h"
#include "..\munga\appmgr.h"
#include "rpl4mode.h"
#include "..\rp\vtv.h"
#include "rpl4mppr.h"
#include "..\rp\rpplayer.h"
#include "..\munga_l4\l4video.h"
#include "..\munga\dropzone.h"
#include "..\munga\director.h"
#include "..\munga\nttmgr.h"
#include "..\munga_l4\l4vb16.h"

//
//#############################################################################
// RPL4Application
//#############################################################################
//
const Receiver::HandlerEntry
	RPL4Application::MessageHandlerEntries[]=
{
	MESSAGE_ENTRY(RPL4Application, StopMission),
	MESSAGE_ENTRY(RPL4Application, WinnersCircle)
};

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

Derivation* RPL4Application::GetClassDerivations()
{
	static Derivation classDerivations(
		L4Application::GetClassDerivations(), "RPL4Application");
	return &classDerivations;
}

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

//
// How long to sit on black between the race fading out and the podium
// fading in. The race fade is FADE_OUT_TIME (half a second), so this is
// that plus a beat, to land on black rather than on the tail of it.
//
const Scalar winnersCircleFadeOutTime = 0.7f;

// and how quickly the stand comes up out of the black afterwards
const Scalar winnersCircleFadeInTime = 0.45f;

RPL4Application::RPL4Application(
	HINSTANCE hInstance,
	HWND hWnd,
	ResourceFile *resource_file,
	ClassID class_ID,
	SharedData &shared_data
):
	L4Application(hInstance, hWnd, resource_file, RPL4, class_ID, shared_data)
{
	Check_Fpu();
}

//
//#############################################################################
// ~RPL4Application
//#############################################################################
//
RPL4Application::~RPL4Application()
{
	Check_Fpu();
}

//
//#############################################################################
// ShowWinnersCircle
//#############################################################################
//
// The pod hall's award platform. Every map carries a "wcircle" stand at
// (1200,0,0) with eight ranked dropzones named win1..win8 on it - rank 1 at
// the front on the low tier, ranks 4-8 across the back on the high one. All
// this shipped; only the mission-review build ever drove it.
//
// Stand the finishers on their spots in finishing order, freeze them, and
// look back at the stand from in front of it.
//
void
	RPL4Application::ShowWinnersCircle()
{
	Check(this);

	// RP412PODIUM=0 skips the whole thing
	const char *podium_mode = getenv("RP412PODIUM");
	if (podium_mode != NULL && atoi(podium_mode) == 0)
	{
		DEBUG_STREAM << "WinnersCircle: disabled\n" << std::flush;
		return;
	}

	EntityManager *entity_manager = GetEntityManager();
	if (entity_manager == NULL)
	{
		return;
	}
	EntityGroup *dropzones = entity_manager->FindGroup("DropZones");
	if (dropzones == NULL)
	{
		DEBUG_STREAM << "WinnersCircle: no DropZones group\n" << std::flush;
		return;
	}

	//
	//---------------------------------------------------------------------
	// Walk the finishing order. Player::CalcRanking has been ranking every
	// scoring player by score each frame, in football as much as in a race
	// (CalcFootballRanking exists but is never called), so rank order is
	// meaningful in both.
	//---------------------------------------------------------------------
	//
	//
	//---------------------------------------------------------------------
	// The stand's own geometry first, before anybody stands on it.
	//
	// The eight spots are fixed furniture on every map, so reading them up
	// front gives one set of numbers to both jobs below: where to stand
	// the finishers, and where to put the camera. Only the first is
	// allowed to care who finished.
	//---------------------------------------------------------------------
	//
	enum { winnersSpotCount = 8 };
	DropZone *spot[winnersSpotCount];
	int spots_found = 0;

	char winners_spot[] = "win?";
	char *place = winners_spot + 3;

	for (int i = 0; i < winnersSpotCount; ++i)
	{
		*place = (char) ('1' + i);
		spot[i] = NULL;

		ChainIteratorOf<Node*> iterator(dropzones->groupMembers);
		DropZone *dropzone;
		while ((dropzone = (DropZone*) iterator.ReadAndNext()) != NULL)
		{
			if (!strcmp(dropzone->GetDropZoneName(), winners_spot))
			{
				spot[i] = dropzone;
				++spots_found;
				Point3D at = dropzone->localOrigin.linearPosition;
				DEBUG_STREAM << "WinnersCircle: " << winners_spot << " at "
					<< at.x << "," << at.y << "," << at.z << "\n" << std::flush;
				break;
			}
		}
	}

	if (spot[0] == NULL)
	{
		DEBUG_STREAM << "WinnersCircle: no win1 spot on this map\n" << std::flush;
		return;
	}

	//
	//---------------------------------------------------------------------
	// Stand the finishers up, in finishing order.
	//---------------------------------------------------------------------
	//
	int placed = 0;
	Player *p;
	for (int rank = 0; (p = CameraDirector::FindPlayerByRank(rank)) != NULL; ++rank)
	{
		if (rank >= winnersSpotCount)
		{
			break;			// only eight spots exist on the stand
		}
		if (spot[rank] == NULL)
		{
			continue;
		}

		Entity *vehicle = p->GetPlayerVehicle();
		if (vehicle == NULL || vehicle->GetClassID() != VTVClassID)
		{
			continue;
		}
		VTV *vtv = (VTV*) vehicle;
		vtv->Reset(spot[rank]->localOrigin, VTV::MissionReviewReset);
		vtv->SetPerformance(&VTV::DoNothing);
		vtv->FlushEvents();
		++placed;
	}

	if (placed == 0)
	{
		DEBUG_STREAM << "WinnersCircle: nobody to place\n" << std::flush;
		return;
	}

	//
	//---------------------------------------------------------------------
	// Switch the world back on.
	//
	// Dying collapses the view and sets the application "dead" until the
	// pilot reincarnates - while that flag is up the renderer skips every
	// static object, which is the whole map. A pilot killed near the buzzer
	// is still waiting for a respawn that will never come, so the flag is
	// still up and the podium would play out against a black screen.
	//
	// The mission-review build never hit this: it watches from a CameraShip,
	// which cannot die, so nothing ever set the flag there.
	//
	// Standing the finishers up IS the resurrection, so clear it.
	//---------------------------------------------------------------------
	//
	SetIsDead(false);

	DPLRenderer *dpl_renderer = GetVideoRenderer();
	if (dpl_renderer == NULL)
	{
		return;
	}

	//
	//---------------------------------------------------------------------
	// Your own vehicle is built as a cockpit with no hull, so from any
	// camera outside it there is nothing where you should be - on the stand
	// you would be the one empty spot. Turn it inside out before the shot.
	//---------------------------------------------------------------------
	//
	dpl_renderer->ShowViewpointFromOutside();

	//
	//---------------------------------------------------------------------
	// The name plates are drawn per rank slot, so they have to be re-sorted
	// now that the finishing order is final - otherwise the signs read in
	// whatever order the players were created.
	//---------------------------------------------------------------------
	//
	dpl_renderer->SortAndReloadNameBitmaps();

	//
	//---------------------------------------------------------------------
	// Widen to 45 degrees and pull back in front of the stand so the whole
	// line-up frames up. The stand runs from z~3 (rank 1, low) to z~39
	// (ranks 4-8, high) and x~1180..1219; at 45 degrees that needs roughly
	// 30 units of standoff. Eye is above the top tier looking slightly
	// down at the winner's spot.
	//
	// Framed off the stand, NOT off who is standing on it. This used to
	// average the filled spots and anchor on the first one filled, which
	// meant the camera moved with the head count: eight finishers put the
	// anchor in the middle of the tiers and the eye about 58 units out,
	// one finisher collapsed it onto the winner's spot and brought the eye
	// in to 36. Same stand, different shot per race - and per machine, if
	// a remote player's vehicle was not there to be placed. The furniture
	// does not move, so the camera does not either.
	//---------------------------------------------------------------------
	//
	Point3D standFront = spot[0]->localOrigin.linearPosition;

	//
	// The stand's own axis: back rows to the winner's spot, which points
	// out of the front however the map has the stand turned. Taken from
	// the geometry rather than assumed, so a map that mounts its stand at
	// another angle still gets photographed from the front.
	//
	Point3D standBack(0.0f, 0.0f, 0.0f);
	int back_count = 0;
	for (int i = 1; i < winnersSpotCount; ++i)
	{
		if (spot[i] != NULL)
		{
			standBack += spot[i]->localOrigin.linearPosition;
			++back_count;
		}
	}
	if (back_count > 0)
	{
		standBack.x /= (Scalar) back_count;
		standBack.y /= (Scalar) back_count;
		standBack.z /= (Scalar) back_count;
	}

	Vector3D facing;
	facing.x = standFront.x - standBack.x;
	facing.y = 0.0f;
	facing.z = standFront.z - standBack.z;
	Scalar reach = (Scalar) sqrt(facing.x * facing.x + facing.z * facing.z);
	if (reach < 0.01f)
	{
		// a one-spot stand, or all eight stacked: nothing to take a
		// direction from, so fall back to the way the shipped stands face
		facing.x = 0.0f;  facing.z = -1.0f;  reach = 1.0f;
	}
	//
	// Framing is tunable while the shot is being dialled in:
	//   RP412PODIUMSTANDOFF  distance out in front of the stand
	//   RP412PODIUMHEIGHT    eye height above the group
	//   RP412PODIUMAIM       height of the aim point above the group
	//
	//
	// Framed off the stand itself: down low and tilted up across the tiers,
	// which is how you photograph a podium. It is a balance in both
	// directions - drop the camera further or tilt harder and the sky takes
	// the top half while the winner's spot slides off the bottom; tilt down
	// instead and it becomes a floor plan. Pillarboxing to 4:3 is what lets
	// it sit this close without the platform trailing off at the sides.
	//
	Scalar standoff = 36.0f;
	Scalar height = 12.0f;
	Scalar aim_lift = 2.0f;
	const char *tune = getenv("RP412PODIUMSTANDOFF");
	if (tune != NULL && atof(tune) != 0.0)	standoff = (Scalar) atof(tune);
	tune = getenv("RP412PODIUMHEIGHT");
	if (tune != NULL && atof(tune) != 0.0)	height = (Scalar) atof(tune);
	tune = getenv("RP412PODIUMAIM");
	if (tune != NULL)						aim_lift = (Scalar) atof(tune);

	Point3D eye;
	eye.x = standFront.x + (facing.x / reach) * standoff;
	eye.y = standFront.y + height;
	eye.z = standFront.z + (facing.z / reach) * standoff;

	Point3D aim = standFront;
	aim.y += aim_lift;

	//
	// RP412PODIUMCAM=0 leaves the view in the cockpit, which is also the
	// way to tell a bad camera from a scene that is not drawing at all.
	//
	//
	// Clear the cockpit glass away. The MFDs and the radar sit over the
	// viewscreen like the pod's bezels and have nothing to say once the
	// race is over; the podium gets the whole canvas.
	//
	SVGA16 *cockpit = SVGA16::GetCockpit();
	if (cockpit != NULL)
	{
		cockpit->HideSecondaryDisplays();
	}

	//
	// Fog first: it pulls the clip plane in to 1100, and SetViewAngle is what
	// rebuilds the projection that reads it.
	//
	dpl_renderer->SetFogStyle(DPLRenderer::winnersCircleFogStyle);

	const char *camera_mode = getenv("RP412PODIUMCAM");
	if (camera_mode == NULL || atoi(camera_mode) != 0)
	{
		dpl_renderer->SetViewAngle(Degree(45.0f));
		dpl_renderer->SetPresentationCamera(eye, aim);
	}
	else
	{
		// still rebuild the projection so the new clip plane takes effect
		dpl_renderer->SetViewAngle(Degree(40.0f));
	}
	if (camera_mode != NULL && atoi(camera_mode) == 0)
	{
		DEBUG_STREAM << "WinnersCircle: presentation camera disabled\n"
			<< std::flush;
	}

	//
	// Everything is in place behind the black - bring it up.
	// RP412PODIUMFADEIN sets the ramp in seconds.
	//
	Scalar fade_in = winnersCircleFadeInTime;
	const char *fade_tune = getenv("RP412PODIUMFADEIN");
	if (fade_tune != NULL && atof(fade_tune) > 0.0)
	{
		fade_in = (Scalar) atof(fade_tune);
	}
	dpl_renderer->StartPresentationFadeIn(fade_in);

	//
	// placed is reported for diagnosis only - the camera numbers beside it
	// must not move when it does.
	//
	DEBUG_STREAM << "WinnersCircle: " << placed << " placed on "
		<< spots_found << " spots; aim "
		<< aim.x << "," << aim.y << "," << aim.z
		<< " eye " << eye.x << "," << eye.y << "," << eye.z
		<< "\n" << std::flush;
}

//
//#############################################################################
// StopMissionMessageHandler
//#############################################################################
//
// The mission is over. Show the podium, then let the base handler run - it
// puts the player into MissionEndingState, whose fade timer is what actually
// keeps the sim and the renderer alive until teardown.
//
void
	RPL4Application::StopMissionMessageHandler(StopMissionMessage *message)
{
	Check(this);

	//
	// StopMission arrives twice: once from the console at the buzzer, and
	// again from the player when the ending fade runs out - that second one
	// is what actually retires the application. Only the first is the end of
	// the race.
	//
	if (GetApplicationState() != Application::EndingMission)
	{
		//
		//-------------------------------------------------------------
		// Don't cut straight to the podium. The race gets its own
		// fade-to-black first - that fade is already running by the time
		// this returns - and the Winners Circle comes up out of the
		// black afterwards. Standing everyone up now would just fade out
		// the podium instead of the race.
		//
		// FADE_OUT_TIME is half a second; a beat more than that lands on
		// black rather than on the tail of the fade.
		//-------------------------------------------------------------
		//
		Receiver::Message podium_message(
			WinnersCircleMessageID, sizeof(Receiver::Message));

		Time event_time;
		event_time = Now();
		event_time += winnersCircleFadeOutTime;

		Post(LowEventPriority, this, &podium_message, event_time);

		DEBUG_STREAM << "WinnersCircle: race over, fading out\n" << std::flush;
	}

	L4Application::StopMissionMessageHandler(message);
	Check_Fpu();
}

//
//#############################################################################
// WinnersCircleMessageHandler
//#############################################################################
//
// The race has faded to black. Set the stand up behind the black and fade
// back in to it.
//
void
	RPL4Application::WinnersCircleMessageHandler(Receiver::Message *)
{
	Check(this);

	DEBUG_STREAM << "WinnersCircle: standing the finishers up\n" << std::flush;
	ShowWinnersCircle();
	Check_Fpu();
}

//
//#############################################################################
// MakeRegistry
//#############################################################################
//
Registry*
	RPL4Application::MakeRegistry()
{
	Check_Fpu();
	return new RPRegistry(GetResourceFile());
}

//
//#############################################################################
// MakeVideoRenderer
//#############################################################################
//
VideoRenderer*
	RPL4Application::MakeVideoRenderer()
{
	if (divisionParameters)
	{
		return
			new RPL4VideoRenderer(
				ghWnd,
				L4Application::mScreenWidth,
				L4Application::mScreenHeight,
				L4Application::mFullscreen,
				VisualInterestType,
				DefaultInterestDepth
			);
	}
	return NULL;
}

//
//#############################################################################
// MakeAudioRenderer
//#############################################################################
//
AudioRenderer*
	RPL4Application::MakeAudioRenderer()
{
	Check_Fpu();
//	char *blaster1, *blaster2;

//	blaster1 = getenv(FRONT_CARD_ENV_VAR);
//	blaster2 = getenv(REAR_CARD_ENV_VAR);

	//if (blaster1 != NULL && blaster2 != NULL)
	//{
		return new RPL4AudioRenderer(
			1000.0f,
			GetMissionReviewMode()
		);
	//}
//	return NULL;
}

//#############################################################################
// MakeModeManager
//#############################################################################
ModeManager*
	RPL4Application::MakeModeManager()
{
	RPL4ModeManager
		*manager = new RPL4ModeManager(RPL4ModeManager::ModeInitial);
	Check(manager);
	return manager;
}

//
//#############################################################################
// MakeGaugeRenderer
//#############################################################################
//
GaugeRenderer*
	RPL4Application::MakeGaugeRenderer(int *secondaryIndex, int *aux1Index, int *aux2Index)
{
	char *mode_string = getenv("L4GAUGE");
	if (mode_string != NULL && (secondaryIndex != NULL || aux1Index != NULL || aux2Index != NULL))
	{
      return new RPL4GaugeRenderer(!L4Application::mFullscreen, secondaryIndex, aux1Index, aux2Index);
	}
	return NULL;
}

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

	Check_Fpu();
	return new RPL4Mission(notation_file, resources);
}

//
//#############################################################################
// MakeViewpointEntity
//#############################################################################
//
Entity*
	RPL4Application::MakeViewpointEntity(Entity::MakeMessage *message)
{
	Check(this);

//	Entity
//		*viewing_entity = L4Application::MakeViewpointEntity(message);
//
//	if(viewing_entity)
//	{
//		Check_Fpu();
//		return viewing_entity;
//	}

	LBE4ControlsManager* controls =
		(LBE4ControlsManager *)GetControlsManager();
	Check(controls);

	Entity
		*viewing_entity = NULL;
	char
		*primary_mapping_name = NULL;

	switch(message->classToCreate)
	{
	case CameraShipClassID:
		//=======================================================================
		// Create Camera ship
		//=======================================================================
		{
			CameraShip
				*viewing_camera =
					CameraShip::Make((CameraShip::MakeMessage*)message);
			Check(viewing_camera);

			viewing_entity = viewing_camera;
			//--------------------------------------------------
			// Create controls mapping object
			//--------------------------------------------------
			CameraControlsMapper::SubsystemResource
				control_subsystem_resource;

			Str_Copy(
				control_subsystem_resource.subsystemName,
				"ControlsMapper",
				sizeof(control_subsystem_resource.subsystemName)
			);
			control_subsystem_resource.classID = TrivialSubsystemClassID;
			control_subsystem_resource.subsystemModelSize =
				sizeof(control_subsystem_resource);

			CameraControlsMapper
				*camera_mapper;

			switch (controls->primaryControlType)
			{
			case LBE4ControlsManager::PrimaryThrustMaster:
				Tell("RPL4Application, CameraShip using ThrustMaster\n");
				camera_mapper = new CameraThrustmasterMapper(
					viewing_camera,
					CameraShip::ControlsMapperSubsystem,
					&control_subsystem_resource
				);
				primary_mapping_name = "Thrustmaster";
				break;

			case LBE4ControlsManager::PrimaryRIO:
				Tell("RPL4Application, CameraShip using RIO\n");
				camera_mapper = new CameraRIOMapper(
					viewing_camera,
					CameraShip::ControlsMapperSubsystem,
					&control_subsystem_resource
				);
				primary_mapping_name = "L4";
				break;

			default:
				Tell("RPL4Application, CameraShip using default mapper\n");
				camera_mapper = new CameraL4Mapper(
					viewing_camera,
					CameraShip::ControlsMapperSubsystem,
					&control_subsystem_resource
				);
				// primary mapping name is NULL
				break;
			}

			//-------------------------------------------
			// Set mapping subsystem for this entity
			//-------------------------------------------
			Register_Object(camera_mapper);
			viewing_camera->SetMappingSubsystem(camera_mapper);
		}
		break;

	case VTVClassID:
		//=======================================================================
		// Create VTV
		//=======================================================================
		{
			VTV
				*viewing_VTV = VTV::Make((VTV::MakeMessage*)message);
			Check(viewing_VTV);

			viewing_entity = viewing_VTV;
			//--------------------------------------------------
			// Set control deadbanding
			//--------------------------------------------------
			controls->SetJoystickDeadBand(.1);
			controls->SetThrottleDeadBand(.1);
			controls->SetPedalsDeadBand(.1);
			//--------------------------------------------------
			// Create controls mapping object
			//--------------------------------------------------
			VTVControlsMapper::SubsystemResource
				control_subsystem_resource;
			Str_Copy(
				control_subsystem_resource.subsystemName,
				"ControlsMapper",
				sizeof(control_subsystem_resource.subsystemName)
			);
			control_subsystem_resource.classID = TrivialSubsystemClassID;
			control_subsystem_resource.subsystemModelSize =
				sizeof(control_subsystem_resource);
			control_subsystem_resource.segmentIndex = -1;
			control_subsystem_resource.subsystemFlags = 0;

			VTVControlsMapper
				*vtv_mapper;

			switch (controls->primaryControlType)
			{
			case LBE4ControlsManager::PrimaryThrustMaster:
				Tell("RPL4Application, VTV using ThrustMaster\n");
				vtv_mapper = new VTVThrustmasterMapper(
					viewing_VTV,
					VTV::ControlsMapperSubsystem,
					&control_subsystem_resource
				);
				primary_mapping_name = "Thrustmaster";
				break;

			case LBE4ControlsManager::PrimaryRIO:
				Tell("RPL4Application, VTV using RIO\n");
				vtv_mapper = new VTVRIOMapper(
					viewing_VTV,
					VTV::ControlsMapperSubsystem,
					&control_subsystem_resource
				);
				primary_mapping_name = "L4";
				break;

			default:
				DEBUG_STREAM << "*****VTV has no controls mapping!*****" << std::flush;
				Fail("RPL4Application::MakeViewpointEntity");
				vtv_mapper = NULL;
				// primary mapping name is NULL
				break;
			}

			//-------------------------------------------
			// Set mapping subsystem for this entity
			//-------------------------------------------
			Register_Object(vtv_mapper);
			viewing_VTV->SetMappingSubsystem(vtv_mapper);
			//-------------------------------------------
			// Enable collision detection for this entity
			//-------------------------------------------
			viewing_VTV->StartCollisionAssistant();
		}
		break;
	}
	Check(viewing_entity);

	//------------------------------------------------------------------------
	// Load up the control mapping stream for the entity
	//------------------------------------------------------------------------
	if (primary_mapping_name != NULL)
	{
		ResourceDescription
			*resource_description =
				application->GetResourceFile()->SearchList(
					viewing_entity->GetResourceID(),
					ResourceDescription::ControlMappingsListResourceType
				);

		if (resource_description != NULL)
		{
			Check(resource_description);

			Check(GetResourceFile());
			resource_description =
				GetResourceFile()->SearchList(
					resource_description->resourceID,
					primary_mapping_name
				);

			if (resource_description != NULL)
			{
				Check(resource_description);

				resource_description->Lock();
				controls->CreateStreamedMappings(
					viewing_entity,
					(int*)resource_description->resourceAddress
				);
				resource_description->Unlock();
			}
		}
	}

	//------------------------------------------------------------------------
	// Create gauges for this entity
	//------------------------------------------------------------------------
	RPL4GaugeRenderer
		*gauge_renderer = (RPL4GaugeRenderer *) GetGaugeRenderer();

	if (gauge_renderer != NULL)
	{
		Check(gauge_renderer);
		gauge_renderer->ConfigureForModel("Init", viewing_entity);
	}

	Check_Fpu();
	return viewing_entity;
}
