;;; This code is adapted from Ring (http://github.com/mmcgrana/ring) and patches
;;; by Adam Blinkinsop
;;; (https://github.com/coonsta/compojure/commit/dd36e217de2ea968eca1953a0b9d5a81b54d0d9c).
;;;
;;; Required change from Ring: use of the streaming multipart API instead of the
;;; temporary file API from Apache Commons FileUpload.


(ns appengine-magic.multipart-params
  (:import [org.apache.commons.fileupload.servlet ServletFileUpload]
           [org.apache.commons.fileupload.util Streams]
           org.apache.commons.io.IOUtils))


(defn- multipart-form?
  "Does a request have a multipart form?"
  [request]
  (ServletFileUpload/isMultipartContent (:request request)))


(defn- itemiterator-to-seq
  "Converts an ItemIterator into a sequence."
  [it]
  (lazy-seq (when (.hasNext it)
              (cons (.next it) (itemiterator-to-seq it)))))


(defn- field-seq
  "Map field names to values, which will either be a simple string or map.
   Multipart values will be maps with content-type, name (original filename),
   and stream (an open input stream object)."
  [request encoding]
  (into {}
        (map (fn [i]
               [(.getFieldName i)
                (if (.isFormField i)
                    (Streams/asString (.openStream i) encoding)
                    (let [upload-bytes (IOUtils/toByteArray (.openStream i))
                          size (alength upload-bytes)
                          upload-bytes (if (zero? size) nil upload-bytes)]
                      {:content-type (.getContentType i)
                       :filename (.getName i)
                       :size size
                       :bytes upload-bytes}))])
             (itemiterator-to-seq (.getItemIterator (ServletFileUpload.)
                                                    (:request request))))))


(defn wrap-multipart-params
  "Works just like ring.middleware.multipart-params/wrap-multipart-params:
   adds :multipart-params and :params to the request map (the latter requires
   ring.middleware.params/wrap-params). Takes a map with an optional :encoding
   map."
  [handler & [opts]]
  (fn [request]
    (let [encoding (or (:encoding opts)
                       (:character-encoding request)
                       "UTF-8")
          params (if (multipart-form? request)
                     (field-seq request encoding)
                     {})
          request (merge-with merge request
                              {:multipart-params params}
                              {:params params})]
      (handler request))))