/******************************************************************************
 * Copyright (c) 2000-2021 Ericsson Telecom AB
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
 *
 * Contributors:
 *   Balasko, Jeno
 *   Forstner, Matyas
 *   Gecse, Roland
 *   Kremer, Peter
 *   Raduly, Csaba
 *   Szabados, Kristof
 *   Szabo, Janos Zoltan – initial implementation
 *   Zalanyi, Balazs Andor
 *
 ******************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <ctype.h>

#include "../common/memory.h"
#include "error.h"

#include "string.hh"
#include "ustring.hh"

#include "Int.hh"

/** Parameters for tuning memory usage of class stringpool */
#define STRINGPOOL_INITIAL_SIZE 8
#define STRINGPOOL_INCREMENT_FACTOR 2

/** The amount of memory needed for a string containing n characters. */
#define MEMORY_SIZE(n) (sizeof(string_struct) - sizeof(size_t) + 1 + (n))

void string::init_struct(size_t n_chars)
{
  if (n_chars == 0) {
    /** This will represent the empty strings so they won't need allocated
     * memory, this delays the memory allocation until it is really needed. */
    static string_struct empty_string = { 1, 0, "" };
    val_ptr = &empty_string;
    empty_string.ref_count++;
  } else {
    val_ptr = static_cast<string_struct*>(Malloc(MEMORY_SIZE(n_chars)));
    val_ptr->ref_count = 1;
    val_ptr->n_chars = n_chars;
    val_ptr->chars_ptr[n_chars] = '\0';
  }
}

void string::copy_value_and_append(const char *s, size_t n)
{
  if (n > max_string_len - val_ptr->n_chars)
    FATAL_ERROR("string::copy_value_and_append(const char*, size_t): " \
      "length overflow");
  if (val_ptr->ref_count == 1) {
    ptrdiff_t offset = s - val_ptr->chars_ptr;
    val_ptr = static_cast<string_struct*>
      (Realloc(val_ptr, MEMORY_SIZE(val_ptr->n_chars + n)));
    // check whether the source buffer s is (part of) our existing buffer
    if (offset >= 0 && static_cast<size_t>(offset) < val_ptr->n_chars)
      memcpy(val_ptr->chars_ptr + val_ptr->n_chars,
	     val_ptr->chars_ptr + offset, n);
    else memcpy(val_ptr->chars_ptr + val_ptr->n_chars, s, n);
    val_ptr->n_chars += n;
    val_ptr->chars_ptr[val_ptr->n_chars] = '\0';
  } else {
    string_struct *old_ptr = val_ptr;
    old_ptr->ref_count--;
    init_struct(old_ptr->n_chars + n);
    memcpy(val_ptr->chars_ptr, old_ptr->chars_ptr, old_ptr->n_chars);
    memcpy(val_ptr->chars_ptr + old_ptr->n_chars, s, n);
  }
}

/** Internal worker for various replace() methods.
 *
 * @param pos   the index of the first character to replace
 * @param n     the number of characters to replace
 * @param s     the replacement
 * @param s_len the length of the replacement
 *
 * @pre \p pos        must point inside the string
 * @pre \p pos + \p n must point inside the string
 * @pre the resulting string must not exceed max_string_len
 */
