#include "gosFXHeaders.hpp"
#include <MLR\MLRCardCloud.hpp>

static bool Disable_Fade;

static bool __stdcall CheckDisableFade() {return Disable_Fade;}
static void __stdcall EnableDisableFade() {Disable_Fade = !Disable_Fade;}

static bool Disable_Cast;

static bool __stdcall CheckDisableCast() {return Disable_Cast;}
static void __stdcall EnableDisableCast() {Disable_Cast = !Disable_Cast;}

//############################################################################
//########################  gosFX::Flare__Specification  #############################
//############################################################################

//------------------------------------------------------------------------------
//
gosFX::Flare__Specification::Flare__Specification(
	Stuff::MemoryStream *stream,
	int gfx_version
):
	Card__Specification(FlareClassID, stream, gfx_version)
{
	Check_Pointer(this);
	Verify(m_class == FlareClassID);
	if (gfx_version>=20)
		*stream >> m_fadeInRate >> m_fadeOutRate >> m_rayCastTime;
	else
	{
		m_fadeInRate = 4.0f;
		m_fadeOutRate = 4.0f;
		m_rayCastTime = 0.25f;
	}
}

//------------------------------------------------------------------------------
//
gosFX::Flare__Specification::Flare__Specification():
	Card__Specification(gosFX::FlareClassID)
{
	Check_Pointer(this);
	m_fadeInRate = 4.0f;
	m_fadeOutRate = 4.0f;
	m_rayCastTime = 0.25f;
}

//------------------------------------------------------------------------------
//
gosFX::Flare__Specification::~Flare__Specification()
{
	Check_Object(this);
}

//------------------------------------------------------------------------------
//
gosFX::Flare__Specification*
	gosFX::Flare__Specification::Make(
		Stuff::MemoryStream *stream,
		int gfx_version
	)
{
	Check_Object(stream);

	gos_PushCurrentHeap(Heap);
	Flare__Specification *spec =
		new gosFX::Flare__Specification(stream, gfx_version);
	gos_PopCurrentHeap();

	return spec;
}

//------------------------------------------------------------------------------
//
void
	gosFX::Flare__Specification::Save(Stuff::MemoryStream *stream)
{
	Check_Object(this);
	Check_Object(stream);
	BaseClass::Save(stream);
	*stream << m_fadeInRate << m_fadeOutRate << m_rayCastTime;
}

//------------------------------------------------------------------------------
//
void 
	gosFX::Flare__Specification::BuildDefaults()
{

	Check_Object(this);
	BaseClass::BuildDefaults();
	m_fadeInRate = 4.0f;
	m_fadeOutRate = 4.0f;
	m_rayCastTime = 0.25f;
}

//------------------------------------------------------------------------------
//
bool 
	gosFX::Flare__Specification::IsDataValid(bool fix_data)
{

	Check_Object(this);
	return BaseClass::IsDataValid(fix_data);
}

//------------------------------------------------------------------------------
//
void
	gosFX::Flare__Specification::Copy(Flare__Specification *spec)
{
	Check_Object(this);
	Check_Object(spec);

	BaseClass::Copy(spec);
	m_fadeInRate = spec->m_fadeInRate;
	m_fadeOutRate = spec->m_fadeOutRate;
	m_rayCastTime = spec->m_rayCastTime;
}

//############################################################################
//##############################  gosFX::Flare  ################################
//############################################################################

gosFX::Flare::ClassData*
	gosFX::Flare::DefaultData = NULL;

static bool
	stub_caster(Stuff::Line3D *line)
{
	return false;
}

gosFX::Flare::RayCastMethod
	gosFX::Flare::RayCaster = stub_caster;

//------------------------------------------------------------------------------
//
void
	gosFX::Flare::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			FlareClassID,
			"gosFX::Flare",
			BaseClass::DefaultData,
			(Effect::Factory)&Make,
			(Specification::Factory)&Specification::Make
		);
	Register_Object(DefaultData);

	Disable_Fade = false;
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable Flare Fade", CheckDisableFade, EnableDisableFade, 0 );
	Disable_Cast = false;
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable Flare Cast Delay", CheckDisableCast, EnableDisableCast, 0 );
}

//------------------------------------------------------------------------------
//
void
	gosFX::Flare::TerminateClass()
{
	Check_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}

//------------------------------------------------------------------------------
//
gosFX::Flare::Flare(
	Specification *spec,
	unsigned flags
):
	Card(DefaultData, spec, flags)
{
	Check_Object(spec);

	m_fadeLevel = 0.0f;
}

//------------------------------------------------------------------------------
//
gosFX::Flare::~Flare()
{
}

//------------------------------------------------------------------------------
//
gosFX::Flare*
	gosFX::Flare::Make(
		Specification *spec,
		unsigned flags
	)
{
	Check_Object(spec);

	gos_PushCurrentHeap(Heap);
	Flare *cloud = new gosFX::Flare(spec, flags);
	gos_PopCurrentHeap();

	return cloud;
}

