//////////////////////////////////////////////////////////////////////////////
//
// Asura_String.cpp
//
// Created by Morgan Parry.
//
// Copyright (c) 2002 Rebellion. All rights reserved.
//
// This code is partly based on IBM's Unicode library. Head over to
// http://www.ibm.com/software/globalization/icu/ for further details.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Core/Asura_String.h"

// Stdlib independent.
#include <ctype.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <wchar.h>

// Engine independent.
#include "Core/Asura_Maths.h"
#include "Debugging/Asura_Assert.h"

// M A C R O S ///////////////////////////////////////////////////////////////

// (MP - 04.08.2002) I would obviously much rather this was a templated
// function, as almost everything else is below. However, we need to be able
// to use these for case labels, forcing the following unpleasant solution.
#define GET_CHAR( CHAR_TYPE, c ) (static_cast<CHAR_TYPE>(L##c))

#ifdef ASURA_PLATFORM_VSPRINTF_USES_VA_ARG
    #define SKIP_ARG( args, type ) { }
#else
    // Since we don't use the value, cast to void to prevent warnings.
    #define SKIP_ARG( args, type ) { static_cast<void>(va_arg( args, type )); }
#endif

// T Y P E S /////////////////////////////////////////////////////////////////

template< typename CHAR_TYPE >
struct FmtSpecInfo
{
    
    // C O N S T A N T S /////////////////////////////////////////////////////

    enum
    {
        DEFAULT_WIDTH_OR_PRECISION = -1,
    };

    // D A T A ///////////////////////////////////////////////////////////////

    CHAR_TYPE xSpec;

    int iPrecision;
    int iWidth;

    bool bAlt;           // '#' flag.
    bool bSpaceForSign;  // Space flag.
    bool bLeftAlign;     // '-' flag.
    bool bShowSign;      // '+' flag.
    bool bPadWithZeroes; // '0' flag.
    bool bIsLongDouble;  // 'L' flag.
    bool bIsShort;       // 'h' flag.
    bool bIsLong;        // 'l' flag.
    bool bIsLongLong;    // 'll' flag.

};
// End struct FmtSpecInfo.

// C O N S T A N T S /////////////////////////////////////////////////////////

static const u_int LENGTH_OF_NULL_DESCRIPTION_STRING              = 6;
static const u_int NUM_CHARS_TO_ALLOW_IN_INTERNAL_VSPRINTF_BUFFER = 64;
static const u_int NUM_CHARS_TO_ALLOW_FOR_FMT_SPEC_COPY           = 16;

// P R O T O T Y P E S   A N D   I N L I N E   F U N C T I O N S /////////////

// Generic implementation for single->single or wide->wide.
template< typename CHAR_TYPE_OUT, typename CHAR_TYPE_IN >
static void ConvertInputCharToOutputChar( CHAR_TYPE_OUT* pxOut, const CHAR_TYPE_IN* pxIn )
{
    *pxOut = *pxIn;
}

// Wide->single specialisation.
//template<>
static void ConvertInputCharToOutputChar( Asura_Char* pcOut, const Asura_UnicodeChar* pwIn )
{
    *pcOut = ( *pwIn <= 255 ? static_cast<Asura_Char>(*pwIn) : '?' );
}

// Single->wide specialisation.
//template<>
static void ConvertInputCharToOutputChar( Asura_UnicodeChar* pwOut, const Asura_Char* pcIn )
{
    *pwOut = static_cast<Asura_UnicodeChar>( *pcIn & 0xff );
}

template< typename CHAR_TYPE >
static inline void GetNarrowCopyOfFmtSpec( Asura_Char* pxDst, const CHAR_TYPE* pxFmtSpec, u_int uNumChars )
{
    Asura_Assert( uNumChars < NUM_CHARS_TO_ALLOW_FOR_FMT_SPEC_COPY, "Need a bigger buffer for fmt spec copy." );
    if( uNumChars >= NUM_CHARS_TO_ALLOW_FOR_FMT_SPEC_COPY )
    {
        uNumChars = NUM_CHARS_TO_ALLOW_FOR_FMT_SPEC_COPY - 1;
    }

    while( uNumChars-- )
    {
        *pxDst++ = static_cast<char>( *pxFmtSpec++ );
    }
    *pxDst++ = '\0';
}

//template< typename CHAR_TYPE >
//static inline void GetNullDescriptionString( const CHAR_TYPE** ppxNullDesc );

//template<>
static inline void GetNullDescriptionString( const Asura_Char** ppcNullDesc )
{
    *ppcNullDesc = "[NULL]";
}

//template<>
static inline void GetNullDescriptionString( const Asura_UnicodeChar** ppwNullDesc )
{
    *ppwNullDesc = GET_CHAR(const Asura_UnicodeChar*,"[NULL]");
}

template< typename CHAR_TYPE >
static inline bool IsDigit( CHAR_TYPE c )
{
    return( GET_CHAR(CHAR_TYPE, '0') <= c && c <= GET_CHAR(CHAR_TYPE, '9') );
}

template< typename CHAR_TYPE_OUT, typename CHAR_TYPE_IN >
static void OutputStr( CHAR_TYPE_OUT** ppxOut, u_int uMaxChars, const FmtSpecInfo<CHAR_TYPE_OUT>& xSpecInfo, const CHAR_TYPE_IN* pxData, u_int uLen );

template< typename CHAR_TYPE >
static void OutputUsingVsprintf( CHAR_TYPE** ppxOut,
                                 u_int uMaxChars,
                                 const FmtSpecInfo<CHAR_TYPE>& xSpecInfo,
                                 va_list xArgs,
                                 const CHAR_TYPE* pxFmtSpec,
                                 u_int uNumCharsInFmtSpec );

template< typename CHAR_TYPE >
static void ParseFmtSpec( const CHAR_TYPE* pxSpec, va_list xArgs,
                          FmtSpecInfo<CHAR_TYPE>* pxSpecInfo, u_int* puNumCharsInSpec, bool* pbNeedsWidthParam, bool* pbNeedsPrecisionParam );

template< typename CHAR_TYPE >
static inline u_int StringCopy( CHAR_TYPE* pxDest, const CHAR_TYPE* pxSrc, u_int uMax = UINT_MAX )
{
    u_int uNumLeft = uMax;
    while( uNumLeft )
    {
        --uNumLeft;
        if( (*pxDest++ = *pxSrc++) == GET_CHAR(CHAR_TYPE, '\0') )
        {
            break;
        }
    }

    return( uMax - uNumLeft );
}

// This version is necessary for copying from our internal buffer in the
// Unicode version.
static inline u_int StringCopy( Asura_UnicodeChar* pxDest, const Asura_Char* pxSrc, u_int uMax = UINT_MAX )
{
    u_int uNumLeft = uMax;
    while( uNumLeft )
    {
        --uNumLeft;
        if( (*pxDest++ = *pxSrc++) == GET_CHAR(const Asura_UnicodeChar, '\0') )
        {
            break;
        }
    }

    return( uMax - uNumLeft );
}

