//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================
#include <assert.h>
#include <time.h>
#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
#include "depcheck_util.h"
#include "codeprocessor.h"

/*
================
UTIL_FloatTime
================
*/
double UTIL_FloatTime (void)
{
// more precise, less portable
	clock_t current;
	static clock_t base;
	static bool first = true;

	current = clock();

	if ( first )
	{
		first = false;

		base = current;
	}
	
	return (double)(current - base)/(double)CLOCKS_PER_SEC;
}

void CCodeProcessor::AddHeader( int depth, const char *filename, const char *rootmodule )
{
//	if ( depth < 1 )
//		return;
	if ( depth != 1 )
		return;

	// Check header list
	for ( int i = 0; i < m_nHeaderCount; i++ )
	{
		if ( !stricmp( m_Headers[ i ].name, filename ) )
		{
			vprint( 0, "%s included twice in module %s\n", filename, rootmodule );
			return;
		}
	}
	
	// Add to list
	strcpy( m_Headers[ m_nHeaderCount++ ].name, filename );
}

void CCodeProcessor::CreateBackup( const char *filename, bool& wasreadonly )
{
	assert( strstr( filename, ".cpp" ) );

	// attrib it, change extension, save it
	if ( GetFileAttributes( filename ) & FILE_ATTRIBUTE_READONLY )
	{
		wasreadonly = true;

		SetFileAttributes( filename, FILE_ATTRIBUTE_NORMAL );
	}
	else
	{
		wasreadonly = false;
	}

	char backupname[ 256 ];
	strcpy( backupname, filename );
	strcpy( (char *)&backupname[ strlen( filename ) - 4 ], ".bak" );

	unlink( backupname );
	rename( filename, backupname );

}

void CCodeProcessor::RestoreBackup( const char *filename, bool makereadonly )
{
	assert( strstr( filename, ".cpp" ) );

	char backupname[ 256 ];
	strcpy( backupname, filename );
	strcpy( (char *)&backupname[ strlen( filename ) - 4 ], ".bak" );

	SetFileAttributes( filename, FILE_ATTRIBUTE_NORMAL );

	unlink( filename );
	rename( backupname, filename );

	if ( makereadonly )
	{
		SetFileAttributes( filename, FILE_ATTRIBUTE_READONLY );
	}

	unlink( backupname );
}

bool CCodeProcessor::TryBuild( const char *rootdir, const char *filename, unsigned char *buffer, int filelength )
{
//	vprintf( "trying build\n" );

	FILE *fp;
	fp = fopen( filename, "wb" );
	if ( !fp )
	{
		assert( 0 );
		return false;
	}

	fwrite( buffer, filelength, 1, fp );
	fclose( fp );

	// if build is successful, return true
	//
	//	return true;
	char commandline[ 512 ];
	char directory[ 512 ];

	sprintf( directory, rootdir );

	//	sprintf( commandline, "msdev engdll.dsw /MAKE \"quiver - Win32 GL Debug\" /OUT log.txt" );

	// Builds the default configuration
	sprintf( commandline, "\"C:\\Program Files\\Microsoft Visual Studio\\Common\\MSDev98\\Bin\\msdev.exe\" %s /MAKE \"%s\" /OUT log.txt", m_szDSP, m_szConfig );

	PROCESS_INFORMATION pi;
	memset( &pi, 0, sizeof( pi ) );

	STARTUPINFO si;
	memset( &si, 0, sizeof( si ) );
	si.cb = sizeof( si );

	if ( !CreateProcess( NULL, commandline, NULL, NULL, TRUE, 0, NULL, directory, &si, &pi ) )
	{
LPVOID lpMsgBuf;
FormatMessage( 
    FORMAT_MESSAGE_ALLOCATE_BUFFER | 
    FORMAT_MESSAGE_FROM_SYSTEM | 
    FORMAT_MESSAGE_IGNORE_INSERTS,
    NULL,
    GetLastError(),
    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
    (LPTSTR) &lpMsgBuf,
    0,
    NULL 
);
// Process any inserts in lpMsgBuf.
// ...
// Display the string.
MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );
// Free the buffer.
LocalFree( lpMsgBuf );
		return false;
	}

	// Wait until child process exits.
    WaitForSingleObject( pi.hProcess, INFINITE );

	bool retval = false;
	DWORD exitCode = -1;
	if ( GetExitCodeProcess( pi.hProcess, &exitCode ) )
	{
		if ( !exitCode )
		{
			retval = true;
		}
	}
	
    // Close process and thread handles. 
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );

	return retval;
}

