//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//

#include <stdio.h>
#include <io.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
#include <stdarg.h>

#include "tier0/platform.h"
#include "tier0/dbg.h"
#include "tier1/utlbuffer.h"

#include "cfgprocessor.h"

// Type conversions should be controlled by programmer explicitly - shadercompile makes use of 64-bit integer arithmetics
#pragma warning( error : 4244 )

namespace
{

	int Usage()
	{
		printf( "Usage:     expparser [OPTIONS] <input.txt 2>>output.txt\n" );
		printf( "Options:   [none]\n" );
		printf( "Input:     Sections in a file:\n" );
		printf( "           #BEGIN\n" );
		printf( "           #DEFINES:\n" );
		printf( "           FASTPATH=0..2\n" );
		printf( "           FOGTYPE=0..5\n" );
		printf( "           #SKIP:\n" );
		printf( "           ($FOGTYPE > 1) && (!$FASTPATH)\n" );
		printf( "           #COMMAND:\n" );
		printf( "           fxc.exe /DFLAGS=0x00\n" );
		printf( "           /Foshader.o myshader.fxc > output.txt\n" );
		printf( "           #END\n" );
		printf( "Version:   expparser compiled on " __DATE__ " @ " __TIME__ ".\n" );

		return -1;
	}
};

namespace
{

static bool s_bNoOutput = true;

void OutputF( FILE *f, char const *szFmt, ... )
{
	if( s_bNoOutput )
		return;

	va_list args;
	va_start( args, szFmt );
	vfprintf( f, szFmt, args );
	va_end( args );
}

};

//////////////////////////////////////////////////////////////////////////
//
// Utility classes:
//	QuickArray<T>
//	QuickStrIdx
//	QuickString
//	QuickStrUnique
//	QuickMap
//
//////////////////////////////////////////////////////////////////////////

#include <unordered_map>
#include <map>
#include <set>
#include <vector>
#include <string>
#include <algorithm>

template < typename T >
class QuickArray : private std::vector < T >
{
public:
	void Append( T const &e ) { push_back( e ); };
	int Size( void ) const { return ( int ) size(); };
	T const & Get( int idx ) const { return at( idx ); };
	T & GetForEdit( int idx ) { return at( idx ); }
	void Clear( void ) { clear(); }
	T const * ArrayBase() const { return empty() ? NULL : &at( 0 ); }
	T * ArrayBaseForEdit() { return empty() ? NULL : &at( 0 ); }
};

template < typename T >
class QuickStack : private std::vector < T >
{
public:
	void Push( T const &e ) { push_back( e ); };
	int Size( void ) const { return ( int ) size(); };
	T const & Top( void ) const { return at( Size() - 1 ); };
	void Pop( void ) { pop_back(); }
	void Clear( void ) { clear(); }
};

template < typename K, typename V >
class QuickMap : private std::map < K, V >
{
public:
	void Append( K const &k, V const &v ) { insert( value_type( k, v ) ); };
	int Size( void ) const { return ( int ) size(); };
	V const & GetLessOrEq( K &k, V const &v ) const;
	V const & Get( K const &k, V const &v ) const { const_iterator it = find( k ); return ( it != end() ? it->second : v ); };
	V & GetForEdit( K const &k, V &v ) { iterator it = find( k ); return ( it != end() ? it->second : v ); };
	void Clear( void ) { clear(); }
};

template < typename K, typename V >
V const & QuickMap< K, V >::GetLessOrEq( K &k, V const &v ) const
{
	const_iterator it = lower_bound( k );
	
	if ( end() == it )
	{
		if ( empty() )
			return v;
		-- it;
	}

	if ( k < it->first )
	{
		if ( begin() == it )
			return v;
		-- it;
	}

	k = it->first;
	return it->second;
}

class QuickStrIdx : private std::unordered_map < std::string, int >
{
public:
	void Append( char const *szName, int idx ) { insert( value_type( szName, idx ) ); };
	int Size( void ) const { return ( int ) size(); };
	int Get( char const *szName ) const { const_iterator it = find( szName ); if ( end() != it ) return it->second; else return -1; };
	void Clear( void ) { clear(); }
};

class QuickStrUnique : private std::set < std::string >
{
public:
	int Size( void ) const { return ( int ) size(); }
	bool Add( char const *szString ) { return insert( szString ).second; }
	void Remove( char const *szString ) { erase( szString ); }
	char const * Lookup( char const *szString ) { const_iterator it = find( szString ); if ( end() != it ) return it->data(); else return NULL; }
	char const * AddLookup( char const *szString ) { iterator it = insert( szString ).first; if ( end() != it ) return it->data(); else return NULL; }
	void Clear( void ) { clear(); }
};

class QuickString : private std::vector< char >
{
public:
	explicit QuickString( char const *szValue, size_t len = -1 );
	int Size() const { return ( int ) ( size() - 1 ); }
	char * Get() { return &at( 0 ); }
};

QuickString::QuickString( char const *szValue, size_t len )
{
	if ( size_t( -1 ) == len )
		len = ( size_t ) strlen( szValue );

	resize( len + 1, 0 );
	memcpy( Get(), szValue, len );
}


//////////////////////////////////////////////////////////////////////////
//
// Define class
//
//////////////////////////////////////////////////////////////////////////

class Define
{
public:
	explicit Define( char const *szName, int min, int max, bool bStatic ) : m_sName( szName ), m_min( min ), m_max( max ), m_bStatic( bStatic ) {}

public:
	char const * Name() const { return m_sName.data(); };
	int Min() const { return m_min; }; 
	int Max() const { return m_max; }; 
	bool IsStatic() const { return m_bStatic; }

protected:
	std::string m_sName;
	int m_min, m_max;
	bool m_bStatic;
};



//////////////////////////////////////////////////////////////////////////
//
// Expression parser
//
//////////////////////////////////////////////////////////////////////////

