//-----------------------------------------------------------------------------------------------
// EngineUtilities.cpp
//
#include "GypsyPrecompiledHeader.hpp"

//-----------------------------------------------------------------------------------------------
#include "Engine/Core/EngineUtilities.hpp"
#include "Engine/Core/ErrorWarningAssert.hpp"
#include "Engine/Console/Console.hpp"
#include <stdarg.h>


//-----------------------------------------------------------------------------------------------
const int STRINGF_STACK_LOCAL_TEMP_LENGTH = 2048;



//-----------------------------------------------------------------------------------------------
void Sprintf( std::string& destination, const char* format, ... )
{
	char textLiteral[ STRINGF_STACK_LOCAL_TEMP_LENGTH ];
	va_list variableArgumentList;
	va_start( variableArgumentList, format );
	vsnprintf_s( textLiteral, STRINGF_STACK_LOCAL_TEMP_LENGTH, _TRUNCATE, format, variableArgumentList );	
	va_end( variableArgumentList );
	textLiteral[ STRINGF_STACK_LOCAL_TEMP_LENGTH - 1 ] = '\0'; // In case vsnprintf overran (doesn't auto-terminate)

	destination = textLiteral;
}


//-----------------------------------------------------------------------------------------------
void Sprintf( const int maxLength, std::string& destination, const char* format, ... )
{
	char textLiteralSmall[ STRINGF_STACK_LOCAL_TEMP_LENGTH ];
	char* textLiteral = textLiteralSmall;
	if( maxLength > STRINGF_STACK_LOCAL_TEMP_LENGTH )
		textLiteral = new char[ maxLength ];

	va_list variableArgumentList;
	va_start( variableArgumentList, format );
	vsnprintf_s( textLiteral, maxLength, _TRUNCATE, format, variableArgumentList );	
	va_end( variableArgumentList );
	textLiteral[ maxLength - 1 ] = '\0'; // In case vsnprintf overran (doesn't auto-terminate)

	destination = textLiteral;
	if( maxLength > STRINGF_STACK_LOCAL_TEMP_LENGTH )
		delete[] textLiteral;
}


//-----------------------------------------------------------------------------------------------
const std::string Stringf( const char* format, ... )
{
	char textLiteral[ STRINGF_STACK_LOCAL_TEMP_LENGTH ];
	va_list variableArgumentList;
	va_start( variableArgumentList, format );
	vsnprintf_s( textLiteral, STRINGF_STACK_LOCAL_TEMP_LENGTH, _TRUNCATE, format, variableArgumentList );	
	va_end( variableArgumentList );
	textLiteral[ STRINGF_STACK_LOCAL_TEMP_LENGTH - 1 ] = '\0'; // In case vsnprintf overran (doesn't auto-terminate)

	return std::string( textLiteral );
}


//-----------------------------------------------------------------------------------------------
const std::string Stringf( const int maxLength, const char* format, ... )
{
	char textLiteralSmall[ STRINGF_STACK_LOCAL_TEMP_LENGTH ];
	char* textLiteral = textLiteralSmall;
	if( maxLength > STRINGF_STACK_LOCAL_TEMP_LENGTH )
		textLiteral = new char[ maxLength ];

	va_list variableArgumentList;
	va_start( variableArgumentList, format );
	vsnprintf_s( textLiteral, maxLength, _TRUNCATE, format, variableArgumentList );	
	va_end( variableArgumentList );
	textLiteral[ maxLength - 1 ] = '\0'; // In case vsnprintf overran (doesn't auto-terminate)

	std::string returnValue( textLiteral );
	if( maxLength > STRINGF_STACK_LOCAL_TEMP_LENGTH )
		delete[] textLiteral;

	return returnValue;
}


//-----------------------------------------------------------------------------------------------
std::string GetAsLowerCase( const std::string& mixedCaseString )
{
	std::string lowerCaseString( mixedCaseString );
	unsigned int stringLength = lowerCaseString.size();
	for( unsigned int charIndex = 0; charIndex < stringLength; ++ charIndex )
	{
		lowerCaseString[ charIndex ] = static_cast< char >( tolower( lowerCaseString[ charIndex ] ) );
	}

	return lowerCaseString;
}


