//////////////////////////////////////////////////////////////////////////////
//
// Asura_TiledTextureManagement.cpp
//
// Created by Neil Massey.
//
// Copyright (c) 2002 Rebellion. All rights reserved.
//
// Implementation of the tiled texture system
//
//	TP - 21/07/08 - Changed to use a Heap array so we can resize in tools
//
//////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

// This file's header.

#include "Core/Asura_TiledTextureManagement.h"

// Engine independent.
#include "Core/Asura_HeapArray.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_Stream.h"
#include "Core/Asura_TextureManagement.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"

// M A C R O S ///////////////////////////////////////////////////////////////
// You can override this limit in your project's / platform's
// Asura_EngineConfiguration.h. Set it to 0 to disable tiled textures.
#ifndef ASURA_MAX_NO_OF_TEXTURE_TILES
    #define ASURA_MAX_NO_OF_TEXTURE_TILES (0)
#endif

// T Y P E S /////////////////////////////////////////////////////////////////

class Asura_InternalTiledTextureInfo  : public Asura_TiledTextureInfo
{
    public:
		Asura_InternalTiledTextureInfo();
        bool m_bInUse;
};

Asura_InternalTiledTextureInfo::Asura_InternalTiledTextureInfo()
{
	m_bInUse = false;
}

// G L O B A L S /////////////////////////////////////////////////////////////

#if ASURA_MAX_NO_OF_TEXTURE_TILES > 0
static Asura_HeapArray<Asura_InternalTiledTextureInfo> g_axTiles;
const int Asura_TiledTextureManagement::ASURA_TILEDTEXTURE_UNKNOWN_FILE(-1);
const int Asura_TiledTextureManagement::ASURA_TILEDTEXTURE_NONE(-1);
#endif

// P R O T O T Y P E S   A N D   I N L I N E   F U N C T I O N S /////////////

#if ASURA_MAX_NO_OF_TEXTURE_TILES > 0

#if _DEBUG
static u_int CountTiles( void )
{
    u_int uNumTiles = 0;
	for( int u = 0; u < Asura_TiledTextureManagement::MaxIndex(); ++u )
    {
        if( g_axTiles[u].m_bInUse ) { ++uNumTiles; }
    }
    return uNumTiles;
}
#endif

#endif // #if ASURA_MAX_NO_OF_TEXTURE_TILES > 0

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

int Asura_TiledTextureManagement::s_iTileMaxIndex(0);
int Asura_TiledTextureManagement::s_iTileCount(0);

#ifdef ASURA_TOOLS
Tools_DynamicArray<Asura_String> Asura_TiledTextureManagement::s_xAsuraFileNameCollection; // collection of full paths to all asr files from which tiles have been loaded
#endif

void Asura_TiledTextureManagement::Initialise(void)
{
	// Set the size of the HeapArray
	g_axTiles.SetSize(ASURA_MAX_NO_OF_TEXTURE_TILES);

#if ASURA_MAX_NO_OF_TEXTURE_TILES > 0
    for( int u = 0; u < s_iTileMaxIndex; ++u ) 
    {
        // (MP - 20.03.2006) This will get 0 initialised anyway so rather
        // than setting the 'in use' flag to false again, let's check that's
        // already the case. This has the side effect that we'll pick up on
        // code mistakenly loading tiled texture data before this has been
        // initialised.
        Asura_Assert( !g_axTiles[u].m_bInUse, "" );
	}

    PlatformProject_Initialise();
#endif

	s_iTileCount = 0;
}

void Asura_TiledTextureManagement::Shutdown(void)
{
#if ASURA_MAX_NO_OF_TEXTURE_TILES > 0
    ResetInternal( 0, UINT_MAX );

#if _DEBUG
    for( int u = 0; u < s_iTileMaxIndex; ++u ) 
	{ 
		Asura_Assert( !g_axTiles[u].m_bInUse, "" ); 
	}
#endif

#endif // #if ASURA_MAX_NO_OF_TEXTURE_TILES > 0
}

void Asura_TiledTextureManagement::Reset( void )
{
#if ASURA_MAX_NO_OF_TEXTURE_TILES > 0
#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	u_int uTextureResetGroup = Asura_Texture_Management::GetTextureResetGroup();
	ResetInternal( uTextureResetGroup, uTextureResetGroup );
#else
    ResetInternal( ASURA_TEXTURE_START_OF_RESETTABLE_GROUPS, UINT_MAX );
#endif // #if defined(ASURA_USE_MULTIPLE_HANDLERS)
#endif // #if ASURA_MAX_NO_OF_TEXTURE_TILES > 0
}