// F U N C T I O N   I M P L E M E N T A T I O N S ///////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     InternalPrintfToBfr().
// PARAMETERS   pxBfr     - Output buffer.
//              pxFormat  - Format string.
//              uMaxChars - Maximum characters that may be stored in output
//                          buffer; this should include a slot for the null
//                          terminator if that's desired.
//              bForceNul - Guarantees a nul terminator is written.
//              xArgs     - List of arguments.
// RETURNS      Number of characters stored in output buffer, not including
//              the nul terminator.
// DESCRIPTION  Templated implementation of a printf() style function that
//              takes a va_list instead of the actual arguments and also
//              permits the caller to limit the number of characters output,
//              allowing more robust use of fixed-size buffers.
// AUTHOR       Morgan Parry - 04.08.2002.
//
//////////////////////////////////////////////////////////////////////////////
template< typename CHAR_TYPE, typename OTHER_CHAR_TYPE >
static u_int InternalPrintfToBfr( CHAR_TYPE* pxBfr, const CHAR_TYPE* pxFormat, u_int uMaxChars, bool bForceNul, va_list xArgs )
{
	Asura_Assert(pxBfr && pxFormat, "Null pointer passed to InternalPrintfToBfr");
	if(!pxBfr || !pxFormat)
	{
		return 0;
	}
    FmtSpecInfo<CHAR_TYPE> xSpecInfo;

    CHAR_TYPE* pxOut       = &pxBfr[0];
    const CHAR_TYPE* pxFmt = &pxFormat[0];

    u_int uNumCharsLeft = uMaxChars;
    while( uNumCharsLeft )
    {
        const CHAR_TYPE* const pxFmtStartThisIteration = pxFmt;

        // Find the next % character.
        while( *pxFmt != GET_CHAR(CHAR_TYPE, '\0') && *pxFmt != GET_CHAR(CHAR_TYPE, '%') )
        {
            ++pxFmt;
        }

        // Copy any characters before the %.
        if( pxFmt > pxFmtStartThisIteration )
        {
            // Determine how many we can copy.
            u_int uNumToCopy = Asura_Maths::Min( static_cast<u_int>(pxFmt - pxFmtStartThisIteration), uNumCharsLeft );
            u_int uNumCopied = StringCopy( pxOut, pxFmtStartThisIteration, uNumToCopy );

            // Update stats.
            {
                Asura_Assert( uNumCopied <= uNumCharsLeft, "" );
                pxOut         += uNumCopied;
                uNumCharsLeft -= uNumCopied;
            }
        }

        // Stop if we've reached the end of the string or run out of output
        // buffer.
        if( !*pxFmt || !uNumCharsLeft )
        {
            break;
        }

        u_int			uNumCharsInSpecification;
		bool bNeedsWidthParam(false);
		bool bNeedsPrecisionParam(false);

        // Parse the specifier.
        {
            ParseFmtSpec( pxFmt, xArgs,
                          &xSpecInfo, &uNumCharsInSpecification,
						  &bNeedsWidthParam, &bNeedsPrecisionParam );

        }

        // Output.
        {
            CHAR_TYPE* const pxOutBeforeOutput = pxOut;

            switch( xSpecInfo.xSpec )
            {
                case GET_CHAR(CHAR_TYPE, 'd'):
                case GET_CHAR(CHAR_TYPE, 'i'):
                case GET_CHAR(CHAR_TYPE, 'o'):
                case GET_CHAR(CHAR_TYPE, 'u'):
                case GET_CHAR(CHAR_TYPE, 'x'):
                case GET_CHAR(CHAR_TYPE, 'X'):
				{
                    OutputUsingVsprintf( &pxOut, uNumCharsLeft, xSpecInfo, xArgs, pxFmt, uNumCharsInSpecification );
					if (bNeedsWidthParam)
					{
                        SKIP_ARG( xArgs, int );
					}
					if (bNeedsPrecisionParam)
					{
                        SKIP_ARG( xArgs, int );
					}
                    if( xSpecInfo.bIsLong )
                    {
                        SKIP_ARG( xArgs, long );
                    }
#ifdef ASURA_PLATFORM_HAS_LONG_LONG_TYPE
                    else if( xSpecInfo.bIsLongLong )
                    {
                        SKIP_ARG( xArgs, long long );
                    }
#endif
                    else
                    {
                        SKIP_ARG( xArgs, int );
                    }
                    break;
				}
                case GET_CHAR(CHAR_TYPE, 'p'):
				{
                    OutputUsingVsprintf( &pxOut, uNumCharsLeft, xSpecInfo, xArgs, pxFmt, uNumCharsInSpecification );
					if (bNeedsWidthParam)
					{
                        SKIP_ARG( xArgs, int );
					}
					if (bNeedsPrecisionParam)
					{
                        SKIP_ARG( xArgs, int );
					}
                    SKIP_ARG( xArgs, void* );
                    break;
				}
                case GET_CHAR(CHAR_TYPE, 'e'):
                case GET_CHAR(CHAR_TYPE, 'E'):
                case GET_CHAR(CHAR_TYPE, 'f'):
                case GET_CHAR(CHAR_TYPE, 'g'):
                case GET_CHAR(CHAR_TYPE, 'G'):
				{
                    OutputUsingVsprintf( &pxOut, uNumCharsLeft, xSpecInfo, xArgs, pxFmt, uNumCharsInSpecification );
					if (bNeedsWidthParam)
					{
                        SKIP_ARG( xArgs, int );
					}
					if (bNeedsPrecisionParam)
					{
                        SKIP_ARG( xArgs, int );
					}
                    if( xSpecInfo.bIsLongDouble )
                    {
                        SKIP_ARG( xArgs, long double );
                    }
                    else
                    {
                        SKIP_ARG( xArgs, double );
                    }
                    break;
				}
				default://everything that doesn't use OutputUsingVsprintf
				{
					if (bNeedsWidthParam)
					{
						xSpecInfo.iWidth = va_arg( xArgs, int );
					}
					if (bNeedsPrecisionParam)
					{
						xSpecInfo.iPrecision = va_arg( xArgs, int );
					}
					switch( xSpecInfo.xSpec )
					{

						case GET_CHAR(CHAR_TYPE, 'c'):
						{
							CHAR_TYPE xChar = static_cast<CHAR_TYPE>( va_arg(xArgs, int) );
							OutputStr( &pxOut, uNumCharsLeft, xSpecInfo, &xChar, 1 );
							break;
						}

						case GET_CHAR(CHAR_TYPE, 'C'):
						{
							OTHER_CHAR_TYPE xChar = static_cast<OTHER_CHAR_TYPE>( va_arg(xArgs, int) );
							OutputStr( &pxOut, uNumCharsLeft, xSpecInfo, &xChar, 1 );
							break;
						}

						case GET_CHAR(CHAR_TYPE, 's'):
						{
							OutputStr( &pxOut, uNumCharsLeft, xSpecInfo, va_arg(xArgs, const CHAR_TYPE*), UINT_MAX );
							break;
						}
						case GET_CHAR(CHAR_TYPE, 'S'):
						{
							OutputStr( &pxOut, uNumCharsLeft, xSpecInfo, va_arg(xArgs, const OTHER_CHAR_TYPE*), UINT_MAX );
							break;
						}
						case GET_CHAR(CHAR_TYPE, 'n'):
						{
							// Special case: output the number of characters written
							// to the output so far.}
							int* piCount = va_arg( xArgs, int* );
							if( piCount )
							{
								*piCount = uMaxChars - uNumCharsLeft;
							}
							break;
						}

						case GET_CHAR(CHAR_TYPE, '%'):
						{
							// Note that we know it's okay to copy at least one character
							// here because we checked after searching for the percent
							// character up above.
							*pxOut++ = GET_CHAR( CHAR_TYPE, '%' );
							break;
						}
						default:
						{
							// If unrecognised, simply echo the format specification
							// (or as much of it as we can fit in.)
							pxOut += StringCopy( pxOut, pxFmt, Asura_Maths::Min(uNumCharsInSpecification, uNumCharsLeft) );
							break;
						}
					}
				}
            }
            // End switch( xSpecInfo.xSpec ).

            // Update num characters used.
            {
                u_int uNumCharsUsed = pxOut - pxOutBeforeOutput;
                Asura_Assert( uNumCharsUsed <= uNumCharsLeft, "" );
                uNumCharsLeft -= uNumCharsUsed;
            }
        }
        // End format spec output.

        // Skip over specification in format string.
        pxFmt += uNumCharsInSpecification;
    }
    // End while( uNumCharsLeft ).

    // Calculate the number of characters before attempting to append a nul
    // terminator because we don't count that.
    u_int uNumCharsWritten = uMaxChars - uNumCharsLeft;

    // Terminate the buffer, if there's room or if we've asked to force it.
    if( uNumCharsLeft || bForceNul )
    {
        if( !uNumCharsLeft && uNumCharsWritten )
        {
            // Overwrite the last character if we have to force it.
            --uNumCharsWritten;
            --pxOut;
        }
        *pxOut++ = GET_CHAR( CHAR_TYPE, '\0' );
    }

    // Return number of characters written.
    return uNumCharsWritten;
}
// End function InternalPrintfToBfr().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Asura_PrintfToBfr().
// PARAMETERS   szBfr     - Destination buffer.
//              szFormat  - Format for output string.
//              xArgs     - Arguments to format string.
//              uMaxChars - Max characters that may be output to buffer, 
//                          including space for nul terminator if one is
//                          required.
//              bForceNul - True iff the output string should always be nul-
//                          terminated regardless of whether or not the
//                          actual output string has filled the buffer.
// RETURNS      Number of characters output, excluding nul terminator.
// DESCRIPTION  Essentially sprintf() except with an optional limit on the
//              number of characters output and the requirement that the
//              caller deals with the variable number of arguments.
// AUTHOR       MP - 11.11.2002.
//
//////////////////////////////////////////////////////////////////////////////
u_int Asura_PrintfToBfr( Asura_Char* szBfr, const Asura_Char* szFormat, va_list xArgs, u_int uMaxChars, bool bForceNul )
{
    return InternalPrintfToBfr<Asura_Char, Asura_UnicodeChar>( szBfr, szFormat, uMaxChars, bForceNul, xArgs );
}
// End function Asura_PrintfToBfr().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Asura_PrintfToBfr().
// DESCRIPTION  See above.
//
//////////////////////////////////////////////////////////////////////////////
u_int Asura_PrintfToBfr( Asura_UnicodeChar* wszBfr, const Asura_UnicodeChar* wszFormat, va_list xArgs, u_int uMaxChars, bool bForceNul )
{
    return InternalPrintfToBfr<Asura_UnicodeChar, Asura_Char>( wszBfr, wszFormat, uMaxChars, bForceNul, xArgs );
}
// End function Asura_PrintfToBfr().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Asura_PrintfToBfr().
// DESCRIPTION  See above; identical except it accepts variable arguments
//              instead of requiring the caller to deal with them.
//
//////////////////////////////////////////////////////////////////////////////
u_int Asura_PrintfToBfr( Asura_Char* szBfr, const Asura_Char* szFormat, u_int uMaxChars /*= UINT_MAX*/, bool bForceNul /*= true*/, ... )
{
	va_list xArgs;
	va_start( xArgs, bForceNul );
    u_int uNumChars = InternalPrintfToBfr<Asura_Char, Asura_UnicodeChar>( szBfr, szFormat, uMaxChars, bForceNul, xArgs );
    va_end( xArgs );
    return uNumChars;
}
// End function Asura_PrintfToBfr().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Asura_PrintfToBfr().
// DESCRIPTION  See above; identical except it accepts variable arguments
//              instead of requiring the caller to deal with them.
//
//////////////////////////////////////////////////////////////////////////////
u_int Asura_PrintfToBfr( Asura_UnicodeChar* wszBfr, const Asura_UnicodeChar* wszFormat, u_int uMaxChars /*= UINT_MAX*/, bool bForceNul /*= true*/, ... )
{
	va_list xArgs;
	va_start( xArgs, bForceNul );
    u_int uNumChars = InternalPrintfToBfr<Asura_UnicodeChar, Asura_Char>( wszBfr, wszFormat, uMaxChars, bForceNul, xArgs );
    va_end( xArgs );
    return uNumChars;
}
// End function Asura_PrintfToBfr().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     OutputStr().
// PARAMETERS   ppxOut    - Pointer to output pointer.
//              uMaxChars - Max chars that should be written.
//              xSpecInfo - Details of format specification.
//              pxData    - Pointer to input string.
//              uLen      - Length of string, excluding nul terminator.
//                          Pass UINT_MAX (default) to deduce this from
//                          position of nul terminator.
// RETURNS      None.
// DESCRIPTION  Outputs a string according to a format specification,
//              updating the specified output pointer accordingly.
// AUTHOR       Morgan Parry - 05.08.2002.
//
//////////////////////////////////////////////////////////////////////////////
template< typename CHAR_TYPE_OUT, typename CHAR_TYPE_IN >
static void OutputStr( CHAR_TYPE_OUT** ppxOut, u_int uMaxChars, const FmtSpecInfo<CHAR_TYPE_OUT>& xSpecInfo, const CHAR_TYPE_IN* pxData, u_int uLen )
{
    CHAR_TYPE_OUT* pxOut = *ppxOut;
    u_int uCharsLeft     = uMaxChars;

    int iWidth     = xSpecInfo.iWidth;
    int iPrecision = xSpecInfo.iPrecision;

    // Use defaults?
    {
        if( FmtSpecInfo<CHAR_TYPE_OUT>::DEFAULT_WIDTH_OR_PRECISION == iWidth )
        {
            // Default is not to pad at all.
            iWidth = 0;
        }
        else  
        {
            Asura_Assert( iWidth >= 0, "" );
        }

        if( FmtSpecInfo<CHAR_TYPE_OUT>::DEFAULT_WIDTH_OR_PRECISION == iPrecision )
        {
            // Default is to copy up to nul terminator.
            iPrecision = INT_MAX;
        }
        else
        {
            Asura_Assert( iPrecision >= 0, "" );
        }
    }

    // Determine how much of the string we should use.
    u_int uNumCharsToUse = 0;
    if( pxData )
    {
        const CHAR_TYPE_IN* pxSrc = pxData;
        while( uNumCharsToUse < uLen && iPrecision-- && *pxSrc++ != GET_CHAR(CHAR_TYPE_IN, '\0') )
        {
            ++uNumCharsToUse;
        }
    }
    else
    {
        uNumCharsToUse = LENGTH_OF_NULL_DESCRIPTION_STRING;
    }

    // Determine how much padding will be required.
    u_int uPadding = 0;
    if( uNumCharsToUse < static_cast<u_int>(iWidth) )
    {
        uPadding = iWidth - uNumCharsToUse;
    }

    // By default, pad on the left.
    if( !xSpecInfo.bLeftAlign && !xSpecInfo.bPadWithZeroes )
    {
        for( ; uPadding && uCharsLeft; --uPadding, --uCharsLeft )
        {
            *pxOut++ = GET_CHAR( CHAR_TYPE_OUT, ' ' );
        }
    }
   
    // Output string data.
    {
        const CHAR_TYPE_IN* pxSrc = pxData;
        if( !pxSrc ) { GetNullDescriptionString( &pxSrc ); }
        
        for( ; uNumCharsToUse && uCharsLeft; --uNumCharsToUse, --uCharsLeft )
        {
            ConvertInputCharToOutputChar( pxOut++, pxSrc++ );
        }
    }

    // Pad on right if required.
    if( xSpecInfo.bLeftAlign || xSpecInfo.bPadWithZeroes )
    {
        CHAR_TYPE_OUT xPadChar = ( xSpecInfo.bPadWithZeroes ? GET_CHAR(CHAR_TYPE_OUT, '0') : GET_CHAR(CHAR_TYPE_OUT, ' ') );
        for( ; uPadding && uCharsLeft; --uPadding, --uCharsLeft )
        {
            *pxOut++ = xPadChar;
        }
    }

    // Update caller's output ptr.
    *ppxOut = pxOut;
}
// End function OutputStr().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     OutputUsingVsprintf().
// PARAMETERS   ppxOut    - Pointer to output pointer.
//              uMaxChars - Max chars that should be written.
//              xSpecInfo - Details of format specification.
//              pxArgs    - Pointer to argument list.
// RETURNS      None.
// DESCRIPTION  Uses stdlib vsprintf() to write to a temporary narrow buffer
//              and then copies to our output buffer, limiting the output as
//              required. Adjusts arg lists for optional width or precision
//              args but *does not* adjust for the data itself; that must be
//              done externally.
// AUTHOR       Morgan Parry - 05.08.2002.
//
//////////////////////////////////////////////////////////////////////////////
template< typename CHAR_TYPE >
void OutputUsingVsprintf( CHAR_TYPE** ppxOut,
                          u_int uMaxChars,
                          const FmtSpecInfo<CHAR_TYPE>& xSpecInfo,
                          va_list xArgs,
                          const CHAR_TYPE* pxFmtSpec,
                          u_int uNumCharsInFmtSpec )
{
    // We need a temporary buffer for using stdlib's sprintf(). We'll ensure
    // this will be long enough for any integer or floating point value. We
    // will then deal with things that may be longer - such as strings -
    // manually.
    char szTempBuffer[NUM_CHARS_TO_ALLOW_IN_INTERNAL_VSPRINTF_BUFFER];
	Asura_Char szNarrowFmtSpec[NUM_CHARS_TO_ALLOW_FOR_FMT_SPEC_COPY];

	GetNarrowCopyOfFmtSpec(szNarrowFmtSpec, pxFmtSpec, uNumCharsInFmtSpec);
    int iNumCharsWritten = vsprintf( szTempBuffer, szNarrowFmtSpec, xArgs );

    // Copy as much of the buffer as we have space for.
    if( iNumCharsWritten > 0 )
    {
        CHAR_TYPE* pxOut = *ppxOut;
        pxOut += StringCopy( pxOut, szTempBuffer, Asura_Maths::Min(static_cast<u_int>(iNumCharsWritten), uMaxChars) );
        *ppxOut = pxOut;
    }
}
// End function OutputUsingVsprintf().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     ParseFmtSpec().
// PARAMETERS   pxSpec     - Pointer to beginning of format specification.
//              pxSpecInfo - Output information struct detailing the format
//                           specification.
// RETURNS      Number of characters in format specification.
// DESCRIPTION  Parses the specified format specification, extracting all
//              information necessary to prepare the corresponding output. In
//              particular, this involves filling in all the fields of the
//              specified FmtSpecInfo structure.
// AUTHOR       Morgan Parry - 04.08.2002.
//
//////////////////////////////////////////////////////////////////////////////
template< typename CHAR_TYPE >
static void ParseFmtSpec( const CHAR_TYPE* pxSpec, va_list xArgs,
                          FmtSpecInfo<CHAR_TYPE>* pxSpecInfo, u_int* puNumCharsInSpec,
						  bool* pbNeedsWidthParam, bool* pbNeedsPrecisionParam  )
{
    // Initialize specification data to default values.
    {
        pxSpecInfo->iPrecision     = FmtSpecInfo<CHAR_TYPE>::DEFAULT_WIDTH_OR_PRECISION;
        pxSpecInfo->iWidth         = FmtSpecInfo<CHAR_TYPE>::DEFAULT_WIDTH_OR_PRECISION;
        pxSpecInfo->xSpec          = GET_CHAR( CHAR_TYPE, '\0' );
        pxSpecInfo->bAlt           = false;
        pxSpecInfo->bSpaceForSign  = false;
        pxSpecInfo->bLeftAlign     = false;
        pxSpecInfo->bShowSign      = false;
        pxSpecInfo->bPadWithZeroes = false;
        pxSpecInfo->bIsLongDouble  = false;
        pxSpecInfo->bIsShort       = false;
        pxSpecInfo->bIsLong        = false;
        pxSpecInfo->bIsLongLong    = false;
    }

    const CHAR_TYPE* pxNext = &pxSpec[0];

    // Skip over the initial '%' character.
    Asura_Assert( *pxNext == GET_CHAR(CHAR_TYPE, '%'), "" );
    ++pxNext;

    // Get any flags.
    {
        bool bWasFlag = true;
        while( bWasFlag )
        {
            const CHAR_TYPE xNextChar = *pxNext;
            switch( xNextChar )
            {
                case GET_CHAR(CHAR_TYPE, '#'):
                    pxSpecInfo->bAlt = true;
                    break;

                case GET_CHAR(CHAR_TYPE, '-'):
                    // Left justify.
                    pxSpecInfo->bLeftAlign = true;
                    break;

                case GET_CHAR(CHAR_TYPE, '+'):
                    // Show + or - sign as necessary.
                    pxSpecInfo->bShowSign = true;
                    break;

                case GET_CHAR(CHAR_TYPE, ' '):
                    // If value is positive and we haven't been asked to display
                    // the sign, use a space.
                    pxSpecInfo->bSpaceForSign = true;
                    break;

                case GET_CHAR(CHAR_TYPE, '0'):
                    // Pad with leading zeroes.
                    pxSpecInfo->bPadWithZeroes = true;
                    break;

                default:
                    bWasFlag = false;
                    break;
            }

            if( bWasFlag )
            {
                ++pxNext;
            }
        }
    }
    // End flag processing.

    // Get the width. If the width is just '*', we take it from the next
    // argument instead of from the format string.
    if( *pxNext == GET_CHAR(CHAR_TYPE, '*') )
    {
		//tell caller to extract the param if they need it
		*pbNeedsWidthParam = true;
		
        // Swallow the '*' character.
        ++pxNext;
    }
    else if( IsDigit(*pxNext) )
    {
        pxSpecInfo->iWidth = 0;
        do
        {
            pxSpecInfo->iWidth = pxSpecInfo->iWidth * 10 + (*pxNext - GET_CHAR(CHAR_TYPE, '0'));
        }
        while( IsDigit(*++pxNext) );
    }
    // End width processing.

    // Get the precision.
    if( *pxNext == GET_CHAR(CHAR_TYPE, '.') )
    {
        // Skip the '.' character.
        ++pxNext;

        // If the precision is just '*', we take it from the next argument
        // instead of from the format string.
        if( *pxNext == GET_CHAR(CHAR_TYPE, '*') )
        {
			//tell caller to extract the param if they need it
			*pbNeedsPrecisionParam = true;

            // Swallow the '*' character.
            ++pxNext;
        }
        else if( IsDigit(*pxNext) )
        {
            pxSpecInfo->iPrecision = 0;
            do
            {
                pxSpecInfo->iPrecision = pxSpecInfo->iPrecision * 10 + (*pxNext - GET_CHAR(CHAR_TYPE, '0'));
            }
            while( IsDigit(*++pxNext) );
        }
    }
    // End precision processing.

    // Get any modifiers.
    {
        bool bWasModifier = true;
        while( bWasModifier )
        {
            const CHAR_TYPE xNextChar = *pxNext;
            switch( xNextChar )
            {
                case GET_CHAR(CHAR_TYPE, 'h'):
                    pxSpecInfo->bIsShort = true;
                    break;

                case GET_CHAR(CHAR_TYPE, 'l'):
                    if( *(pxNext + 1) == GET_CHAR(CHAR_TYPE, 'l') )
                    {
                        ++pxNext;
#ifdef ASURA_PLATFORM_HAS_LONG_LONG_TYPE
                        pxSpecInfo->bIsLongLong = true;
#else
                        pxSpecInfo->bIsLong = true;
#endif
                    }
                    else
                    {
                        pxSpecInfo->bIsLong = true;
                    }
                    break;

                case GET_CHAR(CHAR_TYPE, 'L'):
                    pxSpecInfo->bIsLongDouble = true;
                    break;

                default:
                    bWasModifier = false;
                    break;
            }

            if( bWasModifier )
            {
                ++pxNext;
            }
        }
    }
    // End modifier processing.

    // Finally, get the specification letter.
    pxSpecInfo->xSpec = *pxNext++;

    *puNumCharsInSpec   = pxNext - pxSpec;
}
// End function ParseFmtSpec().

