//
// Asura_Attractor_System.cpp
//
// Created by JGP 03/05/06
//
// Copyright (C) 2006 Rebellion (Derby), All Rights Reserved.
//

#include "Core/Asura.h"

#ifdef ASURA_USE_ATTRACTORS

// This file's header
#include "Asura_Attractor_System.h"

// Asura headers
#include "Entity/Attractors/Asura_ServerEntity_AttractorController.h"
#include "Network/Asura_Message.h"



/////////////////////////////////////////////////////////////////////////////////////////////////////
/*-------------------------**
** Asura_Attractor_Handler **
**-------------------------*/

Asura_Attractor_Handler* Asura_Attractor_Handler::s_pxHandlerForCallback = 0;



Asura_Attractor_Handler::Asura_Attractor_Handler()
:m_xAttractorList(5,true)
{
	m_iNumCompiledAttractors = 0;
	m_pxCompiledAttractors = NULL;
	m_iNumRelativeAttractors = 0;
	m_pxRelativeAttractors = NULL;
	m_uNumLinks = 0;
	m_puLinks = 0;
}


Asura_Attractor_Handler::~Asura_Attractor_Handler()
{
	DestroyTree();
}

void Asura_Attractor_Handler::Reset()
{
	DestroyTree();
}



void Asura_Attractor_Handler::DestroyTree()
{
	if ( m_iNumCompiledAttractors == 0 && m_iNumRelativeAttractors == 0 )
	{
		return;
	}

	Asura_ResourcePushType(ASURA_RESOURCE_ATTRACTORS);
	{
		Asura_ResourceDeallocateArray( m_pxCompiledAttractors );
		m_iNumCompiledAttractors = 0;

		Asura_ResourceDeallocateArray( m_pxRelativeAttractors );
		m_iNumRelativeAttractors = 0;

		Asura_ResourceDeallocateArray( m_puLinks );
		m_uNumLinks = 0;
	}
	Asura_ResourcePopType();

	m_xAttractorList.Clear();

	m_xAttractorTree.DeleteNodes();
}


///////////////////////////////////////////////////////////////////////////////////////
// Collect Attractors
///////////////////////////////////////////////////////////////////////////////////////


static Asura_Vector_3 g_xLOSPos;
static Asura_Vector_3 g_xLOSDir;
static float g_fLOSDistance;

void Asura_Attractor_Handler::CollectAttractors( const Asura_Vector_3& xPos, const Asura_Vector_3& xDir, const float fMinDistance, const float fMaxDistance, AttractorCallback pfnCallback, void* pCallbackData )
{

	if ( m_iNumCompiledAttractors == 0 && m_iNumRelativeAttractors == 0 )
	{
		return;
	}

	m_pfnCallback = pfnCallback;
	m_pCallbackData = pCallbackData;

	g_xLOSDir = xDir;
	float fDist = g_fLOSDistance = fMaxDistance - fMinDistance;

	g_xLOSPos = xPos + g_xLOSDir * fMinDistance;

	//check fixed attractors
	s_pxHandlerForCallback = this;
	m_xAttractorTree.TestTreeWithRay( g_xLOSPos, g_xLOSDir, &fDist, CollectAttractorsCallback );
	s_pxHandlerForCallback = 0;

	//check entity relative attractors
	Asura_Attractor_EntityRelative* pxAttractor = m_pxRelativeAttractors;
	for(int i=0;i<m_iNumRelativeAttractors;i++,pxAttractor++)
	{
		if(!pxAttractor->IsActive()) continue;
		if(!pxAttractor->UpdatePosition()) continue;

		if(pxAttractor->LOSTest(g_xLOSPos, g_xLOSDir, g_fLOSDistance))
		{
			#ifdef ASURA_USE_STREAMING
			m_pfnCallback( m_pCallbackData, pxAttractor, m_uHashID );
			#else
			m_pfnCallback( m_pCallbackData, pxAttractor );
			#endif
		}
	}
}

// Takes a world-space axis-aligned bounding box
void Asura_Attractor_Handler::CollectAttractors( const Asura_Bounding_Box& xBB, AttractorCallback pfnCallback, void* pCallbackData /* = NULL */ )
{
	if ( m_iNumCompiledAttractors == 0 && m_iNumRelativeAttractors == 0 )
	{
		return;
	}

	m_pfnCallback = pfnCallback;
	m_pCallbackData = pCallbackData;

	//check fixed attractors
	s_pxHandlerForCallback = this;
	m_xAttractorTree.TestTreeWithBB( xBB, CollectAttractorsBBCallback );
	s_pxHandlerForCallback = 0;

	//check entity relative attractors
	Asura_Attractor_EntityRelative* pxAttractor = m_pxRelativeAttractors;
	for(int i=0;i<m_iNumRelativeAttractors;i++,pxAttractor++)
	{
		if(!pxAttractor->IsActive()) continue;
		if(!pxAttractor->UpdatePosition()) continue;

		if(pxAttractor->CouldIntersect(xBB))
		{
#ifdef ASURA_USE_STREAMING
			m_pfnCallback( m_pCallbackData, pxAttractor, m_uHashID );
#else
			m_pfnCallback( m_pCallbackData, pxAttractor );
#endif
		}
	}
}


