#ifndef TABLEAU_HYPER_IMPL_RESULT_IMPL_HPP
#define TABLEAU_HYPER_IMPL_RESULT_IMPL_HPP

#include <cassert>
#include <cctype>
#include <hyperapi/Connection.hpp>
#include <hyperapi/Numeric.hpp>
#include <sstream>
#include <string>
#include <hyperapi/hyperapi.h>

namespace hyperapi {

namespace internal {
[[noreturn]] inline void throwTypeError(string_view requestedType, SqlType actualType, string_view columnName) {
   std::string message = "Type conflict on Value access for column '" + std::string(columnName) + "'. " + "Requested type: '" + std::string(requestedType) +
      "' but actually got: '" + actualType.toString() + "'. Request: '" + getNativeTypeName(actualType) + "'.";
   HYPER_PRECONDITION_MSG(false, message);
   std::abort(); // unreachable
}

inline std::string createNullabilityErrorMessage(string_view requestedType, string_view columnName) {
   return "Type conflict on Value access for column '" + std::string(columnName) + "'. " + "Requested type: '" + std::string(requestedType) +
      "' but actually got: NULL. Try requesting 'optional<" + std::string(requestedType) + ">'.";
}

template <typename T>
inline T getIntegral(string_view requestedType, SqlType type, hyper_value_t value, string_view columnName) {
   switch (type.getTag()) {
      case TypeTag::SmallInt: {
         assert(value.size == sizeof(int16_t));
         int16_t v = hyper_read_int16(value.value);
         if (sizeof(T) < 2) {
            break;
         }
         return static_cast<T>(v);
      }
      case TypeTag::Int: {
         assert(value.size == sizeof(int32_t));
         int32_t v = hyper_read_int32(value.value);
         if (sizeof(T) < 4) {
            break;
         }
         return static_cast<T>(v);
      }
      case TypeTag::BigInt: {
         assert(value.size == sizeof(int64_t));
         int64_t v = hyper_read_int64(value.value);
         if (sizeof(T) < 8) {
            break;
         }
         return static_cast<T>(v);
      }
      default:
         break;
   }
   throwTypeError(requestedType, type, columnName);
}

template <typename ReturnType>
struct ValueAccess {
   ReturnType getValue(Value) { static_assert(false_type<ReturnType>::value, "This data type cannot be used to retrieve a result value."); }
};

template <unsigned precision, unsigned scale>
struct ValueAccess<Numeric<precision, scale>> {
   inline Numeric<precision, scale> getValue(Value value) {
      HYPER_PRECONDITION_MSG(value.value_.value, internal::createNullabilityErrorMessage("Numeric<" + std::to_string(precision) + "," + std::to_string(scale) + ">", value.columnName_));
      switch (value.type_.getTag()) {
         case TypeTag::SmallInt:
            return value.get<int16_t>();
         case TypeTag::Int:
            return value.get<int32_t>();
         case TypeTag::BigInt:
            return value.get<int64_t>();
         case TypeTag::Float:
            return value.get<float>();
         case TypeTag::Double:
            return value.get<double>();
         case TypeTag::Numeric:
            if (value.type_.getPrecision() <= 18) {
               assert(value.value_.size == sizeof(int64_t));
               return Numeric<precision, scale>(hyper_read_int64(value.value_.value), value.type_.getPrecision(), value.type_.getScale(), typename Numeric<precision, scale>::raw_t());
            } else {
               assert(value.value_.size == sizeof(hyper_data128_t));
               assert(value.type_.getPrecision() > 18);
               assert(value.type_.getPrecision() <= 38);
               return Numeric<precision, scale>(hyper_read_data128(value.value_.value), value.type_.getPrecision(), value.type_.getScale(), typename Numeric<precision, scale>::raw_t());
            }
         default:
            internal::throwTypeError("Numeric<" + std::to_string(precision) + "," + std::to_string(scale) + ">", value.type_, value.columnName_);
      }
   }
};

template <typename ReturnType>
struct ValueAccess<optional<ReturnType>> {
   inline optional<ReturnType> getValue(Value value) {
      if (value.isNull()) {
         return {};
      }
      return value.get<ReturnType>();
   }
};

template <typename T, typename F>
T bitCast(F f) noexcept {
   static_assert(sizeof(T) == sizeof(F), "size mismatch");
   union {
      F f;
      T t;
   } u{f};
   return u.t;
}

inline bool hasCopyData(Result& result) {
   return hyper_rowset_has_copy_data(result.rowset_);
}

struct CopyData {
   /// The pointer to the copy data
   char* ptr = nullptr;
   /// The length of the copy data
   size_t length = 0;
   /// Get the pointer to the data
   char* data() const { return ptr; }
   /// The size of this copy data
   size_t size() const { return length; }
   /// Is this copy done?
   bool done() const { return ptr == nullptr; }
   /// Operator bool
   operator bool() const { return !done(); }
   /// Destructor
   ~CopyData() {
      if (ptr) {
         hyper_rowset_free_copy_data(ptr);
      }
   }
};
inline CopyData getCopyData(Result& result) {
   CopyData buffer;
   if (hyper_error_t* error = hyper_rowset_get_copy_data(result.rowset_, &buffer.ptr, &buffer.length)) {
      throw internal::makeHyperException(error);
   }
   return buffer;
}

inline hyper_rowset_type_t getResultType(Result& result) {
   return hyper_rowset_get_type(result.rowset_);
}
}

template <>
inline short Value::get<short>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("short", columnName_));
   return internal::getIntegral<short>("short", type_, value_, columnName_);
}

