#ifndef __VEREIGN_BYTES_VIEW_HH
#define __VEREIGN_BYTES_VIEW_HH

#include <cstring>
#include <string>
#include <string_view>
#include <stdexcept>
#include <algorithm>

namespace vereign::bytes {

class View {
public:
  View() noexcept : size_{0}, data_{nullptr} {}

  View(const uint8_t* data, std::size_t size) noexcept
    : size_{size},
      data_{data}
  {
  }

  View(std::string_view str) noexcept
    : size_{str.length()},
      data_{str.length() > 0 ? reinterpret_cast<const uint8_t*>(str.data()): nullptr}
  {
  }

  View(const void* ptr, std::size_t size) noexcept
    : size_{size},
      data_{static_cast<const uint8_t*>(ptr)}
  {
  }

  View(const View&) = default;
  auto operator=(const View&) -> View& = default;

  auto Slice(std::size_t start) const -> View {
    if (start >= size_) {
      return View(data_, 0);
    }

    return View(data_ + start, size_ - start);
  }

  auto Slice(std::size_t start, std::size_t end) const -> View {
    if (start >= size_) {
      return View(data_, 0);
    }

    return View(data_ + start, std::min(size_, end) - start);
  }

  auto Data() const noexcept -> const uint8_t* {
    return data_;
  }

  auto CharData() const noexcept -> const char* {
    return reinterpret_cast<const char*>(data_);
  }

  auto Size() const noexcept -> std::size_t {
    return size_;
  }

  auto String() const noexcept -> std::string_view {
    return std::string_view{CharData(), size_};
  }

  auto operator==(View other) const noexcept -> bool {
    if (size_ != other.size_) {
      return false;
    }

    return std::memcmp(data_, other.data_, size_) == 0;
  }

  auto operator[](std::size_t index) const -> const uint8_t& {
    if (index >= size_ ) {
      throw std::runtime_error("index out of bounds");
    }

    return data_[index];
  }

private:
  std::size_t size_ = 0;
  const uint8_t* data_ = nullptr;
};

} // namespace vereign::bytes

#endif // __VEREIGN_BYTES_VIEW_HH