//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================

#define __STDC_LIMIT_MACROS
#include <stdint.h>

#include <tier1/utlstring.h>
#include <tier1/strtools.h>
#include <ctype.h>

// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"

//-----------------------------------------------------------------------------
// Simple string class.
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Either allocates or reallocates memory to the length
//
// Allocated space for length characters.  It automatically adds space for the
// nul and the cached length at the start of the memory block.  Will adjust
// m_pString and explicitly set the nul at the end before returning.
void *CUtlString::AllocMemory(uint32 length)
{
    void *pMemoryBlock;
    if (m_pString)
    {
        pMemoryBlock = realloc(m_pString, length + 1);
    }
    else
    {
        pMemoryBlock = malloc(length + 1);
    }
    m_pString         = (char *) pMemoryBlock;
    m_pString[length] = 0;

    return pMemoryBlock;
}

//-----------------------------------------------------------------------------
void CUtlString::SetDirect(const char *pValue, int nChars)
{
    if (pValue && nChars > 0)
    {
        if (pValue == m_pString)
        {
            AssertMsg(nChars == Q_strlen(m_pString), "CUtlString::SetDirect does not support resizing strings "
                                                     "in place.");
            return; // Do nothing. Realloc in AllocMemory might move pValue's
                    // location resulting in a bad memcpy.
        }

        Assert(nChars <= Min<int>(strnlen(pValue, nChars) + 1, nChars));
        AllocMemory(nChars);
        Q_memcpy(m_pString, pValue, nChars);
    }
    else
    {
        Purge();
    }
}

void CUtlString::Set(const char *pValue)
{
    int length = pValue ? V_strlen(pValue) : 0;
    SetDirect(pValue, length);
}

// Sets the length (used to serialize into the buffer )
void CUtlString::SetLength(int nLen)
{
    if (nLen > 0)
    {
#ifdef _DEBUG
        int prevLen = m_pString ? Length() : 0;
#endif
        AllocMemory(nLen);
#ifdef _DEBUG
        if (nLen > prevLen)
        {
            V_memset(m_pString + prevLen, 0xEB, nLen - prevLen);
        }
#endif
    }
    else
    {
        Purge();
    }
}

const char *CUtlString::Get() const
{
    if (!m_pString)
    {
        return "";
    }
    return m_pString;
}

char *CUtlString::GetForModify()
{
    if (!m_pString)
    {
        // In general, we optimise away small mallocs for empty strings
        // but if you ask for the non-const bytes, they must be writable
        // so we can't return "" here, like we do for the const version - jd
        void *pMemoryBlock = malloc(1);
        m_pString          = (char *) pMemoryBlock;
        *m_pString         = 0;
    }

    return m_pString;
}

char CUtlString::operator[](int i) const
{
    if (!m_pString)
        return '\0';

    if (i >= Length())
    {
        return '\0';
    }

    return m_pString[i];
}

void CUtlString::Clear()
{
    Purge();
}

void CUtlString::Purge()
{
    free(m_pString);
    m_pString = NULL;
}

bool CUtlString::IsEqual_CaseSensitive(const char *src) const
{
    if (!src)
    {
        return (Length() == 0);
    }
    return (V_strcmp(Get(), src) == 0);
}

bool CUtlString::IsEqual_CaseInsensitive(const char *src) const
{
    if (!src)
    {
        return (Length() == 0);
    }
    return (V_stricmp(Get(), src) == 0);
}

void CUtlString::ToLower()
{
    if (!m_pString)
    {
        return;
    }

    V_strlower(m_pString);
}

void CUtlString::ToUpper()
{
    if (!m_pString)
    {
        return;
    }

    V_strupr(m_pString);
}

CUtlString &CUtlString::operator=(const CUtlString &src)
{
    SetDirect(src.Get(), src.Length());
    return *this;
}

CUtlString &CUtlString::operator=(const char *src)
{
    Set(src);
    return *this;
}

bool CUtlString::operator==(const CUtlString &src) const
{
    if (IsEmpty())
    {
        if (src.IsEmpty())
        {
            return true;
        }

        return false;
    }
    else
    {
        if (src.IsEmpty())
        {
            return false;
        }

        return Q_strcmp(m_pString, src.m_pString) == 0;
    }
}

CUtlString &CUtlString::operator+=(const CUtlString &rhs)
{
    const int lhsLength(Length());
    const int rhsLength(rhs.Length());

    if (!rhsLength)
    {
        return *this;
    }

    const int requestedLength(lhsLength + rhsLength);

    AllocMemory(requestedLength);
    Q_memcpy(m_pString + lhsLength, rhs.m_pString, rhsLength);

    return *this;
}

