/*++

 Copyright (c) 2001 Microsoft Corporation

 Module Name:

    CString.cpp

 Abstract:
    A CString class, pure UNICODE internally.

   This code was ripped from MFC Strcore.cpp and Strex.cpp

 History:

    05/11/2001   robkenny     Added this header
    05/11/2001   robkenny     Fixed SplitPath.
    05/11/2001   robkenny     Do not Truncate(0) GetShortPathNameW,
                              GetLongPathNameW and GetFullPathNameW if
                              the API does not succeed.
    08/14/2001  robkenny      Moved code inside the ShimLib namespace.
    
--*/



#include "ShimHook.h"
#include "ShimLib.h"
#include "Win9xPath.h"
#include <stdio.h> // for _vsnwprintf
#include <stdlib.h>


namespace ShimLib
{

typedef WCHAR  _TUCHAR;
struct _AFX_DOUBLE  { BYTE doubleBits[sizeof(double)]; };


#ifdef USE_SEH
const ULONG_PTR  CString::m_CStringExceptionValue = CString::eCStringExceptionValue;

// Exception filter for CString __try/__except blocks
// Return EXCEPTION_EXECUTE_HANDLER if this is a CString exception
// otherwise return EXCEPTION_CONTINUE_SEARCH
int CString::ExceptionFilter(PEXCEPTION_POINTERS pexi)
{
    if (pexi->ExceptionRecord->ExceptionCode            == CString::eCStringNoMemoryException &&
        pexi->ExceptionRecord->NumberParameters         == 1 &&
        pexi->ExceptionRecord->ExceptionInformation[0]  == CString::m_CStringExceptionValue
        )
    {
        // This is a CString exception, handle it
        return EXCEPTION_EXECUTE_HANDLER;
    }

    // Not our error
    return EXCEPTION_CONTINUE_SEARCH;
}
#endif


// The original code was written using a memcpy that correctly handled
// overlapping buffers, despite the documentation.
// Replace memcpy with memmove, which correctly handles overlapping buffers
#define memcpy memmove

const WCHAR * wcsinc(const WCHAR * s1)                                    
{ 
    return (s1) + 1; 
}

LPWSTR wcsinc(LPWSTR s1)                                    
{ 
    return (s1) + 1; 
}

// WCS routines that are only available in MSVCRT

wchar_t * __cdecl _wcsrev (
    wchar_t * string
    )
{
    wchar_t *start = string;
    wchar_t *left = string;
    wchar_t ch;

    while (*string++)         /* find end of string */
        ;
    string -= 2;

    while (left < string)
    {
        ch = *left;
        *left++ = *string;
        *string-- = ch;
    }

    return(start);
}


void __cdecl _wsplitpath (
        register const WCHAR *path,
        WCHAR *drive,
        WCHAR *dir,
        WCHAR *fname,
        WCHAR *ext
        )
{
        register WCHAR *p;
        WCHAR *last_slash = NULL, *dot = NULL;
        unsigned len;

        /* we assume that the path argument has the following form, where any
         * or all of the components may be missing.
         *
         *  <drive><dir><fname><ext>
         *
         * and each of the components has the following expected form(s)
         *
         *  drive:
         *  0 to _MAX_DRIVE-1 characters, the last of which, if any, is a
         *  ':'
         *  dir:
         *  0 to _MAX_DIR-1 characters in the form of an absolute path
         *  (leading '/' or '\') or relative path, the last of which, if
         *  any, must be a '/' or '\'.  E.g -
         *  absolute path:
         *      \top\next\last\     ; or
         *      /top/next/last/
         *  relative path:
         *      top\next\last\  ; or
         *      top/next/last/
         *  Mixed use of '/' and '\' within a path is also tolerated
         *  fname:
         *  0 to _MAX_FNAME-1 characters not including the '.' character
         *  ext:
         *  0 to _MAX_EXT-1 characters where, if any, the first must be a
         *  '.'
         *
         */

        /* extract drive letter and :, if any */

        if ((wcslen(path) >= (_MAX_DRIVE - 2)) && (*(path + _MAX_DRIVE - 2) == L':')) {
            if (drive) {
                wcsncpy(drive, path, _MAX_DRIVE - 1);
                *(drive + _MAX_DRIVE-1) = L'\0';
            }
            path += _MAX_DRIVE - 1;
        }
        else if (drive) {
            *drive = L'\0';
        }

        /* extract path string, if any.  Path now points to the first character
         * of the path, if any, or the filename or extension, if no path was
         * specified.  Scan ahead for the last occurence, if any, of a '/' or
         * '\' path separator character.  If none is found, there is no path.
         * We will also note the last '.' character found, if any, to aid in
         * handling the extension.
         */

        for (last_slash = NULL, p = (WCHAR *)path; *p; p++) {
            if (*p == L'/' || *p == L'\\')
                /* point to one beyond for later copy */
                last_slash = p + 1;
            else if (*p == L'.')
                dot = p;
        }

        if (last_slash) {

            /* found a path - copy up through last_slash or max. characters
             * allowed, whichever is smaller
             */

            if (dir) {
                len = __min((unsigned)(((char *)last_slash - (char *)path) / sizeof(WCHAR)),
                    (_MAX_DIR - 1));
                wcsncpy(dir, path, len);
                *(dir + len) = L'\0';
            }
            path = last_slash;
        }
        else if (dir) {

            /* no path found */

            *dir = L'\0';
        }

        /* extract file name and extension, if any.  Path now points to the
         * first character of the file name, if any, or the extension if no
         * file name was given.  Dot points to the '.' beginning the extension,
         * if any.
         */

        if (dot && (dot >= path)) {
            /* found the marker for an extension - copy the file name up to
             * the '.'.
             */
            if (fname) {
                len = __min((unsigned)(((char *)dot - (char *)path) / sizeof(WCHAR)),
                    (_MAX_FNAME - 1));
                wcsncpy(fname, path, len);
                *(fname + len) = L'\0';
            }
            /* now we can get the extension - remember that p still points
             * to the terminating nul character of path.
             */
            if (ext) {
                len = __min((unsigned)(((char *)p - (char *)dot) / sizeof(WCHAR)),
                    (_MAX_EXT - 1));
                wcsncpy(ext, dot, len);
                *(ext + len) = L'\0';
            }
        }
        else {
            /* found no extension, give empty extension and copy rest of
             * string into fname.
             */
            if (fname) {
                len = __min((unsigned)(((char *)p - (char *)path) / sizeof(WCHAR)),
                    (_MAX_FNAME - 1));
                wcsncpy(fname, path, len);
                *(fname + len) = L'\0';
            }
            if (ext) {
                *ext = L'\0';
            }
        }
}

// conversion helpers
int AFX_CDECL _mbstowcsz(wchar_t* wcstr, const char* mbstr, size_t count);

// AfxIsValidString() returns TRUE if the passed pointer
// references a string of at least the given length in characters.
// A length of -1 (the default parameter) means that the string
// buffer's minimum length isn't known, and the function will
// return TRUE no matter how long the string is. The memory
// used by the string can be read-only.

BOOL AFXAPI AfxIsValidString(LPCWSTR lpsz, int nLength /* = -1 */)
{
    if (lpsz == NULL)
        return FALSE;
    return ::IsBadStringPtrW(lpsz, nLength) == 0;
}

// AfxIsValidAddress() returns TRUE if the passed parameter points
// to at least nBytes of accessible memory. If bReadWrite is TRUE,
// the memory must be writeable; if bReadWrite is FALSE, the memory
// may be const.

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

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

WCHAR CString::ChNil = L'\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)
int                    CString::_afxInitData[] = { -1, 0, 0, 0 };
CStringData<WCHAR> *   CString::_afxDataNil    = (CStringData<WCHAR>*)&_afxInitData;
const WCHAR *          CString::_afxPchNil     = (const WCHAR *)(((BYTE*)&_afxInitData)+sizeof(CStringData<WCHAR>));

// special function to make afxEmptyString work even during initialization
//const CString& AFXAPI AfxGetEmptyString()
//  { return *(CString*)&CString::_afxPchNil; }


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

CString::CString(const CString& stringSrc)
{
    ASSERT(stringSrc.GetData()->nRefs != 0, "CString::CString(const CString& stringSrc)");
    if (stringSrc.GetData()->nRefs >= 0)
    {
        ASSERT(stringSrc.GetData() != _afxDataNil, "CString::CString(const CString& stringSrc)");
        // robkenny: increment before copy is safer
        InterlockedIncrement(&stringSrc.GetData()->nRefs);
        m_pchData = stringSrc.m_pchData;
        m_pchDataAnsi = NULL;
    }
    else
    {
        Init();
        *this = stringSrc.m_pchData;
    }
}

inline int Round4(int x)
{
    return (x + 3) & ~3;
}

inline int RoundBin(int x)
{
    return Round4(x * sizeof(WCHAR) + sizeof(CStringData<WCHAR>) );
}

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

