//====== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. =======
//
// Purpose: 
//
//=============================================================================

#include "vpc.h"
#include "baseprojectdatacollector.h"
#include "tier1/utlstack.h"
#include "p4lib/ip4.h"

// ------------------------------------------------------------------------------------------------ //
// CSpecificConfig implementation.
// ------------------------------------------------------------------------------------------------ //

CSpecificConfig::CSpecificConfig( CSpecificConfig *pParentConfig )
	: m_pParentConfig( pParentConfig )
{
	m_pKV = new KeyValues( "" );
	m_bFileExcluded = false;
	m_bIsSchema = false;
}

CSpecificConfig::~CSpecificConfig()
{
	m_pKV->deleteThis();
}

const char* CSpecificConfig::GetConfigName()
{
	return m_pKV->GetName();
}

const char* CSpecificConfig::GetOption( const char *pOptionName )
{
	const char *pRet = m_pKV->GetString( pOptionName, NULL );
	if ( pRet )
		return pRet;

	if ( m_pParentConfig )
		return m_pParentConfig->m_pKV->GetString( pOptionName, NULL );

	return NULL;
}


// ------------------------------------------------------------------------------------------------ //
// CFileConfig implementation.
// ------------------------------------------------------------------------------------------------ //

CFileConfig::~CFileConfig()
{
	Term();
}

void CFileConfig::Term()
{
	m_Configurations.PurgeAndDeleteElements();
}

const char* CFileConfig::GetName()
{
	return m_Filename.String();
}

CSpecificConfig* CFileConfig::GetConfig( const char *pConfigName )
{
	int i = m_Configurations.Find( pConfigName );
	if ( i == m_Configurations.InvalidIndex() )
		return NULL;
	else
		return m_Configurations[i];
}

CSpecificConfig* CFileConfig::GetOrCreateConfig( const char *pConfigName, CSpecificConfig *pParentConfig )
{
	int i = m_Configurations.Find( pConfigName );
	if ( i == m_Configurations.InvalidIndex() )
	{
		CSpecificConfig *pConfig = new CSpecificConfig( pParentConfig );
		i = m_Configurations.Insert( pConfigName, pConfig );
	}

	return m_Configurations[i];
}

bool CFileConfig::IsExcludedFrom( const char *pConfigName )
{
	CSpecificConfig *pSpecificConfig = GetConfig( pConfigName );
	if ( pSpecificConfig )
		return pSpecificConfig->m_bFileExcluded;
	else
		return false;
}



// ------------------------------------------------------------------------------------------------ //
// CBaseProjectDataCollector implementation.
// ------------------------------------------------------------------------------------------------ //

CBaseProjectDataCollector::CBaseProjectDataCollector( CRelevantPropertyNames *pNames ) : m_Files( k_eDictCompareTypeFilenames )
{
	m_RelevantPropertyNames.m_nNames = 0;
	m_RelevantPropertyNames.m_pNames = NULL;

	if ( pNames )
	{
		m_RelevantPropertyNames = *pNames;
	}
}

CBaseProjectDataCollector::~CBaseProjectDataCollector()
{
	Term();
}

void CBaseProjectDataCollector::StartProject()
{
	m_ProjectName = "UNNAMED";
	m_CurFileConfig.Push( &m_BaseConfigData );
	m_CurSpecificConfig.Push( NULL );
}

void CBaseProjectDataCollector::EndProject()
{
}

void CBaseProjectDataCollector::Term()
{
	m_BaseConfigData.Term();
	m_Files.PurgeAndDeleteElements();
	m_CurFileConfig.Purge();
	m_CurSpecificConfig.Purge();
}

CUtlString CBaseProjectDataCollector::GetProjectName()
{
	return m_ProjectName;
}

void CBaseProjectDataCollector::SetProjectName( const char *pProjectName )
{
	char tmpBuf[MAX_PATH];
	V_strncpy( tmpBuf, pProjectName, sizeof( tmpBuf ) );
	Q_strlower( tmpBuf );
	m_ProjectName = tmpBuf;
}

// Get a list of all configurations.
void CBaseProjectDataCollector::GetAllConfigurationNames( CUtlVector< CUtlString > &configurationNames )
{
	configurationNames.Purge();
	for ( int i=m_BaseConfigData.m_Configurations.First(); i != m_BaseConfigData.m_Configurations.InvalidIndex(); i=m_BaseConfigData.m_Configurations.Next(i) )
	{
		configurationNames.AddToTail( m_BaseConfigData.m_Configurations.GetElementName(i) );
	}
}

void CBaseProjectDataCollector::StartConfigurationBlock( const char *pConfigName, bool bFileSpecific )
{
	CFileConfig *pFileConfig = m_CurFileConfig.Top();

	// Find or add a new config block.
	char sLowerCaseConfigName[MAX_PATH];
	V_strncpy( sLowerCaseConfigName, pConfigName, sizeof( sLowerCaseConfigName ) );
	V_strlower( sLowerCaseConfigName );

	int index = pFileConfig->m_Configurations.Find( sLowerCaseConfigName );
	if ( index == -1 )
	{
		CSpecificConfig *pParent = ( pFileConfig==&m_BaseConfigData ? NULL : m_BaseConfigData.GetOrCreateConfig( sLowerCaseConfigName, NULL ) );

		CSpecificConfig *pConfig = new CSpecificConfig( pParent );
		pConfig->m_bFileExcluded = false;
		pConfig->m_pKV->SetName( sLowerCaseConfigName );
		index = pFileConfig->m_Configurations.Insert( sLowerCaseConfigName, pConfig );
	}

	// Remember what the current config is.
	m_CurSpecificConfig.Push( pFileConfig->m_Configurations[index] );
}