class IEvaluationContext
{
public:
	virtual int GetVariableValue( int nSlot ) = 0;
	virtual char const * GetVariableName( int nSlot ) = 0;
	virtual int GetVariableSlot( char const *szVariableName ) = 0;
};

class IExpression
{
public:
	virtual int  Evaluate( IEvaluationContext *pCtx ) const = 0;
	virtual void Print( IEvaluationContext *pCtx ) const = 0;
};

#define EVAL virtual int Evaluate( IEvaluationContext *pCtx ) const 
#define PRNT virtual void Print( IEvaluationContext *pCtx ) const

class CExprConstant : public IExpression
{
public:
	CExprConstant( int value ) : m_value( value ) {}
	EVAL { pCtx; return m_value; };
	PRNT { pCtx; OutputF( stdout, "%d", m_value ); }
public:
	int m_value;
};

class CExprVariable : public IExpression
{
public:
	CExprVariable( int nSlot ) : m_nSlot( nSlot ) {}
	EVAL { return (m_nSlot >= 0) ? pCtx->GetVariableValue( m_nSlot ) : 0; };
	PRNT { (m_nSlot >= 0) ? OutputF( stdout, "$%s", pCtx->GetVariableName( m_nSlot ) ) : OutputF( stdout, "$**@**" ); }
public:
	int m_nSlot;
};

class CExprUnary : public IExpression
{
public:
	CExprUnary( IExpression *x ) : m_x( x ) {}
public:
	IExpression *m_x;
};

#define BEGIN_EXPR_UNARY( className ) class className : public CExprUnary { public: className( IExpression *x ) : CExprUnary( x ) {}
#define END_EXPR_UNARY() };

BEGIN_EXPR_UNARY( CExprUnary_Negate )
	EVAL { return ! m_x->Evaluate( pCtx ); };
	PRNT { OutputF( stdout, "!" ); m_x->Print(pCtx); }
END_EXPR_UNARY()

class CExprBinary : public IExpression
{
public:
	CExprBinary( IExpression *x, IExpression *y ) : m_x( x ), m_y( y ) {}
	virtual int Priority() const = 0;
public:
	IExpression *m_x;
	IExpression *m_y;
};

#define BEGIN_EXPR_BINARY( className ) class className : public CExprBinary { public: className( IExpression *x, IExpression *y ) : CExprBinary( x, y ) {}
#define EXPR_BINARY_PRIORITY( nPriority ) virtual int Priority() const { return nPriority; };
#define END_EXPR_BINARY() };

BEGIN_EXPR_BINARY( CExprBinary_And )
	EVAL { return m_x->Evaluate( pCtx ) && m_y->Evaluate( pCtx ); }
	PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " && " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); }
	EXPR_BINARY_PRIORITY( 1 );
END_EXPR_BINARY()

BEGIN_EXPR_BINARY( CExprBinary_Or )
	EVAL { return m_x->Evaluate( pCtx ) || m_y->Evaluate( pCtx ); }
	PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " || " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); }
	EXPR_BINARY_PRIORITY( 2 );
END_EXPR_BINARY()

BEGIN_EXPR_BINARY( CExprBinary_Eq )
	EVAL { return m_x->Evaluate( pCtx ) == m_y->Evaluate( pCtx ); }
	PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " == " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); }
	EXPR_BINARY_PRIORITY( 0 );
END_EXPR_BINARY()

BEGIN_EXPR_BINARY( CExprBinary_Neq )
	EVAL { return m_x->Evaluate( pCtx ) != m_y->Evaluate( pCtx ); }
	PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " != " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); }
	EXPR_BINARY_PRIORITY( 0 );
END_EXPR_BINARY()

BEGIN_EXPR_BINARY( CExprBinary_G )
	EVAL { return m_x->Evaluate( pCtx ) > m_y->Evaluate( pCtx ); }
	PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " > " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); }
	EXPR_BINARY_PRIORITY( 0 );
END_EXPR_BINARY()

BEGIN_EXPR_BINARY( CExprBinary_Ge )
	EVAL { return m_x->Evaluate( pCtx ) >= m_y->Evaluate( pCtx ); }
	PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " >= " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); }
	EXPR_BINARY_PRIORITY( 0 );
END_EXPR_BINARY()

BEGIN_EXPR_BINARY( CExprBinary_L )
	EVAL { return m_x->Evaluate( pCtx ) < m_y->Evaluate( pCtx ); }
	PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " < " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); }
	EXPR_BINARY_PRIORITY( 0 );
END_EXPR_BINARY()

BEGIN_EXPR_BINARY( CExprBinary_Le )
	EVAL { return m_x->Evaluate( pCtx ) <= m_y->Evaluate( pCtx ); }
	PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " <= " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); }
	EXPR_BINARY_PRIORITY( 0 );
END_EXPR_BINARY()


class CComplexExpression : public IExpression
{
public:
	CComplexExpression( IEvaluationContext *pCtx ) : m_pRoot( NULL ), m_pContext( pCtx ) { }
	~CComplexExpression() { Clear(); }

	void Parse( char const *szExpression );
	void Clear( void );

public:
	EVAL { return m_pRoot ? m_pRoot->Evaluate( pCtx ? pCtx : m_pContext ) : 0; };
	PRNT { OutputF( stdout, "[ " ); m_pRoot ? m_pRoot->Print( pCtx ? pCtx : m_pContext ) : OutputF( stdout, "**NEXPR**" ); OutputF( stdout, " ]\n" ); }

protected:
	IExpression * ParseTopLevel( char *&szExpression );
	IExpression * ParseInternal( char *&szExpression );
	IExpression * Allocated( IExpression *pExpression );
	IExpression * AbortedParse( char *&szExpression ) const { *szExpression = 0; return m_pDefFalse; }

protected:
	QuickArray < IExpression * > m_arrAllExpressions;
	IExpression *m_pRoot;
	IEvaluationContext *m_pContext;