CUtlString &CUtlString::operator+=(const char *rhs)
{
    const int lhsLength(Length());
    const int rhsLength(V_strlen(rhs));
    const int requestedLength(lhsLength + rhsLength);

    if (!requestedLength)
    {
        return *this;
    }

    AllocMemory(requestedLength);
    Q_memcpy(m_pString + lhsLength, rhs, rhsLength);

    return *this;
}

CUtlString &CUtlString::operator+=(char c)
{
    const int lhsLength(Length());

    AllocMemory(lhsLength + 1);
    m_pString[lhsLength] = c;

    return *this;
}

CUtlString &CUtlString::operator+=(int rhs)
{
    Assert(sizeof(rhs) == 4);

    char tmpBuf[12]; // Sufficient for a signed 32 bit integer [ -2147483648 to
                     // +2147483647 ]
    V_snprintf(tmpBuf, sizeof(tmpBuf), "%d", rhs);
    tmpBuf[sizeof(tmpBuf) - 1] = '\0';

    return operator+=(tmpBuf);
}

CUtlString &CUtlString::operator+=(double rhs)
{
    char tmpBuf[256]; // How big can doubles be???  Dunno.
    V_snprintf(tmpBuf, sizeof(tmpBuf), "%lg", rhs);
    tmpBuf[sizeof(tmpBuf) - 1] = '\0';

    return operator+=(tmpBuf);
}

bool CUtlString::MatchesPattern(const CUtlString &Pattern, int nFlags) const
{
    const char *pszSource  = String();
    const char *pszPattern = Pattern.String();
    bool bExact            = true;

    while (1)
    {
        if ((*pszPattern) == 0)
        {
            return ((*pszSource) == 0);
        }

        if ((*pszPattern) == '*')
        {
            pszPattern++;

            if ((*pszPattern) == 0)
            {
                return true;
            }

            bExact = false;
            continue;
        }

        int nLength = 0;

        while ((*pszPattern) != '*' && (*pszPattern) != 0)
        {
            nLength++;
            pszPattern++;
        }

        while (1)
        {
            const char *pszStartPattern = pszPattern - nLength;
            const char *pszSearch       = pszSource;

            for (int i = 0; i < nLength; i++, pszSearch++, pszStartPattern++)
            {
                if ((*pszSearch) == 0)
                {
                    return false;
                }

                if ((*pszSearch) != (*pszStartPattern))
                {
                    break;
                }
            }

            if (pszSearch - pszSource == nLength)
            {
                break;
            }

            if (bExact == true)
            {
                return false;
            }

            if ((nFlags & PATTERN_DIRECTORY) != 0)
            {
                if ((*pszPattern) != '/' && (*pszSource) == '/')
                {
                    return false;
                }
            }

            pszSource++;
        }

        pszSource += nLength;
    }
}

int CUtlString::Format(const char *pFormat, ...)
{
    va_list marker;

    va_start(marker, pFormat);
    int len = FormatV(pFormat, marker);
    va_end(marker);

    return len;
}

//--------------------------------------------------------------------------------------------------
// This can be called from functions that take varargs.
//--------------------------------------------------------------------------------------------------

int CUtlString::FormatV(const char *pFormat, va_list marker)
{
    char tmpBuf[4096]; //< Nice big 4k buffer, as much memory as my first
                       // computer had, a Radio Shack Color Computer

    // va_start( marker, pFormat );
    int len = V_vsprintf_safe(tmpBuf, pFormat, marker);
    // va_end( marker );
    Set(tmpBuf);
    return len;
}

//-----------------------------------------------------------------------------
// Strips the trailing slash
//-----------------------------------------------------------------------------
void CUtlString::StripTrailingSlash()
{
    if (IsEmpty())
        return;

    int nLastChar = Length() - 1;
    char c        = m_pString[nLastChar];
    if (c == '\\' || c == '/')
    {
        SetLength(nLastChar);
    }
}

void CUtlString::FixSlashes(char cSeparator /*=CORRECT_PATH_SEPARATOR*/)
{
    if (m_pString)
    {
        V_FixSlashes(m_pString, cSeparator);
    }
}

