#include "rp.h"
#pragma hdrstop

#include "chute.h"
#include "..\munga\fileutil.h"
#include "vtv.h"
#include "..\munga\app.h"
#include "vtvmppr.h"

//#############################################################################
// Shared Data Support
//
Chute::SharedData
	Chute::DefaultData(
		Chute::GetClassDerivations(),
		Chute::MessageHandlers,
		Chute::GetAttributeIndex(),
		Chute::StateCount
	);

Derivation* Chute::GetClassDerivations()
{	static Derivation classDerivations(VTVSubsystem::GetClassDerivations(), "Chute");
	return &classDerivations;
}


//#############################################################################
// Messaging Support
//
const Receiver::HandlerEntry
	Chute::MessageHandlerEntries[]=
{
	MESSAGE_ENTRY(Chute, Activate),
	MESSAGE_ENTRY(Chute, ConfigureMappables),
	MESSAGE_ENTRY(Chute, ChooseButton)
};

Receiver::MessageHandlerSet
	Chute::MessageHandlers(
		ELEMENTS(Chute::MessageHandlerEntries),
		Chute::MessageHandlerEntries,
		VTVSubsystem::GetMessageHandlers()
	);

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Chute::ConfigureMappablesMessageHandler(
		ReceiverDataMessageOf<ControlsButton> *message
	)
{
	Check(this);
	Check(message);

	//---------------------------------------------------------------------
	// If the hardwired button was pressed, process presses of the mappable
	// buttons, otherwise it was released, so erase any temporary mappings
	//---------------------------------------------------------------------
	if (message->dataContents > 0)
	{
		EnterConfiguration(
			NULL,									// direct target (none here)
			this,									// receiver
			ActivateMessageID,				// activation message ID
			ChooseButtonMessageID			// configuration message ID
		);
	}
	else
	{
		ExitConfiguration();
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Chute::ActivateMessageHandler(
		ReceiverDataMessageOf<ControlsButton> *message
	)
{
	Check(this);
	Check(message);

	VTV* vtv = GetEntity();
	Check(vtv);
	Check(application);

	if (
		vtv->GetSimulationState() == VTV::BurningState
		 || application->GetApplicationState() != Application::RunningMission
	)
	{
		Check_Fpu();
		return;
	}

	if (message->dataContents > 0)
	{
		switch (GetSimulationState())
		{
		case Ready:
			if (vtv->BoosterOn() || DeviantChute())
			{
				Check_Fpu();
				return;
			}
			if (chutesRemaining > 0)
			{
				SetSimulationState(Deploy);
				deployTimeUsed = 0.0f;
				chuteOn = 1;
				--chutesRemaining;
				ForceUpdate();
				lastPerformance = Now();
				AlwaysExecute();
				Check_Fpu();
			}
			break;
		case Dragging:
			ReleaseChute();
			Check_Fpu();
			break;
		}
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Chute::ChooseButtonMessageHandler(
		ReceiverDataMessageOf<ControlsButton> *message
	)
{
	if (message->dataContents > 0)
	{
		//
		//---------------------------------------------------------------------
		// Find the control manager of the vehicle, and have it turn off an
		// existing mapping of a given type if it is there, otherwise it should
		// create a new one
		//---------------------------------------------------------------------
		//
		VTV* vtv = GetEntity();
		Check(vtv);
		VTVControlsMapper* controls =
			Cast_Object(
				VTVControlsMapper*,
				vtv->GetSubsystem(VTV::ControlsMapperSubsystem)
			);
		Check(controls);

		controls->AddOrErase(message->dataContents, this, ActivateMessageID);
	}
	Check_Fpu();
}

//#############################################################################
// Attribute Support
//
const Chute::IndexEntry
	Chute::AttributePointers[]=
{
	ATTRIBUTE_ENTRY(Chute, TimeToReady, timeToReady),
	ATTRIBUTE_ENTRY(Chute, ChutesRemaining, chutesRemaining),
	ATTRIBUTE_ENTRY(Chute, ChuteDirection, chuteDirection),
	ATTRIBUTE_ENTRY(Chute, ScaleFactor, scaleFactor),
	ATTRIBUTE_ENTRY(Chute, ChuteOn, chuteOn),
	ATTRIBUTE_ENTRY(Chute, PercentDone, percentDone)
};

Chute::AttributeIndexSet& Chute::GetAttributeIndex()
{
	static Chute::AttributeIndexSet attributeIndex(ELEMENTS(Chute::AttributePointers), Chute::AttributePointers, VTVSubsystem::GetAttributeIndex());
	return attributeIndex;
}

//#############################################################################
// Model Support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Chute::ReadUpdateRecord(Simulation::UpdateRecord *message)
{
	Check(this);
	Check_Pointer(message);
	VTVSubsystem::ReadUpdateRecord(message);
	UpdateRecord* record = (UpdateRecord*) message;

	chuteDirection = record->chuteDirection;
	chuteOn = record->chuteOn;
	scaleFactor = record->scaleFactor;
	lastPerformance = lastUpdate;

	Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Chute::WriteUpdateRecord(Simulation::UpdateRecord *record, int update_model)
{
	Check(this);
	Check_Pointer(record);

	VTVSubsystem::WriteUpdateRecord(record, update_model);
	UpdateRecord* update = (UpdateRecord*) record;

	update->recordLength = sizeof(*update);
	update->chuteDirection = chuteDirection;
	update->chuteOn = chuteOn;
	update->scaleFactor = scaleFactor;
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
static Vector3D forward(0.0f,0.0f,-1.0f);

void
	Chute::ChuteSimulation(Scalar time_slice)
{
	//
	//-------------------------
	// Apply the Chute
	//-------------------------
	//
	percentDone = percentDone = (reloadTime - timeToReady)/reloadTime;
	Check_Fpu();

	if(percentDone > 1.0f)
	{
		percentDone = 1.0f;
	}
	if(percentDone < 0.0f)
	{
		percentDone = 0.0f;
	}
	Verify(percentDone >= 0.0f);
	Verify(percentDone <= 1.0f);

	int sim_state = GetSimulationState();
	VTV *vtv = GetEntity();
	Check(vtv);

	switch(sim_state)
	{
		case Loading:
			timeToReady -= time_slice;
			if (timeToReady <=0.0f)
			{
				SetSimulationState(Ready);
				NeverExecute();
			}
			break;

		case Dragging:
			if (DeviantChute() || vtv->BoosterOn())
			{
				ReleaseChute();
			}
			else
			{
				ApplyDrag(time_slice);
			}
			break;

		case Deploy:
			if (DeviantChute() || vtv->BoosterOn())
			{
				ReleaseChute();
			}
			else
			{
				deployTimeUsed += time_slice;
				if (deployTimeUsed >= deployTime)
				{
					scaleFactor.x = 1.0f;
					scaleFactor.y = 1.0f;
					scaleFactor.z = 1.0f;
					SetSimulationState(Dragging);
					ForceUpdate();
				}
				else
				{
					deployTimeUsed += time_slice;
					scaleFactor.y = scaleFactor.x = ScaleXY();
					scaleFactor.z = ScaleZ();
					deployTimeUsed += time_slice;
				}
				AlwaysExecute();
			}
			ApplyDrag(time_slice);
			break;

		case Released:
			SetSimulationState(Loading);
			break;
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Chute::PointChute(Scalar)
{
	if (GetSimulationState() != Dragging || GetSimulationState() != Deploy)
	{
		Check_Fpu();
		return;
	}

	VTV* vtv = GetEntity();
	Check(vtv);
	if (!Small_Enough(vtv->localVelocity.linearMotion.LengthSquared()))
	{
		chuteDirection.Subtract(vtv->localVelocity.linearMotion, forward);
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Scalar
	Chute::ScaleZ()
{
	Scalar
		 top_half = 2*(deployTimeUsed) -1 / (deployTimeUsed) + 1;
	Scalar
		bottom_half = 2*(deployTime) -1 / (deployTime) + 1;

	Scalar result = top_half/bottom_half;

	Check_Fpu();
	return result;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Scalar Chute::ScaleXY()
{
	Scalar
		 top_half = (deployTimeUsed)*(deployTimeUsed)*(deployTimeUsed);
	Check_Fpu();

	Scalar
		bottom_half = (deployTime)*(deployTime)*(deployTime);

	Check_Fpu();
	Scalar result = top_half/bottom_half;

	Check_Fpu();

	return result;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Chute::ReleaseChute()
{
	SetSimulationState(Released);
	chuteOn = 0;
	ForceUpdate();
	if (GetEntity()->GetInstance() == VTV::ReplicantInstance)
	{
		NeverExecute();
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Chute::ApplyDrag(Scalar)
{
	//
	//--------------------------------------------------------------------------
	// Get pointers to the other subsystems we will have to deal with inside the
	// VTV
	//--------------------------------------------------------------------------
	//
	VTV* vtv = GetEntity();

	//
	//  If slow enough automatically release the chute
	//
	Scalar	vel2 =  vtv->localVelocity.linearMotion.LengthSquared();
	if(vel2 < minVelocity)
	{
		ReleaseChute();
		Check_Fpu();
		return;
	}

	//
	// Calculate the direction to point the chute in
	//
	chuteDirection.Subtract(vtv->localVelocity.linearMotion, forward);

	//
	//	Calculate the dragForce to Apply to the VTV
	//
	Vector3D force;
	force.Negate(vtv->localVelocity.linearMotion);
	force *= dragForce;
	vtv->ApplyLocalAcceleration(force, chuteOffset);
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	Chute::DeviantChute()
{
	Check(this);
	VTV *vtv = GetEntity();
	Check(vtv);
	Check_Fpu();
	return vtv->localVelocity.linearMotion.z > zDeviation;
}

//#############################################################################
// Damage Support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Chute::DeathReset(int reset_command)
{
	Check(this);
	if (reset_command == VTV::FootballReset)
	{
		chutesRemaining = chuteCount;
	}
	if (reset_command != VTV::MissionReviewReset)
	{
		SetSimulationState(Ready);
		timeToReady = 0.0f;
	}
	NeverExecute();
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

void
	Chute::DeathShutdown(int)
{
	chuteOn = False;
	chuteDirection = Quaternion::Identity;
	NeverExecute();
	Check_Fpu();
}

//#############################################################################
//   Contructor and Destructor
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Chute::Chute(
	VTV *entity,
	int subsystem_ID,
	SubsystemResource *subsystem_resource
):
	VTVSubsystem(
		entity,
		subsystem_ID,
		subsystem_resource,
		DefaultData,
		NULL,								// assumes no direct controls mapping
		Chute::ActivateMessageID	// message ID used for activation
	),
	scaleFactor(0.0f,0.0f,0.0f)
{
	//-------------------------------------------
	// Set values
	//-------------------------------------------
	chuteOn = 0;
	chuteCount = subsystem_resource->chuteCount;
	chutesRemaining = chuteCount;
	reloadTime = subsystem_resource->reloadTime;
	dragForce = subsystem_resource->dragForce;
	deployTime = subsystem_resource->deployTime;
	deployTimeUsed  = 0.0f;
	EntitySegment *site_front_segment =
		entity->GetSegment(subsystem_resource->segmentIndex);

	chuteOffset = site_front_segment->GetSegmentToEntity();

	minVelocity =
		subsystem_resource->minVelocity * subsystem_resource->minVelocity;
	zDeviation = subsystem_resource->zDeviation;
	timeToReady = 0.0f;
	chuteDirection = Quaternion::Identity;
	percentDone = 1.0f;
	SetSimulationState(Ready);
	SetPerformance(&Chute::ChuteSimulation);
	NeverExecute();
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Chute::~Chute()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	Chute::CreateStreamedSubsystem(
		NotationFile					*model_file,
		const char						*model_name,
		const char						*subsystem_name,
		SubsystemResource				*subsystem_resource,
		NotationFile					*subsystem_file,
		const ResourceDirectories	*directories
	)
{
	Check(model_file);
	Check_Pointer(subsystem_name);
	Check_Pointer(subsystem_resource);
	Check(subsystem_file);

	if (
		!Subsystem::CreateStreamedSubsystem(
			model_file,
         model_name,
			subsystem_name,
			subsystem_resource,
			subsystem_file,
			directories
		)
	)
	{
		return False;
	}

	subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource);
	subsystem_resource->classID = RegisteredClass::ChuteClassID;

	//
	// Figure out who our voltage source is
	//

	if (
		!subsystem_file->GetEntry(
			subsystem_name,
			"ChuteCount",
			&subsystem_resource->chuteCount
		)
	)
	{
		std::cerr << subsystem_name << " missing ChuteCount!\n";
		return False;
	}

	if (
		!subsystem_file->GetEntry(
			subsystem_name,
			"ReloadTime",
			&subsystem_resource->reloadTime
		)
	)
	{
		std::cerr << subsystem_name << " missing ReloadTime!\n";
		return False;
	}

	if (
		!subsystem_file->GetEntry(
			subsystem_name,
			"DragForce",
			&subsystem_resource->dragForce
		)
	)
	{
		std::cerr << subsystem_name << " missing DragForce!\n";
		return False;
	}
	if (
		!subsystem_file->GetEntry(
			subsystem_name,
			"MinVelocity",
			&subsystem_resource->minVelocity
		)
	)
	{
		std::cerr << subsystem_name << " missing MinVelocity!\n";
		return False;
	}
	if (
		!subsystem_file->GetEntry(
			subsystem_name,
			"DeployTime",
			&subsystem_resource->deployTime
		)
	)
	{
		std::cerr << subsystem_name << " missing DeployTime!\n";
		return False;
	}
	Scalar	degree;
	if (
		!subsystem_file->GetEntry(
			subsystem_name,
			"MaxRotation",
			&degree
		)
	)
	{
		std::cerr << subsystem_name << " missing MaxRotation!\n";
		return False;
	}
	else
	{
			Radian rad(degree);
			subsystem_resource->zDeviation = cos(rad);
	}
	if (subsystem_resource->dragForce < 0.0f)
	{
		std::cerr << subsystem_name << " must have a positive dragForce!\n";
		return False;
	}
	return True;
}

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