//-----------------------------------------------------------------------------------------------
void GetAsLowerCase( const std::string& mixedCaseString, std::string& out_lowerCaseString )
{
	unsigned int stringLength = mixedCaseString.size();
	out_lowerCaseString.reserve( stringLength );

	for( unsigned int charIndex = 0; charIndex < stringLength; ++ charIndex )
	{
		char lowerCaseChar = static_cast< char >( tolower( mixedCaseString[ charIndex ] ) );
		out_lowerCaseString.push_back( lowerCaseChar );
	}
}


//-----------------------------------------------------------------------------------------------
bool DoesShorterStringMatch( const std::string& firstString, const std::string& secondString )
{
	unsigned int firstStringLength = firstString.size();
	unsigned int secondStringLength = secondString.size();
	for( unsigned int charIndex = 0; (charIndex < secondStringLength) && (charIndex < firstStringLength); ++ charIndex )
	{
		if( firstString[ charIndex ] != secondString[ charIndex ] )
			return false;
	}

	return true;
}


//-----------------------------------------------------------------------------------------------
bool DoesShorterStringMatchCaseInsensitive( const std::string& firstString, const std::string& secondString )
{
	unsigned int firstStringLength = firstString.size();
	unsigned int secondStringLength = secondString.size();
	for( unsigned int charIndex = 0; (charIndex < secondStringLength) && (charIndex < firstStringLength); ++ charIndex )
	{
		char firstLoweredChar = (char) tolower( firstString[ charIndex ] );
		char secondLoweredChar = (char) tolower( secondString[ charIndex ] );
		if( firstLoweredChar != secondLoweredChar )
			return false;
	}

	return true;
}


//-----------------------------------------------------------------------------------------------
bool IsFirstStringCaseInsensitiveSubsetOfSecond( const std::string& firstString, const std::string& secondString )
{
	std::string firstStringLowered;
	std::string secondStringLowered;
	GetAsLowerCase( firstString, firstStringLowered );
	GetAsLowerCase( secondString, secondStringLowered );
	return( secondString.find( firstStringLowered ) != std::string::npos );
}


//-----------------------------------------------------------------------------------------------
void SkipWhitespace( const char*& scan )
{
	for( ;; )
	{
		switch( *scan )
		{
			case ' ':
			case '\t':
			case '\r':
			case '\n':
				++ scan;
				break;

			default:
				return;
		}
	}
}


//-----------------------------------------------------------------------------------------------
void SkipInlineWhitespace( const char*& scan )
{
	for( ;; )
	{
		switch( *scan )
		{
		case ' ':
		case '\t':
			++ scan;
			break;

		default:
			return;
		}
	}
}


/*
//-----------------------------------------------------------------------------------------------
void SkipWhitespace( const char*& scan )
{
	while( isspace( *scan ) )
		++ scan;
}
*/


//-----------------------------------------------------------------------------------------------
void SkipWhitespaceAndCountLineNumbers( const char*& scan, int& lineNumber )
{
	for( ;; )
	{
		switch( *scan )
		{
			case '\n':
				++ lineNumber;
				// fall through
			case '\r':
			case ' ':
			case '\t':
				++ scan;
				break;

			default:
				return;
		}
	}
}


//-----------------------------------------------------------------------------------------------
void SkipNonWhitespace( const char*& scan )
{
	for( ;; )
	{
		switch( *scan )
		{
			case ' ':
			case '\t':
			case '\r':
			case '\n':
			case '\0':
				return;

			default:
				++ scan;
		}
	}
}


/*
//-----------------------------------------------------------------------------------------------
void SkipNonWhitespace( const char*& scan )
{
	while( *scan != '\0' && !isspace( *scan ) )
		++ scan;
}
*/


//-----------------------------------------------------------------------------------------------
void SkipQuotedString( const char*& scan )
{
	FATAL_ASSERTION( *scan == '\"', Stringf( "Internal Error: SkipQuotedString() called on non-quoted string:\n  %20.20s\n", scan ) );
	++ scan; // skip opening quote
	while( *scan != '\0' && *scan != '\"' )
		++ scan;

	if( *scan == '\"' )
		++ scan;
}


