001  (ns cmp.exchange
002    ^{:author "wactbprot"
003      :doc "Handles the access to the exchange interface."}
004    (:require [com.brunobonacci.mulog :as mu]
005              [clojure.string         :as string]
006              [cmp.st-mem             :as st]
007              [cmp.st-utils           :as stu]
008              [cmp.utils              :as u]))
009  
010  (defn exch-key
011    "Returns the base key for the exchange path.
012  
013    ```clojure
014    (exch-key  \"foo\" \"bar.baz\")
015    ;; \"foo@exchange@bar\"
016    (exch-key \"foo\" \"bar\")
017    ;; \"foo@exchange@bar\"
018    ```
019    "
020    [mp-id s]
021    {:pre [(not (nil? s))]}
022    (stu/exch-key mp-id (first (string/split s #"\."))))
023  
024  (defn key->second-kw
025    "Returns the keyword or nil.
026  
027    ```clojure
028    (key->second-kw \"foo\" )
029    ;; nil
030    (key->second-kw \"foo.bar\" )
031    ;; :bar
032    ```"  
033    [s]
034    (when-let [x (second (string/split s #"\."))] (keyword x)))
035  
036  (defn key->first-kw
037    "Returns the keyword or nil.
038  
039    ```clojure
040    (key->second-kw \"foo\" )
041    ;; nil
042    (key->second-kw \"foo.bar\" )
043    ;; :bar
044    ```"  
045    [s]
046    (when-let [x (first (string/split s #"\."))] (keyword x)))
047  
048  (defn read!
049    "Returns e.g the *compare value* belonging to a `mp-id` and an
050    ExchangePath `k`. First try is to simply request to
051    `<mp-id>@exchange@<k>`. If this is `nil` Second try is to get the
052    *keyword* `kw` from `k` if `k` looks like this: `aaa.bbb`. If `kw`
053    is not `nil` it is used to extract the related value.
054  
055    ```clojure
056    (read! \"ref\" \"A.Unit\")
057    ;; \"Pa\"
058    ;; or:
059    (read! \"devhub\" \"Vraw_block1\")
060    ;; [1 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0]
061    ```"
062    [mp-id p]
063    (if-let [val-p (st/key->val (stu/exch-key mp-id p))]
064      val-p
065      (let [val-k (st/key->val (exch-key mp-id p))]
066        (if-let [kw (key->second-kw p)]
067          (kw val-k)
068          val-k))))
069  
070  (defn from!
071    "Builds a map by replacing the values of the input map `m`.
072    The replacements are gathered from the `exchange` interface with the
073    keys: `<mp-id>@exchange@<input-map-value>`
074  
075    The example key: `ref@exchange@Vraw_block1` with the example value:
076    `[1 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0]` should return:
077    `{:%stateblock1 [1 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0]}`
078  
079    
080    ```clojure
081    (from! \"ref\" {:%stateblock1 \"Vraw_block1\"})
082    ;; =>
083    ;; {:%stateblock1 [1 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0]}
084    ```
085    
086    **Todo**
087  
088    Check for non trivial `<input-map-value>` like
089    `{:%aaa \"bbb.ccc\"}`
090    "
091    [mp-id m]
092    (when (and (string? mp-id) (map? m))
093      (u/apply-to-map-values
094       (fn [v] (read! mp-id v))
095       m)))
096  
097  (defn enclose-map
098    "Encloses the given map `m` with respect to the path `p`.
099  
100    Example:
101    ```clojure
102    (enclose-map {:gg \"ff\"} \"mm.ll\")
103    ;; gives:
104    ;; {:mm {:ll {:gg \"ff\"}}}
105  
106    (enclose-map {:gg \"ff\"} \"mm\")
107    ;; gives:
108    ;; {:mm {:gg \"ff\"}}
109  
110    (enclose-map {:gg \"ff\"} nil)
111    ;; gives:
112    ;; {:gg \"ff\"}
113    ```"
114    [m p]
115    (if-not p
116      m
117      (let [a (key->first-kw p)]
118        (if-let [b (key->second-kw p)]
119          {a {b m}}
120          {a m}))))
121  
122  (defn to!
123    "Writes `m` to the exchange interface.  The first level keys of `m`
124    are used for the key. The return value of the storing
125    process (e.g. \"OK\") is converted to a `keyword`. After storing the
126    amounts of `:OK` is compared to `(count m)`.
127  
128    Example:
129    ```clojure
130    {:A 1
131     :B 2}
132    ```
133    Stores the value `1` under the key
134    `<mp-id>@exchange@A` and a `2` under
135    `<mp-id>@exchange@B`.
136  
137    If a path `p` is given the `enclose-map` function
138    respects `p`.
139    "
140    ([mp-id m p]
141     (to! mp-id (enclose-map m p)))
142    ([mp-id m]
143     (if (string? mp-id)
144      (if (map? m)
145        (let [res     (map (fn [[k new-val]]
146                             (let [exch-key  (stu/exch-key mp-id (name k))
147                                   curr-val  (st/key->val exch-key)
148                                   both-map? (and (map? new-val) (map? curr-val))]
149                               (st/set-val! exch-key (if both-map? (merge curr-val new-val) new-val))))
150                           m)
151              res-kw  (map keyword res)
152              res-ok? (= (count m) (:OK (frequencies res-kw)))]
153          (if res-ok? {:ok true} {:error "not all write processes succeed"}))
154        {:error "second arg mus be a map"})
155      {:error "mp-id must be a string"})))
156  
157  (defn ok?
158    "Checks a certain exchange endpoint to evaluate
159    to true"
160    [mp-id k]
161    (contains? u/ok-set (read! mp-id k)))
162  
163  (defn exists? [mp-id k] (some? (read! mp-id k)))
164  
165  (defn stop-if
166    "Checks if the exchange path given with `:MpName` and `:StopIf`
167    evaluates to true."
168    [{mp-id :MpName k :StopIf}]
169    (if k
170      (ok? mp-id k)
171      true))
172  
173  (defn run-if
174    "Checks if the exchange path given with `:MpName` and `:RunIf`
175    evaluates to true."
176    [{mp-id :MpName k :RunIf}]
177    (if k
178      (ok? mp-id k)
179      true))
180  
181  (defn only-if-not
182    "Runs the task `only-if-not` the exchange path given with `:MpName`
183    and `:OnlyIfNot` evaluates to true."
184    [{mp-id :MpName k :OnlyIfNot}]
185    (cond
186      (nil? k)                true
187      (not (exists? mp-id k)) false
188      (not (ok? mp-id k))     true))