#include "MW4Headers.hpp"

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

#include "StickyMover.hpp"
#include <Adept\EntityManager.hpp>

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

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

const StateEngine::StateEntry
	StickyMover::ExecutionStateEngine::StateEntries[]=
{
	STATE_ENTRY(StickyMover__ExecutionStateEngine, StickyExecution)
};

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

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

//#############################################################################
//###############################    StickyMover   ############################
//#############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
StickyMover::ClassData*
	StickyMover::DefaultData = NULL;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	StickyMover::InitializeClass()
{
	Check_Object(StickyMover::ExecutionStateEngine::DefaultData);
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			StickyMoverClassID,
			"MechWarrior4::StickyMover",
			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
		);
	Register_Object(DefaultData);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		StickyMover__GameModel,
		ActiveTime,
		activeTime,
		Scalar
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		StickyMover__GameModel,
		IdleLoopResource,
		idleLoopResource,
		ResourceID
	);
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
StickyMover*
	StickyMover::Make(
		CreateMessage *message,
		ReplicatorID *base_id
	)
{
	Check_Object(message);
	gos_PushCurrentHeap(Heap);
	Check_Object(EntityManager::GetInstance());
	StickyMover *new_entity;
	Entity *entity = EntityManager::GetInstance()->RequestFromArmory(message->dataListID);
	if (entity)
	{
		new_entity = Cast_Object(StickyMover*, entity);
		new_entity->Reuse(message, base_id);
	}
	else
		new_entity = new StickyMover(DefaultData, message, base_id, NULL);
	gos_PopCurrentHeap();
	Check_Object(new_entity);
	return new_entity;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
StickyMover::StickyMover(
	ClassData *class_data,
	CreateMessage *message,
	ReplicatorID *base_id,
	ElementRenderer::Element *element
):
	WeaponMover(class_data, message, base_id, element),
	attachedEntity(this),
	idleEffect(NULL)
{
	Check_Pointer(this);
	Check_Object(message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
StickyMover::~StickyMover()
{
	DESTRUCTOR("StickyMover");
	Check_Object(this);
	
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void StickyMover::SentenceToDeathRow()
{
	Check_Object(this);

	if(idleEffect.GetCurrent() != NULL)
	{
		idleEffect.GetCurrent()->SentenceToDeathRow();
	}

	BaseClass::SentenceToDeathRow ();
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	StickyMover::PreCollisionExecute(Time till)
{
	Check_Object(this);
	PRECOLLISION_LOGIC("Sticky Mover");


	//MUST SKIP WEAPONMOVE PRE COLLISION SO COPY THIS STUFF HERE!
	Vector3D distance;
	Point3D initial_position;
	initial_position = initialLocalToParent;
	Point3D pointStartThisFrame;
	pointStartThisFrame = GetLocalToParent();

	distance.Subtract(pointStartThisFrame, initial_position);
	Scalar maxDistanceSquared = maxDistance * maxDistance;

	Set_Statistic(Executed_WeaponMover_Count, Executed_WeaponMover_Count+1);
	UsePostCollision();
	Mover::PreCollisionExecute(till);

	Check_Object(executionState);
	int execution_state = executionState->GetState();
	if(execution_state == ExecutionStateEngine::StraightLineMotionState)
	{
		Point3D current_position;
		ElementRenderer::Element *element = GetElement();
		current_position = element->GetNewLocalToParent();
		distance.Subtract(current_position, initial_position);
		//
		// We still need to run post collision this frame on anything within our
		// range, we can't just skip in the same frame as we went to far so clamp
		// the far point.
		//
		if(distance.GetLengthSquared() >= maxDistanceSquared)
		{
			//
			// Clamp the end point
			//
			Vector3D direction;
			direction.Normalize(distance);
			Line3D line;
			line.m_origin = initial_position;
			line.m_direction = direction;
			line.m_length = maxDistance+1.0f;
			line.FindEnd(&pointStartThisFrame); // reusing variable
			LinearMatrix4D new_local_to_parent;
			new_local_to_parent = element->GetNewLocalToParent();
			new_local_to_parent.BuildTranslation(pointStartThisFrame);
			SetNewLocalToParent(new_local_to_parent);

		}
	}

	BlowUpIfOffMap();

	//
	//----------------------------------------------------
	// Make sure we use post collision and call our parent
	//----------------------------------------------------
	//
	switch(execution_state)
	{
	case ExecutionStateEngine::StickyExecutionState:
		{
			const GameModel *model = GetGameModel();
			Check_Object(model);

			if((GetTimeParameter(till) >= model->activeTime) ||
				(attachedEntity.GetCurrent() == NULL) ||
				(attachedEntity.GetCurrent()->IsDestroyed()))
			{
				SentenceToDeathRow();
			}
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	StickyMover::CollisionHandler(
		Stuff::LinearMatrix4D *new_position,
		Stuff::DynamicArrayOf<CollisionData> *collisions
	)
{
	Check_Object(this);
	Check_Object(collisions);
	Verify(GetInterestLevel() != DormantInterestLevel);
	Verify(EntityManager::GetInstance()->IsInPostCollisionExecution(this));

	//
	//----------------------------------------------
	//This is where we need to start the follow code
	//----------------------------------------------
	//
	CollisionData *data = &(*collisions)[0];
	Check_Pointer(data);
	Check_Object(executionState);
	executionState->RequestState(ExecutionStateEngine::StickyExecutionState);

	*new_position = LinearMatrix4D::Identity;
	new_position->AlignLocalAxisToWorldVector(data->m_normal, Y_Axis, X_Axis, Z_Axis);
	new_position->BuildTranslation(data->m_worldIntersectionPoint);
	SetNewLocalToParent(*new_position);
	lastParameterization = gos_GetElapsedTime();

	attachedEntity.Remove();
	attachedEntity.Add(data->m_otherEntity);
	SetFollowing();
	entityOffset.MultiplyByInverse(data->m_worldIntersectionPoint, data->m_otherEntity->GetLocalToWorld());
	
	if(trailEffect.GetCurrent())
		trailEffect.GetCurrent()->executionState->RequestState(Effect::ExecutionStateEngine::StoppingState);
	OnActiveTimeStart();
	//
	//--------------------------------------------------
	//This is so the Narc will not block the target Mech
	//--------------------------------------------------
	//
	SetCollisionMask(NeverCollidesMask);
	
	CreateIdleEffect();
	PostCollision(collisions);
	Entity::ExecutionStateEngine *engine = executionState;
	if (engine->GetState() == ExecutionStateEngine::NeverExecuteState)
	{
		Verify(!newCollisions);
		delete collisions;
	}
	else
		newCollisions = collisions;
	return false;
}

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

	Check_Object(executionState);
	int execution_state = executionState->GetState();
	switch(execution_state)
	{
	case ExecutionStateEngine::StickyExecutionState:
		{
			Entity *attached_entity;
			attached_entity = attachedEntity.GetCurrent();
			if(attached_entity)
			{
				Check_Object(attached_entity);
				LinearMatrix4D new_position;
				Point3D new_location;
				LinearMatrix4D entity_location;
				UnitQuaternion rotation;
				rotation = GetLocalToWorld();
				entity_location = attached_entity->GetLocalToWorld();
				new_location.Multiply(entityOffset, entity_location);
				new_position.BuildTranslation(new_location);
				new_position.BuildRotation(rotation);
				SetNewLocalToParent(new_position);
				SyncMatrices(true);
			}
			else
			{
				SentenceToDeathRow();
			}
			break;
		}
	}

	BaseClass::PostCollisionExecute(till);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	StickyMover::CreateIdleEffect()
{
	Check_Object(this);

	const GameModel *model = GetGameModel();
	Check_Object(model);
	
	if(model->idleLoopResource != ResourceID::Null)
	{
		Resource idle_resource(model->idleLoopResource);
		if(idle_resource.DoesResourceExist())
		{
			ClassID class_ID;
			class_ID = GetClassIDFromDataListID(model->idleLoopResource);
			Verify(class_ID != NullClassID);

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

			Entity *entity;
			ReplicatorID trail_id = Connection::Hermit->GetNextReplicatorID();
			entity = CreateEntity(&stream, &trail_id, false);
			Check_Object(entity);
			Check_Object(Map::GetInstance());
			Map::GetInstance()->AddChild(entity);

			Effect *effect = Cast_Object(Effect *, entity);
			idleEffect.Add(effect);
			effect->SetFollowEntity(this);
			effect->executionState->RequestState(Effect::ExecutionStateEngine::RunningState);
			entity->SyncMatrices(true);
		}
	}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	StickyMover::TestInstance() const
{
	Verify(IsDerivedFrom(DefaultData));
}