void string::replace(size_t pos, size_t n, const char *s, size_t s_len)
{
  if (pos > val_ptr->n_chars)
    FATAL_ERROR("string::replace(): start position is outside the string");
  if (pos + n > val_ptr->n_chars)
    FATAL_ERROR("string::replace(): end position is outside the string");
  if (s_len > max_string_len - val_ptr->n_chars + n)
    FATAL_ERROR("string::replace(): length overflow");
  // do nothing if we are replacing with the same string
  if (n == s_len && memcmp(val_ptr->chars_ptr + pos, s, n) == 0) return;
  size_t new_size = val_ptr->n_chars - n + s_len;
  if (new_size == 0) {
    // the result is an empty string
    clean_up(val_ptr);
    init_struct(0);
  } else if (val_ptr->ref_count == 1 && (s < val_ptr->chars_ptr ||
      s >= val_ptr->chars_ptr + val_ptr->n_chars)) {
    // check whether this string is not referenced by others and we are not
    // replacing with ourselves
    if (val_ptr->n_chars < new_size) val_ptr = static_cast<string_struct*>
	(Realloc(val_ptr, MEMORY_SIZE(new_size)));
    memmove(val_ptr->chars_ptr + pos + s_len,
            val_ptr->chars_ptr + pos + n, val_ptr->n_chars - pos - n);
    memcpy(val_ptr->chars_ptr + pos, s, s_len);
    if (val_ptr->n_chars > new_size) val_ptr = static_cast<string_struct*>
	(Realloc(val_ptr, MEMORY_SIZE(new_size)));
    val_ptr->n_chars = new_size;
    val_ptr->chars_ptr[new_size] = '\0';
  } else {
    // the result must be copied into a new memory area
    string_struct *old_ptr = val_ptr;
    old_ptr->ref_count--;
    init_struct(new_size);
    memcpy(val_ptr->chars_ptr, old_ptr->chars_ptr, pos);
    memcpy(val_ptr->chars_ptr + pos, s, s_len);
    memcpy(val_ptr->chars_ptr + pos + s_len, old_ptr->chars_ptr + pos + n,
      old_ptr->n_chars - pos - n);
    if (old_ptr->ref_count == 0) Free(old_ptr);
  }
}

void string::clean_up(string_struct *ptr)
{
  if (ptr->ref_count > 1) ptr->ref_count--;
  else if (ptr->ref_count == 1) Free(ptr);
  else FATAL_ERROR("string::clean_up()");
}

int string::compare(const string& s) const
{
  if (val_ptr == s.val_ptr) return 0;
  else return strcmp(val_ptr->chars_ptr, s.val_ptr->chars_ptr);
}

int string::compare(const char *s) const
{
  if (val_ptr->chars_ptr == s) return 0;
  else return strcmp(val_ptr->chars_ptr, s);
}

string::string(const char *s)
{
  if (s == NULL) FATAL_ERROR("string::string(const char*): called with NULL");
  size_t n_chars = strlen(s);
  init_struct(n_chars);
  memcpy(val_ptr->chars_ptr, s, n_chars);
}

string::string(size_t n, const char *s)
{
  if (s == NULL && n > 0)
    FATAL_ERROR("string::string(size_t, const char*): called with NULL");
  init_struct(n);
  memcpy(val_ptr->chars_ptr, s, n);
}

string::string(const ustring& s)
{
  size_t s_len = s.size();
  init_struct(s_len);
  const ustring::universal_char *src = s.u_str();
  for (size_t i = 0; i < s_len; i++) {
    if (src[i].group != 0 || src[i].plane != 0 || src[i].row != 0)
      FATAL_ERROR("string::string(const ustring&)");
    val_ptr->chars_ptr[i] = src[i].cell;
  }
}

bool string::is_cstr() const
{
  for (size_t i = 0; i < val_ptr->n_chars; i++)
    if ((unsigned char)val_ptr->chars_ptr[i] > 127) return false;
  return true;
}

void string::clear()
{
  if (val_ptr->n_chars > 0) {
    clean_up(val_ptr);
    init_struct(0);
  }
}

string string::substr(size_t pos, size_t n) const
{
  if (pos > val_ptr->n_chars)
    FATAL_ERROR("string::substr(size_t, size_t): position is outside the " \
      "string");
  size_t n_chars = val_ptr->n_chars - pos;
  if (n_chars > n) n_chars = n;
  if (n_chars == val_ptr->n_chars) return *this;
  else return string(n_chars, val_ptr->chars_ptr + pos);
}

