(ns jepsen.antietcd.wr
  "Tests transactional writes and reads to registers using Elle."
  (:refer-clojure :exclude [read])
  (:require
    [clojure.tools.logging :refer [info warn]]
    [jepsen
      [client :as client]
      [checker :as checker]
      [generator :as gen]
      [store :as store]
      [util :as util]
    ]
    [jepsen.tests.cycle :as cycle]
    [jepsen.tests.cycle.wr :as wr]
    [jepsen.antietcd.client :as anticli]
  )
)

(defn encode-put
  "Takes a test, an Op, and a value to write, and transforms the value: in
  debug mode, adds extra debugging information."
  [test op value]
  (if (:debug test)
    {
      :time          (str (util/local-time))
      :invoke-index  (:index op)
      :dir           (.getName (store/path test))
      :txn           (:value op)
      :value         value
    }
    value
  )
)

(defn decode-get
  "Takes a test and a value read for a key (which may be a bare value or a map
  with debug information, and returns the bare value."
  [test value]
  (if (:debug test)
    (:value value)
    value
  )
)

(defn antietcd-txn
  "We take a test and an Op, and convert it to a series of antietcd txn ops
  like {:request_put}."
  [test op]
  (mapv
    (fn [[f k v]]
      (case f
        :r {:request_range {:key k}}
        :w {:request_put {:key k :value (encode-put test op v)}}
      )
    )
    (:value op)
  )
)

(defrecord TxnClient [url]
  client/Client
  (open! [this test node] (assoc this :url (anticli/node-url node)))

  (setup! [_ test])

  (invoke! [this test op]
    (when
      (:debug test)
      (gen/log (str "=================================================\nOp: " (pr-str op)))
    )

    (let
      [
        txn (:value op)
        res (anticli/throw #(anticli/antietcd_request (str (:url this) "/v3/kv/txn") {:success (antietcd-txn test op)}))

        ; Stitch reads back in to elle txn
        txn' (
          mapv (fn [[f k v :as mop] r]
            (case f
              :w mop
              :r [f k (decode-get test (-> r :response_range :kvs first :value))]
            )
          )
          txn
          (:responses (:body res))
        )
        op' (if (:debug test)
          (assoc op :debug {:txn-res (:responses (:body res))})
          op
        )
        op' (if (= (:type res) :ok)
          (assoc op' :type :ok, :value txn')
          (assoc op' :type :fail, :error (:error res))
        )
      ]
      op'
    )
  )

  (teardown! [_ test])

  (close! [_ test])
)

(defn workload
  "A generator, client, and checker for an Elle register test."
  [opts]
  (assoc
    (wr/test {
      :key-count          3
      :max-txn-length     4
      :consistency-models [(if (get opts :stale-read false) :serializable :strict-serializable)]
      :wfr-keys           true
      ; Expensive
      ;:linearizable-keys? true
    })
    :client (TxnClient. nil)
  )
)