//-----------------------------------------------------------------------------
// Trim functions
//-----------------------------------------------------------------------------
void CUtlString::TrimLeft(char cTarget)
{
    int nIndex = 0;

    if (IsEmpty())
    {
        return;
    }

    while (m_pString[nIndex] == cTarget)
    {
        ++nIndex;
    }

    // We have some whitespace to remove
    if (nIndex > 0)
    {
        memcpy(m_pString, &m_pString[nIndex], Length() - nIndex);
        SetLength(Length() - nIndex);
    }
}

void CUtlString::TrimLeft(const char *szTargets)
{
    int i;

    if (IsEmpty())
    {
        return;
    }

    for (i = 0; m_pString[i] != 0; i++)
    {
        bool bWhitespace = false;

        for (int j = 0; szTargets[j] != 0; j++)
        {
            if (m_pString[i] == szTargets[j])
            {
                bWhitespace = true;
                break;
            }
        }

        if (!bWhitespace)
        {
            break;
        }
    }

    // We have some whitespace to remove
    if (i > 0)
    {
        memcpy(m_pString, &m_pString[i], Length() - i);
        SetLength(Length() - i);
    }
}

void CUtlString::TrimRight(char cTarget)
{
    const int nLastCharIndex = Length() - 1;
    int nIndex               = nLastCharIndex;

    while (nIndex >= 0 && m_pString[nIndex] == cTarget)
    {
        --nIndex;
    }

    // We have some whitespace to remove
    if (nIndex < nLastCharIndex)
    {
        m_pString[nIndex + 1] = 0;
        SetLength(nIndex + 2);
    }
}

void CUtlString::TrimRight(const char *szTargets)
{
    const int nLastCharIndex = Length() - 1;
    int i;

    for (i = nLastCharIndex; i > 0; i--)
    {
        bool bWhitespace = false;

        for (int j = 0; szTargets[j] != 0; j++)
        {
            if (m_pString[i] == szTargets[j])
            {
                bWhitespace = true;
                break;
            }
        }

        if (!bWhitespace)
        {
            break;
        }
    }

    // We have some whitespace to remove
    if (i < nLastCharIndex)
    {
        m_pString[i + 1] = 0;
        SetLength(i + 2);
    }
}

void CUtlString::Trim(char cTarget)
{
    TrimLeft(cTarget);
    TrimRight(cTarget);
}

void CUtlString::Trim(const char *szTargets)
{
    TrimLeft(szTargets);
    TrimRight(szTargets);
}

CUtlString CUtlString::Slice(int32 nStart, int32 nEnd) const
{
    int length = Length();
    if (length == 0)
    {
        return CUtlString();
    }

    if (nStart < 0)
        nStart = length - (-nStart % length);
    else if (nStart >= length)
        nStart = length;

    if (nEnd == INT32_MAX)
        nEnd = length;
    else if (nEnd < 0)
        nEnd = length - (-nEnd % length);
    else if (nEnd >= length)
        nEnd = length;

    if (nStart >= nEnd)
        return CUtlString();

    const char *pIn = String();

    CUtlString ret;
    ret.SetDirect(pIn + nStart, nEnd - nStart);
    return ret;
}

// Grab a substring starting from the left or the right side.
CUtlString CUtlString::Left(int32 nChars) const
{
    return Slice(0, nChars);
}

CUtlString CUtlString::Right(int32 nChars) const
{
    return Slice(-nChars);
}

CUtlString CUtlString::Replace(char cFrom, char cTo) const
{
    if (!m_pString)
    {
        return CUtlString();
    }

    CUtlString ret = *this;
    int len        = ret.Length();
    for (int i = 0; i < len; i++)
    {
        if (ret.m_pString[i] == cFrom)
            ret.m_pString[i] = cTo;
    }

    return ret;
}