template <>
inline int Value::get<int>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("int", columnName_));
   return internal::getIntegral<int>("int", type_, value_, columnName_);
}

template <>
inline long Value::get<long>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("long", columnName_));
   return internal::getIntegral<long>("long", type_, value_, columnName_);
}

template <>
inline long long Value::get<long long>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("long long", columnName_));
   return internal::getIntegral<long long>("long long", type_, value_, columnName_);
}

template <>
inline bool Value::get<bool>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("bool", columnName_));
   switch (type_.getTag()) {
      case TypeTag::Bool: {
         assert(value_.size == sizeof(int8_t));
         int8_t v = hyper_read_int8(value_.value);
         assert((v == 0) || (v == 1));
         return v != 0;
      }
      default:
         internal::throwTypeError("bool", type_, columnName_);
   }
}

template <>
inline float Value::get<float>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("float", columnName_));
   switch (type_.getTag()) {
      case TypeTag::SmallInt:
         return static_cast<float>(get<int16_t>());
      case TypeTag::Int:
         return static_cast<float>(get<int32_t>());
      case TypeTag::BigInt:
         return static_cast<float>(get<int64_t>());
      case TypeTag::Float:
         assert(value_.size == sizeof(float));
         return internal::bitCast<float>(hyper_read_int32(value_.value));
      case TypeTag::Double:
         assert(value_.size == sizeof(double));
         return static_cast<float>(internal::bitCast<double>(hyper_read_int64(value_.value)));
      case TypeTag::Numeric:
         assert(type_.getScale() != HYPER_UNUSED_MODIFIER);
         if (type_.getPrecision() <= 18) {
            assert(value_.size == sizeof(int64_t));
            return internal::numericToFloat(hyper_read_int64(value_.value), type_.getScale());
         } else {
            assert(type_.getPrecision() > 18);
            assert(type_.getPrecision() <= 38);
            assert(value_.size == sizeof(hyper_data128_t));
            return internal::numericToFloat(hyper_read_data128(value_.value), type_.getScale());
         }
      default:
         internal::throwTypeError("float", type_, columnName_);
   }
}

