#pragma once

#include "storage/single_node_ha/edge.hpp"
#include "storage/single_node_ha/record_accessor.hpp"

// forward declaring the VertexAccessor because it's returned
// by some functions
class VertexAccessor;

/**
 * Provides ways for the client programmer (i.e. code generated by the compiler)
 * to interact with an Edge.
 *
 * Note that EdgeAccessors do not necessary read versioned (MVCC) data. This is
 * possible because edge endpoints (from and to), as well as the edge type, are
 * all immutable. These are the most often used aspects of an edge, and are
 * stored also in the vertex endpoints of the edge. Using them when creating an
 * EdgeAccessor means that data does not have to be read from a random memory
 * location, which is often a performance bottleneck in traversals.
 */
class EdgeAccessor final : public RecordAccessor<Edge> {
 public:
  /** Constructor that reads data from the random memory location (lower
   * performance, see class docs). */
  EdgeAccessor(mvcc::VersionList<Edge> *address,
               database::GraphDbAccessor &db_accessor);

  /**
   * Constructor that does NOT read data from the random memory location
   * (better performance, see class docs).
   */
  EdgeAccessor(mvcc::VersionList<Edge> *address,
               database::GraphDbAccessor &db_accessor,
               mvcc::VersionList<Vertex> *from, mvcc::VersionList<Vertex> *to,
               storage::EdgeType edge_type);

  storage::EdgeType EdgeType() const;

  /** Returns an accessor to the originating Vertex of this edge. */
  VertexAccessor from() const;

  /** Returns the address of the originating Vertex of this edge. */
  auto from_addr() const { return from_; }

  /** Checks if the given vertex is the source of this edge, without
   * creating an additional accessor to perform the check. */
  bool from_is(const VertexAccessor &v) const;

  /** Returns an accessor to the destination Vertex of this edge. */
  VertexAccessor to() const;

  /** Returns the address of the destination Vertex of this edge. */
  auto to_addr() const { return to_; }

  /** Checks if the given vertex is the destination of this edge, without
   * creating an additional accessor to perform the check. */
  bool to_is(const VertexAccessor &v) const;

  /** Returns true if this edge is a cycle (start and end node are
   * the same. */
  bool is_cycle() const;

 private:
  mvcc::VersionList<Vertex> *from_;
  mvcc::VersionList<Vertex> *to_;
  storage::EdgeType edge_type_;
};

std::ostream &operator<<(std::ostream &, const EdgeAccessor &);

// hash function for the edge accessor
namespace std {
template <>
struct hash<EdgeAccessor> {
  size_t operator()(const EdgeAccessor &e) const { return e.gid(); };
};
}  // namespace std