void string::resize(size_t n, char c)
{
  size_t old_length = val_ptr->n_chars;
  if (old_length == n) return;
  if (val_ptr->ref_count == 1) {
    if (n > 0) {
      val_ptr = static_cast<string_struct*>(Realloc(val_ptr, MEMORY_SIZE(n)));
      if (n > old_length) {
        memset(val_ptr->chars_ptr + old_length, c, n - old_length);
      }
      val_ptr->chars_ptr[n] = '\0';
      val_ptr->n_chars = n;
    } else {
      clean_up(val_ptr);
      init_struct(0);
    }
  } else {
    val_ptr->ref_count--;
    string_struct *tmp_ptr = val_ptr;
    init_struct(n);
    if (n > old_length) {
      memcpy(val_ptr->chars_ptr, tmp_ptr->chars_ptr, old_length);
      memset(val_ptr->chars_ptr + old_length, c, n - old_length);
    } else {
      memcpy(val_ptr->chars_ptr, tmp_ptr->chars_ptr, n);
    }
  }
}

void string::replace(size_t pos, size_t n, const char *s)
{
  if (s == NULL) FATAL_ERROR("string::replace(size_t, size_t, const char*): " \
    "called with NULL");
  replace(pos, n, s, strlen(s));
}

void string::replace(size_t pos, size_t n, const string& s)
{
  if (pos == 0 && n == val_ptr->n_chars) *this = s;
  else replace(pos, n, s.val_ptr->chars_ptr, s.val_ptr->n_chars);
}

size_t string::find(char c, size_t pos) const
{
  if (pos > val_ptr->n_chars)
    FATAL_ERROR("string::find(char, size_t): position is outside of string");
  for (size_t r = pos; r < val_ptr->n_chars; r++)
    if (c == val_ptr->chars_ptr[r]) return r;
  return val_ptr->n_chars;
}

size_t string::find(const char *s, size_t pos) const
{
  if (s == NULL)
    FATAL_ERROR("string::find(const char *, size_t): called with NULL");
  if (pos >= val_ptr->n_chars)
    FATAL_ERROR("string::find(const char *, size_t): position outside of string");
  const char *ptr = strstr(val_ptr->chars_ptr + pos, s);
  if (ptr != NULL) return ptr - val_ptr->chars_ptr;
  else return val_ptr->n_chars;
}

size_t string::rfind(char c, size_t pos) const
{
  if (pos > val_ptr->n_chars) pos = val_ptr->n_chars;
  for (size_t i = pos; i > 0; i--)
    if (c == val_ptr->chars_ptr[i - 1]) return i - 1;
  return val_ptr->n_chars;
}

size_t string::find_if(size_t first, size_t last, int (*pred)(int)) const
{
  if (first > last) FATAL_ERROR("string::find_if(): first greater than last");
  else if (last > val_ptr->n_chars)
    FATAL_ERROR("string::find_if(): last greater than string length");
  for (size_t r = first; r < last; r++)
    if (pred(val_ptr->chars_ptr[r])) return r;
  return last;
}

bool string::is_whitespace(unsigned char c)
{
  switch (c) {
  case ' ':
  case '\t':
  case '\n':
  case '\r':
  case '\v':
  case '\f':
    return true;
  default:
    return false;
  }
}

bool string::is_printable(unsigned char c)
{
  if (isprint(c)) return true;
  else {
    switch (c) {
    case '\a':
    case '\b':
    case '\t':
    case '\n':
    case '\v':
    case '\f':
    case '\r':
      return true;
    default:
      return false;
    }
  }
}

void string::append_stringRepr(char c)
{
  switch (c) {
  case '\a':
    *this += "\\a";
    break;
  case '\b':
    *this += "\\b";
    break;
  case '\t':
    *this += "\\t";
    break;
  case '\n':
    *this += "\\n";
    break;
  case '\v':
    *this += "\\v";
    break;
  case '\f':
    *this += "\\f";
    break;
  case '\r':
    *this += "\\r";
    break;
  case '\\':
    *this += "\\\\";
    break;
  case '"':
    *this += "\\\"";
    break;
  default:
    if (is_printable(c)) *this += c;
    else FATAL_ERROR("string::append_stringRepr()");
    break;
  }
}

