#include "global.h"
#include "RageUtil.h"
#include "RageMath.h"
#include "RageLog.h"
#include "RageFile.h"
#include "Foreach.h"
#include "LocalizedString.h"
#include "LuaBinding.h"
#include "LuaManager.h"
#include <float.h>

#include <numeric>
#include <ctime>
#include <sstream>
#include <map>
#include <sys/types.h>
#include <sys/stat.h>
#include <math.h>

bool HexToBinary(const RString&, RString&);
void utf8_sanitize(RString &);
void UnicodeUpperLower(wchar_t *, size_t, const unsigned char *);

RandomGen g_RandomNumberGenerator;

MersenneTwister::MersenneTwister( int iSeed ) : m_iNext(0)
{
	Reset( iSeed );
}

void MersenneTwister::Reset( int iSeed )
{
	if( iSeed == 0 )
		iSeed = time(NULL);

	m_Values[0] = iSeed;
	m_iNext = 0;
	for( int i = 1; i < 624; ++i )
		m_Values[i] = ((69069 * m_Values[i-1]) + 1) & 0xFFFFFFFF;

	GenerateValues();
}

void MersenneTwister::GenerateValues()
{
	static const unsigned mask[] = { 0, 0x9908B0DF };

	for( int i = 0; i < 227; ++i )
	{
		int iVal = (m_Values[i] & 0x80000000) | (m_Values[i+1] & 0x7FFFFFFF);
		int iNext = (i + 397);

		m_Values[i] = m_Values[iNext];
		m_Values[i] ^= (iVal >> 1);
		m_Values[i] ^= mask[iVal&1];
	}

	for( int i = 227; i < 623; ++i )
	{
		int iVal = (m_Values[i] & 0x80000000) | (m_Values[i+1] & 0x7FFFFFFF);
		int iNext = (i + 397) - 624;

		m_Values[i] = m_Values[iNext];
		m_Values[i] ^= (iVal >> 1);
		m_Values[i] ^= mask[iVal&1];
	}

	int iVal = (m_Values[623] & 0x80000000) + (m_Values[0] & 0x7FFFFFFF);
	int iNext = (623 + 397) - 624;
	m_Values[623] = m_Values[iNext] ^ (iVal>>1);
	m_Values[623] ^= mask[iVal&1];
}

int MersenneTwister::Temper( int iVal )
{
	iVal ^= (iVal >> 11);
	iVal ^= (iVal << 7) & 0x9D2C5680;
	iVal ^= (iVal << 15) & 0xEFC60000;
	iVal ^= (iVal >> 18);
	return iVal;
}

int MersenneTwister::operator()()
{
	if( m_iNext == 624 )
	{
		m_iNext = 0;
		GenerateValues();
	}

	return Temper( m_Values[m_iNext++] );
}

/* Extend MersenneTwister into Lua space. This is intended to replace
 * math.randomseed and math.random, so we conform to their behavior. */

namespace
{
	MersenneTwister g_LuaPRNG;

	/* To map from [0..2^31-1] to [0..1), we divide by 2^31. */
	const double DIVISOR = pow( double(2), double(31) );

	static int Seed( lua_State *L )
	{
		g_LuaPRNG.Reset( IArg(1) );
		return 0;
	}

	static int Random( lua_State *L )
	{
		switch( lua_gettop(L) )
		{
			/* [0..1) */
			case 0:
			{
				double r = double(g_LuaPRNG()) / DIVISOR;
				lua_pushnumber( L, r );
				return 1;
			}

			/* [1..u] */
			case 1:
			{
				int upper = IArg(1);
				luaL_argcheck( L, 1 <= upper, 1, "interval is empty" );
				lua_pushnumber( L, g_LuaPRNG(upper) + 1 );
				return 1;
			}
			/* [l..u] */
			case 2:
			{
				int lower = IArg(1);
				int upper = IArg(2);
				luaL_argcheck( L, lower < upper, 2, "interval is empty" );
				lua_pushnumber( L, (int(g_LuaPRNG()) % (upper-lower+1)) + lower );
				return 1;
			}

			/* wrong amount of arguments */
			default:
			{
				return luaL_error( L, "wrong number of arguments" );
			}
		}
	}

	const luaL_Reg MersenneTwisterTable[] =
	{
		LIST_METHOD( Seed ),
		LIST_METHOD( Random ),
		{ NULL, NULL }
	};
}

LUA_REGISTER_NAMESPACE( MersenneTwister );

void fapproach( float& val, float other_val, float to_move )
{
	ASSERT_M( to_move >= 0, ssprintf("to_move: %f < 0", to_move) );
	if( val == other_val )
		return;
	float fDelta = other_val - val;
	float fSign = fDelta / fabsf( fDelta );
	float fToMove = fSign*to_move;
	if( fabsf(fToMove) > fabsf(fDelta) )
		fToMove = fDelta;	// snap
	val += fToMove;
}

/* Return a positive x mod y. */
float fmodfp(float x, float y)
{
	x = fmodf(x, y);	/* x is [-y,y] */
	x += y;			/* x is [0,y*2] */
	x = fmodf(x, y);	/* x is [0,y] */
	return x;
}

int power_of_two( int iInput )
{
	int iValue = 1;

	while( iValue < iInput )
		iValue <<= 1;

	return iValue;
}

bool IsAnInt( const RString &s )
{
	if( !s.size() )
		return false;

	for( size_t i=0; i < s.size(); ++i )
		if( s[i] < '0' || s[i] > '9' )
			return false;

	return true;
}

bool IsHexVal( const RString &s )
{
	if( !s.size() )
		return false;

	for( size_t i=0; i < s.size(); ++i )
		if( !(s[i] >= '0' && s[i] <= '9') && 
			!(toupper(s[i]) >= 'A' && toupper(s[i]) <= 'F'))
			return false;

	return true;
}

RString BinaryToHex( const void *pData_, int iNumBytes )
{
	const unsigned char *pData = (const unsigned char *) pData_;
	RString s;
	for( int i=0; i<iNumBytes; i++ )
	{
		unsigned val = pData[i];
		s += ssprintf( "%02x", val );
	}
	return s;
}

RString BinaryToHex( const RString &sString )
{
	return BinaryToHex( sString.data(), sString.size() );
}

bool HexToBinary( const RString &s, unsigned char *stringOut )
{
	if( !IsHexVal(s) )
		return false;

	for( int i=0; true; i++ )
	{
		if( (int)s.size() <= i*2 )
			break;
		RString sByte = s.substr( i*2, 2 );

		uint8_t val = 0;
		if( sscanf( sByte, "%hhx", &val ) != 1 )
			return false;
		stringOut[i] = val;
	}
	return true;
}

bool HexToBinary( const RString &s, RString &sOut )
{
	sOut.resize(s.size() / 2);
	return HexToBinary(s, (unsigned char *) sOut.data());
}

float HHMMSSToSeconds( const RString &sHHMMSS )
{
	vector<RString> arrayBits;
	split( sHHMMSS, ":", arrayBits, false );

	while( arrayBits.size() < 3 )
		arrayBits.insert(arrayBits.begin(), "0" );	// pad missing bits

	float fSeconds = 0;
	fSeconds += StringToInt( arrayBits[0] ) * 60 * 60;
	fSeconds += StringToInt( arrayBits[1] ) * 60;
	fSeconds += StringToFloat( arrayBits[2] );

	return fSeconds;
}

RString SecondsToHHMMSS( float fSecs )
{
	const int iMinsDisplay = (int)fSecs/60;
	const int iSecsDisplay = (int)fSecs - iMinsDisplay*60;
	RString sReturn = ssprintf( "%02d:%02d:%02d", iMinsDisplay/60, iMinsDisplay%60, iSecsDisplay );
	return sReturn;
}

RString SecondsToMMSSMsMs( float fSecs )
{
	const int iMinsDisplay = (int)fSecs/60;
	const int iSecsDisplay = (int)fSecs - iMinsDisplay*60;
	const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 100 );
	RString sReturn = ssprintf( "%02d:%02d.%02d", iMinsDisplay, iSecsDisplay, min(99,iLeftoverDisplay) );
	return sReturn;
}

RString SecondsToMSSMsMs( float fSecs )
{
	const int iMinsDisplay = (int)fSecs/60;
	const int iSecsDisplay = (int)fSecs - iMinsDisplay*60;
	const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 100 );
	RString sReturn = ssprintf( "%01d:%02d.%02d", iMinsDisplay, iSecsDisplay, min(99,iLeftoverDisplay) );
	return sReturn;
}

