text: //=============================================================================
  //
  // Adventure Game Studio (AGS)
  //
  // Copyright (C) 1999-2011 Chris Jones and 2011-20xx others
  // 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
  // http://www.opensource.org/licenses/artistic-license-2.0.php
  //
  //=============================================================================
  #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"
  
  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 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(*ptr))
              return false;
      }
      return true;
  }
  
  void String::Read(Stream *in, size_t max_chars, bool stop_at_limit)
  {
      Empty();
      if (!in)
      {
          return;
      }
      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)
  {
      if (in && count > 0)
      {
          ReserveAndShift(false, count);
          count = in->Read(_cstr, count);
          _cstr[count] = 0;
          _len = strlen(_cstr);
      }
      else
      {
          Empty();
      }
  }
  
  void String::Write(Stream *out) const
  {
      if (out)
      {
          out->Write(_cstr, _len + 1);
      }
  }
  
  void String::WriteCount(Stream *out, size_t count) const
  {
      if (out)
      {
          size_t str_out_len = Math::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::CompareLeft(const char *cstr, size_t count) const
  {
      cstr = cstr ? cstr : "";
      return strncmp(_cstr, cstr, count != -1 ? count : strlen(cstr));
  }
  
  int String::CompareLeftNoCase(const char *cstr, size_t count) const
  {
      cstr = cstr ? cstr : "";
      return ags_strnicmp(_cstr, cstr, count != -1 ? count : strlen(cstr));
  }
  
  int String::CompareMid(const char *cstr, size_t from, size_t count) const
  {
      cstr = cstr ? cstr : "";
      from = Math::Min(from, _len);
      return strncmp(_cstr + from, cstr, count != -1 ? count : strlen(cstr));
  }
  
  int String::CompareMidNoCase(const char *cstr, size_t from, size_t count) const
  {
      cstr = cstr ? cstr : "";
      from = Math::Min(from, _len);
      return ags_strnicmp(_cstr + from, cstr, count != -1 ? count : strlen(cstr));
  }
  
  int String::CompareRight(const char *cstr, size_t count) const
  {
      cstr = cstr ? cstr : "";
      count = count != -1 ? count : strlen(cstr);
      size_t off = Math::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 != -1 ? count : strlen(cstr);
      size_t off = Math::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 : -1;
      }
      return -1;
  }
  
  size_t String::FindCharReverse(char c, size_t from) const
  {
      if ((_len == 0) || !c)
      {
          return -1;
      }
  
      from = Math::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 -1;
  }
  
  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 : -1;
      }
      return -1;
  }
  
  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 = -1;
      do
      {
          slice_at = FindChar(separator, slice_at + 1);
          if (slice_at == -1)
              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;
  }
  
  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::Left(size_t count) const
  {
      count = Math::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 = Math::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 != -1)
          {
              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 != -1)
          {
              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(Math::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 = Math::Min(count, _len);
          BecomeUnique();
          _len -= count;
          _cstr += count;
      }
  }
  
  void String::ClipMid(size_t from, size_t count)
  {
      if (from < _len)
      {
          count = Math::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 (count > 0)
      {
          count = Math::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 != -1)
          {
              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 != -1)
          {
              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::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? or dont care
      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);
  }
  
  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 = Math::Min(length, strlen(cstr));
          if (length > 0)
          {
              ReserveAndShift(false, Math::Surplus(length, _len));
              memcpy(_cstr, cstr, length);
              _len = length;
              _cstr[length] = 0;
          }
          else
          {
              Empty();
          }
      }
      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(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(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 = Math::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 = Math::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 != -1)
          {
              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 != -1)
          {
              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 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 = Math::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(Math::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
  