 (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 the value
-  of `(callback <http-response>)` or `((or error-callback callback) <exception>)`
-  will be delivered. See also `request`."
-  [{:keys [client url method headers data timeout]
-    :or   {method :get client (get-default-client)}}
-   callback & [error-callback]]
-  (let [uri (URI. url)
+  "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 data
+    (.exec ^HttpClient client uri method headers body
            (or timeout -1) ; -1 for client default
            (RespListener.
             (reify IResponseHandler
               (onSuccess [this status headers body]
-                (deliver response
-                         (callback {:body body
-                                    :headers (normalize-headers headers true)
-                                    :status status})))
+                (let [resp {:body body
+                            :headers (normalize-headers headers true)
+                            :status status}]
+                  (deliver response resp)
+                  (callback resp)))
               (onThrowable [this t]
-                (deliver response
-                         (try ((or error-callback callback) (Exception. t))
-                              (catch Exception e e)))))))
+                (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 handler's return value will be delivered:
+  Returns a promise object to which the HTTP response or exception will be delivered:
 
      ;; Asynchronous
      (request {:url \"http://www.cnn.com/\"}

(ns me.shenfeng.http.client
  (:require [clojure.string :as str])
  (:import [me.shenfeng.http.client HttpClientConfig HttpClient
            IResponseHandler RespListener RespListener$IFilter]
           (java.net URI URLEncoder)
           me.shenfeng.http.HttpMethod))


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

(defmacro get
  ([url]
     `(request {:method :get :url ~url} resp# nil))
  ([url req]
     `(get ~url ~req resp# nil))
  ([url req resp & forms]
     `(let [req# (assoc ~req :method :get :url ~url)]
        (request req#  ~resp ~@forms))))

(defmacro post
  ([url]
     `(request {:method :post :url ~url} resp# nil))
  ([url req]
     `(post ~url ~req resp# nil))
  ([url req resp & forms]
     `(let [req# (merge {:method :post :url ~url} ~req)]
        (request req# ~resp ~@forms))))

(comment
  (let [a (post "http://127.0.0.1:9091" {:timeout 900
                                         :form-params {:param1 "abc" :param2 ["a" "b"]}
                                         :basic-auth ["user" "password"]}
                {:keys [status headers body] :as resp}
                (if status
                  (println status)
                  (do (.printStackTrace resp))))]
    (println @a)))