string string::get_stringRepr() const
{
  string ret_val;
  enum { INIT, PCHAR, NPCHAR } state = INIT;
  for (size_t i = 0; i < val_ptr->n_chars; i++) {
    char c = val_ptr->chars_ptr[i];
    if (is_printable(c)) {
      // the actual character is printable
      switch (state) {
      case NPCHAR: // concatenation sign if previous part was not printable
	ret_val += " & ";
	// no break
      case INIT: // opening "
	ret_val += '"';
	// no break
      case PCHAR: // the character itself
        ret_val.append_stringRepr(c);
        break;
      }
      state = PCHAR;
    } else {
      // the actual character is not printable
      switch (state) {
      case PCHAR: // closing " if previous part was printable
	ret_val += '"';
	// no break
      case NPCHAR: // concatenation sign
	ret_val += " & ";
	// no break
      case INIT: // the character itself in quadruple notation
	ret_val += "char(0, 0, 0, ";
	ret_val += Common::Int2string((unsigned char)c);
	ret_val += ')';
	break;
      }
      state = NPCHAR;
    }
  }
  // final steps
  switch (state) {
  case INIT: // the string was empty
    ret_val += "\"\"";
    break;
  case PCHAR: // last character was printable -> closing "
    ret_val += '"';
    break;
  default:
    break;
  }
  return ret_val;
}

string& string::operator=(const string& s)
{
  if (&s != this) {
    clean_up(val_ptr);
    val_ptr = s.val_ptr;
    val_ptr->ref_count++;
  }
  return *this;
}

string& string::operator=(const char *s)
{
  if (s == NULL)
    FATAL_ERROR("string::operator=(const char*): called with NULL");
  if (val_ptr->chars_ptr != s) {
      clean_up(val_ptr);
      size_t n_chars = strlen(s);
      init_struct(n_chars);
      memcpy(val_ptr->chars_ptr, s, n_chars);
  }
  return *this;
}

char& string::operator[](size_t n)
{
  if (n >= val_ptr->n_chars)
    FATAL_ERROR("string::operator[](size_t): position is outside the string");
  if (val_ptr->ref_count > 1) {
    string_struct *old_ptr = val_ptr;
    old_ptr->ref_count--;
    init_struct(old_ptr->n_chars);
    memcpy(val_ptr->chars_ptr, old_ptr->chars_ptr, old_ptr->n_chars);
  }
  return val_ptr->chars_ptr[n];
}

char string::operator[](size_t n) const
{
  if (n >= val_ptr->n_chars)
    FATAL_ERROR("string::operator[](size_t) const: position is outside the string");
  return val_ptr->chars_ptr[n];
}

string string::operator+(const string& s) const
{
  if (s.val_ptr->n_chars > max_string_len - val_ptr->n_chars)
    FATAL_ERROR("string::operator+(const string&): length overflow");
  if (val_ptr->n_chars == 0) return s;
  else if (s.val_ptr->n_chars == 0) return *this;
  else {
    string ret_val(val_ptr->n_chars + s.val_ptr->n_chars);
    memcpy(ret_val.val_ptr->chars_ptr, val_ptr->chars_ptr, val_ptr->n_chars);
    memcpy(ret_val.val_ptr->chars_ptr + val_ptr->n_chars,
           s.val_ptr->chars_ptr, s.val_ptr->n_chars);
    return ret_val;
  }
}