RString SecondsToMMSSMsMsMs( float fSecs )
{
	const int iMinsDisplay = (int)fSecs/60;
	const int iSecsDisplay = (int)fSecs - iMinsDisplay*60;
	const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 1000 );
	RString sReturn = ssprintf( "%02d:%02d.%03d", iMinsDisplay, iSecsDisplay, min(999,iLeftoverDisplay) );
	return sReturn;
}

RString SecondsToMSS( float fSecs )
{
	const int iMinsDisplay = (int)fSecs/60;
	const int iSecsDisplay = (int)fSecs - iMinsDisplay*60;
	RString sReturn = ssprintf( "%01d:%02d", iMinsDisplay, iSecsDisplay);
	return sReturn;
}

RString SecondsToMMSS( float fSecs )
{
	const int iMinsDisplay = (int)fSecs/60;
	const int iSecsDisplay = (int)fSecs - iMinsDisplay*60;
	RString sReturn = ssprintf( "%02d:%02d", iMinsDisplay, iSecsDisplay);
	return sReturn;
}

RString PrettyPercent( float fNumerator, float fDenominator)
{
	return ssprintf("%0.2f%%",fNumerator/fDenominator*100);
}

RString Commify( int iNum ) 
{
	RString sNum = ssprintf("%d",iNum);
	return Commify( sNum );
}

RString Commify( RString sNum, RString sSeperator ) 
{
	RString sReturn;
	for( unsigned i=0; i<sNum.length(); i++ )
	{
		char cDigit = sNum[sNum.length()-1-i];
		if( i!=0 && i%3 == 0 )
			sReturn = sSeperator + sReturn;
		sReturn = cDigit + sReturn;
	}
	return sReturn;
}

static LocalizedString NUM_PREFIX	( "RageUtil", "NumPrefix" );
static LocalizedString NUM_ST		( "RageUtil", "NumSt" );
static LocalizedString NUM_ND		( "RageUtil", "NumNd" );
static LocalizedString NUM_RD		( "RageUtil", "NumRd" );
static LocalizedString NUM_TH		( "RageUtil", "NumTh" );
RString FormatNumberAndSuffix( int i )
{
	RString sSuffix;
	switch( i%10 )
	{
	case 1:		sSuffix = NUM_ST; break;
	case 2:		sSuffix = NUM_ND; break;
	case 3:		sSuffix = NUM_RD; break;
	default:	sSuffix = NUM_TH; break;
	}

	// "11th", "113th", etc.
	if( ((i%100) / 10) == 1 )
		sSuffix = NUM_TH;

	return NUM_PREFIX.GetValue() + ssprintf("%i", i) + sSuffix;
}

struct tm GetLocalTime()
{
	const time_t t = time(NULL);
	struct tm tm;
	localtime_r( &t, &tm );
	return tm;
}

RString ssprintf( const char *fmt, ...)
{
	va_list	va;
	va_start(va, fmt);
	return vssprintf(fmt, va);
}

#define FMT_BLOCK_SIZE		2048 // # of bytes to increment per try

RString vssprintf( const char *szFormat, va_list argList )
{
	RString sStr;

#if defined(WIN32) && !defined(__MINGW32__)
	char *pBuf = NULL;
	int iChars = 1;
	int iUsed = 0;
	int iTry = 0;

	do
	{
		// Grow more than linearly (e.g. 512, 1536, 3072, etc)
		iChars += iTry * FMT_BLOCK_SIZE;
		pBuf = (char*) _alloca( sizeof(char)*iChars );
		iUsed = vsnprintf( pBuf, iChars-1, szFormat, argList );
		++iTry;
	} while( iUsed < 0 );

	// assign whatever we managed to format
	sStr.assign( pBuf, iUsed );
#else
	static bool bExactSizeSupported;
	static bool bInitialized = false;
	if( !bInitialized )
	{
		/* Some systems return the actual size required when snprintf
		 * doesn't have enough space.  This lets us avoid wasting time
		 * iterating, and wasting memory. */
		char ignore;
		bExactSizeSupported = ( snprintf( &ignore, 0, "Hello World" ) == 11 );
		bInitialized = true;
	}

	if( bExactSizeSupported )
	{
		va_list tmp;
		va_copy( tmp, argList );
		char ignore;
		int iNeeded = vsnprintf( &ignore, 0, szFormat, tmp );
		va_end(tmp);

		char *buf = sStr.GetBuffer( iNeeded+1 );
		vsnprintf( buf, iNeeded+1, szFormat, argList );
		sStr.ReleaseBuffer( iNeeded );
		return sStr;
	}

	int iChars = FMT_BLOCK_SIZE;
	int iTry = 1;
	while( 1 )
	{
		// Grow more than linearly (e.g. 512, 1536, 3072, etc)
		char *buf = sStr.GetBuffer(iChars);
		int iUsed = vsnprintf(buf, iChars-1, szFormat, argList);

		if( iUsed == -1 )
		{
			iChars += ((iTry+1) * FMT_BLOCK_SIZE);
			sStr.ReleaseBuffer();
			++iTry;
			continue;
		}

		/* OK */
		sStr.ReleaseBuffer(iUsed);
		break;
	}
#endif
	return sStr;
}

/* Windows uses %I64i to format a 64-bit int, instead of %lli. Convert "a b %lli %-3llu c d"
 * to "a b %I64 %-3I64u c d". This assumes a well-formed format string; invalid format strings
 * should not crash, but the results are undefined. */
#if defined(WIN32)
RString ConvertI64FormatString( const RString &sStr )
{
	RString sRet;
	sRet.reserve( sStr.size() + 16 );

	size_t iOffset = 0;
	while( iOffset < sStr.size() )
	{
		size_t iPercent = sStr.find( '%', iOffset );
		if( iPercent != sStr.npos )
		{
			sRet.append( sStr, iOffset, iPercent - iOffset );
			iOffset = iPercent;
		}

		size_t iEnd = sStr.find_first_of( "diouxXeEfFgGaAcsCSpnm%", iOffset + 1 );
		if( iEnd != sStr.npos && iEnd - iPercent >= 3 && iPercent > 2 && sStr[iEnd-2] == 'l' && sStr[iEnd-1] == 'l' )
		{
			sRet.append( sStr, iPercent, iEnd - iPercent - 2 ); // %
			sRet.append( "I64" ); // %I64
			sRet.append( sStr, iEnd, 1 ); // %I64i
			iOffset = iEnd + 1;
		}
		else
		{
			if( iEnd == sStr.npos )
				iEnd = sStr.size() - 1;
			sRet.append( sStr, iOffset, iEnd - iOffset + 1 );
			iOffset = iEnd + 1;
		}
	}
	return sRet;
}
#else
RString ConvertI64FormatString( const RString &sStr ) { return sStr; }
#endif

/* ISO-639-1 codes: http://www.loc.gov/standards/iso639-2/php/code_list.php
 * native forms: http://people.w3.org/rishida/names/languages.html
 * We don't use 3-letter codes, so we don't bother supporting them. */
