;; Contract: 0x731a10897d267e19B34503aD902d0A29173Ba4B1

(ns stone.com
  (:require
   [wisp.runtime :refer [identity odd? even? dictionary? dictionary
                         keys vals key-values merge satisfies?
                         contains-vector? map-dictionary error?
                         string? number? date? boolean? re-pattern?
                         object? nil? true? false? re-find re-matches
                         re-pattern inc dec str char int subs and or
                         print to-string = max min fn? vector?]]
   [wisp.ast :refer [symbol symbol? keyword? keyword-name name
                     namespace gensym unquote? unquote-splicing?
                     quote? syntax-quote? quote-string pr-str
                     meta with-meta]]
   [wisp.sequence :refer [lazy-seq lazy-seq? list? list cons
                          sequential? reverse map filter reduce
                          count empty? first second third rest
                          last butlast take take-while drop conj
                          assoc concat seq seq? vec sort repeat
                          every? some partition interleave nth]]
   [wisp.string :refer [split split-lines join upper-case lower-case
                        capitalize pattern-escape replace-first replace
                        blank? reverse triml trimr trim]
                        :rename {reverse string-reverse}]
   [wisp.expander :refer [macroexpand]]
   [jsyaml]
   [Immutable :refer [List OrderedSet Map Seq]]
   [Handlebars :refer [compile]]
   ;;[Bluebird :refer [Promise]]
   [EthereumjsUtil :as Util]
   [TreeView]
   [_]
   [jQuery :as $]
   [sjcl]))

(def storage nil)
(def save-storage nil)
(def whisper-key nil)
(def topic-key nil)
(def web3 nil)
(def subscriptions {})
(def identity-messages nil)

(defn config
  [options]
  (console.log "setting options" options)
  (set! storage (:storage options))
  (set! save-storage (:saveStorage options))
  (set! web3 window.web3)
  (let [account-connection (or (:accountConnection storage) {:account (:account storage)})]
    (set! (:accountConnection storage) account-connection)
    (-> (config-whisper account-connection)
        (.then save-storage))))

(defn config-whisper
  [con]
  (-> (if (:topicKeyId con)
        (-> (web3.shh.getSymKey (:topicKeyId con))
            (.then (fn [id]
                     (console.log "Found topic key id, using it")
                     id))
            (.catch (fn []
                      (console.log "No topic key found for ID, adding key to the peer")
                      false)))
        (do
          (console.log "No saved topic key id")
          (Promise.resolve false)))
      (.then (fn [had-key] (if (not had-key) (create-whisper-keys con))))
      (.then (fn [] (subscribe-whisper con)))
      (.then (fn [id]
               (console.log "Subscribed" id)
               (set! topic-key (:topicKey con))
               (set! whisper-key (:whisperKey con))))
      (.catch (fn [err] (console.error err)))))

(defn disconnect
  [con]
  )

(defn create-whisper-keys
  [con]
  (let [topic-key (Util.bufferToHex (web3.utils.sha3 "written in stone" (:account con)))
        topic (subs topic-key 0 10)]
    (set! (:topicKey con) topic-key)
    (set! (:topic con) topic)
    (-> (web3.shh.addSymKey topic-key)
        (.then (fn [id] (set! (:topicKeyId con) id))))))

(defn subscribe-whisper
  [con handler]
  (Promise. (fn [resolve reject]
              (let [success true
                    sub (web3.shh.subscribe "messages"
                                            {:privateKeyID (:topicKeyId con)
                                             :decryptWith (:topicKeyId con)
                                             ;;:sig receiver
                                             ;;:allowP2P true
                                             :topics [(:topic con)]})]
                (setTimeout (fn []
                              (if success
                                (resolve (:id sub)))) 1000)
                (-> sub
                    (.on "data" handler)
                    (.on "error" (fn []
                                   (set! success false)
                                   (reject "Failed to subscribe"))))))))

