// $Id: Wchar.cpp 8019 2007-07-14 22:30:11Z trick $

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Deadly Rooms of Death.
 *
 * The Initial Developer of the Original Code is
 * Caravel Software.
 * Portions created by the Initial Developer are Copyright (C) 2003
 * Caravel Software. All Rights Reserved.
 *
 * Contributor(s):
 * Matt Schikore (schik)
 * Mike Rimer (mrimer)
 *
 * ***** END LICENSE BLOCK ***** */

#include "Wchar.h"
#include "Assert.h"
#include "Types.h"
#include "Ports.h"  // for towlower, iswspace

//Some common small strings.  Larger strings are localizable and should be kept in database.
const WCHAR wszAsterisk[] =     { We('*'),We(0) };
const WCHAR wszOpenAngle[] =    { We('<'),We(0) };
const WCHAR wszCloseAngle[] =   { We('>'),We(0) };
const WCHAR wszColon[] =        { We(':'),We(0) };
const WCHAR wszComma[] =        { We(','),We(0) };
const WCHAR wszCRLF[] =         { We('\r'),We('\n'),We(0) };
const WCHAR wszElipsis[] =      { We('.'),We('.'), We('.'),We(0) };
const WCHAR wszEmpty[] =        { We(0) };
const WCHAR wszExclamation[] =  { We('!'),We(0) };
const WCHAR wszForwardSlash[] = { We('/'),We(0) };
const WCHAR wszHyphen[] =       { We('-'),We(0) };
const WCHAR wszParentDir[] =    { We('.'),We('.'),We(0) };
const WCHAR wszPercent[] =      { We('%'),We(0) };
const WCHAR wszPeriod[] =       { We('.'),We(0) };
const WCHAR wszQuestionMark[] = { We('?'),We(0) };
const WCHAR wszQuote[] =        { We('\"'),We(0) };
const WCHAR wszLeftParen[] =    { We('('),We(0) };
const WCHAR wszRightParen[] =   { We(')'),We(0) };
const WCHAR wszSemicolon[] =    { We(';'),We(0) };
const WCHAR wszSpace[] =        { We(' '),We(0) };
const WCHAR wszZero[] =         { We('0'),We(0) };
const WCHAR wszDrodDotDat[] =
        { We('d'),We('r'),We('o'),We('d'),We('1'),We('_'),We('5'),We('.'),
        We('d'),We('a'),We('t'),We(0) };
#ifdef WIN32
const WCHAR wszSlash[] = { We('\\'),We(0) };
#else
const WCHAR wszSlash[] = { We('/'),We(0) };
#endif

//HTML formatting strings.
const WCHAR wszHtml[] = { We('<'),We('h'),We('t'),We('m'),We('l'),We('>'), We(0) };
const WCHAR wszEndHtml[] = { We('<'),We('/'),We('h'),We('t'),We('m'),We('l'),We('>'), We(0) };
const WCHAR wszBody[] = { We('<'),We('b'),We('o'),We('d'),We('y'),We('>'), We(0) };
const WCHAR wszEndBody[] = { We('<'),We('/'),We('b'),We('o'),We('d'),We('y'),We('>'), We(0) };
const WCHAR wszHeader[] = { We('<'),We('h'),We('3'),We('>'), We(0) };
const WCHAR wszEndHeader[] = { We('<'),We('/'),We('h'),We('3'),We('>'), We(0) };
const WCHAR wszBTag[] = { We('<'),We('b'),We('>'), We(0) };
const WCHAR wszEndBTag[] = { We('<'),We('/'),We('b'),We('>'), We(0) };
const WCHAR wszITag[] = { We('<'),We('i'),We('>'), We(0) };
const WCHAR wszEndITag[] = { We('<'),We('/'),We('i'),We('>'), We(0) };
const WCHAR wszPTag[] = { We('<'),We('p'),We('>'), We(0) };

//*****************************************************************************
void AsciiToUnicode(const char *psz, WSTRING &wstr)
{
	WCHAR *pwczConvertBuffer = new WCHAR[strlen(psz) + 1];

	const unsigned char *pszRead = (unsigned char*)psz;
	WCHAR *pwczWrite = pwczConvertBuffer;
	while (*pszRead != '\0')
	{
		pWCv(pwczWrite++) = static_cast<WCHAR_t>(*(pszRead++));
	}
	pWCv(pwczWrite) = 0;

	wstr = pwczConvertBuffer;
	delete[] pwczConvertBuffer;
}

