//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Defines a symbol table
//
// $Header: $
// $NoKeywords: $
//=============================================================================//

#pragma warning (disable:4514)

#include "utlsymbol.h"
#include "tier0/threadtools.h"
#include "stringpool.h"
#include "generichash.h"
#include "tier0/vprof.h"
#include <stddef.h>

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#define INVALID_STRING_INDEX CStringPoolIndex( 0xFFFF, 0xFFFF )

#define MIN_STRING_POOL_SIZE	2048

//-----------------------------------------------------------------------------
// globals
//-----------------------------------------------------------------------------

CUtlSymbolTableMT* CUtlSymbol::s_pSymbolTable = 0; 
bool CUtlSymbol::s_bAllowStaticSymbolTable = true;


//-----------------------------------------------------------------------------
// symbol methods
//-----------------------------------------------------------------------------

void CUtlSymbol::Initialize()
{
	// If this assert fails, then the module that this call is in has chosen to disallow
	// use of the static symbol table. Usually, it's to prevent confusion because it's easy
	// to accidentally use the global symbol table when you really want to use a specific one.
	Assert( s_bAllowStaticSymbolTable );

	// necessary to allow us to create global symbols
	static bool symbolsInitialized = false;
	if (!symbolsInitialized)
	{
		s_pSymbolTable = new CUtlSymbolTableMT;
		symbolsInitialized = true;
	}
}

void CUtlSymbol::LockTableForRead()
{
	Initialize();
	s_pSymbolTable->LockForRead();
}

void CUtlSymbol::UnlockTableForRead()
{
	s_pSymbolTable->UnlockForRead();
}

//-----------------------------------------------------------------------------
// Purpose: Singleton to delete table on exit from module
//-----------------------------------------------------------------------------
class CCleanupUtlSymbolTable
{
public:
	~CCleanupUtlSymbolTable()
	{
		delete CUtlSymbol::s_pSymbolTable;
		CUtlSymbol::s_pSymbolTable = NULL;
	}
};

static CCleanupUtlSymbolTable g_CleanupSymbolTable;

CUtlSymbolTableMT* CUtlSymbol::CurrTable()
{
	Initialize();
	return s_pSymbolTable; 
}


//-----------------------------------------------------------------------------
// string->symbol->string
//-----------------------------------------------------------------------------

CUtlSymbol::CUtlSymbol( const char* pStr )
{
	m_Id = CurrTable()->AddString( pStr );
}

const char* CUtlSymbol::String( ) const
{
	return CurrTable()->String(m_Id);
}

const char* CUtlSymbol::StringNoLock( ) const
{
	return CurrTable()->StringNoLock(m_Id);
}

void CUtlSymbol::DisableStaticSymbolTable()
{
	s_bAllowStaticSymbolTable = false;
}

//-----------------------------------------------------------------------------
// checks if the symbol matches a string
//-----------------------------------------------------------------------------

bool CUtlSymbol::operator==( const char* pStr ) const
{
	if (m_Id == UTL_INVAL_SYMBOL) 
		return false;
	return strcmp( String(), pStr ) == 0;
}



//-----------------------------------------------------------------------------
// symbol table stuff
//-----------------------------------------------------------------------------
inline const char* CUtlSymbolTable::DecoratedStringFromIndex( const CStringPoolIndex &index ) const
{
	Assert( index.m_iPool < m_StringPools.Count() );
	Assert( index.m_iOffset < m_StringPools[index.m_iPool]->m_TotalLen );

	// step over the hash decorating the beginning of the string
	return (&m_StringPools[index.m_iPool]->m_Data[index.m_iOffset]);
}

inline const char* CUtlSymbolTable::StringFromIndex( const CStringPoolIndex &index ) const
{
	// step over the hash decorating the beginning of the string
	return DecoratedStringFromIndex(index)+sizeof(hashDecoration_t);
}

