#pragma warning (disable:4786)
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

#include "comutil.hpp"
#include <limits.h>
#include <windows.h>

/////////////////////////////////////////////////////////////////////////////
// static class data, special inlines

// comline_afxChNil is left for backward compatibility
TCHAR comline_afxChNil = '\0';

// For an empty string, m_pchData will point here
// (note: avoids special case of checking for NULL m_pchData)
// empty string data (and locked)
static int rgInitData[] = { -1, 0, 0, 0 };
static  CCom_StringData* comline_afxDataNil = (CCom_StringData*)&rgInitData;
static LPCTSTR comline_afxPchNil = (LPCTSTR)(((BYTE*)&rgInitData)+sizeof(CCom_StringData));
// special function to make comline_afxEmptyString work even during initialization
const CCom_String&  comline_AfxGetEmptyString()
	{ return *(CCom_String*)&comline_afxPchNil; }



__declspec (nothrow) bool comline_AfxIsValidString(LPCWSTR lpsz, int nLength)
{
	if (lpsz == NULL)
		return false;
	return ::IsBadStringPtrW(lpsz, nLength) == 0;
}

__declspec (nothrow) bool comline_AfxIsValidString(LPCSTR lpsz, int nLength)
{
	if (lpsz == NULL)
		return false;
	return ::IsBadStringPtrA(lpsz, nLength) == 0;
}

__declspec (nothrow) bool comline_AfxIsValidAddress(const void* lp, UINT nBytes, bool bReadWrite)
{
	// simple version using Win-32 APIs for pointer validation.
	return (lp != NULL && !IsBadReadPtr(lp, nBytes) &&
		(!bReadWrite || !IsBadWritePtr((LPVOID)lp, nBytes)));
}

//////////////////////////////////////////////////////////////////////////////
// Construction/Destruction

__declspec (nothrow) CCom_String::CCom_String()
{
	Init();
}

__declspec (nothrow) CCom_String::CCom_String(const CCom_String& stringSrc)
{
	assert(stringSrc.GetData()->nRefs != 0);
	if (stringSrc.GetData()->nRefs >= 0)
	{
		assert(stringSrc.GetData() != comline_afxDataNil);
		m_pchData = stringSrc.m_pchData;
		InterlockedIncrement(&GetData()->nRefs);
	}
	else
	{
		Init();
		*this = stringSrc.m_pchData;
	}
}

__declspec (nothrow) void CCom_String::AllocBuffer(int nLen)
// always allocate one extra character for '\0' termination
// assumes [optimistically] that data length will equal allocation length
{
	assert(nLen >= 0);
	assert(nLen <= INT_MAX-1);    // max size (enough room for 1 extra)

	if (nLen == 0)
		Init();
	else
	{
		CCom_StringData* pData =
			(CCom_StringData*)malloc (sizeof(CCom_StringData) + (nLen+1)*sizeof(TCHAR));
		pData->nRefs = 1;
		pData->data()[nLen] = '\0';
		pData->nDataLength = nLen;
		pData->nAllocLength = nLen;
		m_pchData = pData->data();
	}
}

__declspec (nothrow) void CCom_String::Release()
{
	if (GetData() != comline_afxDataNil)
	{
		assert(GetData()->nRefs != 0);
		if (InterlockedDecrement(&GetData()->nRefs) <= 0)
			free (GetData());
		Init();
	}
}

__declspec (nothrow) void PASCAL CCom_String::Release(CCom_StringData* pData)
{
	if (pData != comline_afxDataNil)
	{
		assert(pData->nRefs != 0);
		if (InterlockedDecrement(&pData->nRefs) <= 0)
			free (pData);
	}
}

__declspec (nothrow) void CCom_String::Empty()
{
	if (GetData()->nDataLength == 0)
		return;
	if (GetData()->nRefs >= 0)
		Release();
	else
		*this = &comline_afxChNil;
	assert(GetData()->nDataLength == 0);
	assert(GetData()->nRefs < 0 || GetData()->nAllocLength == 0);
}

__declspec (nothrow) void CCom_String::CopyBeforeWrite()
{
	if (GetData()->nRefs > 1)
	{
		CCom_StringData* pData = GetData();
		Release();
		AllocBuffer(pData->nDataLength);
		memcpy(m_pchData, pData->data(), (pData->nDataLength+1)*sizeof(TCHAR));
	}
	assert(GetData()->nRefs <= 1);
}

__declspec (nothrow) void CCom_String::AllocBeforeWrite(int nLen)
{
	if (GetData()->nRefs > 1 || nLen > GetData()->nAllocLength)
	{
		Release();
		AllocBuffer(nLen);
	}
	assert(GetData()->nRefs <= 1);
}

__declspec (nothrow) CCom_String::~CCom_String()
//  free any attached data
{
	if (GetData() != comline_afxDataNil)
	{
		if (InterlockedDecrement(&GetData()->nRefs) <= 0)
			free (GetData());
	}
}

//////////////////////////////////////////////////////////////////////////////
// Helpers for the rest of the implementation

__declspec (nothrow) void CCom_String::AllocCopy(CCom_String& dest, int nCopyLen, int nCopyIndex,
	 int nExtraLen) const
{
	// will clone the data attached to this string
	// allocating 'nExtraLen' characters
	// Places results in uninitialized string 'dest'
	// Will copy the part or all of original data to start of new string

	int nNewLen = nCopyLen + nExtraLen;
	if (nNewLen == 0)
	{
		dest.Init();
	}
	else
	{
		dest.AllocBuffer(nNewLen);
		memcpy(dest.m_pchData, m_pchData+nCopyIndex, nCopyLen*sizeof(TCHAR));
	}
}

//////////////////////////////////////////////////////////////////////////////
// More sophisticated construction

