//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
#include "vbsp.h"
#include "UtlBuffer.h"
#include "UtlSymbol.h"
#include "UtlRBTree.h"
#include "KeyValues.h"
#include "bsplib.h"
#include "materialpatch.h"
#include "vstdlib/strtools.h"

// case insensitive
static CUtlSymbolTable s_SymbolTable( 0, 32, true );

struct NameTranslationLookup_t
{
	CUtlSymbol m_OriginalFileName;
	CUtlSymbol m_PatchFileName;
};

static bool NameTranslationLessFunc( NameTranslationLookup_t const& src1, 
							  NameTranslationLookup_t const& src2 )
{
	return src1.m_PatchFileName < src2.m_PatchFileName;
}

CUtlRBTree<NameTranslationLookup_t, int> s_MapPatchedMatToOriginalMat( 0, 256, NameTranslationLessFunc );

void AddNewTranslation( const char *pOriginalMaterialName, const char *pNewMaterialName )
{
	NameTranslationLookup_t newEntry;
	
	newEntry.m_OriginalFileName = s_SymbolTable.AddString( pOriginalMaterialName );
	newEntry.m_PatchFileName = s_SymbolTable.AddString( pNewMaterialName );

	s_MapPatchedMatToOriginalMat.Insert( newEntry );
}

const char *GetOriginalMaterialNameForPatchedMaterial( const char *pPatchMaterialName )
{
	const char *pRetName = NULL;
	int id;
	NameTranslationLookup_t lookup;
	lookup.m_PatchFileName = s_SymbolTable.AddString( pPatchMaterialName );
	do
	{
		id = s_MapPatchedMatToOriginalMat.Find( lookup );
		if( id >= 0 )
		{
			NameTranslationLookup_t &found = s_MapPatchedMatToOriginalMat[id];
			lookup.m_PatchFileName = found.m_OriginalFileName;
			pRetName = s_SymbolTable.String( found.m_OriginalFileName );
		}
	} while( id >= 0 );
	if( !pRetName )
	{
		// This isn't a patched material, so just return the original name.
		return pPatchMaterialName;
	}
	return pRetName;
}


void CreateMaterialPatchRecursive( KeyValues *pOriginalKeyValues, KeyValues *pPatchKeyValues, int nKeys, const MaterialPatchInfo_t *pInfo )
{
	int i;
	for( i = 0; i < nKeys; i++ )
	{
		const char *pVal = pOriginalKeyValues->GetString( pInfo[i].m_pKey, NULL );
		if( !pVal )
			continue;
		if( pInfo[i].m_pRequiredOriginalValue && Q_stricmp( pVal, pInfo[i].m_pRequiredOriginalValue ) != 0 )
			continue;
		pPatchKeyValues->SetString( pInfo[i].m_pKey, pInfo[i].m_pValue );
	}
	KeyValues *pScan;
	for( pScan = pOriginalKeyValues->GetFirstTrueSubKey(); pScan; pScan = pScan->GetNextTrueSubKey() )
	{
		CreateMaterialPatchRecursive( pScan, pPatchKeyValues->FindKey( pScan->GetName(), true ), nKeys, pInfo );
	}
}

//-----------------------------------------------------------------------------
// A version which allows you to patch multiple key values
//-----------------------------------------------------------------------------
void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName,
						 int nKeys, const MaterialPatchInfo_t *pInfo, MaterialPatchType_t nPatchType )
{	
	char pOldVMTFile[ 512 ];
	char pNewVMTFile[ 512 ];

	AddNewTranslation( pOriginalMaterialName, pNewMaterialName );
	
	Q_snprintf( pOldVMTFile, 512, "materials/%s.vmt", pOriginalMaterialName );
	Q_snprintf( pNewVMTFile, 512, "materials/%s.vmt", pNewMaterialName );

//	printf( "Creating material patch file %s which points at %s\n", newVMTFile, oldVMTFile );

	KeyValues *kv = new KeyValues( "patch" );
	if ( !kv )
	{
		Error( "Couldn't allocate KeyValues for %s!!!", pNewMaterialName );
	}

	kv->SetString( "include", pOldVMTFile );

	const char *pSectionName = (nPatchType == PATCH_INSERT) ? "insert" : "replace";
	KeyValues *section = kv->FindKey( pSectionName, true );

	if( nPatchType == PATCH_REPLACE )
	{
		char name[512];
		Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pOriginalMaterialName ) );
		KeyValues *origkv = new KeyValues( "blah" );

		if ( !origkv->LoadFromFile( g_pFileSystem, name ) )
		{
			origkv->deleteThis();
			Assert( 0 );
			return;
		}

		CreateMaterialPatchRecursive( origkv, section, nKeys, pInfo );
		origkv->deleteThis();
	}
	else
	{
		for ( int i = 0; i < nKeys; ++i )
		{
			section->SetString( pInfo[i].m_pKey, pInfo[i].m_pValue );
		}
	}
	
	// Write patched .vmt into a memory buffer
	CUtlBuffer buf( 0, 0, true );
	kv->RecursiveSaveToFile( buf, 0 );

	// Add to pak file for this .bsp
	AddBufferToPack( pNewVMTFile, (void*)buf.Base(), buf.TellPut(), true );

	// Cleanup
	kv->deleteThis();
}