void Asura_TiledTextureManagement::Reset( u_int uResetGroup )
{
#if ASURA_MAX_NO_OF_TEXTURE_TILES > 0
    ResetInternal( uResetGroup, uResetGroup );
#endif
}

#if ASURA_MAX_NO_OF_TEXTURE_TILES > 0
void Asura_TiledTextureManagement::ResetInternal( u_int uMinResetGroup, u_int uMaxResetGroup )
{
#if _DEBUG
    Asura_OutputToDebugger(( "Asura_TiledTextureManagement::Reset(): %u tiles at reset\n", CountTiles() ));
#endif
    
    for( int u = 0; u < s_iTileMaxIndex; ++u )
    {
        const Asura_InternalTiledTextureInfo & xTileInfo = g_axTiles[u];

        if( !xTileInfo.m_bInUse ) 
		{ 
			continue; 
		}

		const u_int uResetGroup = xTileInfo.m_uResetGroup;

        if( !(uMinResetGroup <= uResetGroup && uResetGroup <= uMaxResetGroup) ) 
		{ 
			continue; 
		}

        DeleteTileByIndex( u );
    }
}
#endif

Asura_Handle Asura_TiledTextureManagement::AddTile( const Asura_Hash_ID uHashIDOfTile, const Asura_TiledTextureInfo& xNewTile )
{
	Asura_TiledTextureInfo xTile;
	
	xTile = xNewTile;
	xTile.m_uTileHashID = uHashIDOfTile;

	return AddTile( xTile );
}

Asura_Handle Asura_TiledTextureManagement::AddTile( const Asura_TiledTextureInfo& xNewTile )
{
    Asura_Handle iTileIndex( ASURA_INVALID_HANDLE );

#if ASURA_MAX_NO_OF_TEXTURE_TILES > 0

	// if tile already exists then return its assigned 'handle' except in 
	// tools builds where tiles can be added more than once - NDB
	iTileIndex = GetHandleOfTile( xNewTile.m_uTileHashID );

	if( iTileIndex != ASURA_INVALID_HANDLE )
	{
#ifdef ASURA_TOOLS
		DeleteTileByIndex( iTileIndex,false );
#else
		return iTileIndex;
#endif
	}

	const u_int uCurrentSize( g_axTiles.Size() );

	// find next free slot in global tile array
    for( u_int u(0); u<uCurrentSize;++u )
    {
        if( !g_axTiles[u].m_bInUse ) 
		{ 
			iTileIndex = u; 
			break; 
		}
    }

#ifdef ASURA_TOOLS
	if (ASURA_INVALID_HANDLE == iTileIndex)
	{
		// We hit the array's limit, so we need to increase it
		g_axTiles.Resize(uCurrentSize*2);
		// Assign iFree to the first value of the new extended part of the array
		iTileIndex = static_cast<int>(uCurrentSize);
	}
#endif // ASURA_TOOLS

    Asura_Assert( ASURA_INVALID_HANDLE != iTileIndex, "No free tile slots. Increase ASURA_MAX_NO_OF_TEXTURE_TILES." );
    
	if( iTileIndex != ASURA_INVALID_HANDLE )
    {
        static_cast< Asura_TiledTextureInfo& >( g_axTiles[iTileIndex] ) = xNewTile;

        g_axTiles[iTileIndex].m_uResetGroup = Asura_Texture_Management::GetTextureResetGroup();
        g_axTiles[iTileIndex].m_bInUse      = true;
		// g_axTiles[iFree].m_iIndexOfTextureUsed = ASURA_TEXTURE_NONE;// (MH - 23.02.2005) initialise this to avoid it being 0.

		++s_iTileCount;

		if( !(iTileIndex < s_iTileMaxIndex) ) s_iTileMaxIndex = iTileIndex+1;
	}
#endif

    return iTileIndex;
}

#if ASURA_MAX_NO_OF_TEXTURE_TILES > 0
bool Asura_TiledTextureManagement::DeleteTile( Asura_Hash_ID uHashIDOfTile )
{
    Asura_Handle iIdx = GetHandleOfTile( uHashIDOfTile );
    
	if( ASURA_INVALID_HANDLE != iIdx )
    {
        DeleteTileByIndex( iIdx );
        return true;
    }

    return false;
}