    if (nLen == 0)
    {
        Init();
    }
    else
    {
        int allocGranularity = nLen + 1;

        if (nLen < 64)
        {
            allocGranularity = 64;
        }
        else if (nLen < 128)
        {
            allocGranularity = 128;
        }
        else if (nLen < MAX_PATH)
        {
            allocGranularity = MAX_PATH;
        }
        else if (nLen < 512)
        {
            allocGranularity = 512;
        }

        // Number of bytes necessary for the CStringData thingy.
        DWORD dwBufferSize = RoundBin(allocGranularity);

        CStringData<WCHAR>* pData = (CStringData<WCHAR>*) new BYTE[dwBufferSize];
        if (pData)
        {
            pData->nAllocLength = allocGranularity;
            pData->nRefs = 1;
            pData->data()[nLen] = '\0';
            pData->nDataLength = nLen;
            m_pchData = pData->data();
        }
        else
        {
            CSTRING_THROW_EXCEPTION
        }
    }
}

void CString::Release()
{
    if (GetData() != _afxDataNil)
    {
        ASSERT(GetData()->nRefs != 0, "CString::Release()");
        if (InterlockedDecrement(&GetData()->nRefs) <= 0)
            FreeData(GetData());
        Init();
    }
}

void CString::Release(CStringData<WCHAR>* pData)
{
    if (pData != _afxDataNil)
    {
        ASSERT(pData->nRefs != 0, "CString::Release(CStringData<WCHAR>* pData)");
        if (InterlockedDecrement(&pData->nRefs) <= 0)
            FreeData(pData);
    }
}

void CString::Empty()
{
    if (GetData()->nDataLength == 0)
        return;
    if (GetData()->nRefs >= 0)
        Release();
    else
        *this = &ChNil;
    ASSERT(GetData()->nDataLength == 0, "CString::Empty()");
    ASSERT(GetData()->nRefs < 0 || GetData()->nAllocLength == 0, "CString::Empty()");
}

void CString::CopyBeforeWrite()
{
    if (GetData()->nRefs > 1)
    {
        CStringData<WCHAR>* pData = GetData();
        Release();
        AllocBuffer(pData->nDataLength);
        memcpy(m_pchData, pData->data(), (pData->nDataLength+1)*sizeof(WCHAR));
    }
    ASSERT(GetData()->nRefs <= 1, "CString::CopyBeforeWrite()");
}

void CString::AllocBeforeWrite(int nLen)
{
    if (GetData()->nRefs > 1 || nLen >= GetData()->nAllocLength)
    {
        Release();
        AllocBuffer(nLen);
    }
    ASSERT(GetData()->nRefs <= 1, "CString::AllocBeforeWrite(int nLen)");
}

CString::~CString()
//  free any attached data
{
    if (GetData() != _afxDataNil)
    {
        if (InterlockedDecrement(&GetData()->nRefs) <= 0)
            FreeData(GetData());
    }
    if (m_pchDataAnsi)
    {
        free(m_pchDataAnsi);
    }
}

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

void CString::AllocCopy(CString& dest, int nCopyLen, int nCopyIndex,
     int nExtraLen) const
{
    // Copy nCopyIndex to nCopyIndex+nCopyLen into dest
    // Make sure dest has nExtraLen chars left over in the dest string
    int nNewLen = nCopyLen + nExtraLen;
    if (nNewLen == 0)
    {
        dest.Init();
    }
    else
    {
        WCHAR * lpszDestBuffer = dest.GetBuffer(nNewLen);
        memcpy(lpszDestBuffer, m_pchData+nCopyIndex, nCopyLen*sizeof(WCHAR));
        lpszDestBuffer[nCopyLen] = '\0';
        dest.ReleaseBuffer(nCopyLen);
    }
}

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

int AFX_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, "CString::_mbstowcsz");
    if (result > 0)
        wcstr[result-1] = 0;
    return result;
}


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

