#include "AdeptHeaders.hpp"

#include "VideoHeaders.hpp"
#include <ElementRenderer\gosFXElement.hpp>
#include <gosFX\EffectLibrary.hpp>
#include <gosFX\Beam.hpp>

#include "Entity.hpp"
#include "Attribute.hpp"

//############################################################################
//###########################  BeamComponent  ################################
//############################################################################

//
// Class Data Support
//
Component::ClassData*
	BeamComponent::DefaultData = NULL;

static DWORD
	Beam_Effect_Count;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BeamComponent::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			BeamComponentClassID,
			"Adept::BeamComponent",
			BaseClass::DefaultData,
			0,
			NULL
		);
	Register_Object(DefaultData);

	Beam_Effect_Count = 0;
#if !defined(NO_STATS)
	AddStatistic("Beam Effects Executed", "effects", gos_DWORD, &Beam_Effect_Count, Stat_AutoReset);
#endif
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BeamComponent::BeamComponent(
	ClassData *class_data,
	MemoryStream *stream,
	VideoComponentWeb *owning_web
):
	VideoComponent(
		class_data,
		stream,
		owning_web,
		ReadFX(stream, owning_web)
	)
{
	Check_Object(this);
	ElementRenderer::gosFXElement *effect = GetElement();
	Check_Object(effect);
	gosFX::Effect *gos_effect = effect->GetEffect();
	Check_Object(gos_effect);

	//
	//------------------------------------------------------
	// Read the attribute id and find it in the web's entity
	//------------------------------------------------------
	//
	int attribute_id;
	*stream >> attribute_id;
	owningEntity = owning_web->GetEntity();
	Check_Object(owningEntity);
	if (attribute_id != -1)
	{
		statusPointer = owningEntity->GetAttributeEntry(attribute_id);
		Check_Object(statusPointer);
		Verify(statusPointer->attributeType == BoolClassID);
	}
	else
	{
		statusPointer = NULL;
		Verify(gos_effect->IsLooped());
		Verify(gos_effect->IsExecuted());
	}

	//
	//--------------------------------------------
	// Start the effect if its execute flag is set
	//--------------------------------------------
	//
	if (gos_effect->IsExecuted())
	{
		gosFX::Effect::ExecuteInfo
			info(
				gos_GetElapsedTime(),
				&effect->GetLocalToWorld(),
				NULL
			);
		gos_effect->Start(&info);
		if(statusPointer)
		{
			bool result = true;
			statusPointer->SetValue(owningEntity, &result);
		}
	}

	//
	//------------------------------------------------------
	// Read the attribute id and find it in the web's entity
	//------------------------------------------------------
	//
	*stream >> attribute_id;
	Verify(attribute_id != -1);
	endPointer = owningEntity->GetAttributeEntry(attribute_id);
	Check_Object(endPointer);
	Verify(endPointer->attributeType == Point3DClassID);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::gosFXElement* BeamComponent::ReadFX(
	MemoryStream *stream,
	VideoComponentWeb *owning_web
)
{
	Check_Object(stream);
	Check_Object(owning_web);

	//
	//----------------------------------------
	// Figure out where the end of the beam is
	//----------------------------------------
	//
	int end_id;
	*stream >> end_id;
	Entity *owning_entity = owning_web->GetEntity();
	Check_Object(owning_entity);
	Verify(end_id != -1);
	AttributeEntry* end_pointer = owning_entity->GetAttributeEntry(end_id);
	Check_Object(end_pointer);
	Verify(end_pointer->attributeType == Point3DClassID);
	Point3D end;
	end_pointer->GetValue(owning_entity, &end);

	//
	//----------------------
	// Compute the beam line
	//----------------------
	//
	Line3D beam;
	Element *element = owning_entity->GetElement();
	Check_Object(element);
	beam.m_origin = element->GetNewLocalToParent();
	end -= beam.m_origin;
	beam.m_length = end.GetLength();
	if (beam.m_length>SMALL)
	{
		Scalar t = 1.0f/beam.m_length;
		beam.m_direction.x = end.x * t;
		beam.m_direction.y = end.y * t;
		beam.m_direction.z = end.z * t;
	}
	else
	{
		beam.m_length = 0.0f;
		beam.m_direction = UnitVector3D::Forward;
	}
	Point3D nearest;
	beam.GetClosestPointTo(gosFX::Effect::LastCameraPosition, &nearest);
	nearest -= gosFX::Effect::LastCameraPosition;
	Scalar range = nearest.GetLengthSquared();

	//
	//---------------------------------------------
	// Find the LOD distance we will be looking for
	//---------------------------------------------
	//
	Scalar lod;
	*stream >> lod;
	int selected_id = -1;
	while (lod != -1.0f)
	{
		int id;
		*stream >> id;
		if (selected_id == -1 && range > lod)
			selected_id = id;
		*stream >> lod;
	}

	//
	//---------------------
	// Get the gosFX number
	//---------------------
	//
	int gfx_id;
	unsigned flags;
	*stream >> gfx_id >> flags;
	if (selected_id != -1)
		gfx_id = selected_id;
	Check_Object(gosFX::EffectLibrary::Instance);
	gosFX::Effect *gos_effect =
		gosFX::EffectLibrary::Instance->MakeEffect(gfx_id, flags);
	Register_Object(gos_effect);

	gos_PushCurrentHeap(ElementRenderer::gosFXElement::s_Heap);
	ElementRenderer::gosFXElement *effect = new ElementRenderer::gosFXElement(gos_effect);
	gos_PopCurrentHeap();

	Register_Object(effect);
	return effect;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BeamComponent::SkipStreamData(MemoryStream *stream)
{
	Check_Object(this);
	Check_Object(stream);

	int id;
	*stream >> id;
	Scalar lod;
	*stream >> lod;
	while (lod != -1.0f)
		*stream >> id;
	unsigned flags;
	*stream >> id >> flags;
	BaseClass::SkipStreamData(stream);

	*stream >> id >> id;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BeamComponent*
	BeamComponent::Create(
		MemoryStream *stream,
		VideoComponentWeb *owning_web
	)
{
	Check_Object(stream);
	Check_Object(owning_web);
	Component *component = DoesComponentExist(stream, owning_web);
	BeamComponent *object;
	if (component)
	{
		object = static_cast<BeamComponent*>(component);
		Check_Object(object);
		object->SkipStreamData(stream);
	}
	else
	{
		object = new BeamComponent(DefaultData, stream, owning_web);
		Register_Object(object);
	}
	return object;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BeamComponent::Execute()
{
	Check_Object(this);
	ElementRenderer::gosFXElement *effect = GetElement();
	Check_Object(effect);
	gosFX::Beam *gos_effect = Cast_Object(gosFX::Beam*, effect->GetEffect());
	Check_Object(gos_effect);
	Verify(
		gos_effect->GetSimulationMode() != gosFX::Effect::DynamicWorldSpaceSimulationMode
		 || !effect->IsMatrixDirty()
	);

	//
	//------------------
	// Execute the gosFX
	//------------------
	//
	bool result = false;
	if (gos_effect->IsExecuted())
	{
		Set_Statistic(Beam_Effect_Count, Beam_Effect_Count+1);
		Point3D end;
		endPointer->GetValue(owningEntity, &end);
		gosFX::Beam::ExecuteInfo
			info(
				gos_GetElapsedTime(),
				&effect->GetLocalToWorld(),
				&end,
				&effect->m_localOBB
			);
		{
			Start_Timer(gosFX::Animation_Time);
			result = gos_effect->Execute(&info);
			Stop_Timer(gosFX::Animation_Time);
		}

		//
		//---------------------------
		// Update the bounding sphere
		//---------------------------
		//
		if (result && effect->m_localOBB.sphereRadius > 0.0f)
			effect->NeedNewBounds();
		else
			effect->SetNeverCullMode();
		effect->Sync();
	}
	else
		effect->SetAlwaysCullMode();

	//
	//------------------------------
	// Set the status is appropriate
	//------------------------------
	//
	if (statusPointer)
	{
		Check_Object(statusPointer);
		Check_Object(owningEntity);
		if(gos_effect->IsLooped())
			result = true;
		statusPointer->SetValue(owningEntity, &result);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BeamComponent::ChannelChanged(Channel *channel)
{
	Check_Object(this);
	Check_Object(channel);
	STOP(("Broken"));
	ElementRenderer::gosFXElement *effect = GetElement();
	Check_Object(effect);
	gosFX::Effect *gos_effect = effect->GetEffect();
	Check_Object(gos_effect);

	gosFX::Effect::ExecuteInfo
		info(
			gos_GetElapsedTime(),
			&effect->GetLocalToWorld(),
			NULL
		);
	switch (channel->GetCommand())
	{
	case VideoRenderer::StartEffectCommandID:
//		Verify(!gos_effect->InWorldSpace() || !effect->IsMatrixDirty());
		gos_effect->Start(&info);
		break;

	case VideoRenderer::StopEffectCommandID:
		gos_effect->Stop();
		break;

	case VideoRenderer::KillEffectCommandID:
		gos_effect->Kill();
		break;

	default:
		BaseClass::ChannelChanged(channel);
		break;
	}
}

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