 )
   #_
   (:import clojure.lang.Reflector
-
-           [com.amazonaws
-            AmazonServiceException
-            ClientConfiguration]
-           [com.amazonaws.auth
-            AWSCredentials
-            AWSCredentialsProvider
-            AWSStaticCredentialsProvider
-            BasicAWSCredentials
-            BasicSessionCredentials
-            DefaultAWSCredentialsProviderChain]
-           com.amazonaws.auth.profile.ProfileCredentialsProvider
-
-           com.amazonaws.client.builder.AwsSyncClientBuilder
-           com.amazonaws.client.builder.AwsClientBuilder$EndpointConfiguration
-           
-           
-           
            java.lang.reflect.InvocationTargetException
-           
-           
-           
-           java.math.BigDecimal
-           java.math.BigInteger
            
            java.text.ParsePosition
            java.text.SimpleDateFormat

           java.lang.reflect.Modifier
           java.lang.reflect.Parameter
           java.nio.ByteBuffer
           java.io.File
           java.util.Date
           ;;org.joda.time.DateTime

   in the DynamoDBV2Client.
   http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/model/DeleteItemRequest.html#setKey(java.util.Map.Entry, java.util.Map.Entry)
   http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/model/GetItemRequest.html#setKey(java.util.Map.Entry, java.util.Map.Entry)"
   [method name value]
   (let [args (.getParameterTypes method)]
     (and (= name (normalized-name (.getName method)))
          (if (empty? value)
            (= 0 (count args))
            (and (< 0 (count args))
                 (or (= (count args) (count (flatten value)))
                     (and (coll? value)
                          (= 1 (count args))
                          (or (contains? @coercions (first args))
                              (.isArray (first args))
                              (and (.getPackage (first args))
                                   (.startsWith
                                    (.getName (.getPackage (first args)))
                                    "java.util")))))
                 (not (and (= 2 (count args))
                           (every? (partial = java.util.Map$Entry)
                                   args))))))))

 (defn- accessor-methods
   [class-methods name value]
   (reduce
    #(if (matches? %2 name value)
       (conj %1 %2)
       %1)
    []
    class-methods))

 (defn- find-methods
   [pojo k & v]
   (-> (.getClass pojo)
       (.getMethods)
       (accessor-methods
        (.toLowerCase (keyword->camel k))
        v)))

 (defn to-java-coll
   "Need this only because S3 methods actually try to
   mutate (e.g. sort) collections passed to them."
   [col]
   (cond
     (map? col)
     (doto
         (java.util.HashMap.)
         (.putAll col))
     (set? col)
     (java.util.HashSet. col)
     (or (list? col) (vector? col))
     (java.util.ArrayList. col)))

 (defn- invoke
   [pojo method v]
   (.invoke method pojo
            (into-array
             ;; misbehaving S3Client mutates the coll
             (if (and (coll? v)
                      (not (nil? *client-class*))
                      (= "AmazonS3Client" (.getSimpleName *client-class*)))
               (if (and (> (count (.getParameterTypes method)) 1)
                        (sequential? v))
                 (to-java-coll v)
                 [(to-java-coll v)])
               [v])))
   true)

 (defn kw->str [k]
   (if (keyword? k) (name k) k))

 (defn- populate
   [types key props]
   (let [type (-> types key last)]
     (assert type (str "Bad data type - there is no " key " in " types))
     (if (contains? @coercions type)
       (coerce-value props type)
       (set-fields (new-instance type) props))))

 (defn- unmarshall
   "Transform Clojure data to the required Java objects."
   [types col]
   (let [type (last (or (:actual types)
                        (:generic types)))
         pp   (partial populate types :actual)]
     (try
       (if (aws-package? type)
         (if (map? col)
           (if (contains? types :actual)
             (if (< (:depth types) 3)
               (apply assoc {}
                      (interleave (fmap kw->str (apply vector (keys col)))
                                  (fmap pp (apply vector (vals col)))))
               (apply assoc {}
                      (interleave (fmap kw->str (apply vector (keys col)))
                                  [(fmap #(populate {:generic [type]}
                                                    :generic
                                                    %)
                                         (first (apply vector (vals col))))])))
             (populate types :generic col))
           (if (and (contains? types :actual)
                    (= (:depth types) 3))
             (fmap #(fmap pp %) col)
             (fmap pp col)))
         (if (and (contains? types :actual)
                  (aws-package? type))
           (fmap pp col)
           (fmap #(coerce-value % type) col)))
       (catch Throwable e
         (throw (RuntimeException. (str
                                    "Failed to create an instance of "
                                    (.getName type)
                                    " from " col
                                    " due to " e
                                    ". Make sure the data matches an existing constructor and setters.")))))))

 (defn- invoke-method
   [pojo v method]
   (let [f       (partial invoke pojo method)
         types   (paramter-types method)
         generic (last (:generic types))]
     (if (and
          (coll? v)
          (not (contains? @coercions generic)))
       (f (unmarshall types v))
       (if (instance? generic v)
         (f v)
         (f (coerce-value v generic))))))

 (defn set-fields
   "Returns the populated AWS *Request bean with 'args' as
   the values. args is a map with keywords as keys and any
   type values. Complex values will be recursively resolved
   to the corresponding method calls on the object graph."
   [pojo args]
   (doseq [[k v] args]
     (try
       (->> (find-methods pojo k v)
            (some (partial invoke-method pojo v)))
       (catch Throwable e
         (throw (ex-info
                 (str "Error setting " k ": " (.getMessage e) ". Perhaps the value isn't compatible with the setter?")
                 {:property k, :value v}
                 e)))))
   pojo)

 (defn- create-bean
   [clazz args]
   (-> clazz new-instance (set-fields args)))

 (defn- create-request-bean
   "Create a new instance of an AWS *Request style Java
   bean passed as the argument to a method call on the
   Amazon*Client class. (Note that we assume all AWS
   service calls take at most a single argument.)"
   [method args]
   (let [clazz (first (.getParameterTypes method))]
     (if (contains? @coercions clazz)
       (coerce-value (into {} args) clazz)
       (create-bean clazz args))))


 (defprotocol IMarshall
   "Defines the contract for converting Java types to Clojure
  data. All return values from AWS service calls are
  marshalled. As such, the AWS service-specific namespaces
  will frequently need to implement this protocol in order
  to provide convenient data representations. See also the
  register-coercions function for coercing Clojure data to
  Java types."
   (marshall [obj]))

 (defn- getter?
   [method]
   (let [name (.getName method)
         type (.getName (.getReturnType method))]
     (or (and
          (.startsWith name "get")
          (= 0 (count (.getParameterTypes method))))
         (and
          (.startsWith name "is")
          (= "boolean" type)))))

 (defn accessors
   "Returns a vector of getters or setters for the class."
   [clazz getters?]
   (reduce
    #(if (or
          (and getters? (getter? %2))
          (and (not getters?)
               (.startsWith (.getName %2) "set")))
       (conj % %2)
       %)
    []
    (.getDeclaredMethods clazz)))

 (defn- prop->name
   [method]
   (let [name (.getName method)]
     (if (.startsWith name "is")
       (str (.substring name 2) "?")
       (.substring name 3))))

 (defn get-fields
   "Returns a map of all non-null values returned by
  invoking all public getters on the specified object."
   [obj]
   (let [no-arg (make-array Object 0)]
     (into {}
           (for [m (accessors (class obj) true)]
             (let [r (marshall (.invoke m obj no-arg))]
               (if-not (nil? r)
                 (hash-map
                  (camel->keyword (prop->name m))
                  r)))))))

 (extend-protocol IMarshall
   nil
   (marshall [obj] nil)

   java.util.Map
   (marshall [obj]
     (if-not (empty? obj)
       (apply assoc {}
              (interleave
               (fmap #(if (string? %) (keyword %) %)
                     (apply vector (keys obj)))
               (fmap marshall
                     (apply vector (vals obj)))))))

   java.util.Collection
   (marshall [obj]
     (if (instance? clojure.lang.IPersistentSet obj)
       obj
       (fmap marshall (apply vector obj))))

   java.util.Date
   (marshall [obj] (DateTime. (.getTime obj)))

                                        ; `false` boolean objects (i.e. (Boolean. false)) come out of e.g.
                                        ; .doesBucketExist, which wreak havoc on Clojure truthiness
   Boolean
   (marshall [obj] (when-not (nil? obj) (.booleanValue obj)))

   Object
   (marshall [obj]
     (if (aws-package? (class obj))
       (get-fields obj)
       obj)))

 (defn- use-aws-request-bean?
   [method args]
   (let [types (.getParameterTypes method)]
     (and (or (map? args) (< 1 (count args)))
          (< 0 (count types))
          (or (map? args)
              (and
               (or (and
                    (even? (count args))
                    (not= java.io.File (last types)))
                   (and
                    (odd? (count args))
                    (= java.io.File (last types)))) ; s3 getObject() support
               (some keyword? args)))
          (or (aws-package? (first types))
              (and (aws-package? (last types))
                   (not (< (count types) (count args))))))))

 (defn- prepare-args
   [method args]
   (let [types (.getParameterTypes method)
         num   (count types)]
     (if (and (empty? args) (= 0 num))
       (into-array Object args)
       (if (= num (count args))
         (into-array Object
                     (map #(if (and (aws-package? %2) (seq (.getConstructors %2)))
                                        ; must be a concrete, instantiatable class
                             (if (contains? @coercions %2)
                               (coerce-value % %2)
                               (create-bean %2 %))
                             (coerce-value % %2))
                          (vec args)
                          types))
         (if (use-aws-request-bean? method args)
           (cond
             (= 1 num)
             (into-array Object
                         [(create-request-bean
                           method
                           (seq (apply hash-map args)))])
             (and (aws-package? (first types))
                  (= 2 num)
                  (= File (last types)))
             (into-array Object
                         [(create-request-bean
                           method
                           (seq (apply hash-map (butlast args))))
                          (last args)])))))))




 (defn transfer-manager*
   [credential client-config crypto]
   (let [encryption-client (:encryption-client-fn client-config)
         amazon-client (:amazon-client-fn client-config)]
     (invoke-constructor
      "com.amazonaws.services.s3.transfer.TransferManager"
      (if crypto
        [(encryption-client crypto credential client-config)]