CString::CString(LPCWSTR lpsz)
{
    Init();
    {
        int nLen = SafeStrlen(lpsz);
        if (nLen != 0)
        {
            AllocBuffer(nLen);
            memcpy(m_pchData, lpsz, nLen*sizeof(WCHAR));
        }
    }
}

/////////////////////////////////////////////////////////////////////////////
// Special conversion constructors
CString::CString(LPCSTR lpsz)
{
    Init();
    int nSrcLen = lpsz != NULL ? strlenChars(lpsz) : 0;
    if (nSrcLen != 0)
    {
        AllocBuffer(nSrcLen);
        _mbstowcsz(m_pchData, lpsz, nSrcLen+1);
        ReleaseBuffer();
    }
}
CString::CString(LPCSTR lpsz, int nCharacters)
{
    Init();
    if (nCharacters != 0)
    {
        AllocBuffer(nCharacters);
        _mbstowcsz(m_pchData, lpsz, nCharacters);
        ReleaseBuffer(nCharacters);
    }
}

//////////////////////////////////////////////////////////////////////////////
// Diagnostic support

#ifdef _DEBUG
CDumpContext& AFXAPI operator<<(CDumpContext& dc, const CString& string)
{
    dc << string.m_pchData;
    return dc;
}
#endif //_DEBUG

//////////////////////////////////////////////////////////////////////////////
// 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 CString&' so that
//      assigning it again will cause a copy, eg: s1 = s2 = "hi there".
//

void CString::AssignCopy(int nSrcLen, LPCWSTR lpszSrcData)
{
    AllocBeforeWrite(nSrcLen);
    memcpy(m_pchData, lpszSrcData, nSrcLen*sizeof(WCHAR));
    GetData()->nDataLength = nSrcLen;
    m_pchData[nSrcLen] = '\0';
}

const CString& CString::operator=(const CString& stringSrc)
{
    if (m_pchData != stringSrc.m_pchData)
    {
        if ((GetData()->nRefs < 0 && GetData() != _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() != _afxDataNil, "CString::operator=(const CString& stringSrc)");
            // robkenny: increment before copy is safer
            InterlockedIncrement(&stringSrc.GetData()->nRefs);
            m_pchData = stringSrc.m_pchData;
            m_pchDataAnsi = NULL;
        }
    }
    return *this;
}

const CString& CString::operator=(LPCWSTR lpsz)
{
    ASSERT(lpsz == NULL || AfxIsValidString(lpsz), "CString::operator=(LPCWSTR lpsz)");
    AssignCopy(SafeStrlen(lpsz), lpsz);
    return *this;
}

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

const CString& CString::operator=(LPCSTR lpsz)
{
    int nSrcLen = lpsz != NULL ? strlenChars(lpsz) : 0;
    AllocBeforeWrite(nSrcLen);
    _mbstowcsz(m_pchData, lpsz, nSrcLen+1);
    ReleaseBuffer();
    return *this;
}

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

// NOTE: "operator+" is done as friend functions for simplicity
//      There are three variants:
//          CString + CString
// and for ? = WCHAR, LPCWSTR
//          CString + ?
//          ? + CString

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

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

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

CString AFXAPI operator+(const CString& string, LPCWSTR lpsz)
{
    ASSERT(lpsz == NULL || AfxIsValidString(lpsz), "CString::operator+(const CString& string, LPCWSTR lpsz)");
    CString s;
    s.ConcatCopy(string.GetData()->nDataLength, string.m_pchData,
        CString::SafeStrlen(lpsz), lpsz);
    return s;
}

CString AFXAPI operator+(LPCWSTR lpsz, const CString& string)
{
    ASSERT(lpsz == NULL || AfxIsValidString(lpsz), "CString::operator+(LPCWSTR lpsz, const CString& string)");
    CString s;
    s.ConcatCopy(CString::SafeStrlen(lpsz), lpsz, string.GetData()->nDataLength,
        string.m_pchData);
    return s;
}

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

void CString::ConcatInPlace(int nSrcLen, LPCWSTR 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 + 1 > GetData()->nAllocLength)
    {
        // we have to grow the buffer, use the ConcatCopy routine
        CStringData<WCHAR>* pOldData = GetData();
        ConcatCopy(GetData()->nDataLength, m_pchData, nSrcLen, lpszSrcData);
        ASSERT(pOldData != NULL, "CString::ConcatInPlace");
        CString::Release(pOldData);
    }
    else
    {
        // fast concatenation when buffer big enough
        memcpy(m_pchData+GetData()->nDataLength, lpszSrcData, nSrcLen*sizeof(WCHAR));
        GetData()->nDataLength += nSrcLen;
        ASSERT(GetData()->nDataLength <= GetData()->nAllocLength, "CString::ConcatInPlace");
        m_pchData[GetData()->nDataLength] = '\0';
    }
}

const CString& CString::operator+=(LPCWSTR lpsz)
{
    ASSERT(lpsz == NULL || AfxIsValidString(lpsz), "CString::operator+=(LPCWSTR lpsz)");
    ConcatInPlace(SafeStrlen(lpsz), lpsz);
    return *this;
}

const CString& CString::operator+=(WCHAR ch)
{
    ConcatInPlace(1, &ch);
    return *this;
}

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

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

LPWSTR CString::GetBuffer(int nMinBufLength)
{
    ASSERT(nMinBufLength >= 0, "CString::GetBuffer");

    if (GetData()->nRefs > 1 || nMinBufLength > GetData()->nAllocLength)
    {
#ifdef _DEBUG
        // give a warning in case locked string becomes unlocked
        if (GetData() != _afxDataNil && GetData()->nRefs < 0)
            TRACE0("Warning: GetBuffer on locked CString creates unlocked CString!\n");
#endif
        // we have to grow the buffer
        CStringData<WCHAR>* 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(WCHAR));
        GetData()->nDataLength = nOldLen;
        CString::Release(pOldData);
    }
    ASSERT(GetData()->nRefs <= 1, "CString::GetBuffer");

    // return a pointer to the character storage for this string
    ASSERT(m_pchData != NULL, "CString::GetBuffer");
    return m_pchData;
}

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

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

    ASSERT(nNewLength <= GetData()->nAllocLength, "CString::ReleaseBuffer");
    GetData()->nDataLength = nNewLength;
    m_pchData[nNewLength] = '\0';
}