//-----------------------------------------------------------------------------------------------
void SkipQuotedStringAndCountLineNumbers( const char*& scan, int& lineNumber )
{
	FATAL_ASSERTION( *scan == '\"', Stringf( "Internal Error: SkipQuotedString() called on non-quoted string:\n  %20.20s\n", scan ) );
	++ scan; // skip opening quote

	for( ;; )
	{
		switch( *scan )
		{
			case '\"':
				++ scan;
				// fall through
			case '\0':
				return;

			case '\n':
				++ lineNumber;
				// fall through
			default:
				++ scan;
		}
	}
}


//-----------------------------------------------------------------------------------------------
std::string ParseContiguousStringAndAdvance( const char*& scan )
{
	SkipWhitespace( scan );
	if( *scan == '\0' )
		return "";

	const char* stringStart = scan;
	SkipNonWhitespace( scan );
	int stringLength = scan - stringStart;
	return std::string( stringStart, stringLength );
}


//-----------------------------------------------------------------------------------------------
std::string ParseContiguousOrQuotedStringAndAdvance( const char*& scan )
{
	SkipWhitespace( scan );
	if( *scan == '\0' )
		return "";

	const char* stringStart = scan;
	if( *stringStart == '\"' )
	{
		SkipQuotedString( scan );
		int stringLength = scan - stringStart - 2; // Account for opening and closing quotes
		return std::string( stringStart + 1, stringLength ); // Skip opening quotes
	}
	else
	{
		SkipNonWhitespace( scan );
		int stringLength = scan - stringStart;
		return std::string( stringStart, stringLength );
	}
}


//-----------------------------------------------------------------------------------------------
CommandLineCommands::CommandLineCommands( const std::string& rawCommandLineString )
	: m_rawCommandLineString( rawCommandLineString )
{
	const char* scan = rawCommandLineString.c_str();
	SkipWhitespace( scan );

//	RECOVERABLE_GUARANTEE( *scan == '-' || *scan == '\0', Stringf( "PROGRAM USAGE ERROR: Command-line commands must begin with a hypen, and may then be followed by arguments.  For example:\n\n  MyGame.exe -cmd0 arg0 arg1 -cmd1 -cmd2 arg0\n\nIllegal command line given was:\n\n  %s\n", rawCommandLineString.c_str() ) );
	while( *scan == '-' )
	{
		ParseCommandAndArgs( scan );
	}	
}


//-----------------------------------------------------------------------------------------------
void CommandLineCommands::ParseCommandAndArgs( const char*& scan )
{
	SkipWhitespace( scan );

//	if( *scan == '\0' )
	if( *scan != '-' )
		return;

//	RECOVERABLE_GUARANTEE( *scan == '-', Stringf( "Internal error: found unexpected command line command NOT starting with a hypen; text was:\n  %s\n", scan ) );
	++ scan; // skip '-'

	CommandLineCommand newCommand;
	newCommand.m_commandName = ParseContiguousStringAndAdvance( scan );
	SkipWhitespace( scan );
	while( *scan != '-' && *scan != '\0' )
	{
		std::string commandArg = ParseContiguousOrQuotedStringAndAdvance( scan );
		newCommand.m_args.push_back( commandArg );
		SkipWhitespace( scan );
	}

	m_commandsWithArgs.push_back( newCommand );
}


//-----------------------------------------------------------------------------------------------
bool CommandLineCommands::IsCommandPresent( const std::string& commandName ) const
{
	for( unsigned int commandIndex = 0; commandIndex < m_commandsWithArgs.size(); ++ commandIndex )
	{
		const CommandLineCommand& command = m_commandsWithArgs[ commandIndex ];
		if( !Stricmp( command.m_commandName, commandName ) )
			return true;
	}

	return false;
}