//-----------------------------------------------------------------------------
// Patches a single keyvalue in a material 
//-----------------------------------------------------------------------------
void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName,
						 const char *pNewKey, const char *pNewValue, MaterialPatchType_t nPatchType )
{
	MaterialPatchInfo_t info;
	info.m_pKey = pNewKey;
	info.m_pValue = pNewValue;
	CreateMaterialPatch( pOriginalMaterialName, pNewMaterialName, 1, &info, nPatchType );
}


//-----------------------------------------------------------------------------
// Scan material + all subsections for key
//-----------------------------------------------------------------------------
static bool DoesMaterialHaveKey( KeyValues *pKeyValues, const char *pKeyName )
{
	const char *pVal;
	pVal = pKeyValues->GetString( pKeyName, NULL );
	if ( pVal != NULL  )
		return true;

	for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() )
	{
		if ( DoesMaterialHaveKey( pSubKey, pKeyName) )
			return true;
	}
	
	return false;
}

//-----------------------------------------------------------------------------
// Scan material + all subsections for key/value pair
//-----------------------------------------------------------------------------
static bool DoesMaterialHaveKeyValuePair( KeyValues *pKeyValues, const char *pKeyName, const char *pSearchValue )
{
	const char *pVal;
	pVal = pKeyValues->GetString( pKeyName, NULL );
	if ( pVal != NULL && ( Q_stricmp( pSearchValue, pVal ) == 0 ) )
		return true;

	for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() )
	{
		if ( DoesMaterialHaveKeyValuePair( pSubKey, pKeyName, pSearchValue ) )
			return true;
	}
	
	return false;
}

//-----------------------------------------------------------------------------
// Scan material + all subsections for key
//-----------------------------------------------------------------------------
bool DoesMaterialHaveKey( const char *pMaterialName, const char *pKeyName )
{
	char name[512];
	Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) );
	KeyValues *kv = new KeyValues( "blah" );

	if ( !kv->LoadFromFile( g_pFileSystem, name ) )
	{
		kv->deleteThis();
		return NULL;
	}

	bool retVal = DoesMaterialHaveKey( kv, pKeyName );

	kv->deleteThis();
	return retVal;
}

//-----------------------------------------------------------------------------
// Scan material + all subsections for key/value pair
//-----------------------------------------------------------------------------
bool DoesMaterialHaveKeyValuePair( const char *pMaterialName, const char *pKeyName, const char *pSearchValue )
{
	char name[512];
	Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) );
	KeyValues *kv = new KeyValues( "blah" );

	if ( !kv->LoadFromFile( g_pFileSystem, name ) )
	{
		kv->deleteThis();
		return NULL;
	}

	bool retVal = DoesMaterialHaveKeyValuePair( kv, pKeyName, pSearchValue );

	kv->deleteThis();
	return retVal;
}

//-----------------------------------------------------------------------------
// Gets a material value from a material. Ignores all patches
//-----------------------------------------------------------------------------
bool GetValueFromMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len )
{
	char name[512];
	Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) );
	KeyValues *kv = new KeyValues( "blah" );

	if ( !kv->LoadFromFile( g_pFileSystem, name ) )
	{
//		Assert( 0 );
		kv->deleteThis();
		return NULL;
	}

	const char *pTmpValue = kv->GetString( pKey, NULL );
	if( pTmpValue )
	{
		Q_strncpy( pValue, pTmpValue, len );
	}

	kv->deleteThis();
	return ( pTmpValue != NULL );
}


