/*
	Trig Wars.
	Copyright (c) 2008, Darragh Coy.
	All rights reserved.
	Original work(s), source code & author(s) details available at: http://www.darraghcoy.com

	Redistribution and use in source and binary forms, with or without modification, are permitted 
	provided that the following conditions are met:

		* 	Redistributions of source code must retain the above copyright notice, this list of 
			conditions and the following disclaimer.
		* 	Redistributions in binary form must reproduce the above copyright notice, this list of 
			conditions and the following disclaimer in the documentation.
		* 	Neither the name of the author nor the names of contributors may be used to endorse or 
			promote products derived from this software without specific prior written permission.
		* 	The use of any third party libraries, modules or data files provided with the source 
			form of the program must follow and respect the individual licensing terms of these 
			components. You do not have permission to modify or circumvent the licensing terms for 
			provided third party components unless the third party licenses specifically allow this. 
			If you do not agree to the terms of provided third party components then you do not have 
			permission to use them in your derived work(s).
		* 	The terms of this license do not apply to separate (but related) distributions if supplied 
			by the author(s), only to the contents of this source distribution, derived works, and the 
			binaries produced by either modified or unmodified versions of the source code. The terms 
			of this license do not also apply to any third party libraries or components provided with 
			the source distribution. Such third party elements are licensed under their own separate 
			agreements.

	THIS SOFTWARE IS PROVIDED BY DARRAGH COY ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
	ARE DISCLAIMED. IN NO EVENT SHALL DARRAGH COY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
	OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
	OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
	IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

//=====================================================================================================================
/// @brief		Static class containing helper functions for operating on strings
//=====================================================================================================================

#include "Precompiled.h"

using namespace Game;
using namespace std;

//=====================================================================================================================
/// @brief		Tells if the given string is numeric and contains only numbers. Spaces are not allowed.
/// @param		s - string to test
/// @returns	True if the string is a number
/// @pre		The given string must not be null
//=====================================================================================================================

bool String::IsNumeric( const char * s )
{
    // Can't be null
    
    ENSURE( s != 0 , "The given string must not be null" );

    // If string is empty then return false:

    if ( * s == 0 ) return false;

    //-----------------------------------------------------------------------------------------------------------------
    // Walk through the string: look for any chars out of the ordinary
    //-----------------------------------------------------------------------------------------------------------------

    // Found the decimal dot ? can only be one dot in a proper number !

    bool foundDot = false;

    // Walk the string:

    for ( const char * i = s ; * i ; ++i )
    {
        // See if this character is a number character:

        if ( * i < '0' || * i > '9' )
        {
            // Not a number: investigate further..

            if ( * i == '.' )
            {
                // Found a dot: ok if we have not already found one

                if ( foundDot == false )
                {
                    // One dot is allowed: ok

                    foundDot = true;
                }
                else
                {
                    // Can't have two dots: not a valid number

                    return false;
                }
            }
            else if ( * i == '-' )
            {
                // Found a minus: must be at the start of the string!

                if ( s != i ) return false;
            }
            else
            {
                // Not a valid number: return false!

                return false;
            }
        }
    }

    // If we have reached this point then it is ok: string is a number

    return true;
}

//=====================================================================================================================
/// @brief		Tells if the given string is a numeric integer. Spaces are not allowed.
/// @param		s - string to test
/// @returns	True if the string is an integer
/// @pre		The given string must not be null
//=====================================================================================================================

bool String::IsInteger( const char * s )
{
    // Can't be null
    
    ENSURE( s != 0 , "The given string must not be null" );

    // If string is empty then return false:

    if ( * s == 0 ) return false;

    //-----------------------------------------------------------------------------------------------------------------
    // Walk through the string: look for any chars out of the ordinary
    //-----------------------------------------------------------------------------------------------------------------

    for ( const char * i = s ; * i ; ++i )
    {
        // See if this character is a number character:

        if ( * i < '0' || * i > '9' )
        {
            // Not a number charcter: see if it is a minus

            if ( * i == '-' )
            {
                // Minus: must be at the start of the string

                if ( s != i ) return false;
            }
            else
            {
                // Not a valid character: return false

                return false;
            }
        }
    }

    // If we have reached this point then it is ok: string is an integer number

    return true;
}

//=====================================================================================================================
/// @brief		Tells if the given string is a boolean of the form 'true' or 'false'. Spaces are not allowed.
///				The test is case insensitive.
/// @param		s - string to test
/// @returns	True if the string is a boolean
/// @pre		The given string must not be null
//=====================================================================================================================

bool String::IsBoolean( const char * s )
{
    // Can't be null
    
    ENSURE( s != 0 , "The given string must not be null" );

    // Return false on zero lenght string:

    if ( * s == 0 ) return false;
    
    // Get the length of the string:
    
    unsigned length = strlen(s);

    // Make an uppercase copy of the string:

    char * s_upper = new char[ length + 1 ]; strcpy_s(s_upper,length+1,s);

    // Make the copy upper case:

    ToUpper(s_upper);

    // Now see if it equals 'TRUE' or 'FALSE'

    if ( strcmp(s_upper,"FALSE") == 0 || strcmp(s_upper,"TRUE") == 0 )
    {
        // String is boolean: cleanup and return true

        delete[] s_upper; return true;
    }
    else
    {
        // String not boolean: cleanup and return false

        delete[] s_upper; return false;
    }
}

//=====================================================================================================================
/// @brief		Makes the given string uppercase
/// @param		s - string to make uppercase
/// @pre		The given string must not be null
//=====================================================================================================================

void String::ToUpper( char * s )
{
    // Can't be null
    
    ENSURE( s != 0 , "The given string must not be null" );

    // Walk through the string:

    while ( * s )
    {
        // See if this character is lower case:

        if ( * s >= 'a' && * s <= 'z' )
        {
            // Lower case: make upper case

            * s -= 32;
        }

        // Next character

        s++;
    }
}

//=====================================================================================================================
/// @brief		Makes the given string lowercase
/// @param		s - string to make lowercase
/// @pre		The given string must not be null
//=====================================================================================================================

void String::ToLower( char * s )
{
    // Can't be null
    
    ENSURE( s != 0 , "The given string must not be null" );

    // Walk through the string:

    while ( * s )
    {
        // See if this character is upper case:

        if ( * s >= 'A' && * s <= 'Z' )
        {
            // Upper case: make lower case

            * s += 32;
        }

        // Next character

        s++;
    }
}

//=====================================================================================================================
/// @brief		Converts the given string into a boolean
/// @param		s - string to convert
/// @param		default_value - value to return on failure
/// @returns	Boolean value of the string
/// @pre		The given string must not be null
//=====================================================================================================================

bool String::ToBool( const char * s , bool default_value )
{
    // Can't be null
    
    ENSURE( s != 0 , "The given string must not be null" );
    
    // See if the string is a valid boolean string:

    if ( IsBoolean(s) )
    {
		// Get the length of the string:
	    
		unsigned length = strlen(s);

		// Make an uppercase copy of the string:

		char * s_upper = new char[ length + 1 ]; strcpy_s(s_upper,length+1,s);    
    
        // See if it is true or false:

        if ( strcmp( s_upper , "TRUE" ) == 0 )
        {
			delete[] s_upper; return true;
		}
		else 
		{
			delete[] s_upper; return false;
		}
    }
    else
    {
        // Not a valid boolean string. return default value

        return default_value;
    }
}

//=====================================================================================================================
/// @brief		Converts the given string into a boolean, throws a std::exception on error
/// @param		s - string to convert
/// @returns	Boolean value of the string
/// @pre		The given string must not be null
//=====================================================================================================================

bool String::ToBoolCritical( const char * s )
{
    // Can't be null
    
    ENSURE( s != 0 , "The given string must not be null" );
    
    // See if the string is a valid boolean string:

    if ( IsBoolean(s) )
    {
		// Get the length of the string:
	    
		unsigned length = strlen(s);

		// Make an uppercase copy of the string:

		char * s_upper = new char[ length + 1 ]; strcpy_s(s_upper,length+1,s);    
    
        // See if it is true or false:

        if ( strcmp( s_upper , "TRUE" ) == 0 )
        {
			delete[] s_upper; return true;
		}
		else 
		{
			delete[] s_upper; return false;
		}
    }
    else
    {
        // Not a valid boolean string throw an exception

        throw exception("Boolean string conversion failed due to invalid string format");
    }
}

//=====================================================================================================================
/// @brief		Converts the given string into a boolean, throws a std::exception on failure
/// @param		s - string to convert
/// @param		default_value - value to return on failure
/// @returns	Boolean value of the string
/// @pre		The given string must not be null
//=====================================================================================================================

int String::ToInt( const char * s , int default_value )
{
    // Can't be null
    
    ENSURE( s != 0 , "The given string must not be null" );

    // Make sure it is a valid integer string:

    if ( IsInteger(s) )
    {
        // Valid integer convert to an int and return:

        return atoi(s);
    }
    else
    {
        // Not a valid integer: return default value

        return default_value;
    }
}

//=====================================================================================================================
/// @brief		Converts the given string into a boolean
/// @param		s - string to convert
/// @returns	Boolean value of the string
/// @pre		The given string must not be null
//=====================================================================================================================

int String::ToIntCritical( const char * s )
{
    // Can't be null
    
    ENSURE( s != 0 , "The given string must not be null" );

    // Make sure it is a valid integer string:

    if ( IsInteger(s) )
    {
        // Valid integer convert to an int and return:

        return atoi(s);
    }
    else
    {
        // Not a valid integer: throw an exception

        throw exception("Integer string conversion failed due to invalid string format");
    }
}

//=====================================================================================================================
/// @brief		Converts the given string into a floating point decimal, returns the given default value on failure
/// @param		s - string to convert
/// @param		default_value - default value to return on failure
/// @returns	Boolean value of the string
/// @pre		The given string must not be null
//=====================================================================================================================

float String::ToFloat( const char * s , float default_value )
{
    // Can't be null
    
    ENSURE( s != 0 , "The given string must not be null" );
    
    // Make sure it is a valid number string:

    if ( IsNumeric(s) )
    {
        // Valid integer convert to an int and return:

        return float(atof(s));
    }
    else
    {
        // Not a valid number: throw an exception

        return default_value;
    }
}

//=====================================================================================================================
/// @brief		Converts the given string into a floating point decimal
/// @param		s - string to convert
/// @returns	Boolean value of the string
/// @pre		The given string must not be null
//=====================================================================================================================

float String::ToFloatCritical( const char * s )
{
    // Can't be null
    
    ENSURE( s != 0 , "The given string must not be null" );
    
    // Make sure it is a valid number string:

    if ( IsNumeric(s) )
    {
        // Valid integer convert to an int and return:

        return float(atof(s));
    }
    else
    {
        // Not a valid number: just return the default value

        throw exception("floating point string conversion failed due to invalid string format");
    }
}

//=====================================================================================================================
/// @brief		Converts the given boolean to a string
/// @param		value - value to convert
/// @returns	string version of the value
//=====================================================================================================================

std::string String::ToString( bool value )
{
	// Do the conversion:
	
	if ( value ) return "true"; else return "false";
}

//=====================================================================================================================
/// @brief		Converts the given integer to a string
/// @param		value - value to convert
/// @returns	string version of the value
//=====================================================================================================================

std::string String::ToString( int value )
{
	// Do the conversion:
	
	char buf[64]; sprintf_s( buf , 63 , "%i" , value );
	
	// Return it:
	
	return buf;
}

//=====================================================================================================================
/// @brief		Converts the given floating point decimal to a string
/// @param		value - value to convert
/// @returns	string version of the value
//=====================================================================================================================

std::string String::ToString( float value )
{
	// Do the conversion:
	
	char buf[64]; sprintf_s( buf , 63 , "%f" , value );
	
	// Return it:
	
	return buf;	
}

//=====================================================================================================================
/// @brief		Tells if the given variable name is valid according the C/C++ languages
/// @param		name to check for correctness 
/// @returns	True if the variable name is valid
/// @pre		The given string must not be null
//=====================================================================================================================

bool String::IsValidVariableName( const char * name )
{
    // Can't be null
    
    ENSURE( name != 0 , "The given string must not be null" );
    
    // Get name length:
    
    int len = strlen(name);
    
    // If zero length then it is invalid:
    
    if ( len <= 0 ) return false;
    
    // The first character must be a letter or underscore:
    
    {
		// Get:
		
		char first = name[0];
    
		// Make sure in range:
		
		if 
		(
			( ! ( first >= 'a' && first <= 'z' ) )
			&&
			( ! ( first >= 'A' && first <= 'Z' ) )
			&&
			( first != '_' )
		)
		{
			// Invalid
			
			return false;
		}
    }
    
    // Run through the name:
    
    for ( int i = 1 ; i < len ; ++i )
    {
		// Get this character:
		
		char c = name[i];
    
		// Make sure is valid:
		
		if 
		(
			( ! ( c >= 'a' && c <= 'z' ) )
			&&
			( ! ( c >= 'A' && c <= 'Z' ) )
			&&
			( ! ( c >= '0' && c <= '9' ) )			
			&&
			( c != '_' )
		)
		{
			// Invalid
			
			return false;
		}
    }
    
    // If we got to here then all is ok:
    
    return true;
}

//=====================================================================================================================
/// @brief		Splits a string on the specified character saving resultant strings to the given list
/// @param		text - text to split up
/// @param		c - character to split on
/// @param		results - a list where to save the results of the split
/// @pre		Given string must not be null
//=====================================================================================================================

void String::Split( const char * text , char c , std::list<std::string> & results )
{
	// Make sure ok:

	ENSURE( text != 0 , "Given string must not be null" )
	
	// Get the length of the string:
	
	int len = strlen(text);
	
	// Continue until we have reached the end:
	
	int current = 0;
	
	// Go through the string:
	
	while ( current < len )
	{
		// Look for the next instance of the given character:
		
		int found_index = -1;
		
		// Do it:
		
		for ( int i = current ; i < len ; i++ )
		{
			if ( text[i] == c )
			{
				found_index = i; break;
			}			
		}
		
		// See if found:
		
		if ( found_index == -1 )
		{		
			// Give the rest of the string:
			
			results.push_back( std::string(text + current) );
			
			// Done with this string:
			
			current = len;
		}
		else
		{
			// Not found: see if the index is the current character
			
			if ( found_index == current ) 
			{
				// Just move onto the next character
				
				current++;
			}
			else
			{
				// Calculate how many characters to take:
				
				int num_chars = found_index - current;
				
				// Make a buffer to hold the substring:
				
				char * buffer = new char[num_chars+1];
				
				// Copy the substring into the buffer:
				
				for ( int i = current ; i < found_index ; i++ )
				{
					buffer[i-current] = text[i];
				}
				
				// Null terminate:
				
				buffer[num_chars] = 0;
				
				// Now put the substring into the list:
				
				results.push_back(buffer);
				
				// Clear up buffer
				
				delete[] buffer;
				
				// Move past the character found:
				
				current = found_index + 1;
			}			
		}
	}
}