LPWSTR CString::GetBufferSetLength(int nNewLength)
{
    ASSERT(nNewLength >= 0, "CString::GetBufferSetLength");

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

void CString::FreeExtra()
{
    ASSERT(GetData()->nDataLength <= GetData()->nAllocLength, "CString::FreeExtra");
    if (GetData()->nDataLength != GetData()->nAllocLength)
    {
        CStringData<WCHAR>* pOldData = GetData();
        AllocBuffer(GetData()->nDataLength);
        memcpy(m_pchData, pOldData->data(), pOldData->nDataLength*sizeof(WCHAR));
        ASSERT(m_pchData[GetData()->nDataLength] == '\0', "CString::FreeExtra");
        CString::Release(pOldData);
    }
    ASSERT(GetData() != NULL, "CString::FreeExtra");
}

LPWSTR CString::LockBuffer()
{
    LPWSTR lpsz = GetBuffer(0);
    GetData()->nRefs = -1;
    return lpsz;
}

void CString::UnlockBuffer()
{
    ASSERT(GetData()->nRefs == -1, "CString::UnlockBuffer");
    if (GetData() != _afxDataNil)
        GetData()->nRefs = 1;
}

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

int CString::Find(WCHAR ch) const
{
    return Find(ch, 0);
}

int CString::Find(WCHAR ch, int nStart) const
{
    int nLength = GetData()->nDataLength;
    if (nStart >= nLength)
        return -1;

    // find first single character
    LPWSTR lpsz = wcschr(m_pchData + nStart, (_TUCHAR)ch);

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

int CString::FindOneOf(LPCWSTR lpszCharSet) const
{
    return FindOneOf(lpszCharSet, 0);
}

int CString::FindOneOf(LPCWSTR lpszCharSet, int nCount) const
{
    ASSERT(AfxIsValidString(lpszCharSet), "CString::FindOneOf");
    LPCWSTR lpsz = wcspbrk(m_pchData + nCount, lpszCharSet);
    return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData);
}

int CString::FindOneNotOf(const WCHAR * lpszCharSet, int nCount) const
{
    ASSERT(AfxIsValidString(lpszCharSet), "CString::FindOneNotOf");
    while (wcschr(lpszCharSet, m_pchData[nCount]))
    {
        nCount += 1;
    }
    if (nCount >= GetLength())
    {
        // entire string contains lpszCharSet
        return -1;
    }
    return nCount;

}

void CString::MakeUpper()
{
    CopyBeforeWrite();
    _wcsupr(m_pchData);
}

void CString::MakeLower()
{
    CopyBeforeWrite();
    _wcslwr(m_pchData);
}

void CString::MakeReverse()
{
    CopyBeforeWrite();
    _wcsrev(m_pchData);
}

void CString::SetAt(int nIndex, WCHAR ch)
{
    ASSERT(nIndex >= 0, "CString::SetAt");
    ASSERT(nIndex < GetData()->nDataLength, "CString::SetAt");

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

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

CString::CString(WCHAR ch, int nLength)
{
    Init();
    if (nLength >= 1)
    {
        AllocBuffer(nLength);
        for (int i = 0; i < nLength; i++)
            m_pchData[i] = ch;
    }
}

CString::CString(int nLength)
{
    Init();
    if (nLength >= 1)
    {
        AllocBuffer(nLength);
        GetData()->nDataLength = 0;
    }
}

CString::CString(LPCWSTR lpch, int nLength)
{
    Init();
    if (nLength != 0)
    {
        ASSERT(AfxIsValidAddress(lpch, nLength, FALSE), "CString::CString(LPCWSTR lpch, int nLength)");
        AllocBuffer(nLength);
        memcpy(m_pchData, lpch, nLength*sizeof(WCHAR));
    }
}

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

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

const CString& CString::operator=(WCHAR ch)
{
    AssignCopy(1, &ch);
    return *this;
}

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

CString AFXAPI operator+(const CString& string1, WCHAR ch)
{
    CString s;
    s.ConcatCopy(string1.GetData()->nDataLength, string1.m_pchData, 1, &ch);
    return s;
}

CString AFXAPI operator+(WCHAR ch, const CString& string)
{
    CString s;
    s.ConcatCopy(1, &ch, string.GetData()->nDataLength, string.m_pchData);
    return s;
}

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

int CString::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(WCHAR));
        GetData()->nDataLength = nNewLength - nCount;
    }

    return nNewLength;
}

int CString::Insert(int nIndex, WCHAR ch)
{
    CopyBeforeWrite();

    if (nIndex < 0)
        nIndex = 0;

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

    if (GetData()->nAllocLength < nNewLength)
    {
        CStringData<WCHAR>* pOldData = GetData();
        LPWSTR pstr = m_pchData;
        AllocBuffer(nNewLength);
        memcpy(m_pchData, pstr, (pOldData->nDataLength+1)*sizeof(WCHAR));
        CString::Release(pOldData);
    }

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

    return nNewLength;
}

int CString::Insert(int nIndex, LPCWSTR 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)
        {
            CStringData<WCHAR>* pOldData = GetData();
            LPWSTR lpwsz = m_pchData;
            AllocBuffer(nNewLength);
            memcpy(m_pchData, lpwsz, (pOldData->nDataLength+1)*sizeof(WCHAR));
            CString::Release(pOldData);
        }

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

    return nNewLength;
}

int CString::Replace(WCHAR chOld, WCHAR chNew)
{
    int nCount = 0;

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


int CString::Replace(LPCWSTR lpszOld, LPCWSTR lpszNew)
{
    return ReplaceRoutine(lpszOld, lpszNew, wcsstr);
}

int CString::ReplaceI(LPCWSTR lpszOld, LPCWSTR lpszNew)
{
    return ReplaceRoutine(lpszOld, lpszNew, wcsistr);
}

int CString::ReplaceRoutine(LPCWSTR lpszOld, LPCWSTR lpszNew, _pfn_wcsstr tcsstr)
{
    // 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;
    LPWSTR lpszStart = m_pchData;
    LPWSTR lpszEnd = m_pchData + GetData()->nDataLength;
    LPWSTR lpszTarget;
    while (lpszStart < lpszEnd)
    {
        while ((lpszTarget = tcsstr(lpszStart, lpszOld)) != NULL)
        {
            nCount++;
            lpszStart = lpszTarget + nSourceLen;
        }
        lpszStart += wcslen(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 + 1 || GetData()->nRefs > 1)
        {
            CStringData<WCHAR>* pOldData = GetData();
            LPWSTR pstr = m_pchData;
            AllocBuffer(nNewLength);
            memcpy(m_pchData, pstr, pOldData->nDataLength*sizeof(WCHAR));
            CString::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 = wcsstr(lpszStart, lpszOld)) != NULL)
            {
                int nBalance = nOldLength - ((int)(lpszTarget - m_pchData) + nSourceLen);
                memmove(lpszTarget + nReplacementLen, lpszTarget + nSourceLen,
                    nBalance * sizeof(WCHAR));
                memcpy(lpszTarget, lpszNew, nReplacementLen*sizeof(WCHAR));
                lpszStart = lpszTarget + nReplacementLen;
                lpszStart[nBalance] = '\0';
                nOldLength += (nReplacementLen - nSourceLen);
            }
            lpszStart += wcslen(lpszStart) + 1;
        }
        ASSERT(m_pchData[nNewLength] == '\0', "CString::ReplaceRoutine");
        GetData()->nDataLength = nNewLength;
    }

    return nCount;
}