__declspec (nothrow) CCom_String::CCom_String(LPCTSTR lpsz)
{
	Init();
	{
		int nLen = SafeStrlen(lpsz);
		if (nLen != 0)
		{
			AllocBuffer(nLen);
			memcpy(m_pchData, lpsz, nLen*sizeof(TCHAR));
		}
	}
}

/////////////////////////////////////////////////////////////////////////////
// Special conversion constructors

#ifdef _UNICODE
__declspec (nothrow) CCom_String::CCom_String(LPCSTR lpsz)
{
	Init();
	int nSrcLen = lpsz != NULL ? lstrlenA(lpsz) : 0;
	if (nSrcLen != 0)
	{
		AllocBuffer(nSrcLen);
		_mbstowcsz(m_pchData, lpsz, nSrcLen+1);
		ReleaseBuffer();
	}
}
#else //_UNICODE
__declspec (nothrow) CCom_String::CCom_String(LPCWSTR lpsz)
{
	Init();
	int nSrcLen = lpsz != NULL ? wcslen(lpsz) : 0;
	if (nSrcLen != 0)
	{
		AllocBuffer(nSrcLen*2);
		_wcstombsz(m_pchData, lpsz, (nSrcLen*2)+1);
		ReleaseBuffer();
	}
}
#endif //!_UNICODE

//////////////////////////////////////////////////////////////////////////////
// Assignment operators
//  All assign a new value to the string
//      (a) first see if the buffer is big enough
//      (b) if enough room, copy on top of old buffer, set size and type
//      (c) otherwise free old string data, and create a new one
//
//  All routines return the new string (but as a 'const CCom_String&' so that
//      assigning it again will cause a copy, eg: s1 = s2 = "hi there".
//

__declspec (nothrow) void CCom_String::AssignCopy(int nSrcLen, LPCTSTR lpszSrcData)
{
	AllocBeforeWrite(nSrcLen);
	memcpy(m_pchData, lpszSrcData, nSrcLen*sizeof(TCHAR));
	GetData()->nDataLength = nSrcLen;
	m_pchData[nSrcLen] = '\0';
}

__declspec (nothrow) const CCom_String& CCom_String::operator=(const CCom_String& stringSrc)
{
	if (m_pchData != stringSrc.m_pchData)
	{
		if ((GetData()->nRefs < 0 && GetData() != comline_afxDataNil) ||
			stringSrc.GetData()->nRefs < 0)
		{
			// actual copy necessary since one of the strings is locked
			AssignCopy(stringSrc.GetData()->nDataLength, stringSrc.m_pchData);
		}
		else
		{
			// can just copy references around
			Release();
			assert(stringSrc.GetData() != comline_afxDataNil);
			m_pchData = stringSrc.m_pchData;
			InterlockedIncrement(&GetData()->nRefs);
		}
	}
	return *this;
}

__declspec (nothrow) const CCom_String& CCom_String::operator=(LPCTSTR lpsz)
{
	assert(lpsz == NULL || comline_AfxIsValidString(lpsz, false));
	AssignCopy(SafeStrlen(lpsz), lpsz);
	return *this;
}

/////////////////////////////////////////////////////////////////////////////
// Special conversion assignment

#ifdef _UNICODE
__declspec (nothrow) const CCom_String& CCom_String::operator=(LPCSTR lpsz)
{
	int nSrcLen = lpsz != NULL ? lstrlenA(lpsz) : 0;
	AllocBeforeWrite(nSrcLen);
	_mbstowcsz(m_pchData, lpsz, nSrcLen+1);
	ReleaseBuffer();
	return *this;
}
#else //!_UNICODE
__declspec (nothrow) const CCom_String& CCom_String::operator=(LPCWSTR lpsz)
{
	int nSrcLen = lpsz != NULL ? wcslen(lpsz) : 0;
	AllocBeforeWrite(nSrcLen*2);
	_wcstombsz(m_pchData, lpsz, (nSrcLen*2)+1);
	ReleaseBuffer();
	return *this;
}
#endif  //!_UNICODE

//////////////////////////////////////////////////////////////////////////////
// concatenation

// NOTE: "operator+" is done as friend functions for simplicity
//      There are three variants:
//          CCom_String + CCom_String
// and for ? = TCHAR, LPCTSTR
//          CCom_String + ?
//          ? + CCom_String

__declspec (nothrow) void CCom_String::ConcatCopy(int nSrc1Len, LPCTSTR lpszSrc1Data,
	int nSrc2Len, LPCTSTR lpszSrc2Data)
{
  // -- master concatenation routine
  // Concatenate two sources
  // -- assume that 'this' is a new CCom_String object

	int nNewLen = nSrc1Len + nSrc2Len;
	if (nNewLen != 0)
	{
		AllocBuffer(nNewLen);
		memcpy(m_pchData, lpszSrc1Data, nSrc1Len*sizeof(TCHAR));
		memcpy(m_pchData+nSrc1Len, lpszSrc2Data, nSrc2Len*sizeof(TCHAR));
	}
}

__declspec (nothrow) CCom_String  operator+(const CCom_String& string1, const CCom_String& string2)
{
	CCom_String s;
	s.ConcatCopy(string1.GetData()->nDataLength, string1.m_pchData,
		string2.GetData()->nDataLength, string2.m_pchData);
	return s;
}

__declspec (nothrow) CCom_String  operator+(const CCom_String& string, LPCTSTR lpsz)
{
	assert(lpsz == NULL || comline_AfxIsValidString(lpsz, false));
	CCom_String s;
	s.ConcatCopy(string.GetData()->nDataLength, string.m_pchData,
		CCom_String::SafeStrlen(lpsz), lpsz);
	return s;
}