bool Asura_StringUtil::GetFloatValue( const Asura_Char* szToConvert, float* pfValue )
{
    Asura_Assert( szToConvert, "" );
    if( !szToConvert || !*szToConvert )
    {
        return false;
    }
    
	const Asura_Char* nptr = szToConvert;
	char c;              // current char
	float total = 0.0f;        // current total

	// skip whitespace
	while( isspace(*nptr) )
    {
		++nptr;
    }

	c = *nptr++;
	const char sign = c;           // save sign indication
	if (c == '-' || c == '+')
	{
		c = *nptr++;
	}

    if( !isdigit(c) && '.' != c ) { return false; }

	while( isdigit(c) )
	{
		total = 10.0f * total + (float)(c - '0');     // accumulate digit
		c = *nptr++;
	}

	// Fractional part?
    if( '.' == c )
    {
        const float ONE_TENTH = 1.0f / 10.0f;

        // (MP - 09.10.2002) TODO: Might want to add a cut-off here when
        // precision is lost.
	    float fOneOverDecimalPlace = ONE_TENTH; //used to handle decimal digits

        c = *nptr++;
	    while( isdigit(c) )
	    {
		    total += static_cast<float>(c - '0') * fOneOverDecimalPlace;     // accumulate digit
		    fOneOverDecimalPlace *= ONE_TENTH;
            c = *nptr++;
	    }
    }

    // Skip trailing 'f', if there is one.
    if( 'f' == c )
    {
        c = *nptr++;
    }

    // If there are more characters and the first one isn't whitespace then
    // this is invalid.
    if( c && !isspace(c) )
    {
        return false;
    }

    *pfValue = ( '-' == sign ? -total : total );
    return true;
}

