#include "munga.h"
#pragma hdrstop

#include "exptbl.h"
#include "namelist.h"
#include "registry.h"
#include "app.h"
#include "explode.h"
#include "damage.h"
#include "notation.h"
#include "fileutil.h"
#include "renderer.h"

//##############################################################################
//######################   Class ExplosionTableEntry    ########################
//##############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ExplosionTableEntry::CreateExplosion(
		const EntityID	&entity_hit_ID,
		const EntityID	&creating_entity_ID,
		const Point3D	&explode_position
	)
{
	Check(this);
	Origin	explode_origin(Origin::Identity);
	explode_origin = explode_position;
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Create the explosion MakeMessage
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	Explosion::MakeMessage exp_message(
		Explosion::MakeMessageID,
		sizeof(Explosion::MakeMessage),
		RegisteredClass::ExplosionClassID,
		EntityID::Null,
		explosionResourceID,
		Explosion::DefaultFlags,
		explode_origin,
		entity_hit_ID,
      creating_entity_ID
	);
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Calculate actual time based on time_delay
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	Time when  = Now();
	when += timeDelay;
	Check(application);

	Registry *registry;
	registry =  application->GetRegistry();
	Check_Pointer(registry);
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Create a MakeEntityMessage
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	Registry::MakeEntityMessage *registry_message;
	registry_message = Registry::MakeEntityMessage::Make(&exp_message);
	Register_Object(registry_message);
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Post entity message to the registry
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	#if 0 // ECH 1/28/96
	application->Post(
		DefaultEventPriority,
		registry,
		registry_message,
		when
	);
	#else
	application->Post(
		CreationEventPriority,
		registry,
		registry_message,
		when
	);
	#endif
	Unregister_Object(registry_message);
	delete registry_message;

	Check_Fpu();
}
//##############################################################################
//  Construction/Destruction
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ExplosionTableEntry::ExplosionTableEntry(MemoryStream *exp_stream)
{
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Read in the data from the stream
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	*exp_stream >> damageLevel >> explosionResourceID >> graphicState;
	*exp_stream >> timeDelay;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ExplosionTableEntry::~ExplosionTableEntry()
{

}

//##############################################################################
//  Tool Support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	ExplosionTableEntry::CreateStreamedExplosion(
		ResourceFile	*resource_file,
		CString			exp_file_name,
		NotationFile 	*exp_file,
		CString        exp_page_name,
		MemoryStream 	*explosion_stream
	)
{
	Check(exp_file);

	//
	//~~~~~~~~~~~~~~~~~~~~
	// Get the DamageLevel
	//~~~~~~~~~~~~~~~~~~~~
	//
	Scalar	damage_level;
	if (!exp_file->GetEntry(
			exp_page_name,
			"DamageLevel",
			&damage_level
		)
	)
	{
		damage_level = -1.0f;
	}
	*explosion_stream << damage_level;

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Get the Explosion Model File
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	const char *explosion_model_file;
	if (!exp_file->GetEntry(
		exp_page_name,
		"ExplosionModelFile",
		&explosion_model_file
		)
	)
	{
		DEBUG_STREAM <<exp_file_name<<" : "<<exp_page_name<<" missing ExplosionModelFile!"<<std::endl << std::flush;
		return False;
	}
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Hunt for Model file in ResourceFile
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	ResourceDescription *res;

	res = resource_file->FindResourceDescription(
		explosion_model_file,
		ResourceDescription::ModelListResourceType
	);
	if (!res)
	{
		std::cout << exp_file_name <<  " cannot find " << explosion_model_file
		 << " in resource file!\n";
		return False;
	}
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~
	// Write out the resourceID
	//~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	*explosion_stream << res->resourceID;

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Read in the Graphic State
	//~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	const char *graphic_state_char;
	Enumeration graphic_state;
	if (exp_file->GetEntry(
			exp_page_name,
			"GraphicState",
			&graphic_state_char
		)
	)
	{
		if (!strcmp(graphic_state_char, "Destroyed"))
		{
			graphic_state = DamageZone::DestroyedGraphicState;
		}
		else if (!strcmp(graphic_state_char, "Gone"))
		{
			graphic_state = DamageZone::GoneGraphicState;
		}
	}
	else
	{
		graphic_state = DamageZone::ExistsGraphicState;
	}
	*explosion_stream << graphic_state;
	//
	//~~~~~~~~~~~~~~~~~~~~
	// Get the TimeDelay
	//~~~~~~~~~~~~~~~~~~~~
	//
	Scalar	time_delay;
	if (!exp_file->GetEntry(
			exp_page_name,
			"TimeDelay",
			&time_delay
		)
	)
	{
		time_delay = 0.0f;
	}
	*explosion_stream << time_delay;
	return True;
}