void CCodeProcessor::ProcessModule( bool forcequiet, int depth, int& maxdepth, int& numheaders, int& skippedfiles, const char *baseroot, const char *root, const char *module )
{
	char filename[ 256 ];

	bool checkroot = false;

	if ( depth > maxdepth )
	{
		maxdepth = depth;
	}

	// Load the base module
	sprintf( filename, "%s\\%s", root, module );
	strlwr( filename );

	bool firstheader = true;
retry:

	// Check module list
	for ( int i = 0; i < m_nModuleCount; i++ )
	{
		if ( !stricmp( m_Modules[ i ].name, filename ) )
		{
			if ( forcequiet )
			{
				m_nHeadersProcessed++;
				numheaders++;

				if ( m_Modules[ i ].skipped )
				{
					skippedfiles++;
				}
			}

			AddHeader( depth, filename, m_szCurrentCPP );

			return;
		}
	}

	int filelength;
	char *buffer = (char *)COM_LoadFile( filename, &filelength );
	if ( !buffer )
	{
		if ( !checkroot )
		{
			checkroot = true;
			// Load the base module
			sprintf( filename, "%s\\%s", baseroot, module );
			goto retry;
		}
		m_Modules[ m_nModuleCount ].skipped = true;
		strcpy( m_Modules[ m_nModuleCount++ ].name, filename );
		
		skippedfiles++;
		return;
	}

	m_nBytesProcessed += filelength;

	m_Modules[ m_nModuleCount ].skipped = false;
	strcpy( m_Modules[ m_nModuleCount++ ].name, filename );

	bool readonly = false;
	bool madechanges = false;
	CreateBackup( filename, readonly );

	if ( !forcequiet )
	{
		strcpy( m_szCurrentCPP, filename );
		
		vprint( 0, "- %s\n", (char *)&filename[ m_nOffset ] );
	}

	// Parse tokens looking for #include directives or class starts
	char *current = buffer;
	char *startofline;

	current = CC_ParseToken( current );
	while ( current )
	{
		// No more tokens
		if ( strlen( com_token ) <= 0 )
			break;

		if ( !stricmp( com_token, "#include" ) )
		{
			startofline = current - strlen( "#include" );

			current = CC_ParseToken( current );

			if ( strlen( com_token ) > 0)
			{
				vprint( 1, "#include %s", com_token );
				m_nHeadersProcessed++;
				numheaders++;				

				AddHeader( depth, filename, m_szCurrentCPP );

				bool dobuild = true;
				if ( firstheader )
				{
					if ( !stricmp( com_token, "cbase.h" ) )
					{
						dobuild = false;
					}

					if ( !TryBuild( baseroot, filename, (unsigned char *)buffer, filelength ) )
					{
						// build is broken, stop
						assert( 0 );
					}
				}

				firstheader = false;

				if ( dobuild )
				{
					// Try removing the header and compiling
					char saveinfo[2];
					memcpy( saveinfo, startofline, 2 );
					startofline[ 0 ] = '/';
					startofline[ 1 ] = '/';

					if ( TryBuild( baseroot, filename, (unsigned char *)buffer, filelength ) )
					{
						vprint( 0, ", unnecessary\n" );
						madechanges = true;
					}
					else
					{
						// Restore line
						memcpy( startofline, saveinfo, 2 );
						vprint( 0, "\n" );
					}
				}
				else
				{
					vprint( 0, "\n" );
				}
			}
		}

		current = CC_ParseToken( current );
	}

	// Save out last set of changes
	{
		FILE *fp;
		fp = fopen( filename, "wb" );
		if ( fp )
		{
			fwrite( buffer, filelength, 1, fp );
			fclose( fp );
		}
	}

	COM_FreeFile( (unsigned char *)buffer );

	if ( !madechanges )
	{
		RestoreBackup( filename, readonly );
	}

	if ( !forcequiet && !GetQuiet() )
	{
		vprint( 0, " %s: headers (%i)", (char *)&filename[ m_nOffset ], numheaders );
		if ( maxdepth > 1 )
		{
			vprint( 0, ", depth %i", maxdepth );
		}
		vprint( 0, "\n" );
	}

	m_nLinesOfCode += linesprocessed;
	linesprocessed = 0;
}

