//
// Asura_PS2_Object_System.cpp
//
// Created by Richard M. Smith - 30/07/2002
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Rendering/Asura_PS2_Object_System.h"

// Engine independent.
#include "Core/Asura_Material_System.h"
#include "Core/Asura_Stream.h"
#include "Core/Asura_TextureFlags.h"
#include "Debugging/Asura_Logfile.h"

// Engine platform-specific.
#include "Rendering/Asura_PlatformRenderObject.h"

// C O N S T A N T S /////////////////////////////////////////////////////////

static const float OBJECT_THRESHOLD_SCALE_SQD_FOR_1_VIEWPORT  = 0.6f  * 0.6f;
static const float OBJECT_THRESHOLD_SCALE_SQD_FOR_2_VIEWPORTS = 0.45f * 0.45f;
static const float OBJECT_THRESHOLD_SCALE_SQD_FOR_4_VIEWPORTS = 0.3f  * 0.3f;  // Also used for 3.

// S T A T I C S /////////////////////////////////////////////////////////////

#if defined(ASURA_USE_MULTIPLE_HANDLERS) || defined(ASURA_USE_STREAMING)
Asura_TemplateHashTree<Asura_PS2_Object_Handler>	Asura_PS2_Object_System::s_xHandlerTree;
Asura_PS2_Object_Handler*						Asura_PS2_Object_System::s_pxCurrentHandler	= NULL;
#else
Asura_PS2_Object_Handler							Asura_PS2_Object_System::s_xHandler;
#endif

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

//////////////////////////////////////////////////////////////////////////
// Asura_PS2_Object_System
//////////////////////////////////////////////////////////////////////////

void Asura_PS2_Object_System::Initialise( void )
{
	// Nothing to do here
}

void Asura_PS2_Object_System::Shutdown( void )
{
#if defined(ASURA_USE_STREAMING) || defined(ASURA_USE_MULTIPLE_HANDLERS)
	s_xHandlerTree.Reset();
	s_pxCurrentHandler	= NULL;
#else
	GetCurrentHandler().Reset();
#endif
}

void Asura_PS2_Object_System::Reset( void )
{
	#if _DEBUG
	    Asura_OutputToDebugger(( "Asura_PS2_ObjectSystem::Reset(): %u skins at reset\n", CountObjects() ));
	#endif

#if defined(ASURA_USE_STREAMING)
	s_xHandlerTree.Reset();
	s_pxCurrentHandler	= NULL;
#else
	GetCurrentHandler().Reset();
#endif
}

int Asura_PS2_Object_System::CountObjects()
{
#if defined(ASURA_USE_STREMING) || defined(ASURA_USE_MULTIPLE_HANDLERS)
	int iObjectCount = 0;
	
	Asura_TemplateHashTree_Iterator<Asura_PS2_Object_Handler> xIt(s_xHandlerTree);
	for ( ; !xIt.Done(); xIt.Next() )
	{
		Asura_PS2_Object_Handler* pxHandler = xIt.GetCurrent();
		iObjectCount += pxHandler->CountObjects();
	}
	
	return iObjectCount;
#else
	return GetCurrentHandler().CountObjects();
#endif
}

void Asura_PS2_Object_System::SetNumViewports( u_int uNumViewports )
{
#if defined(ASURA_USE_MULTIPLE_HANDLERS) || defined(ASURA_USE_STREAMING)
	Asura_TemplateHashTree_Iterator<Asura_PS2_Object_Handler> xIt(s_xHandlerTree);
	for ( ; !xIt.Done(); xIt.Next() )
	{
		Asura_PS2_Object_Handler* pxHandler = xIt.GetCurrent();
		Asura_Assert(pxHandler,"");

		pxHandler->SetNumViewports( uNumViewports );
	}
#else
	s_xHandler.SetNumViewports( uNumViewports );
#endif
}

#if defined(ASURA_USE_MULTIPLE_HANDLERS) || defined(ASURA_USE_STREAMING)

void Asura_PS2_Object_System::ReparentHandler(Asura_PS2_Object_Handler& xSourceHandler, const Asura_Hash_ID uNewParentHash)
{
	Asura_PS2_Object_Handler*	pxParentHandler	= s_xHandlerTree.GetDataByHash(uNewParentHash);

	if(pxParentHandler != NULL)
	{
		xSourceHandler.m_xObjectStorage.SetParent(&(pxParentHandler->m_xObjectStorage));
	}
}