//*****************************************************************************
void UnicodeToAscii(const WSTRING& wstr, char *psz)
{
	unsigned char* pszWrite = (unsigned char*)psz;
	for (WSTRING::const_iterator i = wstr.begin(); i != wstr.end(); ++i)
	{
		*(pszWrite++) = (unsigned char)pWCv(i);
	}
	*(pszWrite++) = 0;
}

//*****************************************************************************
void UnicodeToAscii(const WSTRING& wstr, string &str)
{
	str.reserve(wstr.size());
	for (WSTRING::const_iterator i = wstr.begin(); i != wstr.end(); ++i)
		str += (unsigned char)pWCv(i);
}

//*****************************************************************************
#define MB_BLOCK_BEGIN(mask,cmp) \
	ucs4 = (ch & mask) << 6; \
	if ((ch = *++*ppsz) < cmp) \
		success = false; \
	if (ch) do \
	{ \
		ucs4 += (ch & 0x3f)

#define MB_BLOCK_END \
	} while (0)

#define ADD_NEXT_BYTE \
	if ((ch = *++*ppsz)) \
	{ \
		ucs4 <<= 6; \
		ucs4 += (ch & 0x3f); \
	} \
	else \
		return 0xffffffff

unsigned long UTF8ToUCS4Char(
//Convert UTF-8 to UCS-4 (Unicode). (Based on info from RFC 2279.)
//
//Params:
	const char **ppsz)   //(in/out)  Pointer to pointer to UTF-8 char-data.
								//          The size of the char-data is determined
								//          automagically, so no need for 0-termination.
								//          The pointer will be updated to point to the
								//          end of the current UTF-8 char after the
								//          call, even if it was an invalid char.  If a
								//          zero byte is encountered, conversion will
								//          abort and the pointer will point to that.
//
//Returns:
//UCS-4 char, or 0xffffffff if the UTF-8 code was invalid.
{
	ASSERTP(ppsz && *ppsz, "NULL pointer passed to UTF8ToUCS4Char");

	unsigned char ch = **ppsz;
	bool success = true;
	DWORD ucs4 = DWORD(-1);

	// Could be done with a loop, but this is faster
	if (ch < 0x80)      // 00-7f: 1 byte
		ucs4 = ch;
	else if (ch < 0xc2) // 80-c1: error
		success = false;
	else if (ch < 0xe0) // c2-df: 2 bytes
	{
		ucs4 = (ch & 0x1f);
		ADD_NEXT_BYTE;
	}
	else if (ch < 0xf0) // e0-ef: 3 bytes
	{
		MB_BLOCK_BEGIN(0x0f,0xa0);
			ADD_NEXT_BYTE;
		MB_BLOCK_END;
	}
	else if (ch < 0xf8) // f0-f7: 4 bytes
	{
		MB_BLOCK_BEGIN(0x07,0x90);
			ADD_NEXT_BYTE;
			ADD_NEXT_BYTE;
		MB_BLOCK_END;
	}
	else if (ch < 0xfc) // f8-fb: 5 bytes
	{
		MB_BLOCK_BEGIN(0x03,0x88);
			ADD_NEXT_BYTE;
			ADD_NEXT_BYTE;
			ADD_NEXT_BYTE;
		MB_BLOCK_END;
	}
	else if (ch < 0xfe) // fc-fd: 6 bytes
	{
		MB_BLOCK_BEGIN(0x01,0x84);
			ADD_NEXT_BYTE;
			ADD_NEXT_BYTE;
			ADD_NEXT_BYTE;
			ADD_NEXT_BYTE;
		MB_BLOCK_END;
	}
	else                // fe-ff: error
		success = false;

	ASSERT(!success || ucs4 != DWORD(-1));
	return(success ? ucs4 : 0xffffffff);
}
#undef MB_BLOCK_BEGIN
#undef MB_BLOCK_END
#undef ADD_NEXT_BYTE

//*****************************************************************************
WSTRING UTF8ToUCS2str(const char* s, const UINT len)
//Returns: a utf-8 string converted into a 16-bit Unicode string
{
	WCHAR *wszText = new WCHAR[len + 1];
	WSTRING wstr;

	UINT i = 0;
	for (const char *s_end = s + len; s < s_end; ++s)
	{
		// UTF-8 to UCS-2 conversion (UCS-4 is just stripped)
		wszText[i++] = W_t(UTF8ToUCS4Char(&s));
	}
	WCv(wszText[i]) = 0;

	wstr += wszText;
	delete[] wszText;
	return wstr;
}