void Asura_Attractor_Handler::CollectAttractorsCallback( const unsigned int uIndex )
{
	Asura_Assert(s_pxHandlerForCallback,"");

	const Asura_Attractor_Compiled* const pxAttractor = s_pxHandlerForCallback->m_pxCompiledAttractors + uIndex;
	if(!pxAttractor->IsActive()) return;
	if(pxAttractor->LOSTest(g_xLOSPos, g_xLOSDir, g_fLOSDistance))
	{
		#ifdef ASURA_USE_STREAMING
		s_pxHandlerForCallback->m_pfnCallback( s_pxHandlerForCallback->m_pCallbackData, pxAttractor, s_pxHandlerForCallback->m_uHashID );
		#else
		s_pxHandlerForCallback->m_pfnCallback( s_pxHandlerForCallback->m_pCallbackData, pxAttractor );
		#endif
	}
}

void Asura_Attractor_Handler::CollectAttractorsBBCallback( const unsigned int uIndex )
{
	Asura_Assert(s_pxHandlerForCallback,"");

	const Asura_Attractor_Compiled* const pxAttractor = s_pxHandlerForCallback->m_pxCompiledAttractors + uIndex;
	if(!pxAttractor->IsActive()) return;

	// We could do a more accurate check here, but it isn't really necessary.
	{
#ifdef ASURA_USE_STREAMING
		s_pxHandlerForCallback->m_pfnCallback( s_pxHandlerForCallback->m_pCallbackData, pxAttractor, s_pxHandlerForCallback->m_uHashID );
#else
		s_pxHandlerForCallback->m_pfnCallback( s_pxHandlerForCallback->m_pCallbackData, pxAttractor );
#endif
	}
}



/////////////////////////////////////////////////////////////////////////////////////////////////////
/*------------------------**
** Asura_Attractor_System **
**------------------------*/



// Static singleton access
Asura_Attractor_System* Asura_Attractor_System::s_pxInstance;



///////////////////////////////////////////////////////////////////////////////////////
// Singleton
///////////////////////////////////////////////////////////////////////////////////////


Asura_Attractor_System* Asura_Attractor_System::Initialise()
{
	s_pxInstance = new Asura_Attractor_System();
	return s_pxInstance;
}


void Asura_Attractor_System::Shutdown()
{
	delete s_pxInstance;
	s_pxInstance = NULL;
}


void Asura_Attractor_System::Reset()
{
	#if defined(ASURA_USE_STREAMING)
	// Reset the entire handler tree...
	s_pxInstance->m_xHandlerTree.Reset();

	// Remove the current handler...
	s_pxInstance->m_pxCurrentHandler	= NULL;
	#else
	//Reset the only instance
	s_pxInstance->m_xHandler.Reset();
	#endif	// defined(ASURA_USE_STREAMING)
}



///handler handling
#if defined(ASURA_USE_STREAMING)
Asura_Attractor_Handler* Asura_Attractor_System::CreateHandler (const Asura_Hash_ID uHash)
{
	Asura_Attractor_Handler*	pxHandler	= m_xHandlerTree.GetDataByHash(uHash);

	// Do we already have this handler?
	if(pxHandler == NULL)
	{
		// No - create one...
		pxHandler	= m_xHandlerTree.AddNode(uHash);
		pxHandler->m_uHashID = uHash;
	}
	else
	{
		// Yes - this shouldn't happen...
		Asura_Assert(false, "Already have a texture animation handler with this hash-ID.");
	}

	// Return the handler...
	return(pxHandler);
}

bool Asura_Attractor_System::DestroyHandler (const Asura_Hash_ID uHash)
{
	// Is this the current handler?
	if(m_pxCurrentHandler == m_xHandlerTree.GetDataByHash(uHash))
	{
		m_pxCurrentHandler	= NULL;
	}

	// Call the delete function...
	return(m_xHandlerTree.DeleteNodeByHash(uHash));
}

void Asura_Attractor_System::SetCurrentHandler(Asura_Attractor_Handler* pxHandler)
{
	if(pxHandler != m_pxCurrentHandler)
	{
		//and change the handler
		m_pxCurrentHandler = pxHandler;

	}
}
#endif	// defined(ASURA_USE_STREAMING)


#ifdef ASURA_USE_STREAMING
struct CollectAttractorsCallbackData
{
	Asura_Vector_3 xPos;
	Asura_Vector_3 xDir;
	float fMinDistance;
	float fMaxDistance;
	AttractorCallback pfnCallback;
	void* pCallbackData;
};

struct CollectAttractorsBBCallbackData
{
	Asura_Bounding_Box	m_xBB;
	AttractorCallback	m_pfnCallback;
	void*				m_pCallbackData;
};

