#include "MW4Headers.hpp"

#include "BombastWeapon.hpp"
#include "Vehicle.hpp"
#include "BeamEntity.hpp"
#include "GUIWeaponManager.hpp"
#include "VehicleInterface.hpp"
#include "SubsystemClassData.hpp"

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

#include <MLR\MLRTexturePool.hpp>

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

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

const StateEngine::StateEntry
	BombastWeapon::ExecutionStateEngine::StateEntries[]=
{
	STATE_ENTRY(BombastWeapon__ExecutionStateEngine, Charging)
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BombastWeapon::ExecutionStateEngine::InitializeClass()
{
	Check_Object(StateEngine::DefaultData);
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			BombastWeapon__ExecutionStateEngineClassID,
			"BombastWeapon::ExecutionStateEngine",
			BaseClass::DefaultData,
			ELEMENTS(StateEntries), StateEntries,
			(Entity::ExecutionStateEngine::Factory)Make,
			(Entity::ExecutionStateEngine::FactoryRequest::Factory)
				&FactoryRequest::ConstructFactoryRequest
		);
	Register_Object(DefaultData);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BombastWeapon::ExecutionStateEngine::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BombastWeapon::ExecutionStateEngine*
	BombastWeapon::ExecutionStateEngine::Make(
		BombastWeapon *weapon,
		FactoryRequest *request
	)
{
	Check_Object(weapon);
	Check_Object(request);
	gos_PushCurrentHeap(Heap);
	BombastWeapon::ExecutionStateEngine *engine =
		new BombastWeapon::ExecutionStateEngine(DefaultData, weapon, request);
	gos_PopCurrentHeap();

	return engine;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	BombastWeapon::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 firing, see if this is the start of the bombast.  If so,
	// start the charging processes instead.  Otherwise don't let anything
	// fire until at least half a second has gone by
	//--------------------------------------------------------------------
	//
	BombastWeapon *beam;
	beam = Cast_Object(BombastWeapon*, owningEntity);
	if (new_state == FiringState)
	{
		switch (currentState)
		{
		case NeverExecuteState:
			new_state = ChargingState;
			break;
		case ChargingState:
			if ((gos_GetElapsedTime() - beam->startChargeTime) <= 0.5f)
				return currentState;
			break;
		case RechargingState:
			return currentState;
		}
	}

	//
	//---------------------------------------------------------------------
	// Otherwise, make sure we only enter charging state from never execute
	//---------------------------------------------------------------------
	//
	else if ((new_state == ChargingState) && (currentState != NeverExecuteState))
		return currentState;

	//
	//-------------------------------------------------------------------------
	// We are calling weapon directly because we do not want beam functionality
	//-------------------------------------------------------------------------
	//
	switch (BaseClass::BaseClass::RequestState(new_state, data))
	{
	case ChargingState:
		{
			beam->UsePostCollision();
			beam->lastParameterization = gos_GetElapsedTime();
			beam->startChargeTime = gos_GetElapsedTime();
			beam->percentCharged = 0.25f;
			beam->beamRechargeTime = 0.0f;
			break;
		}
	case FiringState:
		{
			//We need to set up new target info here!
			
			beam->UsePostCollision();
			Check_Object(EntityManager::GetInstance());
			EntityManager::GetInstance()->RequestPostCollisionExecution(beam);

			// Start the sound
			beam->beamStartSFX = 1;
			beam->LeaveNeverExecuteState();
			beam->lastParameterization = gos_GetElapsedTime();
			if(beam->guiWeapon.GetCurrent())
				beam->guiWeapon.GetCurrent()->Reload();
			beam->ApplyHeat();
			beam->beamRechargeTime = (Scalar)(gos_GetElapsedTime() - beam->startChargeTime);
			const BeamWeapon::GameModel *beam_model = beam->GetGameModel();
			Clamp(beam->beamRechargeTime, 0.0f, beam_model->reloadTime);
		}
		break;
	case NeverExecuteState:
	case DestroyedState:
			break;
	default:
		{
			owningEntity->IgnorePostCollision();
			break;
		}
	}
	return currentState;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BombastWeapon::ExecutionStateEngine::TestInstance() const
{
	Verify(IsDerivedFrom(DefaultData));
}

//#############################################################################
//###############################    BombastWeapon    #########################
//#############################################################################

BombastWeapon::ClassData*
	BombastWeapon::DefaultData = NULL;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BombastWeapon::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			BombastWeaponClassID,
			"MechWarrior4::BombastWeapon",
			BaseClass::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);

	INDIRECT_STATE_ATTRIBUTE(
		BombastWeapon,
		ExecutionState,
		executionState,
		BombastWeapon__ExecutionStateEngine
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		BombastWeapon__GameModel,
		MaxChargeTime,
		maxChargeTime,
		Scalar
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		BombastWeapon__GameModel,
		TimeForMaxCharge,
		timeForMaxCharge,
		Scalar
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		BombastWeapon__GameModel,
		ChargeEffectResource,
		chargeEffectResource,
		ResourceID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		BombastWeapon__GameModel,
		InitialChargeBeamResource,
		initialChargeBeamResource,
		ResourceID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		BombastWeapon__GameModel,
		HalfChargeBeamResource,
		halfChargeBeamResource,
		ResourceID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		BombastWeapon__GameModel,
		ThreeQuarterChargeBeamResource,
		threeQuarterChargeBeamResource,
		ResourceID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		BombastWeapon__GameModel,
		FullChargeBeamResource,
		fullChargeBeamResource,
		ResourceID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		BombastWeapon__GameModel,
		InitialChargeFlareResource,
		initialChargeFlareResource,
		ResourceID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		BombastWeapon__GameModel,
		HalfChargeFlareResource,
		halfChargeFlareResource,
		ResourceID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		BombastWeapon__GameModel,
		ThreeQuarterChargeFlareResource,
		threeQuarterChargeFlareResource,
		ResourceID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		BombastWeapon__GameModel,
		FullChargeFlareResource,
		fullChargeFlareResource,
		ResourceID
	);
}

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BombastWeapon::BombastWeapon(
	ClassData *class_data,
	CreateMessage *message,
	ReplicatorID *base_id,
	ElementRenderer::Element *element
):
	BeamWeapon(class_data, message, base_id, element),
	chargeEffect(NULL)
{
	Check_Pointer(this);
	Check_Object(message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BombastWeapon::~BombastWeapon()
{
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool BombastWeapon::ReadyToFire()
{

	
	if (CanFireFromCurrentArm() == false)
	{
		return (false);
	}

	if (executionState->GetState()==ExecutionStateEngine::NeverExecuteState)
	{
		return true;
	}

	if ( executionState->GetState() == ExecutionStateEngine::ChargingState )
	{
		if ((gos_GetElapsedTime() - startChargeTime) > 0.5f)
		{
			return true;
		}
	}



	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void BombastWeapon::PreCollisionExecute(Time till)
{
	PRECOLLISION_LOGIC("Subsystem::Bombast");

	Check_Object(executionState);
	int pre_state = executionState->GetState();

	
	switch (pre_state)
	{
	case ExecutionStateEngine::ChargingState:
		{
			if((GetTimeParameter(till) > 1.0f) && (percentCharged < 1.0f))
			{ 
				percentCharged = percentCharged + 0.25f;
				lastParameterization = gos_GetElapsedTime();
			}
#if 0
			if((gos_GetElapsedTime() - startChargeTime) > model->maxChargeTime)
			{
				Verify(GetParentVehicle()->IsDerivedFrom(Vehicle::DefaultData));
				Vehicle *vehicle = Cast_Object(Vehicle *, GetParentVehicle());
				Check_Object(vehicle);

				if(vehicle->vehicleInterface)
				{
					vehicle->vehicleInterface->targetQuery.m_raySource = 
						vehicle->vehicleInterface->targetEntityPart.GetCurrent();
					Fire(&vehicle->vehicleInterface->targetQuery, 0.0f, Stuff::Point3D::Identity);
				}
				else
				{
					executionState->RequestState(ExecutionStateEngine::FiringState);
				}
			}
#endif
			break;
		}
	}

	BaseClass::PreCollisionExecute(till);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BombastWeapon::PostCollisionExecute(Time till)
{
	Check_Object(this);
	Check_Object(executionState);

	POSTCOLLISION_LOGIC("Subsystem::Bombast");
	int pre_state = executionState->GetState();

	switch (pre_state)
	{
	case ExecutionStateEngine::ChargingState:
		{
			//
			//--------------------------
			//Create the charging effect
			//--------------------------
			//
			if(!chargeEffect.GetCurrent())
				CreateChargeEffect();
			break;
		}
	default:
		{
			if(chargeEffect.GetCurrent())
				chargeEffect.GetCurrent()->SentenceToDeathRow();

			BaseClass::PostCollisionExecute(till);
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void BombastWeapon::CreateBeam()
{
	const GameModel *model = GetGameModel();
	Check_Object(model);

	ResourceID beam_resource;
	if(percentCharged <= 0.25f)
		beam_resource = model->initialChargeBeamResource;
	else if(percentCharged <= 0.50f)
		beam_resource = model->halfChargeBeamResource;
	else if(percentCharged <= 0.75)
		beam_resource = model->threeQuarterChargeBeamResource;
	else
		beam_resource = model->fullChargeBeamResource;

	Verify(beam_resource != ResourceID::Null);

	ClassID class_ID;
	class_ID = GetClassIDFromDataListID(beam_resource);
	Verify(class_ID != NullClassID);

	Point3D target_point = Point3D::Identity;
	ReplicatorID target_id = ReplicatorID::Null;
	Normal3D target_normal(1.0f, 0.0f, 0.0f);

	Check_Object(sitePointer);
	const char *site_name = sitePointer->GetName();
	ReplicatorID inflicting_id = ReplicatorID::Null;
	inflicting_id = GetParentVehicle()->GetReplicatorID();
	
	if(targetQuery.m_line->m_length > model->maxDistance)
	{
		targetQuery.m_line->m_length = model->maxDistance;
		targetQuery.m_line->FindEnd(&target_point);
		targetQuery.m_raySource = NULL;
	}
	else
	{
		if(targetQuery.m_raySource)
		{
			Check_Object(targetQuery.m_raySource);
			target_id = targetQuery.m_raySource->GetReplicatorID();
			targetQuery.m_line->FindEnd(&target_point);
			target_normal = *targetQuery.m_normal;
		}
		else
		{
			targetQuery.m_line->FindEnd(&target_point);
		}
	}

	Stuff::Scalar damage_amount, heat_amount;
	damage_amount = percentCharged * model->damageAmount;
	heat_amount = percentCharged * model->heatToDeal;

	BeamEntity::CreateMessage beam_create_message(
		sizeof(BeamEntity::CreateMessage),
		DefaultEventPriority,
		CreateMessage::DefaultFlags,
		class_ID,
		BeamEntity::DefaultFlags,
		beam_resource,
		sitePointer->GetLocalToWorld(),
		0.0f,
		ExecutionStateEngine::AlwaysExecuteState,
		NameTable::NullObjectID,
		Entity::DefaultAlignment,
		inflicting_id,
		site_name,
		target_point,
		target_id,
		target_normal,
		damage_amount,
		heat_amount,
		targetQuery.m_material,
		model->maxDistance,
// MSL 5.03 Min Distance
//		model->minDistance,
		model->itemID
	);
	MemoryStream stream(&beam_create_message, beam_create_message.messageLength);

	Entity *entity;
	ReplicatorID beam_id = Connection::Hermit->GetNextReplicatorID();
	entity = CreateEntity(&stream, &beam_id, false);
	Check_Object(entity);
	Verify(entity->IsDerivedFrom(BeamEntity::DefaultData));
	beamEntity.Add(Cast_Object(BeamEntity *, entity));
	Check_Object(Map::GetInstance());
	Map::GetInstance()->AddChild(beamEntity.GetCurrent());
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BombastWeapon::CreateChargeEffect()
{
	Check_Object(this);

	const GameModel *model = GetGameModel();
	Check_Object(model);
	
	if(model->chargeEffectResource == ResourceID::Null)
		return;

	Resource charge_resource(model->chargeEffectResource);

	if(charge_resource.DoesResourceExist())
	{
		ClassID class_ID;
		class_ID = GetClassIDFromDataListID(model->chargeEffectResource);
		Verify(class_ID != NullClassID);

		LinearMatrix4D effect_position = LinearMatrix4D::Identity;
		UnitVector3D unit_forward;
		Check_Object(sitePointer);
		LinearMatrix4D site_position = sitePointer->GetLocalToWorld();
		site_position.GetLocalForwardInWorld(&unit_forward);
		effect_position.AlignLocalAxisToWorldVector(unit_forward, Y_Axis, X_Axis, Z_Axis);
		Point3D muzzle_translation;
		muzzle_translation = sitePointer->GetLocalToWorld();
		effect_position.BuildTranslation(muzzle_translation);

		Effect::CreateMessage effect_create_message(
			sizeof(Effect::CreateMessage),
			DefaultEventPriority,
			CreateMessage::DefaultFlags,
			class_ID,
			Effect::DefaultFlags,
			model->chargeEffectResource,
			effect_position,
			0.0f,
			ExecutionStateEngine::AlwaysExecuteState,
			NameTable::NullObjectID,
			Entity::DefaultAlignment
		);
		MemoryStream stream(&effect_create_message, effect_create_message.messageLength);

		Entity *entity;
		ReplicatorID beam_id = Connection::Hermit->GetNextReplicatorID();
		entity = CreateEntity(&stream, &beam_id, false);
		Check_Object(Map::GetInstance());
		Map::GetInstance()->AddChild(entity);
		chargeEffect.Remove();
		chargeEffect.Add(Cast_Object(Effect *, entity));
		chargeEffect.GetCurrent()->SetFollowEntity(sitePointer);
		chargeEffect.GetCurrent()->SyncMatrices(true);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BombastWeapon::CleanUpCreatedBeam()
{
	Check_Object(this);
	if(beamEntity.GetCurrent())
		beamEntity.GetCurrent()->SentenceToDeathRow();
	if(chargeEffect.GetCurrent())
		chargeEffect.GetCurrent()->SentenceToDeathRow();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BombastWeapon::CreateMuzzleFlash()
{
	Check_Object(this);

	const GameModel *model = GetGameModel();
	Check_Object(model);
	
	ResourceID flare_resource;
	if(percentCharged <= 0.25f)
		flare_resource = model->initialChargeFlareResource;
	else if(percentCharged <= 0.50f)
		flare_resource = model->halfChargeFlareResource;
	else if(percentCharged < 0.75)
		flare_resource = model->threeQuarterChargeFlareResource;
	else
		flare_resource = model->fullChargeFlareResource;

	Verify(flare_resource != ResourceID::Null);

//	if(model->muzzleFlashResource == ResourceID::Null)
//		return;

	Resource muzzle_flash_resource(flare_resource);
	if(muzzle_flash_resource.DoesResourceExist())
	{
		ClassID class_ID;
		class_ID = GetClassIDFromDataListID(flare_resource);
		Verify(class_ID != NullClassID);

		LinearMatrix4D effect_position = LinearMatrix4D::Identity;
		UnitVector3D unit_forward;
		Check_Object(sitePointer);
		LinearMatrix4D site_position = sitePointer->GetLocalToWorld();
		site_position.GetLocalForwardInWorld(&unit_forward);
		effect_position.AlignLocalAxisToWorldVector(unit_forward, Y_Axis, X_Axis, Z_Axis);
		Point3D muzzle_translation;
		muzzle_translation = sitePointer->GetLocalToWorld();
		effect_position.BuildTranslation(muzzle_translation);

		Effect::CreateMessage effect_create_message(
			sizeof(Effect::CreateMessage),
			DefaultEventPriority,
			CreateMessage::DefaultFlags,
			class_ID,
			Effect::DefaultFlags,
			flare_resource,
			effect_position,
			0.0f,
			ExecutionStateEngine::AlwaysExecuteState,
			NameTable::NullObjectID,
			Entity::DefaultAlignment
		);
		MemoryStream stream(&effect_create_message, effect_create_message.messageLength);

		Entity *entity;
		ReplicatorID beam_id = Connection::Hermit->GetNextReplicatorID();
		entity = CreateEntity(&stream, &beam_id, false);
		Check_Object(Map::GetInstance());
		Map::GetInstance()->AddChild(entity);
		Effect *effect;
		effect = Cast_Object(Effect *, entity);
		effect->SetFollowEntity(sitePointer);
		entity->SyncMatrices(true);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void 
	BombastWeapon::SetQuery(CollisionQuery *query)
{
	Check_Object(this);
	Check_Object(sitePointer);

	targetQuery.m_collisionMask = query->m_collisionMask;
	targetQuery.m_material = query->m_material;
	targetQuery.m_raySource = query->m_raySource;

	collisionLine = *query->m_line;
	collisionNormal = *query->m_normal;
}

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

