//=============================================================================
//
// Adventure Game Studio (AGS)
//
// Copyright (C) 1999-2011 Chris Jones and 2011-2026 various contributors
// The full list of copyright holders can be found in the Copyright.txt
// file, which is part of this source code distribution.
//
// The AGS source code is provided under the Artistic License 2.0.
// A copy of this license can be found in the file License.txt and at
// https://opensource.org/license/artistic-2-0/
//
//=============================================================================
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <cctype>
#include "util/math.h"
#include "util/stream.h"
#include "util/string.h"
#include "util/string_compat.h"
#include "utf8.h"

namespace AGS
{
namespace Common
{

String::String()
    : _cstr(const_cast<char*>(""))
    , _len(0)
    , _buf(nullptr)
{
}

String::String(const String &str)
    : _cstr(const_cast<char*>(""))
    , _len(0)
    , _buf(nullptr)
{
    *this = str;
}

String::String(String &&str)
{
    _cstr = str._cstr;
    _len = str._len;
    _buf = str._buf;
    _bufHead = str._bufHead;
    str._cstr = const_cast<char*>("");
    str._len = 0;
    str._buf = nullptr;
    str._bufHead = nullptr;
}

String::String(const std::string &str)
    : _cstr(const_cast<char*>(""))
    , _len(0)
    , _buf(nullptr)
{
    *this = str;
}

String::String(const char *cstr)
    : _cstr(const_cast<char*>(""))
    , _len(0)
    , _buf(nullptr)
{
    *this = cstr;
}

String::String(const char *cstr, size_t length)
    : _cstr(const_cast<char*>(""))
    , _len(0)
    , _buf(nullptr)
{
    SetString(cstr, length);
}

String::String(char c, size_t count)
    : _cstr(const_cast<char*>(""))
    , _len(0)
    , _buf(nullptr)
{
    FillString(c, count);
}

String::~String()
{
    Free();
}

bool String::IsNullOrSpace() const
{
    if (_len == 0)
        return true;
    for (const char *ptr = _cstr; *ptr; ++ptr)
    {
        if (!std::isspace(static_cast<uint8_t>(*ptr)))
            return false;
    }
    return true;
}

void String::Read(Stream *in, size_t max_chars, bool stop_at_limit)
{
    assert(in);
    Empty();
    if (max_chars == 0 && stop_at_limit)
    {
        return;
    }

    char buffer[1024];
    char *read_ptr = buffer;
    size_t read_size = 0;
    int ichar;
    do
    {
        ichar = in->ReadByte();
        read_size++;
        if (read_size > max_chars)
        {
            continue;
        }
        *read_ptr = (char)(ichar >= 0 ? ichar : 0);
        if (!*read_ptr || ((read_ptr - buffer) == (sizeof(buffer) - 1 - 1)))
        {
            buffer[sizeof(buffer) - 1] = 0;
            Append(buffer);
            read_ptr = buffer;
        }
        else
        {
            read_ptr++;
        }
    }
    while(ichar > 0 && !(stop_at_limit && read_size == max_chars));
}

void String::ReadCount(Stream *in, size_t count)
{
    assert(in);
    if (count > 0)
    {
        ReserveAndShift(false, count);
        count = in->Read(_cstr, count);
        _cstr[count] = 0;
        _len = strlen(_cstr);
    }
    else
    {
        Empty();
    }
}

void String::Write(Stream *out) const
{
    assert(out);
    out->Write(_cstr, _len + 1);
}

void String::WriteCount(Stream *out, size_t count) const
{
    assert(out);
    size_t str_out_len = std::min(count - 1, _len);
    if (str_out_len > 0)
        out->Write(_cstr, str_out_len);
    size_t null_out_len = count - str_out_len;
    if (null_out_len > 0)
        out->WriteByteCount(0, null_out_len);
}

int String::Compare(const char *cstr) const
{
    return strcmp(_cstr, cstr ? cstr : "");
}

int String::CompareNoCase(const char *cstr) const
{
    return ags_stricmp(_cstr, cstr ? cstr : "");
}

int String::CompareUtf8NoCase(const char *cstr) const
{
    return Utf8::CStrCompareNoCase(_cstr, cstr ? cstr : "");
}

int String::CompareLeft(const char *cstr, size_t count) const
{
    cstr = cstr ? cstr : "";
    return strncmp(_cstr, cstr, count != NoIndex ? count : strlen(cstr));
}

int String::CompareLeftNoCase(const char *cstr, size_t count) const
{
    cstr = cstr ? cstr : "";
    return ags_strnicmp(_cstr, cstr, count != NoIndex ? count : strlen(cstr));
}

int String::CompareMid(const char *cstr, size_t from, size_t count) const
{
    cstr = cstr ? cstr : "";
    from = std::min(from, _len);
    return strncmp(_cstr + from, cstr, count != NoIndex ? count : strlen(cstr));
}

int String::CompareMidNoCase(const char *cstr, size_t from, size_t count) const
{
    cstr = cstr ? cstr : "";
    from = std::min(from, _len);
    return ags_strnicmp(_cstr + from, cstr, count != NoIndex ? count : strlen(cstr));
}

int String::CompareRight(const char *cstr, size_t count) const
{
    cstr = cstr ? cstr : "";
    count = count != NoIndex ? count : strlen(cstr);
    size_t off = std::min(_len, count);
    return strncmp(_cstr + _len - off, cstr, count);
}

int String::CompareRightNoCase(const char *cstr, size_t count) const
{
    cstr = cstr ? cstr : "";
    count = count != NoIndex ? count : strlen(cstr);
    size_t off = std::min(_len, count);
    return ags_strnicmp(_cstr + _len - off, cstr, count);
}

size_t String::FindChar(char c, size_t from) const
{
    if (c && from < _len)
    {
        const char *found_cstr = strchr(_cstr + from, c);
        return found_cstr ? found_cstr - _cstr : NoIndex;
    }
    return NoIndex;
}

size_t String::FindCharReverse(char c, size_t from) const
{
    if ((_len == 0) || !c)
    {
        return NoIndex;
    }

    from = std::min(from, _len - 1);
    const char *seek_ptr = _cstr + from;
    while (seek_ptr >= _cstr)
    {
        if (*seek_ptr == c)
        {
            return seek_ptr - _cstr;
        }
        seek_ptr--;
    }
    return NoIndex;
}

size_t String::FindAnyChar(const char *chars, size_t from) const
{
    if (chars && chars[0] && from < _len)
    {
        const char *found_cstr = strpbrk(_cstr + from, chars);
        return found_cstr ? found_cstr - _cstr : NoIndex;
    }
    return NoIndex;
}

size_t String::FindString(const char *cstr, size_t from) const
{
    if (cstr && from < _len)
    {
        const char * found_cstr = strstr(_cstr + from, cstr);
        return found_cstr ? found_cstr - _cstr : NoIndex;
    }
    return NoIndex;
}

bool String::FindSection(char separator, size_t first, size_t last, bool exclude_first_sep, bool exclude_last_sep,
                        size_t &from, size_t &to) const
{
    if ((_len == 0) || !separator)
    {
        return false;
    }
    if (first > last)
    {
        return false;
    }

    size_t this_field = 0;
    size_t slice_from = 0;
    size_t slice_to = _len;
    size_t slice_at = NoIndex;
    do
    {
        slice_at = FindChar(separator, slice_at + 1);
        if (slice_at == NoIndex)
            slice_at = _len;
        // found where previous field ends
        if (this_field == last)
        {
            // if previous field is the last one to be included,
            // then set the section tail
            slice_to = exclude_last_sep ? slice_at : slice_at + 1;
        }
        if (slice_at != _len)
        {
            this_field++;
            if (this_field == first)
            {
                // if the new field is the first one to be included,
                // then set the section head
                slice_from = exclude_first_sep ? slice_at + 1 : slice_at;
            }
        }
    }
    while (slice_at < _len && this_field <= last);

    // the search is a success if at least the first field was found
    if (this_field >= first)
    {
        // correct the indices to stay in the [0; length] range
        assert(slice_from <= slice_to);
        from = Math::Clamp(slice_from, (size_t)0, _len);
        to   = Math::Clamp(slice_to, (size_t)0, _len);
        return true;
    }
    return false;
}

size_t String::FindSection(const String &section_text, char separator, bool case_insensitive) const
{
    if (!separator)
    {
        return NoIndex;
    }

    for (size_t prev_sep = 0u, sec_at = 0u, next_sep = FindChar(separator);
        prev_sep != NoIndex;
        prev_sep = next_sep,
            sec_at = prev_sep != NoIndex ? prev_sep + 1 : NoIndex,
            next_sep = prev_sep != NoIndex ? FindChar(separator, prev_sep + 1) : NoIndex)
    {
        // TODO: CompareMid (and similar methods) logic results in success if substring matches arg's beginning, while being shorter;
        // perhaps introduce Equals() that compares with a substring and is strict about both lengths
        const size_t sec_len = next_sep != String::NoIndex ? next_sep - sec_at : _len - sec_at;
        if (section_text.GetLength() != sec_len)
            continue;
        if ((case_insensitive && CompareMidNoCase(section_text, sec_at, sec_len) == 0) ||
            (!case_insensitive && CompareMid(section_text, sec_at, sec_len) == 0))
            return sec_at;
    }

    return NoIndex; // was not found
}

int String::ToInt() const
{
    return atoi(_cstr);
}

String String::Wrapper(const char *cstr)
{
    String str;
    // Note that String will NOT *modify* the const buffer.
    // Any write operation on the buffer is preceded by a call to BecomeUnique.
    str._cstr = const_cast<char*>(cstr ? cstr : "");
    str._len = strlen(str._cstr);
    return str;
}

/* static */ String String::FromFormat(const char *fcstr, ...)
{
    fcstr = fcstr ? fcstr : "";
    String str;
    va_list argptr;
    va_start(argptr, fcstr);
    str.FormatV(fcstr, argptr);
    va_end(argptr);
    return str;
}

/* static */ String String::FromFormatV(const char *fcstr, va_list argptr)
{
    String str;
    str.FormatV(fcstr, argptr);
    return str;
}

/* static */ String String::FromStream(Stream *in, size_t max_chars, bool stop_at_limit)
{
    String str;
    str.Read(in, max_chars, stop_at_limit);
    return str;
}

/* static */ String String::FromStreamCount(Stream *in, size_t count)
{
    String str;
    str.ReadCount(in, count);
    return str;
}

String String::Lower() const
{
    String str = *this;
    str.MakeLower();
    return str;
}

String String::Upper() const
{
    String str = *this;
    str.MakeUpper();
    return str;
}

String String::LowerUTF8() const
{
    String str = *this;
    str.MakeLowerUTF8();
    return str;
}

String String::UpperUTF8() const
{
    String str = *this;
    str.MakeUpperUTF8();
    return str;
}

String String::Left(size_t count) const
{
    count = std::min(count, _len);
    return count == _len ? *this : String(_cstr, count);
}

String String::Mid(size_t from, size_t count) const
{
    Math::ClampLength(from, count, (size_t)0, _len);
    return count == _len ? *this : String(_cstr + from, count);
}

String String::Right(size_t count) const
{
    count = std::min(count, _len);
    return count == _len ? *this : String(_cstr + _len - count, count);
}

String String::LeftSection(char separator, bool exclude_separator) const
{
    if ((_len != 0) && separator)
    {
        size_t slice_at = FindChar(separator);
        if (slice_at != NoIndex)
        {
            slice_at = exclude_separator ? slice_at : slice_at + 1;
            return Left(slice_at);
        }
    }
    return *this;
}

String String::RightSection(char separator, bool exclude_separator) const
{
    if ((_len != 0) && separator)
    {
        size_t slice_at = FindCharReverse(separator);
        if (slice_at != NoIndex)
        {
            size_t count = exclude_separator ? _len - slice_at - 1 : _len - slice_at;
            return Right(count);
        }
    }
    return *this;
}

String String::Section(char separator, size_t first, size_t last,
                          bool exclude_first_sep, bool exclude_last_sep) const
{
    if ((_len == 0) || !separator)
    {
        return String();
    }

    size_t slice_from;
    size_t slice_to;
    if (FindSection(separator, first, last, exclude_first_sep, exclude_last_sep,
        slice_from, slice_to))
    {
        return Mid(slice_from, slice_to - slice_from);
    }
    return String();
}

std::vector<String> String::Split(char separator) const
{
    if (separator == 0)
        return std::vector<String>{_cstr};

    std::vector<String> result;
    const char *ptr = _cstr;
    while (*ptr)
    {
        const char *found_cstr = strchr(ptr, separator);
        if (!found_cstr) break;
        result.push_back(String(ptr, found_cstr - ptr));
        ptr = found_cstr + 1;
    }
    result.push_back(String(ptr));
    return result;
}

void String::Reserve(size_t max_length)
{
    if (_bufHead)
    {
        if (max_length > _bufHead->Capacity)
        {
            // grow by 50%
            size_t grow_length = _bufHead->Capacity + (_bufHead->Capacity / 2);
            Copy(std::max(max_length, grow_length));
        }
    }
    else
    {
        Create(max_length);
    }
}

void String::ReserveMore(size_t more_length)
{
    Reserve(_len + more_length);
}

void String::Compact()
{
    if (_bufHead && _bufHead->Capacity > _len)
    {
        Copy(_len);
    }
}

void String::Append(const String &str)
{
    if (str._len > 0)
    {
        ReserveAndShift(false, str._len);
        memcpy(_cstr + _len, str._cstr, str._len);
        _len += str._len;
        _cstr[_len] = 0;
    }
}

void String::Append(const char *cstr, size_t len)
{
    if (len == 0)
        return;
    // Test for null-terminator in the range
    const char *ptr = cstr;
    for (; *ptr && (static_cast<size_t>(ptr - cstr) < len); ++ptr);
    if (static_cast<size_t>(ptr - cstr) < len)
        len = ptr - cstr;
    ReserveAndShift(false, len);
    memcpy(_cstr + _len, cstr, len);
    _len += len;
    _cstr[_len] = 0;
}

void String::AppendChar(char c)
{
    if (c)
    {
        ReserveAndShift(false, 1);
        _cstr[_len++] = c;
        _cstr[_len] = 0;
    }
}

void String::AppendFmt(const char *fcstr, ...)
{
    va_list argptr;
    va_start(argptr, fcstr);
    AppendFmtv(fcstr, argptr);
    va_end(argptr);
}

void String::AppendFmtv(const char *fcstr, va_list argptr)
{
    fcstr = fcstr ? fcstr : "";
    va_list argptr_cpy;
    va_copy(argptr_cpy, argptr);
    size_t length = vsnprintf(nullptr, 0u, fcstr, argptr);
    ReserveAndShift(false, length);
    vsprintf(_cstr + _len, fcstr, argptr_cpy);
    va_end(argptr_cpy);
    _len += length;
    _cstr[_len] = 0;
}

void String::ClipLeft(size_t count)
{
    if ((_len != 0) && (count > 0))
    {
        count = std::min(count, _len);
        BecomeUnique();
        _len -= count;
        _cstr += count;
    }
}

void String::ClipMid(size_t from, size_t count)
{
    if (from < _len)
    {
        count = std::min(count, _len - from);
        if (count > 0)
        {
            BecomeUnique();
            if (!from)
            {
                _len -= count;
                _cstr += count;
            }
            else if (from + count == _len)
            {
                _len -= count;
                _cstr[_len] = 0;
            }
            else
            {
                char *cstr_mid = _cstr + from;
                memmove(cstr_mid, _cstr + from + count, _len - from - count + 1);
                _len -= count;
            }
        }
    }
}

void String::ClipRight(size_t count)
{
    if (_len > 0 && count > 0)
    {
        count = std::min(count, _len);
        BecomeUnique();
        _len -= count;
        _cstr[_len] = 0;
    }
}

void String::ClipLeftSection(char separator, bool include_separator)
{
    if ((_len != 0) && separator)
    {
        size_t slice_at = FindChar(separator);
        if (slice_at != NoIndex)
        {
            ClipLeft(include_separator ? slice_at + 1 : slice_at);
        }
        else
            Empty();
    }
}

void String::ClipRightSection(char separator, bool include_separator)
{
    if ((_len != 0) && separator)
    {
        size_t slice_at = FindCharReverse(separator);
        if (slice_at != NoIndex)
        {
            ClipRight(include_separator ? _len - slice_at : _len - slice_at - 1);
        }
        else
            Empty();
    }
}

void String::ClipSection(char separator, size_t first, size_t last,
                              bool include_first_sep, bool include_last_sep)
{
    if ((_len == 0) || !separator)
    {
        return;
    }

    size_t slice_from;
    size_t slice_to;
    if (FindSection(separator, first, last, !include_first_sep, !include_last_sep,
        slice_from, slice_to))
    {
        ClipMid(slice_from, slice_to - slice_from);
    }
}

void String::Empty()
{
    if (IsShared())
    {
        Free();
    }
    else
    {
        _len = 0;
        _cstr[0] = 0;
    }
}

void String::FillString(char c, size_t count)
{
    if (count > 0)
    {
        ReserveAndShift(false, count);
        memset(_cstr, c, count);
        _len = count;
        _cstr[count] = 0;
    }
    else
    {
        Empty();
    }
}

void String::Format(const char *fcstr, ...)
{
    va_list argptr;
    va_start(argptr, fcstr);
    FormatV(fcstr, argptr);
    va_end(argptr);
}

void String::FormatV(const char *fcstr, va_list argptr)
{
    fcstr = fcstr ? fcstr : "";
    va_list argptr_cpy;
    va_copy(argptr_cpy, argptr);
    size_t length = vsnprintf(nullptr, 0u, fcstr, argptr);
    ReserveAndShift(false, Math::Surplus(length, _len));
    vsprintf(_cstr, fcstr, argptr_cpy);
    va_end(argptr_cpy);
    _len = length;
    _cstr[_len] = 0;
}

void String::Free()
{
    if (_bufHead)
    {
        assert(_bufHead->RefCount > 0);
        _bufHead->RefCount--;
        if (!_bufHead->RefCount)
        {
            delete [] _buf;
        }
    }
    _buf = nullptr;
    _cstr = const_cast<char*>("");
    _len = 0;
}

void String::MakeLower()
{
    if (_len != 0)
    {
        BecomeUnique();
        ags_strlwr(_cstr);
    }
}

void String::MakeUpper()
{
    if (_len != 0)
    {
        BecomeUnique();
        ags_strupr(_cstr);
    }
}

void String::MakeLowerUTF8()
{
    if (_len != 0)
    {
        const size_t newsize = Utf8::CStrToLower(_cstr, nullptr, 0u);
        if (newsize == _len + 1)
        {
            BecomeUnique();
            Utf8::CStrToLower(_cstr, _cstr, newsize);
        }
        else
        {
            String temp = *this; // TODO: we might benefit from a separate StringBuffer object
            Create(newsize - 1);
            Utf8::CStrToLower(temp.GetCStr(), _cstr, newsize);
        }
    }
}

void String::MakeUpperUTF8()
{
    if (_len != 0)
    {
        const size_t newsize = Utf8::CStrToUpper(_cstr, nullptr, 0);
        if (newsize == _len + 1)
        {
            BecomeUnique();
            Utf8::CStrToUpper(_cstr, _cstr, newsize);
        }
        else
        {
            String temp = *this; // TODO: we might benefit from a separate StringBuffer object
            Create(newsize - 1);
            Utf8::CStrToUpper(temp.GetCStr(), _cstr, newsize);
        }
    }
}

void String::MergeSequences(char c)
{
    if (_len <= 1)
        return; // no point merging if 1 char or less
    BecomeUnique();
    char last = 0;
    char *wp = _cstr;
    for (char *rp = _cstr; *rp; ++rp)
    {
        if ((c && *rp != c) || *rp != last)
            *(wp++) = *rp;
        last = *rp;
    }
    *wp = 0;
    _len = wp - _cstr;
}

void String::Prepend(const String &str)
{
    size_t length = str._len;
    if (length > 0)
    {
        ReserveAndShift(true, length);
        memcpy(_cstr - length, str._cstr, length);
        _len += length;
        _cstr -= length;
    }
}

void String::PrependChar(char c)
{
    if (c)
    {
        ReserveAndShift(true, 1);
        _len++;
        _cstr--;
        _cstr[0] = c;
    }
}

void String::Replace(char what, char with)
{
    if ((_len == 0) || !what || !with || (what == with))
        return;
    char *ptr = _cstr;
    // Special case for calling BecomeUnique on the first find
    if (IsShared())
    {
        for (; *ptr; ++ptr)
        {
            if (*ptr == what)
            {
                ptrdiff_t diff = ptr - _cstr;
                BecomeUnique();
                ptr = _cstr + diff; // BecomeUnique will realloc memory
                break;
            }
        }
    }
    // Do the full search & replace
    for (; *ptr; ++ptr)
    {
        if (*ptr == what)
            *ptr = with;
    }
}

void String::Replace(const String &what, const String &with)
{
    if ((what._len == 0) || (_len < what._len) || strcmp(what._cstr, with._cstr) == 0)
        return;

    const size_t len_src = what._len;
    const size_t len_dst = with._len;
    const size_t len_add = Math::Surplus(len_dst, len_src);
    char *ptr = strstr(_cstr, what._cstr);
    if (!ptr)
        return; // pattern not found once, bail out early
    // Special case for calling BecomeUnique on the first find
    if (IsShared() && len_add == 0)
    {
        ptrdiff_t diff = ptr - _cstr;
        BecomeUnique(); // if same length make a unique copy once
        ptr = _cstr + diff; // BecomeUnique will realloc memory
    }
    // Do the full search & replace
    for (; ptr; ptr = strstr(ptr, what._cstr))
    {
        if (len_add > 0)
        {
            ptrdiff_t diff = ptr - _cstr;
            ReserveAndShift(false, len_add);
            ptr = _cstr + diff; // ReserveAndShift may realloc memory
        }
        if (len_src != len_dst)
            memmove(ptr + len_dst, ptr + len_src, _len - (ptr - _cstr + len_src) + 1);
        memcpy(ptr, with._cstr, len_dst);
        _len += len_dst - len_src;
        ptr += len_dst;
    }
}

void String::ReplaceMid(size_t from, size_t count, const String &str)
{
    size_t length = str._len;
    Math::ClampLength(from, count, (size_t)0, _len);
    ReserveAndShift(false, Math::Surplus(length, count));
    if (count != str._len)
        memmove(_cstr + from + length, _cstr + from + count, _len - (from + count) + 1);
    memcpy(_cstr + from, str._cstr, length);
    _len += length - count;
}

void String::Reverse()
{
    if (_len <= 1)
        return; // nothing to reverse if 1 char or less
    BecomeUnique();
    for (char *fw = _cstr, *bw = _cstr + _len - 1;
        fw < bw; ++fw, --bw)
    {
        std::swap(*fw, *bw);
    }
}

void String::ReverseUTF8()
{
    if (_len <= 1)
        return; // nothing to reverse if 1 char or less
    // TODO: may this be optimized to not alloc new buffer?
    // otherwise, allocate a proper String data buf and replace existing
    char *newstr = new char[_len + 1];
    for (char *fw = _cstr, *fw2 = _cstr + 1,
              *bw = _cstr + _len - 1, *bw2 = _cstr + _len;
        fw <= bw; // FIXME: <= catches odd middle char, optimize?
        fw = fw2++, bw2 = bw--)
    {
        // find end of next character forwards
        for (; (fw2 < bw) && ((*fw2 & 0xC0) == 0x80); ++fw2);
        // find beginning of the prev character backwards
        for (; (bw > fw) && ((*bw & 0xC0) == 0x80); --bw);
        // put these in opposite sides on the new buffer
        char *fw_place = newstr + (_cstr + _len - bw2);
        char *bw_place = newstr + _len - (fw2 - _cstr);
        memcpy(fw_place, bw, bw2 - bw);
        if (fw != bw) // FIXME, optimize?
            memcpy(bw_place, fw, fw2 - fw);
    }
    newstr[_len] = 0;
    SetString(newstr);
    delete[] newstr;
}

void String::SetAt(size_t index, char c)
{
    if ((index < _len) && c)
    {
        BecomeUnique();
        _cstr[index] = c;
    }
}

void String::SetString(const char *cstr, size_t length)
{
    if (cstr && length > 0u)
    {
        SetStringImpl(cstr, std::min(length, strlen(cstr)));
    }
    else
    {
        Empty();
    }
}

void String::SetStringImpl(const char *cstr, size_t length)
{
    if (length > 0)
    {
        ReserveAndShift(false, Math::Surplus(length, _len));
        memcpy(_cstr, cstr, length);
        _len = length;
        _cstr[length] = 0;
    }
    else
    {
        Empty();
    }
}

void String::Trim(char c)
{
    TrimLeft(c);
    TrimRight(c);
}

void String::TrimLeft(char c)
{
    if (_len == 0)
    {
        return;
    }

    const char *trim_ptr = _cstr;
    for (;;)
    {
        auto t = *trim_ptr;
        if (t == 0) { break; }
        if (c && t != c) { break; }
        if (!c && !isspace(static_cast<uint8_t>(t))) { break; }
        trim_ptr++;
    }
    size_t trimmed = trim_ptr - _cstr;
    if (trimmed > 0)
    {
        BecomeUnique();
        _len -= trimmed;
        _cstr += trimmed;
    }
}

void String::TrimRight(char c)
{
    if (_len == 0)
    {
        return;
    }

    const char *trim_ptr = _cstr + _len - 1;
    for (;;) 
    {
        if (trim_ptr < _cstr) { break; }
        auto t = *trim_ptr;
        if (c && t != c) { break; }
        if (!c && !isspace(static_cast<uint8_t>(t))) { break; }
        trim_ptr--;
    }
    size_t trimmed = (_cstr + _len - 1) - trim_ptr;
    if (trimmed > 0)
    {
        BecomeUnique();
        _len -= trimmed;
        _cstr[_len] = 0;
    }
}

void String::TruncateToLeft(size_t count)
{
    if (_len != 0)
    {
        count = std::min(count, _len);
        if (count < _len)
        {
            BecomeUnique();
            _len = count;
            _cstr[_len] = 0;
        }
    }
}

void String::TruncateToMid(size_t from, size_t count)
{
    if (_len != 0)
    {
        Math::ClampLength(from, count, (size_t)0, _len);
        if (from > 0 || count < _len)
        {
            BecomeUnique();
            _len = count;
            _cstr += from;
            _cstr[_len] = 0;
        }
    }
}

void String::TruncateToRight(size_t count)
{
    if (_len != 0)
    {
        count = std::min(count, _len);
        if (count < _len)
        {
            BecomeUnique();
            _cstr += _len - count;
            _len = count;
        }
    }
}

void String::TruncateToLeftSection(char separator, bool exclude_separator)
{
    if ((_len != 0) && separator)
    {
        size_t slice_at = FindChar(separator);
        if (slice_at != NoIndex)
        {
            TruncateToLeft(exclude_separator ? slice_at : slice_at + 1);
        }
    }
}

void String::TruncateToRightSection(char separator, bool exclude_separator)
{
    if ((_len != 0) && separator)
    {
        size_t slice_at = FindCharReverse(separator);
        if (slice_at != NoIndex)
        {
            TruncateToRight(exclude_separator ? _len - slice_at - 1 : _len - slice_at);
        }
    }
}

void String::TruncateToSection(char separator, size_t first, size_t last,
                          bool exclude_first_sep, bool exclude_last_sep)
{
    if ((_len == 0) || !separator)
    {
        return;
    }

    size_t slice_from;
    size_t slice_to;
    if (FindSection(separator, first, last, exclude_first_sep, exclude_last_sep,
        slice_from, slice_to))
    {
        TruncateToMid(slice_from, slice_to - slice_from);
    }
    else
    {
        Empty();
    }
}

void String::Wrap(const char *cstr)
{
    Free();
    _buf = nullptr;
    // Note that String will NOT *modify* the const buffer.
    // Any write operation on the buffer is preceded by a call to BecomeUnique.
    _cstr = const_cast<char*>(cstr ? cstr : "");
    _len = strlen(_cstr);
}

String &String::operator=(const String& str)
{
    if (_cstr != str._cstr)
    {
        Free();
        _buf = str._buf;
        _cstr = str._cstr;
        _len = str._len;
        if (_bufHead)
        {
            _bufHead->RefCount++;
        }
    }
    return *this;
}

String &String::operator=(String &&str)
{
    Free();
    _cstr = str._cstr;
    _len = str._len;
    _buf = str._buf;
    _bufHead = str._bufHead;
    str._cstr = const_cast<char*>("");
    str._len = 0;
    str._buf = nullptr;
    str._bufHead = nullptr;
    return *this;
}

String &String::operator=(const std::string &str)
{
    SetStringImpl(str.c_str(), str.length());
    return *this;
}

String &String::operator=(const char *cstr)
{
    SetString(cstr);
    return *this;
}

void String::Create(size_t max_length)
{
    _buf = new char[sizeof(String::BufHeader) + max_length + 1];
    _bufHead->RefCount = 1;
    _bufHead->Capacity = max_length;
    _len = 0;
    _cstr = _buf + sizeof(String::BufHeader);
    _cstr[_len] = 0;
}

void String::Copy(size_t max_length, size_t offset)
{
    char *new_data = new char[sizeof(String::BufHeader) + max_length + 1];
    // remember, that _cstr may point to any address in buffer
    char *cstr_head = new_data + sizeof(String::BufHeader) + offset;
    size_t copy_length = std::min(_len, max_length);
    memcpy(cstr_head, _cstr, copy_length);
    Free();
    _buf = new_data;
    _bufHead->RefCount = 1;
    _bufHead->Capacity = max_length;
    _len = copy_length;
    _cstr = cstr_head;
    _cstr[_len] = 0;
}

void String::Align(size_t offset)
{
    char *cstr_head = _buf + sizeof(String::BufHeader) + offset;
    memmove(cstr_head, _cstr, _len + 1);
    _cstr = cstr_head;
}

inline bool String::IsShared() const
{
    // no allocated buffer == wrapping an external char[]
    // has buffer and refcount > 1 == shared string buffer
    return !_bufHead || (_bufHead->RefCount > 1);
}

void String::BecomeUnique()
{
    if (IsShared())
    {
        Copy(_len);
    }
}

void String::ReserveAndShift(bool left, size_t more_length)
{
    if (_bufHead)
    {
        size_t total_length = _len + more_length;
        if (_bufHead->Capacity < total_length)
        { // not enough capacity - reallocate buffer
            // grow by 50% or at least to total_size
            size_t grow_length = _bufHead->Capacity + (_bufHead->Capacity >> 1);
            Copy(std::max(total_length, grow_length), left ? more_length : 0u);
        }
        else if (_bufHead->RefCount > 1)
        { // is a shared string - clone buffer
            Copy(total_length, left ? more_length : 0u);
        }
        else
        {
            // make sure we make use of all of our space
            const char *cstr_head = _buf + sizeof(String::BufHeader);
            size_t free_space = left ?
                _cstr - cstr_head :
                (cstr_head + _bufHead->Capacity) - (_cstr + _len);
            if (free_space < more_length)
            {
                Align((left ?
                    _cstr + (more_length - free_space) :
                    _cstr - (more_length - free_space)) - cstr_head);
            }
        }
    }
    else
    { // either empty string, or wrapping external char[] - create new buffer
        Create(more_length);
    }
}

} // namespace Common
} // namespace AGS
