///////////////////////////////////////////////////////////////////////////////
/*  File: strclass.cpp

    Description: Typical class to handle strings.


    Revision History:

    Date        Description                                          Programmer
    --------    ---------------------------------------------------  ----------
    07/01/97    Initial creation.                                    BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
#include "pch.h"
#pragma hdrstop

#include "strclass.h"


#ifdef StrCpy
#   undef StrCpy
#endif
#ifdef StrCpyN
#   undef StrCpyN
#endif
#ifdef StrLen
#   undef StrLen
#endif

#ifdef UNICODE
#   define StrCpy   StrCpyW
#   define StrCpyN  StrCpyNW
#   define StrLen   StrLenW
#else
#   define StrCpy   StrCpyA
#   define StrCpyN  StrCpyNA
#   define StrLen   StrLenA
#endif // UNICODE

const INT MAX_RESOURCE_STR_LEN = 4097;


//
// Disable "'operator ->' is not a UDT or reference to a UDT" warning.
// This is caused when we create an autoptr to a non-UDT.  It's meaningless
// since there's no reason to call operator-> on a non-UDT autoptr.
//
#pragma warning (disable : 4284)

CString::CString(
    VOID
    ) : m_pValue(new StringValue())
{

}

CString::CString(
    INT cch
    ) : m_pValue(new StringValue(cch))
{

}

CString::CString(
    LPCSTR pszA
    ) : m_pValue(new StringValue(pszA))
{

}

CString::CString(
    LPCWSTR pszW
    ) : m_pValue(new StringValue(pszW))
{

}

CString::CString(
    const CString& rhs
    ) : m_pValue(rhs.m_pValue)
{
    InterlockedIncrement(&(m_pValue->m_cRef));
}


CString::CString(
    HINSTANCE hInst,
    INT idMsg,
    ...
    ) : m_pValue(NULL)
{
    LPTSTR pszMsg = NULL;
    va_list args;

    va_start(args, idMsg);

    Format(hInst, idMsg, &args);

    va_end(args);
}

CString::~CString(
    VOID
    )
{
    if (NULL != m_pValue)
    {
        if (0 == InterlockedDecrement(&(m_pValue->m_cRef)))
            delete m_pValue;
    }
}

//
// Length of string, excluding nul terminator.
//
INT
CString::Length(
    VOID
    ) const
{
    return m_pValue->Length();
}


VOID
CString::Empty(
    VOID
    )
{
    if (0 == InterlockedDecrement(&(m_pValue->m_cRef)))
    {
        delete m_pValue;
        m_pValue = NULL;
    }

    m_pValue = new StringValue();
}

BOOL
CString::IsEmpty(
    VOID
    ) const
{
    return (NULL != m_pValue && 0 == m_pValue->Length());
}



CString&
CString::operator = (
    const CString& rhs
    )
{
    if (m_pValue != rhs.m_pValue) // Chk for assignment to *this.
    {
        if (0 == InterlockedDecrement(&(m_pValue->m_cRef)))
            delete m_pValue;

        m_pValue = rhs.m_pValue;
        InterlockedIncrement(&(m_pValue->m_cRef));
    }
    return *this;
}

CString&
CString::operator = (
    LPCWSTR rhsW
    )
{
    if (0 == InterlockedDecrement(&(m_pValue->m_cRef)))
    {
        delete m_pValue;
        m_pValue = NULL;
    }

    m_pValue = new StringValue(rhsW);
    return *this;
}


CString&
CString::operator = (
    LPCSTR rhsA
    )
{
    if (0 == InterlockedDecrement(&(m_pValue->m_cRef)))
    {
        delete m_pValue;
        m_pValue = NULL;
    }

    m_pValue = new StringValue(rhsA);
    return *this;
}


CString
CString::operator + (
    const CString& rhs
    ) const
{
    CString strNew;
    LPTSTR pszTemp = NULL;
    try
    {
        pszTemp = StringValue::Concat(m_pValue, rhs.m_pValue);
        strNew = pszTemp;
    }
    catch(...)
    {
        delete[] pszTemp;
        throw;
    }
    delete[] pszTemp;

    return strNew;
}



CString&
CString::operator += (
    const CString& rhs
    )
{
    LPTSTR pszTemp = NULL;
    try
    {
        pszTemp = StringValue::Concat(m_pValue, rhs.m_pValue);
        *this = pszTemp;
    }
    catch(...)
    {
        delete[] pszTemp;
        throw;
    }
    delete[] pszTemp;
    return *this;
}


BOOL
CString::operator == (
    const CString& rhs
    ) const
{
    return (0 == lstrcmp(m_pValue->m_psz, rhs.m_pValue->m_psz));
}


INT
CString::Compare(
    LPCWSTR rhsW
    ) const
{
    StringValue Value(rhsW);
    return lstrcmp(m_pValue->m_psz, Value.m_psz);
}


INT
CString::Compare(
    LPCSTR rhsA
    ) const
{
    StringValue Value(rhsA);
    return lstrcmp(m_pValue->m_psz, Value.m_psz);
}

INT
CString::CompareNoCase(
    LPCWSTR rhsW
    ) const
{
    StringValue Value(rhsW);
    return lstrcmpi(m_pValue->m_psz, Value.m_psz);
}


INT
CString::CompareNoCase(
    LPCSTR rhsA
    ) const
{
    StringValue Value(rhsA);
    return lstrcmpi(m_pValue->m_psz, Value.m_psz);
}

BOOL
CString::operator < (
    const CString& rhs
    ) const
{
    return (0 > lstrcmp(m_pValue->m_psz, rhs.m_pValue->m_psz));
}


TCHAR
CString::operator[](
    INT index
    ) const
{
    if (!ValidIndex(index))
        throw CMemoryException(CMemoryException::index);

    return m_pValue->m_psz[index];
}


TCHAR&
CString::operator[](
    INT index
    )
{
    if (!ValidIndex(index))
        throw CMemoryException(CMemoryException::index);

    CopyOnWrite();
    return m_pValue->m_psz[index];
}

INT
CString::First(
    TCHAR ch
    ) const
{
    LPCTSTR psz = m_pValue->m_psz;
    LPCTSTR pszLast = psz;
    INT i = 0;
    while(psz && *psz)
    {
        if (ch == *psz)
            return i;

        psz = CharNext(psz);
        i += (INT)(psz - pszLast);
        pszLast = psz;
    }
    return -1;
}


INT
CString::Last(
    TCHAR ch
    ) const
{
    INT iLast = -1;
    INT i = 0;
    LPCTSTR psz = m_pValue->m_psz;
    LPCTSTR pszPrev = psz;
    while(psz && *psz)
    {
        if (ch == *psz)
            iLast = i;

        psz = CharNext(psz);
        i += (INT)(psz - pszPrev);
        pszPrev = psz;
    }
    return iLast;
}



CString
CString::SubString(
    INT iFirst,
    INT cch
    )
{

    if (!ValidIndex(iFirst))
        throw CMemoryException(CMemoryException::index);

    INT cchToEnd = Length() - iFirst;

    if (-1 == cch || cch > cchToEnd)
        return CString(m_pValue->m_psz + iFirst);

    LPTSTR pszTemp = new TCHAR[cch + 1];
    if (NULL == pszTemp)
        throw CAllocException();

    CString::StrCpyN(pszTemp, m_pValue->m_psz + iFirst, cch + 1);
    CString strTemp(pszTemp);
    delete[] pszTemp;

    return strTemp;
}


VOID
CString::ToUpper(
    INT iFirst,
    INT cch
    )
{
    if (!ValidIndex(iFirst))
        throw CMemoryException(CMemoryException::index);

    CopyOnWrite();
    INT cchToEnd = Length() - iFirst;

    if (-1 == cch || cch > cchToEnd)
        cch = cchToEnd;

    CharUpperBuff(m_pValue->m_psz + iFirst, cch);
}


VOID
CString::ToLower(
    INT iFirst,
    INT cch
    )
{
    if (!ValidIndex(iFirst))
        throw CMemoryException(CMemoryException::index);

    CopyOnWrite();
    INT cchToEnd = Length() - iFirst;

    if (-1 == cch || cch > cchToEnd)
        cch = cchToEnd;

    CharLowerBuff(m_pValue->m_psz + iFirst, cch);
}


VOID
CString::Size(
    INT cch
    )
{
    StringValue *m_psv = new StringValue(cch + 1);
    CString::StrCpyN(m_psv->m_psz, m_pValue->m_psz, cch);

    if (m_pValue->m_cRef > 1)
    {
        InterlockedDecrement(&(m_pValue->m_cRef));
    }
    else
    {
        delete m_pValue;
    }
    m_pValue = m_psv;
}


VOID
CString::CopyOnWrite(
    VOID
    )
{
    //
    // Only need to copy if ref cnt > 1.
    //
    if (m_pValue->m_cRef > 1)
    {
        LPTSTR pszTemp = m_pValue->m_psz;
        InterlockedDecrement(&(m_pValue->m_cRef));
        m_pValue = NULL;
        m_pValue = new StringValue(pszTemp);
    }
}



BOOL
CString::Format(
    LPCTSTR pszFmt,
    ...
    )
{
    BOOL bResult;
    va_list args;
    va_start(args, pszFmt);
    bResult = Format(pszFmt, &args);
    va_end(args);

    return bResult;
}


BOOL
CString::Format(
    LPCTSTR pszFmt,
    va_list *pargs
    )
{
    BOOL bResult = FALSE;
    TCHAR szBuffer[MAX_RESOURCE_STR_LEN];
    INT cchLoaded;

    cchLoaded = ::FormatMessage(FORMAT_MESSAGE_FROM_STRING,
                                pszFmt,
                                0,
                                0,
                                szBuffer,
                                ARRAYSIZE(szBuffer),
                                pargs);

    if (0 < cchLoaded)
    {
        if (NULL != m_pValue && 0 == InterlockedDecrement(&(m_pValue->m_cRef)))
            delete m_pValue;

        m_pValue = NULL;
        m_pValue = new StringValue(szBuffer);

        bResult = TRUE;
    }
    else
    {
        DWORD dwLastError = GetLastError();
        if (ERROR_SUCCESS != dwLastError)
        {
            DBGERROR((TEXT("CString::Format failed with error 0x%08X"), dwLastError));
            throw CResourceException(CResourceException::string, NULL, 0);
        }
    }

    return bResult;
}


BOOL
CString::Format(
    HINSTANCE hInst,
    UINT idFmt,
    ...
    )
{
    BOOL bResult;
    va_list args;
    va_start(args, idFmt);
    bResult = Format(hInst, idFmt, &args);
    va_end(args);
    return bResult;
}


BOOL
CString::Format(
    HINSTANCE hInst,
    UINT idFmt,
    va_list *pargs
    )
{
    BOOL bResult = FALSE;

    TCHAR szFmtStr[MAX_RESOURCE_STR_LEN]; // Buffer for format string (if needed).
    INT cchLoaded;

    //
    // Try to load the format string as a string resource.
    //
    cchLoaded = ::LoadString(hInst, idFmt, szFmtStr, ARRAYSIZE(szFmtStr));

    if (0 < cchLoaded)
    {
        //
        // The format string was in a string resource.
        // Now format it with the arg list.
        //
        bResult = Format(szFmtStr, pargs);
    }
    else
    {
        TCHAR szBuffer[MAX_RESOURCE_STR_LEN];

        //
        // The format string may be in a message resource.
        // Note that if it is, the resulting formatted string will
        // be automatically attached to m_psz by ::FormatMessage.
        //
        cchLoaded = ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                                    FORMAT_MESSAGE_FROM_HMODULE,
                                    hInst,
                                    (DWORD)idFmt,
                                    LANGIDFROMLCID(GetThreadLocale()),
                                    (LPTSTR)szBuffer,
                                    ARRAYSIZE(szBuffer),
                                    pargs);

        if (0 < cchLoaded)
        {
            if (NULL != m_pValue && 0 == InterlockedDecrement(&(m_pValue->m_cRef)))
                delete m_pValue;

            m_pValue = NULL;
            m_pValue = new StringValue(szBuffer);

            bResult = TRUE;
        }
        else
        {
            DWORD dwLastError = GetLastError();
            if (ERROR_SUCCESS != dwLastError)
            {
                DBGERROR((TEXT("CString::Format failed with error 0x%08X"), dwLastError));
                throw CResourceException(CResourceException::string, hInst, idFmt);
            }
        }
    }
    return bResult;
}


LPTSTR
CString::GetBuffer(
    INT cchMax
    )
{
    if (-1 == cchMax)
        cchMax = m_pValue->m_cchAlloc;

    CopyOnWrite();
    if (cchMax > m_pValue->m_cchAlloc)
    {
        //
        // Extend the buffer, copying original contents to dest.
        //
        StringValue *pv = new StringValue(cchMax);

        StrCpyN(pv->m_psz, m_pValue->m_psz, cchMax);

        if (0 == InterlockedDecrement(&(m_pValue->m_cRef)))
            delete m_pValue;

        m_pValue = pv;

        LPTSTR pszEnd = m_pValue->m_psz + m_pValue->m_cchAlloc - 1;
        if (pszEnd >= m_pValue->m_psz && TEXT('\0') != *(pszEnd))
        {
            //
            // Ensure it's nul terminated.
            //
            *(pszEnd) = TEXT('\0');
        }
    }

    return m_pValue->m_psz;
}


VOID
CString::ReleaseBuffer(
    void
    )
{
    //
    // Update the string length member after client has had free access
    // to internal buffer.
    //
    m_pValue->m_cch = StrLen(m_pValue->m_psz);
}

void
CString::Rtrim(
    void
    )
{
    LPTSTR s = GetBuffer();
    int len = Length();

    while(0 < --len && IsWhiteSpace(s[len]))
        s[len] = TEXT('\0');

    ReleaseBuffer();
}


void
CString::Ltrim(
    void
    )
{
    LPTSTR s0;
    LPTSTR s = s0 = GetBuffer();

    while(*s && IsWhiteSpace(*s))
        s++;
    while(*s)
        *s0++ = *s++;
    *s0 = TEXT('\0');

    ReleaseBuffer();
}


VOID
CString::ExpandEnvironmentStrings(
    VOID
    )
{
    DWORD cchBuffer = 0;  // Size of expansion buffer.
    DWORD cchPath   = 0;  // Count of actual chars in expanded buffer.

    CString strExpanded;  // Expansion buffer.

    //
    // If necessary, keep increasing expansion buffer size until entire
    // expanded string fits.
    //
    do
    {
        cchBuffer += MAX_PATH;

        cchPath = ::ExpandEnvironmentStrings(*this,
                                             strExpanded.GetBuffer(cchBuffer),
                                             cchBuffer);
    }
    while(0 != cchPath && cchPath > cchBuffer);
    ReleaseBuffer();

    *this = strExpanded;
}


bool
CString::GetDisplayRect(
    HDC hdc,
    LPRECT prc
    ) const
{
    return (0 != DrawText(hdc, Cstr(), Length(), prc, DT_CALCRECT));
}


VOID
CString::DebugOut(
    BOOL bNewline
    ) const
{
    OutputDebugString(m_pValue->m_psz);
    if (bNewline)
        OutputDebugString(TEXT("\n\r"));
}


CString::StringValue::StringValue(
    VOID
    ) : m_psz(new TCHAR[1]),
        m_cchAlloc(1),
        m_cch(0),
        m_cRef(1)
{
    *m_psz = TEXT('\0');
}


CString::StringValue::StringValue(
    LPCSTR pszA
    ) : m_psz(NULL),
        m_cchAlloc(0),
        m_cch(0),
        m_cRef(1)
{
#ifdef UNICODE
    m_psz = AnsiToWide(pszA, &m_cchAlloc);
    m_cch = StrLenW(m_psz);
#else
    m_cch = CString::StrLenA(pszA);
    m_psz = Dup(pszA, m_cch + 1);
    m_cchAlloc = m_cch + 1;
#endif
}

CString::StringValue::StringValue(
    LPCWSTR pszW
    ) : m_psz(NULL),
        m_cchAlloc(0),
        m_cch(0),
        m_cRef(1)
{
#ifdef UNICODE
    m_cch = CString::StrLenW(pszW);
    m_psz = Dup(pszW, m_cch + 1);
    m_cchAlloc = m_cch + 1;
#else
    m_psz  = WideToAnsi(pszW, &m_cchAlloc);
    m_cch  = StrLenA(m_psz);
#endif
}

CString::StringValue::StringValue(
    INT cch
    ) : m_psz(NULL),
        m_cchAlloc(0),
        m_cch(0),
        m_cRef(0)
{
    m_psz      = Dup(TEXT(""), cch);
    m_cRef     = 1;
    m_cchAlloc = cch;
}


CString::StringValue::~StringValue(
    VOID
    )
{
    delete[] m_psz;
}

LPWSTR
CString::StringValue::AnsiToWide(
    LPCSTR pszA,
    INT *pcch
    )
{
    INT cchW    = 0;
    LPWSTR pszW = NULL;

    cchW = MultiByteToWideChar(CP_ACP,
                               0,
                               pszA,
                               -1,
                               NULL,
                               0);

    pszW = new WCHAR[cchW];
    if (NULL == pszW)
        throw CAllocException();

    MultiByteToWideChar(CP_ACP,
                        0,
                        pszA,
                        -1,
                        pszW,
                        cchW);

    if (NULL != pcch)
        *pcch = cchW;

    return pszW;
}

LPSTR
CString::StringValue::WideToAnsi(
    LPCWSTR pszW,
    INT *pcch
    )
{
    INT cchA   = 0;
    LPSTR pszA = NULL;

    cchA = WideCharToMultiByte(CP_ACP,
                               0,
                               pszW,
                               -1,
                               NULL,
                               0,
                               NULL,
                               NULL);

    pszA = new CHAR[cchA];
    if (NULL == pszA)
        throw CAllocException();

    WideCharToMultiByte(CP_ACP,
                        0,
                        pszW,
                        -1,
                        pszA,
                        cchA,
                        NULL,
                        NULL);

    if (NULL != pcch)
        *pcch = cchA;

    return pszA;
}


INT
CString::StringValue::Length(
    VOID
    ) const
{
    if (0 == m_cch && NULL != m_psz)
    {
        m_cch = StrLen(m_psz);
    }
    return m_cch;
}


LPWSTR
CString::StringValue::Dup(
    LPCWSTR pszW,
    INT cch
    )
{
    if (0 == cch)
        cch = CString::StrLenW(pszW) + 1;

    LPWSTR pszNew = new WCHAR[cch];
    if (NULL == pszNew)
        throw CAllocException();

    CString::StrCpyW(pszNew, pszW);
    return pszNew;
}


LPSTR
CString::StringValue::Dup(
    LPCSTR pszA,
    INT cch
    )
{
    if (0 == cch)
        cch = CString::StrLenA(pszA) + 1;

    LPSTR pszNew = new CHAR[cch];
    if (NULL == pszNew)
        throw CAllocException();

    CString::StrCpyA(pszNew, pszA);
    return pszNew;
}


LPTSTR
CString::StringValue::Concat(
    CString::StringValue *psv1,
    CString::StringValue *psv2
    )
{
    LPTSTR pszTemp = NULL;
    INT len1 = psv1->Length();
    INT len2 = psv2->Length();

    pszTemp = new TCHAR[len1 + len2 + 1];
    if (NULL == pszTemp)
        throw CAllocException();

    CString::StrCpy(pszTemp, psv1->m_psz);
    CString::StrCpy(pszTemp + len1, psv2->m_psz);

    return pszTemp;
}


#pragma warning (default : 4284)