template <>
inline double Value::get<double>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("double", columnName_));
   switch (type_.getTag()) {
      case TypeTag::SmallInt:
         return static_cast<double>(get<int16_t>());
      case TypeTag::Int:
         return static_cast<double>(get<int32_t>());
      case TypeTag::BigInt:
         return static_cast<double>(get<int64_t>());
      case TypeTag::Float:
         assert(value_.size == sizeof(float));
         return static_cast<double>(internal::bitCast<float>(hyper_read_int32(value_.value)));
      case TypeTag::Double:
         assert(value_.size == sizeof(double));
         return internal::bitCast<double>(hyper_read_int64(value_.value));
      case TypeTag::Numeric:
         assert(type_.getScale() != HYPER_UNUSED_MODIFIER);
         if (type_.getPrecision() <= 18) {
            assert(value_.size == sizeof(int64_t));
            return internal::numericToDouble(hyper_read_int64(value_.value), type_.getScale());
         } else {
            assert(type_.getPrecision() > 18);
            assert(type_.getPrecision() <= 38);
            assert(value_.size == sizeof(hyper_data128_t));
            return internal::numericToDouble(hyper_read_data128(value_.value), type_.getScale());
         }
      default:
         internal::throwTypeError("double", type_, columnName_);
   }
}

template <>
inline long double Value::get<long double>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("long double", columnName_));
   return static_cast<long double>(get<double>());
}

template <typename T>
inline T Value::get() const {
   return internal::ValueAccess<T>().getValue(*this);
}

template <>
inline uint32_t Value::get<uint32_t>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("uint32_t", columnName_));
   switch (type_.getTag()) {
      case TypeTag::Oid:
         assert(value_.size == sizeof(int32_t));
         return static_cast<uint32_t>(hyper_read_int32(value_.value));
      default:
         internal::throwTypeError("uint32_t", type_, columnName_);
   }
}

template <>
inline string_view Value::get<string_view>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("string", columnName_));
   switch (type_.getTag()) {
      case TypeTag::Text:
      case TypeTag::Varchar:
      case TypeTag::Char:
      case TypeTag::Json:
         return string_view(reinterpret_cast<const char*>(hyper_read_varbinary(value_.value)), value_.size);
      default:
         internal::throwTypeError("string", type_, columnName_);
   }
}

template <>
inline std::string Value::get<std::string>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("string", columnName_));
   return std::string(get<string_view>());
}

template <>
inline ByteSpan Value::get<ByteSpan>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("ByteSpan", columnName_));
   return {hyper_read_varbinary(value_.value), value_.size};
}

template <>
inline std::vector<uint8_t> Value::get<std::vector<uint8_t>>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("vector<uint8_t>", columnName_));
   ByteSpan bytes = get<ByteSpan>();
   return {bytes.data, bytes.data + bytes.size};
}

template <>
inline Interval Value::get<Interval>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("Interval", columnName_));
   switch (type_.getTag()) {
      case TypeTag::Interval:
         assert(value_.size == sizeof(hyper_data128_t));
         return Interval(hyper_read_data128(value_.value), Interval::raw_t());
      default:
         internal::throwTypeError("Interval", type_, columnName_);
   }
}

template <>
inline Date Value::get<Date>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("Date", columnName_));
   switch (type_.getTag()) {
      case TypeTag::Date:
         assert(value_.size == sizeof(int32_t));
         return Date(static_cast<hyper_date_t>(hyper_read_int32(value_.value)), Date::raw_t());
      default:
         internal::throwTypeError("Date", type_, columnName_);
   }
}

template <>
inline Time Value::get<Time>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("Time", columnName_));
   switch (type_.getTag()) {
      case TypeTag::Time:
         assert(value_.size == sizeof(int64_t));
         return Time(static_cast<hyper_time_t>(hyper_read_int64(value_.value)), Time::raw_t());
      default:
         internal::throwTypeError("Time", type_, columnName_);
   }
}

template <>
inline Timestamp Value::get<Timestamp>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("Timestamp", columnName_));
   switch (type_.getTag()) {
      case TypeTag::Timestamp:
         assert(value_.size == sizeof(int64_t));
         return Timestamp(static_cast<hyper_timestamp_t>(hyper_read_int64(value_.value)), Timestamp::raw_t());
      default:
         internal::throwTypeError("Timestamp", type_, columnName_);
   }
}