void Asura_PS2_Object_System::ReparentHandler(const Asura_Hash_ID uSourceHash, const Asura_Hash_ID uNewParentHash)
{
	Asura_PS2_Object_Handler*	pxHandler	= s_xHandlerTree.GetDataByHash(uSourceHash);

	if(pxHandler != NULL)
	{
		ReparentHandler(*pxHandler, uNewParentHash);
	}
}

Asura_PS2_Object_Handler* Asura_PS2_Object_System::CreateHandler (const Asura_Hash_ID uHash, const Asura_Hash_ID uParentHandlerHash)
{
	Asura_PS2_Object_Handler*	pxHandler	= s_xHandlerTree.GetDataByHash(uHash);

	// Do we already have this zone?
	if(pxHandler == NULL)
	{
		// No - create one...
		pxHandler	= s_xHandlerTree.AddNode(uHash);

		if (pxHandler != NULL)
		{
			ReparentHandler(*pxHandler, uParentHandlerHash);
		}
	}
	else
	{
		// Yes - this shouldn't happen...
		Asura_Assert(false, "Already have an handler with this hash-ID.");
	}

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


bool Asura_PS2_Object_System::DestroyHandler (const Asura_Hash_ID uHash)
{
	// Call the delete function...
	return(s_xHandlerTree.DeleteNodeByHash(uHash));
}

void Asura_PS2_Object_System::WriteHandlerReport(u_int uHandler, Asura_Stream& xLog)
{
	const u_int uLOGBUFFERSIZE = 96;
	char szLogBuffer[uLOGBUFFERSIZE];

	Asura_PS2_Object_Handler* pxHandler = s_xHandlerTree.GetDataByHash(uHandler);
	if (pxHandler)
	{
		if (!pxHandler->m_xObjectStorage.GetNumberOfNodes())
		{
			xLog << "PS2 Shape:\t\t\tNone\n";
		}
		else
		{
			Asura_TemplateHashTree_Iterator<Asura_PS2_ObjectEntry> xIt(pxHandler->m_xObjectStorage);
			for ( ; !xIt.Done(); xIt.Next() )
			{
				Asura_PS2_ObjectEntry* pxObjectEntry = xIt.GetCurrent();
#ifdef _DEBUG
				Asura_PrintfToBfr( szLogBuffer, "PS2 Shape:\t(0x%08x)\t%s\n", uLOGBUFFERSIZE, true, pxObjectEntry->m_uHashID, pxObjectEntry->m_szName);
#else
				Asura_PrintfToBfr( szLogBuffer, "PS2 Shape:\t(0x%08x)\n", uLOGBUFFERSIZE, true, pxObjectEntry->m_uHashID );
#endif
				xLog << szLogBuffer;
			}		
		}
	}
	else
	{
		xLog << "PS2 Shape:\t\t\tNo Handler\n";
	}
}

#endif	// defined(ASURA_USE_MULTIPLE_HANDLERS) || defined(ASURA_USE_STREAMING)


//////////////////////////////////////////////////////////////////////////
// Asura_PS2_Object_System_Handler
//////////////////////////////////////////////////////////////////////////

void Asura_PS2_Object_Handler::Reset(void)
{
	m_xObjectStorage.Reset();
	m_fAppliedThresholdMultiplier = 0.0f;
}

int Asura_PS2_Object_Handler::CountObjects()
{
	return m_xObjectStorage.GetNumberOfNodes();
}

Asura_PS2_StrippedObject* Asura_PS2_Object_Handler::CreatePS2Object( const char* szName, const float fLODThreshold )
{
	Asura_Assert(m_fAppliedThresholdMultiplier == 0.0f,"Objects should be added before SetNumViewpoints() is called");

    // PS2 object LODs should no longer include the LOD level specification
    // in their name; all LODs should have the same name as the top level.
    Asura_Assert( !('L' == szName[0] && '#' == szName[2]), "" );

    Asura_Hash_ID uHashID = Asura_GetHashID( szName );
    if( ASURA_HASH_ID_UNSET == uHashID ) { return NULL; }

    Asura_PS2_ObjectEntry* pxObjectEntry = FindEntryForHashID( uHashID, true );
    if( pxObjectEntry )
	{
		Asura_PS2_StrippedObject* pxObj = new Asura_PS2_StrippedObject( szName );
		if( pxObj )
		{
			u_int uLOD;
			if( ASURA_HASH_ID_UNSET == pxObjectEntry->m_uHashID )
			{
				// **This is the first LOD**

				pxObjectEntry->m_uHashID = uHashID;
#if _DEBUG
				pxObjectEntry->m_szName  = Asura_StringUtil::Duplicate( szName );
#endif
				uLOD = 0;
			}
			else
			{
				// **This is a subsequent LOD**
#if _DEBUG
				Asura_Assertf( pxObjectEntry->m_uNumLODs, ( "Pre-existing shape %s with no LODs?", szName ));
				// Verify that the name matches; if it doesn't, we must have a hash
				// clash.
				Asura_Assertf( !stricmp(szName, pxObjectEntry->m_szName), ("Hash Clash? (existing name %s, new name %s)", pxObjectEntry->m_szName, szName ));
				// Check against limit.
				Asura_Assertf( pxObjectEntry->m_uNumLODs < ASURA_PS2_MAX_LODS_PER_OBJECT, ("Too many LODs for shape %s", szName ));

				if (pxObjectEntry->m_uNumLODs < ASURA_PS2_MAX_LODS_PER_OBJECT)
				{
					// We assume that the LODs are loaded in order of decreasing detail.
					// PJC - Assume if the threshold is the same that its a duplicate LOD, and ignore it
					if (fLODThreshold * fLODThreshold == pxObjectEntry->m_afLODThresholdsSqd[pxObjectEntry->m_uNumLODs - 1])
					{
						Asura_Assertf( fLODThreshold * fLODThreshold != pxObjectEntry->m_afLODThresholdsSqd[pxObjectEntry->m_uNumLODs - 1], ("Ignoring shape LOD for %s with identical LOD threshold (duplicate?)", szName ));
					}
					else
					{
						Asura_Assertf( fLODThreshold * fLODThreshold > pxObjectEntry->m_afLODThresholdsSqd[pxObjectEntry->m_uNumLODs - 1], ("Shape LODs for %s presented in wrong order", szName ));
					}				
					
					// We assume that all LODs have the same material.
					// PJC - I don't think this assert worked as pxObj's material is not assigned until after this function has returned
					//Asura_Assert( pxObj->GetMaterialIndex() == xEntry.apxLODs[0]->GetMaterialIndex(), "" );
				}
#endif
				uLOD = pxObjectEntry->m_uNumLODs;
			}

			// If uLOD is in range and, if this is not the first LOD, that its threshold is greater than the previous
			if ((uLOD < ASURA_PS2_MAX_LODS_PER_OBJECT)&&((uLOD < 1)||(fLODThreshold * fLODThreshold > pxObjectEntry->m_afLODThresholdsSqd[uLOD - 1])))
			{
				pxObjectEntry->m_uNumLODs                 = uLOD + 1;
				pxObjectEntry->m_apxLODs[uLOD]            = pxObj;
				pxObjectEntry->m_afLODThresholdsSqd[uLOD] = fLODThreshold * fLODThreshold;
				
				return pxObj;
			}
			else
			{
				delete pxObj;
			}
		}
	}

	return NULL;
}

void Asura_PS2_Object_Handler::SetNumViewports( u_int uNumViewports )
{
	if (m_xObjectStorage.GetNumberOfNodes() > 0)
	{
		float fScaleSqd;
		switch( uNumViewports )
		{
		default:
			Asura_Assert( false, "Invalid number of viewports, initalising as 1." );
			// Fall through to a valid case just so fScale gets initialised.

		case 1:
			fScaleSqd = OBJECT_THRESHOLD_SCALE_SQD_FOR_1_VIEWPORT * OBJECT_THRESHOLD_SCALE_SQD_FOR_1_VIEWPORT;
			break;

		case 2:
			fScaleSqd = OBJECT_THRESHOLD_SCALE_SQD_FOR_1_VIEWPORT * OBJECT_THRESHOLD_SCALE_SQD_FOR_1_VIEWPORT;
			break;

		case 3:
		case 4:
			fScaleSqd = OBJECT_THRESHOLD_SCALE_SQD_FOR_1_VIEWPORT * OBJECT_THRESHOLD_SCALE_SQD_FOR_1_VIEWPORT;
			break;
		}

		if (fScaleSqd != m_fAppliedThresholdMultiplier)
		{
			bool bRemoveOld = (m_fAppliedThresholdMultiplier > 0.0f);

			Asura_TemplateHashTree_Iterator<Asura_PS2_ObjectEntry> xIt(m_xObjectStorage);
			for ( ; !xIt.Done(); xIt.Next() )
			{
				Asura_PS2_ObjectEntry* pxObjectEntry = xIt.GetCurrent();
				Asura_Assert(pxObjectEntry,"");

				for( u_int u = pxObjectEntry->m_uNumLODs; u--; ) 
				{ 
					if (bRemoveOld) pxObjectEntry->m_afLODThresholdsSqd[u] /= m_fAppliedThresholdMultiplier;
					pxObjectEntry->m_afLODThresholdsSqd[u] *= fScaleSqd;
#ifdef _DEBUG
					pxObjectEntry->m_bHasBeenScaled = true;
#endif
				}	
			}

			m_fAppliedThresholdMultiplier = fScaleSqd;
		}
	}
}

const Asura_PS2_StrippedObject* Asura_PS2_Object_Handler::GetPS2ObjectToRender( Asura_Hash_ID uHashID, float fDistFromCamSqd )
{
    Asura_PS2_StrippedObject* pxResult = NULL;

    // Find entry in storage.
    const Asura_PS2_ObjectEntry* pxObjectEntry = FindEntryForHashID( uHashID, false );
    if( pxObjectEntry )
	{
		// Verify that LOD thresholds have been scaled appropriately.
#ifdef _DEBUG
		Asura_Assert( pxObjectEntry->m_bHasBeenScaled, "Object LOD thresholds haven't been scaled. Are you calling SetNumViewports()?" );
#endif

		// Find appropriate LOD.
		Asura_Assertf( pxObjectEntry->m_uNumLODs, ("Shape 0x%x has no LODs", uHashID));
		u_int uLOD = pxObjectEntry->m_uNumLODs - 1;
		for( ; uLOD; --uLOD )
		{
			if( fDistFromCamSqd >= pxObjectEntry->m_afLODThresholdsSqd[uLOD] ) { break; }
		}

		pxResult = pxObjectEntry->m_apxLODs[uLOD];
	}

	return pxResult;
}

int Asura_PS2_Object_Handler::GetMaterialIdx( Asura_Hash_ID uHashID )
{
	int iMaterialIdx = ASURA_MATERIAL_NONE;

    // Find entry in storage.
    const Asura_PS2_ObjectEntry* pxObjectEntry = FindEntryForHashID( uHashID, false );
    if (pxObjectEntry && pxObjectEntry->m_uNumLODs) 
	{
		// Extract material ID from first LOD. (We assume all LODs will have the
		// same material so it shouldn't matter which one we take this from.)
		iMaterialIdx = pxObjectEntry->m_apxLODs[0]->GetMaterialIndex();
	}

	Asura_Assertf( pxObjectEntry, ("Couldn't find shape 0x%x to retrive material ID", uHashID ));
	Asura_Assertf( !pxObjectEntry || pxObjectEntry->m_uNumLODs, ("Shape 0x%x has no LODs so cannot retrive material ID", uHashID ));
	return iMaterialIdx;
}

Asura_PS2_ObjectEntry* Asura_PS2_Object_Handler::FindEntryForHashID( Asura_Hash_ID uHashID, bool bAddIfNotFound )
{
	Asura_PS2_ObjectEntry* pxObjectEntry = NULL;
	if (bAddIfNotFound)
	{
		pxObjectEntry = m_xObjectStorage.AddOrFindNode( uHashID );
		Asura_Assertf( pxObjectEntry, ("Couldn't find or add shape 0x%x", uHashID) );
	}
	else
	{
		pxObjectEntry = m_xObjectStorage.GetDataByHash( uHashID );
		Asura_Assertf( pxObjectEntry, ("Shape with hash ID 0x%08X not loaded.", uHashID) );
	}

	return pxObjectEntry;
}