	IExpression *m_pDefTrue, *m_pDefFalse;
};

void CComplexExpression::Parse( char const *szExpression )
{
	Clear();

	m_pDefTrue = Allocated( new CExprConstant( 1 ) );
	m_pDefFalse = Allocated( new CExprConstant( 0 ) );

	m_pRoot = m_pDefFalse;

	if (szExpression)
	{
		QuickString qs( szExpression );
		char *szExpression = qs.Get(), *szExpectEnd = szExpression + qs.Size(), *szParse = szExpression;
		m_pRoot = ParseTopLevel( szParse );

		if ( szParse != szExpectEnd )
		{
			m_pRoot = m_pDefFalse;
		}
	}
}

IExpression * CComplexExpression::ParseTopLevel( char *&szExpression )
{
	QuickStack< CExprBinary * > exprStack;
	IExpression *pFirstToken = ParseInternal( szExpression );

	for ( ; ; )
	{
		// Skip whitespace
		while ( *szExpression && V_isspace( *szExpression ) )
		{
			++ szExpression;
		}

		// End of binary expression
		if ( !*szExpression || ( *szExpression == ')' ) )
		{
			break;
		}

		// Determine the binary expression type
		CExprBinary *pBinaryExpression = NULL;

		if ( 0 )
		{
			NULL;
		}
		else if ( !strncmp( szExpression, "&&", 2 ) )
		{
			pBinaryExpression = new CExprBinary_And( NULL, NULL );
			szExpression += 2;
		}
		else if ( !strncmp( szExpression, "||", 2 ) )
		{
			pBinaryExpression = new CExprBinary_Or( NULL, NULL );
			szExpression += 2;
		}
		else if ( !strncmp( szExpression, ">=", 2 ) )
		{
			pBinaryExpression = new CExprBinary_Ge( NULL, NULL );
			szExpression += 2;
		}
		else if ( !strncmp( szExpression, "<=", 2 ) )
		{
			pBinaryExpression = new CExprBinary_Le( NULL, NULL );
			szExpression += 2;
		}
		else if ( !strncmp( szExpression, "==", 2 ) )
		{
			pBinaryExpression = new CExprBinary_Eq( NULL, NULL );
			szExpression += 2;
		}
		else if ( !strncmp( szExpression, "!=", 2 ) )
		{
			pBinaryExpression = new CExprBinary_Neq( NULL, NULL );
			szExpression += 2;
		}
		else if ( *szExpression == '>' )
		{
			pBinaryExpression = new CExprBinary_G( NULL, NULL );
			++ szExpression;
		}
		else if ( *szExpression == '<' )
		{
			pBinaryExpression = new CExprBinary_L( NULL, NULL );
			++ szExpression;
		}
		else
		{
			return AbortedParse( szExpression );
		}

		Allocated( pBinaryExpression );
		pBinaryExpression->m_y = ParseInternal( szExpression );

		// Figure out the expression priority
		int nPriority = pBinaryExpression->Priority();
		IExpression *pLastExpr = pFirstToken;
		while ( exprStack.Size() )
		{
			CExprBinary *pStickTo = exprStack.Top();
			pLastExpr = pStickTo;

			if ( nPriority > pStickTo->Priority() )
				exprStack.Pop();
			else
				break;
		}

		if ( exprStack.Size() )
		{
			CExprBinary *pStickTo = exprStack.Top();
			pBinaryExpression->m_x = pStickTo->m_y;
			pStickTo->m_y = pBinaryExpression;
		}
		else
		{
			pBinaryExpression->m_x = pLastExpr;
		}

		exprStack.Push( pBinaryExpression );
	}

	// Tip-of-the-tree retrieval
	{
		IExpression *pLastExpr = pFirstToken;
		while ( exprStack.Size() )
		{
			pLastExpr = exprStack.Top();
			exprStack.Pop();
		}

		return pLastExpr;
	}
}

IExpression * CComplexExpression::ParseInternal( char *&szExpression )
{
	// Skip whitespace
	while ( *szExpression && V_isspace( *szExpression ) )
	{
		++ szExpression;
	}

	if ( !*szExpression )
		return AbortedParse( szExpression );

	if ( 0 )
	{
		NULL;
	}
	else if ( V_isdigit( *szExpression ) )
	{
		long lValue = strtol( szExpression, &szExpression, 10 );
		return Allocated( new CExprConstant( lValue ) );
	}
	else if ( !strncmp( szExpression, "defined", 7 ) )
	{
		szExpression += 7;
		IExpression *pNext = ParseInternal( szExpression );
		return Allocated( new CExprConstant( pNext->Evaluate( m_pContext ) ) );
	}
	else if ( *szExpression == '(' )
	{
		++ szExpression;
		IExpression *pBracketed = ParseTopLevel( szExpression );
		if ( ')' == *szExpression )
		{
			++ szExpression;
			return pBracketed;
		}
		else
		{
			return AbortedParse( szExpression );
		}
	}
	else if ( *szExpression == '$' )
	{
		size_t lenVariable = 0;
		for ( char *szEndVar = szExpression + 1; *szEndVar; ++ szEndVar, ++ lenVariable )
		{
			if ( !V_isalnum( *szEndVar ) )
			{
				switch ( *szEndVar )
				{
				case '_':
					break;
				default:
					goto parsed_variable_name;
				}
			}
		}

parsed_variable_name:
		int nSlot = m_pContext->GetVariableSlot( QuickString( szExpression + 1, lenVariable ).Get() );
		szExpression += lenVariable + 1;

		return Allocated( new CExprVariable( nSlot ) );
	}
	else if ( *szExpression == '!' )
	{
		++ szExpression;
		IExpression *pNext = ParseInternal( szExpression );
		return Allocated( new CExprUnary_Negate( pNext ) );
	}
	else
	{
		return AbortedParse( szExpression );
	}
}