;; writer uses a password to generate confirmation code and listens on topic computed from code
;; writer sends confirmation code to reader by other means (paste into messenger, SMS, etc.)
;; reader computes topic based on password and confirmation code and sends request, including public key
;; writer encrypts file code with public key and sends to reader

(defn open-send-topic
  [password file-code]
  (let [conf-code (subs (web3.utils.randomHex 4) 2)
        topic (compute-topic password conf-code)
        connection {:topic topic
                    :fileCode file-code}
        sub nil]
    (-> (create-whisper-keys connection)
        (.then (fn []
                 (set! (:subscription connection)
                       (subscribe-whisper con (fn [] (send-state-await-request connection))))
                 (-> sub
                     (.on "data" (receive-code-request connection data))))))))

(defn compute-topic
  [password conf-code]
  (if (not (== "0x" (subs conf-code 0 2)))
    (set! password (str "0x" conf-code)))
  (web3.utils.sha3 password conf-code))

(defn receive-code-request
  [connection data]
  (if (not (:received-request))
    (try
      (let [msg (JSON.parse data)]
        )
      (catch err
          ;; ignore bad messages in case someone else is using this topic
          (console.log "bad JSON message format" data)))))

(defn subscribe-identity
  [ident]
  (let [sub (web3.shh.subscribe "messages"
                                 {:privateKeyID (:id ident)
                                  :topics [(->topic (:publicKey ident))]}
                                 (fn [err data sub] (console.log "SUBSCRIPTION CALLBACK" err data sub)))]
    (set! (get subscriptions (:publicKey ident)) sub)
    (-> sub
        (.on "data" (fn [data]
                      (try
                        (let [msg (JSON.parse data)]
                          (console.log "got message" msg))
                        (catch err ;;ignore
                            ))))
        (.on "error" (fn [err] (console.log "error" err))))))

(defn ->topic
  [hex-str]
  (str "0x" (if (.startsWith hex-str "0x")
              (subs hex-str 2, 10)
              (subs hex-str 0 8))))

(defn unsubscribe-identity
  [ident]
  (if (get subscriptions (:publicKey ident))
    (do
      (.unsubscribe (get subscriptions (:publicKey ident)))
      (delete (get subscriptions (:publicKey ident))))))

(defn send-identity-message
  [signing-ident receiving-ident msg]
  )

(defn receive-identity-message
  [msg]
  (if (_.has identity-messages (:cmd msg))
    ((get identity-messages (:cmd msg)) msg)))

(defn send-online-message
  [from to]
  (let [post (parity.api.shh.post {:pubKey (:publicKey to)
                                   :topics [(->topic (:publicKey to))]
                                   ;;:sig (:id from)
                                   :payload (web3.utils.asciiToHex (JSON.stringify {:cmd "online"
                                                                                    :nonce (web3.utils.randomHex 8)}))
                                   :priority 1000 ;; in parity, this is milliseconds spent computing PoW
                                   :ttl 10
                                   ;;:powTime 15
                                   ;;:powTarget 2.5
                                   })]
      (console.log "POST" post)))

(defn send-connect-message
  [from to]
  (console.log "send connected" from to)
  (let [post (parity.api.shh.post {:pubKey (:publicKey to)
                                   :topics [(->topic (:publicKey to))]
                                   ;;:sig (:id from)
                                   :payload (web3.utils.asciiToHex (JSON.stringify {:cmd "connect"
                                                                                    :nonce (web3.utils.randomHex 8)}))
                                   :priority 1000 ;; in parity, this is milliseconds spent computing PoW
                                   :ttl 5
                                   ;;:powTime 15
                                   ;;:powTarget 2.5
                                   })]
      (console.log "POST" post)))

(defn receive-online-message
  [msg]
  (console.log "RECEIVED ONLINE MESSAGE" msg))

(defn receive-connnect-message
  [msg]
  (console.log "RECEIVED CONNECT MESSAGE" msg))

(set! identity-messages {:online receive-online-message
                         :connect receive-connnect-message})

(defn display-error
  [err]
  (alert err))
