             [clojure-fabric.request :as request]
             [clojure-fabric.response :as response]
             [clojure.core.cache :as cache])
-  (:import [org.hyperledger.fabric.sdk User Enrollment]
+  (:import [org.hyperledger.fabric.sdk User Enrollment Peer Orderer]
            [org.bouncycastle.jcajce.provider.asymmetric.ec BCECPrivateKey]))
+
+(defprotocol Marshall
+  (marshall [obj]))
+
+;;; FIXME: call marshal from generated code when return type matches
+(extend-protocol Marshall
+  java.util.Collection
+  (marshall [obj]
+    (if (instance? clojure.lang.IPersistentSet obj)
+      obj
+      (seq obj))))
 
 (defonce client-lru-cache
   ;; FIXME: magic number
   (atom (cache/lru-cache-factory {} :threshold 64)))
 
-(defn make-client [msp-id name priv-key cert & [{:keys [roles account affiliation]}]]
+(defn make-client [msp-id name priv-key cert {:keys [roles account affiliation]}]
   (let [new-client (client/create-new-instance)]
     (client/set-crypto-suite new-client (crypto/get-crypto-suite))
     (client/set-user-context new-client 

                                      (.getCert ^Enrollment enrollment))]
    (swap! client-lru-cache cache/evict cache-key)))

(defn get-or-make-client [msp-id name priv-key cert & opts]
  (let [cache-key (priv-key+cert->hash priv-key cert)
        lru-cache (swap! client-lru-cache cache/hit cache-key)]
    (if-let [existing-client (cache/lookup lru-cache cache-key)]

(defn get-or-make-channel [client channel-id]
  (or (client/get-channel client channel-id) (client/new-channel client channel-id)))

(defprotocol ChannelEndOpts
  (new-channel-end [channel-end client])
  (get-channel-ends [channel-end client]))

;; Channel ends are: Peers(Endorsers,...), Orderers
(defrecord OrdererOpts [name grpc-url properties]
  ChannelEndOpts
    (new-channel-end [{:keys [name grpc-url properties]} client]
    (client/new-orderer client name grpc-url properties))
  (get-channel-ends [this channel]
    (channel/get-orderers channel)))

(defrecord PeerOpts [name grpc-url properties]
  ChannelEndOpts
  (new-channel-end [{:keys [name grpc-url properties]} client]
    (client/new-orderer client name grpc-url properties))
  (get-channel-ends [this channel]
    (channel/get-peers channel)))

(defprotocol ChannelEnd
  (add-channel-end-to-channel [channel-end channel]))
  
(extend-protocol ChannelEnd
  Orderer
  (add-channel-end-to-channel [this channel]
    (channel/add-orderer channel this)))

(extend-protocol ChannelEnd
  Peer
  (add-channel-end-to-channel [this channel]
    (channel/add-peer channel this)))

(defn add-channel-end
  ([client channel-id channel-end-opts]
   ;; Add a {orderer peer} if there is no {orderer peer}
   (add-channel-end client channel-id channel-end-opts false))
  ([client channel-id channel-end-opts force?]
   ;; To add an additional {orderer peer} set 'force?' true
   (let [channel (get-or-make-channel client channel-id)]
     (when (or force?
               (empty? (get-channel-ends channel-end-opts (get-or-make-channel client channel-id))))
       (add-channel-end-to-channel (new-channel-end channel-end-opts client) channel)))))


;;;
;;; Peers
;;;     Endorser