void CBaseProjectDataCollector::EndConfigurationBlock()
{
	m_CurSpecificConfig.Pop();
}

bool CBaseProjectDataCollector::StartPropertySection( configKeyword_e keyword, bool *pbShouldSkip )
{
	return true;
}

void CBaseProjectDataCollector::HandleProperty( const char *pProperty, const char *pCustomScriptData )
{
	int i;
	for ( i=0; i < m_RelevantPropertyNames.m_nNames; i++ )
	{
		if ( V_stricmp( m_RelevantPropertyNames.m_pNames[i], pProperty ) == 0 )
			break;
	}
	if ( i == m_RelevantPropertyNames.m_nNames )
	{
		// not found
		return;
	}

	if ( pCustomScriptData )
	{
		g_pVPC->GetScript().PushScript( "HandleProperty( custom script data )", pCustomScriptData );
	}

	const char *pNextToken = g_pVPC->GetScript().PeekNextToken( false );
	if ( pNextToken && pNextToken[0] != 0 )
	{
		// Pass in the previous value so the $base substitution works.
		CSpecificConfig *pConfig = m_CurSpecificConfig.Top();
		const char *pBaseString = pConfig->m_pKV->GetString( pProperty );
		char buff[MAX_SYSTOKENCHARS];
		if ( g_pVPC->GetScript().ParsePropertyValue( pBaseString, buff, sizeof( buff ) ) )
		{
			pConfig->m_pKV->SetString( pProperty, buff );
		}
	}

	if ( pCustomScriptData )
	{
		// Restore prior script state
		g_pVPC->GetScript().PopScript();
	}
}

void CBaseProjectDataCollector::EndPropertySection( configKeyword_e keyword )
{
}

void CBaseProjectDataCollector::StartFolder( const char *pFolderName )
{
}
void CBaseProjectDataCollector::EndFolder()
{
}

bool CBaseProjectDataCollector::StartFile( const char *pFilename, bool bWarnIfAlreadyExists )
{
	CFileConfig *pFileConfig = new CFileConfig;
	pFileConfig->m_Filename = pFilename;
	m_Files.Insert( pFilename, pFileConfig );

	m_CurFileConfig.Push( pFileConfig );
	m_CurSpecificConfig.Push( NULL );

	char szFullPath[MAX_PATH];

	V_GetCurrentDirectory( szFullPath, sizeof( szFullPath ) );
	V_AppendSlash( szFullPath, sizeof( szFullPath ) );
	V_strncat( szFullPath, pFilename, sizeof( szFullPath ) );
	V_RemoveDotSlashes( szFullPath );

#if 0
	// Add file to Perforce if it isn't there already
	if ( Sys_Exists( szFullPath ) )
	{	
		if ( m_bP4AutoAdd && p4 && !p4->IsFileInPerforce( szFullPath ) )
		{
			p4->OpenFileForAdd( szFullPath );
			VPCStatus( "%s automatically opened for add in default changelist.", szFullPath );

		}
	}
	else 
	{
		// g_pVPC->Warning( "%s not found on disk at location specified in project script.", szFullPath );
	}
#endif

	return true;
}

void CBaseProjectDataCollector::EndFile()
{
	m_CurFileConfig.Pop();
	m_CurSpecificConfig.Pop();
}

// This is actually just per-file configuration data.
void CBaseProjectDataCollector::FileExcludedFromBuild( bool bExcluded )
{
	CSpecificConfig *pConfig = m_CurSpecificConfig.Top();
	pConfig->m_bFileExcluded = bExcluded;
}

void CBaseProjectDataCollector::FileIsSchema( bool bIsSchema )
{
	CSpecificConfig *pConfig = m_CurSpecificConfig.Top();
	pConfig->m_bIsSchema = bIsSchema;
}


bool CBaseProjectDataCollector::RemoveFile( const char *pFilename )
{
	bool bRet = false;
	int i = m_Files.Find( pFilename );
	if ( i != m_Files.InvalidIndex() )
	{
		delete m_Files[i];
		m_Files.RemoveAt( i );
		bRet = true;
	}
	return bRet;
}

void CBaseProjectDataCollector::DoStandardVisualStudioReplacements( const char *pStartString, const char *pFullInputFilename, char *pOut, int outLen )
{
	// Decompose the input filename.
	char sInputDir[MAX_PATH], sFileBase[MAX_PATH];
	if ( !V_ExtractFilePath( pFullInputFilename, sInputDir, sizeof( sInputDir ) ) )
		g_pVPC->VPCError( "V_ExtractFilePath failed on %s.", pFullInputFilename );
	
	V_FileBase( pFullInputFilename, sFileBase, sizeof( sFileBase ) );
	
	// Handle $(InputPath), $(InputDir), $(InputName)
	char *strings[2] =
		{
			(char*)stackalloc( outLen ),
			(char*)stackalloc( outLen )
		};
	
	V_StrSubst( pStartString, "$(InputPath)", pFullInputFilename, strings[0], outLen );
	V_StrSubst( strings[0], "$(InputDir)", sInputDir, strings[1], outLen );
	V_StrSubst( strings[1], "$(InputName)", sFileBase, strings[0], outLen );
	V_StrSubst( strings[0], "$(IntDir)", "$(OBJ_DIR)", strings[1], outLen );
	V_StrSubst( strings[1], "$(InputFileName)", pFullInputFilename + Q_strlen(sInputDir), strings[0], outLen );
	
	V_strncpy( pOut, strings[0], outLen );
	V_FixSlashes( pOut, '/' );
}