// The first two bytes of each string in the pool are actually the hash for that string.
// Thus we compare hashes rather than entire strings for a significant perf benefit.
// However since there is a high rate of hash collision we must still compare strings
// if the hashes match.
bool CUtlSymbolTable::CLess::operator()( const CStringPoolIndex &i1, const CStringPoolIndex &i2 ) const
{
	// Need to do pointer math because CUtlSymbolTable is used in CUtlVectors, and hence
	// can be arbitrarily moved in memory on a realloc. Yes, this is portable. In reality,
	// right now at least, because m_LessFunc is the first member of CUtlRBTree, and m_Lookup
	// is the first member of CUtlSymbolTabke, this == pTable
	CUtlSymbolTable *pTable = (CUtlSymbolTable *)( (byte *)this - offsetof(CUtlSymbolTable::CTree, m_LessFunc) ) - offsetof(CUtlSymbolTable, m_Lookup );

#if 1 // using the hashes
	const char *str1, *str2;
	hashDecoration_t hash1, hash2;

	if (i1 == INVALID_STRING_INDEX)
	{
		str1 = pTable->m_pUserSearchString;
		hash1 = pTable->m_nUserSearchStringHash;
	}
	else
	{
		str1 = pTable->DecoratedStringFromIndex( i1 );
		hashDecoration_t storedHash = *reinterpret_cast<const hashDecoration_t *>(str1);
		str1 += sizeof(hashDecoration_t);
		AssertMsg2( storedHash == ( !pTable->m_bInsensitive ? HashString(str1) : HashStringCaseless(str1) ),
			"The stored hash (%d) for symbol %s is not correct.", storedHash, str1 );
		hash1 = storedHash;
	}

	if (i2 == INVALID_STRING_INDEX)
	{
		str2 = pTable->m_pUserSearchString;
		hash2 = pTable->m_nUserSearchStringHash;
	}
	else
	{
		str2 = pTable->DecoratedStringFromIndex( i2 );
		hashDecoration_t storedHash = *reinterpret_cast<const hashDecoration_t *>(str2);
		str2 += sizeof(hashDecoration_t);
		AssertMsg2( storedHash == ( !pTable->m_bInsensitive ? HashString(str2) : HashStringCaseless(str2) ),
			"The stored hash (%d) for symbol '%s' is not correct.", storedHash, str2 );
		hash2 = storedHash;
	}

	// compare the hashes
	if ( hash1 == hash2 )
	{
		if ( !str1 && str2 )
			return false;
		if ( !str2 && str1 )
			return true;
		if ( !str1 && !str2 )
			return false;
		
		// if the hashes match compare the strings
		if ( !pTable->m_bInsensitive )
			return strcmp( str1, str2 ) < 0;
		else
			return V_stricmp( str1, str2 ) < 0;
	}
	else
	{
		return hash1 < hash2;
	}

#else // not using the hashes, just comparing strings
	const char* str1 = (i1 == INVALID_STRING_INDEX) ? pTable->m_pUserSearchString :
		pTable->StringFromIndex( i1 );
	const char* str2 = (i2 == INVALID_STRING_INDEX) ? pTable->m_pUserSearchString :
		pTable->StringFromIndex( i2 );

	if ( !str1 && str2 )
		return false;
	if ( !str2 && str1 )
		return true;
	if ( !str1 && !str2 )
		return false;
	if ( !pTable->m_bInsensitive )
		return strcmp( str1, str2 ) < 0;
	else
		return strcmpi( str1, str2 ) < 0;
#endif
}


//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
CUtlSymbolTable::CUtlSymbolTable( int growSize, int initSize, bool caseInsensitive ) : 
	m_Lookup( growSize, initSize ), m_bInsensitive( caseInsensitive ), m_StringPools( 8 )
{
}

CUtlSymbolTable::~CUtlSymbolTable()
{
	// Release the stringpool string data
	RemoveAll();
}


CUtlSymbol CUtlSymbolTable::Find( const char* pString ) const
{	
	VPROF( "CUtlSymbol::Find" );
	if (!pString)
		return CUtlSymbol();
	
	// Store a special context used to help with insertion
	m_pUserSearchString = pString;
	m_nUserSearchStringHash = m_bInsensitive ? HashStringCaseless(pString) : HashString(pString) ;
	
	// Passing this special invalid symbol makes the comparison function
	// use the string passed in the context
	UtlSymId_t idx = m_Lookup.Find( INVALID_STRING_INDEX );

#ifdef _DEBUG
	m_pUserSearchString = NULL;
	m_nUserSearchStringHash = 0;
#endif

	return CUtlSymbol( idx );
}


