#include "global.h"
/*
-----------------------------------------------------------------------------
 File: RageUtil

 Desc: See header.

 Copyright (c) 2001-2002 by the person(s) listed below.  All rights reserved.
	Chris Danford
-----------------------------------------------------------------------------
*/
#include "RageMath.h"
#include "RageUtil.h"
#include "RageUtil_FileDB.h"
#include "RageLog.h"
#include "arch/arch.h"

#include <numeric>
#include <time.h>
#include <math.h>
#include "RageFile.h"
#include <map>
#include <sys/types.h>
#include <sys/stat.h>

unsigned long randseed = time(NULL);

RandomGen::RandomGen( unsigned long seed_ )
{
	seed = seed_;
	if(seed == 0)
		seed = time(NULL);
}

int RandomGen::operator() ( int maximum )
{
	seed = 1664525L * seed + 1013904223L;
	return (seed >> 2) % maximum;
}


void fapproach( float& val, float other_val, float to_move )
{
	ASSERT( to_move >= 0 );
	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 input)
{
    int value = 1;

	while ( value < input ) value <<= 1;

	return value;
}

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

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

	return true;
}

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

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

	return true;
}

float TimeToSeconds( CString sHMS )
{
	CStringArray arrayBits;
	split( sHMS, ":", arrayBits, false );

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

	float fSeconds = 0;
	fSeconds += atoi( arrayBits[0] ) * 60 * 60;
	fSeconds += atoi( arrayBits[1] ) * 60;
	fSeconds += (float)atof( arrayBits[2] );

	return fSeconds;
}

CString SecondsToTime( float fSecs )
{
	const int iMinsDisplay = (int)fSecs/60;
	const int iSecsDisplay = (int)fSecs - iMinsDisplay*60;
	const float fLeftoverDisplay = (fSecs - iMinsDisplay*60 - iSecsDisplay) * 100;
	CString sReturn = ssprintf( "%02d:%02d:%02.0f", iMinsDisplay, iSecsDisplay, min(99.0f,fLeftoverDisplay) );
	if( iMinsDisplay >= 60 )
	{
		/* Oops.  Probably a really long endless course.  Do "hh:mm.ss"; use a period
		 * to differentiate between "mm:ss:ms".  I'd much prefer reversing those, since
		 * it makes much more sense to do "mm:ss.ms", but people would probably complain
		 * about "arcade accuracy" ... 
		 */
		sReturn = ssprintf( "%02d:%02d.%02d", iMinsDisplay/60, iMinsDisplay%60, iSecsDisplay );
	}

	ASSERT( sReturn.GetLength() <= 8 );
	return sReturn;
}

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


CString vssprintf( const char *fmt, va_list argList)
{
	CString str;
	str.FormatV(fmt, argList);
	return str;
}

#ifdef WIN32
#ifdef _XBOX
#include "D3DX8Core.h"
#else
#include "Dxerr8.h"
#pragma comment(lib, "Dxerr8.lib")
#endif

CString hr_ssprintf( int hr, const char *fmt, ...)
{
    va_list	va;
    va_start(va, fmt);
    CString s = vssprintf( fmt, va );
    va_end(va);

#ifdef _XBOX
	/* XXX: This doesn't work for DirectInput or DirectSound; why is it needed? 
	 * XBox uses DI and DS, too, right? -glenn */
	char szError[1024] = "";
	D3DXGetErrorString( hr, szError, sizeof(szError) );
#else	
	const char *szError = DXGetErrorString8( hr );
#endif

	return s + ssprintf( " (%s)", szError );
}

CString werr_ssprintf( int err, const char *fmt, ...)
{
	char buf[1024] = "";
#ifndef _XBOX
	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
		0, err, 0, buf, sizeof(buf), NULL);
#endif

    va_list	va;
    va_start(va, fmt);
    CString s = vssprintf( fmt, va );
    va_end(va);

	return s += ssprintf( " (%s)", buf );
}

#endif

CString join( const CString &Deliminator, const CStringArray& Source)
{
	if( Source.empty() )
		return "";

	CString csTmp;

	// Loop through the Array and Append the Deliminator
	for( unsigned iNum = 0; iNum < Source.size()-1; iNum++ ) {
		csTmp += Source[iNum];
		csTmp += Deliminator;
	}
	csTmp += Source.back();
	return csTmp;
}