IExpression * CComplexExpression::Allocated( IExpression *pExpression )
{
	m_arrAllExpressions.Append( pExpression );
	return pExpression;
}

void CComplexExpression::Clear( void )
{
	for ( int k = 0; k < m_arrAllExpressions.Size() ; ++ k )
	{
		delete m_arrAllExpressions.Get( k );
	}

	m_arrAllExpressions.Clear();
	m_pRoot = NULL;
}


#undef BEGIN_EXPR_UNARY
#undef BEGIN_EXPR_BINARY

#undef END_EXPR_UNARY
#undef END_EXPR_BINARY

#undef EVAL
#undef PRNT

//////////////////////////////////////////////////////////////////////////
//
// Combo Generator class
//
//////////////////////////////////////////////////////////////////////////

class ComboGenerator : public IEvaluationContext
{
public:
	void AddDefine( Define const &df );
	Define const * const GetDefinesBase( void ) { return m_arrDefines.ArrayBase(); }
	Define const * const GetDefinesEnd( void ) { return m_arrDefines.ArrayBase() + m_arrDefines.Size(); }

	uint64 NumCombos();
	uint64 NumCombos( bool bStaticCombos );
	void RunAllCombos( CComplexExpression const &skipExpr );

	// IEvaluationContext
public:
	virtual int GetVariableValue( int nSlot ) { return m_arrVarSlots.Get( nSlot ); };
	virtual char const * GetVariableName( int nSlot ) { return m_arrDefines.Get( nSlot ).Name(); };
	virtual int GetVariableSlot( char const *szVariableName ) { return m_mapDefines.Get( szVariableName ); };

protected:
	QuickArray< Define >	m_arrDefines;
	QuickStrIdx				m_mapDefines;
	QuickArray < int >		m_arrVarSlots;
};

void ComboGenerator::AddDefine( Define const &df )
{
	m_mapDefines.Append( df.Name(), m_arrDefines.Size() );
	m_arrDefines.Append( df );
	m_arrVarSlots.Append( 1 );
}

uint64 ComboGenerator::NumCombos()
{
	uint64 numCombos = 1;

	for ( int k = 0, kEnd = m_arrDefines.Size(); k < kEnd; ++ k )
	{
		Define const &df = m_arrDefines.Get( k );
		numCombos *= ( df.Max() - df.Min() + 1 );
	}

	return numCombos;
}

uint64 ComboGenerator::NumCombos( bool bStaticCombos )
{
	uint64 numCombos = 1;

	for ( int k = 0, kEnd = m_arrDefines.Size(); k < kEnd; ++ k )
	{
		Define const &df = m_arrDefines.Get( k );
		( df.IsStatic() == bStaticCombos ) ? numCombos *= ( df.Max() - df.Min() + 1 ) : 0;
	}

	return numCombos;
}


struct ComboEmission
{
	std::string m_sPrefix;
	std::string m_sSuffix;
} g_comboEmission;

size_t const g_lenTmpBuffer = 1 * 1024 * 1024; // 1Mb buffer for tmp storage
char g_chTmpBuffer[g_lenTmpBuffer];

void ComboGenerator::RunAllCombos( CComplexExpression const &skipExpr )
{
	// Combo numbers
	uint64 const nTotalCombos = NumCombos();

	// Get the pointers
	int * const pnValues = m_arrVarSlots.ArrayBaseForEdit();
	int * const pnValuesEnd = pnValues + m_arrVarSlots.Size();
	int *pSetValues;

	// Defines
	Define const * const pDefVars = m_arrDefines.ArrayBase();
	Define const *pSetDef;

	// Set all the variables to max values
	for ( pSetValues = pnValues, pSetDef = pDefVars;
		pSetValues < pnValuesEnd;
		++ pSetValues, ++ pSetDef )
	{
		*pSetValues = pSetDef->Max();
	}

	// Expressions distributed [0] = skips, [1] = evaluated
	uint64 nSkipEvalCounters[2] = { 0, 0 };

	// Go ahead and run the iterations
	{
		uint64 nCurrentCombo = nTotalCombos;

next_combo_iteration:
		-- nCurrentCombo;
		int const valExprSkip = skipExpr.Evaluate( this );

		++ nSkipEvalCounters[ !valExprSkip ];

		if ( valExprSkip )
		{
			// TECH NOTE: Giving performance hint to compiler to place a jump here
			// since there will be much more skips and actually less than 0.8% cases
			// will be "OnCombo" hits.
			NULL;
		}
		else
		{
			// ------- OnCombo( nCurrentCombo ); ----------
			OutputF( stderr, "%s ", g_comboEmission.m_sPrefix.data() );
			OutputF( stderr, "/DSHADERCOMBO=%d ", nCurrentCombo );

			for ( pSetValues = pnValues, pSetDef = pDefVars;
				pSetValues < pnValuesEnd;
				++ pSetValues, ++ pSetDef )
			{
				OutputF( stderr, "/D%s=%d ", pSetDef->Name(), *pSetValues );
			}

			OutputF( stderr, "%s\n", g_comboEmission.m_sSuffix.data() );
			// ------- end of OnCombo ---------------------
		}

		// Do a next iteration
		for ( pSetValues = pnValues, pSetDef = pDefVars;
			pSetValues < pnValuesEnd;
			++ pSetValues, ++ pSetDef )
		{
			if ( -- *pSetValues >= pSetDef->Min() )
				goto next_combo_iteration;

			*pSetValues = pSetDef->Max();
		}
	}

	OutputF( stdout, "Generated %d combos: %d evaluated, %d skipped.\n", nTotalCombos, nSkipEvalCounters[1], nSkipEvalCounters[0] );
}