__declspec (nothrow) CCom_String  operator+(LPCTSTR lpsz, const CCom_String& string)
{
	assert(lpsz == NULL || comline_AfxIsValidString(lpsz, false));
	CCom_String s;
	s.ConcatCopy(CCom_String::SafeStrlen(lpsz), lpsz, string.GetData()->nDataLength,
		string.m_pchData);
	return s;
}

//////////////////////////////////////////////////////////////////////////////
// concatenate in place

__declspec (nothrow) void CCom_String::ConcatInPlace(int nSrcLen, LPCTSTR lpszSrcData)
{
	//  -- the main routine for += operators

	// concatenating an empty string is a no-op!
	if (nSrcLen == 0)
		return;

	// if the buffer is too small, or we have a width mis-match, just
	//   allocate a new buffer (slow but sure)
	if (GetData()->nRefs > 1 || GetData()->nDataLength + nSrcLen > GetData()->nAllocLength)
	{
		// we have to grow the buffer, use the ConcatCopy routine
		CCom_StringData* pOldData = GetData();
		ConcatCopy(GetData()->nDataLength, m_pchData, nSrcLen, lpszSrcData);
		assert(pOldData != NULL);
		CCom_String::Release(pOldData);
	}
	else
	{
		// fast concatenation when buffer big enough
		memcpy(m_pchData+GetData()->nDataLength, lpszSrcData, nSrcLen*sizeof(TCHAR));
		GetData()->nDataLength += nSrcLen;
		assert(GetData()->nDataLength <= GetData()->nAllocLength);
		m_pchData[GetData()->nDataLength] = '\0';
	}
}

__declspec (nothrow) const CCom_String& CCom_String::operator+=(LPCTSTR lpsz)
{
	assert(lpsz == NULL || comline_AfxIsValidString(lpsz, false));
	ConcatInPlace(SafeStrlen(lpsz), lpsz);
	return *this;
}

__declspec (nothrow) const CCom_String& CCom_String::operator+=(TCHAR ch)
{
	ConcatInPlace(1, &ch);
	return *this;
}

__declspec (nothrow) const CCom_String& CCom_String::operator+=(const CCom_String& string)
{
	ConcatInPlace(string.GetData()->nDataLength, string.m_pchData);
	return *this;
}

///////////////////////////////////////////////////////////////////////////////
// Advanced direct buffer access

__declspec (nothrow) LPTSTR CCom_String::GetBuffer(int nMinBufLength)
{
	assert(nMinBufLength >= 0);

	if (GetData()->nRefs > 1 || nMinBufLength > GetData()->nAllocLength)
	{
		// we have to grow the buffer
		CCom_StringData* pOldData = GetData();
		int nOldLen = GetData()->nDataLength;   // AllocBuffer will tromp it
		if (nMinBufLength < nOldLen)
			nMinBufLength = nOldLen;
		AllocBuffer(nMinBufLength);
		memcpy(m_pchData, pOldData->data(), (nOldLen+1)*sizeof(TCHAR));
		GetData()->nDataLength = nOldLen;
		CCom_String::Release(pOldData);
	}
	assert(GetData()->nRefs <= 1);

	// return a pointer to the character storage for this string
	assert(m_pchData != NULL);
	return m_pchData;
}

__declspec (nothrow) void CCom_String::ReleaseBuffer(int nNewLength)
{
	CopyBeforeWrite();  // just in case GetBuffer was not called

	if (nNewLength == -1)
		nNewLength = lstrlen(m_pchData); // zero terminated

	assert(nNewLength <= GetData()->nAllocLength);
	GetData()->nDataLength = nNewLength;
	m_pchData[nNewLength] = '\0';
}

__declspec (nothrow) LPTSTR CCom_String::GetBufferSetLength(int nNewLength)
{
	assert(nNewLength >= 0);

	GetBuffer(nNewLength);
	GetData()->nDataLength = nNewLength;
	m_pchData[nNewLength] = '\0';
	return m_pchData;
}

__declspec (nothrow) void CCom_String::FreeExtra()
{
	assert(GetData()->nDataLength <= GetData()->nAllocLength);
	if (GetData()->nDataLength != GetData()->nAllocLength)
	{
		CCom_StringData* pOldData = GetData();
		AllocBuffer(GetData()->nDataLength);
		memcpy(m_pchData, pOldData->data(), pOldData->nDataLength*sizeof(TCHAR));
		assert(m_pchData[GetData()->nDataLength] == '\0');
		CCom_String::Release(pOldData);
	}
	assert(GetData() != NULL);
}

__declspec (nothrow) LPTSTR CCom_String::LockBuffer()
{
	LPTSTR lpsz = GetBuffer(0);
	GetData()->nRefs = -1;
	return lpsz;
}

__declspec (nothrow) void CCom_String::UnlockBuffer()
{
	assert(GetData()->nRefs == -1);
	if (GetData() != comline_afxDataNil)
		GetData()->nRefs = 1;
}

///////////////////////////////////////////////////////////////////////////////
// Commonly used routines (rarely used routines in STREX.CPP)

__declspec (nothrow) int CCom_String::Find(TCHAR ch) const
{
	// find first single character
	LPTSTR lpsz = _tcschr(m_pchData, (_TUCHAR)ch);

	// return -1 if not found and index otherwise
	return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData);
}

__declspec (nothrow) int CCom_String::FindOneOf(LPCTSTR lpszCharSet) const
{
	assert(comline_AfxIsValidString(lpszCharSet, false));
	LPTSTR lpsz = _tcspbrk(m_pchData, lpszCharSet);
	return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData);
}

__declspec (nothrow) void CCom_String::MakeUpper()
{
	CopyBeforeWrite();
	::CharUpper(m_pchData);
}

__declspec (nothrow) void CCom_String::MakeLower()
{
	CopyBeforeWrite();
	::CharLower(m_pchData);
}