static const LanguageInfo g_langs[] =
{
	{"aa", "Afar"},
	{"ab", "Abkhazian"},
	{"af", "Afrikaans"},
	{"am", "Amharic"},
	{"ar", "Arabic"},
	{"as", "Assamese"},
	{"ay", "Aymara"},
	{"az", "Azerbaijani"},
	{"ba", "Bashkir"},
	{"be", "Byelorussian"},
	{"bg", "Bulgarian"},
	{"bh", "Bihari"},
	{"bi", "Bislama"},
	{"bn", "Bengali"},
	{"bo", "Tibetan"},
	{"br", "Breton"},
	{"ca", "Catalan"},
	{"co", "Corsican"},
	{"cs", "Czech"},
	{"cy", "Welsh"},
	{"da", "Danish"},
	{"de", "German"},
	{"dz", "Bhutani"},
	{"el", "Greek"},
	{"en", "English"},
	{"eo", "Esperanto"},
	{"es", "Spanish"},
	{"et", "Estonian"},
	{"eu", "Basque"},
	{"fa", "Persian"},
	{"fi", "Finnish"},
	{"fj", "Fiji"},
	{"fo", "Faeroese"},
	{"fr", "French"},
	{"fy", "Frisian"},
	{"ga", "Irish"},
	{"gd", "Gaelic"},
	{"gl", "Galician"},
	{"gn", "Guarani"},
	{"gu", "Gujarati"},
	{"ha", "Hausa"},
	{"he", "Hebrew"},
	{"hi", "Hindi"},
	{"hr", "Croatian"},
	{"hu", "Hungarian"},
	{"hy", "Armenian"},
	{"ia", "Interlingua"},
	{"id", "Indonesian"},
	{"ie", "Interlingue"},
	{"ik", "Inupiak"},
	{"in", "Indonesian"}, // compatibility
	{"is", "Icelandic"},
	{"it", "Italian"},
	{"iw", "Hebrew"}, // compatibility
	{"ja", "Japanese"},
	{"ji", "Yiddish"}, // compatibility
	{"jw", "Javanese"},
	{"ka", "Georgian"},
	{"kk", "Kazakh"},
	{"kl", "Greenlandic"},
	{"km", "Cambodian"},
	{"kn", "Kannada"},
	{"ko", "Korean"},
	{"ks", "Kashmiri"},
	{"ku", "Kurdish"},
	{"ky", "Kirghiz"},
	{"la", "Latin"},
	{"ln", "Lingala"},
	{"lo", "Laothian"},
	{"lt", "Lithuanian"},
	{"lv", "Latvian"},
	{"mg", "Malagasy"},
	{"mi", "Maori"},
	{"mk", "Macedonian"},
	{"ml", "Malayalam"},
	{"mn", "Mongolian"},
	{"mo", "Moldavian"},
	{"mr", "Marathi"},
	{"ms", "Malay"},
	{"mt", "Maltese"},
	{"my", "Burmese"},
	{"na", "Nauru"},
	{"ne", "Nepali"},
	{"nl", "Dutch"},
	{"no", "Norwegian"},
	{"oc", "Occitan"},
	{"om", "Oromo"},
	{"or", "Oriya"},
	{"pa", "Punjabi"},
	{"pl", "Polish"},
	{"ps", "Pashto"},
	{"pt", "Portuguese"},
	{"qu", "Quechua"},
	{"rm", "Rhaeto-Romance"},
	{"rn", "Kirundi"},
	{"ro", "Romanian"},
	{"ru", "Russian"},
	{"rw", "Kinyarwanda"},
	{"sa", "Sanskrit"},
	{"sd", "Sindhi"},
	{"sg", "Sangro"},
	{"sh", "Serbo-Croatian"},
	{"si", "Singhalese"},
	{"sk", "Slovak"},
	{"sl", "Slovenian"},
	{"sm", "Samoan"},
	{"sn", "Shona"},
	{"so", "Somali"},
	{"sq", "Albanian"},
	{"sr", "Serbian"},
	{"ss", "Siswati"},
	{"st", "Sesotho"},
	{"su", "Sudanese"},
	{"sv", "Swedish"},
	{"sw", "Swahili"},
	{"ta", "Tamil"},
	{"te", "Tegulu"},
	{"tg", "Tajik"},
	{"th", "Thai"},
	{"ti", "Tigrinya"},
	{"tk", "Turkmen"},
	{"tl", "Tagalog"},
	{"tn", "Setswana"},
	{"to", "Tonga"},
	{"tr", "Turkish"},
	{"ts", "Tsonga"},
	{"tt", "Tatar"},
	{"tw", "Twi"},
	{"uk", "Ukrainian"},
	{"ur", "Urdu"},
	{"uz", "Uzbek"},
	{"vi", "Vietnamese"},
	{"vo", "Volapuk"},
	{"wo", "Wolof"},
	{"xh", "Xhosa"},
	{"yi", "Yiddish"},
	{"yo", "Yoruba"},
	{"zh", "Chinese"},
	{"zu", "Zulu"},
};

void GetLanguageInfos( vector<const LanguageInfo*> &vAddTo )
{
	for( unsigned i=0; i<ARRAYLEN(g_langs); ++i )
		vAddTo.push_back( &g_langs[i] );
}

const LanguageInfo *GetLanguageInfo( const RString &sIsoCode )
{
	for( unsigned i=0; i<ARRAYLEN(g_langs); ++i )
	{
		if( sIsoCode.EqualsNoCase(g_langs[i].szIsoCode) )
			return &g_langs[i];
	}

	return NULL;
}

RString join( const RString &sDeliminator, const vector<RString> &sSource)
{
	if( sSource.empty() )
		return RString();

	RString sTmp;

	for( unsigned iNum = 0; iNum < sSource.size()-1; iNum++ )
	{
		sTmp += sSource[iNum];
		sTmp += sDeliminator;
	}
	sTmp += sSource.back();
	return sTmp;
}

RString join( const RString &sDelimitor, vector<RString>::const_iterator begin, vector<RString>::const_iterator end )
{
	if( begin == end )
		return RString();

	RString sRet;
	while( begin != end )
	{
		sRet += *begin;
		++begin;
		if( begin != end )
			sRet += sDelimitor;
	}

	return sRet;
}

RString SmEscape( const RString &sUnescaped )
{
	return SmEscape( sUnescaped.c_str(), sUnescaped.size() );
}

RString SmEscape( const char *cUnescaped, int len )
{
	RString answer = "";
	for( int i = 0; i < len; ++i )
	{
		// Other characters we could theoretically escape:
		// NotesWriterSM.cpp used to claim ',' should be escaped, but there was no explanation why
		// '#' is both a control character and a valid part of a parameter.  The only way for there to be
		//   any confusion is in a misformatted .sm file, though, so it is unnecessary to escape it.
		if( cUnescaped[i] == '/' && i + 1 < len && cUnescaped[i + 1] == '/' )
		{
			answer += "\\/\\/";
			++i; // increment here so we skip both //s
			continue;
		}
		if( cUnescaped[i] == '\\' || cUnescaped[i] == ':' || cUnescaped[i] == ';' )
		    answer += "\\";
		answer += cUnescaped[i];
	}
	return answer;
}

RString DwiEscape( const RString &sUnescaped )
{
	return DwiEscape( sUnescaped.c_str(), sUnescaped.size() );
}

RString DwiEscape( const char *cUnescaped, int len )
{
	RString answer = "";
	for( int i = 0; i < len; ++i )
	{
		switch( cUnescaped[i] )
		{
		// TODO: Which of these characters actually affect DWI?
		case '\\':
		case ':':
		case ';': answer += '|'; break;
		case '[': answer += '('; break;
		case ']': answer += ')'; break;
		default: answer += cUnescaped[i];
		}
	}
	return answer;
}

template <class S>
static int DelimitorLength( const S &Delimitor )
{
	return Delimitor.size();
}

static int DelimitorLength( char Delimitor )
{
	return 1;
}

static int DelimitorLength( wchar_t Delimitor )
{
	return 1;
}

template <class S, class C>
void do_split( const S &Source, const C Delimitor, vector<S> &AddIt, const bool bIgnoreEmpty )
{
	/* Short-circuit if the source is empty; we want to return an empty vector if
	 * the string is empty, even if bIgnoreEmpty is true. */
	if( Source.empty() )
		return;

	size_t startpos = 0;

	do {
		size_t pos;
		pos = Source.find( Delimitor, startpos );
		if( pos == Source.npos )
			pos = Source.size();

		if( pos-startpos > 0 || !bIgnoreEmpty )
		{
			/* Optimization: if we're copying the whole string, avoid substr; this
			 * allows this copy to be refcounted, which is much faster. */
			if( startpos == 0 && pos-startpos == Source.size() )
				AddIt.push_back(Source);
			else
			{
				const S AddRString = Source.substr(startpos, pos-startpos);
				AddIt.push_back(AddRString);
			}
		}

		startpos = pos+DelimitorLength(Delimitor);
	} while ( startpos <= Source.size() );
}

void split( const RString &sSource, const RString &sDelimitor, vector<RString> &asAddIt, const bool bIgnoreEmpty )
{
	if( sDelimitor.size() == 1 )
		do_split( sSource, sDelimitor[0], asAddIt, bIgnoreEmpty );
	else
		do_split( sSource, sDelimitor, asAddIt, bIgnoreEmpty );
}

void split( const wstring &sSource, const wstring &sDelimitor, vector<wstring> &asAddIt, const bool bIgnoreEmpty )
{
	if( sDelimitor.size() == 1 )
		do_split( sSource, sDelimitor[0], asAddIt, bIgnoreEmpty );
	else
		do_split( sSource, sDelimitor, asAddIt, bIgnoreEmpty );
}

/* Use:

RString str="a,b,c";
int start = 0, size = -1;
while( 1 )
{
	do_split( str, ",", start, size );
	if( start == str.size() )
		break;
	str[start] = 'Q';
}

*/