namespace ConfigurationProcessing
{
	class CfgEntry
	{
	public:
		CfgEntry() : m_szName( "" ), m_szShaderSrc( "" ), m_pCg( NULL ), m_pExpr( NULL ) { memset( &m_eiInfo, 0, sizeof( m_eiInfo ) ); }
		static void Destroy( CfgEntry const &x ) { delete x.m_pCg; delete x.m_pExpr; }

	public:
		bool operator < ( CfgEntry const &x ) const { return m_pCg->NumCombos() < x.m_pCg->NumCombos(); }

	public:
		char const *m_szName;
		char const *m_szShaderSrc;
		ComboGenerator *m_pCg;
		CComplexExpression *m_pExpr;
		std::string m_sPrefix;
		std::string m_sSuffix;

		CfgProcessor::CfgEntryInfo m_eiInfo;
	};

	QuickStrUnique s_uniqueSections, s_strPool;
	std::multiset< CfgEntry > s_setEntries;

	class ComboHandleImpl : public IEvaluationContext
	{
	public:
		uint64 m_iTotalCommand;
		uint64 m_iComboNumber;
		uint64 m_numCombos;
		CfgEntry const *m_pEntry;

	public:
		ComboHandleImpl() : m_iTotalCommand( 0 ), m_iComboNumber( 0 ), m_numCombos( 0 ), m_pEntry( NULL ) {}

		// IEvaluationContext
	public:
		QuickArray < int >		m_arrVarSlots;
	public:
		virtual int GetVariableValue( int nSlot ) { return m_arrVarSlots.Get( nSlot ); };
		virtual char const * GetVariableName( int nSlot ) { return m_pEntry->m_pCg->GetVariableName( nSlot ); };
		virtual int GetVariableSlot( char const *szVariableName ) { return m_pEntry->m_pCg->GetVariableSlot( szVariableName ); };

		// External implementation
	public:
		bool Initialize( uint64 iTotalCommand, const CfgEntry *pEntry );
		bool AdvanceCommands( uint64 &riAdvanceMore );
		bool NextNotSkipped( uint64 iTotalCommand );
		bool IsSkipped( void ) { return ( m_pEntry->m_pExpr->Evaluate( this ) != 0 ); }
		void FormatCommand( char *pchBuffer );
	};

	QuickMap < uint64, ComboHandleImpl > s_mapComboCommands;

	bool ComboHandleImpl::Initialize( uint64 iTotalCommand, const CfgEntry *pEntry )
	{
		m_iTotalCommand = iTotalCommand;
		m_pEntry = pEntry;
		m_numCombos = m_pEntry->m_pCg->NumCombos();

		// Defines
		Define const * const pDefVars = m_pEntry->m_pCg->GetDefinesBase();
		Define const * const pDefVarsEnd = m_pEntry->m_pCg->GetDefinesEnd();
		Define const *pSetDef;

		// Set all the variables to max values
		for ( pSetDef = pDefVars;
			  pSetDef < pDefVarsEnd;
			  ++ pSetDef )
		{
			m_arrVarSlots.Append( pSetDef->Max() );
		}

		m_iComboNumber = m_numCombos - 1;
		return true;
	}

	bool ComboHandleImpl::AdvanceCommands( uint64 &riAdvanceMore )
	{
		if ( !riAdvanceMore )
			return true;

		// Get the pointers
		int * const pnValues = m_arrVarSlots.ArrayBaseForEdit();
		int * const pnValuesEnd = pnValues + m_arrVarSlots.Size();
		int *pSetValues;

		// Defines
		Define const * const pDefVars = m_pEntry->m_pCg->GetDefinesBase();
		Define const *pSetDef;

		if ( m_iComboNumber < riAdvanceMore )
		{
			riAdvanceMore -= m_iComboNumber;
			return false;
		}

		// Do the advance
		m_iTotalCommand += riAdvanceMore;
		m_iComboNumber -= riAdvanceMore;
		for ( pSetValues = pnValues, pSetDef = pDefVars;
		  	  ( pSetValues < pnValuesEnd ) && ( riAdvanceMore > 0 );
			  ++ pSetValues, ++ pSetDef )
		{
			riAdvanceMore += ( pSetDef->Max() - *pSetValues );
			*pSetValues = pSetDef->Max();
			
			int iInterval = ( pSetDef->Max() - pSetDef->Min() + 1 );
			*pSetValues -= int( riAdvanceMore % iInterval );
			riAdvanceMore /= iInterval;
		}

		return true;
	}

	bool ComboHandleImpl::NextNotSkipped( uint64 iTotalCommand )
	{
		// Get the pointers
		int * const pnValues = m_arrVarSlots.ArrayBaseForEdit();
		int * const pnValuesEnd = pnValues + m_arrVarSlots.Size();
		int *pSetValues;

		// Defines
		Define const * const pDefVars = m_pEntry->m_pCg->GetDefinesBase();
		Define const *pSetDef;

		// Go ahead and run the iterations
		{
next_combo_iteration:
			if ( m_iTotalCommand + 1 >= iTotalCommand ||
				 !m_iComboNumber )
				return false;

			-- m_iComboNumber;
			++ m_iTotalCommand;

			// Do a next iteration
			for ( pSetValues = pnValues, pSetDef = pDefVars;
				pSetValues < pnValuesEnd;
				++ pSetValues, ++ pSetDef )
			{
				if ( -- *pSetValues >= pSetDef->Min() )
					goto have_combo_iteration;

				*pSetValues = pSetDef->Max();
			}

			return false;

have_combo_iteration:
			if ( m_pEntry->m_pExpr->Evaluate( this ) )
				goto next_combo_iteration;
			else
				return true;
		}
	}