//*****************************************************************************
UINT utf8len(const WCHAR* pwczText)
//Returns: byte length of 16-bit Unicode string, converted to utf-8
{
	UINT wLength=0;
	while (WCv(*pwczText))
	{
		const UINT ch=WCv(*pwczText++);
		if (ch < 0x7F)
			++wLength;
		else if (ch < 0x7FF)
			wLength+=2;
		else if (ch < 0xFFFF)
			wLength+=3;
		else if (ch < 0x1FFFFF)
			wLength+=4;
		else if (ch < 0x3FFFFFF)
			wLength+=5;
		else
			wLength+=6;
	}
	return wLength;
}

//*****************************************************************************
UINT to_utf8(const WCHAR *pwStr, BYTE* &pbOutStr)
//OUT: allocated byte buffer of Unicode-2 text passed in, converted to utf-8.
//Caller must delete allocated byte buffer.
{
	ASSERT(pwStr);
	ASSERT(!pbOutStr);

	const UINT wByteLength = utf8len(pwStr);
	pbOutStr = new BYTE[wByteLength]; //NOT null terminated
	BYTE *res = pbOutStr;

	while (WCv(*pwStr))
	{
		//Convert one Unicode character and append to byte buffer.
		const UINT ch=WCv(*pwStr++);
		if (ch < 0x7F) {
			*res++=static_cast<char unsigned>(ch);
		} else if (ch < 0x7FF) {
			*res++=static_cast<char unsigned>(0xC0 | ((ch >> 6) & 0x1F));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 0) & 0x3F));
		} else if (ch < 0xFFFF) {
			*res++=static_cast<char unsigned>(0xE0 | ((ch >> 12) & 0x0F));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 6) & 0x3F));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 0) & 0x3F));
		} else if (ch < 0x1FFFFF) {
			*res++=static_cast<char unsigned>(0xF0 | ((ch >> 18) & 0x07));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 12) & 0x3F));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 6) & 0x3F));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 0) & 0x3F));
		} else if (ch < 0x3FFFFFF) {
			*res++=static_cast<char unsigned>(0xF8 | ((ch >> 24) & 0x03));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 18) & 0x3F));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 12) & 0x3F));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 6) & 0x3F));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 0) & 0x3F));
		} else {
			*res++=static_cast<char unsigned>(0xFC | ((ch >> 30) & 0x03));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 24) & 0x3F));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 18) & 0x3F));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 12) & 0x3F));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 6) & 0x3F));
			*res++=static_cast<char unsigned>(0x80 | ((ch >> 0) & 0x3F));
		}
	}

	return wByteLength;
}

//*****************************************************************************
bool charFilenameSafe(const WCHAR wc)
//Returns: whether char can be used in filenames
//
//ASSUME: alphanumeric and space and some punctuation are usable
{
	const WCHAR lwc = towlower(wc);
	if (wc >= 256)
		return true; //assume Unicode chars are usable
	return
		(lwc >= 'a' && lwc <= 'z') ||
		(lwc >= '0' && lwc <= '9') ||
		lwc == ' ' || lwc == '-' || lwc == '_';
}

//*****************************************************************************
WSTRING filenameFilter(const WSTRING &wstr)
//Returns: input string with all characters, unusable in filenames, removed
{
	WSTRING filteredStr;
	for (WSTRING::const_iterator i = wstr.begin(); i != wstr.end(); ++i)
	{
		const WCHAR wc = (WCHAR)*i;
		if (charFilenameSafe(wc))
			filteredStr += wc;
	}
	return filteredStr;
}

//*****************************************************************************
WSTRING filterFirstLettersAndNumbers(const WSTRING &wstr)
//Returns: string with only the first letter from each word, and digits
{
	WSTRING filteredStr;
	bool bAfterWhiteSpace = true;
	for (WSTRING::const_iterator i = wstr.begin(); i != wstr.end(); ++i)
	{
		const WCHAR wc = (WCHAR)*i;
		if (iswspace(wc))
			bAfterWhiteSpace = true;
		else if ((bAfterWhiteSpace && iswalpha(wc)) || iswdigit(wc))
		{
			filteredStr += wc;
			bAfterWhiteSpace = false;
		}
	}
	return filteredStr;
}

