//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
// vmf_tweak.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "chunkfile.h"
#include "utllinkedlist.h"
#include <stdlib.h>
#include "tier1/strtools.h"
#include "tier0/icommandline.h"
#include "tier1/utlrbtree.h"
#include "tier1/utlbuffer.h"
#include "tier1/KeyValues.h"
#include "filesystem.h"
#include "FileSystem_Tools.h"
#include "cmdlib.h"

#include <windows.h>

char const *g_pInFilename = NULL;

char *CopyString( char const *pStr );

unsigned long g_CurLoadOrder = 0;


//-----------------------------------------------------------------------------
// default spec function
//-----------------------------------------------------------------------------
SpewRetval_t VMFTweakSpewFunc( SpewType_t spewType, char const *pMsg )
{
	OutputDebugString( pMsg );
	printf( pMsg );
	switch( spewType )
	{
	case SPEW_MESSAGE:
	case SPEW_WARNING:
	case SPEW_LOG:
		return SPEW_CONTINUE;

	case SPEW_ASSERT:
	case SPEW_ERROR:
	default:
		return SPEW_DEBUGGER;
	}
}

void logprint( char const *logfile, const char *fmt, ... )
{
	char string[ 8192 ];
	va_list va;
	va_start( va, fmt );
	vsprintf( string, fmt, va );
	va_end( va );

	FILE *fp = NULL;
	static bool first = false;
	if ( first )
	{
		first = false;
		fp = fopen( logfile, "wb" );
	}
	else
	{
		fp = fopen( logfile, "ab" );
	}
	if ( fp )
	{
		char *p = string;
		while ( *p )
		{
			if ( *p == '\n' )
			{
				fputc( '\r', fp );
			}
			fputc( *p, fp );
			p++;
		}
		fclose( fp );
	}
}


class CChunkKeyBase
{
public:
	unsigned long m_LoadOrder;	// Which order it appeared in the file.
};


class CKeyValue : public CChunkKeyBase
{
public:
	void SetKey( const char *pStr )
	{
		delete [] m_pKey;
		m_pKey = CopyString( pStr );
	}

	void SetValue( const char *pStr )
	{
		delete [] m_pValue;
		m_pValue = CopyString( pStr );
	}
	

public:
	char	*m_pKey;
	char	*m_pValue;
};


class CChunk : public CChunkKeyBase
{
public:
	// Returns true if the chunk has the specified key and if its value
	// is equal to pValueStr (case-insensitive).
	bool			CompareKey( char const *pKeyName, char const *pValueStr );

	// Look for a key by name.
	CKeyValue*		FindKey( char const *pKeyName );
	CKeyValue*		FindKey( char const *pKeyName, const char *pValue );

	CChunk*			FindChunk( char const *pKeyName );

	// Find a key by name, and replace any occurences with the new name.
	void			RenameKey( const char *szOldName, const char *szNewName );

public:

	char										*m_pChunkName;
	CUtlLinkedList<CKeyValue*, unsigned short>	m_Keys;
	CUtlLinkedList<CChunk*, unsigned short>		m_Chunks;
};


CChunk* ParseChunk( char const *pChunkName, bool bOnlyOne );


CChunk		*g_pCurChunk = 0;
CChunkFile	*g_pChunkFile = 0;
int g_DotCounter = 0;



// --------------------------------------------------------------------------------- //
// CChunk implementation.
// --------------------------------------------------------------------------------- //

bool CChunk::CompareKey( char const *pKeyName, char const *pValueStr )
{
	CKeyValue *pKey = FindKey( pKeyName );

	if( pKey && stricmp( pKey->m_pValue, pValueStr ) == 0 )
		return true;
	else
		return false;
}	


CKeyValue* CChunk::FindKey( char const *pKeyName )
{
	for( unsigned short i=m_Keys.Head(); i != m_Keys.InvalidIndex(); i=m_Keys.Next(i) )
	{
		CKeyValue *pKey = m_Keys[i];

		if( stricmp( pKey->m_pKey, pKeyName ) == 0 )
			return pKey;
	}

	return NULL;
}


CChunk* CChunk::FindChunk( char const *pChunkName )
{
	for( unsigned short i=m_Chunks.Head(); i != m_Chunks.InvalidIndex(); i=m_Chunks.Next(i) )
	{
		CChunk *pChunk = m_Chunks[i];

		if( stricmp( pChunk->m_pChunkName, pChunkName ) == 0 )
			return pChunk;
	}

	return NULL;
}

CKeyValue* CChunk::FindKey( char const *pKeyName, const char *pValue )
{
	for( unsigned short i=m_Keys.Head(); i != m_Keys.InvalidIndex(); i=m_Keys.Next(i) )
	{
		CKeyValue *pKey = m_Keys[i];

		if( stricmp( pKey->m_pKey, pKeyName ) == 0 && stricmp( pKey->m_pValue, pValue ) == 0 )
			return pKey;
	}

	return NULL;
}


