             [oxcart.passes.defs
              :refer [locate-var-as-value]]
             [clojure.tools.emitter.jvm.transform
-             :refer [-compile]]))
+             :refer [-compile]]
+            [clojure.tools.analyzer.jvm.utils
+             :refer [primitive? prim-or-obj] :as j.u]
+            [clojure.tools.emitter.jvm.emit
+             :refer [emit-line-number
+                     emit-constants]]))
 
 (defn var->class [x]
   {:pre  [(var? x)]

  (str (var->ns x) "." (var->name x)))

(defn write-class
  [classname bytecode])

(defn dispatch-fn
  []
  )

(defmulti -emit
  "(λ AST) → OpSeq


  OpSeq is (Seq (Σ Op Opseq))"
  dispatch-fn)

(defn label []
  (keyword (gensym "label__")))

(defmethod -emit :fn-method
  [{:keys [params tag fixed-arity variadic? body env]}
   {:keys [class] :as frame}]
  (let [arg-tags               (mapv (comp prim-or-obj :tag) params)
        return-type            (prim-or-obj tag)
        tags                   (conj arg-tags return-type)
        prim-interface         (j.u/prim-interface tags)

        primitive?             (some primitive? tags)

        method-name            (cond
                                variadic? :doInvoke
                                primitive? :invokePrim
                                :else
                                :invoke)

        ;; arg-types
        [loop-label end-label] (repeatedly label)

        code
        `[[:start-method]
          [:local-variable :this :clojure.lang.AFunction nil ~loop-label ~end-label :this]
          ~@(mapcat (fn [{:keys [name arg-id o-tag tag]}]
                      `[~[:local-variable name tag nil loop-label end-label name]
                        ~@(when-not (= tag o-tag)
                            [[:load-arg arg-id]
                             [:check-cast tag]
                             [:store-arg arg-id]])])
                  params)
          [:mark ~loop-label]
          ~@(emit-line-number env loop-label)
          ~@(emit body (assoc frame
                         :loop-label  loop-label
                         :loop-locals params))
          [:mark ~end-label]
          [:return-value]
          [:end-method]]]

    ;; should emit typed only when there's an interface, otherwise it's useless

    `[{:op     :method
       :attr   #{:public}
       :method [(into [method-name] arg-tags) return-type]
       :code   code}
      (when primitive?
        {:op        :method
         :attr      #{:public}
         :interface prim-interface
         :method    [(into [:invoke] (repeat (count params) :java.lang.Object))
                     :java.lang.Object]
         :code      `[[:start-method]
                      [:load-this]
                      ~@(mapcat (fn [{:keys [tag]} id]
                                  `[~[:load-arg id]
                                    ~@(emit-cast Object tag)])
                                params (range))
                      ~[:invoke-virtual (into [(keyword class "invokePrim")] arg-tags) return-type]
                      ~@(emit-cast return-type Object)
                      [:return-value]
                      [:end-method]]})]))

(defmethod -emit :def
  [{:keys [init var]}]
  (-> (-emit init)
      (assoc :class-name (var->class var))))

(defn emit-fn-class
  [{:keys [class-name methods variadic? constants closed-overs env
           annotations super interfaces op fields class-id]
    :as ast}]
  (let [constants          (->> constants
                                (remove #(let [{:keys [tag type]} (val %)]
                                           (or (primitive? tag)
                                               (#{:string :bool} type))))
                                (into {}))

        consts             (vals constants)

        constant-table     (zipmap (mapv :id consts) consts)

        consts             (mapv (fn [{:keys [id tag]}]
                                   {:op   :field
                                    :attr #{:public :final :static}
                                    :name (str "const__" id)
                                    :tag  tag})
                                 consts)

        ctor-types         (into (if meta [:clojure.lang.IPersistentMap] [])
                                 (mapv :tag closed-overs))

        class-ctors        [{:op     :method
                             :attr   #{:public :static}
                             :method [[:<clinit>] :void]
                             :code   `[[:start-method]
                                       [:return-value]
                                       [:end-method]]}
                            {:op     :method
                             :attr   #{:public}
                             :method `[[:<init> ~@ctor-types] :void]
                             :code   `[[:start-method]
                                       [:load-this]
                                       [:invoke-constructor [~(keyword (name super) "<init>")] :void]
                                       [:return-value]
                                       [:end-method]]}]

        variadic-method    (when variadic?
                             (let [required-arity (->> methods (filter :variadic?) first :fixed-arity)]
                               [{:op     :method
                                 :attr   #{:public}
                                 :method [[:getRequiredArity] :int]
                                 :code   [[:start-method]
                                          [:push (int required-arity)]
                                          [:return-value]
                                          [:end-method]]}]))]

    {:op          :class
     :attr        #{:public :super :final}
     :annotations annotations
     :class-name  class-name
     :name        (s/replace class-name \. \/)
     :super       (s/replace (name super) \. \/)
     :interfaces  interfaces
     :fields      consts
     :methods     (concat class-ctors
                          variadic-method
                          (mapcat #(-emit %) methods))}))

(defmethod -emit :fn
  [{:keys [form internal-name variadic?] :as ast}]
  (let [class-name (str (namespace-munge *ns*) "$" (munge internal-name))
        super      (if variadic? :clojure.lang.RestFn :clojure.lang.AFunction)
        ast        (assoc ast
                     :class-name class-name
                     :super super)]
    (emit-fn-class ast)))

(defn emit-class
  "(λ Def-AST) → Class-AST