__declspec (nothrow) void CCom_String::MakeReverse()
{
	CopyBeforeWrite();
	_tcsrev(m_pchData);
}

__declspec (nothrow) void CCom_String::SetAt(int nIndex, TCHAR ch)
{
	assert(nIndex >= 0);
	assert(nIndex < GetData()->nDataLength);

	CopyBeforeWrite();
	m_pchData[nIndex] = ch;
}

#ifndef _UNICODE
__declspec (nothrow) void CCom_String::AnsiToOem()
{
	CopyBeforeWrite();
	::AnsiToOem(m_pchData, m_pchData);
}
__declspec (nothrow) void CCom_String::OemToAnsi()
{
	CopyBeforeWrite();
	::OemToAnsi(m_pchData, m_pchData);
}
#endif

///////////////////////////////////////////////////////////////////////////////
// CCom_String conversion helpers (these use the current system locale)

__declspec (nothrow) int _cdecl _wcstombsz(char* mbstr, const wchar_t* wcstr, size_t count)
{
	if (count == 0 && mbstr != NULL)
		return 0;

	int result = ::WideCharToMultiByte(CP_ACP, 0, wcstr, -1,
		mbstr, count, NULL, NULL);
	assert(mbstr == NULL || result <= (int)count);
	if (result > 0)
		mbstr[result-1] = 0;
	return result;
}

__declspec (nothrow) int _cdecl _mbstowcsz(wchar_t* wcstr, const char* mbstr, size_t count)
{
	if (count == 0 && wcstr != NULL)
		return 0;

	int result = ::MultiByteToWideChar(CP_ACP, 0, mbstr, -1,
		wcstr, count);
	assert(wcstr == NULL || result <= (int)count);
	if (result > 0)
		wcstr[result-1] = 0;
	return result;
}

__declspec (nothrow) LPWSTR  comline_AfxA2WHelper(LPWSTR lpw, LPCSTR lpa, int nChars)
{
	if (lpa == NULL)
		return NULL;
	assert(lpw != NULL);
	// verify that no illegal character present
	// since lpw was allocated based on the size of lpa
	// don't worry about the number of chars
	lpw[0] = '\0';
	(MultiByteToWideChar(CP_ACP, 0, lpa, -1, lpw, nChars));
	return lpw;
}

__declspec (nothrow) LPSTR  comline_AfxW2AHelper(LPSTR lpa, LPCWSTR lpw, int nChars)
{
	if (lpw == NULL)
		return NULL;
	assert(lpa != NULL);
	// verify that no illegal character present
	// since lpa was allocated based on the size of lpw
	// don't worry about the number of chars
	lpa[0] = '\0';
	(WideCharToMultiByte(CP_ACP, 0, lpw, -1, lpa, nChars, NULL, NULL));
	return lpa;
}

///////////////////////////////////////////////////////////////////////////////





__declspec (nothrow) CCom_String::CCom_String(TCHAR ch, int nLength)
{
	assert(!_istlead(ch));    // can't create a lead byte string
	Init();
	if (nLength >= 1)
	{
		AllocBuffer(nLength);
#ifdef _UNICODE
		for (int i = 0; i < nLength; i++)
			m_pchData[i] = ch;
#else
		memset(m_pchData, ch, nLength);
#endif
	}
}

__declspec (nothrow) CCom_String::CCom_String(LPCTSTR lpch, int nLength)
{
	Init();
	if (nLength != 0)
	{
		assert(comline_AfxIsValidAddress(lpch, nLength, false));
		AllocBuffer(nLength);
		memcpy(m_pchData, lpch, nLength*sizeof(TCHAR));
	}
}

//////////////////////////////////////////////////////////////////////////////
// Assignment operators

__declspec (nothrow) const CCom_String& CCom_String::operator=(TCHAR ch)
{
	assert(!_istlead(ch));    // can't set single lead byte
	AssignCopy(1, &ch);
	return *this;
}

//////////////////////////////////////////////////////////////////////////////
// less common string expressions

__declspec (nothrow) CCom_String  operator+(const CCom_String& string1, TCHAR ch)
{
	CCom_String s;
	s.ConcatCopy(string1.GetData()->nDataLength, string1.m_pchData, 1, &ch);
	return s;
}

__declspec (nothrow) CCom_String  operator+(TCHAR ch, const CCom_String& string)
{
	CCom_String s;
	s.ConcatCopy(1, &ch, string.GetData()->nDataLength, string.m_pchData);
	return s;
}

//////////////////////////////////////////////////////////////////////////////
// Very simple sub-string extraction

__declspec (nothrow) CCom_String CCom_String::Mid(int nFirst) const
{
	return Mid(nFirst, GetData()->nDataLength - nFirst);
}

__declspec (nothrow) CCom_String CCom_String::Mid(int nFirst, int nCount) const
{
	// out-of-bounds requests return sensible things
	if (nFirst < 0)
		nFirst = 0;
	if (nCount < 0)
		nCount = 0;

	if (nFirst + nCount > GetData()->nDataLength)
		nCount = GetData()->nDataLength - nFirst;
	if (nFirst > GetData()->nDataLength)
		nCount = 0;

	CCom_String dest;
	AllocCopy(dest, nCount, nFirst, 0);
	return dest;
}

__declspec (nothrow) CCom_String CCom_String::Right(int nCount) const
{
	if (nCount < 0)
		nCount = 0;
	else if (nCount > GetData()->nDataLength)
		nCount = GetData()->nDataLength;

	CCom_String dest;
	AllocCopy(dest, nCount, GetData()->nDataLength-nCount, 0);
	return dest;
}