CUtlString CUtlString::Replace(const char *pszFrom, const char *pszTo) const
{
    Assert(pszTo);               // Can be 0 length, but not null
    Assert(pszFrom && *pszFrom); // Must be valid and have one character.

    const char *pos = V_strstr(String(), pszFrom);
    if (!pos)
    {
        return *this;
    }

    const char *pFirstFound = pos;

    // count number of search string
    int nSearchCount  = 0;
    int nSearchLength = V_strlen(pszFrom);
    while (pos)
    {
        nSearchCount++;
        int nSrcOffset = (pos - String()) + nSearchLength;
        pos            = V_strstr(String() + nSrcOffset, pszFrom);
    }

    // allocate the new string
    int nReplaceLength = V_strlen(pszTo);
    int nAllocOffset   = nSearchCount * (nReplaceLength - nSearchLength);
    size_t srcLength   = Length();
    CUtlString strDest;
    size_t destLength = srcLength + nAllocOffset;
    strDest.SetLength(destLength);

    // find and replace the search string
    pos             = pFirstFound;
    int nDestOffset = 0;
    int nSrcOffset  = 0;
    while (pos)
    {
        // Found an instance
        int nCurrentSearchOffset = pos - String();
        int nCopyLength          = nCurrentSearchOffset - nSrcOffset;
        V_strncpy(strDest.GetForModify() + nDestOffset, String() + nSrcOffset, nCopyLength + 1);
        nDestOffset += nCopyLength;
        V_strncpy(strDest.GetForModify() + nDestOffset, pszTo, nReplaceLength + 1);
        nDestOffset += nReplaceLength;

        nSrcOffset = nCurrentSearchOffset + nSearchLength;
        pos        = V_strstr(String() + nSrcOffset, pszFrom);
    }

    // making sure that the left over string from the source is the same size as
    // the left over dest buffer
    Assert(destLength - nDestOffset == srcLength - nSrcOffset);
    if (destLength - nDestOffset > 0)
    {
        V_strncpy(strDest.GetForModify() + nDestOffset, String() + nSrcOffset, destLength - nDestOffset + 1);
    }

    return strDest;
}

CUtlString CUtlString::AbsPath(const char *pStartingDir) const
{
    char szNew[MAX_PATH];
    V_MakeAbsolutePath(szNew, sizeof(szNew), this->String(), pStartingDir);
    return CUtlString(szNew);
}

CUtlString CUtlString::UnqualifiedFilename() const
{
    const char *pFilename = V_UnqualifiedFileName(this->String());
    return CUtlString(pFilename);
}

CUtlString CUtlString::DirName() const
{
    CUtlString ret(this->String());
    V_StripLastDir((char *) ret.Get(), ret.Length() + 1);
    V_StripTrailingSlash((char *) ret.Get());
    return ret;
}

CUtlString CUtlString::StripExtension() const
{
    char szTemp[MAX_PATH];
    V_StripExtension(String(), szTemp, sizeof(szTemp));
    return CUtlString(szTemp);
}

CUtlString CUtlString::StripFilename() const
{
    const char *pFilename = V_UnqualifiedFileName(Get()); // NOTE: returns 'Get()' on failure, never NULL
    int nCharsToCopy      = pFilename - Get();
    CUtlString result;
    result.SetDirect(Get(), nCharsToCopy);
    result.StripTrailingSlash();
    return result;
}

CUtlString CUtlString::GetBaseFilename() const
{
    char szTemp[MAX_PATH];
    V_FileBase(String(), szTemp, sizeof(szTemp));
    return CUtlString(szTemp);
}

CUtlString CUtlString::GetExtension() const
{
    char szTemp[MAX_PATH];
    V_ExtractFileExtension(String(), szTemp, sizeof(szTemp));
    return CUtlString(szTemp);
}

CUtlString CUtlString::PathJoin(const char *pStr1, const char *pStr2)
{
    char szPath[MAX_PATH];
    V_ComposeFileName(pStr1, pStr2, szPath, sizeof(szPath));
    return CUtlString(szPath);
}

CUtlString CUtlString::operator+(const char *pOther) const
{
    CUtlString s = *this;
    s += pOther;
    return s;
}

CUtlString CUtlString::operator+(const CUtlString &other) const
{
    CUtlString s = *this;
    s += other;
    return s;
}

CUtlString CUtlString::operator+(int rhs) const
{
    CUtlString ret = *this;
    ret += rhs;
    return ret;
}

//-----------------------------------------------------------------------------
// Purpose: concatenate the provided string to our current content
//-----------------------------------------------------------------------------
void CUtlString::Append(const char *pchAddition)
{
    (*this) += pchAddition;
}

void CUtlString::Append(const char *pchAddition, int nChars)
{
    nChars = Min<int>(nChars, V_strlen(pchAddition));

    const int lhsLength(Length());
    const int rhsLength(nChars);
    const int requestedLength(lhsLength + rhsLength);

    AllocMemory(requestedLength);
    const int allocatedLength(requestedLength);
    const int copyLength(allocatedLength - lhsLength < rhsLength ? allocatedLength - lhsLength : rhsLength);
    memcpy(GetForModify() + lhsLength, pchAddition, copyLength);
    m_pString[allocatedLength] = '\0';
}

// Shared static empty string.
const CUtlString &CUtlString::GetEmptyString()
{
    static const CUtlString s_emptyString;

    return s_emptyString;
}