#>cpp
#pragma once

#include "communication/bolt/v1/decoder/decoder.hpp"
#include "communication/bolt/v1/encoder/base_encoder.hpp"
#include "durability/hashed_file_reader.hpp"
#include "durability/hashed_file_writer.hpp"
#include "storage/single_node_ha/mvcc/version_list.hpp"
#include "storage/common/types/property_value.hpp"
#include "storage/common/types/types.hpp"
#include "utils/typeinfo.hpp"

class Vertex;
class Edge;
cpp<#

(lcp:namespace database)

#>cpp
class GraphDbAccessor;
cpp<#

(lcp:define-struct state-delta ()
  (
   ;; Members valid for every delta.
   (type "Type")
   (transaction-id "::tx::TransactionId")
   ;; Members valid only for some deltas, see StateDelta::Type comments above.
   (vertex-id "::storage::Gid")
   (edge-id "::storage::Gid")
   (vertex-from-id "::storage::Gid")
   (vertex-to-id "::storage::Gid")
   (edge-type "::storage::EdgeType")
   (edge-type-name "std::string")
   (property "::storage::Property")
   (property-name "std::string")
   (properties "std::vector<storage::Property>")
   (property-names "std::vector<std::string>")
   (value "PropertyValue")
   (label "::storage::Label")
   (label-name "std::string")
   (check-empty :bool))
  (:documentation
   "Describes single change to the database state. Used for state communication
over network in HA.

Labels, Properties and EdgeTypes are stored both as values (integers) and
strings (their names). The values are used when applying deltas in a running
database. Names are used when recovering the database as it's not guaranteed
that after recovery the old name<->value mapping will be preserved.

TODO: ensure the mapping is preserved after recovery and don't save strings
in StateDeltas.")
  (:public
   (lcp:define-enum type
       (transaction-begin
        transaction-commit
        transaction-abort
        create-vertex ;; vertex_id
        create-edge   ;; edge_id, from_vertex_id, to_vertex_id, edge_type, edge_type_name
        set-property-vertex ;; vertex_id, property, property_name, property_value
        set-property-edge   ;; edge_id, property, property_name, property_value
        ;; remove property is done by setting a PropertyValue to Null
        add-label     ;; vertex_id, label, label_name
        remove-label  ;; vertex_id, label, label_name
        remove-vertex ;; vertex_id, check_empty
        remove-edge   ;; edge_id
        build-index   ;; label, label_name, property, property_name
        drop-index    ;; label, label_name, property, property_name
        no-op         ;; no-op state delta required by Raft protocol
        build-unique_constraint ;; label, label_name, properties, property_names
        drop-unique_constraint ;; label, label_name, properties, property_names
        )
     (:documentation
      "Defines StateDelta type. For each type the comment indicates which values
need to be stored. All deltas have the transaction_id member, so that's
omitted in the comment.")
     (:serialize))
     #>cpp
     StateDelta() = default;
     StateDelta(const enum Type &type, tx::TransactionId tx_id)
         : type(type), transaction_id(tx_id) {}

     /** Attempts to decode a StateDelta from the given decoder. Returns the
      * decoded value if successful, otherwise returns nullopt. */
     static std::optional<StateDelta> Decode(
         HashedFileReader &reader,
         communication::bolt::Decoder<HashedFileReader> &decoder);

     /** Encodes the delta using primitive encoder, and writes out the new hash
      * with delta to the writer */
     void Encode(
         HashedFileWriter &writer,
         communication::bolt::BaseEncoder<HashedFileWriter> &encoder) const;

     static StateDelta TxBegin(tx::TransactionId tx_id);
     static StateDelta TxCommit(tx::TransactionId tx_id);
     static StateDelta TxAbort(tx::TransactionId tx_id);
     static StateDelta CreateVertex(tx::TransactionId tx_id,
                                    storage::Gid vertex_id);
     static StateDelta CreateEdge(tx::TransactionId tx_id, storage::Gid edge_id,
                                  storage::Gid vertex_from_id,
                                  storage::Gid vertex_to_id,
                                  storage::EdgeType edge_type,
                                  const std::string &edge_type_name);
     static StateDelta PropsSetVertex(tx::TransactionId tx_id,
                                      storage::Gid vertex_id,
                                      storage::Property property,
                                      const std::string &property_name,
                                      const PropertyValue &value);
     static StateDelta PropsSetEdge(tx::TransactionId tx_id, storage::Gid edge_id,
                                    storage::Property property,
                                    const std::string &property_name,
                                    const PropertyValue &value);
     static StateDelta AddLabel(tx::TransactionId tx_id, storage::Gid vertex_id,
                                storage::Label label,
                                const std::string &label_name);
     static StateDelta RemoveLabel(tx::TransactionId tx_id, storage::Gid vertex_id,
                                   storage::Label label,
                                   const std::string &label_name);
     static StateDelta RemoveVertex(tx::TransactionId tx_id, storage::Gid vertex_id,
                                    bool check_empty);
     static StateDelta RemoveEdge(tx::TransactionId tx_id, storage::Gid edge_id);
     static StateDelta BuildIndex(tx::TransactionId tx_id, storage::Label label,
                                  const std::string &label_name,
                                  storage::Property property,
                                  const std::string &property_name);
     static StateDelta DropIndex(tx::TransactionId tx_id, storage::Label label,
                                  const std::string &label_name,
                                  storage::Property property,
                                  const std::string &property_name);
     static StateDelta NoOp(tx::TransactionId tx_id);
     static StateDelta BuildUniqueConstraint(
                                  tx::TransactionId tx_id, storage::Label label,
                                  const std::string &label_name,
                                  const std::vector<storage::Property> &properties,
                                  const std::vector<std::string> &property_names);
     static StateDelta DropUniqueConstraint(
                                  tx::TransactionId tx_id, storage::Label label,
                                  const std::string &label_name,
                                  const std::vector<storage::Property> &property,
                                  const std::vector<std::string> &property_names);

     /// Applies CRUD delta to database accessor. Fails on other types of deltas
     void Apply(GraphDbAccessor &dba) const;
     cpp<#)
  (:serialize (:slk)))

(lcp:pop-namespace) ;; database