__declspec (nothrow) CCom_String CCom_String::Left(int nCount) const
{
	if (nCount < 0)
		nCount = 0;
	else if (nCount > GetData()->nDataLength)
		nCount = GetData()->nDataLength;

	CCom_String dest;
	AllocCopy(dest, nCount, 0, 0);
	return dest;
}

// strspn equivalent
__declspec (nothrow) CCom_String CCom_String::SpanIncluding(LPCTSTR lpszCharSet) const
{
	assert(comline_AfxIsValidString(lpszCharSet, false));
	return Left(_tcsspn(m_pchData, lpszCharSet));
}

// strcspn equivalent
__declspec (nothrow) CCom_String CCom_String::SpanExcluding(LPCTSTR lpszCharSet) const
{
	assert(comline_AfxIsValidString(lpszCharSet, false));
	return Left(_tcscspn(m_pchData, lpszCharSet));
}

//////////////////////////////////////////////////////////////////////////////
// Finding

__declspec (nothrow) int CCom_String::ReverseFind(TCHAR ch) const
{
	// find last single character
	LPTSTR lpsz = _tcsrchr(m_pchData, (_TUCHAR)ch);

	// return -1 if not found, distance from beginning otherwise
	return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData);
}

// find a sub-string (like strstr)
__declspec (nothrow) int CCom_String::Find(LPCTSTR lpszSub) const
{
	assert(comline_AfxIsValidString(lpszSub, false));

	// find first matching substring
	LPTSTR lpsz = _tcsstr(m_pchData, lpszSub);

	// return -1 for not found, distance from beginning otherwise
	return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData);
}

/////////////////////////////////////////////////////////////////////////////
// CCom_String formatting

#ifdef _MAC
	#define TCHAR_ARG   int
	#define WCHAR_ARG   unsigned
	#define CHAR_ARG    int
#else
	#define TCHAR_ARG   TCHAR
	#define WCHAR_ARG   WCHAR
	#define CHAR_ARG    char
#endif

#if defined(_68K_) || defined(_X86_)
	#define DOUBLE_ARG  _AFX_DOUBLE
#else
	#define DOUBLE_ARG  double
#endif

#define FORCE_ANSI      0x10000
#define FORCE_UNICODE   0x20000

__declspec (nothrow) void CCom_String::FormatV(LPCTSTR lpszFormat, va_list argList)
{
	assert(comline_AfxIsValidString(lpszFormat, false));

	va_list argListSave = argList;

	// make a guess at the maximum length of the resulting string
	int nMaxLen = 0;
	for (LPCTSTR lpsz = lpszFormat; *lpsz != '\0'; lpsz = _tcsinc(lpsz))
	{
		// handle '%' character, but watch out for '%%'
		if (*lpsz != '%' || *(lpsz = _tcsinc(lpsz)) == '%')
		{
			nMaxLen += _tclen(lpsz);
			continue;
		}

		int nItemLen = 0;

		// handle '%' character with format
		int nWidth = 0;
		for (; *lpsz != '\0'; lpsz = _tcsinc(lpsz))
		{
			// check for valid flags
			if (*lpsz == '#')
				nMaxLen += 2;   // for '0x'
			else if (*lpsz == '*')
				nWidth = va_arg(argList, int);
			else if (*lpsz == '-' || *lpsz == '+' || *lpsz == '0' ||
				*lpsz == ' ')
				;
			else // hit non-flag character
				break;
		}
		// get width and skip it
		if (nWidth == 0)
		{
			// width indicated by
			nWidth = _ttoi(lpsz);
			for (; *lpsz != '\0' && _istdigit(*lpsz); lpsz = _tcsinc(lpsz))
				;
		}
		assert(nWidth >= 0);

		int nPrecision = 0;
		if (*lpsz == '.')
		{
			// skip past '.' separator (width.precision)
			lpsz = _tcsinc(lpsz);

			// get precision and skip it
			if (*lpsz == '*')
			{
				nPrecision = va_arg(argList, int);
				lpsz = _tcsinc(lpsz);
			}
			else
			{
				nPrecision = _ttoi(lpsz);
				for (; *lpsz != '\0' && _istdigit(*lpsz); lpsz = _tcsinc(lpsz))
					;
			}
			assert(nPrecision >= 0);
		}

		// should be on type modifier or specifier
		int nModifier = 0;
		switch (*lpsz)
		{
		// modifiers that affect size
		case 'h':
			nModifier = FORCE_ANSI;
			lpsz = _tcsinc(lpsz);
			break;
		case 'l':
			nModifier = FORCE_UNICODE;
			lpsz = _tcsinc(lpsz);
			break;

		// modifiers that do not affect size
		case 'F':
		case 'N':
		case 'L':
			lpsz = _tcsinc(lpsz);
			break;
		}

		// now should be on specifier
		switch (*lpsz | nModifier)
		{
		// single characters
		case 'c':
		case 'C':
			nItemLen = 2;
			va_arg(argList, TCHAR_ARG);
			break;
		case 'c'|FORCE_ANSI:
		case 'C'|FORCE_ANSI:
			nItemLen = 2;
			va_arg(argList, CHAR_ARG);
			break;
		case 'c'|FORCE_UNICODE:
		case 'C'|FORCE_UNICODE:
			nItemLen = 2;
			va_arg(argList, WCHAR_ARG);
			break;

		// strings
		case 's':
			nItemLen = lstrlen(va_arg(argList, LPCTSTR));
			nItemLen = max(1, nItemLen);
			break;

		case 'S':
#ifndef _UNICODE
			nItemLen = wcslen(va_arg(argList, LPWSTR));
#else
			nItemLen = lstrlenA(va_arg(argList, LPCSTR));
#endif
			nItemLen = max(1, nItemLen);
			break;

		case 's'|FORCE_ANSI:
		case 'S'|FORCE_ANSI:
			nItemLen = lstrlenA(va_arg(argList, LPCSTR));
			nItemLen = max(1, nItemLen);
			break;
#ifndef _MAC
		case 's'|FORCE_UNICODE:
		case 'S'|FORCE_UNICODE:
			nItemLen = wcslen(va_arg(argList, LPWSTR));
			nItemLen = max(1, nItemLen);
			break;
#endif
		}

		// adjust nItemLen for strings
		if (nItemLen != 0)
		{
			nItemLen = max(nItemLen, nWidth);
			if (nPrecision != 0)
				nItemLen = min(nItemLen, nPrecision);
		}
		else
		{
			switch (*lpsz)
			{
			// integers
			case 'd':
			case 'i':
			case 'u':
			case 'x':
			case 'X':
			case 'o':
				va_arg(argList, int);
				nItemLen = 32;
				nItemLen = max(nItemLen, nWidth+nPrecision);
				break;

			case 'e':
			case 'f':
			case 'g':
			case 'G':
				va_arg(argList, double);
				nItemLen = 128;
				nItemLen = max(nItemLen, nWidth+nPrecision);
				break;

			case 'p':
				va_arg(argList, void*);
				nItemLen = 32;
				nItemLen = max(nItemLen, nWidth+nPrecision);
				break;

			// no output
			case 'n':
				va_arg(argList, int*);
				break;

			default:
				assert(false);  // unknown formatting option
			}
		}

		// adjust nMaxLen for output nItemLen
		nMaxLen += nItemLen;
	}

	GetBuffer(nMaxLen);
	(_vstprintf(m_pchData, lpszFormat, argListSave) <= GetAllocLength());
	ReleaseBuffer();

	va_end(argListSave);
}