	void ComboHandleImpl::FormatCommand( char *pchBuffer )
	{
		// Get the pointers
		int * const pnValues = m_arrVarSlots.ArrayBaseForEdit();
		int * const pnValuesEnd = pnValues + m_arrVarSlots.Size();
		int *pSetValues;

		// Defines
		Define const * const pDefVars = m_pEntry->m_pCg->GetDefinesBase();
		Define const *pSetDef;
		
		{
			// ------- OnCombo( nCurrentCombo ); ----------
			sprintf( pchBuffer, "%s ", m_pEntry->m_sPrefix.data() );
			pchBuffer += strlen( pchBuffer );

			sprintf( pchBuffer, "/DSHADERCOMBO=%llu ", m_iComboNumber );
			pchBuffer += strlen( pchBuffer );

			for ( pSetValues = pnValues, pSetDef = pDefVars;
				  pSetValues < pnValuesEnd;
				  ++ pSetValues, ++ pSetDef )
			{
				sprintf( pchBuffer, "/D%s=%d ", pSetDef->Name(), *pSetValues );
				pchBuffer += strlen( pchBuffer );
			}

			sprintf( pchBuffer, "%s\n", m_pEntry->m_sSuffix.data() );
			pchBuffer += strlen( pchBuffer );
			// ------- end of OnCombo ---------------------
		}
	}

	struct CAutoDestroyEntries {
		~CAutoDestroyEntries( void ) {
			std::for_each( s_setEntries.begin(), s_setEntries.end(), CfgEntry::Destroy );
		}
	} s_autoDestroyEntries;

	
	FILE *& GetInputStream( FILE * )
	{
		static FILE *s_fInput = stdin;
		return s_fInput;
	}

	CUtlInplaceBuffer *& GetInputStream( CUtlInplaceBuffer * )
	{
		static CUtlInplaceBuffer *s_fInput = NULL;
		return s_fInput;
	}

	char * GetLinePtr_Private( void )
	{
		if ( CUtlInplaceBuffer *pUtlBuffer = GetInputStream( ( CUtlInplaceBuffer * ) NULL ) )
			return pUtlBuffer->InplaceGetLinePtr();

		if ( FILE *fInput = GetInputStream( ( FILE * ) NULL ) )
			return fgets( g_chTmpBuffer, g_lenTmpBuffer, fInput );

		return NULL;
	}

	bool LineEquals( char const *sz1, char const *sz2, int nLen )
	{
		return 0 == strncmp( sz1, sz2, nLen );
	}

	char * NextLine( void )
	{
		if ( char *szLine = GetLinePtr_Private() )
		{
			// Trim trailing whitespace as well
			size_t len = ( size_t ) strlen( szLine );
			while ( len -- > 0 && V_isspace( szLine[ len ] ) )
			{
				szLine[ len ] = 0;
			}
			return szLine;
		}
		return NULL;
	}

	char * WaitFor( char const *szWaitString, int nMatchLength )
	{
		while ( char *pchResult = NextLine() )
		{
			if ( LineEquals( pchResult, szWaitString, nMatchLength ) )
				return pchResult;
		}

		return NULL;
	}

	bool ProcessSection( CfgEntry &cfge )
	{
		bool bStaticDefines;

		// Read the next line for the section src file
		if ( char *szLine = NextLine() )
		{
			cfge.m_szShaderSrc = s_strPool.AddLookup( szLine );
		}

		if ( char *szLine = WaitFor( "#DEFINES-", 9 ) )
		{
			bStaticDefines = ( szLine[9] == 'S' );
		}
		else
			return false;

		// Combo generator
		ComboGenerator &cg = *( cfge.m_pCg = new ComboGenerator );
		CComplexExpression &exprSkip = *( cfge.m_pExpr = new CComplexExpression( &cg ) );

		// #DEFINES:
		while ( char *szLine = NextLine() )
		{
			if ( LineEquals( szLine, "#SKIP", 5 ) )
				break;

			// static defines
			if ( LineEquals( szLine, "#DEFINES-", 9 ) )
			{
				bStaticDefines = ( szLine[9] == 'S' );
				continue;
			}

			while ( *szLine && V_isspace(*szLine) )
			{
				++ szLine;
			}

			// Find the eq
			char *pchEq = strchr( szLine, '=' );
			if ( !pchEq )
				continue;

			char *pchStartRange = pchEq + 1;
			*pchEq = 0;
			while ( -- pchEq >= szLine &&
				V_isspace( *pchEq ) )
			{
				*pchEq = 0;
			}
			if ( !*szLine )
				continue;

			// Find the end of range
			char *pchEndRange = strstr( pchStartRange, ".." );
			if ( !pchEndRange )
				continue;
			pchEndRange += 2;

			// Create the define
			Define df( szLine, atoi( pchStartRange ), atoi( pchEndRange ), bStaticDefines );
			if ( df.Max() < df.Min() )
				continue;

			// Add the define
			cg.AddDefine( df );
		}

		// #SKIP:
		if ( char *szLine = NextLine() )
		{
			exprSkip.Parse( szLine );
		}
		else
			return false;

		// #COMMAND:
		if ( !WaitFor( "#COMMAND", 8 ) )
			return false;
		if ( char *szLine = NextLine() )
			cfge.m_sPrefix = szLine;
		if ( char *szLine = NextLine() )
			cfge.m_sSuffix = szLine;

		// #END
		if ( !WaitFor( "#END", 4 ) )
			return false;

		return true;
	}