//*****************************************************************************
WSTRING filterUpperCase(const WSTRING &wstr)
//Returns: string with only upper-case characters
{
	WSTRING filteredStr;
	for (WSTRING::const_iterator i = wstr.begin(); i != wstr.end(); ++i)
	{
		const WCHAR wc = (WCHAR)*i;
		if (iswupper(wc))
			filteredStr += wc;
	}
	return filteredStr;
}

//*****************************************************************************
WCHAR* getFilenameFromPath(const WCHAR *wstrFilepath) //(in) file path + name
//Returns: pointer to filename part of path only
{
	const WCHAR *wszFilename = wstrFilepath;
	while (*wszFilename != wszEmpty[0]) ++wszFilename; //Search to end.
	while (wszFilename >= wstrFilepath && *wszFilename != wszSlash[0])
		--wszFilename; //Search backwards to last backslash.
	if (*wszFilename == wszSlash[0])
		++wszFilename;  //go past backslash

	return (WCHAR*)wszFilename;
}

//*****************************************************************************
int _Wtoi(const WCHAR* wsz)
{
	static char buffer[33];
	UnicodeToAscii(wsz, buffer);
	return atoi(buffer);
}

//*****************************************************************************
WCHAR* _itoW(int value, WCHAR* wcz, int radix)
{
	char buffer[66];
	int i = 0;
	bool bNegative = false;
	if (value < 0) //negative?
	{
		value = -value;
		bNegative = true;
	}
	do {
		int val = value % radix;
		if (val < 10) buffer[i++] = (value % radix) + '0';
		else buffer[i++] = (value % radix) - 10 + 'A';
	} while ((value /= radix) > 0);
	if (bNegative)
		buffer[i++] = '-';

	buffer[i] = '\0';

	char c;
	int j;
	for (i = 0, j = strlen(buffer)-1; i<j; ++i, --j) {
		c = buffer[i];
		buffer[i] = buffer[j];
		buffer[j] = c;
	}
	WSTRING wStr;
	AsciiToUnicode(buffer, wStr);
	WCScpy(wcz, wStr.c_str());
	return wcz;
}

//*****************************************************************************
WCHAR* _ltoW(long value, WCHAR* wcz, int radix)
{
	char buffer[66];
	int i = 0;
	bool bNegative = false;
	if (value < 0) //negative?
	{
		value = -value;
		bNegative = true;
	}
	do {
		int val = value % radix;
		if (val < 10) buffer[i++] = static_cast<char>((value % radix) + '0');
		else buffer[i++] = static_cast<char>((value % radix) - 10 + 'A');
	} while ((value /= radix) > 0);
	if (bNegative)
		buffer[i++] = '-';

	buffer[i] = '\0';

	char c;
	int j;
	for (i = 0, j = strlen(buffer)-1; i<j; ++i, --j) {
		c = buffer[i];
		buffer[i] = buffer[j];
		buffer[j] = c;
	}
	WSTRING wStr;
	AsciiToUnicode(buffer, wStr);
	WCScpy(wcz, wStr.c_str());
	return wcz;
}

//*****************************************************************************
UINT WCSlen(const WCHAR* wsz)
{
	UINT length = 0;
	const WCHAR* wszRead = wsz;
	while (*wszRead++ != 0)
		++length;

	return length;
}

//*****************************************************************************
WCHAR* WCScpy(WCHAR* pwczDest, const WCHAR* pwczSrc)
{
	UINT index = 0;
	while (pwczSrc[index] != 0)
		++index;

	memcpy(pwczDest, pwczSrc, (index+1) * sizeof(WCHAR));
	return pwczDest;
}

//*****************************************************************************
WCHAR* WCSncpy(WCHAR* pwczDest, const WCHAR* pwczSrc, UINT n)
{
	WCHAR *pwczWrite = pwczDest;
	const WCHAR *pwczRead = pwczSrc;

	while ((*pwczRead != 0) && n--)
	{
		*(pwczWrite++) = *(pwczRead++);
	}
	pWCv(pwczWrite) = 0;
	return pwczDest;
}

//*****************************************************************************
WCHAR* WCScat(WCHAR *pwcz1, const WCHAR *pwcz2)
{
	WCHAR *pwczWrite = pwcz1;
	while (*pwczWrite != 0)
		++pwczWrite;

	const WCHAR *pwczRead = pwcz2;
	while (*pwczRead != 0)
		*(pwczWrite++) = *(pwczRead++);
	pWCv(pwczWrite) = 0;

	return pwcz1;
}