string string::operator+(const char *s) const
{
  if (s == NULL)
    FATAL_ERROR("string::operator+(const char*): called with NULL)");
  size_t s_len = strlen(s);
  if (s_len > max_string_len - val_ptr->n_chars)
    FATAL_ERROR("string::operator+(const char*): length overflow");
  if (s_len == 0) return *this;
  else {
    string ret_val(val_ptr->n_chars + s_len);
    memcpy(ret_val.val_ptr->chars_ptr, val_ptr->chars_ptr, val_ptr->n_chars);
    memcpy(ret_val.val_ptr->chars_ptr + val_ptr->n_chars, s, s_len);
    return ret_val;
  }
}

string& string::operator+=(const string& s)
{
  if (s.val_ptr->n_chars > 0) {
    if (val_ptr->n_chars > 0) {
      copy_value_and_append(s.val_ptr->chars_ptr, s.val_ptr->n_chars);
    } else {
      clean_up(val_ptr);
      val_ptr = s.val_ptr;
      val_ptr->ref_count++;
    }
  }
  return *this;
}

string& string::operator+=(const char *s)
{
  if (s == NULL)
    FATAL_ERROR("string::operator+=(const char*): called with NULL");
  size_t s_len = strlen(s);
  if (s_len > 0) copy_value_and_append(s, s_len);
  return *this;
}

bool string::operator==(const string& s) const
{
  if (val_ptr == s.val_ptr) return true;
  else if (val_ptr->n_chars != s.val_ptr->n_chars) return false;
  else return memcmp(val_ptr->chars_ptr, s.val_ptr->chars_ptr,
    val_ptr->n_chars) == 0;
}

bool string::operator==(const char *s) const
{
  if (s == NULL)
    FATAL_ERROR("string::operator==(const char*): called with NULL");
  if (s == val_ptr->chars_ptr) return true;
  else return strcmp(val_ptr->chars_ptr, s) == 0;
}

string operator+(const char *s1, const string& s2)
{
  if (s1 == NULL)
    FATAL_ERROR("operator+(const char *, const string&): called with NULL");
  size_t s1_len = strlen(s1);
  if (s1_len > string::max_string_len - s2.val_ptr->n_chars)
    FATAL_ERROR("operator+(const char *,const string&): length overflow");
  if (s1_len > 0) {
    string s(s1_len + s2.val_ptr->n_chars);
    memcpy(s.val_ptr->chars_ptr, s1, s1_len);
    memcpy(s.val_ptr->chars_ptr + s1_len, s2.val_ptr->chars_ptr,
            s2.val_ptr->n_chars);
    return s;
  } else return s2;
}

void stringpool::clear()
{
  for (size_t i = 0; i < list_len; i++) string::clean_up(string_list[i]);
  Free(string_list);
  string_list = NULL;
  list_size = 0;
  list_len = 0;
}

const char *stringpool::add(const string& s)
{
  if (list_len > list_size) FATAL_ERROR("stringpool::add()");
  else if (list_len == list_size) {
    if (list_size == 0) {
      string_list = static_cast<string::string_struct**>
	(Malloc(STRINGPOOL_INITIAL_SIZE * sizeof(*string_list)));
      list_size = STRINGPOOL_INITIAL_SIZE;
    } else {
      list_size *= STRINGPOOL_INCREMENT_FACTOR;
      string_list = static_cast<string::string_struct**>
	(Realloc(string_list, list_size * sizeof(*string_list)));
    }
    if (list_len >= list_size) FATAL_ERROR("stringpool::add()");
  }
  string::string_struct *val_ptr = s.val_ptr;
  string_list[list_len++] = val_ptr;
  val_ptr->ref_count++;
  return val_ptr->chars_ptr;
}

const char *stringpool::get_str(size_t n) const
{
  if (n >= list_len) FATAL_ERROR("stringpool::get_str()");
  return string_list[n]->chars_ptr;
}

string stringpool::get_string(size_t n) const
{
  if (n >= list_len) FATAL_ERROR("stringpool::get_string()");
  return string(string_list[n]);
}