bool Asura_StringUtil::GetIntValue( const Asura_Char* szToConvert, int* piValue, bool bForceHex )
{
    Asura_Assert( szToConvert, "" );
    if( !szToConvert || !*szToConvert )
    {
        return false;
    }

	const Asura_Char* nptr = szToConvert;
	char c;              // current char
	int total = 0;         // current total

	// skip whitespace
	while( isspace(*nptr) )
    {
		++nptr;
    }

	c = *nptr++;
	const char sign = c;           // save sign indication
	if (c == '-' || c == '+')
	{
		c = *nptr++;
	}

	// check for hexadecimal 
	bool bHex = bForceHex;
	if( c == '0' )
    {
    	char c2 = *(nptr);
        if( c2 )
        {
            if( c2 == 'X' || c2 == 'x' )
	        {
		        bHex = true;
		        c = *++nptr;
                ++nptr;
	        }
            else if( isdigit(c2) && !bForceHex )
            {
                // Octal values are dangerous and unsupported.
                return false;
            }
        }
    }

	if( bHex )
	{	
		const char* pcStartPos = nptr;

		while( isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') )
		{ 
			if(c >= 'a' && c <= 'f')
			{
				total = 16 * total + ((c - 'a') + 10);				
			}
			else if(c >= 'A' && c <= 'F')
			{
				total = 16 * total + ((c - 'A') + 10);				
			}	
			else
			{	
				total = 16 * total + (c - '0');     // accumulate digit
			}	
			c = *nptr++;
		}

		// if the end position and the start position are the same then return false ('0x' is not a valid number!)
		if( nptr == pcStartPos ) return false;
	}
	else
	{
		if(!isdigit(c)) return false;

		while( isdigit(c) )
		{
			total = 10 * total + (c - '0');     // accumulate digit
			c = *nptr++;
		}
	}

    // If there are more characters and the first one isn't whitespace then
    // this is invalid.
    if( c && !isspace(c) )
    {
        return false;
    }

    *piValue = ( '-' == sign ? -total : total );
    return true;
}

bool Asura_StringUtil::GetBoolValue( const Asura_Char* szToConvert, bool* pbValue )
{
    Asura_Assert( szToConvert, "" );
    if( !szToConvert || !*szToConvert )
    {
        return false;
    }

    // Skip leading whitespace.
    while( *szToConvert && isspace(*szToConvert) ) { ++szToConvert; }

    if( !stricmp(szToConvert, "1") || !stricmp(szToConvert, "true") )
    {
        *pbValue = true;
        return true;
    }
    else if( !stricmp(szToConvert, "0") || !stricmp(szToConvert, "false") )
    {
        *pbValue = false;
        return true;
    }
    else
    {
        return false;
    }
}


// Richard May - 26/08/2003 10:42:08 - Unicode versions of the above functions
bool Asura_StringUtil::GetBoolValue( const Asura_UnicodeChar* szToConvert, bool* pbValue )
{
    Asura_Assert( szToConvert, "" );
    if( !szToConvert || !*szToConvert )
    {
        return false;
    }
	
    // Skip leading whitespace.
    while( *szToConvert && iswspace(*szToConvert) ) { ++szToConvert; }
	
    if( !wcsicmp(szToConvert, GET_CHAR(const Asura_UnicodeChar*,"1"))
        || !wcsicmp(szToConvert,GET_CHAR(const Asura_UnicodeChar*,"true")) )
    {
        *pbValue = true;
        return true;
    }
    else if( !wcsicmp(szToConvert,GET_CHAR(const Asura_UnicodeChar*,"0"))
        || !wcsicmp(szToConvert,GET_CHAR(const Asura_UnicodeChar*,"false")) )
    {
        *pbValue = false;
        return true;
    }
    else
    {
        return false;
    }	
}

bool Asura_StringUtil::GetFloatValue( const Asura_UnicodeChar* szToConvert, float* pfValue )
{
    Asura_Assert( szToConvert, "" );
    if( !szToConvert || !*szToConvert )
    {
        return false;
    }
    
	const Asura_UnicodeChar* nptr = szToConvert;
	Asura_UnicodeChar c;              // current char
	float total = 0.0f;        // current total
	
	// skip whitespace
	while( iswspace(*nptr) )
    {
		++nptr;
    }
	
	c = *nptr++;
	const Asura_UnicodeChar sign = c;           // save sign indication
	if (c == GET_CHAR(const Asura_UnicodeChar,'-')
        || c == GET_CHAR(const Asura_UnicodeChar,'+'))
	{
		c = *nptr++;
	}
	
    if( !iswdigit(c) && c != GET_CHAR(const Asura_UnicodeChar,'.'))
    { return false; }
	
	while( iswdigit(c) )
	{
		total = 10.0f * total
            + static_cast<float>(c - GET_CHAR(const Asura_UnicodeChar,'0'));
		c = *nptr++;
	}
	
	// Fractional part?
    if( c == GET_CHAR(const Asura_UnicodeChar,'.') )
    {
        const float ONE_TENTH = 1.0f / 10.0f;
		
        // (MP - 09.10.2002) TODO: Might want to add a cut-off here when
        // precision is lost.
		float fOneOverDecimalPlace = ONE_TENTH; //used to handle decimal digits
		
        c = *nptr++;
		while( iswdigit(c) )
		{
			total += fOneOverDecimalPlace
                * static_cast<float>(c - GET_CHAR(const Asura_UnicodeChar,'0'));
			fOneOverDecimalPlace *= ONE_TENTH;
            c = *nptr++;
		}
    }
	
    // Skip trailing 'f', if there is one.
    if( c == GET_CHAR(const Asura_UnicodeChar,'f') )
    {
        c = *nptr++;
    }
	
    // If there are more characters and the first one isn't whitespace then
    // this is invalid.
    if( c && !iswspace(c) )
    {
        return false;
    }
	
    *pfValue = ( GET_CHAR(const Asura_UnicodeChar,'-') == sign ? -total : total );
    return true;
}



bool Asura_StringUtil::GetIntValue( const Asura_UnicodeChar* szToConvert, int* piValue, bool bForceHex )
{
    Asura_Assert( szToConvert, "" );
    if( !szToConvert || !*szToConvert )
    {
        return false;
    }
	
	const Asura_UnicodeChar* nptr = szToConvert;
	Asura_UnicodeChar c;   // current char
	int total = 0;         // current total
	
	// skip whitespace
	while( iswspace(*nptr) )
    {
		++nptr;
    }
	
	c = *nptr++;
	const Asura_UnicodeChar sign = c;           // save sign indication
	if (c == GET_CHAR(const Asura_UnicodeChar,'-')
        || c == GET_CHAR(const Asura_UnicodeChar,'+'))
	{
		c = *nptr++;
	}
	
	// check for hexadecimal 
	bool bHex = bForceHex;
	if( c == GET_CHAR(const Asura_UnicodeChar,'0') )
    {
		Asura_UnicodeChar c2 = *(nptr);
        if( c2 )
        {
            if( c2 == GET_CHAR(const Asura_UnicodeChar,'X')
                || c2 == GET_CHAR(const Asura_UnicodeChar,'x') )
			{
				bHex = true;
				c = *++nptr;
                ++nptr;
			}
            else if( iswdigit(c2) && !bForceHex )
            {
                // Octal values are dangerous and unsupported.
                return false;
            }
        }
    }
		
	if( bHex )
	{
		const Asura_UnicodeChar* pwcStartPosition = nptr;

		while( iswdigit(c)
            || (c >= GET_CHAR(const Asura_UnicodeChar,'a')
                && c <= GET_CHAR(const Asura_UnicodeChar,'f'))
            || (c >= GET_CHAR(const Asura_UnicodeChar,'A')
                && c <= GET_CHAR(const Asura_UnicodeChar,'F')) )
		{ 
			if(c >= GET_CHAR(const Asura_UnicodeChar,'a')
                && c <= GET_CHAR(const Asura_UnicodeChar,'f'))
			{
				total = 16 * total + ((c - GET_CHAR(const Asura_UnicodeChar,'a')) + 10);
			}
			else if(c >= GET_CHAR(const Asura_UnicodeChar,'A')
                && c <= GET_CHAR(const Asura_UnicodeChar,'F'))
			{
				total = 16 * total + ((c - GET_CHAR(const Asura_UnicodeChar,'A')) + 10);				
			}	
			else
			{	
				total = 16 * total + (c - GET_CHAR(const Asura_UnicodeChar,'0'));
			}	
			c = *nptr++;
		}

		// if we've not read any characters, return false ('0x' is not valid!)
		if( nptr == pwcStartPosition ) return false;		
	}
	else
	{
		if(!iswdigit(c)) return false;

		while( iswdigit(c) )
		{
			total = 10 * total + (c - GET_CHAR(const Asura_UnicodeChar,'0'));
			c = *nptr++;
		}
	}
	
    // If there are more characters and the first one isn't whitespace then
    // this is invalid.
    if( c && !iswspace(c) )
    {
        return false;
    }
	
    *piValue = ( GET_CHAR(const Asura_UnicodeChar,'-') == sign ? -total : total );
    return true;
}

void Asura_StringUtil::ToString( int iValue, Asura_String& xString )
{
    int iSign = 1;
    if ( iValue < 0 )
    {
        iSign  = -1;
        iValue = -iValue;
    }

    do
    {
        xString += static_cast< Asura_Char >( ( iValue % 10 ) + '0' );
    } while ( ( iValue /= 10 ) > 0 );

    if ( iSign < 0 )
    {
        xString += "-";
    }

    xString.Reverse();
}

void Asura_StringUtil::ToString( u_int uValue, Asura_String& xString )
{
    do
    {
        xString += static_cast< Asura_Char >( ( uValue % 10 ) + '0' );
    } while ( ( uValue /= 10 ) > 0 );

    xString.Reverse();
}

void Asura_StringUtil::ToString( float fValue, Asura_String& xString )
{
    enum { MAX_FLOAT_STRING_LENGTH = 32 };
    Asura_Char s_szBuffer[ MAX_FLOAT_STRING_LENGTH ];
    Asura_PrintfToBfr( s_szBuffer, "%.6f", MAX_FLOAT_STRING_LENGTH, true, Asura_Maths::FtoD(fValue) );
    xString = s_szBuffer;
}

void Asura_StringUtil::Reverse( Asura_Char* szString )
{
    int iStringLength = strlen( szString );

    Asura_Char* pcStart = szString;
    Asura_Char* pcEnd   = szString + iStringLength - 1;
    while ( pcStart < pcEnd )
    {
        const Asura_Char cTemp = *pcStart;
        *pcStart = *pcEnd;
        *pcEnd   = cTemp;
        ++pcStart;
        --pcEnd;
    }
}

Asura_Char* Asura_StringUtil::StripWhitespace( Asura_Char* szBfr, u_int uStripFlags )
{
    u_int uNewLen;
    const Asura_Char* szNewBfr = StripWhitespace( static_cast<const Asura_Char*>(szBfr), uStripFlags, &uNewLen );

    if( !szNewBfr ) { return NULL; }

    szBfr += (szNewBfr - static_cast<const Asura_Char*>(szBfr));
    szBfr[uNewLen] = '\0';
    return szBfr;
}

const Asura_Char* Asura_StringUtil::StripWhitespace( const Asura_Char* szBfr, u_int uStripFlags, u_int* puNewLen )
{
    if( !szBfr ) { return NULL; }

    if( uStripFlags & STRIP_LEADING )
    {
        while( *szBfr && isspace(*szBfr) ) { ++szBfr; }
    }

    const Asura_Char* szEnd = szBfr;
    while( *szEnd ) { ++szEnd; }

    if( uStripFlags & STRIP_TRAILING )
    {
        while( szEnd > szBfr && isspace(*(szEnd - 1)) ) { --szEnd; }
    }

    if( puNewLen ) { *puNewLen = szEnd - szBfr; }

    return szBfr;
}

Asura_UnicodeChar* Asura_StringUtil::StripWhitespace( Asura_UnicodeChar* wszBfr, u_int uStripFlags )
{
    u_int uNewLen;
    const Asura_UnicodeChar* wszNewBfr = StripWhitespace( static_cast<const Asura_UnicodeChar*>(wszBfr), uStripFlags, &uNewLen );

    if( !wszNewBfr ) { return NULL; }

    wszBfr += (wszNewBfr - static_cast<const Asura_UnicodeChar*>(wszBfr));
    wszBfr[uNewLen] = L'\0';
    return wszBfr;
}

const Asura_UnicodeChar* Asura_StringUtil::StripWhitespace( const Asura_UnicodeChar* wszBfr, u_int uStripFlags, u_int* puNewLen )
{
    if( !wszBfr ) { return NULL; }

    if( uStripFlags & STRIP_LEADING )
    {
        while( *wszBfr && iswspace(*wszBfr) ) { ++wszBfr; }
    }

    const Asura_UnicodeChar* wszEnd = wszBfr;
    while( *wszEnd ) { ++wszEnd; }

    if( uStripFlags & STRIP_TRAILING )
    {
        while( wszEnd > wszBfr && iswspace(*(wszEnd - 1)) ) { --wszEnd; }
    }

    if( puNewLen ) { *puNewLen = wszEnd - wszBfr; }

    return wszBfr;
}

Asura_Char* Asura_StringUtil::Duplicate( const Asura_Char* szBfr )
{
	// If this leaks, see comment in Asura_MemoryManagement_Core::GetExtraNodeInfo
	Asura_Char* szNew = ( szBfr ? new Asura_Char[strlen(szBfr) + 1] : NULL );
    if( szNew ) { strcpy( szNew, szBfr ); }
    return szNew;
}

Asura_UnicodeChar* Asura_StringUtil::Duplicate( const Asura_UnicodeChar* wszBfr )
{
	// If this leaks, see comment in Asura_MemoryManagement_Core::GetExtraNodeInfo
    Asura_UnicodeChar* wszNew = ( wszBfr ? new Asura_UnicodeChar[wcslen(wszBfr) + 1] : NULL );
    if( wszNew ) { wcscpy( wszNew, wszBfr ); }
    return wszNew;
}

Asura_Char* Asura_StringUtil::StripQuotes( Asura_Char* szBfr )
{
    u_int uNewLen;
    const Asura_Char* szNewBfr = StripQuotes( static_cast<const Asura_Char*>(szBfr), &uNewLen );

    if( !szNewBfr ) { return NULL; }

    szBfr += (szNewBfr - static_cast<const Asura_Char*>(szBfr));
    szBfr[uNewLen] = '\0';
    return szBfr;
}

const Asura_Char* Asura_StringUtil::StripQuotes( const Asura_Char* szBfr, u_int* puNewLen )
{
    u_int uLen = ( szBfr ? strlen(szBfr) : 0 );

    if( uLen >= 2 )
    {
        // Check there are quotes at either end. Assume whitespace will have
        // been removed externally
        if( '\"' == szBfr[0] && '\"' == szBfr[uLen - 1] )
        {
            // Check there's not a quote inbetween. We won't bother dealing
            // with more complex cases like that.
            if( strchr(&szBfr[1], '\"') == &szBfr[uLen - 1] )
            {
                ++szBfr;
                uLen -= 2;
            }
        }
    }

    if( puNewLen ) { *puNewLen = uLen; }

    return szBfr;
}

Asura_UnicodeChar* Asura_StringUtil::StripQuotes( Asura_UnicodeChar* wszBfr )
{
    u_int uNewLen;
    const Asura_UnicodeChar* wszNewBfr = StripQuotes( static_cast<const Asura_UnicodeChar*>(wszBfr), &uNewLen );

    if( !wszNewBfr ) { return NULL; }

    wszBfr += (wszNewBfr - static_cast<const Asura_UnicodeChar*>(wszBfr));
    wszBfr[uNewLen] = L'\0';
    return wszBfr;
}

const Asura_UnicodeChar* Asura_StringUtil::StripQuotes( const Asura_UnicodeChar* wszBfr, u_int* puNewLen )
{
    u_int uLen = ( wszBfr ? wcslen(wszBfr) : 0 );

    if( uLen >= 2 )
    {
        // Check there are quotes at either end. Assume whitespace will have
        // been removed externally
        if( L'\"' == wszBfr[0] && L'\"' == wszBfr[uLen - 1] )
        {
            // Check there's not a quote in between. We won't bother dealing
            // with more complex cases like that.
            if( wcschr(&wszBfr[1], '\"') == &wszBfr[uLen - 1] )
            {
                ++wszBfr;
                uLen -= 2;
            }
        }
    }

    if( puNewLen ) { *puNewLen = uLen; }

    return wszBfr;
}

bool Asura_StringUtil::StripExtension(Asura_Char* szString)
{
	if(szString == NULL)
	{
		return(false);
	}
	else
	{
		for(int i = (strlen(szString) - 1); i >= 0; i--)
		{
			if(szString[i] == '.')
			{
				szString[i]	= '\0';
				return(true);
			}
		}

		return(false);
	}

}

Asura_Char* Asura_StringUtil::Replace( const Asura_Char* szOriginal, const Asura_Char* szToReplace, const Asura_Char* szReplacementText )
{
    if( !szOriginal ) { return NULL; }

    u_int uToReplaceLen = ( szToReplace ? strlen(szToReplace) : 0 );
    if( !uToReplaceLen ) { return Duplicate( szOriginal ); }

    const Asura_Char* szSearch = strstr( szOriginal, szToReplace );
    if( !szSearch ) { return Duplicate( szOriginal ); }

    return Replace( szOriginal, szSearch - szOriginal, uToReplaceLen, szReplacementText );
}

Asura_Char* Asura_StringUtil::Replace( const Asura_Char* szOriginal, u_int uToReplaceStart, u_int uToReplaceLen, const Asura_Char* szReplacementText )
{
    if( !szOriginal ) { return NULL; }

    u_int uReplacementLen = ( szReplacementText ? strlen(szReplacementText) : 0 );
    u_int uOriginalLen = strlen( szOriginal );

    Asura_Assert( uToReplaceStart + uToReplaceLen <= uOriginalLen, "" );

    u_int uCharsBefore = uToReplaceStart;
    u_int uStartOfCharsAfter = uToReplaceStart + uToReplaceLen;
    u_int uCharsAfter = uOriginalLen - uStartOfCharsAfter;

    u_int uNewLen = uCharsBefore + uReplacementLen + uCharsAfter;
    u_int uNewSize = uNewLen + 1;
    Asura_Char* szNewBfr = new Asura_Char[uNewSize];
    *szNewBfr = '\0';

    if( uCharsBefore ) { strncat( szNewBfr, szOriginal, uCharsBefore ); }

    if( uReplacementLen ) { strcat( szNewBfr, szReplacementText ); }

    if( uCharsAfter ) { strncat( szNewBfr, &szOriginal[uStartOfCharsAfter], uCharsAfter ); }

    return szNewBfr;
}

// JAT 080206 - Replace all instances of the specified character with a different character e.g. change all '/' -> '\'
void Asura_StringUtil::ReplaceAllChars( char* const szString, const Asura_Char cSearch, const Asura_Char cReplace )
{
	int i = 0;
	while ( szString[i] )
	{
		if( szString[i] == cSearch )
		{
			szString[i] = cReplace;
		}
		i++;
	}
}

bool Asura_StringUtil::StartsWith( const Asura_Char* szString, const Asura_Char* szPrefix, bool bIgnoreCase )
{
    if( !szString || !szPrefix ) { return false; }
  	
    if( bIgnoreCase )
    {
        while( true )
        {
            Asura_Char cNextString = *szString++;
            Asura_Char cNextPrefix = *szPrefix++;

            if( isupper(cNextString) ) { cNextString = static_cast<Asura_Char>( tolower(cNextString) ); }
            if( isupper(cNextPrefix) ) { cNextPrefix = static_cast<Asura_Char>( tolower(cNextPrefix) ); }

            if( !cNextPrefix ) { break; }

            if( cNextString != cNextPrefix ) { return false; }
        }
    }
    else
    {
        while( true )
        {
            Asura_Char cNextString = *szString++;
            Asura_Char cNextPrefix = *szPrefix++;

            if( !cNextPrefix ) { break; }

            if( cNextString != cNextPrefix ) { return false; }
        }
    }

    return true;
}

bool Asura_StringUtil::EndsWith( const Asura_Char* szString, const Asura_Char* szSuffix, bool bIgnoreCase )
{
    if( !szString || !szSuffix ) { return false; }

    u_int uStringLen = strlen( szString );
    u_int uSuffixLen = strlen( szSuffix );

    if( uSuffixLen > uStringLen ) { return false; }

    const char* szOffsetString = szString + (uStringLen - uSuffixLen);
    return !( bIgnoreCase ? stricmp(szOffsetString, szSuffix) : strcmp(szOffsetString, szSuffix) );
}

bool Asura_StringUtil::CompareAgainstWildcardString( const Asura_Char* szString, const Asura_Char* szWildcard, bool bIgnoreCase )
{
    if( !szString || !szWildcard ) { return false; }

    int(*pfnCmp)(const char*, const char*, size_t) = ( bIgnoreCase ? strnicmp : strncmp );

    while( true )
    {
        // Find the next wildcard character.
        const Asura_Char* szNextWildcardChar = szWildcard;
        while( true )
        {
            Asura_Char cNext = *szNextWildcardChar;
            if( !cNext || cNext == '?' || cNext == '*' )
            {
                break;
            }
            else
            {
                ++szNextWildcardChar;
            }
        }

        // Compare everything before it.
        int iCount = szNextWildcardChar - szWildcard;
        if( iCount )
        {
            if( pfnCmp(szString, szWildcard, iCount) )
            {
                return false;
            }
            
            szString   += iCount;
            szWildcard += iCount;
        }

        switch( *szWildcard )
        {
            case '*':
            {
                // If the subsequent wildcard is also a *, we can avoid some
                // fairly hideous recursive behaviour below by just
                // disregarding the first one, since it's redundant.
                if( *(szWildcard + 1) == '*' )
                {
                    ++szWildcard;
                    break;
                }

                // Match 0 or more arbitrary characters, including nul
                // terminator.
                const Asura_Char* szRecursiveSearch = szString;
                do 
                {
                    if( CompareAgainstWildcardString(szRecursiveSearch, szWildcard + 1, bIgnoreCase) )
                    {
                        return true;
                    }
                }
                while( *szRecursiveSearch++ );
              
                return false;
            }

            case '?':
            {
                // Match precisely one arbitrary character, excluding nul
                // terminator.
                if( *szString )
                {
                    ++szString;
                    ++szWildcard;
                }
                else
                {
                    return false;
                }
                break;
            }

            case '\0':
            {
                // Match providing we've also consumed the entirety of the
                // string we're searching in.
                return !*szString;
            }
        }
    }
}


bool Asura_StringUtil::Contains( const Asura_Char* szString, const Asura_Char* szFindString, int* piPositionInString )
{
	if( !szString ) return false; // no string to check, return false
		
	const char* const szSubString = strstr( szString, szFindString );
	if( !szSubString ) return false; // not found, return false
	
	if( piPositionInString ) { *piPositionInString = (szSubString - szString); }

    return true;
}

bool Asura_StringUtil::Contains( const Asura_UnicodeChar* wszString, const Asura_UnicodeChar* wszFindString, int* piPositionInString )
{
	if( !wszString ) return false; // no string to check, return false

	const Asura_UnicodeChar* const wszSubString = wcsstr( wszString, wszFindString );
	if( !wszSubString ) return false; // not found, return false

	if( piPositionInString ) { *piPositionInString = (wszSubString - wszString); }

	return true;
}

void Asura_StringUtil::MakeLowercase( Asura_Char* szString )
{
    if( !szString ) { return; }

    for( ; ; ++szString )
    {
        Asura_Char cNext = *szString;

        if( !cNext ) { break; }

        if( isupper(cNext) ) { *szString = static_cast< Asura_Char >( tolower(cNext) ); }
    }
}


void Asura_StringUtil::MakeUppercase( Asura_Char* szString )
{
    if( !szString ) { return; }

    for( ; ; ++szString )
    {
        Asura_Char cNext = *szString;

        if( !cNext ) { break; }

        if( islower(cNext) ) { *szString = static_cast< Asura_Char >( toupper(cNext) ); }
    }
}

//
//	FindStringPartByIndex
//
bool Asura_StringUtil::FindStringPartByIndex(const Asura_UnicodeChar* wszString, const Asura_UnicodeChar wcSeparator, u_int uStringPartIndex, u_int& uStringPartStart, u_int& uStringPartLength)
{
	uStringPartStart = 0;
	uStringPartLength = 0;
	bool bFound = false;

	Asura_Assert(wszString != NULL, "Invalid string");
	if (wszString != NULL)
	{
		const u_int uLength = wcslen(wszString);
		u_int uIndex = 0;

		// Find the substring start
		while (uStringPartIndex > 0 && uIndex < uLength)
		{
			if (wszString[uIndex++] == wcSeparator)
			{
				--uStringPartIndex;
			}
		}

		if (uIndex < uLength)
		{
			// Find the substring length
			bFound = true;
			uStringPartStart = uIndex;

			while (uIndex < uLength && wszString[uIndex] != wcSeparator)
			{
				++uIndex;
				++uStringPartLength;
			}
		}

	}

	return bFound;
}

bool Asura_StringUtil::FindStringPartByIndex(const Asura_Char* szString, const Asura_Char cSeparator, u_int uStringPartIndex, u_int& uStringPartStart, u_int& uStringPartLength)
{
	uStringPartStart = 0;
	uStringPartLength = 0;
	bool bFound = false;

	Asura_Assert(szString != NULL, "Invalid string");
	if (szString != NULL)
	{
		const u_int uLength = strlen(szString);
		u_int uIndex = 0;

		// Find the substring start
		while (uStringPartIndex > 0 && uIndex < uLength)
		{
			if (szString[uIndex++] == cSeparator)
			{
				--uStringPartIndex;
			}
		}

		if (uIndex < uLength)
		{
			// Find the substring length
			bFound = true;
			uStringPartStart = uIndex;

			while (uIndex < uLength && szString[uIndex] != cSeparator)
			{
				++uIndex;
				++uStringPartLength;
			}
		}

	}

	return bFound;
}

//
//	CountInstancesOfCharacter
//
u_int Asura_StringUtil::CountInstancesOfCharacter(const Asura_UnicodeChar* wszString, const Asura_UnicodeChar wcCharacter)
{
	u_int uInstances = 0;
	u_int uIndex = 0;

	if (wszString != NULL)
	{
		while (wszString[uIndex] != 0)
		{
			if (wszString[uIndex++] == wcCharacter)
			{
				++uInstances;
			}
		}
	}

	return uInstances;
}

u_int Asura_StringUtil::CountInstancesOfCharacter(const Asura_Char* szString, const Asura_Char cCharacter)
{
	u_int uInstances = 0;
	u_int uIndex = 0;

	if (szString != NULL)
	{
		while (szString[uIndex] != 0)
		{
			if (szString[uIndex++] == cCharacter)
			{
				++uInstances;
			}
		}
	}

	return uInstances;
}


////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_UnicodeToUTF8
// Author       : Andy Tate
// Date         : 29-Apr-2005
//
// Description  : Converts the specified Unicode string into a UTF-8 string
//				  represented in ASCII.
//
// Parameters   : The source Unicode string and destination ASCII string.
//
// Return value : The number of characters in the output string.
////////////////////////////////////////////////////////////////////////////////

u_int Asura_UnicodeToUTF8 (Asura_UnicodeChar const* wszSource, Asura_Char* szDestination, int iDestLength /*= -1*/ )
{
	u_int	uTotalNumChars	= 0;

	if (wszSource == NULL || szDestination == NULL)	
	{ 
		if (szDestination)
		{
			szDestination[0] = '\0';
		}
		return 0;	
	}

	// Check each character within the Unicode string in-turn...

	u_int uSourcePos = 0;

	while(wszSource [uSourcePos]
		&& (iDestLength < 0 || (uTotalNumChars + 1) < static_cast<u_int>(iDestLength)))
	{
		Asura_UnicodeChar	wcCurrent	= wszSource [uSourcePos];


		// How big is the current value?

		if ((1 << 7) > wcCurrent)
		{
			// Single byte - simply output the truncated value...

			szDestination [uTotalNumChars++]	= static_cast<Asura_Char>(wcCurrent & 0x7F);
		}

		else if ((1 << 11) > wcCurrent)
		{
			// Double byte - split as 0x110nnnnn 0x10nnnnnn...

			if (iDestLength >= 0 && (uTotalNumChars + 2) >= static_cast<u_int>(iDestLength))
			{
				//not got room for two chars - terminate here
				szDestination [uTotalNumChars]	= '\0';

				// Return the total number of characters output...
				return	(uTotalNumChars);
			}

			szDestination [uTotalNumChars++]	= static_cast<Asura_Char>(0xC0 | ((wcCurrent >> 6) & 0x1F));
			szDestination [uTotalNumChars++]	= static_cast<Asura_Char>(0x80 | (wcCurrent & 0x3F));
		}

		else
		{
			// Triple byte - split as 0x1110nnnn 0x10nnnnnn 0x10nnnnnn...

			if (iDestLength >= 0 && (uTotalNumChars + 3) >= static_cast<u_int>(iDestLength))
			{
				//not got room for three chars - terminate here
				szDestination [uTotalNumChars]	= '\0';

				// Return the total number of characters output...
				return	(uTotalNumChars);
			}

			szDestination [uTotalNumChars++]	= static_cast<Asura_Char>(0xE0 | ((wcCurrent >> 12) & 0x3F));
			szDestination [uTotalNumChars++]	= static_cast<Asura_Char>(0x80 | ((wcCurrent >> 6) & 0x3F));
			szDestination [uTotalNumChars++]	= static_cast<Asura_Char>(0x80 | (wcCurrent & 0x3F));
		}
		++uSourcePos;
	}


	// Finally, terminate the output string...

	szDestination [uTotalNumChars]	= '\0';


	// Return the total number of characters output...

	return	(uTotalNumChars);
}

/// End of function 'Asura_UnicodeToUTF8'. ///

u_int Asura_UTF8ToUnicode( Asura_Char const* szSource, Asura_UnicodeChar* wszDestination, int iDestLength /*= -1*/ )
{
	u_int	uTotalNumChars	= 0;

	if (szSource == NULL || wszDestination == NULL)	
	{ 
		if (wszDestination) 
		{ 
			wszDestination[0] = L'\0';
		}
		return 0;	
	}

	const char* pcSource = szSource;
	// Check each character within the UTF8 string in-turn...

	while(*pcSource && (iDestLength < 0 || (uTotalNumChars + 1) < static_cast<u_int>(iDestLength)))
	{
		if((*pcSource & 0xE0) == 0xE0)
		{
			// First three bits are on
			wszDestination[uTotalNumChars]  = static_cast<Asura_UnicodeChar>(*pcSource++ & 0x0F) << 12;
			wszDestination[uTotalNumChars] += static_cast<Asura_UnicodeChar>(*pcSource++ & 0x3F) <<  6;
			wszDestination[uTotalNumChars] += static_cast<Asura_UnicodeChar>(*pcSource++) & 0x3F;
			++uTotalNumChars;
		}
		else if((*pcSource & 0xC0) == 0xC0)
		{
			// First two bits are on
			wszDestination[uTotalNumChars]  = static_cast<Asura_UnicodeChar>(*pcSource++ & 0x1F) << 6;
			wszDestination[uTotalNumChars] += static_cast<Asura_UnicodeChar>(*pcSource++) & 0x3F;
			++uTotalNumChars;
		}
		else
		{
			Asura_Assert(!(*pcSource & 0x80), "UTF8 decoding gone wrong somewhere");
			// Single byte only
			wszDestination[uTotalNumChars++] = static_cast<Asura_UnicodeChar>(*pcSource++);
		}
	}


	// Finally, terminate the output string...

	wszDestination [uTotalNumChars]	= L'\0';


	// Return the total number of characters output...

	return	(uTotalNumChars);
}

// Description - Re-formats string into a value filename.
//               Assumes the file name is an 'Asura_Files' relative path
//               Changes '/' to '\'
//               Remove multiple '\' characters
//               Remove '\' at the front of the file
void Asura_String::TidyFilename()
{
	if(!Buffer) return;
	
	// DLH - 30 April 2007 12:52:24 - Used to detect more than one '\' character
	bool bLastCharacterWasDirectoryDelimiter = false;
	
	//Overwrite the original buffer as we go , since we are only ever removing characters
	//RAR 05/10/2007
	const char* pcSrc = Buffer;
	char* pcDest = Buffer;

	//for( u_int uSrcIndex = 0; uSrcIndex < uSrcSize; uSrcIndex += 1)
	for(; *pcSrc; pcSrc++)
	{
		Asura_Char cSrcCharacter = *pcSrc;
		
		// DLH - 30 April 2007 12:53:13 - Replace the '/' with '\'
		if( '/' == cSrcCharacter)
		{
			cSrcCharacter = '\\';
		}
		
		if( '\\' == cSrcCharacter)
		{
			// DLH - 30 April 2007 13:04:33 - Remove Leading '\'
			if( pcSrc == Buffer)
			{
				bLastCharacterWasDirectoryDelimiter = true;
				continue;
			}
			
			// DLH - 30 April 2007 12:53:41 - Remove multiple '\' characters. Asumed Asura will never use \\servername
			if( true == bLastCharacterWasDirectoryDelimiter)
			{
				continue;
			}
			bLastCharacterWasDirectoryDelimiter = true;
		}
		else
		{
			bLastCharacterWasDirectoryDelimiter = false;
		}
		
		*pcDest++ = cSrcCharacter;
	}
	*pcDest++ = 0;

	//Correct the length
	LengthInCharacters = (pcDest - Buffer)-1;
	//Currently there isn't really support in Asura_String for having a buffer that has free space allocated that isn't in use,
	//and a number of the functions effectively assume that the memory used is one more than the length
	//RAR 05/10/2007
	MemoryAllocatedInBytes = LengthInCharacters + 1;
	
}

Asura_String operator+( const Asura_String& xLhs, const Asura_String& xRhs )
{
	Asura_String xResult( xLhs );
	xResult += xRhs;
	return xResult;
}


/// End of file. ///