template <class S>
void do_split( const S &Source, const S &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty )
{
	if( size != -1 )
	{
		// Start points to the beginning of the last delimiter. Move it up.
		begin += size+Delimitor.size();
		begin = min( begin, len );
	}

	size = 0;

	if( bIgnoreEmpty )
	{
		// Skip delims.
		while( begin + Delimitor.size() < Source.size() &&
			!Source.compare( begin, Delimitor.size(), Delimitor ) )
			++begin;
	}

	/* Where's the string function to find within a substring?
	 * C++ strings apparently are missing that ... */
	size_t pos;
	if( Delimitor.size() == 1 )
		pos = Source.find( Delimitor[0], begin );
	else
		pos = Source.find( Delimitor, begin );
	if( pos == Source.npos || (int) pos > len )
		pos = len;
	size = pos - begin;
}

void split( const RString &Source, const RString &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty )
{
	do_split( Source, Delimitor, begin, size, len, bIgnoreEmpty );
}

void split( const wstring &Source, const wstring &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty )
{
	do_split( Source, Delimitor, begin, size, len, bIgnoreEmpty );
}

void split( const RString &Source, const RString &Delimitor, int &begin, int &size, const bool bIgnoreEmpty )
{
	do_split( Source, Delimitor, begin, size, Source.size(), bIgnoreEmpty );
}

void split( const wstring &Source, const wstring &Delimitor, int &begin, int &size, const bool bIgnoreEmpty )
{
	do_split( Source, Delimitor, begin, size, Source.size(), bIgnoreEmpty );
}

/*
 * foo\fum\          -> "foo\fum\", "", ""
 * c:\foo\bar.txt    -> "c:\foo\", "bar", ".txt"
 * \\foo\fum         -> "\\foo\", "fum", ""
 */
void splitpath( const RString &sPath, RString &sDir, RString &sFilename, RString &sExt )
{
	sDir = sFilename = sExt = "";

	vector<RString> asMatches;

	/*
	 * One level of escapes for the regex, one for C. Ew. 
	 * This is really:
	 * ^(.*[\\/])?(.*)$ 
	 */
	static Regex sep("^(.*[\\\\/])?(.*)$");
	bool bCheck = sep.Compare( sPath, asMatches );
	ASSERT( bCheck );

	sDir = asMatches[0];
	const RString sBase = asMatches[1];

	/* ^(.*)(\.[^\.]+)$ */
	static Regex SplitExt("^(.*)(\\.[^\\.]+)$");
	if( SplitExt.Compare(sBase, asMatches) )
	{
		sFilename = asMatches[0];
		sExt = asMatches[1];
	}
	else
	{
		sFilename = sBase;
	}
}

/* "foo.bar", "baz" -> "foo.baz"
 * "foo", "baz" -> "foo.baz"
 * "foo.bar", "" -> "foo" */
RString SetExtension( const RString &sPath, const RString &sExt )
{
	RString sDir, sFileName, sOldExt;
	splitpath( sPath, sDir, sFileName, sOldExt );
	return sDir + sFileName + (sExt.size()? ".":"") + sExt;
}

RString GetExtension( const RString &sPath )
{
	size_t pos = sPath.rfind( '.' );
	if( pos == sPath.npos )
		return RString();

	size_t slash = sPath.find( '/', pos );
	if( slash != sPath.npos )
		return RString(); /* rare: path/dir.ext/fn */

	return sPath.substr( pos+1, sPath.size()-pos+1 );
}

RString GetFileNameWithoutExtension( const RString &sPath )
{
	RString sThrowAway, sFName;
	splitpath( sPath, sThrowAway, sFName, sThrowAway );
	return sFName;
}

void MakeValidFilename( RString &sName )
{
	wstring wsName = RStringToWstring( sName );
	wstring wsInvalid = L"/\\:*?\"<>|";
	for( unsigned i = 0; i < wsName.size(); ++i )
	{
		wchar_t w = wsName[i];
		if( w >= 32 &&
			w < 126 &&
			wsInvalid.find_first_of(w) == wsInvalid.npos )
			continue;

		if( w == L'"' )
		{
			wsName[i] = L'\'';
			continue;
		}

		/* We could replace with closest matches in ASCII: convert the character
		 * to UTF-8 NFD (decomposed) (maybe NFKD?), and see if the first
		 * character is ASCII. This is useless for non-Western languages,
		 * since we'll replace the whole filename. */
		wsName[i] = '_';
	}

	sName = WStringToRString( wsName );
}

int g_argc = 0;
char **g_argv = NULL;

void SetCommandlineArguments( int argc, char **argv )
{
	g_argc = argc;
	g_argv = argv;
}

void GetCommandLineArguments( int &argc, char **&argv )
{
	argc = g_argc;
	argv = g_argv;
}

/* Search for the commandline argument given; eg. "test" searches for the
 * option "--test".  All commandline arguments are getopt_long style: --foo;
 * short arguments (-x) are not supported.  (These are not intended for
 * common, general use, so having short options isn't currently needed.)
 * If argument is non-NULL, accept an argument. */
bool GetCommandlineArgument( const RString &option, RString *argument, int iIndex )
{
	const RString optstr = "--" + option;

	for( int arg = 1; arg < g_argc; ++arg )
	{
		const RString CurArgument = g_argv[arg];

		const size_t i = CurArgument.find( "=" );
		RString CurOption = CurArgument.substr(0,i);
		if( CurOption.CompareNoCase(optstr) )
			continue; // no match

		// Found it.
		if( iIndex )
		{
			--iIndex;
			continue;
		}

		if( argument )
		{
			if( i != RString::npos )
				*argument = CurArgument.substr( i+1 );
			else
				*argument = "";
		}

		return true;
	}

	return false;
}

RString GetCwd()
{
	char buf[PATH_MAX];
	bool ret = getcwd(buf, PATH_MAX) != NULL;
	ASSERT(ret);
	return buf;
}

/*
 * Calculate a standard CRC32.  iCRC should be initialized to 0.
 * References:
 *   http://www.theorem.com/java/CRC32.java,
 *   http://www.faqs.org/rfcs/rfc1952.html
 */
void CRC32( unsigned int &iCRC, const void *pVoidBuffer, size_t iSize )
{
	static unsigned tab[256];
	static bool initted = false;
	if( !initted )
	{
		initted = true;
		const unsigned POLY = 0xEDB88320;

		for( int i = 0; i < 256; ++i )
		{
			tab[i] = i;
			for( int j = 0; j < 8; ++j )
			{
				if( tab[i] & 1 )
					tab[i] = (tab[i] >> 1) ^ POLY;
				else
					tab[i] >>= 1;
			}
		}
	}

	iCRC ^= 0xFFFFFFFF;

	const char *pBuffer = (const char *) pVoidBuffer;
	for( unsigned i = 0; i < iSize; ++i )
		iCRC = (iCRC >> 8) ^ tab[(iCRC ^ pBuffer[i]) & 0xFF];

	iCRC ^= 0xFFFFFFFF;
}

unsigned int GetHashForString( const RString &s )
{
	unsigned crc = 0;
	CRC32( crc, s.data(), s.size() );
	return crc;
}

/* Return true if "dir" is empty or does not exist. */
bool DirectoryIsEmpty( const RString &sDir )
{
	if( sDir.empty() )
		return true;
	if( !DoesFileExist(sDir) )
		return true;

	vector<RString> asFileNames;
	GetDirListing( sDir, asFileNames );
	return asFileNames.empty();
}

bool CompareRStringsAsc( const RString &sStr1, const RString &sStr2 )
{
	return sStr1.CompareNoCase( sStr2 ) < 0;
}

bool CompareRStringsDesc( const RString &sStr1, const RString &sStr2 )
{
	return sStr1.CompareNoCase( sStr2 ) > 0;
}

void SortRStringArray( vector<RString> &arrayRStrings, const bool bSortAscending )
{
	sort( arrayRStrings.begin(), arrayRStrings.end(),
			bSortAscending?CompareRStringsAsc:CompareRStringsDesc );
}

float calc_mean( const float *pStart, const float *pEnd )
{
	return accumulate( pStart, pEnd, 0.f ) / distance( pStart, pEnd );
}

float calc_stddev( const float *pStart, const float *pEnd, bool bSample )
{
	/* Calculate the mean. */
	float fMean = calc_mean( pStart, pEnd );

	/* Calculate stddev. */
	float fDev = 0.0f;
	for( const float *i=pStart; i != pEnd; ++i )
		fDev += (*i - fMean) * (*i - fMean);
	fDev /= distance( pStart, pEnd ) - (bSample ? 1 : 0);
	fDev = sqrtf( fDev );

	return fDev;
}