void CChunk::RenameKey( const char *szOldName, const char *szNewName )
{
	if ((!szOldName) || (!szNewName))
		return;

	CKeyValue *pKey = FindKey( szOldName );
	if ( pKey )
	{
		delete pKey->m_pKey;
		pKey->m_pKey = CopyString( szNewName );
	}
}


// --------------------------------------------------------------------------------- //
// Util functions.
// --------------------------------------------------------------------------------- //
char *CopyString( char const *pStr )
{
	char *pRet = new char[ strlen(pStr) + 1 ];
	strcpy( pRet, pStr );
	return pRet;
}

ChunkFileResult_t MyDefaultHandler( CChunkFile *pFile, void *pData, char const *pChunkName )
{
	CChunk *pChunk = ParseChunk( pChunkName, true );
	g_pCurChunk->m_Chunks.AddToTail( pChunk );
	return ChunkFile_Ok;
}


ChunkFileResult_t MyKeyHandler( const char *szKey, const char *szValue, void *pData )
{
	// Add the key to the current chunk.
	CKeyValue *pKey = new CKeyValue;
	pKey->m_LoadOrder = g_CurLoadOrder++;
	
	pKey->m_pKey   = CopyString( szKey );
	pKey->m_pValue = CopyString( szValue );

	g_pCurChunk->m_Keys.AddToTail( pKey );
	return ChunkFile_Ok;
}


CChunk* ParseChunk( char const *pChunkName, bool bOnlyOne )
{
	// Add the new chunk.
	CChunk *pChunk = new CChunk;
	pChunk->m_pChunkName = CopyString( pChunkName );
	pChunk->m_LoadOrder = g_CurLoadOrder++;

	// Parse it out..
	CChunk *pOldChunk = g_pCurChunk;
	g_pCurChunk = pChunk;

	//if( ++g_DotCounter % 16 == 0 )
	//	printf( "." );

	while( 1 )
	{
		if( g_pChunkFile->ReadChunk( MyKeyHandler ) != ChunkFile_Ok )
			break;
	
		if( bOnlyOne )
			break;
	}

	g_pCurChunk = pOldChunk;
	return pChunk;
}


CChunk* ReadChunkFile( char const *pInFilename )
{
	CChunkFile chunkFile;

	if( chunkFile.Open( pInFilename, ChunkFile_Read ) != ChunkFile_Ok )
	{
		printf( "Error opening chunk file %s for reading.\n", pInFilename );
		return NULL;
	}

	printf( "Reading.." );
	chunkFile.SetDefaultChunkHandler( MyDefaultHandler, 0 );
	g_pChunkFile = &chunkFile;
	
	CChunk *pRet = ParseChunk( "***ROOT***", false );
	printf( "\n\n" );

	return pRet;
}

class CChunkHolder
{
public:
	static bool SortChunkFn( const CChunkHolder &a, const CChunkHolder &b )
	{
		return a.m_LoadOrder < b.m_LoadOrder;
	}

	unsigned long m_LoadOrder;
	CKeyValue *m_pKey;
	CChunk *m_pChunk;
};


void WriteChunks_R( CChunkFile *pFile, CChunk *pChunk, bool bRoot )
{
	if( !bRoot )
	{
		pFile->BeginChunk( pChunk->m_pChunkName );
	}

	// Sort them..
	CUtlRBTree<CChunkHolder,int> sortedStuff( 0, 0, &CChunkHolder::SortChunkFn );
	
	// Write keys.
	for( unsigned short i=pChunk->m_Keys.Head(); i != pChunk->m_Keys.InvalidIndex(); i = pChunk->m_Keys.Next( i ) )
	{
		CChunkHolder holder;
		holder.m_pKey = pChunk->m_Keys[i];
		holder.m_LoadOrder = holder.m_pKey->m_LoadOrder;
		holder.m_pChunk = NULL;
		sortedStuff.Insert( holder );
	}

	// Write subchunks.
	for( int i=pChunk->m_Chunks.Head(); i != pChunk->m_Chunks.InvalidIndex(); i = pChunk->m_Chunks.Next( i ) )
	{
		CChunkHolder holder;
		holder.m_pChunk = pChunk->m_Chunks[i];
		holder.m_LoadOrder = holder.m_pChunk->m_LoadOrder;
		holder.m_pKey = NULL;
		sortedStuff.Insert( holder );
	}

	// Write stuff in sorted order.
	int i = sortedStuff.FirstInorder();
	if ( i != sortedStuff.InvalidIndex() )
	{
		while ( 1 )
		{
			CChunkHolder &h = sortedStuff[i];
			if ( h.m_pKey )
			{
				pFile->WriteKeyValue( h.m_pKey->m_pKey, h.m_pKey->m_pValue );
			}
			else
			{
				WriteChunks_R( pFile, h.m_pChunk, false );
			}
			if ( i == sortedStuff.LastInorder() )
				break;
			i = sortedStuff.NextInorder( i );
		}
	}

	if( !bRoot )
	{
		pFile->EndChunk();
	}
}