bool Asura_TiledTextureManagement::DestroyTexture( const Asura_Hash_ID uTextureHashID )
{
	// delete any tiles which cite this texture as their parent texture
	for( int i=0;i<s_iTileMaxIndex;++i )
    {
		if( (g_axTiles[i].m_bInUse) && (g_axTiles[i].m_uTextureHashID == uTextureHashID) )
        {
            DeleteTileByIndex(i);
        }
    }
    	
	const int iTextureID( Asura_Texture_Management::GetIndexFromHashID(uTextureHashID) );

	bool bSuccess( !IsTextureUsedByAnyTile( iTextureID ) );

	// make sure that texture is destroyed if no tiles refference it
	if( bSuccess )
	{
		Asura_Texture_Management::DestroyTexture( iTextureID );
	}

	// return true if no tiles use this textures
	return bSuccess ;
}

bool Asura_TiledTextureManagement::DestroyTexture( const char* szTextureName )
{
	return DestroyTexture( Asura_GetHashID_IgnoreExtension( szTextureName ) );
}

Asura_Handle Asura_TiledTextureManagement::GetHandleOfTile( Asura_Hash_ID uHashIDOfTile )
{
	Asura_Handle xRtn(ASURA_INVALID_HANDLE);

	if( uHashIDOfTile != ASURA_HASH_ID_UNSET )
	{
		for( int i=0; i < s_iTileMaxIndex; ++i )
		{
			if( (g_axTiles[i].m_bInUse) && (uHashIDOfTile == g_axTiles[i].m_uTileHashID) )
			{
				xRtn = i;
				break;
			}
		}
	}

	return xRtn;
}

const Asura_TiledTextureInfo* Asura_TiledTextureManagement::GetTileInfoByHash( Asura_Hash_ID uHashIDOfTile )
{
	const Asura_TiledTextureInfo* pxRtn( NULL );

	Asura_Handle iIdx = GetHandleOfTile( uHashIDOfTile );

	if( ASURA_INVALID_HANDLE != iIdx )
	{
		pxRtn = &g_axTiles[iIdx];
	}
	
    return pxRtn;
}

const Asura_TiledTextureInfo* Asura_TiledTextureManagement::GetTileInfoByID( u_int uID )
{
	uID = Asura_Maths::ClampToRange( uID, 0u, static_cast<u_int>(s_iTileMaxIndex) );
    return &g_axTiles[uID];
}

void Asura_TiledTextureManagement::SetTextureHandlesFromNames( void )
{
    for( int i=0; i<s_iTileMaxIndex;++i )
    {
        if( g_axTiles[i].m_bInUse )
        {
            g_axTiles[i].m_iIndexOfTextureUsed = Asura_Texture_Management::GetTextureIndex( g_axTiles[i].m_xNameOfTextureUsed );
        }
    }
}

void Asura_TiledTextureManagement::SetTextureHandlesFromHash( void )
{
    for( int i=0; i<s_iTileMaxIndex;++i )
    {
        if( g_axTiles[i].m_bInUse )
        {
			g_axTiles[i].m_iIndexOfTextureUsed = Asura_Texture_Management::GetIndexFromHashID( g_axTiles[i].m_uTextureHashID );
        }
    }
}

const Asura_TiledTextureInfo* Asura_TiledTextureManagement::GetTileInfo( Asura_Handle iHandle )
{
    Asura_Assert( 0 <= iHandle && iHandle < s_iTileMaxIndex && g_axTiles[iHandle].m_bInUse, "Invalid handle." );

	if (!( (0 <= iHandle) && (iHandle < s_iTileMaxIndex) && (g_axTiles[iHandle].m_bInUse) ) )
	{
		return NULL;
	}

    return &g_axTiles[iHandle];
}

void Asura_TiledTextureManagement::SetResetGroup( Asura_Handle iHandle, u_int uResetGroup )
{
    Asura_Assert( 0 <= iHandle && iHandle < s_iTileMaxIndex && g_axTiles[iHandle].m_bInUse, "Invalid handle." );
    g_axTiles[iHandle].m_uResetGroup = uResetGroup;
}
#endif

const u_int* Asura_TiledTextureManagement::MakeTileIDList( u_int* puContainer,const int iSize )
{
	for( int i(0),iID(0); (i<s_iTileMaxIndex) && (iID<iSize);++i )
    {
        if( g_axTiles[i].m_bInUse )
        {
            puContainer[iID] = i;
			++iID;
        }
    }

	return puContainer;
}