bool CalcLeastSquares( const vector< pair<float, float> > &vCoordinates,
                       float &fSlope, float &fIntercept, float &fError )
{
	if( vCoordinates.empty() ) 
		return false;
	float fSumXX = 0.0f, fSumXY = 0.0f, fSumX = 0.0f, fSumY = 0.0f;
	for( unsigned i = 0; i < vCoordinates.size(); ++i ) 
	{
		fSumXX += vCoordinates[i].first * vCoordinates[i].first;
		fSumXY += vCoordinates[i].first * vCoordinates[i].second;
		fSumX += vCoordinates[i].first;
		fSumY += vCoordinates[i].second;
	}
	const float fDenominator = vCoordinates.size() * fSumXX - fSumX * fSumX;
	fSlope = (vCoordinates.size() * fSumXY - fSumX * fSumY) / fDenominator;
	fIntercept = (fSumXX * fSumY - fSumX * fSumXY) / fDenominator;

	fError = 0.0f;
	for( unsigned i = 0; i < vCoordinates.size(); ++i ) 
	{
		const float fOneError = fIntercept + fSlope * vCoordinates[i].first - vCoordinates[i].second;
		fError += fOneError * fOneError;
	}
	fError /= vCoordinates.size();
	fError = sqrtf( fError );
	return true;
}

void FilterHighErrorPoints( vector< pair<float, float> > &vCoordinates,
                            float fSlope, float fIntercept, float fCutoff )
{
	unsigned int iOut = 0;
	for( unsigned int iIn = 0; iIn < vCoordinates.size(); ++iIn )
	{
		const float fError = fIntercept + fSlope * vCoordinates[iIn].first - vCoordinates[iIn].second;
		if( fabsf(fError) < fCutoff )
		{
			vCoordinates[iOut] = vCoordinates[iIn];
			++iOut;
		}
	}
	vCoordinates.resize( iOut );
}

void TrimLeft( RString &sStr, const char *s )
{
	int n = 0;
	while( n < int(sStr.size()) && strchr(s, sStr[n]) )
		n++;

	sStr.erase( sStr.begin(), sStr.begin()+n );
}

void TrimRight( RString &sStr, const char *s )
{
	int n = sStr.size();
	while( n > 0 && strchr(s, sStr[n-1]) )
		n--;

	/* Delete from n to the end. If n == sStr.size(), nothing is deleted;
	 * if n == 0, the whole string is erased. */
	sStr.erase( sStr.begin()+n, sStr.end() );
}

void Trim( RString &sStr, const char *s )
{
	RString::size_type b = 0, e = sStr.size();
	while( b < e && strchr(s, sStr[b]) )
		++b;
	while( b < e && strchr(s, sStr[e-1]) )
		--e;
	sStr.assign( sStr.substr(b, e-b) );
}

void StripCrnl( RString &s )
{
	while( s.size() && (s[s.size()-1] == '\r' || s[s.size()-1] == '\n') )
		s.erase( s.size()-1 );
}

bool BeginsWith( const RString &sTestThis, const RString &sBeginning )
{
	ASSERT( !sBeginning.empty() );
	return sTestThis.compare( 0, sBeginning.length(), sBeginning ) == 0;
}

bool EndsWith( const RString &sTestThis, const RString &sEnding )
{
	ASSERT( !sEnding.empty() );
	if( sTestThis.size() < sEnding.size() )
		return false;
	return sTestThis.compare( sTestThis.length()-sEnding.length(), sEnding.length(), sEnding ) == 0;
}

RString URLEncode( const RString &sStr )
{
	RString sOutput;
	for( unsigned k = 0; k < sStr.size(); k++ )
	{
		char t = sStr[k];
		if( t >= '!' && t <= 'z' )
			sOutput += t;
		else
			sOutput += "%" + ssprintf( "%02X", t );
	}
	return sOutput;
}

// remove various version control-related files
static bool CVSOrSVN( const RString& s )
{
	return s.Right(3).EqualsNoCase("CVS") ||
			s.Right(4) == ".svn" ||
			s.Right(3).EqualsNoCase(".hg");
}

void StripCvsAndSvn( vector<RString> &vs )
{
	RemoveIf( vs, CVSOrSVN );
}

static bool MacResourceFork( const RString& s )
{
	return s.Left(2).EqualsNoCase("._");
}

void StripMacResourceForks( vector<RString> &vs )
{
	RemoveIf( vs, MacResourceFork );
}

// path is a .redir pathname. Read it and return the real one.
RString DerefRedir( const RString &_path )
{
	RString sPath = _path;

	for( int i=0; i<100; i++ )
	{
		if( GetExtension(sPath) != "redir" )
			return sPath;

		RString sNewFileName;
		GetFileContents( sPath, sNewFileName, true );

		// Empty is invalid.
		if( sNewFileName == "" )
			return RString();

		RString sPath2 = Dirname(sPath) + sNewFileName;

		CollapsePath( sPath2 );

		sPath2 += "*";

		vector<RString> matches;
		GetDirListing( sPath2, matches, false, true );

		if( matches.empty() )
			RageException::Throw( "The redirect \"%s\" references a file \"%s\" which doesn't exist.", sPath.c_str(), sPath2.c_str() );
		else if( matches.size() > 1 )
			RageException::Throw( "The redirect \"%s\" references a file \"%s\" with multiple matches.", sPath.c_str(), sPath2.c_str() );

		sPath = matches[0];
	}

	RageException::Throw( "Circular redirect \"%s\".", sPath.c_str() );
}

bool GetFileContents( const RString &sPath, RString &sOut, bool bOneLine )
{
	// Don't warn if the file doesn't exist, but do warn if it exists and fails to open.
	if( !IsAFile(sPath) )
		return false;

	RageFile file;
	if( !file.Open(sPath) )
	{
		LOG->Warn( "GetFileContents(%s): %s", sPath.c_str(), file.GetError().c_str() );
		return false;
	}

	// todo: figure out how to make this UTF-8 safe. -aj
	RString sData;
	int iGot;
	if( bOneLine )
		iGot = file.GetLine( sData );
	else
		iGot = file.Read( sData, file.GetFileSize() );

	if( iGot == -1 )
	{
		LOG->Warn( "GetFileContents(%s): %s", sPath.c_str(), file.GetError().c_str() );
		return false;
	}

	if( bOneLine )
		StripCrnl( sData );

	sOut = sData;
	return true;
}

bool GetFileContents( const RString &sFile, vector<RString> &asOut )
{
	RageFile file;
	if( !file.Open(sFile) )
	{
		LOG->Warn( "GetFileContents(%s): %s", sFile.c_str(), file.GetError().c_str() );
		return false;
	}

	RString sLine;
	while( file.GetLine(sLine) )
		asOut.push_back( sLine );
	return true;
}

#include "../extern/pcre/pcre.h"
void Regex::Compile()
{
	const char *error;
	int offset;
	m_pReg = pcre_compile( m_sPattern.c_str(), PCRE_CASELESS, &error, &offset, NULL );

	if( m_pReg == NULL )
		RageException::Throw( "Invalid regex: \"%s\" (%s).", m_sPattern.c_str(), error );

	int iRet = pcre_fullinfo( (pcre *) m_pReg, NULL, PCRE_INFO_CAPTURECOUNT, &m_iBackrefs );
	ASSERT( iRet >= 0 );

	++m_iBackrefs;
	ASSERT( m_iBackrefs < 128 );
}

void Regex::Set( const RString &sStr )
{
	Release();
	m_sPattern = sStr;
	Compile();
}

void Regex::Release()
{
	pcre_free( m_pReg );
	m_pReg = NULL;
	m_sPattern = RString();
}

Regex::Regex( const RString &sStr ): m_pReg(NULL), m_iBackrefs(0), m_sPattern(RString())
{
	Set( sStr );
}

Regex::Regex( const Regex &rhs ): m_pReg(NULL), m_iBackrefs(0), m_sPattern(RString())
{
	Set( rhs.m_sPattern );
}

Regex &Regex::operator=( const Regex &rhs )
{
	if( this != &rhs )
		Set( rhs.m_sPattern );
	return *this;
}

Regex::~Regex()
{
	Release();
}

bool Regex::Compare( const RString &sStr )
{
	int iMat[128*3];
	int iRet = pcre_exec( (pcre *) m_pReg, NULL, sStr.data(), sStr.size(), 0, 0, iMat, 128*3 );

	if( iRet < -1 )
		RageException::Throw( "Unexpected return from pcre_exec('%s'): %i.", m_sPattern.c_str(), iRet );

	return iRet >= 0;
}

