//
// audioengine/metadatamanager.cpp
//
// Copyright (C) 1999-2006 Rockstar Games.  All Rights Reserved.
//


#include "string/stringhash.h"
#include "atl/string.h"
#include "data/bitfield.h"
#include "file/asset.h"
#include "file/device.h"
#include "grcore/im.h"
#include "grcore/viewport.h"
#include "string/string.h"

#include "audiohardware/debug.h"

#include "engine.h"
#include "enginedefs.h"
#include "metadatamanager.h"
#include "remotecontrol.h"

#include "system/param.h"

namespace rage 
{

NOSTRIP_XPARAM(audiofolder);
PARAM(dontLoadDLCfromAudioFolder, "If set, any DLC audio data is still loaded from the DLC pack, even when using -audiofolder");

PARAM(warnonbadrefs, "[RAGE Audio] Output warnings when object references cannot be resolved");

#define STREAM_READ(dest, size) (void)Verifyf((stream->Read(dest,static_cast<s32>(size))==static_cast<s32>(size)),"Fatal network read error communicating with RAVE.")

struct audNameTableOffset
{
	u32 DECLARE_BITFIELD_2(nto, 24, padding, 8);
};

struct audMetadataObjectHeader
{	
	u32 classId : 8;
	u32 nto : 24;
};

u32 audMetadataObjectInfo::GetChunkId()const 
{	
	return m_MetadataManager->FindChunkIdForObjectPtr(m_ObjectPtr);
}

enum { kNewNameStartId = 0x7FFFFF };

#if __BANK
const char *audMetadataObjectInfo::GetName() const
{
	const audMetadataObjectHeader *nto = reinterpret_cast<const audMetadataObjectHeader*>(m_ObjectPtr);
	return m_MetadataManager->GetObjectNameFromNameTableOffset(nto->nto);
}
#endif

#if !__FINAL
const char *audMetadataObjectInfo::GetName_Debug() const
{
	const audMetadataObjectHeader *nto = reinterpret_cast<const audMetadataObjectHeader*>(m_ObjectPtr);
	return m_MetadataManager->GetObjectNameFromNameTableOffset(nto->nto);
}
#endif

u32 audMetadataManager::ObjectIterator::Next()
{
	for(; m_CurrentIndex < m_MetadataManager.ComputeNumberOfObjects(); m_CurrentIndex++)
	{
		const audMetadataObjectHeader *objectPtr = reinterpret_cast<const audMetadataObjectHeader*>(m_MetadataManager.GetObjectMetadataPtrFromObjectIndex(m_CurrentIndex));
		if(objectPtr->classId == m_TypeFilter)
		{
			return m_CurrentIndex++;
		}
	}
	return m_CurrentIndex;
}

audMetadataManager::audMetadataManager()
{

}

audMetadataManager::~audMetadataManager()
{
}

bool audMetadataManager::Init(const char *metadataType, const char* metadataPath, const NameTableOption nameTableOption, const u32 schemaVersion, const bool optimisedMap/*=false*/, const char *baseChunkName/*=NULL*/)
{
	RAGE_TRACK(audMetadataManager_Init);
#if __BANK
	m_ObjectModifiedCallback = NULL;
	m_IsUsingRAVEData = false;
#endif

	m_OptimisedMap = optimisedMap;	

	m_MetadataTypeHash = atStringHash(metadataType);
	m_SchemaVersion = schemaVersion;
	m_NameTableOption = nameTableOption;

	if(baseChunkName == NULL)
	{
		baseChunkName = "BASE";
	}

	m_BuildMetadataRefHashList = (m_MetadataTypeHash == ATSTRINGHASH("GAMEOBJECTS", 0xA9DAC789));

	audDisplayf("Loading %s:%s from %s,%u", metadataType, baseChunkName, metadataPath, schemaVersion);
	bool ret = LoadMetadataChunk(baseChunkName, metadataPath);

#if __BANK
	// register this manager with the remote control instance 
	g_AudioEngine.GetRemoteControl().RegisterMetadataManager(atStringHash(metadataType), this);
#endif

	return ret;
}

inline int CompareU32(const u32 a, const u32 b)
{
	return a == b ? 0 : (a < b ? -1 : 1);
}

bool audMetadataManager::LoadMetadataChunk(const char *chunkName, const char *metadataPath, u8 *dataBuffer /*= NULL*/, const u32 dataBufferSize /* = 0*/)
{
#if !RSG_FINAL
	if (PARAM_audiofolder.Get() && !PARAM_dontLoadDLCfromAudioFolder.Get())
	{
		const char * lastSlash = strrchr(metadataPath, '\\');
		lastSlash = !lastSlash ? strrchr(metadataPath, '/') : lastSlash;
		s32 offset = (s32)(lastSlash - metadataPath + 1);

		// Strip off the folder path and replace it with the root audio config folder
		if (offset >= 0 && offset < strlen(metadataPath))
		{
			atString modifiedPath;
			modifiedPath += "audio:/config/";
			modifiedPath += &metadataPath[offset];

			// Special case fixes for some DLC packs whose data was renamed by the branch mapping
			modifiedPath.Replace("mpValentines2_", "dlcmpvalentines2_");

			if (stricmp(metadataPath, modifiedPath.c_str()) != 0)
			{
				audDisplayf("Remapping audio file path %s to %s", metadataPath, modifiedPath.c_str());
			}

			return LoadMetadataChunk_Internal(chunkName, modifiedPath.c_str(), dataBuffer, dataBufferSize);
		}
	}
#endif

	return LoadMetadataChunk_Internal(chunkName, metadataPath, dataBuffer, dataBufferSize);
}

bool audMetadataManager::LoadMetadataChunk_Internal(const char *chunkName, const char *metadataPath, u8 *dataBuffer /*= NULL*/, const u32 dataBufferSize /* = 0*/)
{
	SYS_CS_SYNC(m_CritSec);

	const u32 chunkNameHash = atStringHash(chunkName);

#if __BANK
	char fileName[64];
	u8 *buf = NULL;
	u32 len;
	
	audRemoteControl &rc = g_AudioEngine.GetRemoteControl();
	bool requestedChunkFromRAVE = false;
	m_IsUsingRAVEData = rc.IsPresent() && rc.ShouldUseRAVEForMetadataType(m_MetadataTypeHash);
	if(m_IsUsingRAVEData && rc.ShouldUseRAVEForMetadataChunk(chunkName))
	{		
		audDisplayf("Requesting metadata chunk %s from RAVE", chunkName);
		if(!rc.RequestMetadataChunk(m_MetadataTypeHash, chunkNameHash, m_SchemaVersion, buf, len))
		{
			audErrorf("RAVE failed to send metadata!");
		}
		else
		{
			audDisplayf("Got %u bytes", len);
			fiDeviceMemory::MakeMemoryFileName(fileName, sizeof(fileName), buf, len, false, "Metadata");
			metadataPath = fileName;
			requestedChunkFromRAVE = true;
		}
	}
#endif
	
	char nameBuf[255];
	
	s32 chunkIdToUse = -1;
	for(s32 i = 0; i < m_Chunks.GetCount(); i++)
	{
		if(m_Chunks[i].chunkNameHash == chunkNameHash)
		{
			if(m_Chunks[i].enabled)
			{
				audAssertf(false, "Duplicate metadata chunk name: %s", chunkName);
				return false;
			}
			else
			{
				audDisplayf("Using existing metadata chunk index %d for %s", i, chunkName);
				chunkIdToUse = i;
			}
		}
	}

	const char *ext = !__PACKEDDEBUG ? "" : ".rel";

	if(m_SchemaVersion != ~0U)
	{
		formatf(nameBuf, "%s%u%s", metadataPath, m_SchemaVersion, ext);
		if(!ASSET.Exists(nameBuf, ""))
		{
			// fall back to no version number
			audWarningf("Failed to find %s", nameBuf);
			formatf(nameBuf, "%s%s", metadataPath, ext);
		}
	}
	else
	{
		formatf(nameBuf, "%s%s", metadataPath, ext);
	}
	
	fiStream *stream = ASSET.Open(nameBuf, "");

	if(!stream)
	{
		return false;
	}

	u32 schemaVersion;
	STREAM_READ(&schemaVersion, sizeof(schemaVersion));
	if(m_SchemaVersion != ~0U)
	{
		if(schemaVersion < m_SchemaVersion)
		{
			audAssertf(m_SchemaVersion == schemaVersion, "Data is out of date for %s - please get new metadata assets. Code version: %u Assets version: %u", metadataPath, m_SchemaVersion, schemaVersion);
		}
		else if(schemaVersion > m_SchemaVersion)
		{
			audAssertf(m_SchemaVersion == schemaVersion, "Code is out of date for %s - please get latest code. Code version: %u Assets version: %u", metadataPath, m_SchemaVersion, schemaVersion);
		}
	}

	audMetadataChunk &chunk = chunkIdToUse != -1 ? m_Chunks[chunkIdToUse] : m_Chunks.Grow();
	rage_placement_new(&chunk) audMetadataChunk();
	BANK_ONLY(chunk.chunkName = StringDuplicate(chunkName));
	chunk.chunkNameHash = chunkNameHash;
	chunk.metadataPath = StringDuplicate(metadataPath);
	chunk.enabled = true; // ensure the chunk is flagged as enabled before we do any reference fixups

	if(m_OptimisedMap)
	{
		chunk.mapBins = rage_new atRangeArray<audMetadataChunk::MapBin, 256>;
	}
	if(!LoadMetadata(chunk, stream, dataBuffer, dataBufferSize))
	{
		stream->Close();
		return false;
	}
	if(!LoadStringTable(chunk, stream))
	{
		stream->Close();
		return false;
	}
	if(HasExternalNameTable())
	{
		if(!BuildObjectMap_External(chunk, stream))
		{
			stream->Close();
			return false;
		}
	}
	else
	{
		if(!BuildObjectMap(chunk, stream))
		{
			stream->Close();
			return false;
		}
	}

	if (m_BuildMetadataRefHashList)
	{
		audDisplayf("Added %d metadata ref -> hash mappings", chunk.metadataRefHashList.GetCount());

		chunk.metadataRefHashList.QSort(0, chunk.metadataRefHashList.GetCount(), [](const audMetadataChunk::audMetadataRefHashEntry* a, const audMetadataChunk::audMetadataRefHashEntry* b)
		{
			return CompareU32((a->metadataRef.Get() & 0xffffff), (b->metadataRef.Get() & 0xffffff));
		});
	}

	if(HasExternalNameTable() && AreObjectNamesAvailable())
	{	
		fiStream *nameStream = stream;
		u32 nameTableSize = 0;
		BANK_ONLY(if(!requestedChunkFromRAVE))
		{
			char nameTableFileName[128];
			formatf(nameTableFileName, "%s.nametable", nameBuf);
			nameStream = ASSET.Open(nameTableFileName, "");
		}

		if(nameStream)
		{
			nameTableSize = nameStream->Size();

			BANK_ONLY(if(requestedChunkFromRAVE))
			{
				STREAM_READ(&nameTableSize, sizeof(nameTableSize));
				audDisplayf("RAVE name table size %u", nameTableSize);
			}

			LoadExternalNameTable(chunk, nameStream, nameTableSize);
			
			BANK_ONLY(if(!requestedChunkFromRAVE))
			{
				nameStream->Close();
			}
		}
#if __BANK
		else
		{
			audWarningf("Unable to open file %s.nametable", nameBuf);
			//return false; // falling through because we need to close the stream, do we really need to return false
		}		
#endif
	}

	ComputeCrossChunkReferences();
	
	stream->Close();

#if __BANK
	// free up the temporary memory
	if(buf)
	{
		g_AudioEngine.FreeVirtual(buf);
	}
#endif

	return true;
}

bool audMetadataManager::UnloadMetadataChunk(const char *chunkName, const bool deleteDataBuffer /* = true */)
{
	const u32 chunkNameHash = atStringHash(chunkName);
	SYS_CS_SYNC(m_CritSec);
	for(s32 i = 0; i < m_Chunks.GetCount(); i++)
	{
		if(m_Chunks[i].chunkNameHash == chunkNameHash)
		{
			DeleteChunk(m_Chunks[i], deleteDataBuffer);
			m_Chunks[i].enabled = false;
			ComputeStringTableMap();
			ComputeCrossChunkReferences();
			return true;
		}
	}
	return false;
}

void audMetadataManager::ReserveChunk(const char *chunkName)
{
	const u32 chunkNameHash = atStringHash(chunkName);
	SYS_CS_SYNC(m_CritSec);
	for(s32 i = 0; i < m_Chunks.GetCount(); i++)
	{
		if(m_Chunks[i].chunkNameHash == chunkNameHash)
		{
			// Chunk already exists
			return;
		}
	}

	audMetadataChunk &chunk = m_Chunks.Grow();
	rage_placement_new(&chunk) audMetadataChunk();
	BANK_ONLY(chunk.chunkName = StringDuplicate(chunkName));
	chunk.chunkNameHash = chunkNameHash;
	chunk.enabled = false;
}

void audMetadataManager::ComputeCrossChunkReferences()
{
	// for each chunk update any references to include the resolved chunk id and offset
}

void audMetadataManager::ComputeMap(audMetadataChunk &chunk)
{
	sysMemZeroBytes<sizeof(atRangeArray<audMetadataChunk::MapBin, 256>)>(chunk.mapBins);
	
	ASSERT_ONLY(u32 currentBin = 0);
	for(u32 i = 0; i < chunk.numObjects; i++)
	{
		const u32 currentHash = chunk.map[i].key;
		const u32 binForHash = currentHash & 0xFF;
		audMetadataChunk::MapBin &bin = (*chunk.mapBins)[binForHash];
		bin.numEntries++;

		Assert(binForHash >= currentBin);
		ASSERT_ONLY(currentBin = Max(currentBin, binForHash));
	}

	u32 currentSum = 0;
	OUTPUT_ONLY(u32 maxEntries = 0);
	for(u32 i = 1; i < 256; i++)
	{
		audMetadataChunk::MapBin &prevBin = (*chunk.mapBins)[i-1];
		OUTPUT_ONLY(maxEntries = Max<u32>(maxEntries, prevBin.numEntries));
		currentSum += prevBin.numEntries;
		Assign((*chunk.mapBins)[i].startIndex, currentSum);
	}

	audDisplayf("Object map bin max entries %u, average entries %u", maxEntries, chunk.numObjects>>8);
}

void audMetadataManager::Shutdown()
{
	for(atArray<audMetadataChunk>::iterator iter = m_Chunks.begin(); iter != m_Chunks.end(); iter++)
	{
		if((*iter).enabled)
		{
			DeleteChunk(*iter, true);
		}
	}

#if RSG_BANK
	for(s32 i = 0; i < m_NewObjectNameList.GetCount(); i++)
	{
		StringFree(m_NewObjectNameList[i]);
	}
	m_NewObjectNameList.Reset();
#endif // RSG_BANK

	m_Chunks.Reset();
	m_StringTableIdMap.Kill();
}

void audMetadataManager::DeleteChunk(audMetadataChunk &chunk, const bool deleteDataBuffer)
{
	if(chunk.data && deleteDataBuffer)
	{
		delete[] chunk.data;
	}
	chunk.data = NULL;
	if(chunk.stringTableData && chunk.ownsStringTable)
	{
		delete[] (char*) chunk.stringTableData;
		chunk.stringTableData = NULL;
	}
	if(chunk.map)
	{
		delete[] chunk.map;
		chunk.map = NULL;
	}

	if(chunk.objectNameTable)
	{
		delete[] chunk.objectNameTable;
		chunk.objectNameTable = NULL;
	}

	if(chunk.mapBins)
	{
		delete chunk.mapBins;
		chunk.mapBins = NULL;
	}

	chunk.metadataRefHashList.Reset();

#if __BANK
	if(chunk.objectSizes)
	{
		delete[] chunk.objectSizes;
		chunk.objectSizes = NULL;
	}
	for(s32 i = 0; i < chunk.overriddenObjects.GetCount(); i++)
	{
		delete[] (char*) chunk.overriddenObjects[i].ptr;
	}
	
#endif
	BANK_ONLY(StringFree(chunk.chunkName));
	BANK_ONLY(chunk.chunkName = NULL);
	StringFree(chunk.metadataPath);
	chunk.metadataPath = NULL;
}

bool audMetadataManager::LoadMetadata(audMetadataChunk &chunk, fiStream *stream, u8 *dataBuffer, const u32 dataBufferSize)
{
	if(stream == NULL)
	{
		return false;
	}

	u32 size;
	STREAM_READ(&size, sizeof(size));

	if(dataBuffer != NULL)
	{
		if(audVerifyf(size <= dataBufferSize, "Pre-allocated metadata buffer size too small.  Need %u, got %u", size, dataBufferSize))
		{
			chunk.data = dataBuffer;
			chunk.dataSize = dataBufferSize;
		}
		else
		{
			return false;
		}
	}
	else
	{
		chunk.data = rage_aligned_new(16) u8[size];
		chunk.dataSize = size;
	}
	
	STREAM_READ(chunk.data, size);
	
	return true;
}

bool audMetadataManager::BuildObjectMap(audMetadataChunk &chunk, fiStream *stream)
{
	// build object lookup
	u8 len = 0;
	u32 offset;
	u32 stringSize;
	u32 numObjects;
	
	if(stream == NULL)
	{
		return false;
	}

	const u32 chunkId = (u32)(&chunk - m_Chunks.GetElements());
	
	STREAM_READ(&numObjects, sizeof(numObjects));

	STREAM_READ(&stringSize, sizeof(stringSize));

	u32 nameTableVirtOffset = ~0U;
	
	if(AreObjectNamesAvailable() && stringSize > 0)
	{
		chunk.objectNameTableSize = stringSize;
		if(m_NameTableOption == NameTable_BankOnly)
		{
			USE_DEBUG_MEMORY();
			chunk.objectNameTable = rage_new char[chunk.objectNameTableSize];
		}
		else
		{
			chunk.objectNameTable = rage_new char[chunk.objectNameTableSize];
		}
		Assert(chunk.objectNameTable);
		audDisplayf("Using %u bytes for object names", stringSize);

		nameTableVirtOffset = ReserveNameHeapForChunk(chunkId, chunk.objectNameTableSize);
	}
	else
	{
		chunk.objectNameTable = NULL;
		audDisplayf("Name table size %u bytes (not loaded)", stringSize);
	}

#if __BANK
	if(m_IsUsingRAVEData)
	{
		USE_DEBUG_MEMORY();
		chunk.objectSizes = rage_new u32[numObjects];
	}
#endif

	if(numObjects != 0)
	{
		chunk.map = rage_new audMetadataChunk::audObjectMapEntry[numObjects];
		Assert(chunk.map);
	}

	u32 stringOffset = 0;
	
	for(u32 currentObjectIndex = 0; currentObjectIndex < numObjects; currentObjectIndex++)
	{
		STREAM_READ(&len, sizeof(len));

		char objectName[256];
				
		STREAM_READ(&objectName[0], len);
		objectName[len] = 0;

		if(AreObjectNamesAvailable())
		{
			sysMemCpy(chunk.objectNameTable + stringOffset, objectName, len + 1);
			stringOffset += len + 1;
		}

		STREAM_READ(&offset, sizeof(offset));

		const u32 key = atStringHash(objectName);

		chunk.map[currentObjectIndex].key = key;
		chunk.map[currentObjectIndex].offset = offset;
		
		if(AreObjectNamesAvailable())
		{
			// each object stores an offset into the object name table
			// this offset must be updated to contain the chunk ID
			// name table offset is 1 byte in and 24 bits wide
			Assert(offset < chunk.dataSize);
			audMetadataObjectHeader *objectHeader = (audMetadataObjectHeader*)(chunk.data + offset);

			// translate this NTO to virtual
			objectHeader->nto += nameTableVirtOffset;		
		}

		// skip over object size
		u32 objectSize;
		STREAM_READ(&objectSize, sizeof(objectSize));
#if RSG_BANK
		if(chunk.objectSizes)
		{
			chunk.objectSizes[currentObjectIndex] = objectSize;
		}
#endif

	}

	chunk.numObjects = numObjects;
		
	SortHashTable(chunk);

	// parse sound reference list and fix up hashes to offsets
	// NOTE: we only do this when not running -rave, since it makes metadata tweaking much
	// easier if we always deal with hashes
	
#if RSG_BANK
	const bool shouldFixupOffsets = !m_IsUsingRAVEData;
#else
	const bool shouldFixupOffsets = true;
#endif

	u32 numRefs;
	STREAM_READ(&numRefs, sizeof(numRefs));

	/* See m_BuildMetadataRefHashList comments below - if we ever enable this for all objects, this would make sense to do
	if (m_BuildMetadataRefHashList)
	{
		chunk.metadataRefHashList.Reserve(numRefs);
	}
	*/

	for(u32 i = 0 ; i < numRefs; i++)
	{
		STREAM_READ(&offset, sizeof(offset));
		if(shouldFixupOffsets)
		{
			// offset is relative to metadata start, and version id which we dont store as part of
			// m_Data - skip four byte header
			offset -= 8;
			u32 *ref = 	(u32*)(chunk.data + offset);
			const u32 hash = *ref;
			u32 chunkId;
			bool found = SearchForObjectFromHash(hash, chunkId, offset);
			if(!found)
			{
				if(PARAM_warnonbadrefs.Get())
				{
					audWarningf("Invalid audio object reference: %u", *ref);
				}
				*ref = ~0U;
			}
			else
			{
				Assert(chunkId <= 255);
				Assert(offset <= 0x1000000);
				*ref = ((chunkId&0xff) << 24) | (offset & 0xffffff);
			}

			if (m_BuildMetadataRefHashList)
			{
				AddMetadataRefHashEntry(chunk, audMetadataRef::Create(*ref), hash);				
			}
		}
	}

	u32* hashes = Alloca(u32, chunk.numStrings);
	for(u32 i = 0 ; i < chunk.numStrings; i++)
	{
		hashes[i] = atStringHash(chunk.stringsBasePtr + chunk.stringOffsets[i]);
	}

	// bank name reference list and fix up hashes to indices
	STREAM_READ(&numRefs, sizeof(numRefs));
	
	const u32 firstStringId = FindFirstStringIdForChunk(chunkId);
	for(u32 i = 0 ; i < numRefs; i++)
	{
		u32 offset;
		STREAM_READ(&offset, sizeof(offset));
		if(shouldFixupOffsets)
		{
			// offset is relative to metadata start and version id, which we don't store as part of
			// chunk.data
			offset -= 8;
			u32 *ref = 	(u32*)(chunk.data + offset);
			// Check if we already have this string in chunk 0
			u32 existingId = ~0U;
			if(m_Chunks.GetCount() > 1 && (existingId = GetStringIdFromHash(*ref)) < m_Chunks[0].numStrings)
			{
				*ref = existingId;
			}
			else
			{
				*ref = firstStringId + GetStringTableIndexFromHashPrecomputed(*ref, hashes, chunk.numStrings);
			}
		}
	}
	
	return true;
}

void audMetadataManager::AddMetadataRefHashEntry(audMetadataChunk& chunk, audMetadataRef metadataRef, u32 hash)
{
	// url:bugstar:6760226 - Cloud Tunable control over Radio track list starting probability and enable/disable
	// On subsequent projects we generated the metadata ref->hash map for all object types. It has been 
	// retrofitted to GTA to solve a specific issue where we require the hash of RadioStationTrackList refs
	// so we're explicitly testing for that to avoid allocating a massive array for values that we don't need
	const u8 *objectPtr = static_cast<const u8*>(GetObjectMetadataPtr(metadataRef));

	if (objectPtr && *objectPtr == 26) // RadioStationTrackList::TYPE_ID
	{
		audMetadataChunk::audMetadataRefHashEntry metadataRefHashEntry;
		metadataRefHashEntry.hash = hash;
		metadataRefHashEntry.metadataRef = metadataRef;
		chunk.metadataRefHashList.PushAndGrow(metadataRefHashEntry);
	}
}

u32 audMetadataManager::GetObjectHashFromMetadataRef(const audMetadataRef metadataRef) const
{
	if (audVerifyf(m_BuildMetadataRefHashList, "Metadata Ref->Hash is not available for metadata type %u", m_MetadataTypeHash))
	{
#if __BANK
		if (m_IsUsingRAVEData)
		{
			return metadataRef.Get();
		}
		else
#endif
		{
			const u32 searchChunkId = metadataRef.Get() >> 24;
			const u32 searchRefOffset = metadataRef.Get() & 0xffffff;

			if (searchChunkId < (u32)m_Chunks.GetCount())
			{
				const audMetadataChunk& chunk = m_Chunks[searchChunkId];

				s32 start = 0;
				s32 end = static_cast<s32>(chunk.metadataRefHashList.GetCount()) - 1;

				//Binary search for matching object name hash
				while (start <= end)
				{
					const s32 middle = (start + end) >> 1;
					const u32 thisObjectOffset = chunk.metadataRefHashList[middle].metadataRef.Get() & 0xffffff;

					if (searchRefOffset == thisObjectOffset)
					{
						return chunk.metadataRefHashList[middle].hash;
					}
					else if (searchRefOffset < thisObjectOffset)
					{
						end = middle - 1;
					}
					else
					{
						start = middle + 1;
					}
				}
			}
		}
	}

	return 0u;
}

bool audMetadataManager::AddExtraStringTable(const audMetadataChunk &chunk)
{	
	const u32 chunkNameHash = chunk.chunkNameHash ^ ATSTRINGHASH("strings", 0x560A49F7);
	s32 chunkIdToUse = -1;
	for(s32 i = 0; i < m_Chunks.GetCount(); i++)
	{
		if(m_Chunks[i].chunkNameHash == chunkNameHash && m_Chunks[i].enabled == false)
		{
			chunkIdToUse = i;
			break;
		}
	}

	audMetadataChunk &newChunk = chunkIdToUse != -1 ? m_Chunks[chunkIdToUse] : m_Chunks.Grow();
	rage_placement_new(&newChunk) audMetadataChunk();
	newChunk.chunkNameHash = chunkNameHash;
	newChunk.metadataPath = NULL;
	newChunk.numStrings = chunk.numStrings;
	newChunk.stringOffsets = chunk.stringOffsets;
	newChunk.stringsBasePtr = chunk.stringsBasePtr;
	newChunk.stringTableData = chunk.stringTableData;
	newChunk.ownsStringTable = false;
	BANK_ONLY(newChunk.chunkName = "_ExternalStrings");
	ComputeStringTableMap();
	return true;
}

void audMetadataManager::RemoveExtraStringTable(u32 chunkNameHash)
{
	chunkNameHash ^= ATSTRINGHASH("strings", 0x560A49F7);

	for(int i=0; i< m_Chunks.GetCount(); i++)
	{
		if(m_Chunks[i].chunkNameHash == chunkNameHash)
		{
			m_Chunks[i].enabled = false;
			ComputeStringTableMap();
		}
	}
}

void audMetadataManager::LoadExternalNameTable(audMetadataChunk &chunk, fiStream *stream, const u32 nameTableSize)
{
	chunk.objectNameTableSize = nameTableSize;
	if(m_NameTableOption == External_NameTable_BankOnly)
	{
		USE_DEBUG_MEMORY();
		chunk.objectNameTable = rage_new char[chunk.objectNameTableSize];
	}
	else
	{
		chunk.objectNameTable = rage_new char[chunk.objectNameTableSize];
	}
	
	stream->Read(chunk.objectNameTable, chunk.objectNameTableSize);

	ReserveNameHeapForChunk(static_cast<u32>(&chunk - m_Chunks.GetElements()), chunk.objectNameTableSize);
}

bool audMetadataManager::BuildObjectMap_External(audMetadataChunk &chunk, fiStream *stream)
{
	// build object lookup
	
	u32 numObjects;
	
	const u32 chunkId = static_cast<u32>(&chunk - m_Chunks.GetElements());

	STREAM_READ(&numObjects, sizeof(numObjects));

	if(numObjects != 0)
	{
#if __BANK
		if(m_IsUsingRAVEData)
		{
			USE_DEBUG_MEMORY();
			chunk.objectSizes = rage_new u32[numObjects];
		}
#endif

		chunk.map = rage_new audMetadataChunk::audObjectMapEntry[numObjects];
		Assert(chunk.map);
	}

	u32 nameTableVirtOffset = FindFirstNameTableOffsetForChunk(chunkId);	

	for(u32 currentObjectIndex = 0; currentObjectIndex < numObjects; currentObjectIndex++)
	{
		STREAM_READ(&chunk.map[currentObjectIndex].key, sizeof(u32));
		STREAM_READ(&chunk.map[currentObjectIndex].offset, sizeof(u32));

		if(AreObjectNamesAvailable())
		{
			// each object stores an offset into the object name table
			// this offset must be translated via the virtual/reservation table
			// name table offset is 1 byte in and 24 bits wide
			Assert(chunk.map[currentObjectIndex].offset < chunk.dataSize);
			audMetadataObjectHeader *objectHeader = (audMetadataObjectHeader*)(chunk.data + chunk.map[currentObjectIndex].offset);
			objectHeader->nto += nameTableVirtOffset;
		}

		// skip over object size
		u32 objectSize;
		STREAM_READ(&objectSize, sizeof(objectSize));
#if RSG_BANK
		if(chunk.objectSizes)
		{
			chunk.objectSizes[currentObjectIndex] = objectSize;
		}
#endif

	}

	chunk.numObjects = numObjects;

	// We need the map to be setup in order to resolve references
	if(m_OptimisedMap)
	{
		ComputeMap(chunk);
	}

	// parse sound reference list and fix up hashes to offsets
	// NOTE: we only do this when not running -rave, since it makes metadata tweaking much
	// easier if we always deal with hashes

#if RSG_BANK
	const bool shouldFixupOffsets = !m_IsUsingRAVEData;
#else
	const bool shouldFixupOffsets = true;
#endif

	u32 numRefs;
	STREAM_READ(&numRefs, sizeof(numRefs));
	for(u32 i = 0 ; i < numRefs; i++)
	{
		u32 offset;
		STREAM_READ(&offset, sizeof(offset));
		if(shouldFixupOffsets)
		{
			// offset is relative to metadata start, and version id which we dont store as part of
			// m_Data - skip four byte header
			offset -= 8;
			u32 *ref = 	(u32*)(chunk.data + offset);
			const u32 hash = *ref;
			u32 chunkId;
			bool found = SearchForObjectFromHash(hash, chunkId, offset);
			if(!found)
			{
				if(PARAM_warnonbadrefs.Get())
				{
					audWarningf("Invalid audio object reference: %u", *ref);
				}
				*ref = ~0U;
			}
			else
			{
				Assert(chunkId <= 255);
				Assert(offset <= 0x1000000);
				*ref = ((chunkId&0xff) << 24) | (offset & 0xffffff);
			}

			if (m_BuildMetadataRefHashList)
			{
				AddMetadataRefHashEntry(chunk, audMetadataRef::Create(*ref), hash);				
			}
		}
	}		

	u32* hashes = Alloca(u32, chunk.numStrings);
	for(u32 i = 0 ; i < chunk.numStrings; i++)
	{
		hashes[i] = atStringHash(chunk.stringsBasePtr + chunk.stringOffsets[i]);
	}

	// bank name reference list and fix up hashes to indices
	STREAM_READ(&numRefs, sizeof(numRefs));
	
	const u32 firstStringId = FindFirstStringIdForChunk(chunkId);
	for(u32 i = 0 ; i < numRefs; i++)
	{
		u32 offset;
		STREAM_READ(&offset, sizeof(offset));
		if(shouldFixupOffsets)
		{
			// offset is relative to metadata start and version id, which we don't store as part of
			// chunk.data
			offset -= 8;
			u32 *ref = 	(u32*)(chunk.data + offset);
			// Check if we already have this string in chunk 0
			u32 existingId = ~0U;
			if(m_Chunks.GetCount() > 1 && (existingId = GetStringIdFromHash(*ref)) < m_Chunks[0].numStrings)
			{
				*ref = existingId;
			}
			else
			{
				// Workaround for the fact that banks may referenced in several DLC packs, causing us to have multiple BankIDs 
				// for the same bank - this can cause SimpleSounds to get confused when it comes to verifying that the appropriate 
				// bank is loaded (as although the data is technically loaded, we've may have loaded it using the original BankID rather 
				// than the DLC's version)
				existingId = GetStringIdFromHash(*ref);

				if(existingId != ~0u)
				{
					*ref = existingId;
				}
				else
				{
					*ref = firstStringId + GetStringTableIndexFromHashPrecomputed(*ref, hashes, chunk.numStrings);
				}
			}
		}
	}

	return true;
}

bool audMetadataManager::LoadExtraStringTable(const char *chunkName, fiStream *stream, const u32 size)
{
	audMetadataChunk &chunk = m_Chunks.Grow();
	BANK_ONLY(chunk.chunkName = StringDuplicate(chunkName));
	chunk.chunkNameHash = atStringHash(chunkName);
	chunk.metadataPath = NULL;
	return LoadStringTable(chunk, stream, size-4);
}

bool audMetadataManager::LoadStringTable(audMetadataChunk &chunk, fiStream *stream)
{
	s32 size;

	if(stream == NULL)
	{
		return false;
	}

	STREAM_READ(&size, sizeof(size));
	
	// size includes numStrings, which we read/store separately
	size -= 4;
	return LoadStringTable(chunk, stream, size);
}

bool audMetadataManager::LoadStringTable(audMetadataChunk &chunk, fiStream *stream, s32 size)
{
	chunk.stringTableData = rage_new char[size];

	STREAM_READ(&chunk.numStrings, sizeof(u32));
	STREAM_READ((char*)chunk.stringTableData, size);

	chunk.stringOffsets = (const u32*)chunk.stringTableData;
	// string heap begins immediately after the offset table
	chunk.stringsBasePtr = (const char*)(chunk.stringOffsets + chunk.numStrings);
	chunk.stringTableSize = size;

	ComputeStringTableMap();
	return true;
}

void audMetadataManager::ComputeStringTableMap()
{
	m_StringTableIdMap.Reset();

	for(u32 chunkId = 0; chunkId < static_cast<u32>(m_Chunks.GetCount()); chunkId++)
	{
		const audMetadataChunk &chunk = m_Chunks[chunkId];
		if(chunk.enabled && chunk.numStrings > 0)
		{
			const u32 firstId = ReserveStringsForChunk(chunkId, chunk.numStrings);
			for(u32 i = 0; i < chunk.numStrings; i++)
			{
				const u32 key = atStringHash(chunk.stringsBasePtr + chunk.stringOffsets[i]);
				if(!m_StringTableIdMap.Access(key))
				{
					m_StringTableIdMap.Insert(key, static_cast<u16>(firstId + i));
				}
			}
		}
	}
}

u32 audMetadataManager::ReserveStringsForChunk(const u32 chunkId, const u32 numStrings)
{
	u32 count = 0;
	for(s32 i = 0; i < m_StringTableReservations.GetCount(); i++)
	{
		if(m_StringTableReservations[i].chunkId == chunkId)
		{
			audAssertf(m_StringTableReservations[i].numStrings == numStrings, "Number of strings changed for ChunkId %u (%s) - was %u, now %u", chunkId, m_Chunks[chunkId].chunkName, m_StringTableReservations[i].numStrings, numStrings);
			return count;
		}
		count += m_StringTableReservations[i].numStrings;
	}

	// Record this reservation
	audMetadataManager::StringTableReservation &res = m_StringTableReservations.Grow();
	res.chunkId = chunkId;
	res.numStrings = numStrings;

	// Return the count up to this point, to be used as the starting ID for strings in this chunk
	return count;
}

u32 audMetadataManager::FindFirstStringIdForChunk(const u32 chunkId) const
{
	u32 count = 0;
	for(s32 i = 0; i < m_StringTableReservations.GetCount(); i++)
	{
		if(m_StringTableReservations[i].chunkId == chunkId)
		{
			return count;
		}
		count += m_StringTableReservations[i].numStrings;
	}
	// If this chunk doesn't yet exist in the reservation list, assume we're about to register it
	return count;
}

u32 audMetadataManager::ReserveNameHeapForChunk(const u32 chunkId, const u32 nameTableSize)
{
	u32 count = 0;
	for(s32 i = 0; i < m_NameTableReservations.GetCount(); i++)
	{
		if(m_NameTableReservations[i].chunkId == chunkId)
		{
			// Allow name heap to shrink but not grow from the initial reservation
			audAssertf(m_NameTableReservations[i].nameTableSize >= nameTableSize, "Size of name heap changed for ChunkId %u (%s) - was %u, now %u", chunkId, m_Chunks[chunkId].chunkName, m_NameTableReservations[i].nameTableSize, nameTableSize);
			return count;
		}
		count += m_NameTableReservations[i].nameTableSize;
	}

	// Record this reservation
	audMetadataManager::NameTableReservation &res = m_NameTableReservations.Grow();
	res.chunkId = chunkId;
	res.nameTableSize = nameTableSize;

	// Return the count up to this point, to be used as the virtual starting offset for names in this chunk
	return count;
}

u32 audMetadataManager::FindFirstNameTableOffsetForChunk(const u32 chunkId) const
{
	u32 count = 0;
	for(s32 i = 0; i < m_NameTableReservations.GetCount(); i++)
	{
		if(m_NameTableReservations[i].chunkId == chunkId)
		{
			return count;
		}
		count += m_NameTableReservations[i].nameTableSize;
	}
	// If this chunk doesn't yet exist in the reservation list, assume we're about to register it
	return count;
}

u32 audMetadataManager::GetStringIdFromName(const char *string) const
{	
	return GetStringIdFromHash(atStringHash(string));
}

u32 audMetadataManager::GetStringIdFromHash(const u32 hash) const
{
	SYS_CS_SYNC(m_CritSec);

	const u16 *ret = m_StringTableIdMap.Access(hash);
	if(ret)
	{
		return *ret;
	}
	return ~0U;
}

audMetadataRef audMetadataManager::GetObjectMetadataRefFromHash(const u32 hashValue) const
{
#if __BANK
	// when running -rave metadata refs are left as hashes
	if(m_IsUsingRAVEData)
	{
		u32 chunkId, offset;
		if(!SearchForObjectFromHash(hashValue, chunkId, offset) && !FindOverriddenObjectDataByHash(hashValue))
		{
			return audMetadataRef();
		}
		return audMetadataRef::Create(hashValue);
	}
#endif

	u32 chunkId, offset;
	if(!SearchForObjectFromHash(hashValue, chunkId, offset))
	{
		return audMetadataRef();
	}
	u32 refVal = (((chunkId&0xff)<<24) | (offset & 0xffffff));
	return audMetadataRef::Create(refVal);
}

void *audMetadataManager::GetObjectMetadataPtr(const char *objectName) const
{
	return GetObjectMetadataPtr(atStringHash(objectName));
}

void *audMetadataManager::GetObjectMetadataPtr(const audMetadataRef metadataRef) const
{
#if __BANK
	// when running with RAVE all metadata refs are left as hashes
	if(m_IsUsingRAVEData)
	{
		return GetObjectMetadataPtr(metadataRef.Get());
	}
#endif

	// firstly check for an invalid reference
	if(!metadataRef.IsValid())
	{
		return NULL;
	}

	// decompose into chunk and offset
	const u32 chunkId = metadataRef.Get()>>24;
	Assertf(chunkId < (u32)m_Chunks.GetCount(), "Invalid metadata reference %08X (chunk %u, only have %u)", metadataRef.Get(), chunkId, m_Chunks.GetCount());
	if(chunkId >= (u32)m_Chunks.GetCount())
	{
		return NULL;
	}
	const u32 offset = metadataRef.Get()&0xffffff;
	Assertf(offset < (u32)m_Chunks[chunkId].dataSize, "Invalid metadata reference %08X.  Offset %u, chunk size %u", metadataRef.Get(), offset, m_Chunks[chunkId].dataSize);
	if(offset >= (u32)m_Chunks[chunkId].dataSize)
	{
		return NULL;
	}
	return m_Chunks[chunkId].data + offset;
}

void *audMetadataManager::GetObjectMetadataPtrFromObjectIndex(const u32 index) const
{
	// loop over chunks
	u32 chunkId = 0;
	u32 relIndex = index;
	while( chunkId < (u32)m_Chunks.GetCount() )
	{
		if (relIndex < m_Chunks[chunkId].numObjects)
			return m_Chunks[chunkId].data + m_Chunks[chunkId].map[relIndex].offset;
		relIndex -= m_Chunks[chunkId].numObjects;
		chunkId++;
	}
	return NULL;
}

bool audMetadataManager::GetObjectInfo(const u32 objectHash, audMetadataObjectInfo &info) const
{
	const u8 *objectPtr = static_cast<u8*>(GetObjectMetadataPtr(objectHash));
	if(objectPtr)
	{
		info = audMetadataObjectInfo(objectPtr, this);
		return true;
	}
	return false;
}

bool audMetadataManager::GetObjectInfo(const audMetadataRef objectRef, audMetadataObjectInfo &info) const
{
	const u8 *objectPtr = static_cast<const u8*>(GetObjectMetadataPtr(objectRef));
	if(objectPtr)
	{
		info = audMetadataObjectInfo(objectPtr, this);
		return true;
	}
	return false;
}

bool audMetadataManager::GetObjectInfo(const u32 objectNameHash, const u32 chunkNameHash, audMetadataObjectInfo &info) const
{
	const u8 *objectPtr = static_cast<const u8*>(GetObjectMetadataPtr(objectNameHash, chunkNameHash));
	if(objectPtr)
	{
		info = audMetadataObjectInfo(objectPtr, this);
		return true;
	}
	return false;
}

bool audMetadataManager::GetObjectInfoFromSpecificChunk(const u32 objectNameHash, const u32 chunkId, audMetadataObjectInfo &info) const
{
	const u8 *objectPtr = static_cast<const u8*>(GetObjectMetadataPtrFromSpecificChunk(objectNameHash, chunkId));
	if(objectPtr)
	{
		info = audMetadataObjectInfo(objectPtr, this);
		return true;
	}
	return false;
}

bool audMetadataManager::GetObjectInfoFromObjectIndex(const u32 objectIndex, audMetadataObjectInfo &info) const
{
	const u8 *objectPtr = static_cast<const u8*>(GetObjectMetadataPtrFromObjectIndex(objectIndex));
	if(objectPtr)
	{
		info = audMetadataObjectInfo(objectPtr, this);
		return true;
	}
	return false;
}

void *audMetadataManager::GetObject_Verify(const u32 objectNameHash, const u32 typeId) const
{
	u8 *objectPtr = static_cast<u8*>(GetObjectMetadataPtr(objectNameHash));
	if(objectPtr)
	{
		
		if(typeId != *objectPtr)
		{
#if __ASSERT
			if(AreObjectNamesAvailable())
			{
				const audNameTableOffset *nto = reinterpret_cast<audNameTableOffset*>(objectPtr);
				const char *objectName = GetObjectNameFromNameTableOffset(nto->nto);
				audAssertf(typeId == *objectPtr, "Type mismatch; expected %u but found %u (name %s hash: %u)", typeId, *objectPtr, objectName, objectNameHash);
			}
			else
			{
				audAssertf(typeId == *objectPtr, "Type mismatch; expected %u but found %u (name hash: %u)", typeId, *objectPtr, objectNameHash);
			}
#endif
			objectPtr = NULL;
		}
	}
	return objectPtr;
}

void *audMetadataManager::GetObject_Verify(const audMetadataRef objectMetadataRef, const u32 typeId) const
{
	u8 *objectPtr = static_cast<u8*>(GetObjectMetadataPtr(objectMetadataRef));
	if(objectPtr)
	{		
		if(typeId != *objectPtr)
		{
#if __ASSERT
			if(AreObjectNamesAvailable())
			{
				const audNameTableOffset *nto = reinterpret_cast<audNameTableOffset*>(objectPtr);
				const char *objectName = GetObjectNameFromNameTableOffset(nto->nto);
				audAssertf(typeId == *objectPtr, "Type mismatch; expected %u but found %u (name %s metadataref: %u)", typeId, *objectPtr, objectName, objectMetadataRef.Get());
			}
			else
			{
				audAssertf(typeId == *objectPtr, "Type mismatch; expected %u but found %u (metadataref: %u)", typeId, *objectPtr, objectMetadataRef.Get());
			}
#endif
			objectPtr = NULL;
		}
	}
	return objectPtr;
}

void *audMetadataManager::GetObjectMetadataPtrFromSpecificChunk(const u32 hashValue, u32 chunkId) const
{
#if __BANK
	void *p = FindOverriddenObjectDataByHash(hashValue, chunkId);
	if(p != NULL)
	{
		return p;
	}
#endif

	u32 offset;
	if(!SearchForObjectFromHash(hashValue, m_Chunks[chunkId], offset))
	{
		return NULL;
	}
	Assert(offset < m_Chunks[chunkId].dataSize);
	return m_Chunks[chunkId].data + offset;
}

void *audMetadataManager::GetObjectMetadataPtrAndChunkId(const u32 hashValue, u32& chunkId) const
{
#if __BANK
	void *p = FindOverriddenObjectDataByHash(hashValue);
	if(p != NULL)
	{
		return p;
	}
#endif

	u32 offset;
	if(!SearchForObjectFromHash(hashValue, chunkId, offset))
	{
		return NULL;
	}
	Assert(offset < m_Chunks[chunkId].dataSize);
	return m_Chunks[chunkId].data + offset;
}

void *audMetadataManager::GetObjectMetadataPtr(const u32 hashValue) const
{
#if __BANK
	void *p = FindOverriddenObjectDataByHash(hashValue);
	if(p != NULL)
	{
		return p;
	}
#endif

	u32 chunkId,offset;
	if(!SearchForObjectFromHash(hashValue, chunkId, offset))
	{
		return NULL;
	}
	Assert(offset < m_Chunks[chunkId].dataSize);
	return m_Chunks[chunkId].data + offset;
}

void *audMetadataManager::GetObjectMetadataPtr(const u32 nameHash, const u32 chunkNameHash) const
{
	for(s32 i = 0; i < m_Chunks.GetCount(); i++)
	{
		if(m_Chunks[i].chunkNameHash == chunkNameHash && m_Chunks[i].enabled)
		{
			const audMetadataChunk &chunk = m_Chunks[i];
#if __BANK
			// Check overridden list first
			for(s32 j = 0; j < chunk.overriddenObjects.GetCount(); j++)
			{
				if(chunk.overriddenObjects[j].hash == nameHash)
				{
					return chunk.overriddenObjects[j].ptr;
				}
			}
#endif
			u32 offset = 0;
			if(!SearchForObjectFromHash(nameHash, chunk, offset))
			{
				return NULL;
			}
			Assert(offset < chunk.dataSize);
			return chunk.data + offset;
		}
	}
	return NULL;
}

void audMetadataManager::SortHashTable(audMetadataChunk &chunk)
{
	u32 gap = chunk.numObjects;
	u32 tempHash;
	u32 tempOffset;

	if(chunk.numObjects > 0)
	{
		for (;;) 
		{
			gap = (gap * 10) / 13;
			if (gap == 9 || gap == 10)
				gap = 11;
			if (gap < 1)
				gap = 1;

			bool swapped = false;
			for (u32 i = 0; i < chunk.numObjects - gap; i++) 
			{
				u32 j = i + gap;
				if (chunk.map[i].key > chunk.map[j].key) 
				{
					tempHash = chunk.map[j].key;
					tempOffset = chunk.map[j].offset;
					
					chunk.map[j].key = chunk.map[i].key;
					chunk.map[j].offset = chunk.map[i].offset;

					chunk.map[i].key = tempHash;
					chunk.map[i].offset = tempOffset;

#if __BANK
					if(m_IsUsingRAVEData)
					{
						tempHash = chunk.objectSizes[j];
						chunk.objectSizes[j] = chunk.objectSizes[i];
						chunk.objectSizes[i] = tempHash;
					}
#endif

					swapped = true;
				}
			}
			if(gap == 1 && !swapped)
			{
				break;
			}
		}
	}
}

u32 audMetadataManager::GetStringTableIndexFromMetadataRef(const u32 ref) const
{
#if __BANK
	// when running -rave, these references are left as hashes, so we need to convert
	// them here
	if(m_IsUsingRAVEData)
	{
		return GetStringIdFromHash(ref);
	}
#endif
	// otherwise this is a nop - we compute the index at load time
	return ref;
}

const char *audMetadataManager::GetStringFromTableIndex(const u32 globalStringId) const
{
	// Scan the string table reservations to find the chunk containing this string
	u32 chunkStringId = globalStringId;
	for(s32 i = 0; i < m_StringTableReservations.GetCount(); i++)
	{
		if(chunkStringId < m_StringTableReservations[i].numStrings)
		{
			const u32 chunkId = m_StringTableReservations[i].chunkId;
			if(m_Chunks[chunkId].enabled)
			{
				// Translate back to chunk-relative and return string ptr
				return m_Chunks[chunkId].stringsBasePtr + m_Chunks[chunkId].stringOffsets[chunkStringId];
			}
			else
			{
				return NULL;
			}
		}
		chunkStringId -= m_StringTableReservations[i].numStrings;
	}

	return NULL;
}

u32 audMetadataManager::GetStringTableIndexFromHashPrecomputed(const u32 hash, const u32* precomputedHashes, const u32 numPrecomputedHashes) const
{	
	for(u32 j = 0; j < numPrecomputedHashes; j++)
	{
		if(hash == precomputedHashes[j])
		{
			return j;
		}
	}
	Assert(0);
	return ~0U;
}

u32 audMetadataManager::ComputeNumberOfObjects() const
{
	u32 count = 0;
	for(atArray<audMetadataChunk>::const_iterator iter = m_Chunks.begin(); iter != m_Chunks.end(); iter++)
	{
		if((*iter).enabled)
		{
			count += (*iter).numObjects;
		}
	}
	return count;
}


const char *audMetadataManager::GetObjectNameFromNameTableOffset(const u32 nameTableOffset) const
{
	if(!AreObjectNamesAvailable())
	{
		return NULL;
	}

#if RSG_BANK
	if(nameTableOffset >= kNewNameStartId)
	{	
		const s32 newNameIndex = static_cast<s32>(nameTableOffset - kNewNameStartId);
		if(newNameIndex < m_NewObjectNameList.GetCount())
		{
			return m_NewObjectNameList[newNameIndex];
		}
		return "(invalid new-NTO)";
	}
#endif // RSG_BANK

	u32 chunkRelativeOffset = nameTableOffset;
	for(s32 i = 0; i < m_NameTableReservations.GetCount(); i++)
	{
		if(m_NameTableReservations[i].nameTableSize > chunkRelativeOffset)
		{
			const audMetadataChunk &chunk = m_Chunks[m_NameTableReservations[i].chunkId];
			if(!chunk.objectNameTable)
			{
				return "(unknown object name)";
			}
			return m_Chunks[m_NameTableReservations[i].chunkId].objectNameTable + chunkRelativeOffset;
		}
		chunkRelativeOffset -= m_NameTableReservations[i].nameTableSize;
	}
	return "(Invalid NTO)";
}

#if __BANK

const char *audMetadataManager::GetObjectName(const audMetadataRef metadataRef) const
{
	audMetadataObjectInfo info;
	if(GetObjectInfo(metadataRef, info))
	{
		return info.GetName();
	}
	return NULL;
}

const char *audMetadataManager::GetObjectName(const u32 nameHash) const
{
	audMetadataObjectInfo info;
	if(GetObjectInfo(nameHash, info))
	{
		return info.GetName();
	}
	return NULL;
}
#endif // __BANK

const char *audMetadataManager::GetNameFromNTO_Always(const u32 nameTableOffset) const
{
	Assert(AreObjectNamesAlwaysAvailable());
	return GetObjectNameFromNameTableOffset(nameTableOffset);
}

#if !__FINAL || __FINAL_LOGGING
const char *audMetadataManager::GetNameFromNTO_Debug(const u32 nameTableOffset) const
{
	return GetObjectNameFromNameTableOffset(nameTableOffset);	
}
#endif

const char *audMetadataManager::GetObjectName(const u32 nto, const u32 UNUSED_PARAM(chunkId)) const
{
	return GetObjectNameFromNameTableOffset(nto);
}

u32 audMetadataManager::SearchForObjectIndexFromHash(const u32 hash, const audMetadataChunk &chunk) const
{
	if(chunk.numObjects > 0 && chunk.enabled)
	{
		// binary search this chunk
		s32 start = 0;
		s32 middle;
		s32 end = static_cast<s32>(chunk.numObjects) - 1;
		s32 offset = 0;

		if(m_OptimisedMap)
		{
			const u32 bin = hash&0xFF;
			const audMetadataChunk::MapBin &mapBin = (*chunk.mapBins)[bin];
			end = static_cast<s32>(mapBin.numEntries) - 1;
			offset = static_cast<s32>(mapBin.startIndex);
		}

		//Binary search for matching object name hash
		while(start <= end)
		{
			middle = (start + end) >> 1;	//Compute mid-point.
			if(hash > chunk.map[middle + offset].key)
			{
				start = middle + 1;		//Repeat search in top half.
			}
			else if(hash < chunk.map[middle + offset].key)
			{
				end = middle - 1;		//Repeat search in bottom half.
			}
			else
			{
				//Found it!
				return middle + offset;
			}
		}
	}
	return ~0U;
}

bool audMetadataManager::SearchForObjectFromHash(const u32 hash, const audMetadataChunk &chunk, u32 &offset) const
{
	const u32 objectIndex = SearchForObjectIndexFromHash(hash, chunk);
	if(objectIndex != ~0U)
	{
		offset = chunk.map[objectIndex].offset;
		return true;
	}
	return false;
}

bool audMetadataManager::SearchForObjectFromHash(const u32 hash, u32 &chunkId, u32 &offset) const
{
	chunkId = offset = ~0U;

	// early-out check for null hash
	if(hash != 0)
	{
		SYS_CS_SYNC(m_CritSec);
		// search backwards so that we find newly loaded objects first
		for(s32 i = m_Chunks.GetCount()-1; i >= 0; i--)
		{
			if(SearchForObjectFromHash(hash, m_Chunks[i], offset))
			{
				chunkId = i;
				return true;
			}
		}
	}
	return false;
}

u32 audMetadataManager::GetNumStringsInChunkTable(const s32 chunkId) const
{
	Assert(chunkId < m_Chunks.GetCount());
	return m_Chunks[chunkId].numStrings;
}

s32 audMetadataManager::FindChunkId(const char *chunkName) const
{
	s32 iId = FindChunkId(atStringHash(chunkName));
	Assertf(iId != -1, "Failed to find chunk %s", chunkName);
	return iId;
}

s32 audMetadataManager::FindChunkId(const u32 chunkNameHash) const
{
	SYS_CS_SYNC(m_CritSec);
	for(s32 i = 0; i < m_Chunks.GetCount(); i++)
	{
		if(m_Chunks[i].chunkNameHash == chunkNameHash)
		{
			return i;
		}
	}
	Assertf(chunkNameHash != 0, "Failed to find chunk %u", chunkNameHash);
	return -1;
}

s32 audMetadataManager::FindChunkIdForObjectPtr(const void *objectPtr) const
{
	SYS_CS_SYNC(m_CritSec);
	
	for(s32 i = 0; i < m_Chunks.GetCount(); i++)
	{
		if(size_t(m_Chunks[i].data) <= size_t(objectPtr) && size_t(m_Chunks[i].data + m_Chunks[i].dataSize) > size_t(objectPtr))
		{
			return i;
		}
	}
	Assertf(false, "Failed to find chunk containing %p", objectPtr);
	return -1;
}

const char *audMetadataManager::GetStringFromChunkTableFromIndex(const s32 chunkId, const u32 index) const
{
	Assert(chunkId >= 0 && chunkId < m_Chunks.GetCount());
	Assert(m_Chunks[chunkId].numStrings>index);
	return m_Chunks[chunkId].stringsBasePtr + m_Chunks[chunkId].stringOffsets[index];
}

u32 audMetadataManager::GetLoadedDataSize() const
{
	SYS_CS_SYNC(m_CritSec);
	size_t size = 0;
	for(s32 i = 0; i < m_Chunks.GetCount(); i++)
	{
		if(m_Chunks[i].enabled)
		{
			size += m_Chunks[i].dataSize;
			size += m_Chunks[i].numObjects * sizeof(audMetadataChunk::audObjectMapEntry);
			size += m_Chunks[i].stringTableSize;
		}
	}
	return static_cast<u32>(size);
}

u32 audMetadataManager::GetLoadedNameTableSize() const
{
	SYS_CS_SYNC(m_CritSec);
	u32 size = 0;
	for(s32 i = 0; i < m_Chunks.GetCount(); i++)
	{
		if(m_Chunks[i].enabled)
		{
			size += m_Chunks[i].objectNameTableSize;
		}
	}
	return size;
}

u32 audMetadataManager::GetAssetChangelist(const s32 requestedChunkId /* = 0 */) const
{
	s32 chunkId = requestedChunkId;
	SYS_CS_SYNC(m_CritSec);
	// If -1 is requested, find the most recently loaded chunk with data
	if(chunkId == -1)
	{
		for(chunkId = m_Chunks.GetCount() - 1; chunkId >= 0; chunkId--)
		{
			if(m_Chunks[chunkId].enabled && m_Chunks[chunkId].data)
			{
				break;
			}
		}
	}
	return *((u32*)m_Chunks[chunkId].data);
}

#if __BANK
u32 audMetadataManager::GetObjectSize(const char *objectName, const u32 chunkNameHash) const
{
	const s32 chunkId = FindChunkId(chunkNameHash);
	if(chunkId == -1)
	{
		// We don't have this chunk loaded; ignore the edit
		return 0;
	}
	const u32 objectNameHash = atStringHash(objectName);

	const audMetadataChunk &chunk = m_Chunks[chunkId];
	for(s32 i = 0; i < chunk.overriddenObjects.GetCount(); i++)
	{
		if(chunk.overriddenObjects[i].hash == objectNameHash)
		{
			return chunk.overriddenObjects[i].actualSize;
		}
	}

	if(chunk.objectSizes == NULL)
	{
		return 0;
	}

	const u32 objectIndex = SearchForObjectIndexFromHash(objectNameHash, chunk);
	if(objectIndex == ~0U)
	{
		return 0;
	}
	return chunk.objectSizes[objectIndex];
}

bool audMetadataManager::OnObjectEdit(const char *objectName, const u32 chunkNameHash, const u32 objectSize, u8 *objectData)
{
	// Ignore 0-sized objects
	if(objectSize == 0)
	{
		return false;
	}
	const u32 objectNameHash = atStringHash(objectName);

	const s32 chunkId = FindChunkId(chunkNameHash);
	if(chunkId == -1)
	{
		// We don't have this chunk loaded; ignore the edit
		return false;
	}

	audMetadataChunk &chunk = m_Chunks[chunkId];

	audMetadataObjectHeader *newHeader = (audMetadataObjectHeader*)objectData;
	if(ShouldIgnoreEditForObjectTypeId(newHeader->classId))
	{
		return false;
	}

	bool needOverride = false;
	for(s32 i = 0; i < chunk.overriddenObjects.GetCount(); i++)
	{
		if(chunk.overriddenObjects[i].hash == objectNameHash)
		{
			if(objectSize <= chunk.overriddenObjects[i].size)
			{
				audMetadataObjectHeader *originalHeader = (audMetadataObjectHeader*)chunk.overriddenObjects[i].ptr;
				
				newHeader->nto = originalHeader->nto;
				sysMemCpy(chunk.overriddenObjects[i].ptr, objectData, objectSize);
				chunk.overriddenObjects[i].actualSize = objectSize;
				OnObjectModifiedCallback(objectNameHash);
				return true;
			}
			else
			{
				needOverride = true;
			}
		}
	}

	const u32 objectIndex = SearchForObjectIndexFromHash(objectNameHash, chunk);
	if(objectIndex == ~0U || chunk.objectSizes[objectIndex] < objectSize)
	{
		needOverride = true;
	}

	if(needOverride)
	{
		AddObjectOverride(objectName, objectNameHash, chunkNameHash, objectData, objectSize);
		OnObjectOveriddenCallback(objectNameHash);
	}
	else
	{
		// overwriting in-place
		audMetadataObjectHeader *originalHeader = (audMetadataObjectHeader*)(chunk.data + chunk.map[objectIndex].offset);
		newHeader = (audMetadataObjectHeader*)objectData;

		newHeader->nto = originalHeader->nto;
		
		sysMemCpy(chunk.data + chunk.map[objectIndex].offset, objectData, objectSize);

		OnObjectModifiedCallback(objectNameHash);
	}

	return true;
}

void audMetadataManager::AddObjectOverride(const char *objectName, const u32 objectNameHash, const u32 chunkNameHash, void *ptr, const u32 size)
{
	USE_DEBUG_MEMORY();

	const s32 chunkId = FindChunkId(chunkNameHash);
	audMetadataChunk &chunk = m_Chunks[chunkId];
	// we need to rewrite the name table offset, since RAVE will supply an invalid one
	const audMetadataObjectHeader *originalHeader = (audMetadataObjectHeader*)GetObjectMetadataPtr(objectNameHash, chunkNameHash);
	audMetadataObjectHeader *newHeader = (audMetadataObjectHeader*)ptr;
	if(originalHeader)
	{		
		newHeader->nto = originalHeader->nto;
	}
	else
	{
		// This is a new object
		m_NewObjectNameList.Grow() = StringDuplicate(objectName);
		newHeader->nto = kNewNameStartId + m_NewObjectNameList.GetCount() - 1;
	}
	
	// in order to keep this thread safe we'll stick it onto the end of the
	// list, then search and see if we need to replace anything
	// it's entirely likely that other threads will be searching the overridden
	// object list during this operation, however nothing else will be writing to it.
	u8 *buf = rage_new u8[size];
	sysMemCpy(buf, ptr, size);

	chunk.overriddenObjects.Grow() = audObjectDataOverride(buf, objectNameHash, size);
	for(s32 i = 0 ; i < chunk.overriddenObjects.GetCount(); i++)
	{
		if(chunk.overriddenObjects[i].ptr != buf && chunk.overriddenObjects[i].hash == objectNameHash)
		{
			// we're overriding something that's already been overridden ...
			// we can't delete this memory since objects could still be referencing it
			// just invalidate the hash
			chunk.overriddenObjects[i].hash = 0xffffffff;
		}
	}
}

void *audMetadataManager::FindOverriddenObjectDataByHash(const u32 hash) const
{
	// ~0U is used to mark an invalid overridden object slot, so ensure any requests for that hash are ignored
	if(hash != ~0U)
	{
		SYS_CS_SYNC(m_CritSec);
		// search backwards so that we find newly loaded objects first
		for(s32 chunkId = m_Chunks.GetCount()-1; chunkId >= 0; chunkId--)
		{
			const audMetadataChunk &chunk = m_Chunks[chunkId];
			if(chunk.enabled)
			{
				for(s32 j = 0; j < chunk.overriddenObjects.GetCount(); j++)
				{
					if(chunk.overriddenObjects[j].hash == hash)
					{
						return chunk.overriddenObjects[j].ptr;
					}
				}
			}
		}
	}

	return NULL;
}

void *audMetadataManager::FindOverriddenObjectDataByHash(const u32 hash, const u32 chunkId) const
{
	// ~0U is used to mark an invalid overridden object slot, so ensure any requests for that hash are ignored
	if(hash != ~0U)
	{
		SYS_CS_SYNC(m_CritSec);
		
		const audMetadataChunk &chunk = m_Chunks[chunkId];
		for(s32 j = 0; j < chunk.overriddenObjects.GetCount(); j++)
		{
			if(chunk.overriddenObjects[j].hash == hash)
			{
				return chunk.overriddenObjects[j].ptr;
			}
		}
	}

	return NULL;
}

void audMetadataManager::DrawOverriddenObjects() const
{
	PUSH_DEFAULT_SCREEN();
	char buf[128];
	grcColor3f(0.9f,0.9f,0.9f);
	u32 lineCount = 0;
	SYS_CS_SYNC(m_CritSec);
	for(s32 i = m_Chunks.GetCount()-1; i >= 0; i--)
	{
		formatf(buf, "Chunk %u has %u overridden objects", i, m_Chunks[i].overriddenObjects.GetCount());
		grcDraw2dText(100.f, 300.f + lineCount * 15.f, buf);
		lineCount++;

		for(s32 j = 0; j < m_Chunks[i].overriddenObjects.GetCount(); j++)
		{
			if(m_Chunks[i].overriddenObjects[j].hash != ~0U)
			{
				formatf(buf, "%u: %s (%u), %p, %u", i, GetObjectName(m_Chunks[i].overriddenObjects[j].hash), m_Chunks[i].overriddenObjects[j].hash, m_Chunks[i].overriddenObjects[j].ptr, m_Chunks[i].overriddenObjects[j].size);
			}
			else
			{
				formatf(buf, "%u: %u, %p, %u", i, m_Chunks[i].overriddenObjects[j].hash, m_Chunks[i].overriddenObjects[j].ptr, m_Chunks[i].overriddenObjects[j].size);
			}
			grcDraw2dText(130.f, 300.f + lineCount * 15.f, buf);
			lineCount++;
		}
	}
	POP_DEFAULT_SCREEN();
}
#if 0
void audMetadataManager::DrawChunks() const
{
	PUSH_DEFAULT_SCREEN();
	char buf[128];
	grcColor3f(0.9f,0.9f,0.9f);
	u32 lineCount = 0;
	SYS_CS_SYNC(m_CritSec);
	for(s32 i = 0; i < m_Chunks.GetCount(); i++)
	{		
		if(m_Chunks[i].enabled)
		{
			formatf(buf, "%u: Chunk %u %s - %u objects", i, m_Chunks[i].chunkNameHash, m_Chunks[i].metadataPath ? m_Chunks[i].metadataPath : "", m_Chunks[i].numObjects);
		}
		else
		{
			formatf(buf, "%u: Chunk %u %s - disabled", i, m_Chunks[i].chunkNameHash, m_Chunks[i].metadataPath ? m_Chunks[i].metadataPath : "");
		}
		grcDraw2dText(130.f, 300.f + lineCount * 15.f, buf);
		lineCount++;
	}
	POP_DEFAULT_SCREEN();
}
#endif
void audMetadataManager::DisableEditsForObjectType(const u32 typeId)
{
	if(m_IgnoreEditTypeIds.Find((u8)typeId) == -1)
	{
		Assign(m_IgnoreEditTypeIds.Grow(), typeId);
	}
}

bool audMetadataManager::ShouldIgnoreEditForObjectTypeId(const u32 typeId) const
{
	return (m_IgnoreEditTypeIds.Find((u8)typeId) != -1);
}
#endif // __BANK

} // namespace rage
