 (ns me.shenfeng.http.client
+  (:refer-clojure :exclude [get])
   (:require [clojure.string :as str])
   (:import [me.shenfeng.http.client HttpClientConfig HttpClient
             IResponseHandler RespListener RespListener$IFilter]
+           javax.xml.bind.DatatypeConverter
            (java.net URI URLEncoder)
            me.shenfeng.http.HttpMethod))
 

  [options resp & handler]
  `(request* ~options (fn [~resp] ~@handler)))

(comment
  @(request {:url "http://www.cnn.com/"}
            {:keys [status body headers] :as resp}
            (if status ; nil on exceptions
              (do (println "Body: " body) body)
              (do (println "Exceptiond: " resp) resp))))

(comment ; TODO

  (defn- url-encode
    "Returns an UTF-8 URL encoded version of the given string."
    [unencoded]
    (URLEncoder/encode unencoded "UTF-8"))

  (defn- generate-query-string
    "Params: {:param1 \"value1\" :params2 \"value2\" :param3 [\"value3\" \"value4\"}

    Return Http Form encoded bytes array. used as HTTP body. Need to set

    Content-Type: application/x-www-form-urlencoded

    in the Request's Headers for server to properly understand it"
    [params]
    (.getBytes (str/join "&"
                         (mapcat (fn [[k v]]
                                   (if (sequential? v)
                                     (map #(str (url-encode (name %1))
                                                "="
                                                (url-encode (str %2)))
                                          (repeat k) v)
                                     [(str (url-encode (name k))
                                           "="
                                           (url-encode (str v)))]))
                                 params))
               "UTF-8")))
(defn get-default-client "Returns default HTTP client, initializing as neccesary."
  [] (if-let [c @default-client] c (reset! default-client (init-client))))

(defn- coerce-req [{:keys [headers client method body form-params timeout basic-auth]
                    :as req}]
  (merge req  ;; TODO cookie
         {:headers
          (merge headers
                 (when form-params
                   {"Content-Type" "application/x-www-form-urlencoded"})
                 (when basic-auth
                   {"Authorization" (basic-auth-value basic-auth)}))}
         {:body (or (when form-params
                      (form-body form-params)) body)}
         {:client (or client (get-default-client))
          :method (or method :get)}))

(defn request*
  "Issues an async HTTP request and returns a promise object to which
   <http-response> or <exception> will be delivered
  `(callback <http-response>)` or `((or error-callback callback) <exception>)`
   See also `request`."
  [req callback & [error-callback]]
  (let [{:keys [client url method headers body timeout]} (coerce-req req)
        uri (URI. url)
        method (case method
                 :get  HttpMethod/GET
                 :delete HttpMethod/DELETE
                 :post HttpMethod/POST
                 :put  HttpMethod/PUT)
        response (promise)]
    (.exec ^HttpClient client uri method headers body
           (or timeout -1) ; -1 for client default
           (RespListener.
            (reify IResponseHandler
              (onSuccess [this status headers body]
                (let [resp {:body body
                            :headers (normalize-headers headers true)
                            :status status}]
                  (deliver response resp)
                  (callback resp)))
              (onThrowable [this t]
                (deliver response t)
                ((or error-callback callback) t)))))
    response))

(defmacro request
  "Issues an asynchronous HTTP request, binds the HTTP response or exception to
  `resp`, then executes the given handler body in the context of that binding.
  Returns a promise object to which the HTTP response or exception will be delivered:

     ;; Asynchronous
     (request {:url \"http://www.cnn.com/\"}