bool Regex::Compare( const RString &sStr, vector<RString> &asMatches )
{
	asMatches.clear();

	int iMat[128*3];
	int iRet = pcre_exec( (pcre *) m_pReg, NULL, sStr.data(), sStr.size(), 0, 0, iMat, 128*3 );

	if( iRet < -1 )
		RageException::Throw( "Unexpected return from pcre_exec('%s'): %i.", m_sPattern.c_str(), iRet );

	if( iRet == -1 )
		return false;

	for( unsigned i = 1; i < m_iBackrefs; ++i )
	{
		const int iStart = iMat[i*2], end = iMat[i*2+1];
		if( iStart == -1 )
			asMatches.push_back( RString() ); /* no match */
		else
			asMatches.push_back( sStr.substr(iStart, end - iStart) );
	}

	return true;
}

// Arguments and behavior are the same are similar to
// http://us3.php.net/manual/en/function.preg-replace.php
bool Regex::Replace( const RString &sReplacement, const RString &sSubject, RString &sOut )
{
	vector<RString> asMatches;
	if( !Compare(sSubject, asMatches) )
		return false;

	sOut = sReplacement;

	// TODO: optimize me by iterating only once over the string
	for( unsigned i=0; i<asMatches.size(); i++ )
	{
		RString sFrom = ssprintf( "\\${%d}", i );
		RString sTo = asMatches[i];
		sOut.Replace(sFrom, sTo);
	}

	return true;
}

/* Given a UTF-8 byte, return the length of the codepoint (if a start code)
 * or 0 if it's a continuation byte. */
int utf8_get_char_len( char p )
{
	if( !(p & 0x80) ) return 1; /* 0xxxxxxx - 1 */
	if( !(p & 0x40) ) return 1; /* 10xxxxxx - continuation */
	if( !(p & 0x20) ) return 2; /* 110xxxxx */
	if( !(p & 0x10) ) return 3; /* 1110xxxx */
	if( !(p & 0x08) ) return 4; /* 11110xxx */
	if( !(p & 0x04) ) return 5; /* 111110xx */
	if( !(p & 0x02) ) return 6; /* 1111110x */
	return 1; /* 1111111x */
}

static inline bool is_utf8_continuation_byte( char c )
{
	return (c & 0xC0) == 0x80;
}

/* Decode one codepoint at start; advance start and place the result in ch.
 * If the encoded string is invalid, false is returned. */
bool utf8_to_wchar_ec( const RString &s, unsigned &start, wchar_t &ch )
{
	if( start >= s.size() )
		return false;

	if( is_utf8_continuation_byte( s[start] ) || /* misplaced continuation byte */
		(s[start] & 0xFE) == 0xFE ) /* 0xFE, 0xFF */
	{
		start += 1;
		return false;
	}

	int len = utf8_get_char_len( s[start] );

	const int first_byte_mask[] = { -1, 0x7F, 0x1F, 0x0F, 0x07, 0x03, 0x01 };

	ch = wchar_t(s[start] & first_byte_mask[len]);

	for( int i = 1; i < len; ++i )
	{
		if( start+i >= s.size() )
		{
			/* We expected a continuation byte, but didn't get one. Return error, and point
			 * start at the unexpected byte; it's probably a new sequence. */
			start += i;
			return false;
		}

		char byte = s[start+i];
		if( !is_utf8_continuation_byte(byte) )
		{
			/* We expected a continuation byte, but didn't get one. Return error, and point
			 * start at the unexpected byte; it's probably a new sequence. */
			start += i;
			return false;
		}
		ch = (ch << 6) | (byte & 0x3F);
	}

	bool bValid = true;
	{
		unsigned c1 = (unsigned) s[start] & 0xFF;
		unsigned c2 = (unsigned) s[start+1] & 0xFF;
		int c = (c1 << 8) + c2;
		if( (c & 0xFE00) == 0xC000 ||
		    (c & 0xFFE0) == 0xE080 ||
		    (c & 0xFFF0) == 0xF080 ||
		    (c & 0xFFF8) == 0xF880 ||
		    (c & 0xFFFC) == 0xFC80 )
	    {
		    bValid = false;
	    }
	}

	if( ch == 0xFFFE || ch == 0xFFFF )
		bValid = false;

	start += len;
	return bValid;
}

/* Like utf8_to_wchar_ec, but only does enough error checking to prevent crashing. */
bool utf8_to_wchar( const char *s, size_t iLength, unsigned &start, wchar_t &ch )
{
	if( start >= iLength )
		return false;

	int len = utf8_get_char_len( s[start] );

	if( start+len > iLength )
	{
		// We don't have room for enough continuation bytes. Return error.
		start += len;
		ch = L'?';
		return false;
	}

	switch( len )
	{
	case 1:
		ch = (s[start+0] & 0x7F);
		break;
	case 2:
		ch = ( (s[start+0] & 0x1F) << 6 ) |
		       (s[start+1] & 0x3F);
		break;
	case 3:
		ch = ( (s[start+0] & 0x0F) << 12 ) |
		     ( (s[start+1] & 0x3F) << 6 ) |
		       (s[start+2] & 0x3F);
		break;
	case 4:
		ch = ( (s[start+0] & 0x07) << 18 ) |
		     ( (s[start+1] & 0x3F) << 12 ) |
		     ( (s[start+2] & 0x3F) << 6 ) |
		     (s[start+3] & 0x3F);
		break;
	case 5:
		ch = ( (s[start+0] & 0x03) << 24 ) |
		     ( (s[start+1] & 0x3F) << 18 ) |
		     ( (s[start+2] & 0x3F) << 12 ) |
		     ( (s[start+3] & 0x3F) << 6 ) |
		     (s[start+4] & 0x3F);
		break;

	case 6:
		ch = ( (s[start+0] & 0x01) << 30 ) |
		     ( (s[start+1] & 0x3F) << 24 ) |
		     ( (s[start+2] & 0x3F) << 18 ) |
		     ( (s[start+3] & 0x3F) << 12) |
		     ( (s[start+4] & 0x3F) << 6 ) |
		     (s[start+5] & 0x3F);
		break;

	}

	start += len;
	return true;
}