__declspec (nothrow) void _cdecl CCom_String::FormatHex (char *mem,int count)
{
	char buf[20];
	int value;

	buf[3] = 0;
	while (count > 0)
	{
		value = ((*mem >> 4) & 0x0f);
		switch (value)
		{
		case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 8:case 9:
			*this += (char) (value +'0');
			break;
		case 10:case 11:case 12:case 13:case 14:case 15:
			*this += (char) (value-10+'A');
			break;
		}

		value = ((*mem) & 0x0f);
		switch (value)
		{
		case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 8:case 9:
			*this += (char) (value +'0');
			break;
		case 10:case 11:case 12:case 13:case 14:case 15:
			*this += (char) (value-10+'A');
			break;
		}
		*this += ' ';
		mem ++;
		count--;
	}
}

// formatting (using wsprintf style formatting)
__declspec (nothrow) void _cdecl CCom_String::Format(LPCTSTR lpszFormat, ...)
{
	assert(comline_AfxIsValidString(lpszFormat, false));

	va_list argList;
	va_start(argList, lpszFormat);
	FormatV(lpszFormat, argList);
	va_end(argList);
}

__declspec (nothrow) void _cdecl CCom_String::Format(UINT nFormatID,HINSTANCE inst, ...)
{
	CCom_String strFormat;
	(strFormat.LoadString(nFormatID,inst) != 0);

	va_list argList;
//	va_start(argList, nFormatID);
	va_start(argList, inst);
	FormatV(strFormat, argList);
	va_end(argList);
}

#ifndef _MAC
// formatting (using FormatMessage style formatting)
void _cdecl CCom_String::FormatMessage(LPCTSTR lpszFormat, ...)
{
	// format message into temporary buffer lpszTemp
	va_list argList;
	va_start(argList, lpszFormat);
	LPTSTR lpszTemp;
	::FormatMessage(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
		lpszFormat, 0, 0, (LPTSTR)&lpszTemp, 0, &argList);
	if (lpszTemp == NULL)
		return;

	// assign lpszTemp into the resulting string and free the temporary
	*this = lpszTemp;
	LocalFree(lpszTemp);
	va_end(argList);
}

void _cdecl CCom_String::FormatMessage(UINT nFormatID,HINSTANCE inst, ...)
{
	// get format string from string table
	CCom_String strFormat;
	(strFormat.LoadString(nFormatID,inst) != 0);

	// format message into temporary buffer lpszTemp
	va_list argList;
	va_start(argList, nFormatID);
	LPTSTR lpszTemp;
	::FormatMessage(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
		strFormat, 0, 0, (LPTSTR)&lpszTemp, 0, &argList);
	if (lpszTemp == NULL)
		return;

	// assign lpszTemp into the resulting string and free lpszTemp
	*this = lpszTemp;
	LocalFree(lpszTemp);
	va_end(argList);
}
#endif //!_MAC

__declspec (nothrow) void CCom_String::TrimRight()
{
	CopyBeforeWrite();

	// find beginning of trailing spaces by starting at beginning (DBCS aware)
	LPTSTR lpsz = m_pchData;
	LPTSTR lpszLast = NULL;
	while (*lpsz != '\0')
	{
		if (_istspace(*lpsz))
		{
			if (lpszLast == NULL)
				lpszLast = lpsz;
		}
		else
			lpszLast = NULL;
		lpsz = _tcsinc(lpsz);
	}

	if (lpszLast != NULL)
	{
		// truncate at trailing space start
		*lpszLast = '\0';
		GetData()->nDataLength = lpszLast - m_pchData;
	}
}

__declspec (nothrow) void CCom_String::TrimLeft()
{
	CopyBeforeWrite();

	// find first non-space character
	LPCTSTR lpsz = m_pchData;
#pragma warning (disable : 4127)
	while (true)
	{
		if (IsDBCSLeadByte(*lpsz))
		{
			if (IsSpace (*((WORD *) lpsz)))
				lpsz+=2;
			else
				break;
		}
		else
		{
			if (IsSpace (*((BYTE *) lpsz)))
				lpsz++;
			else
				break;
		}
	}
#pragma warning (default:4127)
//	while (_istspace(*lpsz))
//		lpsz = _tcsinc(lpsz);

	// fix up data and length
	int nDataLength = GetData()->nDataLength - (lpsz - m_pchData);
	memmove(m_pchData, lpsz, (nDataLength+1)*sizeof(TCHAR));
	GetData()->nDataLength = nDataLength;
}

