// Copyright (c) 2011-2016 The Cryptonote developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#pragma once

#include "StringView.h"
#include <limits>
#include <string.h>

namespace Common {

// 'StringBuffer' is a string of fixed maximum size.
template<size_t MAXIMUM_SIZE_VALUE> class StringBuffer {
public:
  typedef char Object;
  typedef size_t Size;

  const static Size MAXIMUM_SIZE = MAXIMUM_SIZE_VALUE;
  const static Size INVALID;

  static_assert(MAXIMUM_SIZE != 0, "StringBuffer's size must not be zero");

  // Default constructor.
  // After construction, 'StringBuffer' is empty, that is 'size' == 0
  StringBuffer() : size(0) {
  }

  // Direct constructor.
  // Copies string from 'stringData' to 'StringBuffer'.
  // The behavior is undefined unless ('stringData' != 'nullptr' || 'stringSize' == 0) && 'stringSize' <= 'MAXIMUM_SIZE'.
  StringBuffer(const Object* stringData, Size stringSize) : size(stringSize) {
    assert(stringData != nullptr || size == 0);
    assert(size <= MAXIMUM_SIZE);
    memcpy(data, stringData, size);
  }

  // Constructor from C array.
  // Copies string from 'stringData' to 'StringBuffer'.
  // The behavior is undefined unless ('stringData' != 'nullptr' || 'stringSize' == 0) && 'stringSize' <= 'MAXIMUM_SIZE'. Input state can be malformed using poiner conversions.
  template<Size stringSize> explicit StringBuffer(const Object(&stringData)[stringSize]) : size(stringSize - 1) {
    assert(stringData != nullptr || size == 0);
    assert(size <= MAXIMUM_SIZE);
    memcpy(data, stringData, size);
  }

  // Constructor from StringView
  // Copies string from 'stringView' to 'StringBuffer'.
  // The behavior is undefined unless 'stringView.size()' <= 'MAXIMUM_SIZE'.
  explicit StringBuffer(StringView stringView) : size(stringView.getSize()) {
    assert(size <= MAXIMUM_SIZE);
    memcpy(data, stringView.getData(), size);
  }

  // Copy constructor.
  // Copies string from 'other' to 'StringBuffer'.
  StringBuffer(const StringBuffer& other) : size(other.size) {
    memcpy(data, other.data, size);
  }

  // Destructor.
  // No special action is performed.
  ~StringBuffer() {
  }

  // Copy assignment operator.
  StringBuffer& operator=(const StringBuffer& other) {
    size = other.size;
    memcpy(data, other.data, size);
    return *this;
  }

  // StringView assignment operator.
  // Copies string from 'stringView' to 'StringBuffer'.
  // The behavior is undefined unless 'stringView.size()' <= 'MAXIMUM_SIZE'.
  StringBuffer& operator=(StringView stringView) {
    assert(stringView.getSize() <= MAXIMUM_SIZE);
    memcpy(data, stringView.getData(), stringView.getSize());
    size = stringView.getSize();
    return *this;
  }

  operator StringView() const {
    return StringView(data, size);
  }

  explicit operator std::string() const {
    return std::string(data, size);
  }

  Object* getData() {
    return data;
  }

  const Object* getData() const {
    return data;
  }

  Size getSize() const {
    return size;
  }

  // Return false if 'StringView' is not EMPTY.
  bool isEmpty() const {
    return size == 0;
  }

  // Get 'StringBuffer' element by index.
  // The behavior is undefined unless 'index' < 'size'.
  Object& operator[](Size index) {
    assert(index < size);
    return *(data + index);
  }

  // Get 'StringBuffer' element by index.
  // The behavior is undefined unless 'index' < 'size'.
  const Object& operator[](Size index) const {
    assert(index < size);
    return *(data + index);
  }

  // Get first element.
  // The behavior is undefined unless 'size' > 0
  Object& first() {
    assert(size > 0);
    return *data;
  }

  // Get first element.
  // The behavior is undefined unless 'size' > 0
  const Object& first() const {
    assert(size > 0);
    return *data;
  }

  // Get last element.
  // The behavior is undefined unless 'size' > 0
  Object& last() {
    assert(size > 0);
    return *(data + (size - 1));
  }

  // Get last element.
  // The behavior is undefined unless 'size' > 0
  const Object& last() const {
    assert(size > 0);
    return *(data + (size - 1));
  }

  // Return a pointer to the first element.
  Object* begin() {
    return data;
  }

  // Return a pointer to the first element.
  const Object* begin() const {
    return data;
  }

  // Return a pointer after the last element.
  Object* end() {
    return data + size;
  }

  // Return a pointer after the last element.
  const Object* end() const {
    return data + size;
  }

  // Compare elements of two strings, return false if there is a difference.
  bool operator==(StringView other) const {
    if (size == other.getSize()) {
      for (Size i = 0;; ++i) {
        if (i == size) {
          return true;
        }

        if (!(*(data + i) == *(other.getData() + i))) {
          break;
        }
      }
    }

    return false;
  }

  // Compare elements two strings, return false if there is no difference.
  bool operator!=(StringView other) const {
    return !(*this == other);
  }

  // Compare two strings character-wise.
  bool operator<(StringView other) const {
    Size count = other.getSize() < size ? other.getSize() : size;
    for (Size i = 0; i < count; ++i) {
      Object char1 = *(data + i);
      Object char2 = *(other.getData() + i);
      if (char1 < char2) {
        return true;
      }

      if (char2 < char1) {
        return false;
      }
    }

    return size < other.getSize();
  }

  // Compare two strings character-wise.
  bool operator<=(StringView other) const {
    return !(other < *this);
  }

  // Compare two strings character-wise.
  bool operator>(StringView other) const {
    return other < *this;
  }

  // Compare two strings character-wise.
  bool operator>=(StringView other) const {
    return !(*this < other);
  }

  // Return false if 'StringView' does not contain 'object' at the beginning.
  bool beginsWith(const Object& object) const {
    if (size == 0) {
      return false;
    }

    return *data == object;
  }

  // Return false if 'StringView' does not contain 'other' at the beginning.
  bool beginsWith(StringView other) const {
    if (size >= other.getSize()) {
      for (Size i = 0;; ++i) {
        if (i == other.getSize()) {
          return true;
        }

        if (!(*(data + i) == *(other.getData() + i))) {
          break;
        }
      }
    }

    return false;
  }

  // Return false if 'StringView' does not contain 'object'.
  bool contains(const Object& object) const {
    for (Size i = 0; i < size; ++i) {
      if (*(data + i) == object) {
        return true;
      }
    }

    return false;
  }

  // Return false if 'StringView' does not contain 'other'.
  bool contains(StringView other) const {
    if (size >= other.getSize()) {
      Size i = size - other.getSize();
      for (Size j = 0; !(i < j); ++j) {
        for (Size k = 0;; ++k) {
          if (k == other.getSize()) {
            return true;
          }

          if (!(*(data + j + k) == *(other.getData() + k))) {
            break;
          }
        }
      }
    }

    return false;
  }

  // Return false if 'StringView' does not contain 'object' at the end.
  bool endsWith(const Object& object) const {
    if (size == 0) {
      return false;
    }

    return *(data + (size - 1)) == object;
  }

  // Return false if 'StringView' does not contain 'other' at the end.
  bool endsWith(StringView other) const {
    if (size >= other.getSize()) {
      Size i = size - other.getSize();
      for (Size j = 0;; ++j) {
        if (j == other.getSize()) {
          return true;
        }

        if (!(*(data + i + j) == *(other.getData() + j))) {
          break;
        }
      }
    }

    return false;
  }

  // Looks for the first occurence of 'object' in 'StringView',
  // returns index or INVALID if there are no occurences.
  Size find(const Object& object) const {
    for (Size i = 0; i < size; ++i) {
      if (*(data + i) == object) {
        return i;
      }
    }

    return INVALID;
  }

  // Looks for the first occurence of 'other' in 'StringView',
  // returns index or INVALID if there are no occurences.
  Size find(StringView other) const {
    if (size >= other.getSize()) {
      Size i = size - other.getSize();
      for (Size j = 0; !(i < j); ++j) {
        for (Size k = 0;; ++k) {
          if (k == other.getSize()) {
            return j;
          }

          if (!(*(data + j + k) == *(other.getData() + k))) {
            break;
          }
        }
      }
    }

    return INVALID;
  }

  // Looks for the last occurence of 'object' in 'StringView',
  // returns index or INVALID if there are no occurences.
  Size findLast(const Object& object) const {
    for (Size i = 0; i < size; ++i) {
      if (*(data + (size - 1 - i)) == object) {
        return size - 1 - i;
      }
    }

    return INVALID;
  }

  // Looks for the first occurence of 'other' in 'StringView',
  // returns index or INVALID if there are no occurences.
  Size findLast(StringView other) const {
    if (size >= other.getSize()) {
      Size i = size - other.getSize();
      for (Size j = 0; !(i < j); ++j) {
        for (Size k = 0;; ++k) {
          if (k == other.getSize()) {
            return i - j;
          }

          if (!(*(data + (i - j + k)) == *(other.getData() + k))) {
            break;
          }
        }
      }
    }

    return INVALID;
  }

  // Returns substring of 'headSize' first elements.
  // The behavior is undefined unless 'headSize' <= 'size'.
  StringView head(Size headSize) const {
    assert(headSize <= size);
    return StringView(data, headSize);
  }

  // Returns substring of 'tailSize' last elements.
  // The behavior is undefined unless 'tailSize' <= 'size'.
  StringView tail(Size tailSize) const {
    assert(tailSize <= size);
    return StringView(data + (size - tailSize), tailSize);
  }

  // Returns 'StringView' without 'headSize' first elements.
  // The behavior is undefined unless 'headSize' <= 'size'.
  StringView unhead(Size headSize) const {
    assert(headSize <= size);
    return StringView(data + headSize, size - headSize);
  }

  // Returns 'StringView' without 'tailSize' last elements.
  // The behavior is undefined unless 'tailSize' <= 'size'.
  StringView untail(Size tailSize) const {
    assert(tailSize <= size);
    return StringView(data, size - tailSize);
  }

  // Returns substring starting at 'startIndex' and contaning 'endIndex' - 'startIndex' elements.
  // The behavior is undefined unless 'startIndex' <= 'endIndex' and 'endIndex' <= 'size'.
  StringView range(Size startIndex, Size endIndex) const {
    assert(startIndex <= endIndex && endIndex <= size);
    return StringView(data + startIndex, endIndex - startIndex);
  }

  // Returns substring starting at 'startIndex' and contaning 'sliceSize' elements.
  // The behavior is undefined unless 'startIndex' <= 'size' and 'sliceSize' <= 'size' - 'startIndex'.
  StringView slice(Size startIndex, Size sliceSize) const {
    assert(startIndex <= size && sliceSize <= size - startIndex);
    return StringView(data + startIndex, sliceSize);
  }

  // Appends 'object' to 'StringBuffer'.
  // The behavior is undefined unless 1 <= 'MAXIMUM_SIZE' - 'size'.
  StringBuffer& append(Object object) {
    assert(1 <= MAXIMUM_SIZE - size);
    data[size] = object;
    ++size;
    return *this;
  }

  // Appends 'stringView' to 'StringBuffer'.
  // The behavior is undefined unless 'stringView.size()' <= 'MAXIMUM_SIZE' - 'size'.
  StringBuffer& append(StringView stringView) {
    assert(stringView.getSize() <= MAXIMUM_SIZE - size);
    if (stringView.getSize() != 0) {
      memcpy(data + size, stringView.getData(), stringView.getSize());
      size += stringView.getSize();
    }

    return *this;
  }

  // Sets 'StringBuffer' to empty state, that is 'size' == 0
  StringBuffer& clear() {
    size = 0;
    return *this;
  }

  // Removes substring starting at 'startIndex' and contaning 'cutSize' elements.
  // The behavior is undefined unless 'startIndex' <= 'size' and 'cutSize' <= 'size' - 'startIndex'.
  StringBuffer& cut(Size startIndex, Size cutSize) {
    assert(startIndex <= size && cutSize <= size - startIndex);
    if (cutSize != 0) {
      memcpy(data + startIndex, data + startIndex + cutSize, size - startIndex - cutSize);
      size -= cutSize;
    }

    return *this;
  }

  // Copy 'object' to each element of 'StringBuffer'.
  StringBuffer& fill(Object object) {
    if (size > 0) {
      memset(data, object, size);
    }

    return *this;
  }

  // Inserts 'object' into 'StringBuffer' at 'index'.
  // The behavior is undefined unless 'index' <= 'size' and 1 <= 'MAXIMUM_SIZE' - 'size'.
  StringBuffer& insert(Size index, Object object) {
    assert(index <= size);
    assert(1 <= MAXIMUM_SIZE - size);
    memmove(data + index + 1, data + index, size - index);
    data[index] = object;
    ++size;
    return *this;
  }

  // Inserts 'stringView' into 'StringBuffer' at 'index'.
  // The behavior is undefined unless 'index' <= 'size' and 'stringView.size()' <= 'MAXIMUM_SIZE' - 'size'.
  StringBuffer& insert(Size index, StringView stringView) {
    assert(index <= size);
    assert(stringView.getSize() <= MAXIMUM_SIZE - size);
    if (stringView.getSize() != 0) {
      memmove(data + index + stringView.getSize(), data + index, size - index);
      memcpy(data + index, stringView.getData(), stringView.getSize());
      size += stringView.getSize();
    }

    return *this;
  }

  // Overwrites 'StringBuffer' starting at 'index' with 'stringView', possibly expanding 'StringBuffer'.
  // The behavior is undefined unless 'index' <= 'size' and 'stringView.size()' <= 'MAXIMUM_SIZE' - 'index'.
  StringBuffer& overwrite(Size index, StringView stringView) {
    assert(index <= size);
    assert(stringView.getSize() <= MAXIMUM_SIZE - index);
    memcpy(data + index, stringView.getData(), stringView.getSize());
    if (size < index + stringView.getSize()) {
      size = index + stringView.getSize();
    }

    return *this;
  }

  // Sets 'size' to 'bufferSize', assigning value '\0' to newly inserted elements.
  // The behavior is undefined unless 'bufferSize' <= 'MAXIMUM_SIZE'.
  StringBuffer& resize(Size bufferSize) {
    assert(bufferSize <= MAXIMUM_SIZE);
    if (bufferSize > size) {
      memset(data + size, 0, bufferSize - size);
    }

    size = bufferSize;
    return *this;
  }

  // Reverse 'StringBuffer' elements.
  StringBuffer& reverse() {
    for (Size i = 0; i < size / 2; ++i) {
      Object object = *(data + i);
      *(data + i) = *(data + (size - 1 - i));
      *(data + (size - 1 - i)) = object;
    }

    return *this;
  }

  // Sets 'size' to 'bufferSize'.
  // The behavior is undefined unless 'bufferSize' <= 'size'.
  StringBuffer& shrink(Size bufferSize) {
    assert(bufferSize <= size);
    size = bufferSize;
    return *this;
  }

protected:
  Object data[MAXIMUM_SIZE];
  Size size;
};

template<size_t MAXIMUM_SIZE> const typename StringBuffer<MAXIMUM_SIZE>::Size StringBuffer<MAXIMUM_SIZE>::INVALID = std::numeric_limits<typename StringBuffer<MAXIMUM_SIZE>::Size>::max();

}
