#include "rp.h"
#pragma hdrstop

#include "rivet.h"
#include "..\munga\line.h"
#include "..\munga\explode.h"
#include "..\munga\boxsolid.h"
#include "..\munga\subsystm.h"
#include "..\munga\app.h"
#include "..\munga\hostmgr.h"
#include "..\munga\interest.h"
#include "..\munga\notation.h"

//#############################################################################
// Shared Data Support
//
Derivation* Rivet::GetClassDerivations()
{	static Derivation classDerivations(Mover::GetClassDerivations(), "Rivet");
	return &classDerivations;
}


Rivet::SharedData
	Rivet::DefaultData(
		Rivet::GetClassDerivations(),
		Rivet::GetMessageHandlers(),
		Rivet::GetAttributeIndex(),
		Rivet::StateCount,
		(Entity::MakeHandler)Rivet::Make
	);

//#############################################################################
// Attribute Support
//
const Rivet::IndexEntry
	Rivet::AttributePointers[]=
{
	ATTRIBUTE_ENTRY(Rivet, DamageData,	damageData)
};

Rivet::AttributeIndexSet& Rivet::GetAttributeIndex()
{
	static Rivet::AttributeIndexSet attributeIndex(ELEMENTS(Rivet::AttributePointers), Rivet::AttributePointers, Mover::GetAttributeIndex());
	return attributeIndex;
}

//#############################################################################
// Model support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Rivet::MoveToTarget(Scalar time_slice)
{
	Line
		distance_traveled;
	distance_traveled	= localOrigin.linearPosition;

	//
	//-----------------------------------------------------------------
	// Apply the standard air resistance and gravity, then project the
	// Projectile motion through the time slice and look for collisions
	//-----------------------------------------------------------------
	//
	ApplyAirResistanceAndGravity();
	ApplyWorldAccelerations(time_slice);

	Vector3D
		motion_vector;
	motion_vector.Subtract(
		localOrigin.linearPosition,
		distance_traveled.origin
	);
	distance_traveled = motion_vector;
	Check(&distance_traveled);
	localToWorld = localOrigin;

	BoxedSolid
		*target = FindBoxedSolidHitBy(&distance_traveled, shootingEntity);
	//
	//-------------------------------------------------------
	// If the projectile hit something, sentence it to death!
	//-------------------------------------------------------
	//
	if (target)
	{
		CondemnToDeathRow();
		Origin
			explode_origin = localOrigin;
		distance_traveled.FindEnd(&explode_origin.linearPosition);

		//
		//-----------------------
		// Find the entity we hit
		//-----------------------
		//
		Simulation
			*sim = target->GetOwningSimulation();
		Check(sim);
		Entity
			*entity;
		if (sim->IsDerivedFrom(*Subsystem::GetClassDerivations()))
		{
			Check((Subsystem*)sim);
			entity = ((Subsystem*)sim)->GetEntity();
		}
		else if (sim->IsDerivedFrom(*Mover::GetClassDerivations()))
		{
			Mover
				*mover = (Mover*)sim;
			Check(mover);

			Vector3D
				impact_speed;

			impact_speed.Subtract(worldLinearVelocity, mover->worldLinearVelocity);
			Scalar
				damage_scale = impact_speed.Length() / 350.0f;	// HACK
			damageData.damageAmount *= damage_scale*damage_scale;
			entity = mover;
		}
		else
		{
			entity = (Entity*)sim;
		}
		Check(entity);

		//
		//------------------
		// Make an Explosion
		//------------------
		//
		Check(application);
		ResourceFile
			*resource_file = application->GetResourceFile();
		Check(resource_file);
		ResourceDescription *explosion_res =
			resource_file->FindResourceDescription(explosionResourceID);
		Check(explosion_res);
		explosion_res->Lock();

		Explosion::MakeMessage exp_message(
			Explosion::MakeMessageID,
			sizeof(Explosion::MakeMessage),
			ExplosionClassID,
			EntityID::Null,
			explosion_res->resourceID,
			Explosion::DefaultFlags,
			explode_origin,
			entity->GetEntityID(),
			shootingEntity->GetEntityID()
		);

		explosion_res->Unlock();

		#if DEBUG_LEVEL > 0
			Explosion
				*exp =
		#endif
					Explosion::Make(&exp_message);
			Register_Object(exp);
		//
		//------------------------------------------
		// Send a damage message to the thing we hit
		//------------------------------------------
		//
		Entity::TakeDamageMessage
			take_damage(
				Entity::TakeDamageMessageID,
				sizeof(Entity::TakeDamageMessage),
				shootingEntity->GetEntityID(),
				0,
				damageData
			);
		entity->Dispatch(&take_damage);
	}

	//
	//---------------------------------------------
	// Otherwise, see if we have to update anything
	//---------------------------------------------
	//
	else
	{
		UpdateLocalMotion();

		//
		//------------------------------------------------------------------------
		// Run the dead reckoner, and then see how far apart the two positions are
		//------------------------------------------------------------------------
		//
		(this->*deadReckoner)();
		Vector3D
			error;
		error.Subtract(
			projectedOrigin.linearPosition,
			localOrigin.linearPosition
		);
		if (error.LengthSquared() > 0.1f)
		{
			ForceUpdate();
		}
	}

	//
	//---------------------------------------------------
	// If the bullet has outlived its lifetime, delete it
	//---------------------------------------------------
	//
	if ((Now() - creationTime) > RIVET_MAXIMUM_LIFETIME)
	{
		CondemnToDeathRow();
	}
	Check_Fpu();
}

