/*
  Copyright(C) 2011-2016 Brazil

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#pragma once

#include "dat.hpp"

namespace grn {
namespace dat {

class GRN_DAT_API String {
 public:
  String()
      : ptr_(NULL),
        length_(0) {}
  String(const void *ptr, UInt32 length)
      : ptr_(static_cast<const UInt8 *>(ptr)),
        length_(length) {}
  template <UInt32 T>
  explicit String(const char (&str)[T])
      : ptr_(reinterpret_cast<const UInt8 *>(str)),
        length_(T - 1) {}
  String(const String &rhs)
      : ptr_(rhs.ptr_),
        length_(rhs.length_) {}

  String &operator=(const String &rhs) {
    set_ptr(rhs.ptr());
    set_length(rhs.length());
    return *this;
  }

  const UInt8 &operator[](UInt32 i) const {
    GRN_DAT_DEBUG_THROW_IF(i >= length_);
    return ptr_[i];
  }

  const void *ptr() const {
    return ptr_;
  }
  UInt32 length() const {
    return length_;
  }

  void set_ptr(const void *x) {
    ptr_ = static_cast<const UInt8 *>(x);
  }
  void set_length(UInt32 x) {
    length_ = x;
  }

  void assign(const void *ptr, UInt32 length) {
    set_ptr(ptr);
    set_length(length);
  }

  String substr(UInt32 offset = 0) const {
    return String(ptr_ + offset, length_ - offset);
  }
  String substr(UInt32 offset, UInt32 length) const {
    return String(ptr_ + offset, length);
  }

  // This function returns an integer as follows:
  // - a negative value if *this < rhs,
  // - zero if *this == rhs,
  // - a positive value if *this > rhs,
  // but if the offset is too large, the result is undefined.
  int compare(const String &rhs, UInt32 offset = 0) const {
    GRN_DAT_DEBUG_THROW_IF(offset > length());
    GRN_DAT_DEBUG_THROW_IF(offset > rhs.length());

    for (UInt32 i = offset; i < length(); ++i) {
      if (i >= rhs.length()) {
        return 1;
      } else if ((*this)[i] != rhs[i]) {
        return (*this)[i] - rhs[i];
      }
    }
    return (length() == rhs.length()) ? 0 : -1;
  }

  bool starts_with(const String &str) const {
    if (length() < str.length()) {
      return false;
    }
    for (UInt32 i = 0; i < str.length(); ++i) {
      if ((*this)[i] != str[i]) {
        return false;
      }
    }
    return true;
  }

  bool ends_with(const String &str) const {
    if (length() < str.length()) {
      return false;
    }
    UInt32 offset = length() - str.length();
    for (UInt32 i = 0; i < str.length(); ++i) {
      if ((*this)[offset + i] != str[i]) {
        return false;
      }
    }
    return true;
  }

  void swap(String *rhs) {
    const UInt8 * const ptr_temp = ptr_;
    ptr_ = rhs->ptr_;
    rhs->ptr_ = ptr_temp;

    const UInt32 length_temp = length_;
    length_ = rhs->length_;
    rhs->length_ = length_temp;
  }

 private:
  const UInt8 *ptr_;
  UInt32 length_;
};

inline bool operator==(const String &lhs, const String &rhs) {
  if (lhs.length() != rhs.length()) {
    return false;
  } else if (lhs.ptr() == rhs.ptr()) {
    return true;
  }
  for (UInt32 i = 0; i < lhs.length(); ++i) {
    if (lhs[i] != rhs[i]) {
      return false;
    }
  }
  return true;
}

inline bool operator!=(const String &lhs, const String &rhs) {
  return !(lhs == rhs);
}

inline bool operator<(const String &lhs, const String &rhs) {
  return lhs.compare(rhs) < 0;
}

inline bool operator>(const String &lhs, const String &rhs) {
  return rhs < lhs;
}

inline bool operator<=(const String &lhs, const String &rhs) {
  return !(lhs > rhs);
}

inline bool operator>=(const String &lhs, const String &rhs) {
  return !(lhs < rhs);
}

}  // namespace dat
}  // namespace grn