int CUtlSymbolTable::FindPoolWithSpace( int len )	const
{
	for ( int i=0; i < m_StringPools.Count(); i++ )
	{
		StringPool_t *pPool = m_StringPools[i];

		if ( (pPool->m_TotalLen - pPool->m_SpaceUsed) >= len )
		{
			return i;
		}
	}

	return -1;
}


//-----------------------------------------------------------------------------
// Finds and/or creates a symbol based on the string
//-----------------------------------------------------------------------------

CUtlSymbol CUtlSymbolTable::AddString( const char* pString )
{
	VPROF("CUtlSymbol::AddString");
	if (!pString) 
		return CUtlSymbol( UTL_INVAL_SYMBOL );

	CUtlSymbol id = Find( pString );
	
	if (id.IsValid())
		return id;

	int lenString = strlen(pString) + 1; // length of just the string
	int lenDecorated = lenString + sizeof(hashDecoration_t); // and with its hash decoration
	// make sure that all strings are aligned on 2-byte boundaries so the hashes will read correctly
	COMPILE_TIME_ASSERT(sizeof(hashDecoration_t) == 2);
	lenDecorated = (lenDecorated + 1) & (~0x01); // round up to nearest multiple of 2

	// Find a pool with space for this string, or allocate a new one.
	int iPool = FindPoolWithSpace( lenDecorated );
	if ( iPool == -1 )
	{
		// Add a new pool.
		int newPoolSize = MAX( lenDecorated + sizeof( StringPool_t ), MIN_STRING_POOL_SIZE );
		StringPool_t *pPool = (StringPool_t*)malloc( newPoolSize );
		pPool->m_TotalLen = newPoolSize - sizeof( StringPool_t );
		pPool->m_SpaceUsed = 0;
		iPool = m_StringPools.AddToTail( pPool );
	}

	// Compute a hash
	hashDecoration_t hash = m_bInsensitive ? HashStringCaseless(pString) : HashString(pString) ;

	// Copy the string in.
	StringPool_t *pPool = m_StringPools[iPool];
	Assert( pPool->m_SpaceUsed < 0xFFFF );	// This should never happen, because if we had a string > 64k, it
											// would have been given its entire own pool.
	
	unsigned short iStringOffset = pPool->m_SpaceUsed;
	const char *startingAddr = &pPool->m_Data[pPool->m_SpaceUsed];

	// store the hash at the head of the string
	*((hashDecoration_t *)(startingAddr)) = hash;
	// and then the string's data
	memcpy( (void *)(startingAddr + sizeof(hashDecoration_t)), pString, lenString );
	pPool->m_SpaceUsed += lenDecorated;

	// insert the string into the vector.
	CStringPoolIndex index;
	index.m_iPool = iPool;
	index.m_iOffset = iStringOffset;

	MEM_ALLOC_CREDIT();
	UtlSymId_t idx = m_Lookup.Insert( index );
	return CUtlSymbol( idx );
}


//-----------------------------------------------------------------------------
// Look up the string associated with a particular symbol
//-----------------------------------------------------------------------------

const char* CUtlSymbolTable::String( CUtlSymbol id ) const
{
	if (!id.IsValid()) 
		return "";
	
	Assert( m_Lookup.IsValidIndex((UtlSymId_t)id) );
	return StringFromIndex( m_Lookup[id] );
}


//-----------------------------------------------------------------------------
// Remove all symbols in the table.
//-----------------------------------------------------------------------------

