#include "munga.h"
#pragma hdrstop

#include "explode.h"
#include "mover.h"
#include "boxsolid.h"
#include "collasst.h"
#include "damage.h"
#include "random.h"
#include "app.h"
#include "objstrm.h"
#include "hostmgr.h"
#include "notation.h"
#include "namelist.h"

//#############################################################################
//############################    Explosion    ################################
//#############################################################################

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

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

void
	Explosion::ExplosionSimulation(Scalar)
{
	Check(application);
	if ((Now() - creationTime) > explosionDuration)
	{
		CondemnToDeathRow();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Explosion::SplashDamage
(	EntityID inflicting,
	Damage &damage,
	Scalar collisionRadius,
	Vector3D offset,
	Entity * excluded)             // Excluded should really be a set of entities!
{
	Check(this);
	Check(entityHit);
	if (excluded)
	{
		Check(excluded);
	}
	Check_Pointer(&damage);
	Verify(0 < collisionRadius);


	//
	// The list of volumes we intersect
	//
	BoxedSolidCollisionList collision_list;
	collision_list.Reset();

	//
	// Create the collision assistant to check movers against
	//
	CollisionAssistant *collision_assistant;

	collision_assistant = new CollisionAssistant(entityHit->GetInterestZoneID());
	Register_Object(collision_assistant);
	Check(collision_assistant);

	BoxedSolidResource box;
	Check_Pointer(&box);
	box.solidType = BoxedSolid::YAxisCylinderType;
	box.materialType = BoxedSolid::ConcreteMaterial;
	const Vector3D min = Vector3D(-collisionRadius,-collisionRadius,-collisionRadius);
	const Vector3D max = Vector3D(collisionRadius,collisionRadius,collisionRadius);
	ExtentBox ext_box = ExtentBox(min,max);
	box.solidExtents = ext_box;
	box.sliceExtents = ext_box;

	BoxedSolidResource local_box;
	local_box.Instance(box,localOrigin);

	BoxedSolid *splash_volume = 	BoxedSolid::MakeBoxedSolid(&local_box, this, NULL);
	Register_Object(splash_volume);

	//
	//---------------------------------
	// Test against the tangible movers
	//---------------------------------
	//
	Check(collision_assistant);
	CollisionAssistant::MovingEntityIterator iterator(collision_assistant);
	Entity *entity;
	Mover *mover;
	Check(splash_volume);
	while ((entity = iterator.ReadAndNext()) != NULL)
	{
		//
		//------------------------------------------------------------------
		// If we are checking against ourselves, skip it
		//------------------------------------------------------------------
		//
		Check(entity);
		if (entity == this)
		{
			continue;
		}

		//
		//--------------------------------------------------
		// If we have a mover class object, check against it
		//--------------------------------------------------
		//
		if (entity->IsDerivedFrom(*Mover::GetClassDerivations()))
		{
			mover = Cast_Object(Mover *,entity);
			Check(mover);

			mover->CheckVolumeAgainstBoxedSolidChain(&collision_list, splash_volume);
		}
	}

	//
	//-------------------------------------------------------------------------
	// Test against the static world
	//-------------------------------------------------------------------------
	//
	InterestManager *interest_mgr = application->GetInterestManager();
	Check(interest_mgr);
	InterestZone *zone = interest_mgr->GetInterestZone(interestZoneID);
	Check(zone);
	BoxedSolidTree* tree = zone->GetCollisionRoot();
	Check(tree);
	tree->FindBoundingBoxesContaining(
		splash_volume,
		collision_list
	);

	Check_Fpu();
	Unregister_Object(collision_assistant);
	delete collision_assistant;
	Unregister_Object(splash_volume);
	delete splash_volume;

	//
	//-------------------------------------------------------------------------
	// Make a sorted list of all the entities from the collision list,
	// sorted by their distance from the explosion's origin.
	//-------------------------------------------------------------------------
	//
	VChainOf<Entity*,Vector3D>  radius_chain(NULL,False);
	VChainIteratorOf<Entity*,Vector3D> radius_chain_iterator(radius_chain);

	Vector3D radius;
	Entity *target_entity;


	//

	for(int i = 0;i < collision_list.GetCollisionCount(); i++)
	{
		BoxedSolidCollision bsc = collision_list[i];
		Check(&bsc);
		target_entity = (Entity*) ((bsc.GetTreeVolume())->GetOwningSimulation());
		Check(target_entity);
		Verify(target_entity->IsDerivedFrom(Entity::GetClassDerivations()));
		if ((target_entity != entityHit) && (target_entity != this) && (target_entity != excluded))
		{
			radius = Vector3D::Identity;
			radius.Subtract(target_entity->localOrigin.linearPosition,localOrigin.linearPosition);
			radius_chain.AddValue(target_entity, radius);
		}
	}

	// While remaining_bursts > 0, advance through list, pick a number of bursts
	// to inflict, and call SendDamage to the current entity.

	Damage some_damage = damage;
	int possible_targets = 0;
	int damageable_targets = 0;
	radius_chain_iterator.First();
	while ((target_entity = (Entity*) radius_chain_iterator.GetCurrent()) != NULL)
	{
		Check(target_entity);
		radius = radius_chain_iterator.GetValue();
		radius_chain_iterator.Next();
		possible_targets++;
		if (target_entity->damageZones)
		{

			damageable_targets++;
			//
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			// Set the damage data
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			//
			some_damage.burstCount =damage.burstCount / (pow(radius.Length(), 1.2f));
			Min_Clamp(some_damage.burstCount,1);
			#if DEBUG_LEVEL > 0
			DEBUG_STREAM << "At radius " << radius.Length() << ", applied burstCount is " << some_damage.burstCount << std::endl << std::flush;
			DEBUG_STREAM << "Damage per burst is " << some_damage.damageAmount << std::endl << std::flush;
			#endif
			Vector3D force = Vector3D::Identity;
			force.Multiply(radius,localToWorld);
			some_damage.damageForce = force;
			Point3D origin_point = Point3D::Identity;
			origin_point.Add(localOrigin.linearPosition,offset);
			some_damage.impactPoint = origin_point;
			//some_damage.impactPoint.Add(origin_point,radius);

			//
			//------------------------------------------
			// Send a damage message to the current target entity
			//------------------------------------------
			//

			if (target_entity->GetEntityID() == inflicting)
			{
			  DEBUG_STREAM << "Splash damage hurting inflicting entity." << std::endl << std::flush;
			}

			if (excluded && target_entity->GetEntityID() == excluded->GetEntityID() )
			{
			  DEBUG_STREAM << "Splash damage hurting excluded entity!" << std::endl << std::flush;
			}

			Entity::TakeDamageMessage take_damage(
				Entity::TakeDamageMessageID,
				sizeof(Entity::TakeDamageMessage),
				inflicting,
				-1,       // We can't know what damage zone we hit
				some_damage
			);
			target_entity->Dispatch(&take_damage);
		}
	}

	#if DEBUG_LEVEL >0
	DEBUG_STREAM << "There were " << possible_targets  << "entities within range, only " << damageable_targets << " had damage zones" << std::endl << std::flush;
   #endif
	Check_Fpu();
}

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

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

	//
	//~~~~~~~~~~~~~~~~~~~~~~~
	// zero out any rotation
	//~~~~~~~~~~~~~~~~~~~~~~~
	//
	localOrigin.angularPosition = Quaternion::Identity;

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Get the explosion model resource
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	ResourceDescription *resource_description;
	ModelResource *model_resource;

	Check(application);
	Check(application->GetResourceFile());
	resource_description =
		application->GetResourceFile()->SearchList(
			resourceID,
			ResourceDescription::GameModelResourceType
		);
	Check(resource_description);
	resource_description->Lock();
	model_resource = (ModelResource*)resource_description->resourceAddress;
	Check_Pointer(model_resource);

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Get the creating entity and entity hit by explosion
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	Check(application->GetHostManager());
	entityHit =
		application->GetHostManager()->GetEntityPointer(
			creation_message->entityHitID
		);
	Check(entityHit);

	if (creation_message->creatingEntityID != EntityID::Null)
	{
		creatingEntity =
			application->GetHostManager()->GetEntityPointer(
				creation_message->creatingEntityID
			);
		Check(creatingEntity);
	}
	else
	{
		creatingEntity = NULL;
	}

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Calculate the localHitPosition in terms
	// local coordinate system of the entityHit
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	localHitPosition.MultiplyByInverse(
		localOrigin.linearPosition,
		entityHit->localToWorld
	);

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Get the explosion duration
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	explosionDuration = model_resource->explosionDuration;

#if 0
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// If an explosion resource table is being used then
	// use it to set the resource
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	if (model_resource->resourceTableObjectID != NullObjectID)
	{
		//
		// If the explosion hit us, set the material to our craft,
		// If the explosion hit an opponent, set the material to other craft,
		// If the explosion hit something else then get the material...
		//
		Enumeration material_type;

		Check(application);
		Entity *viewpoint_entity = application->GetViewpointEntity();
		Check(viewpoint_entity);
		Verify(creation_message->entityHitID != EntityID::Null);

		// ECH 8/25/95 - Is this the best way to get our vehicle?
		if (
			creation_message->entityHitID ==
			viewpoint_entity->GetEntityID()
		)
		{
			material_type = BoxedSolid::OurCraftMaterial;
		}
		else
		{
			// ECH 8/25/95 - Is this the best way to identify other vehicles?
			if (
				 entityHit != NULL &&
				entityHit->GetPlayerLink() != NULL
			)
			{
				material_type = BoxedSolid::OtherCraftMaterial;
			}
			else
			{
				// ECH 8/25/95 - How to get material? Just use this for now.
				material_type = BoxedSolid::ConcreteMaterial;
			}
		}

		//
		// Get resource table pointer,
		// Then set the appropriate resource ID
		//
		Plug *plug;
		ExplosionResourceTable *resource_table;
      ResourceDescription::ResourceID new_resource_ID;

		plug = PlugStreamManager::FindPlug(model_resource->resourceTableObjectID);
		resource_table = Cast_Object(ExplosionResourceTable*, plug);
		Check(resource_table);
		new_resource_ID = resource_table->FindResourceID(material_type);
		if (new_resource_ID != ResourceDescription::NullResourceID)
		{
			resourceID = new_resource_ID;
		}
	}
#else
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// If an explosion resource table is being used then
	// use it to set the resource
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	if (model_resource->resourceTableObjectID != NullObjectID)
	{
		//
		// Get resource table pointer,
		// Then set the appropriate resource ID
		//
		Plug *plug;
		ExplosionResourceTable *resource_table;
		ResourceDescription::ResourceID new_resource_ID;

		plug = PlugStreamManager::FindPlug(model_resource->resourceTableObjectID);
		resource_table = Cast_Object(ExplosionResourceTable*, plug);
		Check(resource_table);
		new_resource_ID = resource_table->FindResourceID(entityHit);
		if (new_resource_ID != ResourceDescription::NullResourceID)
		{
			resourceID = new_resource_ID;
		}
	}
#endif

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Set performance and entity validity
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	resource_description->Unlock();
	SetPerformance(&Explosion::ExplosionSimulation);
	SetValidFlag();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Explosion*
	Explosion::Make(Explosion::MakeMessage *creation_message)
{
	Check(application);
	HostManager *host_mgr = application->GetHostManager();
	Check(host_mgr);
	Entity *entity = host_mgr->GetEntityPointer(creation_message->entityHitID);
	if (entity)
	{
		return new Explosion(creation_message);
	}
	Tell(
		"Warning: Explosion ignored - entity " << creation_message->entityHitID
		 << " couldn't be found!\n"
	);
	return NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Explosion::~Explosion()
{
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//  CreateModelResource
//
ResourceDescription::ResourceID
	Explosion::CreateModelResource(
		ResourceFile *resource_file,
		const char *model_name,
		NotationFile *model_file,
		const ResourceDirectories*
	)
{
	Check(resource_file);
	Check_Pointer(model_name);
	Check(model_file);

	ModelResource local_model;
	if(
		!model_file->GetEntry(
			"gamedata",
			"ExplosionDuration",
			&local_model.explosionDuration
		)
	)
	{
		std::cerr<<model_file<<"  missing ExplosionDuration"<<std::endl;
		return -1;
	}
	//
	//--------------------------------------------------------------------------
	//	If there exists the name of an explosion resource table then write
	// its object ID
	//--------------------------------------------------------------------------
	//
	const char *resource_table_name;

	if(
		model_file->GetEntry(
			"gamedata",
			"ExplosionResourceTable",
			&resource_table_name
		)
	)
	{
		//
		// Get the object ID and set it in the model resource
		//
		local_model.resourceTableObjectID =
			PlugStreamManager::FindPlugName(resource_table_name);
		if (local_model.resourceTableObjectID == NullObjectID)
		{
			std::cout << "resource_table_name == " << resource_table_name << "\n";
			Fail("Explosion::CreateModelResource - ExplosionResourceTable not found");
		}
		Verify(local_model.resourceTableObjectID != NullObjectID);
	}
	else
	{
		local_model.resourceTableObjectID = NullObjectID;
	}

	ResourceDescription *resource_description =
		resource_file->AddResource(
			model_name,
			ResourceDescription::GameModelResourceType,
			1,
			ResourceDescription::Preload,
			&local_model,
			sizeof(ModelResource)
		);
	Check(resource_description);
	return resource_description->resourceID;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Enumeration
	Explosion::GetInterestPriority(Entity *linked_entity)
{
	Check(this);
	Check(linked_entity);

	return
	(
		(linked_entity == creatingEntity) ?
			HighInterestPriority :
			LowInterestPriority
	);
}

//#############################################################################
//######################    ExplosionResourceTable    #########################
//#############################################################################

//
//#############################################################################
//#############################################################################
//
ExplosionResourceTable::ExplosionResourceTable(PlugStream *stream):
	Plug(stream),
	resourceIDSocket(NULL, True)
{
	Check(stream);
	Check(&resourceIDSocket);

	CollectionSize table_size;
	*stream >> table_size;

	for (CollectionSize i = 0; i < table_size; i++)
	{
		Enumeration material_type;
		ResourceDescription::ResourceID resource_ID;
		ResourceIDPlug *resource_ID_Plug;

		*stream >> material_type >> resource_ID;

		resource_ID_Plug = new ResourceIDPlug(resource_ID);
		Register_Object(resource_ID_Plug);
		resourceIDSocket.AddValue(resource_ID_Plug, material_type);
	}
}

//
//#############################################################################
//#############################################################################
//
ExplosionResourceTable::~ExplosionResourceTable()
{
	TableIteratorOf<ResourceIDPlug*, Enumeration>
		iterator(&resourceIDSocket);

	Check(&iterator);
	iterator.DeletePlugs();
}

//
//#############################################################################
//#############################################################################
//
void
	ExplosionResourceTable::BuildFromPage(
		ObjectStream *stream,
		NameList *name_list,
		ClassID class_ID,
		ObjectID object_ID
	)
{
	Check(stream);
	Check(name_list);

	Plug::BuildFromPage(stream, name_list, class_ID, object_ID);

	//
	//--------------------------------------------------------------------------
	// Write number of entries
	//--------------------------------------------------------------------------
	//
	CollectionSize table_size = 0;
	NameList::Entry *entry;

	Check(name_list);
	entry = name_list->GetFirstEntry();
	while (entry != NULL)
	{
		Check(entry);
		if (entry->IsName("table_entry"))
			table_size++;
		entry = entry->GetNextEntry();
	}
	*stream << table_size;

	//
	//--------------------------------------------------------------------------
	// Write entries
	//--------------------------------------------------------------------------
	//
	#if DEBUG_LEVEL>0
		CollectionSize i = 0;
	#endif

	Check(name_list);
	entry = name_list->GetFirstEntry();
	while (entry != NULL)
	{
		Check(entry);
		if (entry->IsName("table_entry"))
		{
			CString table_entry, token;
			Enumeration material_type;
			ResourceDescription *resource_description;
			ResourceDescription::ResourceID resource_ID;

			Check_Pointer(entry->GetChar());
			table_entry = entry->GetChar();

			//
			// Material
			//
			Check_Pointer(table_entry.GetNthToken(0));
			material_type = atol(table_entry.GetNthToken(0));

			//
			// Resource
			//
			Check_Pointer(table_entry.GetNthToken(1));
			Check(stream->GetResourceFile());
			resource_description =
				stream->GetResourceFile()->FindResourceDescription(
					table_entry.GetNthToken(1),
					ResourceDescription::ModelListResourceType
				);
			Check(resource_description);
			resource_ID = resource_description->resourceID;

			*stream << material_type << resource_ID;
			#if DEBUG_LEVEL>0
				i++;
			#endif
		}
		entry = entry->GetNextEntry();
	}
	#if DEBUG_LEVEL>0
		Verify(i == table_size);
	#endif
}

//
//#############################################################################
//#############################################################################
//
Logical
	ExplosionResourceTable::TestInstance() const
{
	Plug::TestInstance();
	Check(&resourceIDSocket);
	return True;
}

//
//#############################################################################
//#############################################################################
//
ResourceDescription::ResourceID
	ExplosionResourceTable::FindResourceID(Enumeration material_type)
{
	Check(this);

	ResourceIDPlug *resource_ID_Plug;
	if ((resource_ID_Plug = resourceIDSocket.Find(material_type)) != NULL)
	{
		Check(resource_ID_Plug);
		return resource_ID_Plug->GetItem();
	}
	return ResourceDescription::NullResourceID;
}

//
//#############################################################################
//#############################################################################
//
ResourceDescription::ResourceID
	ExplosionResourceTable::FindResourceID(Entity *entity_hit)
{
	Check(this);
	Check(entity_hit);

	//
	// If the explosion hit us, set the material to our craft,
	// If the explosion hit an opponent, set the material to other craft,
	// If the explosion hit something else then get the material...
	//
	Enumeration material_type;
	Entity *viewpoint_entity;

	Check(application);
	viewpoint_entity = application->GetViewpointEntity();
	Check(viewpoint_entity);

	// ECH 8/25/95 - Is this the best way to get our vehicle?
	if (entity_hit->GetEntityID() == viewpoint_entity->GetEntityID())
	{
		material_type = BoxedSolid::OurCraftMaterial;
	}
	// ECH 8/25/95 - Is this the best way to identify other vehicles?
	else if (entity_hit->GetPlayerLink() != NULL)
	{
		material_type = BoxedSolid::OtherCraftMaterial;
	}
	else
	{
		// ECH 8/25/95 - How to get material? Just use this for now.
		material_type = BoxedSolid::ConcreteMaterial;
	}

	//
	// Return the appropriate resource ID
	//
	return FindResourceID(material_type);
}