//*****************************************************************************
int WCScmp(const WCHAR* pwcz1, const WCHAR* pwcz2)
{
	const UINT len1 = WCSlen(pwcz1);
	const UINT len2 = WCSlen(pwcz2);
	UINT index = 0;

	while (index < len1 && index < len2) {
		if (pwcz1[index] < pwcz2[index]) return -1;
		if (pwcz2[index] < pwcz1[index]) return 1;
		++index;
	}
	// If we get to this point, either they're the same or one string
	// is longer than the other.
	if (len1 == len2) return 0;
	if (len1 < len2) return -1;
	return 1;
}

//*****************************************************************************
int WCSicmp(const WCHAR* pwcz1, const WCHAR* pwcz2)
{
	const UINT len1 = WCSlen(pwcz1);
	const UINT len2 = WCSlen(pwcz2);
	UINT index = 0;

	while (index < len1 && index < len2) {
		if (towlower(pwcz1[index]) < towlower(pwcz2[index])) return -1;
		if (towlower(pwcz2[index]) < towlower(pwcz1[index])) return 1;
		++index;
	}
	// If we get to this point, either they're the same or one string
	// is longer than the other.
	if (len1 == len2) return 0;
	if (len1 < len2) return -1;
	return 1;
}

//*****************************************************************************
int WCSncmp(const WCHAR* pwcz1, const WCHAR* pwcz2, const UINT n)
{
	UINT len1 = 0, len2 = 0;
  while (len1 < n && pwcz1[len1] != 0) ++len1;
  while (len2 < n && pwcz2[len2] != 0) ++len2;
	UINT index = 0;

	while (index < len1 && index < len2 && index < n) {
		if (pwcz1[index] < pwcz2[index]) return -1;
		if (pwcz2[index] < pwcz1[index]) return 1;
		++index;
	}
	// If we get to this point, either n chars have been matched,
	// or they're the same, or one string is longer than the other.
	if (index == n) return 0;
	if (len1 == len2) return 0;
	if (len1 < len2) return -1;
	return 1;
}

WCHAR* WCStok(WCHAR *wszStart, const WCHAR *wszDelim)
{
	static WCHAR *wszNext = NULL;

	if ((NULL == wszStart) && (NULL == (wszStart = wszNext)))
		return NULL;

	UINT dindex;

	// Skip initial delimiters
	for (; pWCv(wszStart); ++wszStart)
	{
		for (dindex = 0;
				wszDelim[dindex] && (*wszStart != wszDelim[dindex]);
				++dindex);

		if (!wszDelim[dindex]) break;  // No more initial delimiters
	}

	// Now hunt for next delimiters
	for (wszNext = wszStart; pWCv(wszNext); ++wszNext)
	{
		for (dindex = 0;
				wszDelim[dindex] && (*wszNext != wszDelim[dindex]);
				++dindex);

		if (!wszDelim[dindex]) continue;

		// Found a delimiter: Terminate token and prepare for next (if any)
		pWCv(wszNext) = 0;
		wszNext = (pWCv(++wszNext) ? wszNext : NULL);
		return wszStart;
	}

	// No delimiters, end of string
	wszNext = NULL;
	return wszStart;
}

//*****************************************************************************
WCHAR* fgetWs(WCHAR* pwcz, int n, FILE* pFile)
{
	ASSERT(pwcz != NULL);

	WCHAR c;
	WCHAR* wszWrite = pwcz;
	int numread;
	do {
		numread = fread( &c, sizeof(WCHAR), 1, pFile );
		if (numread == 0)
		{
			//Reached end of file data.
			pWCv(wszWrite++) = '\0';   //null terminate string
			break;
		}
		*(wszWrite++) = c;
		--n;
	} while (c != 0 && n > 0);

	return pwcz;
}

//*****************************************************************************
WSTRING WCSReplace(
//Replaces all occurrences of a string within another string.
//Returns: copy of transformed string
	WSTRING const &source, //Source string.
	WSTRING const &from,   //The substring to find.
	WSTRING const &to)     //Replace instances of 'from'.
{
	WSTRING result;
	UINT processed=0;
	for (;;) {
		UINT loc = source.find(from, processed);
		if (loc == std::string::npos)
			return result + source.substr(processed);
		ASSERT(loc>=processed);
		result += source.substr(processed, loc-processed)+to;
		processed = loc + from.size();
	}
}