//##############################################################################
//########################   Class ExplosionTable    ###########################
//##############################################################################
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	ExplosionTable::CrossedDamageLevelThreshold(
		Scalar old_value,
		Scalar new_value
	)
{
	ExplosionTableIterator iterator(this);
	ExplosionTableEntry *current_exp;

	while ((current_exp = iterator.ReadAndNext()) != NULL)
	{
		if (
				(old_value < current_exp->GetDamageLevel()) &&
				(new_value >= current_exp->GetDamageLevel())
		)
		{
			return True;
		}
	}
	return False;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ExplosionTableEntry*
	ExplosionTable::SelectDamageLevelEntry(Scalar damage_level)
{
	ExplosionTableIterator iterator(this);
	ExplosionTableEntry *current_exp;
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Choose ExplosionTableEntry by damage Level
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	while ((current_exp = iterator.ReadAndNext()) != NULL)
	{
		if (damage_level < current_exp->GetDamageLevel())
		{
			Check_Fpu();
			return current_exp;
		}
	}

	Check_Fpu();
	return NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ExplosionTableEntry*
	ExplosionTable::SelectGraphicStateEntry(Enumeration graphic_state)
{
	ExplosionTableIterator  iterator(this);
	ExplosionTableEntry *current_entry;
	while((current_entry = iterator.ReadAndNext() ) != NULL)
	{
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// Display Explosion if an Entry Exists
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
		if (current_entry->GetGraphicState() == graphic_state)
		{
			Check_Fpu();
			return current_entry;
		}
	}
	Check_Fpu();
	return NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ExplosionTable::CreateExplosion(
		ExplosionTableEntry	*explosion_entry,
		const EntityID	&entity_hit_ID,
		const EntityID	&creating_entity_ID,
		Point3D	explode_position
	)
{
	Check(this);
	Check(explosion_entry);
	explosion_entry->CreateExplosion(
		entity_hit_ID,
		creating_entity_ID,
		explode_position
	);
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ExplosionTable::ExplosionTable(MemoryStream *exp_stream):
	TableOf<ExplosionTableEntry*, int>(NULL, True)
{
	int entry_count;
	*exp_stream >> entry_count;

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Create all of the ExplosionTableEntries
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	ExplosionTableEntry *exp_entry;

	for(int ii=0;ii<entry_count;++ii)
	{
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// Create a new ExplosionTableEntry
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
		exp_entry = new ExplosionTableEntry(exp_stream);
		Register_Object(exp_entry);
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// Add the entry the the ExplosionTable
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
		AddValue(exp_entry, ii);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ExplosionTable::~ExplosionTable()
{
	ExplosionTableIterator iterator(this);
   iterator.DeletePlugs();
}

//##############################################################################
//  Tool Support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	ExplosionTable::CreateStreamedExplosionTable(
		ResourceFile	*resource_file,
		CString			model_name,
		NotationFile	*exp_file,
		CString			exp_filename,
		MemoryStream	*explosion_stream
	)
{
	Check(resource_file);
	Check(exp_file);
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Make a namelist of all Explosion Entries
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	NameList *exp_namelist = exp_file->MakePageList();
	Register_Object(exp_namelist);
	if (!exp_namelist->EntryCount())
	{
		DEBUG_STREAM << exp_file << " is empty or missing \n"<<std::endl << std::flush;
		Unregister_Object(exp_namelist);
		delete exp_namelist;
		return False;
	}
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Write the Number of Explosion Entries for this Entity
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	int entry_count = exp_namelist->EntryCount();
	*explosion_stream << entry_count;

	NameList::Entry *exp_entry = exp_namelist->GetFirstEntry();
	while(exp_entry)
	{
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// While more pages Add ExplosionEntries to the stream
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
		ExplosionTableEntry::CreateStreamedExplosion(
			resource_file,
			exp_filename,
			exp_file,
			exp_entry->GetName(),
			explosion_stream
		);
		exp_entry = exp_entry->GetNextEntry();
	}
	Unregister_Object(exp_namelist);
	delete exp_namelist;

	return True;
}

//##########################################################################
//######################    EntityEffectWatcher    #########################
//##########################################################################

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


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
EntityEffectWatcher::EntityEffectWatcher(
	Entity *entity_watched
)
{
	entityWatched = entity_watched;
	entityWatched->AddEffectWatcher(this);
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Init array to hold previous damageLevel values
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	Verify(entityWatched->damageZoneCount > 0);
	oldDamageLevel = new Scalar[entityWatched->damageZoneCount];
	Register_Pointer(oldDamageLevel);
	for(int ii=0;ii<entityWatched->damageZoneCount;++ii)
	{
    	oldDamageLevel[ii] = 0.0f;
	}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

EntityEffectWatcher::~EntityEffectWatcher()
{
	Unregister_Pointer(oldDamageLevel);
   delete[] oldDamageLevel;
}
//##############################################################################
//  Execute
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

void
	EntityEffectWatcher::Execute()
{
	Check(this);
	Check(entityWatched);
	Check_Pointer(entityWatched->damageZones);
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Check the flags to see if any damageZones
	// have been modified since the last Execute was called
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	DamageZone *current_zone;
	ExplosionTable *exp_table;
	ExplosionTableEntry *exp_entry(NULL);
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Traverse damageZones looking for Changes
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	for (int ii=0;ii<entityWatched->damageZoneCount; ++ii)
	{
		current_zone = entityWatched->damageZones[ii];
		Check(current_zone);
		ResourceDescription::ResourceID	exp_res;
      exp_table = current_zone->GetExplosionTable();
		Check(exp_table);
		//
		//~~~~~~~~~~~~~~~~~~~
		// Check Damage Level
		//~~~~~~~~~~~~~~~~~~~
		//
		if (current_zone->DamageLevelChanged())
		{
			exp_entry = exp_table->SelectDamageLevelEntry(current_zone->damageLevel);
         //
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			// If delta was large enough signal an update
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			//
			if (
				exp_table->CrossedDamageLevelThreshold(
					oldDamageLevel[ii],
					current_zone->damageLevel
				) &&
				(entityWatched->GetInstance() == Entity::MasterInstance)
			)
			{
				entityWatched->ForceUpdate(Entity::DamageZoneUpdateModelFlag);
			}
			oldDamageLevel[ii] = current_zone->damageLevel;
		}
		//
		//~~~~~~~~~~~~~~~~~~~~
		// Check Graphic State
		//~~~~~~~~~~~~~~~~~~~~
		//
		if (current_zone->GraphicStateChanged())
		{
			exp_entry = exp_table->SelectGraphicStateEntry(
				current_zone->GetGraphicState()
			);
			//
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			// Always update for a graphic state change
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			//
			if (entityWatched->GetInstance() == Entity::MasterInstance)
			{
				entityWatched->ForceUpdate(Entity::DamageZoneUpdateModelFlag);
			}
		}
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// Notify RendererManager of Any Change
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
		if (exp_entry)
		{
			Check(exp_entry);
			exp_res = exp_entry->GetExplosionResourceID();

			Check(application);
			Check(application->GetRendererManager());
			application->GetRendererManager()->StartEntityEffect(
				entityWatched,
				current_zone,
				exp_res
			);
			//
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			// Reset the DamageZone::changedFlags if
			// I am a master and no update occurred OR
			// if I am a replicant
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			//
			if (
					(
						(entityWatched->GetInstance() == Entity::MasterInstance) &&
						!entityWatched->DamageZoneUpdateModelFlagSet()
					) ||
					(
						(entityWatched->GetInstance() == Entity::ReplicantInstance)
					)
			)
			{
				current_zone->ResetChangedFlags();
			}
		}
		exp_entry = NULL;
	}
	Check_Fpu();
}

//##############################################################################
//  Test Support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	EntityEffectWatcher::TestInstance() const
{
	Check(entityWatched);
	return True;
}