template <class S>
void do_split( const S &Source, const S &Deliminator, vector<S> &AddIt, const bool bIgnoreEmpty )
{
	unsigned startpos = 0;

	do {
		unsigned pos = Source.find(Deliminator, startpos);
		if ( pos == Source.npos ) pos=Source.size();

		S AddCString = Source.substr(startpos, pos-startpos);
		if( AddCString.empty() && bIgnoreEmpty )
			; // do nothing
		else
			AddIt.push_back(AddCString);

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


void split( const CString &Source, const CString &Deliminator, CStringArray &AddIt, const bool bIgnoreEmpty )
{
	do_split(Source, Deliminator, AddIt, bIgnoreEmpty );
}

void split( const wstring &Source, const wstring &Deliminator, vector<wstring> &AddIt, const bool bIgnoreEmpty )
{
	do_split(Source, Deliminator, AddIt, bIgnoreEmpty );
}


/*
 * foo\fum\          -> "foo\fum\", "", ""
 * c:\foo\bar.txt    -> "c:\foo\", "bar", ".txt"
 * \\foo\fum         -> "\\foo\", "fum", ""
 */
void splitpath( CString Path, CString& Dir, CString& Filename, CString& Ext )
{
	Dir = Filename = Ext = "";

	CStringArray mat;

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

	Dir = mat[0];
	Path = mat[1];

	/* ^(.*)(\.[^\.]+)$ */
	static Regex SplitExt("^(.*)(\\.[^\\.]+)$");
	if(SplitExt.Compare(Path, mat))
	{
		Filename = mat[0];
		Ext = mat[1];
	} else
		Filename = Path;
}

void splitrelpath( const CString &Path, CString& Dir, CString& FName, CString& Ext )
{
	/* Find the last slash or backslash. */
	int Last = max(Path.ReverseFind('/'), Path.ReverseFind('\\')); 

	/* Set 'Last' to the first character of the filename.  If we have
	 * no directory separators, this is the entire string, so set it
	 * to 0. */
	if(Last == -1) Last = 0;
	else Last++;

	CString sFNameAndExt = Path.Right(Path.GetLength()-Last);
	
	// subtract the FNameAndExt from Path
	Dir = Path.Left( Path.GetLength()-sFNameAndExt.GetLength() );	// don't subtract out the trailing slash

	CStringArray sFNameAndExtBits;
	split( sFNameAndExt, ".", sFNameAndExtBits, false );

	if( sFNameAndExt.GetLength() == 0 )	// no file at the end of this path
	{
		FName = "";
		Ext = "";
	}
	else if( sFNameAndExtBits.size() == 1 )	// file doesn't have extension
	{
		FName = sFNameAndExtBits[0];
		Ext = "";
	}
	else if( sFNameAndExtBits.size() > 1 )	// file has extension and possibly multiple periods
	{
		Ext = sFNameAndExtBits[ sFNameAndExtBits.size()-1 ];

		// subtract the Ext and last period from FNameAndExt
		FName = sFNameAndExt.Left( sFNameAndExt.GetLength()-Ext.GetLength()-1 );
	}
}

CString SetExtension( const CString &path, const CString &ext )
{
	CString Dir, FName, OldExt;
	splitpath( path, Dir, FName, OldExt );
	return Dir + FName + "." + ext;
}

CString GetExtension( CString sPath )
{
	CString Dir, FName, Ext;
	splitrelpath( sPath, Dir, FName, Ext );
	Ext.MakeLower();
	return Ext;
}

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

/* mkdir -p.  Doesn't fail if Path already exists and is a directory. */
bool CreateDirectories( CString Path )
{
	CStringArray parts;
	CString curpath;
	split(Path, SLASH, parts);

	for(unsigned i = 0; i < parts.size(); ++i)
	{
		curpath += parts[i] + SLASH;
		if( mkdir(curpath, 0755) == 0 )
			continue;

		if(errno != EEXIST)
		{
			if( LOG )
				LOG->Warn("Couldn't create %s: %s", curpath.c_str(), strerror(errno) );
			return false;
		}

		/* Make sure it's a directory. */
		if( !IsADirectory(curpath) )
		{
			if( LOG )
				LOG->Warn("Couldn't create %s: path exists and is not a directory", curpath.c_str() );
			return false;
		}
	}
	
	return true;
}

#if 0
void GetDirListing( CString sPath, CStringArray &AddTo, bool bOnlyDirs, bool bReturnPathToo )
{
	CString sDir, sThrowAway;
	splitrelpath( sPath, sDir, sThrowAway, sThrowAway );

	/* XXX: We should use Find* to get a file list only, and handle
	 * wildcard matching ourself.  Windows matching is braindead.  For
	 * example, *.dwi matches "foo.dwi~". */
	WIN32_FIND_DATA fd;
	HANDLE hFind = ::FindFirstFile( sPath, &fd );

	if( INVALID_HANDLE_VALUE == hFind )		// no files found
		return;

	do
	{
		if( bOnlyDirs  &&  !(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
			continue;	// skip

		CString sDirName( fd.cFileName );

		if( sDirName == "."  ||  sDirName == ".." )
			continue;

		if( bReturnPathToo )
			AddTo.push_back( sDir + sDirName );
		else
			AddTo.push_back( sDirName );


	} while( ::FindNextFile( hFind, &fd ) );
	::FindClose( hFind );
}
#endif

/* Reference: http://www.theorem.com/java/CRC32.java, rewritten by Glenn Maynard.
 * Public domain. */
unsigned int GetHashForString ( CString s )
{
	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;
			}
		}
	}

	unsigned crc = 0;
	for(unsigned i = 0; i < s.size(); ++i)
        crc = (crc >> 8) ^ tab[(crc ^ s[i]) & 0xFF];
	return crc;
}

unsigned int GetHashForFile( CString sPath )
{
	unsigned int hash = 0;

	hash += GetHashForString( sPath );

	hash += GetFileSizeInBytes( sPath ); 

	struct stat st;
	if( DoStat(sPath, &st))
		hash += st.st_mtime;

	return hash;
}

unsigned int GetHashForDirectory( CString sDir )
{
	unsigned int hash = 0;

	hash += GetHashForFile( sDir );

	CStringArray arrayFiles;
	GetDirListing( sDir+"*", arrayFiles, false );
	for( unsigned i=0; i<arrayFiles.size(); i++ )
	{
		const CString sFilePath = sDir + arrayFiles[i];
		hash += GetHashForFile( sFilePath );
	}

	return hash; 
}

bool CompareCStringsAsc(const CString &str1, const CString &str2)
{
	return str1.CompareNoCase( str2 ) < 0;
}

bool CompareCStringsDesc(const CString &str1, const CString &str2)
{
	return str1.CompareNoCase( str2 ) > 0;
}

void SortCStringArray( CStringArray &arrayCStrings, const bool bSortAscending )
{
	sort( arrayCStrings.begin(), arrayCStrings.end(),
			bSortAscending?CompareCStringsAsc:CompareCStringsDesc);
}

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

float calc_stddev(const float *start, const float *end)
{
	/* Calculate the mean. */
	float mean = calc_mean(start, end);

	/* Calculate stddev. */
	float dev = 0.0f;
	for( const float *i=start; i != end; ++i )
		dev += (*i - mean) * (*i - mean);
	dev /= distance(start, end) - 1;
	dev = sqrtf(dev);

	return dev;
}

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

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

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

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

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

/* path is a .redir pathname.  Read it and return the real one. */
CString DerefRedir(const CString &path)
{
	CString sDir, sFName, sExt;
	splitrelpath( path, sDir, sFName, sExt );

	if(sExt != "redir") 
	{
		CString path2 = path;
		ResolvePath( path2 );
		return path2;
	}

	CString sNewFileName;
	{
		ifstream file(path);
		getline(file, sNewFileName);
	}

	StripCrnl(sNewFileName);

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

	FixSlashesInPlace( sNewFileName );

	CString path2 = sDir+sNewFileName;

	CollapsePath( path2 );

	ResolvePath( path2 );
	return path2;
}

CString GetRedirContents(const CString &path)
{
	CString sDir, sFName, sExt;
	splitrelpath( path, sDir, sFName, sExt );

	ASSERT( sExt == "redir");	// don't ever call this on a non-redirect

	CString sNewFileName;
	{
		ifstream file(path);
		getline(file, sNewFileName);
	}

	StripCrnl(sNewFileName);

	return sNewFileName;
}

#if 1
/* PCRE */
#include "pcre/pcre.h"
void Regex::Compile()
{
	const char *error;
	int offset;
	reg = pcre_compile(pattern.c_str(), PCRE_CASELESS, &error, &offset, NULL);

    if(reg == NULL)
		RageException::Throw("Invalid regex: '%s' (%s)", pattern.c_str(), error);

	int ret = pcre_fullinfo( (pcre *) reg, NULL, PCRE_INFO_CAPTURECOUNT, &backrefs);
	ASSERT(ret >= 0);

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

void Regex::Set(const CString &str)
{
	Release();
    pattern=str;
	Compile();
}

void Regex::Release()
{
    pcre_free(reg);
	reg = NULL;
	pattern = "";
}

Regex::Regex(const CString &str)
{
	reg = NULL;
	Set(str);
}

Regex::Regex(const Regex &rhs)
{
	reg = NULL;
    Set(rhs.pattern);
}

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

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

bool Regex::Compare(const CString &str)
{
    int mat[128*3];
	int ret = pcre_exec( (pcre *) reg, NULL,
		str.data(), str.size(), 0, 0, mat, 128*3);

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

	return ret >= 0;
}

bool Regex::Compare(const CString &str, vector<CString> &matches)
{
    matches.clear();

    int mat[128*3];
	int ret = pcre_exec( (pcre *) reg, NULL,
		str.data(), str.size(), 0, 0, mat, 128*3);

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

	if(ret == -1)
		return false;

    for(unsigned i = 1; i < backrefs; ++i)
    {
		const int start = mat[i*2], end = mat[i*2+1];
        if(start == -1)
            matches.push_back(""); /* no match */
        else
            matches.push_back(str.substr(start, end - start));
    }

    return true;
}
#else
/* GNU regex */
#include "regex.h"
void Regex::Compile()
{
    reg = new regex_t;

    int ret = regcomp((regex_t *) reg, pattern.c_str(), REG_EXTENDED|REG_ICASE);
    if(ret != 0)
		RageException::Throw("Invalid regex: '%s'", pattern.c_str());

    /* Count the number of backreferences. */
    backrefs = 0;
    for(int i = 0; i < int(pattern.size()); ++i)
        if(pattern[i] == '(') backrefs++;
    ASSERT(backrefs+1 < 128);
}

void Regex::Set(const CString &str)
{
	Release();
    pattern=str;
	Compile();
}

void Regex::Release()
{
    delete (regex_t *)reg;
	reg = NULL;
	pattern = "";
}

Regex::Regex(const CString &str)
{
	reg = NULL;
	Set(str);
}

Regex::Regex(const Regex &rhs)
{
	reg = NULL;
    Set(rhs.pattern);
}

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

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

bool Regex::Compare(const CString &str)
{
    return regexec((regex_t *) reg, str.c_str(), 0, NULL, 0) != REG_NOMATCH;
}

bool Regex::Compare(const CString &str, vector<CString> &matches)
{
    matches.clear();

    regmatch_t mat[128];
    int ret = regexec((regex_t *) reg, str.c_str(), 128, mat, 0);

	if(ret == REG_NOMATCH)
        return false;

    for(unsigned i = 1; i < backrefs+1; ++i)
    {
        if(mat[i].rm_so == -1)
            matches.push_back(""); /* no match */
        else
            matches.push_back(str.substr(mat[i].rm_so, mat[i].rm_eo - mat[i].rm_so));
    }

    return true;
}
#endif

/* UTF-8 decoding code from glib. */
char *utf8_find_next_char (const char *p, const char *end)
{
	if (end)
		for (++p; p < end && (*p & 0xc0) == 0x80; ++p)
			;
	else if (*p) {
		for (++p; (*p & 0xc0) == 0x80; ++p)
			;
	}
	return (p == end) ? NULL : (char *)p;
}

int masks[7] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 };

int utf8_get_char_len (const char *pp)
{
	const unsigned char *p = (const unsigned char *) pp;
	if (*p < 128)					 return 1;
	else if ((*p & 0xe0) == 0xc0)  return 2;
	else if ((*p & 0xf0) == 0xe0)  return 3;
	else if ((*p & 0xf8) == 0xf0)  return 4;
	else if ((*p & 0xfc) == 0xf8)  return 5;
	else if ((*p & 0xfe) == 0xfc)  return 6;
	return -1;
}

bool utf8_is_valid(const CString &str)
{
	unsigned pos = 0;
	while(pos < str.size())
	{
		int len = utf8_get_char_len(str.c_str() + pos);
		if(len == -1)
			return false;
		if( utf8_get_char( str.c_str() + pos ) == INVALID_CHAR )
			return false;

		pos += len;
	}

	return true;
}

wchar_t utf8_get_char (const char *p)
{
  int len = utf8_get_char_len(p);
  if(len == -1)
	  return INVALID_CHAR;

  int mask = masks[len];
  wchar_t result = wchar_t(p[0] & mask);
  for (int i = 1; i < len; ++i) {
      if ((p[i] & 0xc0) != 0x80)
          return INVALID_CHAR;

	  result <<= 6;
      result |= p[i] & 0x3f;
  }

  return result;
}

const wchar_t INVALID_CHAR = 0xFFFF;

wstring CStringToWstring(const CString &str)
{
	const char *ptr = str.c_str(), *end = str.c_str()+str.size();

	wstring ret;

	while(ptr && ptr != end)
	{
		wchar_t c = utf8_get_char (ptr);
		if(c == -1)
			ret += INVALID_CHAR;
		else
			ret += c;
		ptr = utf8_find_next_char (ptr, end);
	}

	return ret;
}

CString WStringToCString(const wstring &str)
{
	CString ret;

	for(unsigned i = 0; i < str.size(); ++i)
		ret.append(WcharToUTF8(str[i]));

	return ret;
}

static int unichar_to_utf8 (wchar_t c, char *outbuf)
{
	unsigned int len = 0;
	int first;

	if (c < 0x80) {
		first = 0;
		len = 1;
	} else if (c < 0x800) {
		first = 0xc0;
		len = 2;
	} else if (c < 0x10000) {
		first = 0xe0;
		len = 3;
	} else if (c < 0x200000) {
		first = 0xf0;
		len = 4;
	} else if (c < 0x4000000) {
		first = 0xf8;
		len = 5;
	} else {
		first = 0xfc;
		len = 6;
	}

	if (outbuf)
	{
		for (int i = len - 1; i > 0; --i)
		{
			outbuf[i] = char((c & 0x3f) | 0x80);
			c >>= 6;
		}
		outbuf[0] = char(c | first);
	}

	return len;
}

CString WcharToUTF8( wchar_t c )
{
	char buf[6];
	int cnt = unichar_to_utf8(c, buf);
	return CString(buf, cnt);
}


/* Replace &#nnnn; (decimal) &xnnnn; (hex) with corresponding UTF-8 characters. */
void Replace_Unicode_Markers( CString &Text )
{
	unsigned start = 0;
	while(start < Text.size())
	{
		/* Look for &#digits; */
		bool hex = false;
		unsigned pos = Text.find("&#", start);
		if(pos == Text.npos) {
			hex = true;
			pos = Text.find("&x", start);
		}

		if(pos == Text.npos) break;
		start = pos+1;

		unsigned p = pos;
		p += 2;

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

		int numdigits = 0;
		while(p < Text.size() &&
			(hex && isxdigit(Text[p])) || (!hex && isdigit(Text[p])))
		{
		   p++;
		   numdigits++;
		}
		if(!numdigits) continue; /* must have at least one digit */
		if(p >= Text.size() || Text[p] != ';') continue;
		p++;

		int num;
		if(hex) sscanf(Text.c_str()+pos, "&x%x;", &num);
		else sscanf(Text.c_str()+pos, "&#%i;", &num);
		if(num > 0xFFFF)
			num = INVALID_CHAR;

		Text.replace(pos, p-pos, WcharToUTF8(wchar_t(num)));
	}
}

void ReplaceText( CString &Text, const map<CString,CString> &m )
{
	basic_string<char,char_traits_char_nocase> txt(Text);
	
	for(map<CString,CString>::const_iterator it = m.begin(); it != m.end(); ++it)
	{
		unsigned start = 0;
		while(1)
		{
			unsigned pos = txt.find(it->first, start);
			if(pos == txt.npos)
				break;

			txt.replace(pos, it->first.size(), it->second);
			start = pos+it->second.size();
		}
	}

	Text = txt.c_str();
}

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

/* Return the last named component of dir:
 * a/b/c -> c
 * a/b/c/ -> c
 */
CString Basename(CString dir)
{
	TrimRight(dir, "/\\");

	unsigned pos = dir.find_last_of("/\\");
	if(pos != dir.npos)
		return dir.substr(pos+1);

	return dir;
}

CString Capitalize( CString s )	
{
	if( s.GetLength()==0 )
		return "";
	CString s1 = s.Left(1);
	s1.MakeUpper();
	CString s2 = s.Right( s.GetLength()-1 );
	return s1+s2;
}