void CCodeProcessor::ProcessModules( const char *root, const char *rootmodule )
{
	m_nFilesProcessed++;

	// Reset header list per module
	m_nHeaderCount = 0;
	m_nModuleCount = 0;

	int numheaders = 0;
	int maxdepth = 0;
	int skippedfiles = 0;

	bool canstart = false;

	if ( strstr( root, "tf2_hud" ) )
	{
		canstart = true;
	}
	if ( !canstart )
	{
		vprint( 0, "skipping %s\n", rootmodule );
		return;
	}
	ProcessModule( false, 0, maxdepth, numheaders, skippedfiles, root, root, rootmodule );
}

void CCodeProcessor::PrintResults( void )
{
	vprint( 0, "\nChecking for errors and totaling...\n\n" );

	vprint( 0, "parsed from %i files ( %i headers parsed )\n",
		m_nFilesProcessed,
		m_nHeadersProcessed );

	vprint( 0, "%.3f K lines of code processed\n",
		(double)m_nLinesOfCode / 1024.0 );
	
	double elapsed = ( m_flEnd - m_flStart );

	if ( elapsed > 0.0 )
	{
		vprint( 0, "%.2f K processed in %.3f seconds, throughput %.2f KB/sec\n\n",
			(double)m_nBytesProcessed / 1024.0, elapsed, (double)m_nBytesProcessed / ( 1024.0 * elapsed ) );
	}
}

CCodeProcessor::CCodeProcessor( void )
{
	m_nModuleCount					= 0;

	m_bQuiet						= false;
	m_bLogToFile					= false;

	m_nFilesProcessed				= 0;
	m_nHeadersProcessed				= 0;
	m_nOffset						= 0;
	m_nBytesProcessed				= 0;
	m_nLinesOfCode					= 0;
	m_flStart						= 0.0;
	m_flEnd							= 0.0;

	m_szCurrentCPP[ 0 ]				= 0;
}

CCodeProcessor::~CCodeProcessor( void )
{
}

char const *stristr( char const *src, char const *search )
{
	char buf1[ 512 ];
	char buf2[ 512 ];

	strcpy( buf1, src );
	_strlwr( buf1 );

	strcpy( buf2, search );
	_strlwr( buf2 );

	char *p =  strstr( buf1, buf2 );
	if ( p )
	{
		int len = p - buf1;
		return src + len;
	}
	return NULL;
}

void CCodeProcessor::ConstructModuleList_R( int level, const char *gamespecific, const char *root )
{
	char directory[ 256 ];
	char filename[ 256 ];
	WIN32_FIND_DATA wfd;
	HANDLE ff;

	sprintf( directory, "%s\\*.*", root );

	if ( ( ff = FindFirstFile( directory, &wfd ) ) == INVALID_HANDLE_VALUE )
		return;

	do
	{
		if ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
		{

			if ( wfd.cFileName[ 0 ] == '.' )
				continue;

			// Once we descend down a branch, don't keep looking for hl2/tf2 in name, just recurse through all children
			if ( level == 0 && !stristr( wfd.cFileName, gamespecific ) )
				continue;

			// Recurse down directory
			sprintf( filename, "%s\\%s", root, wfd.cFileName );
			ConstructModuleList_R( level+1, gamespecific, filename );
		}
		else
		{
			if ( strstr( wfd.cFileName, ".cpp" ) )
			{
				ProcessModules( root, wfd.cFileName );
			}
		}
	} while ( FindNextFile( ff, &wfd ) );
}

void CCodeProcessor::Process( const char *gamespecific, const char *root, const char *dsp, const char *config )
{
	strcpy( m_szDSP, dsp );
	strcpy( m_szConfig, config );

	m_nBytesProcessed	= 0;
	m_nFilesProcessed	= 0;
	m_nHeadersProcessed = 0;
	m_nLinesOfCode		= 0;

	linesprocessed		= 0;

	m_nOffset = strlen( root ) + 1;

	m_nModuleCount = 0;
	m_nHeaderCount = 0;
	m_flStart = UTIL_FloatTime();

	vprint( 0, "--- Processing %s\n\n", root );

	ConstructModuleList_R( 0, gamespecific, root );

	m_flEnd = UTIL_FloatTime();

	PrintResults();
}

void CCodeProcessor::SetQuiet( bool quiet )
{
	m_bQuiet = quiet;
}

bool CCodeProcessor::GetQuiet( void ) const
{
	return m_bQuiet;
}

void CCodeProcessor::SetLogFile( bool log )
{
	m_bLogToFile = log;
}

bool CCodeProcessor::GetLogFile( void ) const
{
	return m_bLogToFile;
}

static CCodeProcessor g_Processor;
ICodeProcessor *processor = ( ICodeProcessor * )&g_Processor;