// File: crn_dynamic_string.cpp
// See Copyright Notice and license at the end of inc/crnlib.h
#include "crn_core.h"
#include "crn_strutils.h"

namespace crnlib {
dynamic_string g_empty_dynamic_string;

dynamic_string::dynamic_string(eVarArg, const char* p, ...)
    : m_buf_size(0), m_len(0), m_pStr(NULL) {
  CRNLIB_ASSERT(p);

  va_list args;
  va_start(args, p);
  format_args(p, args);
  va_end(args);
}

dynamic_string::dynamic_string(const char* p)
    : m_buf_size(0), m_len(0), m_pStr(NULL) {
  CRNLIB_ASSERT(p);
  set(p);
}

dynamic_string::dynamic_string(const char* p, uint len)
    : m_buf_size(0), m_len(0), m_pStr(NULL) {
  CRNLIB_ASSERT(p);
  set_from_buf(p, len);
}

dynamic_string::dynamic_string(const dynamic_string& other)
    : m_buf_size(0), m_len(0), m_pStr(NULL) {
  set(other);
}

void dynamic_string::clear() {
  check();

  if (m_pStr) {
    crnlib_delete_array(m_pStr);
    m_pStr = NULL;

    m_len = 0;
    m_buf_size = 0;
  }
}

void dynamic_string::empty() {
  truncate(0);
}

void dynamic_string::optimize() {
  if (!m_len)
    clear();
  else {
    uint min_buf_size = math::next_pow2((uint)m_len + 1);
    if (m_buf_size > min_buf_size) {
      char* p = crnlib_new_array<char>(min_buf_size);
      memcpy(p, m_pStr, m_len + 1);

      crnlib_delete_array(m_pStr);
      m_pStr = p;

      m_buf_size = static_cast<uint16>(min_buf_size);

      check();
    }
  }
}

int dynamic_string::compare(const char* p, bool case_sensitive) const {
  CRNLIB_ASSERT(p);

  const int result = (case_sensitive ? strcmp : crnlib_stricmp)(get_ptr_priv(), p);

  if (result < 0)
    return -1;
  else if (result > 0)
    return 1;

  return 0;
}

int dynamic_string::compare(const dynamic_string& rhs, bool case_sensitive) const {
  return compare(rhs.get_ptr_priv(), case_sensitive);
}

dynamic_string& dynamic_string::set(const char* p, uint max_len) {
  CRNLIB_ASSERT(p);

  const uint len = math::minimum<uint>(max_len, static_cast<uint>(strlen(p)));
  CRNLIB_ASSERT(len < cUINT16_MAX);

  if ((!len) || (len >= cUINT16_MAX))
    clear();
  else if ((m_pStr) && (p >= m_pStr) && (p < (m_pStr + m_buf_size))) {
    if (m_pStr != p)
      memmove(m_pStr, p, len);
    m_pStr[len] = '\0';
    m_len = static_cast<uint16>(len);
  } else if (ensure_buf(len, false)) {
    m_len = static_cast<uint16>(len);
    memcpy(m_pStr, p, m_len + 1);
  }

  check();

  return *this;
}

dynamic_string& dynamic_string::set(const dynamic_string& other, uint max_len) {
  if (this == &other) {
    if (max_len < m_len) {
      m_pStr[max_len] = '\0';
      m_len = static_cast<uint16>(max_len);
    }
  } else {
    const uint len = math::minimum<uint>(max_len, other.m_len);

    if (!len)
      clear();
    else if (ensure_buf(len, false)) {
      m_len = static_cast<uint16>(len);
      memcpy(m_pStr, other.get_ptr_priv(), m_len);
      m_pStr[len] = '\0';
    }
  }

  check();

  return *this;
}

bool dynamic_string::set_len(uint new_len, char fill_char) {
  if ((new_len >= cUINT16_MAX) || (!fill_char)) {
    CRNLIB_ASSERT(0);
    return false;
  }

  uint cur_len = m_len;

  if (ensure_buf(new_len, true)) {
    if (new_len > cur_len)
      memset(m_pStr + cur_len, fill_char, new_len - cur_len);

    m_pStr[new_len] = 0;

    m_len = static_cast<uint16>(new_len);

    check();
  }

  return true;
}

dynamic_string& dynamic_string::set_from_raw_buf_and_assume_ownership(char* pBuf, uint buf_size_in_chars, uint len_in_chars) {
  CRNLIB_ASSERT(buf_size_in_chars <= cUINT16_MAX);
  CRNLIB_ASSERT(math::is_power_of_2(buf_size_in_chars) || (buf_size_in_chars == cUINT16_MAX));
  CRNLIB_ASSERT((len_in_chars + 1) <= buf_size_in_chars);

  clear();

  m_pStr = pBuf;
  m_buf_size = static_cast<uint16>(buf_size_in_chars);
  m_len = static_cast<uint16>(len_in_chars);

  check();

  return *this;
}

dynamic_string& dynamic_string::set_from_buf(const void* pBuf, uint buf_size) {
  CRNLIB_ASSERT(pBuf);

  if (buf_size >= cUINT16_MAX) {
    clear();
    return *this;
  }

#ifdef CRNLIB_BUILD_DEBUG
  if ((buf_size) && (memchr(pBuf, 0, buf_size) != NULL)) {
    CRNLIB_ASSERT(0);
    clear();
    return *this;
  }
#endif

  if (ensure_buf(buf_size, false)) {
    if (buf_size)
      memcpy(m_pStr, pBuf, buf_size);

    m_pStr[buf_size] = 0;

    m_len = static_cast<uint16>(buf_size);

    check();
  }

  return *this;
}

dynamic_string& dynamic_string::set_char(uint index, char c) {
  CRNLIB_ASSERT(index <= m_len);

  if (!c)
    truncate(index);
  else if (index < m_len) {
    m_pStr[index] = c;

    check();
  } else if (index == m_len)
    append_char(c);

  return *this;
}

dynamic_string& dynamic_string::append_char(char c) {
  if (ensure_buf(m_len + 1)) {
    m_pStr[m_len] = c;
    m_pStr[m_len + 1] = '\0';
    m_len++;
    check();
  }

  return *this;
}

dynamic_string& dynamic_string::truncate(uint new_len) {
  if (new_len < m_len) {
    m_pStr[new_len] = '\0';
    m_len = static_cast<uint16>(new_len);
    check();
  }
  return *this;
}

dynamic_string& dynamic_string::tolower() {
  if (m_len) {
    crnlib_strnlwr(get_ptr_priv(), m_buf_size);
  }
  return *this;
}

dynamic_string& dynamic_string::toupper() {
  if (m_len) {
    crnlib_strnupr(get_ptr_priv(), m_buf_size);
  }
  return *this;
}

dynamic_string& dynamic_string::append(const char* p) {
  CRNLIB_ASSERT(p);

  uint len = static_cast<uint>(strlen(p));
  uint new_total_len = m_len + len;
  if ((new_total_len) && ensure_buf(new_total_len)) {
    memcpy(m_pStr + m_len, p, len + 1);
    m_len = static_cast<uint16>(m_len + len);
    check();
  }

  return *this;
}

dynamic_string& dynamic_string::append(const dynamic_string& other) {
  uint len = other.m_len;
  uint new_total_len = m_len + len;
  if ((new_total_len) && ensure_buf(new_total_len)) {
    memcpy(m_pStr + m_len, other.get_ptr_priv(), len + 1);
    m_len = static_cast<uint16>(m_len + len);
    check();
  }

  return *this;
}

dynamic_string operator+(const char* p, const dynamic_string& a) {
  return dynamic_string(p).append(a);
}

dynamic_string operator+(const dynamic_string& a, const char* p) {
  return dynamic_string(a).append(p);
}

dynamic_string operator+(const dynamic_string& a, const dynamic_string& b) {
  return dynamic_string(a).append(b);
}

dynamic_string& dynamic_string::format_args(const char* p, va_list args) {
  CRNLIB_ASSERT(p);

  const uint cBufSize = 4096;
  char buf[cBufSize];

#if defined(_WIN32)
  int l = vsnprintf_s(buf, cBufSize, _TRUNCATE, p, args);
#else
  int l = crnlib_vsnprintf(buf, cBufSize, p, args);
#endif
  if (l <= 0)
    clear();
  else if (ensure_buf(l, false)) {
    memcpy(m_pStr, buf, l + 1);

    m_len = static_cast<uint16>(l);

    check();
  }

  return *this;
}

dynamic_string& dynamic_string::format(const char* p, ...) {
  CRNLIB_ASSERT(p);

  va_list args;
  va_start(args, p);
  format_args(p, args);
  va_end(args);
  return *this;
}

dynamic_string& dynamic_string::crop(uint start, uint len) {
  if (start >= m_len) {
    clear();
    return *this;
  }

  len = math::minimum<uint>(len, m_len - start);

  if (start)
    memmove(get_ptr_priv(), get_ptr_priv() + start, len);

  m_pStr[len] = '\0';

  m_len = static_cast<uint16>(len);

  check();

  return *this;
}

dynamic_string& dynamic_string::substring(uint start, uint end) {
  CRNLIB_ASSERT(start <= end);
  if (start > end)
    return *this;
  return crop(start, end - start);
}

dynamic_string& dynamic_string::left(uint len) {
  return substring(0, len);
}

dynamic_string& dynamic_string::mid(uint start, uint len) {
  return crop(start, len);
}

dynamic_string& dynamic_string::right(uint start) {
  return substring(start, get_len());
}

dynamic_string& dynamic_string::tail(uint num) {
  return substring(math::maximum<int>(static_cast<int>(get_len()) - static_cast<int>(num), 0), get_len());
}

dynamic_string& dynamic_string::unquote() {
  if (m_len >= 2) {
    if (((*this)[0] == '\"') && ((*this)[m_len - 1] == '\"')) {
      return mid(1, m_len - 2);
    }
  }

  return *this;
}

int dynamic_string::find_left(const char* p, bool case_sensitive) const {
  CRNLIB_ASSERT(p);

  const int p_len = (int)strlen(p);

  for (int i = 0; i <= (m_len - p_len); i++)
    if ((case_sensitive ? strncmp : crnlib_strnicmp)(p, &m_pStr[i], p_len) == 0)
      return i;

  return -1;
}

bool dynamic_string::contains(const char* p, bool case_sensitive) const {
  return find_left(p, case_sensitive) >= 0;
}

uint dynamic_string::count_char(char c) const {
  uint count = 0;
  for (uint i = 0; i < m_len; i++)
    if (m_pStr[i] == c)
      count++;
  return count;
}

int dynamic_string::find_left(char c) const {
  for (uint i = 0; i < m_len; i++)
    if (m_pStr[i] == c)
      return i;
  return -1;
}

int dynamic_string::find_right(char c) const {
  for (int i = (int)m_len - 1; i >= 0; i--)
    if (m_pStr[i] == c)
      return i;
  return -1;
}

int dynamic_string::find_right(const char* p, bool case_sensitive) const {
  CRNLIB_ASSERT(p);
  const int p_len = (int)strlen(p);

  for (int i = m_len - p_len; i >= 0; i--)
    if ((case_sensitive ? strncmp : crnlib_strnicmp)(p, &m_pStr[i], p_len) == 0)
      return i;

  return -1;
}

dynamic_string& dynamic_string::trim() {
  int s, e;
  for (s = 0; s < (int)m_len; s++)
    if (!isspace(m_pStr[s]))
      break;

  for (e = m_len - 1; e > s; e--)
    if (!isspace(m_pStr[e]))
      break;

  return crop(s, e - s + 1);
}

dynamic_string& dynamic_string::trim_crlf() {
  int s = 0, e;

  for (e = m_len - 1; e > s; e--)
    if ((m_pStr[e] != 13) && (m_pStr[e] != 10))
      break;

  return crop(s, e - s + 1);
}

dynamic_string& dynamic_string::remap(int from_char, int to_char) {
  for (uint i = 0; i < m_len; i++)
    if (m_pStr[i] == from_char)
      m_pStr[i] = (char)to_char;
  return *this;
}

#ifdef CRNLIB_BUILD_DEBUG
void dynamic_string::check() const {
  if (!m_pStr) {
    CRNLIB_ASSERT(!m_buf_size && !m_len);
  } else {
    CRNLIB_ASSERT(m_buf_size);
    CRNLIB_ASSERT((m_buf_size == cUINT16_MAX) || math::is_power_of_2((uint32)m_buf_size));
    CRNLIB_ASSERT(m_len < m_buf_size);
    CRNLIB_ASSERT(!m_pStr[m_len]);
#if CRNLIB_SLOW_STRING_LEN_CHECKS
    CRNLIB_ASSERT(strlen(m_pStr) == m_len);
#endif
  }
}
#endif

bool dynamic_string::ensure_buf(uint len, bool preserve_contents) {
  uint buf_size_needed = len + 1;

  CRNLIB_ASSERT(buf_size_needed <= cUINT16_MAX);

  if (buf_size_needed <= cUINT16_MAX) {
    if (buf_size_needed > m_buf_size)
      expand_buf(buf_size_needed, preserve_contents);
  }

  return m_buf_size >= buf_size_needed;
}

bool dynamic_string::expand_buf(uint new_buf_size, bool preserve_contents) {
  new_buf_size = math::minimum<uint>(cUINT16_MAX, math::next_pow2(math::maximum<uint>(m_buf_size, new_buf_size)));

  if (new_buf_size != m_buf_size) {
    char* p = crnlib_new_array<char>(new_buf_size);

    if (preserve_contents)
      memcpy(p, get_ptr_priv(), m_len + 1);

    crnlib_delete_array(m_pStr);
    m_pStr = p;

    m_buf_size = static_cast<uint16>(new_buf_size);

    if (preserve_contents)
      check();
  }

  return m_buf_size >= new_buf_size;
}

void dynamic_string::swap(dynamic_string& other) {
  utils::swap(other.m_buf_size, m_buf_size);
  utils::swap(other.m_len, m_len);
  utils::swap(other.m_pStr, m_pStr);
}

int dynamic_string::serialize(void* pBuf, uint buf_size, bool little_endian) const {
  uint buf_left = buf_size;

  //if (m_len > cUINT16_MAX)
  //   return -1;
  CRNLIB_ASSUME(sizeof(m_len) == sizeof(uint16));

  if (!utils::write_val((uint16)m_len, pBuf, buf_left, little_endian))
    return -1;

  if (buf_left < m_len)
    return -1;

  memcpy(pBuf, get_ptr(), m_len);

  buf_left -= m_len;

  return buf_size - buf_left;
}

int dynamic_string::deserialize(const void* pBuf, uint buf_size, bool little_endian) {
  uint buf_left = buf_size;

  if (buf_left < sizeof(uint16))
    return -1;

  uint16 l;
  if (!utils::read_obj(l, pBuf, buf_left, little_endian))
    return -1;

  if (buf_left < l)
    return -1;

  set_from_buf(pBuf, l);

  buf_left -= l;

  return buf_size - buf_left;
}

void dynamic_string::translate_lf_to_crlf() {
  if (find_left(0x0A) < 0)
    return;

  dynamic_string tmp;
  tmp.ensure_buf(m_len + 2);

  // normal sequence is 0x0D 0x0A (CR LF, \r\n)

  int prev_char = -1;
  for (uint i = 0; i < get_len(); i++) {
    const int cur_char = (*this)[i];

    if ((cur_char == 0x0A) && (prev_char != 0x0D))
      tmp.append_char(0x0D);

    tmp.append_char(cur_char);

    prev_char = cur_char;
  }

  swap(tmp);
}

}  // namespace crnlib
