#include "MW4Headers.hpp"

#include "RTXWeaponSub.hpp"
#include "WeaponMover.hpp"
#include "SubsystemClassData.hpp"
#include "GUIWeaponManager.hpp"

#include <Adept\Site.hpp>
#include <Adept\Map.hpp>

//#############################################################################
//##########################    ExecutionStateEngine    #######################
//#############################################################################

RTXWeaponSub::ExecutionStateEngine::ClassData*
	RTXWeaponSub::ExecutionStateEngine::DefaultData = NULL;

const StateEngine::StateEntry
	RTXWeaponSub::ExecutionStateEngine::StateEntries[]=
{
	STATE_ENTRY(RTXWeaponSub__ExecutionStateEngine, Jammed),
	STATE_ENTRY(RTXWeaponSub__ExecutionStateEngine, Recharging)
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	RTXWeaponSub::ExecutionStateEngine::InitializeClass()
{
	Check_Object(StateEngine::DefaultData);
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			RTXWeaponSub__ExecutionStateEngineClassID,
			"RTXWeaponSub::ExecutionStateEngine",
			BaseClass::DefaultData,
			ELEMENTS(StateEntries), StateEntries,
			(Entity::ExecutionStateEngine::Factory)Make,
			(Entity::ExecutionStateEngine::FactoryRequest::Factory)
				&FactoryRequest::ConstructFactoryRequest
		);
	Register_Object(DefaultData);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RTXWeaponSub::ExecutionStateEngine*
	RTXWeaponSub::ExecutionStateEngine::Make(
		RTXWeaponSub *weapon,
		FactoryRequest *request
	)
{
	Check_Object(weapon);
	Check_Object(request);
	gos_PushCurrentHeap(Heap);
	RTXWeaponSub::ExecutionStateEngine *engine =
		new RTXWeaponSub::ExecutionStateEngine(DefaultData, weapon, request);
	gos_PopCurrentHeap();
	return engine;
		
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	RTXWeaponSub::ExecutionStateEngine::RequestState(
		int new_state,
		void* data
	)
{
	Check_Object(this);
	Check_Object(owningEntity);

	//
	//------------------------------
	// Quick exit if no state change
	//------------------------------
	//
	if (new_state == currentState)
		return currentState;

	//
	//-------------------------------------------------------
	// If we are currently jammed, ignore a firing request
	//-------------------------------------------------------
	//
	if ( currentState == JammedState &&	( new_state == FiringState || new_state == ReloadingState ) )
	{
		return currentState;
	}

	if( new_state == FiringState && currentState == NeverExecuteState ) 
	{
		// Set the initial time of fire
		RTXWeaponSub* owner = Cast_Object( RTXWeaponSub*, owningEntity );		
		owner->m_fireStartTime = owner->m_fireEndTime = gos_GetElapsedTime();
	}

	if (new_state == JammedState)
	{
		RTXWeaponSub* owner = Cast_Object( RTXWeaponSub*, owningEntity );		
		Check_Object(owner);
		owner->StartJamState();
	}
	if (new_state == RechargingState)
	{
		RTXWeaponSub* owner = Cast_Object( RTXWeaponSub*, owningEntity );		
		Check_Object(owner);
		owner->m_enterRecharge = gos_GetElapsedTime();
	}
	//
	//
	//---------------------
	// Deal with our states
	//---------------------
	//
	return ( BaseClass::RequestState(new_state, data) );
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	RTXWeaponSub::ExecutionStateEngine::TestInstance() const
{
	Verify(IsDerivedFrom(DefaultData));
}


//#############################################################################
//###############################    RTXWeaponSub    ##########################
//#############################################################################

RTXWeaponSub::ClassData*
	RTXWeaponSub::DefaultData = NULL;


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	RTXWeaponSub::ExecutionStateEngine::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	RTXWeaponSub::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			RTXWeaponSubClassID,
			"MechWarrior4::RTXWeaponSub",
			ProjectileWeapon::DefaultData,
			0, NULL,
			(Entity::Factory)Make,
			(Entity::CreateMessage::Factory)CreateMessage::ConstructCreateMessage,
			ExecutionStateEngine::DefaultData,
			(Entity::GameModel::Factory)GameModel::ConstructGameModel,
			NULL,
			(Entity::GameModel::ReadAndVerifier)GameModel::ReadAndVerify,
			(Entity::GameModel::ModelWrite)GameModel::WriteToText,
			(Entity::GameModel::ModelSave)GameModel::SaveGameModel,
			(Subsystem::StreamCreate) CreateStream
		);
	Register_Object(DefaultData);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		RTXWeaponSub__GameModel,
		TimeToPotentialJam,
		m_timeToPotentialJam,
		Scalar
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		RTXWeaponSub__GameModel,
		TimeToJam,
		m_timeToJam,
		Scalar
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		RTXWeaponSub__GameModel,
		TimeToUnjam,
		m_timeToUnjam,
		Scalar
	);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	RTXWeaponSub::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RTXWeaponSub*
	RTXWeaponSub::Make(
		CreateMessage *message,
		ReplicatorID *base_id
	)
{
	Check_Object(message);
	gos_PushCurrentHeap(Heap);
	RTXWeaponSub *new_entity =
		new RTXWeaponSub(DefaultData, message, base_id, NULL);
	gos_PopCurrentHeap();
	Check_Object(new_entity);
	return new_entity;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RTXWeaponSub::RTXWeaponSub(
	ClassData *class_data,
	CreateMessage *message,
	ReplicatorID *base_id,
	ElementRenderer::Element *element
):
	ProjectileWeapon(class_data, message, base_id, element), 
		m_jamStartTime( 0 ), 
		m_fireStartTime( 0 ), 
		m_fireEndTime( 0 )
{
	Check_Pointer(this);
	Check_Object(message);
	m_jamThisFrame = false;

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RTXWeaponSub::~RTXWeaponSub()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	RTXWeaponSub::CreateProjectile(Time till)
{
	Check_Object(this);
	const GameModel *model = GetGameModel();
	Check_Object(model);

	if(model->projectileModelResource != ResourceID::Null)
	{
		//
		//---------------------------------
		//Get the ClassID from the Resource
		//---------------------------------
		//
		ClassID class_ID;
		class_ID = GetClassIDFromDataListID(model->projectileModelResource);
		Verify(class_ID != NullClassID);

		ReplicatorID site_id = ReplicatorID::Null;
		ReplicatorID target_id = ReplicatorID::Null;
		Normal3D target_normal;

		Motion3D initial_velocity = Motion3D::Identity;
		Motion3D initial_acceleration = Motion3D::Identity;
		Point3D site_local_to_world;

		//
		//-------------------------
		//Initialize the velocities
		//-------------------------
		//
		Check_Object(sitePointer);
		site_local_to_world = sitePointer->GetLocalToWorld();
		Vector3D projectile_direction;
		Point3D target_point;
		targetQuery.m_line->FindEnd(&target_point);
		projectile_direction.Subtract(
			target_point,
			site_local_to_world
		);
		Check_Object(sitePointer);
		site_local_to_world = sitePointer->GetLocalToWorld();

		LinearMatrix4D direction_matrix = LinearMatrix4D::Identity;
		direction_matrix.BuildTranslation((Stuff::Point3D)site_local_to_world);
		direction_matrix.AlignLocalAxisToWorldVector(projectile_direction, Z_Axis, Y_Axis, X_Axis);
		
		Vector3D initial_scale = model->initialLinearVelocity;
		if(GetParentAsVehicle())
		{
			initial_scale.z += GetParentAsVehicle()->currentSpeedMPS;
		}

		initial_velocity.linearMotion.Multiply(
			initial_scale,
			direction_matrix
		);

		initial_acceleration.linearMotion = model->initialLinearAcceleration;

		//
		//------------------------------------
		//Create the WeaponMover CreateMessage
		//------------------------------------
		//

		WeaponMover::CreateMessage projectile_create_message(
			sizeof(WeaponMover::CreateMessage),
			DefaultEventPriority,
			CreateMessage::DefaultFlags,
			class_ID,
			WeaponMover::DefaultFlags,
			model->projectileModelResource,
			direction_matrix,
			0.0f,
			WeaponMover::ExecutionStateEngine::StraightLineMotionState,
			NameTable::NullObjectID,
			Entity::DefaultAlignment,
			initial_velocity,
			initial_acceleration,
			model->damageAmount,
			model->heatToDeal,
			model->maxDistance,
// MSL 5.03 Min Distance
//			model->minDistance,
			GetParentVehicle()->GetReplicatorID(),
			model->splashRadius,
			model->percentageOfDamageToDirectHit,
			model->minPercentageOfDamageToSphereHit,
			model->maxPercentageOfDamageToSphereHit,
			model->itemID
		);
		MemoryStream stream(&projectile_create_message, projectile_create_message.messageLength);

		Entity *entity;
		ReplicatorID projectile_id = Connection::Hermit->GetNextReplicatorID();
		entity = CreateEntity(&stream, &projectile_id, false);
		Check_Object(entity);
		Map::GetInstance()->AddChild(entity);
		if (vehicleInterface)
		{
			vehicleInterface->PlayForce (VehicleInterface::FireProjectileForce);
		}
	}
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Adding function for this, so we can update Jam at this time.
// this allows the server to jam and tell the client that it did.
//
//
void
	RTXWeaponSub::Fire(Adept::Entity::CollisionQuery *query, 
		 Stuff::Time time_locked, 
		 const Stuff::Point3D& lockedTargetEntityPartOffset)
{
	
// MSL 5.03
	BaseClass::Fire(query, time_locked, lockedTargetEntityPartOffset);

	if (executionState->GetState() == ExecutionStateEngine::FiringState)
	{
		//
		// Only jam if we are the person firing, or we are the server
		//
		//	  If we don't restrict this, then another client may refuse to show the shots because it thinks that the
		//    gun is jammed when it isn't
		//

		Check_Object(GetParentVehicle());
		bool fServer = Network::GetInstance()->AmIServer(); 
		bool fLocalPlayer =	(GetReplicatorID().connectionID == Connection::Local->GetID());

		//
		//
		//
		if (fServer ||	fLocalPlayer) 
		{
			const GameModel* model = GetGameModel();
			Check_Object(model);
			Check_Object(executionState);
			// if we fired, check for jam
			m_fireEndTime = gos_GetElapsedTime();
			double cumulativeFiringTime = m_fireEndTime - m_fireStartTime;

			//
			// For servers, the rules are going to be different. If the fire time is at the absolute max, plus a buffer for lag
			// then we will gaurantee a jam on the server, and have it refresh.
			//
			// This allows for some tolerance of various net traffic since we'd still prefer that the 
			// client handle this, but puts a cap on what we tolerate from a potential cheater.
			if ( fLocalPlayer)
			{
				if ( cumulativeFiringTime >= model->m_timeToPotentialJam)
				{
					Stuff::Scalar idPermutation = (replicatorID.localID % 3)/3.0f; // have weapon's ID permutate it's jam time
					Stuff::Scalar r = (Stuff::Scalar)fmod( gos_GetElapsedTime()+idPermutation, 1.0 );

					Stuff::Scalar e = (Stuff::Scalar)( 
						( cumulativeFiringTime - model->m_timeToPotentialJam ) 
						/ model->m_timeToJam );
					// Potential to jam: 
					if ( r < e )
					{
						m_jamThisFrame = true;
 					}
				}					
			}
			else if (fServer)
			{
				if (cumulativeFiringTime >= (model->m_timeToJam + model->m_timeToPotentialJam + 2.0f))
				{
				  m_jamThisFrame = true;
				}
			}
		}
	}

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	RTXWeaponSub::PreCollisionExecute(Time till)
{
	Check_Object(this);
	PRECOLLISION_LOGIC("Subsystem::RTXWeaponSub");

	const GameModel* model = GetGameModel();
	Check_Object(model);
	Check_Object(executionState);

	Stuff::Scalar timeSlice = GetTimeSlice( till );

	int pre_state = executionState->GetState();
	switch (pre_state)
	{
		case ExecutionStateEngine::FiringState:
			{
				UsePostCollision();
				m_PlayedRechargeSound = false;
				break;
			}

		case ExecutionStateEngine::ReloadingState:
			{
				Scalar count = model->reloadTime - GetTimeParameter(till);
				Min_Clamp(count, 0.0f);
				SetCount(count);

				if (model->reloadTime > 0.49f)
				{
					if ((!m_PlayedRechargeSound) && (GetTimeParameter(till) >= model->reloadTime-0.5f))
					{
						if (GetParentVehicle ())
							if (GetParentVehicle()->vehicleInterface)
							{
								GetParentVehicle ()->vehicleInterface->ReactToEvent (VehicleInterface::RECHARGE_PROJECTILE);
							}
						m_PlayedRechargeSound = true;
					}
				}
				if(GetTimeParameter(till) > model->reloadTime)
				{
					executionState->RequestState(ExecutionStateEngine::RechargingState);
					if(guiWeapon.GetCurrent())
						guiWeapon.GetCurrent()->Ready();
					m_PlayedRechargeSound = false;
				}
				IgnorePostCollision();
			}

		break;
		case ExecutionStateEngine::RechargingState:
			{
				// In order to make the server and client agree on cumulative time more, I'll put a buffer in.
				// but if we don't get a new fire request with .25 seconds of entering this state we'll assume recharge.
				if ( (gos_GetElapsedTime() - m_enterRecharge ) >= 0.25f)
				{
					m_fireStartTime += timeSlice*2; 
					m_fireEndTime += timeSlice;

					double cumulativeFiringTime = m_fireEndTime - m_fireStartTime;
				 

					if ( cumulativeFiringTime <= 0 )
					{
						executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
					}
				}
			}
		break;
		case ExecutionStateEngine::JammedState:
			{
				if( gos_GetElapsedTime() - m_jamStartTime > model->m_timeToUnjam )
				{
					executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
					
					if(guiWeapon.GetCurrent())
						guiWeapon.GetCurrent()->Ready();
				}
				else
				{
					// Update the gui time
					if(guiWeapon.GetCurrent())
					{
						guiWeapon.GetCurrent()->SetJamCount( (Stuff::Scalar)(gos_GetElapsedTime() - m_jamStartTime) );
					}
				}
				IgnorePostCollision();
				break;
			}
	}
	Weapon::PreCollisionExecute(till);
}




//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	RTXWeaponSub::PostCollisionExecute(Time till)
{
	Check_Object(this);
 
	POSTCOLLISION_LOGIC("Subsystem::RTXWeaponSub");

	bool jammed = false;
	if ( executionState->GetState() == ExecutionStateEngine::FiringState )
	{
		if (m_jamThisFrame)
		{
			executionState->RequestState(ExecutionStateEngine::JammedState);
			jammed = true;
		}
	}
	
	if ( !jammed )
		ProjectileWeapon::PostCollisionExecute(till);
	m_jamThisFrame = false;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// This weapon needs to override this function. Unlike the normal projectile classes, this one requires that it
// be able to fire while in the "recharging" state.
//

bool
	RTXWeaponSub::ReadyToFire()
{
	Check_Object(this);
	Check_Object(executionState);

	// if this is the local mech, always true in single player.
	if (GetReplicatorID().connectionID == Connection::Local->GetID())
	{
		if (CanFireFromCurrentArm() == false)
		{
			return (false);
		}

		bool result;
		result = (executionState->GetState()==ExecutionStateEngine::NeverExecuteState ||
			  	executionState->GetState()==ExecutionStateEngine::RechargingState)
			&&	DoesHaveAmmo();
		return result;
	}



	if (executionState->GetState()==ExecutionStateEngine::ReloadingState)
	{
		const GameModel* model = GetGameModel();
	
		// 10% of the recharging plus the server-client packet time difference
		if (gos_GetElapsedTime() - lastParameterization >= (model->reloadTime - (0.1f * model->reloadTime)))
		{
			executionState->RequestState(ExecutionStateEngine::RechargingState);
		}

		return DoesHaveAmmo();
	}


	bool result;
	result = (executionState->GetState()==ExecutionStateEngine::NeverExecuteState ||
			  	executionState->GetState()==ExecutionStateEngine::RechargingState) &&
		DoesHaveAmmo();
	return result;



}


void RTXWeaponSub::StartJamState()
{
	const GameModel *model = GetGameModel();
	Check_Object(model);
	m_jamStartTime = gos_GetElapsedTime();
	if ( guiWeapon.GetCurrent() )
	{
		guiWeapon.GetCurrent()->RecoverFromJam();
		guiWeapon.GetCurrent()->SetJamCount( model->m_timeToUnjam );
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	RTXWeaponSub::TestInstance() const
{
	Verify(IsDerivedFrom(DefaultData));
}



/*
	sprintf( buffer, "Cumulative = %d (start=%d, end=%d)\n", (int)(m_fireEndTime - m_fireStartTime), (int)m_fireStartTime, (int)m_fireEndTime );
	Spew( 0, buffer );

	sprintf( buffer, "Jammed = %d\n", (int)(gos_GetElapsedTime() - m_jamStartTime) );
	Spew( 0,buffer );

	sprintf( buffer, "Cumulative = %d (start=%d, end=%d)\n", (int)(m_fireEndTime - m_fireStartTime), (int)m_fireStartTime, (int)m_fireEndTime );
	Spew( 0, buffer );
*/