///////////////////////////////////////////////////////////////////////////////
// CCom_String support for template collections

__declspec (nothrow) void  ConstructElements(CCom_String* pElements, int nCount)
{
	assert(nCount == 0 ||
		comline_AfxIsValidAddress(pElements, nCount * sizeof(CCom_String)));

	for (; nCount--; ++pElements)
		memcpy(pElements, &comline_afxEmptyString, sizeof(*pElements));
}

__declspec (nothrow) void  DestructElements(CCom_String* pElements, int nCount)
{
	assert(nCount == 0 ||
		comline_AfxIsValidAddress(pElements, nCount * sizeof(CCom_String)));

	for (; nCount--; ++pElements)
		pElements->~CCom_String();
}

__declspec (nothrow) void  CopyElements(CCom_String* pDest, const CCom_String* pSrc, int nCount)
{
	assert(nCount == 0 ||
		comline_AfxIsValidAddress(pDest, nCount * sizeof(CCom_String)));
	assert(nCount == 0 ||
		comline_AfxIsValidAddress(pSrc, nCount * sizeof(CCom_String)));

	for (; nCount--; ++pDest, ++pSrc)
		*pDest = *pSrc;
}

__declspec (nothrow) UINT  HashKey(LPCTSTR key)
{
	UINT nHash = 0;
	while (*key)
		nHash = (nHash<<5) + nHash + *key++;
	return nHash;
}



/////////////////////////////////////////////////////////////////////////////
// Windows extensions to strings

#ifdef _UNICODE
#define CHAR_FUDGE 1    // one TCHAR unused is good enough
#else
#define CHAR_FUDGE 2    // two BYTES unused for case of DBC last char
#endif

__declspec (nothrow) bool CCom_String::LoadString(UINT nID,HINSTANCE inst)
{
	// try fixed buffer first (to avoid wasting space in the heap)
	TCHAR szTemp[256];
	int nLen = comline_AfxLoadString(nID, szTemp, _countof(szTemp),inst);
	if (_countof(szTemp) - nLen > CHAR_FUDGE)
	{
		*this = szTemp;
		return nLen > 0;
	}

	// try buffer size of 512, then larger size until entire string is retrieved
	int nSize = 256;
	do
	{
		nSize += 256;
		nLen = comline_AfxLoadString(nID, GetBuffer(nSize-1), nSize,inst);
	} while (nSize - nLen <= CHAR_FUDGE);
	ReleaseBuffer();

	return nLen > 0;
}

__declspec (nothrow) int comline_AfxLoadString(UINT nID, LPTSTR lpszBuf, UINT nMaxBuf,HINSTANCE inst)
{
	assert(comline_AfxIsValidAddress(lpszBuf, nMaxBuf*sizeof(TCHAR)));
#ifdef _DEBUG
	// LoadString without annoying warning from the Debug kernel if the
	//  segment containing the string is not present
	if(inst == NULL)
	{
		if (::FindResource(GetModuleHandle (NULL),
			 MAKEINTRESOURCE((nID>>4)+1), RT_STRING) == NULL)
		{
			lpszBuf[0] = '\0';
			return 0; // not found
		}
	}
	else
	{
		if (::FindResource(inst,MAKEINTRESOURCE((nID>>4)+1), RT_STRING) == NULL)
		{
			lpszBuf[0] = '\0';
			return 0; // not found
		}
	}
#endif //_DEBUG
	int nLen;
	if(inst == NULL)
		nLen = ::LoadString(GetModuleHandle (NULL), nID, lpszBuf, nMaxBuf);
	else
		nLen = ::LoadString(inst, nID, lpszBuf, nMaxBuf);
	if (nLen == 0)
		lpszBuf[0] = '\0';
	return nLen;
}

/////////////////////////////////////////////////////////////////////////////

__declspec (nothrow) bool comline_AfxExtractSubString(CCom_String& rString, LPCTSTR lpszFullString,
	int iSubString, TCHAR chSep)
{
	if (lpszFullString == NULL)
		return false;

	while (iSubString--)
	{
		lpszFullString = _tcschr(lpszFullString, chSep);
		if (lpszFullString == NULL)
		{
			rString.Empty();        // return empty string as well
			return false;
		}
		lpszFullString++;       // point past the separator
	}
	LPCTSTR lpchEnd = _tcschr(lpszFullString, chSep);
	int nLen = (lpchEnd == NULL) ?
		lstrlen(lpszFullString) : (int)(lpchEnd - lpszFullString);
	assert(nLen >= 0);
	memcpy(rString.GetBufferSetLength(nLen), lpszFullString, nLen*sizeof(TCHAR));
	return true;
}

/////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Advanced manipulation

int CCom_String::Delete(int nIndex, int nCount /* = 1 */)
{
	if (nIndex < 0)
		nIndex = 0;
	int nNewLength = GetData()->nDataLength;
	if (nCount > 0 && nIndex < nNewLength)
	{
		CopyBeforeWrite();
		int nBytesToCopy = nNewLength - (nIndex + nCount) + 1;

		memcpy(m_pchData + nIndex,
			m_pchData + nIndex + nCount, nBytesToCopy * sizeof(TCHAR));
		GetData()->nDataLength = nNewLength - nCount;
	}

	return nNewLength;
}