int CString::Remove(WCHAR chRemove)
{
    CopyBeforeWrite();

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

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

    return nCount;
}

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

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

CString CString::Mid(int nFirst, int nCount) const
{
    CString dest;
    Mid(nFirst, nCount, dest);
    return dest;
}

CString CString::Right(int nCount) const
{
    CString dest;
    Right(nCount, dest);
    return dest;
}

CString CString::Left(int nCount) const
{
    CString dest;
    Left(nCount, dest);
    return dest;
}

// strspn equivalent
CString CString::SpanIncluding(LPCWSTR lpszCharSet) const
{
    ASSERT(AfxIsValidString(lpszCharSet), "CString::SpanIncluding");
    return Left(wcsspn(m_pchData, lpszCharSet));
}

// strcspn equivalent
CString CString::SpanExcluding(LPCWSTR lpszCharSet) const
{
    ASSERT(AfxIsValidString(lpszCharSet), "CString::SpanIncluding");
    return Left(wcscspn(m_pchData, lpszCharSet));
}

void CString::Mid(int nFirst, CString & csMid) const
{
    Mid(nFirst, GetData()->nDataLength - nFirst, csMid);
}

void CString::Mid(int nFirst, int nCount, CString & csMid) 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;

    ASSERT(nFirst >= 0, "CString::Mid(int nFirst, int nCount)");
    ASSERT(nFirst + nCount <= GetData()->nDataLength, "CString::Mid(int nFirst, int nCount)");

    // optimize case of returning entire string
    if (nFirst == 0 && nFirst + nCount == GetData()->nDataLength)
    {
        csMid = *this;
        return;
    }

    AllocCopy(csMid, nCount, nFirst, 0);
}

void CString::Right(int nCount, CString & csRight) const
{
    if (nCount < 0)
        nCount = 0;
    if (nCount >= GetData()->nDataLength)
        return;

    AllocCopy(csRight, nCount, GetData()->nDataLength-nCount, 0);
}

void CString::Left(int nCount, CString & csLeft) const
{
    if (nCount < 0)
        nCount = 0;
    if (nCount >= GetData()->nDataLength)
        return;

    AllocCopy(csLeft, nCount, 0, 0);
}

void CString::SpanIncluding(const WCHAR * lpszCharSet, CString & csSpanInc) const
{
    ASSERT(AfxIsValidString(lpszCharSet), "CString::SpanIncluding");
    return Left(wcsspn(m_pchData, lpszCharSet), csSpanInc);
}

void CString::SpanExcluding(const WCHAR * lpszCharSet, CString & csSpanExc) const
{
    ASSERT(AfxIsValidString(lpszCharSet), "CString::SpanIncluding");
    return Left(wcscspn(m_pchData, lpszCharSet), csSpanExc);
}

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

