///////////////////////////////////////////////////////////////////////////////
//
// File   : Asura_Streaming_ModuleList.cpp
// Author : Martin G. Harris
// Date   : 25-May-2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//
// Used for visibility testing in the PFX system and so forth.
//
///////////////////////////////////////////////////////////////////////////////

// RJT NOTE: Required for GCC builds as the file list generation doesn't account for VC project exclusion from build
#ifdef ASURA_USE_STREAMING

#include "Core/Asura.h"
#include "Streaming/Asura_Streaming_ModuleList.h"

#include "Rendering/Asura_Module.h"

//////////
// Fill //
//////////
// Fill the list with all modules that intersect the bounding box.
// Return true if we generated a valid list.
bool Asura_Streaming_ModuleList_Base::Fill(Asura_Streaming_ModuleList_UserData& xUserData)
{
	// Clear the lists.
	Reset();

#ifdef _DEBUG
	// This isn't strictly necessary, but makes debugging slightly easier.
	// It makes it easier to see what data was written into the arrays.
	for (u_int u=0; u<m_aucMaxNumZones; ++u)
	{
		xUserData.m_puZoneList[u] = ASURA_HASH_ID_UNSET;
		xUserData.m_pucNumModulesForZone[u] = 0;
	}

	for (u_int u=0; u<m_aucMaxNumModules; ++u)
	{
		xUserData.m_pusModuleList[u] = 0;
	}
#endif //_DEBUG

	// Traverse the module handlers looking for intersecting modules.
	Asura_Module_System::s_xHandlerTree.Traverse(&Fill_Callback, &xUserData);

	if ((m_aucNumModules > m_aucMaxNumModules) ||
		(m_aucNumZones   > m_aucMaxNumZones))
	{
		// List overflowed, reset it.
		Reset();
		return false;
	}

	// Valid list generated.
	return true;
}

///////////////////
// Fill_Callback //
///////////////////
// Callback for module handler tree traversal. Accumulate a list of modules.
// Return false to stop traversal.
bool Asura_Streaming_ModuleList_Base::Fill_Callback(Asura_Hash_ID uHash, Asura_Module_Handler* pxHandler, void* pUserData)
{
	const Asura_Streaming_ModuleList_UserData* const pxUserData = static_cast<Asura_Streaming_ModuleList_UserData*>(pUserData);
	Asura_Streaming_ModuleList_Base* const pxThis = pxUserData->m_pxThis;

	Asura_Assert(pxUserData, "Null user data in callback");
	Asura_Assert(pxHandler,  "Null module handler in callback");
	if (!pxUserData || !pxHandler)
	{
		return false;
	}

	if (!pxHandler->m_bInitialised)
	{
		// Not fully initialised yet.
		return true;
	}

	int iSpaceForModules = (pxThis->m_aucMaxNumModules - pxThis->m_aucNumModules);
	// NOTE: if the space is zero we still have to carry on, in order to see whether we overflow the list or not.
	if (iSpaceForModules < 0) return false;

	if (iSpaceForModules > MAX_VARIABLE_LENGTH_CLIPPING_COUNT)
	{
		iSpaceForModules = MAX_VARIABLE_LENGTH_CLIPPING_COUNT;
	}

	// Work out the position in the array where we wish to add data.
	u_short * const pxModuleArrayPos = &pxUserData->m_pusModuleList[pxThis->m_aucNumModules];

	pxHandler->SetLengthOfVariableLengthCullingList( static_cast<u_short>(iSpaceForModules) );
	pxHandler->TestModuleTree( pxUserData->m_xBox, Asura_Module_Handler::VariableLengthCullingListInternalCallback );
	const int iNumAdded = pxHandler->FillVariableLengthModuleListNoTerminator(pxModuleArrayPos);

	if (iNumAdded<0)
	{
		// List overflowed, too many modules. Mark as invalid and stop the traversal.
		pxThis->m_aucNumModules = 0xff;
		return false;
	}
	else if (iNumAdded == 0)
	{
		// Nothing added, carry on looking.
		return true;
	}

	if (!pxThis->CanAddZone())
	{
		// List overflowed, too many zones. Mark as invalid and stop the traversal.
		pxThis->m_aucNumZones = 0xff;
		return false;
	}

	Asura_Assert(uHash != ASURA_HASH_ID_UNSET, "Unset zone hash ID");

	// Add the zone.
	pxUserData->m_puZoneList[pxThis->m_aucNumZones] = uHash;
	pxUserData->m_pucNumModulesForZone[pxThis->m_aucNumZones] = static_cast<u_char>(iNumAdded);
	pxThis->m_aucNumZones++;

	pxThis->m_aucNumModules = static_cast<u_char>(pxThis->m_aucNumModules + iNumAdded);

	// Carry on looking.
	return true;
}

/////////////////////////////
// IsVisibleToActiveCamera //
/////////////////////////////
// Is the module list visible to the active camera?
bool Asura_Streaming_ModuleList_Base::IsVisibleToActiveCamera(const Asura_Hash_ID* const puZoneList, const u_short* const pusModuleList, const u_char* const pucNumModulesForZone) const
{
	if ( m_aucNumModules == 0 ||
		 m_aucNumZones   == 0 )
	{
		return false; // No modules or no zones in the list.
	}

	u_int uModule = 0;

	for (u_int uZone=0; uZone<m_aucNumZones; ++uZone)
	{
		const Asura_Hash_ID uZoneHash = puZoneList[uZone];

		// Do a hash tree look up for the zone's module handler, but only if necessary.
		const Asura_Module_Handler* pxHandler = &Asura_Module_System::GetCurrentHandler();
		if (!pxHandler || pxHandler->GetHandlerHashID() != uZoneHash)
		{
			pxHandler = Asura_Module_System::GetHandler(uZoneHash);
		}
		if (!pxHandler)
		{
			// Zone is apparently not loaded. Skip it.
			uModule += pucNumModulesForZone[uZone];
			continue;
		}
		
		for (u_int u=0; u<pucNumModulesForZone[uZone]; ++u)
		{
			if (pxHandler->IsModuleVisible(pusModuleList[uModule++]))
			{
				return true; // Active camera can see the module!
			}
		}
	}

	Asura_Assert(uModule == m_aucNumModules, "Error in module visibility function");

	// Not visible.
    return false;
}

#endif	// ASURA_USE_STREAMING