template <>
inline OffsetTimestamp Value::get<OffsetTimestamp>() const {
   HYPER_PRECONDITION_MSG(value_.value != nullptr, internal::createNullabilityErrorMessage("OffsetTimestamp", columnName_));
   switch (type_.getTag()) {
      case TypeTag::TimestampTZ:
         assert(value_.size == sizeof(int64_t));
         return OffsetTimestamp(static_cast<hyper_timestamp_t>(hyper_read_int64(value_.value)), OffsetTimestamp::raw_t());
      default:
         internal::throwTypeError("OffsetTimestamp", type_, columnName_);
   }
}

inline std::ostream& operator<<(std::ostream& os, const Value& value) {
   if (value.isNull()) {
      return os << "NULL";
   }
   switch (value.type_.getTag()) {
      case TypeTag::Unsupported:
         break;
      case TypeTag::Text:
      case TypeTag::Varchar:
      case TypeTag::Char: // TODO: char1 handling should be hidden (TFSID 921664)
      case TypeTag::Json:
         return os << value.get<string_view>();
      case TypeTag::SmallInt:
         return os << value.get<short>();
      case TypeTag::Int:
         return os << value.get<int>();
      case TypeTag::BigInt:
         return os << value.get<long long>();
      case TypeTag::Bool:
         return os << value.get<bool>();
      case TypeTag::Date:
         return os << value.get<Date>();
      case TypeTag::Numeric:
         if (value.type_.getPrecision() <= 18) {
            assert(value.value_.size == sizeof(int64_t));
            return os << internal::numericToString(hyper_read_int64(value.value_.value), value.type_.getScale());
         } else {
            assert(value.value_.size == sizeof(hyper_data128_t));
            assert(value.type_.getPrecision() > 18);
            assert(value.type_.getPrecision() <= 38);
            return os << internal::numericToString(hyper_read_data128(value.value_.value), value.type_.getScale());
         }
      case TypeTag::Float:
         return os << value.get<float>();
      case TypeTag::Double:
         return os << value.get<double>();
      case TypeTag::Oid:
         return os << value.get<uint32_t>();
      case TypeTag::Bytes:
      case TypeTag::TabGeography:
         return os << value.get<ByteSpan>();
      case TypeTag::Interval:
         return os << value.get<Interval>();
      case TypeTag::Time:
         return os << value.get<Time>();
      case TypeTag::Timestamp:
         return os << value.get<Timestamp>();
      case TypeTag::TimestampTZ:
         return os << value.get<OffsetTimestamp>();
   }
   std::abort(); // Unreachable
}

inline void Result::close() noexcept {
   if (rowset_) {
      hyper_close_rowset(rowset_);
      rowset_ = nullptr;
   }
}

inline void Result::fetchNextChunk() {
   if (!isOpen()) {
      currentChunk_ = Chunk();
      currentChunkIterator_ = end(currentChunk_);
      return;
   }
   hyper_rowset_chunk_t* newChunk = nullptr;
   hyper_error_t* error = hyper_rowset_get_next_chunk(rowset_, &newChunk);
   if (error) {
      throw internal::makeHyperException(error);
   }
   if (!schema_) {
      createSchema();
   }
   if (newChunk) {
      Chunk chunk(newChunk, *this);
      currentChunk_ = std::move(chunk);
      currentChunkIterator_ = begin(currentChunk_);
   } else {
      // There are no more chunks.
      close(); // We need to close the result here so callers can have a fully-processed result variable in the same scope as a new `executeQuery` call
      currentChunk_ = Chunk();
      currentChunkIterator_ = end(currentChunk_);
   }
}

inline ChunkIterator& Result::getCurrentChunkIterator() {
   getCurrentChunk();
   return currentChunkIterator_;
}

inline Chunk& Result::getCurrentChunk() {
   if (isOpen() && !currentChunk_) {
      fetchNextChunk();
   }
   return currentChunk_;
}

inline Result::Result(hyper_rowset_t* rowset, Connection& conn)
   : rowset_(rowset), conn_(&conn), currentChunkIterator_(end(currentChunk_)) {
   if (!internal::getAsyncResultCallback(conn)) {
      createSchema();
   }
}

inline Result::~Result() noexcept {
   close();
}