int CString::ReverseFind(WCHAR ch) const
{
    // find last single character
    LPCWSTR lpsz = wcsrchr(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)
int CString::Find(LPCWSTR lpszSub) const
{
    return Find(lpszSub, 0);
}

int CString::Find(LPCWSTR lpszSub, int nStart) const
{
    ASSERT(AfxIsValidString(lpszSub), "CString::Find");

    int nLength = GetData()->nDataLength;
    if (nStart > nLength)
        return -1;

    // find first matching substring
    LPWSTR lpsz = wcsstr(m_pchData + nStart, lpszSub);

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


/////////////////////////////////////////////////////////////////////////////
// CString formatting

#define TCHAR_ARG   WCHAR
#define WCHAR_ARG   WCHAR
#define CHAR_ARG    WCHAR

#ifdef _X86_
    #define DOUBLE_ARG  _AFX_DOUBLE
#else
    #define DOUBLE_ARG  double
#endif

#define FORCE_ANSI      0x10000
#define FORCE_UNICODE   0x20000
#define FORCE_INT64     0x40000

void CString::FormatV(LPCWSTR lpszFormat, va_list argList)
{
    ASSERT(AfxIsValidString(lpszFormat), "CString::FormatV");

    va_list argListSave = argList;

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

        int nItemLen = 0;

        // handle '%' character with format
        int nWidth = 0;
        for (; *lpsz != '\0'; lpsz = wcsinc(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 = _wtoi(lpsz);
            for (; *lpsz != '\0' && iswdigit(*lpsz); lpsz = wcsinc(lpsz))
                ;
        }
        ASSERT(nWidth >= 0, "CString::FormatV");

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

            // get precision and skip it
            if (*lpsz == '*')
            {
                nPrecision = va_arg(argList, int);
                lpsz = wcsinc(lpsz);
            }
            else
            {
                nPrecision = _wtoi(lpsz);
                for (; *lpsz != '\0' && iswdigit(*lpsz); lpsz = wcsinc(lpsz))
                    ;
            }
            ASSERT(nPrecision >= 0, "CString::FormatV");
        }

        // should be on type modifier or specifier
        int nModifier = 0;
        if (wcsncmp(lpsz, L"I64", 3) == 0)
        {
            lpsz += 3;
            nModifier = FORCE_INT64;
#if !defined(_X86_) && !defined(_ALPHA_)
            // __int64 is only available on X86 and ALPHA platforms
            ASSERT(FALSE, "CString::FormatV");
#endif
        }
        else
        {
            switch (*lpsz)
            {
            // modifiers that affect size
            case 'h':
                nModifier = FORCE_ANSI;
                lpsz = wcsinc(lpsz);
                break;
            case 'l':
                nModifier = FORCE_UNICODE;
                lpsz = wcsinc(lpsz);
                break;

            // modifiers that do not affect size
            case 'F':
            case 'N':
            case 'L':
                lpsz = wcsinc(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':
            {
                LPCWSTR pstrNextArg = va_arg(argList, LPCWSTR);
                if (pstrNextArg == NULL)
                   nItemLen = 6;  // "(null)"
                else
                {
                   nItemLen = wcslen(pstrNextArg);
                   nItemLen = max(1, nItemLen);
                }
            }
            break;

        case 'S':
            {
#ifndef _UNICODE
                LPWSTR pstrNextArg = va_arg(argList, LPWSTR);
                if (pstrNextArg == NULL)
                   nItemLen = 6;  // "(null)"
                else
                {
                   nItemLen = wcslen(pstrNextArg);
                   nItemLen = max(1, nItemLen);
                }
#else
                LPCWSTR pstrNextArg = va_arg(argList, LPCWSTR);
                if (pstrNextArg == NULL)
                   nItemLen = 6; // "(null)"
                else
                {
                   nItemLen = wcslenChars(pstrNextArg);
                   nItemLen = max(1, nItemLen);
                }
#endif
            }
            break;

        case 's'|FORCE_ANSI:
        case 'S'|FORCE_ANSI:
            {
                LPCWSTR pstrNextArg = va_arg(argList, LPCWSTR);
                if (pstrNextArg == NULL)
                   nItemLen = 6; // "(null)"
                else
                {
                   nItemLen = wcslen(pstrNextArg);
                   nItemLen = max(1, nItemLen);
                }
            }
            break;

        case 's'|FORCE_UNICODE:
        case 'S'|FORCE_UNICODE:
            {
                LPWSTR pstrNextArg = va_arg(argList, LPWSTR);
                if (pstrNextArg == NULL)
                   nItemLen = 6; // "(null)"
                else
                {
                   nItemLen = wcslen(pstrNextArg);
                   nItemLen = max(1, nItemLen);
                }
            }
            break;
        }

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

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

            case 'f':
                va_arg(argList, DOUBLE_ARG);
                nItemLen = 128; // width isn't truncated
                // 312 == strlen("-1+(309 zeroes).")
                // 309 zeroes == max precision of a double
                nItemLen = max(nItemLen, 312+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, "CString::FormatV");  // unknown formatting option
            }
        }

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

    GetBuffer(nMaxLen);
    int nChars = _vsnwprintf(m_pchData, nMaxLen, lpszFormat, argListSave);
    ASSERT(nChars <= GetAllocLength(), "CString::FormatV");
    ReleaseBuffer();

    va_end(argListSave);
}

// formatting (using wsprintf style formatting)
void AFX_CDECL CString::Format(LPCWSTR lpszFormat, ...)
{
    ASSERT(AfxIsValidString(lpszFormat), "CString::Format");

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

// formatting (using FormatMessage style formatting)
void AFX_CDECL CString::FormatMessage(LPCWSTR lpszFormat, ...)
{
    // format message into temporary buffer lpszTemp
    va_list argList;
    va_start(argList, lpszFormat);
    LPWSTR lpszTemp;

    if (::FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
        lpszFormat, 0, 0, (LPWSTR)&lpszTemp, 0, &argList) == 0 ||
        lpszTemp == NULL)
    {
        CSTRING_THROW_EXCEPTION
    }
    else
    {
        // assign lpszTemp into the resulting string and free the temporary
        *this = lpszTemp;
        LocalFree(lpszTemp);
        va_end(argList);
    }
}

void CString::TrimRight(LPCWSTR lpszTargetList)
{
    // find beginning of trailing matches
    // by starting at beginning (DBCS aware)

    CopyBeforeWrite();
    LPWSTR lpsz = m_pchData;
    LPWSTR lpszLast = NULL;

    while (*lpsz != '\0')
    {
        if (wcschr(lpszTargetList, *lpsz) != NULL)
        {
            if (lpszLast == NULL)
                lpszLast = lpsz;
        }
        else
            lpszLast = NULL;
        lpsz = wcsinc(lpsz);
    }

    if (lpszLast != NULL)
    {
        // truncate at left-most matching character
        *lpszLast = '\0';
        GetData()->nDataLength = (int)(lpszLast - m_pchData);
    }
}

void CString::TrimRight(WCHAR chTarget)
{
    // find beginning of trailing matches
    // by starting at beginning (DBCS aware)

    CopyBeforeWrite();
    LPWSTR lpsz = m_pchData;
    LPWSTR lpszLast = NULL;

    while (*lpsz != '\0')
    {
        if (*lpsz == chTarget)
        {
            if (lpszLast == NULL)
                lpszLast = lpsz;
        }
        else
            lpszLast = NULL;
        lpsz = wcsinc(lpsz);
    }

    if (lpszLast != NULL)
    {
        // truncate at left-most matching character
        *lpszLast = '\0';
        GetData()->nDataLength = (int)(lpszLast - m_pchData);
    }
}

void CString::TrimRight()
{
    // find beginning of trailing spaces by starting at beginning (DBCS aware)

    CopyBeforeWrite();
    LPWSTR lpsz = m_pchData;
    LPWSTR lpszLast = NULL;

    while (*lpsz != '\0')
    {
        if (iswspace(*lpsz))
        {
            if (lpszLast == NULL)
                lpszLast = lpsz;
        }
        else
            lpszLast = NULL;
        lpsz = wcsinc(lpsz);
    }

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

void CString::TrimLeft(LPCWSTR lpszTargets)
{
    // if we're not trimming anything, we're not doing any work
    if (SafeStrlen(lpszTargets) == 0)
        return;

    CopyBeforeWrite();
    LPCWSTR lpsz = m_pchData;

    while (*lpsz != '\0')
    {
        if (wcschr(lpszTargets, *lpsz) == NULL)
            break;
        lpsz = wcsinc(lpsz);
    }

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

void CString::TrimLeft(WCHAR chTarget)
{
    // find first non-matching character

    CopyBeforeWrite();
    LPCWSTR lpsz = m_pchData;

    while (chTarget == *lpsz)
        lpsz = wcsinc(lpsz);

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

void CString::TrimLeft()
{
    // find first non-space character

    CopyBeforeWrite();
    LPCWSTR lpsz = m_pchData;

    while (iswspace(*lpsz))
        lpsz = wcsinc(lpsz);

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

void CString::SplitPath(
    CString * csDrive,
    CString * csDir,
    CString * csName,
    CString * csExt) const
{
    WCHAR * drive = NULL;
    WCHAR * dir   = NULL;
    WCHAR * name  = NULL;
    WCHAR * ext   = NULL;

    if (csDrive)
    {
        drive = csDrive->GetBuffer(_MAX_DRIVE);
    } 
    if (csDir)
    {
        dir = csDir->GetBuffer(_MAX_DIR);
    } 
    if (csName)
    {
        name = csName->GetBuffer(_MAX_FNAME);
    } 
    if (csExt)
    {
        ext = csExt->GetBuffer(_MAX_EXT);
    } 
    _wsplitpath(Get(), drive, dir, name, ext);

    if (csDrive)
    {
        csDrive->ReleaseBuffer(-1);
    } 
    if (csDir)
    {
        csDir->ReleaseBuffer(-1);
    } 
    if (csName)
    {
        csName->ReleaseBuffer(-1);
    } 
    if (csExt)
    {
        csExt->ReleaseBuffer(-1);
    } 
}

void CString::MakePath(
    const CString * csDrive,
    const CString * csDir,
    const CString * csName,
    const CString * csExt)
{
    Truncate(0);

    if (csDrive && !csDrive->IsEmpty())
    {
        ConcatInPlace(SafeStrlen(csDrive->Get()), csDrive->Get());
    }
    if (csDir && !csDir->IsEmpty())
    {
        ConcatInPlace(SafeStrlen(csDir->Get()), csDir->Get());
    }
    if (csName && !csName->IsEmpty())
    {
        // Make sure there is a \ between the two
        if (!IsEmpty() && !IsPathSep(GetLength()) && !csName->IsPathSep(0) )
        {
            ConcatInPlace(1, L"\\");
        }
        ConcatInPlace(SafeStrlen(csName->Get()), csName->Get());
    }
    if (csExt && !csExt->IsEmpty())
    {
        // Make sure the extension has a dot
        if (csExt->GetAt(0) != L'.')
        {
            ConcatInPlace(1, L".");
        }
        ConcatInPlace(SafeStrlen(csExt->Get()), csExt->Get());
    }
}

void CString::AppendPath(const WCHAR * lpszPath)
{
    int nLen = GetLength();
    BOOL bThisHasSep = (nLen > 0) ? IsPathSep(nLen - 1) : FALSE;
    BOOL bThatHasSep = ShimLib::IsPathSep(*lpszPath);
    
    if (lpszPath == NULL || *lpszPath == 0)
    {
        return;
    }
    else if (nLen == 0)
    {
        // No path seperator is necessary
    }
    else if ((nLen == 2) && (GetAt(1) == L':') && !bThatHasSep )
    {
        // We must place a path seperator between the two
        ConcatInPlace(1, L"\\");
    }
    else if (!bThisHasSep && !bThatHasSep )
    {
        // We must place a path seperator between the two
        ConcatInPlace(1, L"\\");
    }
    else if (bThisHasSep && bThatHasSep )
    {
        // Both have seperators, remove one
        do
        {
            lpszPath += 1;
        }
        while (ShimLib::IsPathSep(*lpszPath));
    }
    ConcatInPlace(SafeStrlen(lpszPath), lpszPath);
}

// Find the trailing path component
// Return index of the last path seperator or -1 if none found
int CString::FindLastPathComponent() const
{
    for (int nLen = GetLength() - 1; nLen >= 0; --nLen)
    {
        if (IsPathSep(nLen))
        {
            return nLen;
        }
    }

    return -1;
}

// Remove the trailing path component from the string
void CString::StripPath()
{
    int nLastPathComponent = FindLastPathComponent();
    if (nLastPathComponent != -1)
    {
        Truncate(nLastPathComponent);
    }
    else
    {
        Truncate(0);
    }
}

char * CString::GetAnsi() const
{
    // Since we don't know if the original (WCHAR) data has changed
    // we need to update the ansi string each time.
    if (m_pchDataAnsi)
    {
        free(m_pchDataAnsi);
        m_pchDataAnsi = NULL;
    }
    
    // Get the number of bytes necessary for the WCHAR string
    int nBytes = WideCharToMultiByte(CP_ACP, 0, m_pchData, -1, NULL, 0, NULL, NULL);
    m_pchDataAnsi = (char *) malloc(nBytes);
    if (m_pchDataAnsi)
    {
        WideCharToMultiByte(CP_ACP, 0, m_pchData, -1, m_pchDataAnsi, nBytes, NULL, NULL);
    }
    else
    {
        CSTRING_THROW_EXCEPTION
    }

    return m_pchDataAnsi; 
}

void CString::GetLastPathComponent(CString & pathComponent) const
{
    int nPath = FindLastPathComponent();
    if (nPath < 0)
    {
        pathComponent = *this;
    }
    else
    {
        Mid(nPath+1, pathComponent);
    }
}

// Get what's not the "file" portion of this path
void CString::GetNotLastPathComponent(CString & pathComponent) const
{
    int nPath = FindLastPathComponent();
    if (nPath < 1)
    {
        pathComponent.Truncate(0);
    }
    else
    {
        Left(nPath, pathComponent);
    }
}

// Get the Drive portion of this path,
// Either C: or \\server\disk format.
void CString::GetDrivePortion(CString & csDrivePortion) const
{
    const WCHAR * lpwszPath = Get();

    const WCHAR * lpwszNonDrivePortion = ShimLib::GetDrivePortion(lpwszPath);

    if (lpwszPath == lpwszNonDrivePortion)
    {
        csDrivePortion.Truncate(0);
    }
    else
    {
        Left((int)(lpwszNonDrivePortion - lpwszPath), csDrivePortion);
    }
}

DWORD CString::GetModuleFileNameW(
  HMODULE hModule    // handle to module
)
{
    // Force the size to MAX_PATH because there is no way to determine necessary buffer size.

    WCHAR * lpsz = GetBuffer(MAX_PATH);
    DWORD dwChars = ::GetModuleFileNameW(hModule, lpsz, MAX_PATH);
    ReleaseBuffer(dwChars);

    return dwChars;
}

DWORD CString::GetSystemDirectoryW(void)
{
    UINT dwChars = ::GetSystemDirectoryW(NULL, 0);
    if (dwChars)
    {
        dwChars += 1;   // One for the NULL

        // Get a pointer to the actual lpsz data
        WCHAR * lpszPath = GetBuffer(dwChars);

        dwChars = ::GetSystemDirectoryW(lpszPath, dwChars);
        
        ReleaseBuffer(dwChars);
    } 
    else
    {
        Truncate(0);
    }

    return dwChars;
}

UINT CString::GetSystemWindowsDirectoryW(void)
{
    UINT dwChars = ::GetSystemWindowsDirectoryW(NULL, 0);
    if (dwChars)
    {
        dwChars += 1;   // One for the NULL

        // Get a pointer to the actual lpsz data
        WCHAR * lpszPath = GetBuffer(dwChars);

        dwChars = ::GetSystemWindowsDirectoryW(lpszPath, dwChars);
        
        ReleaseBuffer(dwChars);
    } 
    else
    {
        Truncate(0);
    }

    return dwChars;
}


DWORD CString::GetWindowsDirectoryW(void)
{
    UINT dwChars = ::GetWindowsDirectoryW(NULL, 0);
    if (dwChars)
    {
        dwChars += 1;   // One for the NULL

        // Get a pointer to the actual lpsz data
        WCHAR * lpszPath = GetBuffer(dwChars);

        dwChars = ::GetWindowsDirectoryW(lpszPath, dwChars);
        
        ReleaseBuffer(dwChars);
    } 
    else
    {
        Truncate(0);
    }

    return dwChars;
}

DWORD CString::GetShortPathNameW(void)
{
    DWORD dwChars = ::GetShortPathNameW(Get(), NULL, 0);
    if (dwChars)
    {
        dwChars += 1;   // One for the NULL

        CString csCopy(*this);
        
        // Get a pointer to the actual lpsz data
        WCHAR * lpszPath = GetBuffer(dwChars);

        dwChars = ::GetShortPathNameW(csCopy, lpszPath, dwChars);
        
        ReleaseBuffer(dwChars);
    } 

    return dwChars;
}

DWORD CString::GetLongPathNameW(void)
{
    DWORD dwChars = ::GetLongPathNameW(Get(), NULL, 0);
    if (dwChars)
    {
        dwChars += 1;   // One for the NULL

        CString csCopy(*this);
        
        // Get a pointer to the actual lpsz data
        WCHAR * lpszPath = GetBuffer(dwChars);

        dwChars = ::GetLongPathNameW(csCopy, lpszPath, dwChars);
        
        ReleaseBuffer(dwChars);
    } 

    return dwChars;
}

DWORD CString::GetFullPathNameW(void)
{
    DWORD dwChars = ::GetFullPathNameW(Get(), 0, NULL, NULL);
    if (dwChars)
    {
        dwChars += 1;   // One for the NULL

        CString csCopy(*this);
        
        // Get a pointer to the actual lpsz data
        WCHAR * lpszPath = GetBuffer(dwChars);

        dwChars = ::GetFullPathNameW(csCopy, dwChars, lpszPath, NULL);
        
        ReleaseBuffer(dwChars);
    } 

    return dwChars;
}

DWORD CString::GetTempPathW(void)
{
    DWORD dwChars = ::GetTempPathW(0, NULL);
    if (dwChars)
    {
        dwChars += 1;   // One for the NULL

        // Get a pointer to the actual lpsz data
        WCHAR * lpszPath = GetBuffer(dwChars);

        dwChars = ::GetTempPathW(dwChars, lpszPath);
        
        ReleaseBuffer(dwChars);
    } 
    else
    {
        Truncate(0);
    }

    return dwChars;
}

UINT CString::GetTempFileNameW(
  LPCWSTR lpPathName,      // directory name
  LPCWSTR lpPrefixString,  // file name prefix
  UINT uUnique            // integer
)
{
    WCHAR * lpsz = GetBuffer(MAX_PATH);
    DWORD dwChars = ::GetTempFileNameW(lpPathName, lpPrefixString, uUnique, lpsz);
    ReleaseBuffer(dwChars);

    return dwChars;
}


DWORD CString::GetCurrentDirectoryW(void)
{
    DWORD dwChars = ::GetCurrentDirectory(0, NULL);
    if (dwChars)
    {
        dwChars += 1;   // One for the NULL

        // Get a pointer to the actual lpsz data
        WCHAR * lpsz = GetBuffer(dwChars);

        dwChars = ::GetCurrentDirectoryW(dwChars, lpsz);

        ReleaseBuffer(dwChars);
    }
    else
    {
        Truncate(0);
    }

    return dwChars;
}

DWORD CString::ExpandEnvironmentStringsW( )
{
    DWORD dwChars = ::ExpandEnvironmentStringsW(Get(), NULL, 0);
    if (dwChars)
    {
        dwChars += 1;   // One for the NULL

        CString csCopy(*this);

        // Get a pointer to the actual lpsz data
        WCHAR * lpszPath = GetBuffer(dwChars);

        dwChars = ::ExpandEnvironmentStringsW(csCopy, lpszPath, dwChars);
        
        ReleaseBuffer(dwChars-1);
    } 

    return dwChars;
}


// delete all characters to the right of nIndex
void CString::Truncate(int nIndex)
{
    ASSERT(nIndex >= 0, "CString::Truncate");

    if (nIndex < GetLength())
    {
        SetAt(nIndex, L'\0');
        GetData()->nDataLength = nIndex;
    }
}


// Copy the ansi version of this string into the specified buffer,
// If the buffer is not large enough, no data is copied.
//
// Returns the number of BYTES necessary for the buffer
//
DWORD CString::CopyAnsiBytes(char * lpszBuffer, DWORD nBytes)
{
    const char * lpszAnsi = GetAnsi();

    // Length of cstring, in bytes, not including terminator.
    int nDataBytes = strlen(lpszAnsi);

    // Length of buffer (no terminator)
    int nBufBytes = nBytes - 1;

    // Only copy the data if the buffer is large enough
    if (nDataBytes <= nBufBytes)
    {
        // Copy the data into the buffer
        strcpy(lpszBuffer, lpszAnsi);
    }

    // return the actual size of string
    return nDataBytes;
}


// Copy the ansi version of this string into the specified buffer,
// If the buffer is not large enough, no data is copied.
//
// Returns the number of CHARS necessary for the buffer
//
DWORD CString::CopyAnsiChars(char * lpszBuffer, DWORD nChars)
{
    const char * lpszAnsi = GetAnsi();

    // Length of cstring, in bytes, not including terminator.
    int nDataChars = GetLength();

    // Length of buffer (no terminator)
    int nBufChars = nChars - 1;

    // Only copy the data if the buffer is large enough
    if (nDataChars <= nBufChars)
    {
        // Copy the data into the buffer
        strcpy(lpszBuffer, lpszAnsi);
    }

    // return the actual size of string
    return nDataChars;
}



BOOL CString::PatternMatch(const WCHAR * lpszPattern) const
{
    return PatternMatchW(lpszPattern, Get());
}

};  // end of namespace ShimLib