//------------------------------------------------------------------------------
//
void
	gosFX::Flare::Start(ExecuteInfo *info)
{
	Check_Object(this);
	Check_Pointer(info);

	BaseClass::Start(info);

	//
	//------------------------------------------------
	// Cast an initial ray to make sure its done right
	//------------------------------------------------
	//
	Set_Statistic(Flare_Count, Flare_Count+1);
	Specification *spec = GetSpecification();
	m_timeTillCast = spec->m_rayCastTime;
	Stuff::Line3D eye_line;
	eye_line.m_origin = LastCameraPosition;
	Stuff::Point3D to(m_localToWorld);
	Stuff::Vector3D dir;
	dir.Subtract(to, LastCameraPosition);
	eye_line.m_length = dir.GetLength();
	if (!Stuff::Small_Enough(eye_line.m_length))
	{
		Stuff::Scalar len = 1.0f / eye_line.m_length;
		eye_line.m_direction.x = dir.x * len;
		eye_line.m_direction.y = dir.y * len;
		eye_line.m_direction.z = dir.z * len;
		eye_line.m_length -= 0.5f;
		Check_Pointer(RayCaster);
		m_fadeTarget = ((*RayCaster)(&eye_line)) ? 0.0f : 1.0f;
		m_fadeLevel = m_fadeTarget;
	}
	else
	{
		m_fadeTarget = 0.0f;
		m_fadeLevel = 0.0f;
	}
}

//------------------------------------------------------------------------------
//
bool
	gosFX::Flare::Execute(ExecuteInfo *info)
{
	Check_Object(this);
	Check_Object(info);
	GFX_LOGIC("Execute::Flare");

	if (!IsExecuted())
		return false;

	//
	//------------------------
	// Do the effect animation
	//------------------------
	//
	Stuff::Scalar step = static_cast<Stuff::Scalar>(info->m_time - m_lastRan);
	if (!BaseClass::Execute(info))
		return false;

	//
	//----------------------------------
	// See if we need to test visibility
	//----------------------------------
	//
	Set_Statistic(Flare_Count, Flare_Count+1);
	Specification *spec = GetSpecification();
	Check_Object(spec);
	m_timeTillCast -= step;
	if (m_timeTillCast <= 0.0f || Disable_Cast)
	{
		m_timeTillCast = spec->m_rayCastTime;
		Stuff::Line3D eye_line;
		eye_line.m_origin = LastCameraPosition;
		Stuff::Point3D to(m_localToWorld);
		Stuff::Vector3D dir;
		dir.Subtract(to, LastCameraPosition);
		eye_line.m_length = dir.GetLength();
		if (!Stuff::Small_Enough(eye_line.m_length))
		{
			Stuff::Scalar len = 1.0f / eye_line.m_length;
			eye_line.m_direction.x = dir.x * len;
			eye_line.m_direction.y = dir.y * len;
			eye_line.m_direction.z = dir.z * len;
			eye_line.m_length -= 0.5f;
			Check_Pointer(RayCaster);
			m_fadeTarget = ((*RayCaster)(&eye_line)) ? 0.0f : 1.0f;
		}
	}

	//
	//-----------------------------------
	// Adjust the fade level of the flare
	//-----------------------------------
	//
	if (m_age == 0.0f)
		m_fadeLevel = m_fadeTarget;
	else if (m_fadeTarget < m_fadeLevel)
	{
		m_fadeLevel -= step * spec->m_fadeOutRate;
		if (m_fadeLevel < m_fadeTarget || Disable_Fade)
			m_fadeLevel = m_fadeTarget;
	}
	else if (m_fadeTarget > m_fadeLevel)
	{
		m_fadeLevel += step * spec->m_fadeInRate;
		if (m_fadeLevel > m_fadeTarget || Disable_Fade)
			m_fadeLevel = m_fadeTarget;
	}

	return true;
}

