#ifndef JWT_STRING_VIEW_HPP
#define JWT_STRING_VIEW_HPP

#include <limits>
#include <string>
#include <cassert>

namespace jwt {

/*
 * Implements c++17 string_view.
 * Could have used boost::string_ref, but wanted to 
 * keep boost dependency off from this library.
 */

template <
  typename CharT, 
  typename Traits = std::char_traits<CharT>
>
class basic_string_view
{
public: // Member Types
  using traits_type = std::char_traits<CharT>;
  using value_type = CharT;
  using pointer = const CharT*;
  using const_pointer = const CharT*;
  using reference = const CharT&;
  using const_reference = const CharT&;
  using iterator = const CharT*;
  using const_iterator = const CharT*;
  using reverse_iterator = std::reverse_iterator<iterator>;
  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
  using size_type = size_t;
  using difference_type = std::ptrdiff_t;

  static constexpr size_type npos = size_type(-1);

public: // 'tors
  /// The default constructor;
  basic_string_view() = default;

  /// Construct from string literal
  basic_string_view(const CharT* str) noexcept
    : data_(str)
    , len_(str ? traits_type::length(str) : 0)
  {
  }

  /// Construct from CharT pointer and provided length
  basic_string_view(const CharT* p, size_type len) noexcept
    : data_(p)
    , len_(len)
  {
  }

  /// Construct from std::string
  template <typename Allocator>
  basic_string_view(
      const std::basic_string<CharT, Traits, Allocator>& str) noexcept
    : data_(str.data())
    , len_(str.length())
  {
  }

  /// Copy constructor
  basic_string_view(const basic_string_view&) = default;

  /// Assignment operator
  basic_string_view& operator=(const basic_string_view&) = default;

  /// Destructor
  ~basic_string_view()
  {
    data_ = nullptr;
    len_ = 0;
  }

public: // Exposed APIs
  /// Iterator Member Functions

  iterator begin() const noexcept { return data_;        }
  iterator end()   const noexcept { return data_ + len_; }

  iterator rbegin() const noexcept { return reverse_iterator(end());   }
  iterator rend()   const noexcept { return reverse_iterator(begin()); }

  const_iterator cbegin() const noexcept { return begin(); }
  const_iterator cend()   const noexcept { return end();   }

  const_iterator crbegin() const noexcept { return rbegin(); }
  const_iterator crend()   const noexcept { return rend();   }

  /// Capacity Member Functions

  size_type length() const noexcept { return len_; }
  size_type size()   const noexcept { return len_; }

  size_type max_size() const noexcept
  {
    return (npos - sizeof(size_type) - sizeof(void*))
      / sizeof(value_type) / 4;
  }

  bool empty() const noexcept { return len_ == 0; }

  /// Element Access Member Functions
  const_reference operator[](size_type idx) const noexcept
  {
    return data_[idx];
  }

  // NOTE: 'at' not supported
  //CharT at(size_type idx) const;

  const_reference front() const noexcept
  {
    return data_[0];
  }

  const_reference back() const noexcept
  {
    return data_[len_ - 1];
  }

  const_pointer data() const noexcept
  {
    return data_;
  }

  /// Modifier Member Functions
  void remove_prefix(size_type n) noexcept
  {
    assert (n < len_ && "Data would point out of bounds");
    data_ += n;
    len_ -= n;
  }

  void remove_suffix(size_type n) noexcept
  {
    assert (n < len_ && "Suffix length more than data length");
    len_ -= n;
  }

  void swap(basic_string_view& other)
  {
    std::swap(data_, other.data_);
    std::swap(len_, other.len_);
  }

  /// String Operation Member Functions

  template <typename Allocator>
  explicit operator std::basic_string<CharT, Traits, Allocator>() const
  {
    return {data_, len_};
  }

  template <typename Allocator = std::allocator<CharT>>
  std::basic_string<CharT, Traits, Allocator>
  to_string(const Allocator& alloc = Allocator()) const
  {
    return {data_, len_, alloc};
  }

  // NOTE: Does not throw 
  size_type copy(CharT* dest, size_type n, size_type pos = 0) const noexcept
  {
    assert (pos < len_ && n < len_);
    size_type to_copy = std::min(n, len_ - pos);

    for (size_type i = 0; i < to_copy; i++) {
      dest[i] = data_[i + pos];
    }

    return to_copy;
  }

  // NOTE: Does not throw
  basic_string_view substr(size_type pos, size_type n = npos) const noexcept
  {
    assert (pos < len_ && "Start position should be less than length of the view");
    assert (n == npos ? 1 : (n - pos) < len_ && 
        "Substring length asked for is more than the view length");

    if (n == npos) n = len_;

    return basic_string_view{data_ + pos, n};
  }

  /// Comparison Member Functions
  int compare(const basic_string_view& other) const noexcept
  {
    int ret = traits_type::compare(data_, other.data_, std::min(len_, other.len_));
    if (ret == 0) {
      ret = compare_length(len_, other.len_);
    }
    return ret;
  }