int CCom_String::Insert(int nIndex, TCHAR ch)
{
	CopyBeforeWrite();

	if (nIndex < 0)
		nIndex = 0;

	int nNewLength = GetData()->nDataLength;
	if (nIndex > nNewLength)
		nIndex = nNewLength;
	nNewLength++;

	if (GetData()->nAllocLength < nNewLength)
	{
		CCom_StringData* pOldData = GetData();
		LPTSTR pstr = m_pchData;
		AllocBuffer(nNewLength);
		memcpy(m_pchData, pstr, pOldData->nDataLength*sizeof(TCHAR));
		CCom_String::Release(pOldData);
	}

	// move existing bytes down
	memcpy(m_pchData + nIndex + 1,
		m_pchData + nIndex, (nNewLength-nIndex)*sizeof(TCHAR));
	m_pchData[nIndex] = ch;
	GetData()->nDataLength = nNewLength;

	return nNewLength;
}

int CCom_String::Insert(int nIndex, LPCTSTR pstr)
{
	if (nIndex < 0)
		nIndex = 0;

	int nInsertLength = SafeStrlen(pstr);
	int nNewLength = GetData()->nDataLength;
	if (nInsertLength > 0)
	{
		CopyBeforeWrite();
		if (nIndex > nNewLength)
			nIndex = nNewLength;
		nNewLength += nInsertLength;

		if (GetData()->nAllocLength < nNewLength)
		{
			CCom_StringData* pOldData = GetData();
			LPTSTR pstr = m_pchData;
			AllocBuffer(nNewLength);
			memcpy(m_pchData, pstr, pOldData->nDataLength*sizeof(TCHAR));
			CCom_String::Release(pOldData);
		}

		// move existing bytes down
		memcpy(m_pchData + nIndex + nInsertLength,
			m_pchData + nIndex, 
			(nNewLength-nIndex-nInsertLength+1)*sizeof(TCHAR));
		memcpy(m_pchData + nIndex,
			pstr, nInsertLength*sizeof(TCHAR));
		GetData()->nDataLength = nNewLength;
	}

	return nNewLength;
}

int CCom_String::Replace(TCHAR chOld, TCHAR chNew)
{
	int nCount = 0;

	// short-circuit the nop case
	if (chOld != chNew)
	{
		// otherwise modify each character that matches in the string
		CopyBeforeWrite();
		LPTSTR psz = m_pchData;
		LPTSTR pszEnd = psz + GetData()->nDataLength;
		while (psz < pszEnd)
		{
			// replace instances of the specified character only
			if (*psz == chOld)
			{
				*psz = chNew;
				nCount++;
			}
			psz = _tcsinc(psz);
		}
	}
	return nCount;
}

int CCom_String::Replace(LPCTSTR lpszOld, LPCTSTR lpszNew)
{
	// can't have empty or NULL lpszOld

	int nSourceLen = SafeStrlen(lpszOld);
	if (nSourceLen == 0)
		return 0;
	int nReplacementLen = SafeStrlen(lpszNew);

	// loop once to figure out the size of the result string
	int nCount = 0;
	LPTSTR lpszStart = m_pchData;
	LPTSTR lpszEnd = m_pchData + GetData()->nDataLength;
	LPTSTR lpszTarget;
	while (lpszStart < lpszEnd)
	{
		while ((lpszTarget = _tcsstr(lpszStart, lpszOld)) != NULL)
		{
			nCount++;
			lpszStart = lpszTarget + nSourceLen;
		}
		lpszStart += lstrlen(lpszStart) + 1;
	}

	// if any changes were made, make them
	if (nCount > 0)
	{
		CopyBeforeWrite();

		// if the buffer is too small, just
		//   allocate a new buffer (slow but sure)
		int nOldLength = GetData()->nDataLength;
		int nNewLength =  nOldLength + (nReplacementLen-nSourceLen)*nCount;
		if (GetData()->nAllocLength < nNewLength || GetData()->nRefs > 1)
		{
			CCom_StringData* pOldData = GetData();
			LPTSTR pstr = m_pchData;
			AllocBuffer(nNewLength);
			memcpy(m_pchData, pstr, pOldData->nDataLength*sizeof(TCHAR));
			CCom_String::Release(pOldData);
		}
		// else, we just do it in-place
		lpszStart = m_pchData;
		lpszEnd = m_pchData + GetData()->nDataLength;

		// loop again to actually do the work
		while (lpszStart < lpszEnd)
		{
			while ( (lpszTarget = _tcsstr(lpszStart, lpszOld)) != NULL)
			{
				int nBalance = nOldLength - (lpszTarget - m_pchData + nSourceLen);
				memmove(lpszTarget + nReplacementLen, lpszTarget + nSourceLen, 
					nBalance * sizeof(TCHAR));
 				memcpy(lpszTarget, lpszNew, nReplacementLen*sizeof(TCHAR));
				lpszStart = lpszTarget + nReplacementLen;
				lpszStart[nBalance] = '\0';
				nOldLength += (nReplacementLen - nSourceLen);
			}
			lpszStart += lstrlen(lpszStart) + 1;
		}
		assert(m_pchData[nNewLength] == '\0');
		GetData()->nDataLength = nNewLength;
	}

	return nCount;
}

int CCom_String::Remove(TCHAR chRemove)
{
	CopyBeforeWrite();

	LPTSTR pstrSource = m_pchData;
	LPTSTR pstrDest = m_pchData;
	LPTSTR pstrEnd = m_pchData + GetData()->nDataLength;

	while (pstrSource < pstrEnd)
	{
		if (*pstrSource != chRemove)
		{
			*pstrDest = *pstrSource;
			pstrDest = _tcsinc(pstrDest);
		}
		pstrSource = _tcsinc(pstrSource);
	}
	*pstrDest = '\0';
	int nCount = pstrSource - pstrDest;
	GetData()->nDataLength -= nCount;

	return nCount;
}