bool WriteChunkFile( char const *pOutFilename, CChunk *pRoot )
{
	CChunkFile chunkFile;

	if( chunkFile.Open( pOutFilename, ChunkFile_Write ) != ChunkFile_Ok )
	{
		printf( "Error opening chunk file %s for writing.\n", pOutFilename );
		return false;
	}

	printf( "Writing.." );
	WriteChunks_R( &chunkFile, pRoot, true );
	printf( "\n\n" );

	return true;
}


//
//
// EXAMPLE
//
//
void ScanRopeSlack( CChunk *pChunk )
{
	if( stricmp( pChunk->m_pChunkName, "entity" ) == 0 )
	{
		if( pChunk->CompareKey( "classname", "keyframe_rope" ) ||
			pChunk->CompareKey( "classname", "move_rope" ) )
		{
			CKeyValue *pKey = pChunk->FindKey( "slack" );
			if( pKey )
			{
				// Subtract 100 from all the Slack properties.
				float flCur = (float)atof( pKey->m_pValue );
				char str[256];
				sprintf( str, "%f", flCur + 100 );
				pKey->m_pValue = CopyString( str );
			}
		}
	}
}

int g_nLogicAutoReplacementsMade = 0;
void LogicAuto( CChunk *pChunk )
{
	if ( pChunk->CompareKey( "classname", "logic_auto" ) )
	{
		CChunk *pConnections = pChunk->FindChunk( "connections" );
		if ( !pConnections )
			return;
		
		bool bFound = false;
		for ( int i=0; i < pConnections->m_Keys.Count(); i++ )
		{
			CKeyValue *pTestKV = pConnections->m_Keys[i];
			if ( V_stristr( pTestKV->m_pValue, "tonemap" ) == pTestKV->m_pValue )
			{
				bFound = true;
				break;
			}
		}
		
		if ( !bFound )
			return;
			
		++g_nLogicAutoReplacementsMade;			 

		// Ok, this is a logic_auto with OnMapSpawn outputs in its connections.		
		CUtlLinkedList<CKeyValue*,int> newKeys;
		FOR_EACH_LL( pConnections->m_Keys, i )
		{
			CKeyValue *pTestKV = pConnections->m_Keys[i];
			if ( V_stricmp( pTestKV->m_pKey, "OnMapSpawn" ) == 0 )
			{
				if ( V_stristr( pTestKV->m_pValue, "tonemap" ) == pTestKV->m_pValue )
				{
					CKeyValue *pNewKV = new CKeyValue;
					pNewKV->SetKey( "OnMapTransition" );
					pNewKV->SetValue( pTestKV->m_pValue );
					newKeys.AddToTail( pNewKV );
				}
			}
		}
		
		FOR_EACH_LL( newKeys, i )
		{
			if ( !pConnections->FindKey( "OnMapTransition", newKeys[i]->m_pValue ) )
			{
				pConnections->m_Keys.AddToTail( newKeys[i] );
			}
		}
		
		// Fix spawnflags.
		CKeyValue *pFlags = pChunk->FindKey("spawnflags");
		if ( pFlags )
		{
			unsigned long curVal;
			sscanf( pFlags->m_pValue, "%lu", &curVal );
			if ( curVal & 1 )
			{
				--curVal;
				char str[512];
				sprintf( str, "%lu", curVal );
				pFlags->SetValue( str );
			}
		}
	}
}


void ScanChunks( CChunk *pChunk, void (*fn)(CChunk *) )
{
	fn( pChunk );

	// Recurse into the children.
	for( unsigned short i=pChunk->m_Chunks.Head(); i != pChunk->m_Chunks.InvalidIndex(); i = pChunk->m_Chunks.Next( i ) )
	{
		ScanChunks( pChunk->m_Chunks[i], fn );
	}
}

int main(int argc, char* argv[])
{
	CommandLine()->CreateCmdLine( argc, argv );
	SpewOutputFunc( VMFTweakSpewFunc );

	if( argc < 2 )
	{
		printf( "vmf_tweak <input file> [output file]\n" );
		return 1;
	}

	g_pInFilename = argv[1];
	char const *pOutFilename = argc >= 3 ? argv[2] : "";

	CChunk *pRoot = ReadChunkFile( g_pInFilename );
	if( !pRoot )
		return 2;

	//
	//
	//
	// This is where you can put code to modify the VMF.
	//
	//
	//

	// If they didn't specify -game on the command line, use VPROJECT.
	char workingdir[ 256 ];
	workingdir[0] = 0;
	Q_getwd( workingdir, sizeof(workingdir) );
	CmdLib_InitFileSystem( workingdir );
	ScanChunks( pRoot, LogicAuto );
	FileSystem_Term();

	Msg( "%s: %d logic_auto replacements made.\n", g_pInFilename, g_nLogicAutoReplacementsMade );

	if ( argc >= 3 )
	{
		if( !WriteChunkFile( pOutFilename, pRoot ) )
			return 3;
	}

	return 0;
}