#include "ElementRendererHeaders.hpp"
#include "MultiLODElement.hpp"

//#define SPEW_CULL_INFO "your_name_here"

//############################################################################
//############################  MultiLODElement  ################################
//############################################################################

HGOSHEAP
	ElementRenderer::MultiLODElement::s_Heap = NULL;

ElementRenderer::MultiLODElement::ClassData*
	ElementRenderer::MultiLODElement::DefaultData = NULL;

ElementRenderer::Element::DrawMethod
	ElementRenderer::MultiLODElement::DrawMethods[DrawStateCount]=
{
	DRAW_METHOD(MultiLODElement,	Inherit),
	DRAW_METHOD(MultiLODElement,	Override)
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::MultiLODElement::InitializeClass()
{
	Verify(!DefaultData);

	Verify(!s_Heap);
	s_Heap = gos_CreateMemoryHeap("MultiLOD", 0, g_LibraryHeap);
	Check_Pointer(s_Heap);

	DefaultData =
		new ClassData(
			MultiLODElementClassID,
			"ElementRenderer::MultiLODElement",
			BaseClass::DefaultData,
			reinterpret_cast<Factory>(&Make)
		);
	Check_Object(DefaultData);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::MultiLODElement::TerminateClass()
{
	Check_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;

	Check_Pointer(s_Heap);
	gos_DestroyMemoryHeap(s_Heap);
	s_Heap = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::MultiLODElement::MultiLODElement(
	Stuff::MemoryStream *stream,
	int version,
	ShapeHolder shapes
):
	ListElement(DefaultData, stream, version, shapes, false)
{
	Check_Pointer(this);
	Check_Object(stream);

	unsigned index = (m_state>>DrawStateBit) & DrawStateMask;
	Verify(index < DrawStateCount);
	m_draw = DrawMethods[index];

	//
	//------------------------------------------------------
	// Older Multi-LOD versions just read in the whole thing
	//------------------------------------------------------
	//
	if (version < 13)
	{
		if (version >= 11)
			*stream >> m_activeChildren;
		else
		{
			int count;
			*stream >> count;
			Verify(static_cast<unsigned>(count) < 65536);
			m_activeChildren = static_cast<WORD>(count);
		}
		m_list.SetLength(m_activeChildren);
		for (WORD s=0; s<m_activeChildren; ++s)
		{
			m_list[s] = Create(stream, version);
			Element *child = m_list[s];
			SetParentElement(child, this);
			if (child->GetCullMode() != AlwaysCullMode)
			{
				if (child->m_localOBB.sphereRadius > 0.0f || child->AreBoundsWrong())
					child->SetVolumeCullMode();
				else
					child->SetNeverCullMode();
			}
		}

		//
		//-------------------------
		// Load in the LOD settings
		//-------------------------
		//
		Verify(m_list.GetLength() < 65536);
		WORD lod_count = static_cast<WORD>(m_list.GetLength());
		m_LODs.SetLength(lod_count);
		for (WORD lod=0; lod<lod_count; ++lod)
		{
			*stream >> m_LODs[lod].m_nearSquared >> m_LODs[lod].m_fadeInSquared;
			*stream >> m_LODs[lod].m_fadeOutSquared >> m_LODs[lod].m_farSquared;
		}
	}

	//
	//------------------------------------------------------------------------
	// Newer versions only load in the parts of the Multi-LOD that are visible
	//------------------------------------------------------------------------
	//
	else
	{
		*stream >> m_activeChildren;
		m_list.SetLength(m_activeChildren);
		m_LODs.SetLength(m_activeChildren);
		WORD used=0;
		for (WORD i=0; i<m_activeChildren; ++i)
		{
			*stream >> m_LODs[used].m_nearSquared >> m_LODs[used].m_fadeInSquared;
			*stream >> m_LODs[used].m_fadeOutSquared >> m_LODs[used].m_farSquared;

			//
			//-----------------------------------------------------
			// If this can't be seen because of the offset, dump it
			//-----------------------------------------------------
			//
			int length;
			*stream >> length;
			if (i<m_activeChildren-1 && LODElement::s_Offset > m_LODs[used].m_farSquared)
			{
				stream->AdvancePointer(length);
				continue;
			}

			//
			//------------------------------------------------------------------------------------
			// If this is the first visible LOD after we have dropped one, reset the near clipping
			// plane to zero
			//------------------------------------------------------------------------------------
			//
			else if (!used && i>0)
			{
				m_LODs[used].m_nearSquared = 0.0f;
				m_LODs[used].m_fadeInSquared = 0.0f;
			}

			//
			//-----------------------------------
			// Now go ahead and read in the child
			//-----------------------------------
			//
			m_list[used] = Create(stream, version);
			Element *child = m_list[used];
			SetParentElement(child, this);
			if (child->GetCullMode() != AlwaysCullMode)
			{
				if (child->m_localOBB.sphereRadius > 0.0f || child->AreBoundsWrong())
					child->SetVolumeCullMode();
				else
					child->SetNeverCullMode();
			}
			++used;
		}
		Verify(used>0);
		if (used < m_activeChildren)
		{
			m_list.SetLength(used);
			m_LODs.SetLength(used);
			m_activeChildren = used;
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::MultiLODElement::MultiLODElement():
	ListElement(DefaultData)
{
	Check_Pointer(this);

	unsigned index = (m_state>>DrawStateBit) & DrawStateMask;
	Verify(index < DrawStateCount);
	m_draw = DrawMethods[index];
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::MultiLODElement::~MultiLODElement()
{
	Check_Object(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::MultiLODElement*
	ElementRenderer::MultiLODElement::Make(
		Stuff::MemoryStream *stream,
		int version,
		ShapeHolder shapes
	)
{
	Check_Object(stream);

	gos_PushCurrentHeap(s_Heap);
	MultiLODElement *element = new MultiLODElement(stream, version, shapes);
	gos_PopCurrentHeap();

	return element;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::MultiLODElement::Save(Stuff::MemoryStream *stream)
{
	Check_Object(this);
	Check_Object(stream);

	//
	//-----------------------------------------
	// We don't want ListElement doing anything
	//-----------------------------------------
	//
	BaseClass::BaseClass::Save(stream);

	//
	//--------------------
	// Save each LOD entry
	//--------------------
	//
	WORD child_count = (WORD)m_list.GetLength();
	Verify((unsigned)m_list.GetLength() < 65536);
	Verify(m_list.GetLength() == child_count);
	*stream << child_count;
	for (WORD i=0; i<child_count; ++i)
	{
		Check_Object(m_list[i]);
		*stream << m_LODs[i].m_nearSquared << m_LODs[i].m_fadeInSquared;
		*stream << m_LODs[i].m_fadeOutSquared << m_LODs[i].m_farSquared;

		//
		//------------------------------------------------------------
		// Make sure to include the length of the child substream here
		//------------------------------------------------------------
		//
		*stream << (int)0;
		unsigned index = stream->GetIndex();
		m_list[i]->Save(stream);
		unsigned length = stream->GetIndex() - index;
		stream->RewindPointer(length + sizeof(int));
		*stream << length;
		stream->AdvancePointer(length);
	}
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::MultiLODElement::AttachLOD(
		WORD index,
		Element *element,
		const Entry &entry
	)
{
	Check_Object(this);
	Verify(index < m_LODs.GetLength());
	m_LODs[index] = entry;
	AttachIndexedChild(index, element);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//  This is necessary for attaching LOD's into the list that might miss certain 
// meshes in the middle ie.. 4/5 of the LOD's have a toe, but lod 2 doesn't
// Needed for the exporter. The grouping there doesn't allow counters.
int 
	ElementRenderer::MultiLODElement::AttachLODFirstAvailableSlot(
		Element *element,
		const Entry &entry
	)
{
	Check_Object(this);
  for (unsigned short index = 0; index < m_LODs.GetLength(); index ++)
  {
    //
    // When initialized these distances are set to 0 (in SetSize)
    //
    if (Stuff::Small_Enough(m_LODs[index].m_nearSquared) && Stuff::Small_Enough( m_LODs[index].m_farSquared))
    {
	    m_LODs[index] = entry;
	    AttachIndexedChild(index, element);
      return index;
    }
  }
	STOP(("MultiLODElement: Can't attach another LOD, already full!"));
  return -1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::MultiLODElement::SetSize(WORD max_size)
{
	Check_Object(this);/*
	gos_PushCurrentHeap(s_Heap);
	for (WORD i=0; i<max_size; ++i)
	{
		m_LODs[i].m_nearSquared = 0.0f;
		m_LODs[i].m_fadeInSquared = 0.0f;
		m_LODs[i].m_fadeOutSquared = 0.0f;
		m_LODs[i].m_farSquared = 0.0f;
	}
	*/
	DWORD i = m_LODs.GetLength();
	m_LODs.SetLength(max_size);
	for (;i<max_size; ++i)
	{
		m_LODs[i].m_nearSquared = 0.0f;
		m_LODs[i].m_fadeInSquared = 0.0f;
		m_LODs[i].m_fadeOutSquared = 0.0f;
		m_LODs[i].m_farSquared = 0.0f;
	}

	BaseClass::SetSize(max_size);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::MultiLODElement::DetachChild(Element *child)
{
	Check_Object(this);
	Check_Object(child);

	//
	//-------------------------------
	// Find the element and unhook it
	//-------------------------------
	//
	Verify(m_LODs.GetLength() < 65536);
	WORD lod_count = static_cast<WORD>(m_LODs.GetLength());
	for (WORD i=0; i<lod_count; ++i)
	{
		Entry *entry = &m_LODs[i];
		if (m_list[i] == child)
		{
			entry->m_nearSquared = 0.0f;
			entry->m_fadeInSquared = 0.0f;
			entry->m_fadeOutSquared = 0.0f;
			entry->m_farSquared = 0.0f;
			break;
		}
	}
	BaseClass::DetachChild(child);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::MultiLODElement::SetDrawState()
{
	Check_Object(this);
	unsigned index = (m_state>>DrawStateBit) & DrawStateMask;
	Verify(index < DrawStateCount);
	m_draw = DrawMethods[index];
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::MultiLODElement::InheritDrawMethod(
		CameraElement *camera,
		const StateChange *inherited_state,
		int culling_state
	)
{
	Check_Object(this);
	Check_Object(inherited_state);
	Check_Object(camera);
	Verify(culling_state != -1);

	//
	//--------------------------------------
	// Draw our bounds if we are supposed to
	//--------------------------------------
	//
	#if defined(LAB_ONLY)
		Callback *callback = GetCallbackSet();
		unsigned index = GetCallbackIndex();
		if (callback && callback[index])
		{
			Check_Pointer(callback[index]);
			(*callback[index])(camera, inherited_state, culling_state, this);
		}
	#endif

	//
	//----------------------------------------------------------------------
	// We need to find out which lod the camera should traverse into, so
	// transform the camera into LOD space and get the distance from the LOD
	//----------------------------------------------------------------------
	//
	Stuff::Point3D camera_origin(camera->GetLocalToWorld());
	Stuff::Point3D lod_origin(GetLocalToWorld());
	camera_origin -= lod_origin;
	Stuff::Scalar distance = camera_origin.GetLengthSquared() + LODElement::s_Offset;
	Min_Clamp(distance, 0.0f);

	//
	//-------------------------------------------------
	// Evaluate each LOD to see if it needs to be drawn
	//-------------------------------------------------
	//
	for (int i=m_LODs.GetLength()-1; i>=0; --i)
	{
		Entry *entry = &m_LODs[i];
		if (entry->m_nearSquared <= distance && entry->m_farSquared >= distance)
		{
			Element *element = m_list[i];
			Check_Object(element);

			//
			//-----------------------------------------
			// Set the fade color if we are in the band
			//-----------------------------------------
			//
			StateChange mixed(*inherited_state);
			unsigned j;
			for (j=0; j<MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive && inherited_state->m_lights[j]; ++j)
			{
				Check_Object(inherited_state->m_lights[j]);
				mixed.m_lights[j] = inherited_state->m_lights[j];
			}
			if(j<MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive)
			{
				mixed.m_lights[j] = NULL;
			}

			Stuff::Scalar fade = FadeColor.alpha;
			if (distance < entry->m_fadeInSquared)
			{
				Stuff::Scalar temp =
					(distance - entry->m_nearSquared) / (entry->m_fadeInSquared - entry->m_nearSquared);
				Verify(temp >= 0.0f && temp <= 1.0f);
				FadeColor.alpha *= temp;
				mixed.SetAlphaMode(MidLevelRenderer::MLRState::AlphaInvAlphaMode);
				mixed.DisableChildAlphaControl();
				mixed.SetRenderPriority(StateChange::AlphaPriority);
				mixed.DisableChildRenderPriorityControl();
			}
			else if (distance > entry->m_fadeOutSquared)
			{
				Stuff::Scalar temp =
					(entry->m_farSquared - distance) / (entry->m_farSquared - entry->m_fadeOutSquared);
				Verify(temp >= 0.0f && temp <= 1.0f);
				FadeColor.alpha *= temp;
				mixed.SetAlphaMode(MidLevelRenderer::MLRState::AlphaInvAlphaMode);
				mixed.DisableChildAlphaControl();
				mixed.SetRenderPriority(StateChange::AlphaPriority);
				mixed.DisableChildRenderPriorityControl();
			}

			camera->DrawElement(element, &mixed);
			FadeColor.alpha = fade;
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::MultiLODElement::OverrideDrawMethod(
		CameraElement *camera,
		const StateChange *inherited_state,
		int culling_state
	)
{
	Check_Object(this);
	Check_Object(inherited_state);
	Check_Object(camera);
	Verify(culling_state != -1);

	//
	//-------------------
	// Mix the properties
	//-------------------
	//
	Check_Object(m_stateChange);
	StateChange mixed;
	mixed.Mix(*inherited_state, *m_stateChange);
	MixLights(
		mixed.m_lights,
		inherited_state->m_lights,
		m_stateChange->m_lights
	);

	//
	//----------------------------------------------------------------------
	// We need to find out which lod the camera should traverse into, so
	// transform the camera into LOD space and get the distance from the LOD
	//----------------------------------------------------------------------
	//
	Stuff::Point3D camera_origin(camera->GetLocalToWorld());
	Stuff::Point3D lod_origin(GetLocalToWorld());
	camera_origin -= lod_origin;
	Stuff::Scalar distance = camera_origin.GetLengthSquared() + LODElement::s_Offset;
	Min_Clamp(distance, 0.0f);

	//
	//-------------------------------------------------
	// Evaluate each LOD to see if it needs to be drawn
	//-------------------------------------------------
	//
	for (int i=m_LODs.GetLength()-1; i>=0; --i)
	{
		Entry *entry = &m_LODs[i];
		if (entry->m_nearSquared <= distance && entry->m_farSquared >= distance)
		{
			Element *element = m_list[i];
			Check_Object(element);

			//
			//-----------------------------------------
			// Set the fade color if we are in the band
			//-----------------------------------------
			//
			Stuff::Scalar fade = FadeColor.alpha;
			if (distance < entry->m_fadeInSquared)
			{
				Stuff::Scalar temp =
					(distance - entry->m_nearSquared) / (entry->m_fadeInSquared - entry->m_nearSquared);
				Verify(temp >= 0.0f && temp <= 1.0f);
				FadeColor.alpha *= temp;
				mixed.SetAlphaMode(MidLevelRenderer::MLRState::AlphaInvAlphaMode);
				mixed.DisableChildAlphaControl();
				mixed.SetRenderPriority(StateChange::AlphaPriority);
				mixed.DisableChildRenderPriorityControl();
			}
			else if (distance > entry->m_fadeOutSquared)
			{
				Stuff::Scalar temp =
					(entry->m_farSquared - distance) / (entry->m_farSquared - entry->m_fadeOutSquared);
				Verify(temp >= 0.0f && temp <= 1.0f);
				FadeColor.alpha *= temp;
				mixed.SetAlphaMode(MidLevelRenderer::MLRState::AlphaInvAlphaMode);
				mixed.DisableChildAlphaControl();
				mixed.SetRenderPriority(StateChange::AlphaPriority);
				mixed.DisableChildRenderPriorityControl();
			}

			camera->DrawElement(element, &mixed);
			FadeColor.alpha = fade;
		}
	}
}