//------------------------------------------------------------------------------
//
void gosFX::Flare::Draw(DrawInfo *info)
{
	Check_Object(this);
	Check_Object(info);
	GFX_RENDER("Flare");

	//
	//------------------------------------------
	// If the flare has faded out, don't draw it
	//------------------------------------------
	//
	if (!m_fadeLevel)
		return;

	//
	//----------------------------
	// Set up the common draw info
	//----------------------------
	//
	MidLevelRenderer::DrawEffectInformation dInfo;
	dInfo.effect = m_cardCloud;
	Specification *spec = GetSpecification();
	Check_Object(spec);
	dInfo.state.Combine(info->m_state, spec->m_state);
	dInfo.clippingFlags = info->m_clippingFlags;
	Stuff::LinearMatrix4D local_to_world;
	local_to_world.Multiply(m_localToParent, *info->m_parentToWorld);
	dInfo.effectToWorld = &local_to_world;

	//
	//---------------------
	// Fill in the position
	//---------------------
	//
	m_vertices[0].x = m_scale*m_halfX;
	m_vertices[0].y = -m_scale*m_halfY;
	m_vertices[0].z = 0.0f;

	m_vertices[1].x = -m_scale*m_halfX;
	m_vertices[1].y = -m_scale*m_halfY;
	m_vertices[1].z = 0.0f;

	m_vertices[2].x = -m_scale*m_halfX;
	m_vertices[2].y = m_scale*m_halfY;
	m_vertices[2].z = 0.0f;

	m_vertices[3].x = m_scale*m_halfX;
	m_vertices[3].y = m_scale*m_halfY;
	m_vertices[3].z = 0.0f;

	//
	//----------------
	// Animate the uvs
	//----------------
	//
	Stuff::Scalar u = spec->m_UOffset.ComputeValue(m_age, m_seed);
	Stuff::Scalar v = spec->m_VOffset.ComputeValue(m_age, m_seed);
	Stuff::Scalar u2 = spec->m_USize.ComputeValue(m_age, m_seed);
	Stuff::Scalar v2 = spec->m_VSize.ComputeValue(m_age, m_seed);

	//
	//--------------------------------------------------------------
	// If we are animated, figure out the row/column to be displayed
	//--------------------------------------------------------------
	//
	if (spec->m_animated)
	{
		BYTE columns =
			Stuff::Truncate_Float_To_Byte(
				spec->m_index.ComputeValue(m_age, m_seed)
			);
		BYTE rows = static_cast<BYTE>(columns / spec->m_width);
		columns = static_cast<BYTE>(columns - rows*spec->m_width);

		//
		//---------------------------
		// Now compute the end points
		//---------------------------
		//
		u += u2*columns;
		v += v2*rows;
	}
	u2 += u;
	v2 += v;

	m_uvs[0].x = u;
	m_uvs[0].y = v2;
	m_uvs[1].x = u2;
	m_uvs[1].y = v2;
	m_uvs[2].x = u2;
	m_uvs[2].y = v;
	m_uvs[3].x = u;
	m_uvs[3].y = v;

	//
	//------------------
	// Fill in the color
	//------------------
	//
	m_color.red = m_fadeLevel*spec->m_red.ComputeValue(m_age, m_seed);
	m_color.green = m_fadeLevel*spec->m_green.ComputeValue(m_age, m_seed);
	m_color.blue = m_fadeLevel*spec->m_blue.ComputeValue(m_age, m_seed);
	m_color.alpha = spec->m_alpha.ComputeValue(m_age, m_seed);
	m_colors[0] = m_color;
	m_colors[1] = m_color;
	m_colors[2] = m_color;
	m_colors[3] = m_color;

	//
	//--------------------------------------------------------------
	// Check the orientation mode.  The first case is XY orientation
	//--------------------------------------------------------------
	//
	if (spec->m_alignZUsingX)
	{
		Stuff::Point3D
			camera_in_world(info->m_clipper->GetCameraToWorldMatrix());
		Stuff::Point3D card_in_world(local_to_world);
		Stuff::Vector3D look_at;
		look_at.Subtract(camera_in_world, card_in_world);
		if (spec->m_alignZUsingY)
			local_to_world.AlignLocalAxisToWorldVector(
				look_at,
				Stuff::Z_Axis,
				Stuff::Y_Axis,
				Stuff::X_Axis
			);
		else
			local_to_world.AlignLocalAxisToWorldVector(
				look_at,
				Stuff::Z_Axis,
				Stuff::X_Axis,
				-1
			);
	}

	//
	//-------------------------------------------------------
	// Each matrix needs to be aligned to the camera around Y
	//-------------------------------------------------------
	//
	else if (spec->m_alignZUsingY)
	{
		Stuff::Point3D
			camera_in_world(info->m_clipper->GetCameraToWorldMatrix());
		Stuff::Point3D card_in_world(local_to_world);
		Stuff::Vector3D look_at;
		look_at.Subtract(camera_in_world, card_in_world);
		local_to_world.AlignLocalAxisToWorldVector(
			look_at,
			Stuff::Z_Axis,
			Stuff::Y_Axis,
			-1
		);
	}

	//
	//---------------------
	// Now just do the draw
	//---------------------
	//
	info->m_clipper->DrawEffect(&dInfo);
	BaseClass::BaseClass::Draw(info);
}

//------------------------------------------------------------------------------
//
void
	gosFX::Flare::Kill()
{
	Check_Object(this);

	m_fadeTarget = 1.0f;
	m_fadeLevel = 1.0f;
	m_timeTillCast = 0.0f;
	BaseClass::Kill();
}

//------------------------------------------------------------------------------
//
void
	gosFX::Flare::TestInstance() const
{
	Verify(IsDerivedFrom(DefaultData));
}