//-----------------------------------------------------------------------------
// Finds the original material associated with a patched material
//-----------------------------------------------------------------------------
MaterialSystemMaterial_t FindOriginalMaterial( const char *materialName, bool *pFound, bool bComplain )
{
	MaterialSystemMaterial_t matID;
	matID = FindMaterial( GetOriginalMaterialNameForPatchedMaterial( materialName ), pFound, bComplain );
	return matID;
}


//-----------------------------------------------------------------------------
// Load keyvalues from the local pack file, or from a file
//-----------------------------------------------------------------------------
bool LoadKeyValuesFromPackOrFile( const char *pFileName, KeyValues* pKeyValues )
{
	CUtlBuffer buf;
	if ( ReadFileFromPack( pFileName, true, buf ) )
	{
		return pKeyValues->LoadFromBuffer( pFileName, (char*)buf.Base() );
	}

	return pKeyValues->LoadFromFile( g_pFileSystem, pFileName );
}


//-----------------------------------------------------------------------------
// VMT parser
//-----------------------------------------------------------------------------
static void InsertKeyValues( KeyValues& dst, KeyValues& src, bool bCheckForExistence )
{
	KeyValues *pSrcVar = src.GetFirstSubKey();
	while( pSrcVar )
	{
		if ( !bCheckForExistence || dst.FindKey( pSrcVar->GetName() ) )
		{
			switch( pSrcVar->GetDataType() )
			{
			case KeyValues::TYPE_STRING:
				dst.SetString( pSrcVar->GetName(), pSrcVar->GetString() );
				break;
			case KeyValues::TYPE_INT:
				dst.SetInt( pSrcVar->GetName(), pSrcVar->GetInt() );
				break;
			case KeyValues::TYPE_FLOAT:
				dst.SetFloat( pSrcVar->GetName(), pSrcVar->GetFloat() );
				break;
			case KeyValues::TYPE_PTR:
				dst.SetPtr( pSrcVar->GetName(), pSrcVar->GetPtr() );
				break;
			}
		}
		pSrcVar = pSrcVar->GetNextKey();
	}
}

static void ExpandPatchFile( KeyValues& keyValues )
{
	int nCount = 0;
	while( nCount < 10 && stricmp( keyValues.GetName(), "patch" ) == 0 )
	{
//		WriteKeyValuesToFile( "patch.txt", keyValues );
		const char *pIncludeFileName = keyValues.GetString( "include" );
		if( !pIncludeFileName )
			return;

		KeyValues * includeKeyValues = new KeyValues( "vmt" );
		int nBufLen = Q_strlen( pIncludeFileName ) +  Q_strlen( "materials/.vmt" ) + 1;
		char *pFileName = ( char * )stackalloc( nBufLen );
		Q_strncpy( pFileName, pIncludeFileName, nBufLen );
		bool bSuccess = LoadKeyValuesFromPackOrFile( pFileName, includeKeyValues );
		if ( !bSuccess )
		{
			includeKeyValues->deleteThis();
			return;
		}

		KeyValues *pInsertSection = keyValues.FindKey( "insert" );
		if( pInsertSection )
		{
			InsertKeyValues( *includeKeyValues, *pInsertSection, false );
			keyValues = *includeKeyValues;
		}

		KeyValues *pReplaceSection = keyValues.FindKey( "replace" );
		if( pReplaceSection )
		{
			InsertKeyValues( *includeKeyValues, *pReplaceSection, true );
			keyValues = *includeKeyValues;
		}

		// Could add other commands here, like "delete", "rename", etc.

		includeKeyValues->deleteThis();
		nCount++;
	}

	if( nCount >= 10 )
	{
		Warning( "Infinite recursion in patch file?\n" );
	}
}


//-----------------------------------------------------------------------------
// Gets a keyvalue from a *patched* material
//-----------------------------------------------------------------------------
bool GetValueFromPatchedMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len )
{
	// Load the underlying file so that we can check if env_cubemap is in there.
	KeyValues *kv = new KeyValues( "blah" );

	char pFullMaterialName[512];
	Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName );
	if ( !LoadKeyValuesFromPackOrFile( pFullMaterialName, kv ) )
	{
//		Assert( 0 );
		kv->deleteThis();
		return NULL;
	}

	ExpandPatchFile( *kv );

	const char *pTmpValue = kv->GetString( pKey, NULL );
	if( pTmpValue )
	{
		Q_strncpy( pValue, pTmpValue, len );
	}

	kv->deleteThis();
	return ( pTmpValue != NULL );
}