//-----------------------------------------------------------------------------------------------
const std::vector< std::string >& CommandLineCommands::GetCommandArgs( const std::string& commandName ) const
{
	for( unsigned int commandIndex = 0; commandIndex < m_commandsWithArgs.size(); ++ commandIndex )
	{
		const CommandLineCommand& command = m_commandsWithArgs[ commandIndex ];
		if( !Stricmp( command.m_commandName, commandName ) )
		{
			return command.m_args;
		}
	}

	FATAL_ERROR( Stringf( "CommandLineCommands::GetCommandArgs() - no such command line command '%s' was present!", commandName.c_str() ) );
}


//-----------------------------------------------------------------------------------------------
void ParseCommandsAndArgs( const std::string& rawCommandString, std::string& out_commandName, std::string& out_commandArgs, std::vector< std::string >& out_commandArgList )
{
	const char* scan = rawCommandString.c_str();
	out_commandName = ParseContiguousOrQuotedStringAndAdvance( scan );
	SkipWhitespace( scan );
	out_commandArgs = scan;
	
	out_commandArgList.clear();
	while( scan != '\0' )
	{
		std::string argument = ParseContiguousOrQuotedStringAndAdvance( scan );
		if( argument.empty() )
			break;

		out_commandArgList.push_back( argument );
	}
}


//-----------------------------------------------------------------------------------------------
std::string GetLineFromTextBuffer( const std::string& textBuffer, int lineNumber )
{
	int numLineFeedsToSkip = lineNumber - 1;
	size_t posOfLineStart = 0;
	for( int lfSkipCount = 0; lfSkipCount < numLineFeedsToSkip; ++ lfSkipCount )
	{
		posOfLineStart = textBuffer.find( '\n', posOfLineStart );
		if( posOfLineStart == std::string::npos )
		{
			return Stringf( "(ILLEGAL LINE #%d - TEXT BUFFER WAS ONLY %d LINES LONG!)", lineNumber, lfSkipCount + 1 );
		}

		++ posOfLineStart;
	}

	size_t posOfLineEnd = textBuffer.find( '\n', posOfLineStart );
	if( posOfLineEnd == std::string::npos )
	{
		return std::string( textBuffer, posOfLineStart );
	}
	else
	{
		size_t lineLength = posOfLineEnd - posOfLineStart;
		return std::string( textBuffer, posOfLineStart, lineLength );
	}
}


//-----------------------------------------------------------------------------------------------
CaseInsensitiveStringHashValue ComputeCaseInsensitiveHashForString( const char* stringWhoseCaseWillBeIgnoredForHashGeneration )
{
	CaseInsensitiveStringHashValue hashValue = 0;
	const char* scan = stringWhoseCaseWillBeIgnoredForHashGeneration;
	while( *scan != '\0' )
	{
		hashValue &= 0x07ffffff;
		hashValue *= 31;
		hashValue += tolower( *scan );
		++ scan;
	}

	return hashValue;
}


//-----------------------------------------------------------------------------------------------
CaseInsensitiveStringHashValue ComputeCaseInsensitiveHashForString( const char* stringWhoseCaseWillBeIgnoredForHashGeneration, unsigned int stringLength )
{
	CaseInsensitiveStringHashValue hashValue = 0;
	const char* scan = stringWhoseCaseWillBeIgnoredForHashGeneration;
	unsigned int scanIndex = 0;
	while( scanIndex < stringLength )
	{
		hashValue &= 0x07ffffff;
		hashValue *= 31;
		hashValue += tolower( *scan );
		++ scan;
		++ scanIndex;
	}

	return hashValue;
}


//-----------------------------------------------------------------------------------------------
CaseInsensitiveStringHashValue ComputeCaseInsensitiveHashForString( const std::string& stringWhoseCaseWillBeIgnoredForHashGeneration )
{
	CaseInsensitiveStringHashValue hashValue = 0;
	std::string::const_iterator charIter = stringWhoseCaseWillBeIgnoredForHashGeneration.begin();
	std::string::const_iterator charsEnd = stringWhoseCaseWillBeIgnoredForHashGeneration.end();
	while( charIter != charsEnd )
	{
		hashValue &= 0x07ffffff;
		hashValue *= 31;
		hashValue += tolower( *charIter );
		++ charIter;
	}

	return hashValue;
}