	void UnrollSectionCommands( CfgEntry const &cfge )
	{
		// Execute the combo computation
		//
		//

		g_comboEmission.m_sPrefix = cfge.m_sPrefix;
		g_comboEmission.m_sSuffix = cfge.m_sSuffix;

		OutputF( stdout, "Preparing %d combos for %s...\n", cfge.m_pCg->NumCombos(), cfge.m_szName );
		OutputF( stderr, "#%s\n", cfge.m_szName );

		time_t tt_start = time( NULL );
		cfge.m_pCg->RunAllCombos( *cfge.m_pExpr );
		time_t tt_end = time( NULL );

		OutputF( stderr, "#%s\n", cfge.m_szName );
		OutputF( stdout, "Prepared %s combos. %d sec.\n", cfge.m_szName, ( int ) difftime( tt_end, tt_start ) );

		g_comboEmission.m_sPrefix = "";
		g_comboEmission.m_sSuffix = "";
	}

	void RunSection( CfgEntry const &cfge )
	{
		// Execute the combo computation
		//
		//

		g_comboEmission.m_sPrefix = cfge.m_sPrefix;
		g_comboEmission.m_sSuffix = cfge.m_sSuffix;

		OutputF( stdout, "Preparing %d combos for %s...\n", cfge.m_pCg->NumCombos(), cfge.m_szName );
		OutputF( stderr, "#%s\n", cfge.m_szName );

		time_t tt_start = time( NULL );
		cfge.m_pCg->RunAllCombos( *cfge.m_pExpr );
		time_t tt_end = time( NULL );

		OutputF( stderr, "#%s\n", cfge.m_szName );
		OutputF( stdout, "Prepared %s combos. %d sec.\n", cfge.m_szName, ( int ) difftime( tt_end, tt_start ) );

		g_comboEmission.m_sPrefix = "";
		g_comboEmission.m_sSuffix = "";
	}

	void ProcessConfiguration()
	{
		static bool s_bProcessOnce = false;

		while ( char *szLine = WaitFor( "#BEGIN", 6 ) )
		{
			if ( ' ' == szLine[6] && !s_uniqueSections.Add(szLine + 7) )
				continue;

			CfgEntry cfge;
			cfge.m_szName = s_uniqueSections.Lookup( szLine + 7 );
			ProcessSection( cfge );
			s_setEntries.insert( cfge );
		}

		uint64 nCurrentCommand = 0;
		for( std::multiset< CfgEntry >::reverse_iterator it = s_setEntries.rbegin(),
			 itEnd = s_setEntries.rend(); it != itEnd; ++ it )
		{
			// We establish a command mapping for the beginning of the entry
			ComboHandleImpl chi;
			chi.Initialize( nCurrentCommand, &*it );
			s_mapComboCommands.Append( nCurrentCommand, chi );

			// We also establish mapping by either splitting the
			// combos into 500 intervals or stepping by every 1000 combos.
			int iPartStep = ( int ) max( 1000, (int)( chi.m_numCombos / 500 ) );
			for ( uint64 iRecord = nCurrentCommand + iPartStep;
				  iRecord < nCurrentCommand + chi.m_numCombos;
				  iRecord += iPartStep )
			{
				uint64 iAdvance = iPartStep;
				chi.AdvanceCommands( iAdvance );
				s_mapComboCommands.Append( iRecord, chi );
			}

			nCurrentCommand += chi.m_numCombos;
		}

		// Establish the last command terminator
		{
			static CfgEntry s_term;
			s_term.m_eiInfo.m_iCommandStart = s_term.m_eiInfo.m_iCommandEnd = nCurrentCommand;
			s_term.m_eiInfo.m_numCombos = s_term.m_eiInfo.m_numStaticCombos = s_term.m_eiInfo.m_numDynamicCombos = 1;
			s_term.m_eiInfo.m_szName = s_term.m_eiInfo.m_szShaderFileName = "";
			ComboHandleImpl chi;
			chi.m_iTotalCommand = nCurrentCommand;
			chi.m_pEntry = &s_term;
			s_mapComboCommands.Append( nCurrentCommand, chi );
		}
	}

}; // namespace ConfigurationProcessing

/*
int main( int argc, char **argv )
{
	if ( _isatty( _fileno( stdin ) ) )
	{
		return Usage();
	}

	// Go ahead processing the configuration
	ConfigurationProcessing::ProcessConfiguration();

	return 0;
}
*/

namespace CfgProcessor
{