void Asura_TiledTextureManagement::WriteResetGroupReport(u_int uGroup, Asura_Stream& xLog)
{
	const u_int uLOGBUFFERSIZE = 96;
	char szLogBuffer[uLOGBUFFERSIZE];

	bool bFirst = true;
	for (int u = 0; u < s_iTileMaxIndex; u++)
	{
		if (g_axTiles[u].m_bInUse && (g_axTiles[u].m_uResetGroup == uGroup))
		{
			Asura_PrintfToBfr(szLogBuffer,"TiledTexture:\t(0x%08x)\t%s\n", uLOGBUFFERSIZE, true, g_axTiles[u].m_uTileHashID, g_axTiles[u].m_xNameOfTextureUsed.GetString() );
			xLog << szLogBuffer;
			if (bFirst) bFirst = false;
		}
	}

	if( bFirst )
	{
		xLog << "TextureTile:\t\t\tNone\n";
	}
}


#ifdef ASURA_TOOLS
const int Asura_TiledTextureManagement::AddAsuraFileName( const Asura_String& xFileName )
{
	int iIndex(0);
	const int iSize( s_xAsuraFileNameCollection.GetSize() );

	// try to find file name in collection
	for( iIndex=0;(iIndex<iSize) && (s_xAsuraFileNameCollection[iIndex] != xFileName);++iIndex );

	// if file name not found then add it to collection
	if(iIndex == iSize)
	{
		s_xAsuraFileNameCollection += xFileName;
	}

	// return position of file name in collection
	return iIndex;
}

void Asura_TiledTextureManagement::LogAsuraFileName( const Asura_Handle iHandle,const int iAsuraFileNameIndex )
{
	Asura_Assert( 0 <= iHandle && iHandle < s_iTileMaxIndex && g_axTiles[iHandle].m_bInUse, "Invalid handle." );

	if (!( (0 <= iHandle) && (iHandle < s_iTileMaxIndex) && (g_axTiles[iHandle].m_bInUse) ) )
	{
		return;
	}

	g_axTiles[iHandle].m_iAsuraFileNameIndex = iAsuraFileNameIndex;
}

const Asura_String* const Asura_TiledTextureManagement::GetAsuraFileName( const Asura_Handle iTileHandle )
{
	const Asura_String* pxFileName( NULL );

	// find tile
	const Asura_TiledTextureInfo* pxInfo( GetTileInfo(iTileHandle) );
	
	Asura_Assert( pxInfo != NULL,"invalid tile handle" );

	// ensure that the tile's asura file name index is valid
	if( pxInfo && ( pxInfo->m_iAsuraFileNameIndex > -1 ) )
	{
		const u_int uIndex( pxInfo->m_iAsuraFileNameIndex );

		Asura_Assert( uIndex < s_xAsuraFileNameCollection.GetSize(), "Asura file name log index out of range" );

		if( uIndex < s_xAsuraFileNameCollection.GetSize() )
		{
			pxFileName = &s_xAsuraFileNameCollection[uIndex];	
		}
	}

	return pxFileName;
}

const Asura_String* const Asura_TiledTextureManagement::GetAsuraFileNameByHash( const u_int uTileHashID )
{
	const Asura_String* pxRtn(NULL);

	const Asura_Handle iHandle( GetHandleOfTile(uTileHashID) );

	if( iHandle != ASURA_INVALID_HANDLE )
	{
		pxRtn = GetAsuraFileName( iHandle );
	}

	return pxRtn;
}
#endif

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

#if ASURA_MAX_NO_OF_TEXTURE_TILES > 0

void Asura_TiledTextureManagement::DeleteTileByIndex( const int iIdx, const bool bDestroyUnusedTextures /* = true */ )
{
	Asura_Assert( 0 <= iIdx && iIdx < s_iTileMaxIndex, "" );

    Asura_InternalTiledTextureInfo & xTileInfo = g_axTiles[iIdx];

    // Mark as not in use immediately so IsTextureUsedByAnyTile() doesn't
    // count this tile.
    xTileInfo.m_bInUse = false;

    if( bDestroyUnusedTextures && !IsTextureUsedByAnyTile(xTileInfo.m_iIndexOfTextureUsed) )
    {
        Asura_Texture_Management::DestroyTexture( xTileInfo.m_iIndexOfTextureUsed );
    }

#ifdef ASURA_TOOLS
    xTileInfo.m_xNameOfTextureUsed.MakeNull();
#endif

	--s_iTileCount;
}

bool Asura_TiledTextureManagement::IsTextureUsedByAnyTile( int iTexID )
{
    for( int i = 0; i < s_iTileMaxIndex; ++i )
    {
        if( (g_axTiles[i].m_bInUse) && (iTexID == g_axTiles[i].m_iIndexOfTextureUsed) )
        {
            return true;
        }
    }
    return false;
}

#endif // #if ASURA_MAX_NO_OF_TEXTURE_TILES > 0