//#############################################################################
// Construction and Destruction
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Rivet::Rivet(
	Rivet::MakeMessage *creation_message,
	Rivet::SharedData &virtual_data
):
	Mover(creation_message, virtual_data)
{
	Check_Pointer(this);

	//
	//------------------------
	// Setup the initial state
	//------------------------
	//

	SetValidFlag();

	Check(application);
	Check(application->GetInterestManager());
	InterestZone
		*interest_zone = application->GetInterestManager()->
			GetInterestZone(interestZoneID);
	Check(interest_zone);

	localEnvironment = interest_zone->GetEnvironment();
	Check(localEnvironment);

	//
	//
	SetDeadReckoner(&Rivet::AcceleratedDeadReckoner);
	if (GetInstance() == ReplicantInstance)
	{
		SetPerformance(&Rivet::DeadReckon);
	}
	else
	{
		SetPerformance(&Rivet::MoveToTarget);
		StartCollisionAssistant();
	}

	//
	// Initialize the Projectile variables
	//
	ResourceDescription *res =
		application->GetResourceFile()->SearchList(
			resourceID,
			ResourceDescription::GameModelResourceType
		);
	Check(res);
	res->Lock();

	ModelResource
		*model = (ModelResource*)res->resourceAddress;
	Check_Pointer(model);

	damageData.damageType = model->damageData.damageType;
	damageData.damageAmount = model->damageData.damageAmount;
	damageData.damageForce = Vector3D::Identity;
	damageData.surfaceNormal.x = 1.0f;
	damageData.surfaceNormal.y = 0.0f;
	damageData.surfaceNormal.z = 0.0f;
	damageData.impactPoint = Point3D::Identity;
	explosionResourceID = model->explosionResourceID;

	res->Unlock();

	HostManager
		*host_mgr = application->GetHostManager();
	Check(host_mgr);

	shootingEntity =
		host_mgr->GetEntityPointer(creation_message->shootingEntity);
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Rivet*
	Rivet::Make(Rivet::MakeMessage *creation_message)
{
	return new Rivet(creation_message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Rivet::~Rivet()
{
	Check(this);
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//  CreateModelResource
//
int
	Rivet::CreateModelResource(
		ResourceFile *resource_file,
		const char *model_name,
		NotationFile *model_file,
		const ResourceDirectories *directories,
		ModelResource* model
	)
{
	Check(resource_file);
	Check_Pointer(model_name);
	Check(model_file);
	Check_Pointer(directories);

	//
	//-----------------------------------------------------------------------
	// If we were not provided a buffer to write the model data into, we must
	// create it ourselves.  Then make sure that the mover stuff is read in
	//-----------------------------------------------------------------------
	//
	ModelResource
		*local_model = model;
	if (!local_model)
	{
		local_model = new ModelResource;
		Register_Pointer(local_model);
	}

	if(
		Mover::CreateModelResource(
			resource_file,
			model_name,
			model_file,
			directories,
			local_model
			) == -1
		)
	{
Dump_And_Die:
		if (!model)
		{
			Unregister_Pointer(local_model);
			delete local_model;
		}
		Check_Fpu();
		return -1;
	}

	//
	//---------------------------------------
	// Read in the damage amount for the ammo
	//---------------------------------------
	//
	if(
		!model_file->GetEntry(
			"gamedata",
			"DamageAmount",
			&local_model->damageData.damageAmount
		)
	)
	{
		std::cout << model_name <<  " missing DamageAmount!\n";
		goto Dump_And_Die;
	}

	//
	//----------------------------------------------------------------------
	//	Read in the Model Filename of the Explosion renderable, then find its
	// resource ID in the resource file
	//----------------------------------------------------------------------
	//
	const char
		*damage_type;
	if(
		!model_file->GetEntry(
			"gamedata",
			"DamageType",
			&damage_type
		)
	)
	{
		std::cout << model_name <<  " missing DamageType!\n";
		goto Dump_And_Die;
	}
	if (!stricmp(damage_type, "CollisionDamage"))
	{
		local_model->damageData.damageType = Damage::CollisionDamageType;
	}
	else if (!stricmp(damage_type, "BallisticDamage"))
	{
		local_model->damageData.damageType = Damage::BallisticDamageType;
	}
	else if (!stricmp(damage_type, "ExplosiveDamage"))
	{
		local_model->damageData.damageType = Damage::ExplosiveDamageType;
	}
	else if (!stricmp(damage_type, "LaserDamage"))
	{
		local_model->damageData.damageType = Damage::LaserDamageType;
	}
	else if (!stricmp(damage_type, "EnergyDamage"))
	{
		local_model->damageData.damageType = Damage::EnergyDamageType;
	}
	else
	{
		std::cout << model_name <<  " has an unknown DamageType of " << damage_type
			<< std::endl;
		goto Dump_And_Die;
	}
	//
	//----------------------------
	// Init damage fields
	//----------------------------
	//
	local_model->damageData.damageForce = Vector3D::Identity;
	local_model->damageData.surfaceNormal.x = 0.0f;
	local_model->damageData.surfaceNormal.y = 1.0f;
	local_model->damageData.surfaceNormal.z = 0.0f;
	local_model->damageData.impactPoint = Vector3D::Identity;

	//
	//----------------------------------------------------------------------
	//	Read in the Model Filename of the Explosion renderable, then find its
	// resource ID in the resource file
	//----------------------------------------------------------------------
	//
	const char
		*explosion;
	if(
		!model_file->GetEntry(
			"gamedata",
			"ExplosionModelFile",
			&explosion
		)
	)
	{
		std::cout << model_name <<  " missing ExplosionModelFile!\n";
		goto Dump_And_Die;
	}
	ResourceDescription
		*res = resource_file->FindResourceDescription(
			explosion,
			ResourceDescription::ModelListResourceType
		);
	if (!res)
	{
		std::cout << model_name <<  " cannot find " << explosion
		 << " in resource file!\n";
		goto Dump_And_Die;
	}
	local_model->explosionResourceID = res->resourceID;

	//
	//-------------------------------------------------------------------------
	// If we created the model buffer, then we have the responsibility to write
	// it out to the resource file
	//-------------------------------------------------------------------------
	//
	if (!model)
	{
		ResourceDescription
			*new_res = resource_file->AddResource(
				model_name,
				ResourceDescription::GameModelResourceType,
				1,
				ResourceDescription::Preload,
				local_model,
				sizeof(*local_model)
			);
		Unregister_Pointer(local_model);
		delete local_model;
		Check(new_res);
		Check_Fpu();
		return new_res->resourceID;
	}
	else
	{
		Check_Fpu();
		return 0;
	}
}

Logical
	Rivet::TestInstance() const
{
	return IsDerivedFrom(*GetClassDerivations());
}