inline void Result::createSchema() {
   schema_.emplace();
   const hyper_table_definition_t* table_def = hyper_rowset_get_table_definition(rowset_);
   size_t columnCount = hyper_table_definition_column_count(table_def);
   for (hyper_field_index_t columnIndex = 0; columnIndex < columnCount; ++columnIndex) {
      auto tag = static_cast<TypeTag>(hyper_table_definition_column_type_tag(table_def, columnIndex));
      hyper_type_modifier_t modifier = hyper_table_definition_column_type_modifier(table_def, columnIndex);
      uint32_t oid = hyper_table_definition_column_type_oid(table_def, columnIndex);
      const char* collationPtr = hyper_table_definition_column_collation(table_def, columnIndex);
      std::string collation = collationPtr ? std::string(collationPtr) : std::string();
      std::string columnName = hyper_table_definition_column_name(table_def, columnIndex);
      ResultSchema::Column c(std::move(columnName), {tag, oid, modifier});
      schema_->addColumn(std::move(c));
   }
}

inline Result::Result(Result&& other) noexcept
   : rowset_(internal::exchange(other.rowset_, nullptr)), conn_(other.conn_), schema_(std::move(other.schema_)), currentChunkIterator_(std::move(other.currentChunkIterator_)) {
}

inline Result& Result::operator=(Result&& other) noexcept {
   if (&other != this) {
      close();

      rowset_ = other.rowset_;
      other.rowset_ = nullptr;
      schema_ = std::move(other.schema_);
      conn_ = other.conn_;
   }

   return *this;
}

inline bool Result::isOpen() const noexcept {
   return (rowset_ != nullptr);
}

inline optional<size_t> Result::getAffectedRowCount() const {
   HYPER_PRECONDITION_MSG(isOpen(), "Result is closed");
   int64_t rowCount = hyper_rowset_get_affected_row_count(rowset_);
   if (rowCount >= 0) {
      return static_cast<size_t>(rowCount);
   }
   assert(rowCount == -1);
   return {};
}

inline Connection& Result::getConnection() {
   HYPER_PRECONDITION_MSG(isOpen(), "Result is closed");
   return *conn_;
}

/** Constructor */
inline Chunk::Chunk(hyper_rowset_chunk_t* chunk, const Result& result) noexcept
   : chunk_(chunk), result_(&result) {
   rowCount_ = hyper_rowset_chunk_row_count(chunk);
}

/** Destructor */
inline Chunk::~Chunk() noexcept {
   if (chunk_) {
      hyper_destroy_rowset_chunk(chunk_);
   }
}

/** Move constructor */
inline Chunk::Chunk(Chunk&& other) noexcept
   : chunk_(internal::exchange(other.chunk_, nullptr)), values_(internal::exchange(other.values_, nullptr)), valuesSizes_(internal::exchange(other.valuesSizes_, nullptr)), chunkSize_(other.chunkSize_), result_(other.result_), rowCount_(other.rowCount_) {
}

/** Move assignment */
inline Chunk& Chunk::operator=(Chunk&& other) noexcept {
   if (&other != this) {
      if (chunk_) {
         hyper_destroy_rowset_chunk(chunk_);
      }
      chunk_ = internal::exchange(other.chunk_, nullptr);
      values_ = internal::exchange(other.values_, nullptr);
      valuesSizes_ = internal::exchange(other.valuesSizes_, nullptr);
      chunkSize_ = other.chunkSize_;
      result_ = other.result_;
      rowCount_ = other.rowCount_;
   }
   return *this;
}

inline const uint8_t* const* Chunk::getValues() const noexcept {
   if (!values_) {
      size_t columnCount;
      size_t rowCount;
      hyper_rowset_chunk_field_values(chunk_, &columnCount, &rowCount, &values_, &valuesSizes_);
   }
   return values_;
}

inline const size_t* Chunk::getValuesSizes() const noexcept {
   if (!valuesSizes_) {
      size_t columnCount;
      size_t rowCount;
      hyper_rowset_chunk_field_values(chunk_, &columnCount, &rowCount, &values_, &valuesSizes_);
   }
   return valuesSizes_;
}

