(ns riddley.compiler
  (:import
    [clojure.lang
     Var
     Compiler
     Compiler$ObjMethod
     Compiler$ObjExpr]
    [riddley
     Util]))

(defn- stub-method []
  (proxy [Compiler$ObjMethod] [(Compiler$ObjExpr. nil) nil]))

(defn tag-of
  "Returns a symbol representing the tagged class of the symbol, or `nil` if none exists."
  [x]
  (when-let [tag (-> x meta :tag)]
    (let [sym (symbol
                (if (instance? Class tag)
                  (.getName ^Class tag)
                  (name tag)))]
      (when-not (= 'java.lang.Object sym)
        sym))))

(let [n (atom 0)]
  (defn- local-id []
    (swap! n inc)))

(defn locals
  "Returns the local binding map, equivalent to the value of `&env`."
  []
  (when (.isBound Compiler/LOCAL_ENV)
    @Compiler/LOCAL_ENV))

(defmacro with-base-env [& body]
  `(binding [*warn-on-reflection* false]
     (with-bindings (if (locals)
                      {}
                      {Compiler/LOCAL_ENV {}})
       ~@body)))

(defmacro with-lexical-scoping
  "Defines a lexical scope where new locals may be registered."
  [& body]
  `(with-bindings {Compiler/LOCAL_ENV (locals)}
     ~@body))

(defmacro with-stub-vars [& body]
  `(with-bindings {Compiler/CLEAR_SITES nil
                   Compiler/METHOD (stub-method)}
     ~@body))

;; if we don't do this in Java, the checkcasts emitted by Clojure cause an
;; IllegalAccessError on Compiler$Expr.  Whee.
(defn register-local
  "Registers a locally bound variable `v`, which is being set to form `x`."
  [v x]
  (with-stub-vars
    (.set ^Var Compiler/LOCAL_ENV

      ;; we want to allow metadata on the symbols to persist, so remove old symbols first
      (-> (locals)
        (dissoc v)
        (assoc v (try
                   (Util/localBinding (local-id) v (tag-of v) x)
                   (catch Exception _
                     ::analyze-failure)))))))

(defn register-arg
  "Registers a function argument `x`."
  [x]
  (with-stub-vars
    (.set ^Var Compiler/LOCAL_ENV
      (-> (locals)
        (dissoc x)
        (assoc x (Util/localArgument (local-id) x (tag-of x)))))))