bool Asura_Attractor_System::CollectAttractorsCallback(Asura_Hash_ID uHash, Asura_Attractor_Handler* pxHandler, void* pData)
{
	CollectAttractorsCallbackData* pxData = (CollectAttractorsCallbackData*) pData;

	pxHandler->CollectAttractors(pxData->xPos
									,pxData->xDir
									,pxData->fMinDistance
									,pxData->fMaxDistance
									,pxData->pfnCallback
									,pxData->pCallbackData);

	return true;
}

bool Asura_Attractor_System::CollectAttractorsBBCallback(Asura_Hash_ID uHash, Asura_Attractor_Handler* pxHandler, void* pData)
{
	CollectAttractorsBBCallbackData* pxData = reinterpret_cast<CollectAttractorsBBCallbackData*>(pData);
	pxHandler->CollectAttractors(pxData->m_xBB ,pxData->m_pfnCallback ,pxData->m_pCallbackData);

	return true;
}
#endif // ASURA_USE_STREAMING



void Asura_Attractor_System::CollectAttractors( const Asura_Vector_3& xPos, const Asura_Matrix_3x3& xOrient, const float fMinDistance, const float fMaxDistance, AttractorCallback pfnCallback, void* pCallbackData /*= NULL*/ )
{
	const Asura_Vector_3 xDir(xOrient.e13, xOrient.e23, xOrient.e33);

	CollectAttractors(xPos, xDir, fMinDistance, fMaxDistance, pfnCallback, pCallbackData);
}

void Asura_Attractor_System::CollectAttractors(const Asura_Vector_3& xPos, const Asura_Vector_3& xDir, const float fMinDistance, const float fMaxDistance, AttractorCallback pfnCallback, void* pCallbackData/* = NULL*/)
{
	#ifdef ASURA_USE_STREAMING
	//check all handlers
	CollectAttractorsCallbackData xData;
	xData.xPos = xPos;
	xData.xDir = xDir;
	xData.fMinDistance = fMinDistance;
	xData.fMaxDistance = fMaxDistance;
	xData.pfnCallback = pfnCallback;
	xData.pCallbackData = pCallbackData;

	m_xHandlerTree.Traverse(CollectAttractorsCallback,&xData);
	
	#else
	//check the only handler
	GetCurrentHandler().CollectAttractors(xPos,xDir,fMinDistance,fMaxDistance,pfnCallback,pCallbackData);
	#endif
}

void Asura_Attractor_System::CollectAttractors( const Asura_Bounding_Box& xBB, AttractorCallback pfnCallback, void* pCallbackData /* = NULL */ )
{
#ifdef ASURA_USE_STREAMING
	//check all handlers
	CollectAttractorsBBCallbackData xData;
	xData.m_xBB = xBB;
	xData.m_pfnCallback = pfnCallback;
	xData.m_pCallbackData = pCallbackData;

	m_xHandlerTree.Traverse(CollectAttractorsBBCallback,&xData);

#else
	//check the only handler
	GetCurrentHandler().CollectAttractors(xBB,pfnCallback,pCallbackData);
#endif
}

#ifdef ASURA_USE_STREAMING
const Asura_Attractor_Compiled* const  Asura_Attractor_System::GetAttractorByID(u_int uID, Asura_Hash_ID uHandlerID)
{
	Asura_Attractor_Handler* pxHandler = m_xHandlerTree.GetDataByHash(uHandlerID);
	if(!pxHandler) return 0;

	return pxHandler->GetAttractorByID(uID);
}
#endif //ASURA_USE_STREAMING

void Asura_Attractor_System::HandleMessage(const Asura_Message* pxMessage)
{
	switch(pxMessage->GetMsgID())
	{
		case ASURA_MSG_ATTRACTOR_FLAGS :
		{
			const Asura_Message_AttractorFlags* pxData = reinterpret_cast<const Asura_Message_AttractorFlags*>(pxMessage->GetData());
			const u_int* puAttractorID = reinterpret_cast<const u_int*>(pxData + 1);

			//find all the listed attractors , and set the flags.
			#ifdef ASURA_USE_STREAMING
			Asura_Attractor_Handler* pxHandler = GetHandler(pxData->m_uHandlerID);
			#else
			Asura_Attractor_Handler* pxHandler = &GetCurrentHandler();
			#endif

			if(!pxHandler)
			{
				Asura_Assert(pxHandler,"Failed to get attractor (env hook) handler");
				break;
			}

			for(u_int i=0;i<pxData->m_uNumAttractorIDs;i++,puAttractorID++)
			{
				Asura_Attractor_Compiled* pxAttractor = GetCurrentHandler().GetAttractorByID(*puAttractorID);
				if(pxAttractor)
				{
					u_int uFlags = pxAttractor->GetFlags();
					uFlags &=~ pxData->m_uFlagsToSet;
					uFlags |= (pxData->m_uFlags & pxData->m_uFlagsToSet);
					pxAttractor->SetFlags(uFlags);
				}
			}

			break;
		}

		default :
			Asura_Assert(0,"Unexpected message.");
			break;
	}
}

#endif // ASURA_USE_ATTRACTORS