//-----------------------------------------------------------------------------------------------
CaseSensitiveStringHashValue ComputeCaseSensitiveHashForString( const char* stringWhoseCaseWillBeUsedForHashGeneration )
{
	CaseSensitiveStringHashValue hashValue = 0;
	const char* scan = stringWhoseCaseWillBeUsedForHashGeneration;
	while( *scan != '\0' )
	{
		hashValue &= 0x07ffffff;
		hashValue *= 31;
		hashValue += *scan;
		++ scan;
	}

	return hashValue;
}


//-----------------------------------------------------------------------------------------------
CaseSensitiveStringHashValue ComputeCaseSensitiveHashForString( const char* stringWhoseCaseWillBeUsedForHashGeneration, unsigned int stringLength )
{
	CaseSensitiveStringHashValue hashValue = 0;
	const char* scan = stringWhoseCaseWillBeUsedForHashGeneration;
	unsigned int scanIndex = 0;
	while( scanIndex < stringLength )
	{
		hashValue &= 0x07ffffff;
		hashValue *= 31;
		hashValue += *scan;
		++ scan;
		++ scanIndex;
	}

	return hashValue;
}


//-----------------------------------------------------------------------------------------------
CaseSensitiveStringHashValue ComputeCaseSensitiveHashForString( const std::string& stringWhoseCaseWillBeUsedForHashGeneration )
{
	CaseSensitiveStringHashValue hashValue = 0;
	std::string::const_iterator charIter = stringWhoseCaseWillBeUsedForHashGeneration.begin();
	std::string::const_iterator charsEnd = stringWhoseCaseWillBeUsedForHashGeneration.end();
	while( charIter != charsEnd )
	{
		hashValue &= 0x07ffffff;
		hashValue *= 31;
		hashValue += *charIter;
		++ charIter;
	}

	return hashValue;
}


//-----------------------------------------------------------------------------------------------
GenericHashValue ComputeHashForBuffer( const unsigned char* buffer, size_t bufferSizeInBytes )
{
	GenericHashValue hashValue = 0;
	const unsigned char* scan = buffer;
	size_t numBytesScanned = 0;
	while( numBytesScanned < bufferSizeInBytes )
	{
		hashValue &= 0x07ffffff;
		hashValue *= 31;
		hashValue += *scan;
		++ scan;
		++ numBytesScanned;
	}

	return hashValue;
}


//-----------------------------------------------------------------------------------------------
// File I/O utilities
//
char* LoadTextFileToNewBuffer( const char* textFilePath )
{
	FILE* fileHandle = nullptr;
	char* newTextBuffer = nullptr;
	const errno_t errorCode = fopen_s( &fileHandle, textFilePath, "rb" );
	if( !errorCode )
	{
		fseek( fileHandle, 0, SEEK_END );
		int fileSizeInBytes = (int) ftell( fileHandle );
		rewind( fileHandle );

		newTextBuffer = new char[ fileSizeInBytes + 1 ];
		fread( newTextBuffer, 1, fileSizeInBytes, fileHandle );
		fclose( fileHandle );
		newTextBuffer[ fileSizeInBytes ] = '\0';
	}

	return newTextBuffer;
}


//-----------------------------------------------------------------------------------------------
// Console utilities
//

//-----------------------------------------------------------------------------------------------
void ConsolePrintf( const char* messageFormat, ... )
{
#if defined( GYPSY_ENABLE_CONSOLE )
	if( theConsole )
	{
		va_list args;
		va_start( args, messageFormat );
		theConsole->PrintfV( Rgba::WHITE, messageFormat, args );
		va_end( args );
	}
#else
	UNUSED( messageFormat );
#endif
}


//-----------------------------------------------------------------------------------------------
void ConsolePrintf( const Rgba& color, const char* messageFormat, ... )
{
#if defined( GYPSY_ENABLE_CONSOLE )
	if( theConsole )
	{
		va_list args;
		va_start( args, messageFormat );
		theConsole->PrintfV( color, messageFormat, args );
		va_end( args );
	}
#else
	UNUSED( color );
	UNUSED( messageFormat );
#endif
}