  int compare(size_type pos, size_type n, basic_string_view other) const noexcept
  {
    return substr(pos, n).compare(other);
  }

  int compare(const CharT* str) const noexcept
  {
    return compare(basic_string_view{str});
  }

  int compare(size_type pos, size_type n, const CharT* str) const noexcept
  {
    return compare(pos, n, basic_string_view{str});
  }

  int compare(size_type pos, size_type n1, const CharT* str, size_type n2) const noexcept
  {
    return compare(pos, n1, basic_string_view{str, n2});
  }

  /// Find operations
  size_type find(const CharT* str, size_type pos, size_type n) const noexcept;

  size_type find(const CharT ch, size_type pos) const noexcept;

  size_type find(basic_string_view sv, size_type pos = 0) const noexcept
  {
    return find(sv.data(), pos, sv.length());
  }

  size_type find(const CharT* str, size_type pos = 0) const noexcept
  {
    return find(str, pos, traits_type::length(str));
  }

  size_type rfind(const CharT* str, size_type pos, size_type n) const noexcept;

  size_type rfind(const CharT ch, size_type pos) const noexcept;

  size_type rfind(basic_string_view sv, size_type pos = 0) const noexcept
  {
    return rfind(sv.data(), pos, sv.length());
  }

  size_type rfind(const CharT* str, size_type pos = 0) const noexcept
  {
    return rfind(str, pos, traits_type::length(str));
  }

  size_type find_first_of(const CharT* str, size_type pos, size_type count) const noexcept;

  size_type find_first_of(basic_string_view str, size_type pos = 0) const noexcept
  {
    return find_first_of(str.data(), pos, str.length());
  }

  size_type find_first_of(CharT ch, size_type pos = 0) const noexcept
  {
    return find(ch, pos);
  }

  size_type find_first_of(const CharT* str, size_type pos = 0) const noexcept
  {
    return find_first_of(str, pos, traits_type::length(str));
  }

  size_type find_last_of(const CharT* str, size_type pos, size_type count) const noexcept;

  size_type find_last_of(basic_string_view str, size_type pos = npos) const noexcept
  {
    return find_last_of(str.data(), (pos == npos ? len_ - 1 : pos), str.length());
  }

  size_type find_last_of(CharT ch, size_type pos = npos) const noexcept
  {
    return rfind(ch, pos == npos ? len_ - 1 : pos);
  }

  size_type find_last_of(const CharT* str, size_type pos = npos) const noexcept
  {
    return find_last_of(str, (pos == npos ? len_ - 1 : pos), traits_type::length(str));
  }

  size_type find_first_not_of(const CharT* str, size_type pos, size_type n) const noexcept;

  size_type find_first_not_of(CharT ch, size_type pos) const noexcept;

  size_type find_first_not_of(basic_string_view str, size_type pos = 0) const noexcept
  {
    return find_first_not_of(str.data(), pos, str.length());
  }

  size_type find_first_not_of(const CharT* str, size_type pos = 0) const noexcept
  {
    return find_first_not_of(str, pos, traits_type::length(str));
  }

  size_type find_last_not_of(const CharT* str, size_type pos, size_type n) const noexcept;

  size_type find_last_not_of(CharT ch, size_type pos) const noexcept;

  size_type find_last_not_of(basic_string_view str, size_type pos = npos) const noexcept
  {
    return find_last_not_of(str.data(), (pos == npos ? len_ - 1 : pos), str.length());
  }

  size_type find_last_not_of(const CharT* str, size_type pos = npos) const noexcept
  {
    return find_last_not_of(str, (pos == npos ? len_ - 1 : pos), traits_type::length(str));
  }

  /// Comparison operators Member Functions
  /*
  friend bool operator== (basic_string_view a, basic_string_view b) noexcept;

  friend bool operator!= (basic_string_view a, basic_string_view b) noexcept;

  friend bool operator< (basic_string_view a, basic_string_view b) noexcept;

  friend bool operator> (basic_string_view a, basic_string_view b) noexcept;

  friend bool operator<= (basic_string_view a, basic_string_view b) noexcept;

  friend bool operator>= (basic_string_view a, basic_string_view b) noexcept;
  */

private: // private implementations

  static constexpr int compare_length(size_type n1, size_type n2) noexcept
  {
    return static_cast<difference_type>(n1 - n2) > std::numeric_limits<int>::max()
      ? std::numeric_limits<int>::max()
      : static_cast<difference_type>(n1 - n2) < std::numeric_limits<int>::min()
        ? std::numeric_limits<int>::min()
        : static_cast<int>(n1 - n2)
      ;
  }

private:
  // This is what view is basically...
  const char* data_ = nullptr;
  size_type len_ = 0;
};


/// Helper typedef
using string_view = basic_string_view<char>;


} // END namespace jwt

#include "jwt/impl/string_view.ipp"

#endif