
/*
 *   _____    _ _        .      .    .
 *  |_   _|  | | |  .       .           .
 *    | |  __| | | ___         .    .        .
 *    | | / _` | |/ _ \                .
 *   _| || (_| | |  __/ github.com/Naios/idle
 *  |_____\__,_|_|\___| AGPL v3 (Early Access)
 *
 * Copyright(c) 2018 - 2021 Denis Blank <denis.blank at outlook dot com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef IDLE_CORE_UTIL_STRING_VIEW_HPP_INCLUDED
#define IDLE_CORE_UTIL_STRING_VIEW_HPP_INCLUDED

#include <iosfwd>
#include <idle/core/api.hpp>
#include <idle/core/util/iterator_facade.hpp>
#include <idle/core/util/span.hpp>

namespace idle {
class StringView : public Span<char const> {
  /// Makes it easier to consume the char[] generated by compilers which
  /// also contain a null character at the end. This null character is eaten
  /// such that the StringView view only contains the string part.
  static constexpr bool has_null_terminator(char const* str,
                                            std::size_t size) noexcept {
    return (size != 0) && (str[size - 1] == '\0');
  }

public:
  constexpr StringView() noexcept {
    // clang compiler bug workaround
    // https://stackoverflow.com/questions/43819314/default-member-initializer-needed-within-definition-of-enclosing-class-outside
  }
  template <std::size_t Size>
  /* implicit */ constexpr StringView(char const (&str)[Size]) noexcept
    : Span<char const>(str, has_null_terminator(str, Size) ? Size - 1 : Size) {}
  constexpr StringView(pointer data, size_type size) noexcept
    : Span<char const>(data, size) {}

  template <typename C,
            decltype(const_pointer(std::declval<C>().data()))* = nullptr,
            decltype(size_type(std::declval<C>().size()))* = nullptr>
  /* implicit */ constexpr StringView(C&& src) noexcept(
      noexcept(std::declval<C>().data()) && noexcept(std::declval<C>().size()))
    : Span(src.data(), src.size()) {}

  StringView(StringView&&) = default;
  StringView(StringView const&) = default;
  StringView& operator=(StringView&&) = default;
  StringView& operator=(StringView const&) = default;

  using Span<char const>::find;
  using Span<char const>::rfind;

  constexpr size_type find(StringView seq, size_type pos = 0U) const noexcept {
    if (seq.empty()) {
      return pos;
    }
    if (seq.size() > size() - pos) {
      return npos;
    }

    std::size_t const seq_size = seq.size();
    std::size_t const length = size() - seq_size;

    for (; pos < length; ++pos) {
      if (is_subseq(seq, pos)) {
        return pos;
      }
    }
    return npos;
  }

  constexpr StringView substr(size_type pos = 0, //
                              size_type count = npos) const noexcept {
    return subspan(pos, count);
  }

  constexpr bool starts_with(StringView seq) const noexcept {
    if (seq.size() > size()) {
      return false;
    } else {
      return substr(0, seq.size()) == seq;
    }
  }

  constexpr bool operator==(StringView other) const noexcept {
    return Span<char const>::operator==(other);
  }

  constexpr bool operator<(StringView other) const noexcept {
    auto const l_size = size();
    auto const r_size = other.size();

    if (l_size != r_size) {
      return l_size < r_size;
    }

    for (size_type i = 0; i < l_size; ++i) {
      if ((*this)[i] != other[i]) {
        return (*this)[i] < other[i];
      }
    }

    return false;
  }

  friend IDLE_API(idle) std::ostream& operator<<(std::ostream& os,
                                                 StringView const& p);

private:
  constexpr bool is_subseq(StringView seq, std::size_t pos) const noexcept {
    std::size_t const seq_size = seq.size();

    for (std::size_t off = 0U; off < seq_size; ++off) {
      if (operator[](pos + off) != seq[off]) {
        return false;
      }
    }

    return true;
  }
};

constexpr StringView operator"" _sv(char const* str,
                                    std::size_t length) noexcept {
  return {str, length};
}
} // namespace idle

#endif // IDLE_CORE_UTIL_STRING_VIEW_HPP_INCLUDED