// UTF-8 encode ch and append to out.
void wchar_to_utf8( wchar_t ch, RString &out )
{
	if( ch < 0x80 ) { out.append( 1, (char) ch ); return; }

	int cbytes = 0;
	if( ch < 0x800 ) cbytes = 1;
	else if( ch < 0x10000 )    cbytes = 2;
	else if( ch < 0x200000 )   cbytes = 3;
	else if( ch < 0x4000000 )  cbytes = 4;
	else cbytes = 5;

	{
		int shift = cbytes*6;
		const int init_masks[] = { 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
		out.append( 1, (char) (init_masks[cbytes-1] | (ch>>shift)) );
	}

	for( int i = 0; i < cbytes; ++i )
	{
		int shift = (cbytes-i-1)*6;
		out.append( 1, (char) (0x80 | ((ch>>shift)&0x3F)) );
	}
}

wchar_t utf8_get_char( const RString &s )
{
	unsigned start = 0;
	wchar_t ret;
	if( !utf8_to_wchar_ec( s, start, ret ) )
		return INVALID_CHAR;
	return ret;
}

// Replace invalid sequences in s.
void utf8_sanitize( RString &s )
{
	RString ret;
	for( unsigned start = 0; start < s.size(); )
	{
		wchar_t ch;
		if( !utf8_to_wchar_ec( s, start, ch ) )
			ch = INVALID_CHAR;

		wchar_to_utf8( ch, ret );
	}

	s = ret;
}

bool utf8_is_valid( const RString &s )
{
	for( unsigned start = 0; start < s.size(); )
	{
		wchar_t ch;
		if( !utf8_to_wchar_ec( s, start, ch ) )
			return false;
	}
	return true;
}

/* Windows tends to drop garbage BOM characters at the start of UTF-8 text files.
 * Remove them. */
void utf8_remove_bom( RString &sLine )
{
	if( !sLine.compare(0, 3, "\xef\xbb\xbf") )
		sLine.erase(0, 3);
}

static int UnicodeDoUpper( char *p, size_t iLen, const unsigned char pMapping[256] )
{
	// Note: this has problems with certain accented characters. -aj
	wchar_t wc = L'\0';
	unsigned iStart = 0;
	if( !utf8_to_wchar(p, iLen, iStart, wc) )
		return 1;

	wchar_t iUpper = wc;
	if( wc < 256 )
		iUpper = pMapping[wc];
	if( iUpper != wc )
	{
		RString sOut;
		wchar_to_utf8( iUpper, sOut );
		if( sOut.size() == iStart )
			memcpy( p, sOut.data(), sOut.size() );
		else
			WARN( ssprintf("UnicodeDoUpper: invalid character at \"%s\"", RString(p,iLen).c_str()) );
	}

	return iStart;
}

/* Fast in-place MakeUpper and MakeLower. This only replaces characters with characters of the same UTF-8
 * length, so we never have to move the whole string. This is optimized for strings that have no
 * non-ASCII characters. */
void MakeUpper( char *p, size_t iLen )
{
	char *pStart = p;
	char *pEnd = p + iLen;
	while( p < pEnd )
	{
		// Fast path:
		if( likely( !(*p & 0x80) ) )
		{
			if( unlikely(*p >= 'a' && *p <= 'z') )
				*p += 'A' - 'a';
			++p;
			continue;
		}

		int iRemaining = iLen - (p-pStart);
		p += UnicodeDoUpper( p, iRemaining, g_UpperCase );
	}
}

void MakeLower( char *p, size_t iLen )
{
	char *pStart = p;
	char *pEnd = p + iLen;
	while( p < pEnd )
	{
		// Fast path:
		if( likely( !(*p & 0x80) ) )
		{
			if( unlikely(*p >= 'A' && *p <= 'Z') )
				*p -= 'A' - 'a';
			++p;
			continue;
		}

		int iRemaining = iLen - (p-pStart);
		p += UnicodeDoUpper( p, iRemaining, g_LowerCase );
	}
}

void UnicodeUpperLower( wchar_t *p, size_t iLen, const unsigned char pMapping[256] )
{
	wchar_t *pEnd = p + iLen;
	while( p != pEnd )
	{
		if( *p < 256 )
			*p = pMapping[*p];
		++p;
	}
}

void MakeUpper( wchar_t *p, size_t iLen )
{
	UnicodeUpperLower( p, iLen, g_UpperCase );
}

void MakeLower( wchar_t *p, size_t iLen )
{
	UnicodeUpperLower( p, iLen, g_LowerCase );
}

int StringToInt( const RString &sString )
{
	int ret;
	istringstream ( sString ) >> ret;
	return ret;
}

RString IntToString( const int &iNum )
{
	stringstream ss;
	ss << iNum;
	return ss.str();
}

float StringToFloat( const RString &sString )
{
	float ret = strtof( sString, NULL );

	if( !isfinite(ret) )
		ret = 0.0f;
	return ret;
}

bool StringToFloat( const RString &sString, float &fOut )
{
	char *endPtr;

	fOut = strtof( sString, &endPtr );
	return sString.size() && *endPtr == '\0' && isfinite( fOut );
}

RString FloatToString( const float &num )
{
	stringstream ss;
	ss << num;
	return ss.str();
}

const wchar_t INVALID_CHAR = 0xFFFD; /* U+FFFD REPLACEMENT CHARACTER */

wstring RStringToWstring( const RString &s )
{
	wstring ret;
	ret.reserve( s.size() );
	for( unsigned start = 0; start < s.size(); )
	{
		char c = s[start];
		if( !(c&0x80) )
		{
			// ASCII fast path
			ret += c;
			++start;
			continue;
		}
		
		wchar_t ch = L'\0';
		if( !utf8_to_wchar( s.data(), s.size(), start, ch ) )
			ch = INVALID_CHAR;
		ret += ch;
	}

	return ret;
}

RString WStringToRString( const wstring &sStr )
{
	RString sRet;

	for( unsigned i = 0; i < sStr.size(); ++i )
		wchar_to_utf8( sStr[i], sRet );

	return sRet;
}

RString WcharToUTF8( wchar_t c )
{
	RString ret;
	wchar_to_utf8( c, ret );
	return ret;
}

// &a; -> a
void ReplaceEntityText( RString &sText, const map<RString,RString> &m )
{
	RString sRet;

	size_t iOffset = 0;
	while( iOffset != sText.size() )
	{
		size_t iStart = sText.find( '&', iOffset );
		if( iStart == sText.npos )
		{
			// Optimization: if we didn't replace anything at all, do nothing.
			if( iOffset == 0 )
				return;

			// Append the rest of the string.
			sRet.append( sText, iOffset, sRet.npos );
			break;
		}

		// Append the text between iOffset and iStart.
		sRet.append( sText, iOffset, iStart-iOffset );
		iOffset += iStart-iOffset;

		// Optimization: stop early on "&", so "&&&&&&&&&&&" isn't n^2.
		size_t iEnd = sText.find_first_of( "&;", iStart+1 );
		if( iEnd == sText.npos || sText[iEnd] == '&' )
		{
			// & with no matching ;, or two & in a row. Append the & and continue.
			sRet.append( sText, iStart, 1 );
			++iOffset;
			continue;
		}

		RString sElement = sText.substr( iStart+1, iEnd-iStart-1 );
		sElement.MakeLower();

		map<RString,RString>::const_iterator it = m.find( sElement );
		if( it == m.end() )
		{
			sRet.append( sText, iStart, iEnd-iStart+1 );
			iOffset = iEnd + 1;
			continue;
		}

		const RString &sTo = it->second;
		sRet.append( sTo );
		iOffset = iEnd + 1;
	}

	sText = sRet;
}

// abcd -> &a; &b; &c; &d;
void ReplaceEntityText( RString &sText, const map<char,RString> &m )
{
	RString sFind;

	FOREACHM_CONST( char, RString, m, c )
		sFind.append( 1, c->first );

	RString sRet;

	size_t iOffset = 0;
	while( iOffset != sText.size() )
	{
		size_t iStart = sText.find_first_of( sFind, iOffset );
		if( iStart == sText.npos )
		{
			// Optimization: if we didn't replace anything at all, do nothing.
			if( iOffset == 0 )
				return;

			// Append the rest of the string.
			sRet.append( sText, iOffset, sRet.npos );
			break;
		}

		// Append the text between iOffset and iStart.
		sRet.append( sText, iOffset, iStart-iOffset );
		iOffset += iStart-iOffset;

		char sElement = sText[iStart];

		map<char,RString>::const_iterator it = m.find( sElement );
		ASSERT( it != m.end() );

		const RString &sTo = it->second;
		sRet.append( 1, '&' );
		sRet.append( sTo );
		sRet.append( 1, ';' );
		++iOffset;
	}

	sText = sRet;
}

// Replace &#nnnn; (decimal) and &xnnnn; (hex) with corresponding UTF-8 characters.
void Replace_Unicode_Markers( RString &sText )
{
	unsigned iStart = 0;
	while( iStart < sText.size() )
	{
		// Look for &#digits;
		bool bHex = false;
		size_t iPos = sText.find( "&#", iStart );
		if( iPos == sText.npos )
		{
			bHex = true;
			iPos = sText.find( "&x", iStart );
		}

		if( iPos == sText.npos )
			break;
		iStart = iPos+1;

		unsigned p = iPos;
		p += 2;

		// Found &# or &x. Is it followed by digits and a semicolon?
		if( p >= sText.size() )
			continue;

		int iNumDigits = 0;
		while( p < sText.size() && bHex? isxdigit(sText[p]):isdigit(sText[p]) )
		{
			p++;
			iNumDigits++;
		}

		if( !iNumDigits )
			continue; // must have at least one digit
		if( p >= sText.size() || sText[p] != ';' )
			continue;
		p++;

		int iNum;
		if( bHex )
			sscanf( sText.c_str()+iPos, "&x%x;", &iNum );
		else
			sscanf( sText.c_str()+iPos, "&#%i;", &iNum );
		if( iNum > 0xFFFF )
			iNum = INVALID_CHAR;

		sText.replace( iPos, p-iPos, WcharToUTF8(wchar_t(iNum)) );
	}
}

// Form a string to identify a wchar_t with ASCII.
RString WcharDisplayText( wchar_t c )
{
	RString sChr;
	sChr = ssprintf( "U+%4.4x", c );
	if( c < 128 )
		sChr += ssprintf( " ('%c')", char(c) );
	return sChr;
}

/* Return the last named component of dir:
 * a/b/c -> c
 * a/b/c/ -> c
 */
RString Basename( const RString &sDir )
{
	size_t iEnd = sDir.find_last_not_of( "/\\" );
	if( iEnd == sDir.npos )
		return RString();

	size_t iStart = sDir.find_last_of( "/\\", iEnd );
	if( iStart == sDir.npos )
		iStart = 0;
	else
		++iStart;

	return sDir.substr( iStart, iEnd-iStart+1 );
}

/* Return all but the last named component of dir:
 *
 * a/b/c -> a/b/
 * a/b/c/ -> a/b/
 * c/ -> ./
 * /foo -> /
 * / -> /
 */
RString Dirname( const RString &dir )
{
	// Special case: "/" -> "/".
	if( dir.size() == 1 && dir[0] == '/' )
		return "/";

	int pos = dir.size()-1;
	// Skip trailing slashes.
	while( pos >= 0 && dir[pos] == '/' )
		--pos;

	// Skip the last component.
	while( pos >= 0 && dir[pos] != '/' )
		--pos;

	if( pos < 0 )
		return "./";

	return dir.substr(0, pos+1);
}

RString Capitalize( const RString &s )	
{
	if( s.empty() )
		return RString();

	RString s2 = s;
	char *pBuf = s2.GetBuffer();
	UnicodeDoUpper( pBuf, s2.size(), g_UpperCase );
	s2.ReleaseBuffer();

	return s2;
}

unsigned char g_UpperCase[256] =
{
	0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
	0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
	0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
	0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
	0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
	0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
	0x60,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
	0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x7B,0x7C,0x7D,0x7E,0x7F,
	0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
	0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
	0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
	0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
	0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
	0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
	0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
	0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF,
};

unsigned char g_LowerCase[256] =
{
	0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
	0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
	0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
	0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
	0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
	0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x5B,0x5C,0x5D,0x5E,0x5F,
	0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
	0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
	0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
	0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
	0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
	0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
	0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
	0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
	0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
	0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF,
};

void FixSlashesInPlace( RString &sPath )
{
	for( unsigned i = 0; i < sPath.size(); ++i )
		if( sPath[i] == '\\' )
			sPath[i] = '/';
}

/* Keep trailing slashes, since that can be used to illustrate that a path always
 * represents a directory.
 *
 * foo/bar -> foo/bar
 * foo/bar/ -> foo/bar/
 * foo///bar/// -> foo/bar/
 * foo/bar/./baz -> foo/bar/baz
 * foo/bar/../baz -> foo/baz
 * ../foo -> ../foo
 * ../../foo -> ../../foo
 * ./foo -> foo (if bRemoveLeadingDot), ./foo (if !bRemoveLeadingDot)
 * ./ -> ./
 * ./// -> ./
 */

void CollapsePath( RString &sPath, bool bRemoveLeadingDot )
{
	RString sOut;
	sOut.reserve( sPath.size() );

	size_t iPos = 0;
	size_t iNext;
	for( ; iPos < sPath.size(); iPos = iNext )
	{
		// Find the next slash.
		iNext = sPath.find( '/', iPos );
		if( iNext == RString::npos )
			iNext = sPath.size();
		else
			++iNext;

		/* Strip extra slashes, but don't remove slashes from the beginning of the string. */
		if( iNext - iPos == 1 && sPath[iPos] == '/' )
		{
			if( !sOut.empty() )
				continue;
		}

		// If this is a dot, skip it.
		if( iNext - iPos == 2 && sPath[iPos] == '.' && sPath[iPos+1] == '/' )
		{
			if( bRemoveLeadingDot || !sOut.empty() )
				continue;
		}

		// If this is two dots,
		if( iNext - iPos == 3 && sPath[iPos] == '.' && sPath[iPos+1] == '.' && sPath[iPos+2] == '/' )
		{
			/* If this is the first path element (nothing to delete),
			 * or all we have is a slash, leave it. */
			if( sOut.empty() || (sOut.size() == 1 && sOut[0] == '/') )
			{
				sOut.append( sPath, iPos, iNext-iPos );
				continue;
			}

			// Search backwards for the previous path element.
			size_t iPrev = sOut.rfind( '/', sOut.size()-2 );
			if( iPrev == RString::npos )
				iPrev = 0;
			else
				++iPrev;

			// If the previous element is also .., leave it.
			bool bLastIsTwoDots = (sOut.size() - iPrev == 3 && sOut[iPrev] == '.' && sOut[iPrev+1] == '.' );
			if( bLastIsTwoDots )
			{
				sOut.append( sPath, iPos, iNext-iPos );
				continue;
			}

			sOut.erase( iPrev );
			continue;
		}

		sOut.append( sPath, iPos, iNext-iPos );
	}
	
	sOut.swap( sPath );
}

namespace StringConversion
{
	template<> bool FromString<int>( const RString &sValue, int &out )
	{
		if( sscanf( sValue.c_str(), "%d", &out ) == 1 )
			return true;

		out = 0;
		return false;
	}

	template<> bool FromString<unsigned>( const RString &sValue, unsigned  &out )
	{
		if( sscanf( sValue.c_str(), "%u", &out ) == 1 )
			return true;

		out = 0;
		return false;
	}

	template<> bool FromString<float>( const RString &sValue, float &out )
	{
		const char *endptr = sValue.data() + sValue.size();
		out = strtof( sValue, (char **) &endptr );
		if( endptr != sValue.data() && isfinite( out ) )
			return true;
		out = 0;
		return false;
	}

	template<> bool FromString<bool>( const RString &sValue, bool &out )
	{
		if( sValue.size() == 0 )
			return false;

		out = (StringToInt(sValue) != 0);
		return true;
	}

	template<> RString ToString<int>( const int &value )
	{
		return ssprintf( "%i", value );
	}

	template<> RString ToString<unsigned>( const unsigned &value )
	{
		return ssprintf( "%u", value );
	}

	template<> RString ToString<float>( const float &value )
	{
		return ssprintf( "%f", value );
	}

	template<> RString ToString<bool>( const bool &value )
	{
		return ssprintf( "%i", value );
	}
}

bool FileCopy( const RString &sSrcFile, const RString &sDstFile )
{
	if( !sSrcFile.CompareNoCase(sDstFile) )
	{
		LOG->Warn( "Tried to copy \"%s\" over itself", sSrcFile.c_str() );
		return false;
	}

	RageFile in;
	if( !in.Open(sSrcFile, RageFile::READ) )
		return false;

	RageFile out;
	if( !out.Open(sDstFile, RageFile::WRITE) )
		return false;

	RString sError;
	if( !FileCopy(in, out, sError) )
	{
		LOG->Warn( "FileCopy(%s,%s): %s",
				sSrcFile.c_str(), sDstFile.c_str(), sError.c_str() );
		return false;
	}

	return true;
}

bool FileCopy( RageFileBasic &in, RageFileBasic &out, RString &sError, bool *bReadError )
{
	while(1)
	{
		RString data;
		if( in.Read(data, 1024*32) == -1 )
		{
			sError = ssprintf( "read error: %s", in.GetError().c_str() );
			if( bReadError != NULL )
				*bReadError = true;
			return false;
		}
		if( data.empty() )
			break;
		int i = out.Write(data);
		if( i == -1 )
		{
			sError = ssprintf( "write error: %s", out.GetError().c_str() );
			if( bReadError != NULL )
				*bReadError = false;
			return false;
		}
	}

	if( out.Flush() == -1 )
	{
		sError = ssprintf( "write error: %s", out.GetError().c_str() );
		if( bReadError != NULL )
			*bReadError = false;
		return false;
	}

	return true;
}

LuaFunction( SecondsToMSSMsMs, SecondsToMSSMsMs( FArg(1) ) )
LuaFunction( SecondsToHHMMSS, SecondsToHHMMSS( FArg(1) ) )
LuaFunction( SecondsToMMSSMsMs, SecondsToMMSSMsMs( FArg(1) ) )
LuaFunction( SecondsToMMSSMsMsMs, SecondsToMMSSMsMsMs( FArg(1) ) )
LuaFunction( SecondsToMSS, SecondsToMSS( FArg(1) ) )
LuaFunction( SecondsToMMSS, SecondsToMMSS( FArg(1) ) )
LuaFunction( FormatNumberAndSuffix, FormatNumberAndSuffix( IArg(1) ) )
LuaFunction( Basename, Basename( SArg(1) ) )
static RString MakeLower( RString s ) { s.MakeLower(); return s; }
LuaFunction( Lowercase, MakeLower( SArg(1) ) )
static RString MakeUpper( RString s ) { s.MakeUpper(); return s; }
LuaFunction( Uppercase, MakeUpper( SArg(1) ) )
LuaFunction( mbstrlen, (int)RStringToWstring(SArg(1)).length() )
LuaFunction( URLEncode, URLEncode( SArg(1) ) );
LuaFunction( PrettyPercent, PrettyPercent( FArg(1), FArg(2) ) );
//LuaFunction( IsHexVal, IsHexVal( SArg(1) ) );
static bool UndocumentedFeature( RString s ){ sm_crash(s); return true; }
LuaFunction( UndocumentedFeature, UndocumentedFeature(SArg(1)) );

/*
 * Copyright (c) 2001-2005 Chris Danford, Glenn Maynard
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons to
 * whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