void CUtlSymbolTable::RemoveAll()
{
	m_Lookup.Purge();
	
	for ( int i=0; i < m_StringPools.Count(); i++ )
		free( m_StringPools[i] );

	m_StringPools.RemoveAll();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pFileName - 
// Output : FileNameHandle_t
//-----------------------------------------------------------------------------
FileNameHandle_t CUtlFilenameSymbolTable::FindOrAddFileName( const char *pFileName )
{
	if ( !pFileName )
	{
		return NULL;
	}

	// find first
	FileNameHandle_t hFileName = FindFileName( pFileName );
	if ( hFileName )
	{
		return hFileName;
	}

	// Fix slashes+dotslashes and make lower case first..
	char fn[ MAX_PATH ];
	V_strncpy( fn, pFileName, sizeof( fn ) );
	V_RemoveDotSlashes( fn );

	// Split the filename into constituent parts
	char basepath[ MAX_PATH ];
	V_ExtractFilePath( fn, basepath, sizeof( basepath ) );
	char filename[ MAX_PATH ];
	V_strncpy( filename, fn + V_strlen( basepath ), sizeof( filename ) );

	// not found, lock and look again
	FileNameHandleInternal_t handle;
	m_lock.LockForWrite();
	handle.path = m_StringPool.FindStringHandle( basepath );
	handle.file = m_StringPool.FindStringHandle( filename );
	if ( handle.path && handle.file )
	{
		// found
		m_lock.UnlockWrite();
		return *( FileNameHandle_t * )( &handle );
	}

	// safely add it
	handle.path = m_StringPool.ReferenceStringHandle( basepath );
	handle.file = m_StringPool.ReferenceStringHandle( filename );
	m_lock.UnlockWrite();

	return *( FileNameHandle_t * )( &handle );
}

FileNameHandle_t CUtlFilenameSymbolTable::FindFileName( const char *pFileName )
{
	if ( !pFileName )
	{
		return NULL;
	}

	// Fix slashes+dotslashes and make lower case first..
	char fn[ MAX_PATH ];
	V_strncpy( fn, pFileName, sizeof( fn ) );
	V_RemoveDotSlashes( fn );

	// Split the filename into constituent parts
	char basepath[ MAX_PATH ];
	V_ExtractFilePath( fn, basepath, sizeof( basepath ) );
	char filename[ MAX_PATH ];
	V_strncpy( filename, fn + V_strlen( basepath ), sizeof( filename ) );

	FileNameHandleInternal_t handle;

	m_lock.LockForRead();
	handle.path = m_StringPool.FindStringHandle(basepath);
	handle.file = m_StringPool.FindStringHandle(filename);
	m_lock.UnlockRead();


	if ( ( handle.path == 0 )  || ( handle.file == 0 ) )
		return NULL;

	return *( FileNameHandle_t * )( &handle );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : handle - 
// Output : const char
//-----------------------------------------------------------------------------
bool CUtlFilenameSymbolTable::String( const FileNameHandle_t& handle, char *buf, int buflen )
{
	buf[ 0 ] = 0;

	FileNameHandleInternal_t *internal = ( FileNameHandleInternal_t * )&handle;
	if ( !internal )
	{
		return false;
	}

	m_lock.LockForRead();
	const char *path = m_StringPool.HandleToString(internal->path);
	const char *fn = m_StringPool.HandleToString(internal->file);
	m_lock.UnlockRead();

	if ( !path || !fn )
	{
		return false;
	}

	V_strncpy( buf, path, buflen );
	V_strncat( buf, fn, buflen, COPY_ALL_CHARACTERS );

	return true;
}

void CUtlFilenameSymbolTable::RemoveAll()
{
	m_StringPool.FreeAll();
}

void CUtlFilenameSymbolTable::SpewStrings()
{
	m_lock.LockForRead();
	m_StringPool.SpewStrings();
	m_lock.UnlockRead();
}

bool CUtlFilenameSymbolTable::SaveToBuffer( CUtlBuffer &buffer )
{
	m_lock.LockForRead();
	bool bResult = m_StringPool.SaveToBuffer( buffer );
	m_lock.UnlockRead();

	return bResult;
}

bool CUtlFilenameSymbolTable::RestoreFromBuffer( CUtlBuffer &buffer )
{
	m_lock.LockForWrite();
	bool bResult = m_StringPool.RestoreFromBuffer( buffer );
	m_lock.UnlockWrite();

	return bResult;
}