	typedef ConfigurationProcessing::ComboHandleImpl CPCHI_t;
	CPCHI_t * FromHandle( ComboHandle hCombo ) { return reinterpret_cast < CPCHI_t * > ( hCombo ); }
	ComboHandle AsHandle( CPCHI_t *pImpl ) { return reinterpret_cast < ComboHandle > ( pImpl ); }

void ReadConfiguration( FILE *fInputStream )
{
	CAutoPushPop < FILE * > pushInputStream( ConfigurationProcessing::GetInputStream( fInputStream ), fInputStream );
	ConfigurationProcessing::ProcessConfiguration();
}

void ReadConfiguration( CUtlInplaceBuffer *fInputStream )
{
	CAutoPushPop < CUtlInplaceBuffer * > pushInputStream( ConfigurationProcessing::GetInputStream( fInputStream ), fInputStream );
	ConfigurationProcessing::ProcessConfiguration();
}

void DescribeConfiguration( CArrayAutoPtr < CfgEntryInfo > &rarrEntries )
{
	rarrEntries.Delete();
	rarrEntries.Attach( new CfgEntryInfo [ ConfigurationProcessing::s_setEntries.size() + 1 ] );

	CfgEntryInfo *pInfo = rarrEntries.Get();
	uint64 nCurrentCommand = 0;

	for( std::multiset< ConfigurationProcessing::CfgEntry >::reverse_iterator it =
		 ConfigurationProcessing::s_setEntries.rbegin(),
		 itEnd = ConfigurationProcessing::s_setEntries.rend();
		 it != itEnd; ++ it, ++ pInfo )
	{
		ConfigurationProcessing::CfgEntry const &e = *it;

		pInfo->m_szName = e.m_szName;
		pInfo->m_szShaderFileName = e.m_szShaderSrc;

		pInfo->m_iCommandStart = nCurrentCommand;
		pInfo->m_numCombos = e.m_pCg->NumCombos();
		pInfo->m_numDynamicCombos = e.m_pCg->NumCombos( false );
		pInfo->m_numStaticCombos = e.m_pCg->NumCombos( true );
		pInfo->m_iCommandEnd = pInfo->m_iCommandStart + pInfo->m_numCombos;

		const_cast< CfgEntryInfo & > ( e.m_eiInfo ) = *pInfo;

		nCurrentCommand += pInfo->m_numCombos;
	}

	// Terminator
	memset( pInfo, 0, sizeof( CfgEntryInfo ) );
	pInfo->m_iCommandStart = nCurrentCommand;
	pInfo->m_iCommandEnd = nCurrentCommand;
}

ComboHandle Combo_GetCombo( uint64 iCommandNumber )
{
	// Find earlier command
	uint64 iCommandFound = iCommandNumber;
	CPCHI_t emptyCPCHI;
	CPCHI_t const &chiFound = ConfigurationProcessing::s_mapComboCommands.GetLessOrEq( iCommandFound, emptyCPCHI );

	if ( chiFound.m_iTotalCommand < 0 ||
		 chiFound.m_iTotalCommand > iCommandNumber )
		 return NULL;

	// Advance the handle as needed
	CPCHI_t *pImpl = new CPCHI_t( chiFound );
	
	uint64 iCommandFoundAdvance = iCommandNumber - iCommandFound;
	pImpl->AdvanceCommands( iCommandFoundAdvance );

	return AsHandle( pImpl );
}

ComboHandle Combo_GetNext( uint64 &riCommandNumber, ComboHandle &rhCombo, uint64 iCommandEnd )
{
	// Combo handle implementation
	CPCHI_t *pImpl = FromHandle( rhCombo );

	if ( !rhCombo )
	{
		// We don't have a combo handle that corresponds to the command

		// Find earlier command
		uint64 iCommandFound = riCommandNumber;
		CPCHI_t emptyCPCHI;
		CPCHI_t const &chiFound = ConfigurationProcessing::s_mapComboCommands.GetLessOrEq( iCommandFound, emptyCPCHI );

		if ( !chiFound.m_pEntry ||
			 !chiFound.m_pEntry->m_pCg ||
			 !chiFound.m_pEntry->m_pExpr ||
			 chiFound.m_iTotalCommand < 0 ||
			 chiFound.m_iTotalCommand > riCommandNumber )
			 return NULL;

		// Advance the handle as needed
		pImpl = new CPCHI_t( chiFound );
		rhCombo = AsHandle( pImpl );
		
		uint64 iCommandFoundAdvance = riCommandNumber - iCommandFound;
		pImpl->AdvanceCommands( iCommandFoundAdvance );

		if ( !pImpl->IsSkipped() )
			return rhCombo;
	}

	for ( ; ; )
	{
		// We have the combo handle now
		if ( pImpl->NextNotSkipped( iCommandEnd ) )
		{
			riCommandNumber = pImpl->m_iTotalCommand;
			return rhCombo;
		}

		// We failed to get the next combo command (out of range)
		if ( pImpl->m_iTotalCommand + 1 >= iCommandEnd )
		{
			delete pImpl;
			rhCombo = NULL;
			riCommandNumber = iCommandEnd;
			return NULL;
		}

		// Otherwise we just have to obtain the next combo handle
		riCommandNumber = pImpl->m_iTotalCommand + 1;

		// Delete the old combo handle
		delete pImpl;
		rhCombo = NULL;

		// Retrieve the next combo handle data
		uint64 iCommandLookup = riCommandNumber;
		CPCHI_t emptyCPCHI;
		CPCHI_t const &chiNext = ConfigurationProcessing::s_mapComboCommands.GetLessOrEq( iCommandLookup, emptyCPCHI );
		Assert( ( iCommandLookup == riCommandNumber ) && ( chiNext.m_pEntry ) );

		// Set up the new combo handle
		pImpl = new CPCHI_t( chiNext );
		rhCombo = AsHandle( pImpl );
		
		if ( !pImpl->IsSkipped() )
			return rhCombo;
	}
}

void Combo_FormatCommand( ComboHandle hCombo, char *pchBuffer )
{
	CPCHI_t *pImpl = FromHandle( hCombo );
	pImpl->FormatCommand( pchBuffer );
}

uint64 Combo_GetCommandNum( ComboHandle hCombo )
{
	if ( CPCHI_t *pImpl = FromHandle( hCombo ) )
		return pImpl->m_iTotalCommand;
	else
		return ~uint64(0);
}

uint64 Combo_GetComboNum( ComboHandle hCombo )
{
	if ( CPCHI_t *pImpl = FromHandle( hCombo ) )
		return pImpl->m_iComboNumber;
	else
		return ~uint64(0);
}

CfgEntryInfo const *Combo_GetEntryInfo( ComboHandle hCombo )
{
	if ( CPCHI_t *pImpl = FromHandle( hCombo ) )
		return &pImpl->m_pEntry->m_eiInfo;
	else
		return NULL;
}

ComboHandle Combo_Alloc( ComboHandle hComboCopyFrom )
{
	if ( hComboCopyFrom )
		return AsHandle( new CPCHI_t( * FromHandle( hComboCopyFrom ) ) );
	else
		return AsHandle( new CPCHI_t );
}

void Combo_Assign( ComboHandle hComboDst, ComboHandle hComboSrc )
{
	Assert( hComboDst );
	* FromHandle( hComboDst ) = * FromHandle( hComboSrc );
}

void Combo_Free( ComboHandle &rhComboFree )
{
	delete FromHandle( rhComboFree );
	rhComboFree = NULL;
}

}; // namespace CfgProcessor