inline Row Chunk::getRowAt(hyper_row_index_t chunkRowIndex) const noexcept {
   HYPER_PRECONDITION_MSG(isOpen(), "Result is closed");
   return Row(*this, chunkRowIndex);
}

inline Row::Row(const Chunk& chunk, hyper_row_index_t chunkRowIndex) noexcept
   : chunk_(chunk), chunkRowIndex_(chunkRowIndex) {
}

template <>
inline Value Row::get(hyper_field_index_t columnIndex) const {
   hyper_value_t value_t = {chunk_.get().getValues()[chunkRowIndex_ * getSchema().getColumnCount() + columnIndex], chunk_.get().valuesSizes_[chunkRowIndex_ * getSchema().getColumnCount() + columnIndex]};
   const ResultSchema::Column& column = getSchema().getColumn(columnIndex);
   return {value_t, column.getType(), column.getName().getUnescaped()};
}

template <typename ReturnType>
inline ReturnType Row::get(hyper_field_index_t columnIndex) const {
   return get(columnIndex).get<ReturnType>();
}

inline const ResultSchema& Row::getSchema() const noexcept {
   return chunk_.get().result_->getSchema();
}

inline ChunkIterator::reference ChunkIterator::operator*() const noexcept {
   currentRow_ = chunk_.get().getRowAt(rowIndex_);
   return currentRow_;
}

inline ChunkIterator& ChunkIterator::operator++() noexcept {
   ++rowIndex_;
   return *this;
}

inline ChunkIterator ChunkIterator::operator++(int) noexcept {
   ChunkIterator it(*this);
   ++*this;
   return it;
}

inline ColumnIterator::reference ColumnIterator::operator*() const noexcept {
   currentValue_ = row_.get().get(columnIndex_);
   return currentValue_;
}

inline ColumnIterator& ColumnIterator::operator++() {
   ++columnIndex_;
   return *this;
}

inline ColumnIterator ColumnIterator::operator++(int) {
   ColumnIterator it(*this);
   ++*this;
   return it;
}

inline ChunkedResultIterator::ChunkedResultIterator(Result& result, IteratorBeginTag)
   : result_(result), isEndIterator_(false) {
   result.getCurrentChunk();
}

inline ChunkedResultIterator& ChunkedResultIterator::operator++() {
   result_.get().fetchNextChunk();
   return *this;
}

inline bool operator==(const ChunkedResultIterator& lhs, const ChunkedResultIterator& rhs) noexcept {
   hyperapi::Result& lRes = lhs.result_.get();
   hyperapi::Result& rRes = rhs.result_.get();
   if (&lRes == &rRes) {
      if (lhs.isEndIterator_ == rhs.isEndIterator_) {
         return true;
      } else if (!lRes.currentChunk_) {
         // The end iterator is equal to a depleted result's iterator.
         return true;
      }
   }
   return false;
}

inline ResultIterator::ResultIterator(Result& result, IteratorBeginTag)
   : result_(result), isEndIterator_(false) {
   result.getCurrentChunk();
}

inline ResultIterator& ResultIterator::operator++() {
   hyperapi::Result& res = result_.get();
   auto& it = ++res.getCurrentChunkIterator();
   if (it == end(it.chunk_)) {
      res.fetchNextChunk();
   }
   return *this;
}

inline bool operator==(const ResultIterator& lhs, const ResultIterator& rhs) noexcept {
   hyperapi::Result& lRes = lhs.result_.get();
   hyperapi::Result& rRes = rhs.result_.get();
   if (&lRes == &rRes) {
      if (lhs.isEndIterator_ == rhs.isEndIterator_) {
         return true;
      } else if (!lRes.currentChunk_) {
         // The end iterator is equal to a depleted result's iterator.
         return true;
      }
   }
   return false;
}
}

namespace std {
inline size_t hash<hyperapi::ByteSpan>::operator()(const hyperapi::ByteSpan& bs) const noexcept {
   size_t hash = 0;
   const uint8_t* bsData = bs.data;
   for (size_t index = 0; index < bs.size; ++index) {
      hash = 5 * hash + bsData[index];
   }
   return size_t(hash);
}
}
#endif
