 (ns clojure.core)
-
-(def
-  seq (fn* [items] (clj_core/seq.e items)))
 
 (def
   ^{:arglists '([& items])
     :doc "Creates a new list containing the items."
     :added "1.0"}
-  list (fn* [& items] (clojerl.List/new.e (seq items))))
+  list (fn* [& items] (clojerl.List/new.e items)))
+
+(def
+  ^{:arglists '([& items])
+    :doc "Creates a new list containing the items."
+    :added "1.0"}
+  list* (fn* [items] (clojerl.List/new.e items)))
 
 (def
   ^{:arglists '([x seq])

  (fn* [x]
       (io/format.e "~s~n" (seq [(str x)]))))

(def =
  (fn* [a b] (erlang/==.2 a b)))

(def not
  (fn* [a] (erlang/not.1 a)))

(def <
  (fn* [a b] (erlang/<.e a b)))

(def assert ^:macro
  (fn* [v] (if (not v) (throw :assert))))

(def apply
  (fn*
   ([f args] (clj_core/invoke.e f (seq args)))
   ([f x args] (clj_core/invoke.e f (cons x (seq args))))
   ([f x y args] (clj_core/invoke.e f (cons x (cons y (seq args)))))
   ([f x y z args] (clj_core/invoke.e f (cons x (cons y (cons z (seq args))))))
   ([f a b c d args] (clj_core/invoke.e f (cons a (cons b (cons c (cons d (seq args)))))))))

(def first
  (fn* [xs] (clj_core/first.e xs)))

(def rest
  (fn* [xs] (clj_core/rest.e xs)))

(def next
  (fn* [xs] (clj_core/next.e xs)))

(def reverse
  (fn* [s] (lists/reverse.e (seq s))))

(def concat
  (fn*
   ([] (list))
   ([x] (apply list x))
   ([x y]
    (if (seq x)
      (cons (first (seq x)) (concat (rest (seq x)) y))
      y))
   ([x y & zs]
    (if (seq zs)
      (apply concat (concat x y) (first zs) (next zs))
      (concat x y)))))

(def with-meta
  (fn* [x meta] (clj_core/with_meta.e x meta)))

(def meta
  (fn* [x] (clj_core/meta.e x)))

(def vector
  (fn* [& xs] (clj_core/vector.e (seq xs))))

(def hash-map
  (fn* [& xs] (clj_core/hash_map.e (seq xs))))

(def hash-set
  (fn* [& xs] (clj_core/hash_set.e (seq xs))))

(def ^:macro defn
  (fn* [_form _env name args & body]
       `(def ~name (fn* ~args ~@body))))

(defn reduce
  ([f coll]
   (reduce f (first coll) (rest coll)))
  ([f val coll]
   (if (seq coll)
     (reduce f (f val (first coll)) (rest coll))
     val)))

(defn +
  ([] 0)
  ([x] x)
  ([x y] (erlang/+.e x y))
  ([x y & more]
   (reduce + (+ x y) more)))

(defn -
  ([] 0)
  ([x] x)
  ([x y] (erlang/-.e x y))
  ([x y & more]
   (reduce - (- x y) more)))

(defn comp [& fs]
  (let* [fs (reverse fs)]
    (fn* [& xs]
      (reduce #(%2 %1)
              (apply (first fs) xs)
              (rest fs)))))

    :static true}
  cons (fn* [x s] (clj_core/cons.e x s)))

(def
  ^{:macro true
    :added "1.0"}
  let (fn* let [_&form _&env & decl] (cons 'let* decl)))

(def
 ^{:macro true
   :added "1.0"}
 loop (fn* loop [_&form _&env & decl] (cons 'loop* decl)))

(def
 ^{:macro true
   :added "1.0"}
 fn (fn* fn [&form _&env & decl]
        (let [x (cons 'fn* (clojerl.List/new.e decl))]
          (clj_core/with_meta.e x (clj_core/meta.e &form)))))


(def
 ^{:arglists '([coll])
   :doc "Returns the first item in the collection. Calls seq on its
    argument. If coll is nil, returns nil."
   :added "1.0"
   :static true}
 first (fn ^:static first [coll] (clj_core/first.e coll)))

(def
 ^{:arglists '([coll])
   :tag cojerl.ISeq
   :doc "Returns a seq of the items after the first. Calls seq on its
  argument.  If there are no more items, returns nil."
   :added "1.0"
   :static true}  
 next (fn ^:static next [x] (clj_core/next.e x)))

(def
 ^{:arglists '([coll])
   :tag clojerl.ISeq
   :doc "Returns a possibly empty seq of the items after the first. Calls seq on its
  argument."
   :added "1.0"
   :static true}  
 rest (fn ^:static rest [x] (clj_core/rest.e x)))

(def
 ^{:arglists '([coll x] [coll x & xs])
   :doc "conj[oin]. Returns a new collection with the xs
    'added'. (conj nil item) returns (item).  The 'addition' may
    happen at different 'places' depending on the concrete type."
   :added "1.0"
   :static true}
 conj (fn ^:static conj
        ([] [])
        ([coll] coll)
        ([coll x] (clj_core/conj.e coll x))
        ([coll x & xs]
         (if xs
           (recur (conj coll x) (first xs) (next xs))
           (conj coll x)))))

(def
 ^{:doc "Same as (first (next x))"
   :arglists '([x])
   :added "1.0"
   :static true}
 second (fn ^:static second [x] (first (next x))))

(def
 ^{:doc "Same as (first (first x))"
   :arglists '([x])
   :added "1.0"
   :static true}
 ffirst (fn ^:static ffirst [x] (first (first x))))

(def
 ^{:doc "Same as (next (first x))"
   :arglists '([x])
   :added "1.0"
   :static true}
 nfirst (fn ^:static nfirst [x] (next (first x))))

(def
 ^{:doc "Same as (first (next x))"
   :arglists '([x])
   :added "1.0"
   :static true}
 fnext (fn ^:static fnext [x] (first (next x))))

(def
 ^{:doc "Same as (next (next x))"
   :arglists '([x])
   :added "1.0"
   :static true}
 nnext (fn ^:static nnext [x] (next (next x))))

(def
 ^{:arglists '([coll])
   :doc "Returns a seq on the collection. If the collection is
    empty, returns nil.  (seq nil) returns nil. seq also works on
    Strings, native Java arrays (of reference types) and any objects
    that implement Iterable. Note that seqs cache values, thus seq
    should not be used on any Iterable whose iterator repeatedly
    returns the same mutable object."
   :tag clojure.lang.ISeq
   :added "1.0"
   :static true}
 seq (fn ^:static seq [coll] (clj_core/seq.e coll)))

(def
 ^{:arglists '([p x])
   :doc "Evaluates x and tests if it extends the protocol
    p. Returns true or false"
   :added "1.0"}
 extends? (fn extends? [p x]
            (clj_core/extends?.e p (clj_core/type.e x))))

(def
 ^{:arglists '([t x])
   :doc "Evaluates x and tests if it is of type t.
    Returns true or false"
   :added "1.0"}
 instance? (fn instance? [t x] (erlang/==.e t (clj_core/type.e x))))

(def
 ^{:arglists '([x])
   :doc "Return true if x implements ISeq"
   :added "1.0"
   :static true}
 seq? (fn ^:static seq? [x] (extends? :clojerl.ISeq x)))

(def
 ^{:arglists '([x])
   :doc "Return true if x implements IMeta"
   :added "1.0"
   :static true}
 meta? (fn ^:static meta? [x] (extends? :clojerl.IMeta x)))


#_(def
 ^{:arglists '([x])
   :doc "Return true if x is a Character"
   :added "1.0"
   :static true}
 char? (fn ^:static char? [x] (extends? Character x)))

(def
 ^{:arglists '([x])
   :doc "Return true if x is a String"
   :added "1.0"
   :static true}
 string? (fn ^:static string? [x] (instance? :clojerl.String x)))

(def
 ^{:arglists '([x])
   :doc "Return true if x implements IMap"
   :added "1.0"
   :static true}
 map? (fn ^:static map? [x] (extends? :clojerl.IMap x)))

(def
 ^{:arglists '([x])
   :doc "Return true if x is a Vector"
   :added "1.0"
   :static true}
 vector? (fn ^:static vector? [x] (instance? :clojerl.Vector x)))

(def
 ^{:arglists '([x])
   :doc "Return true if x is a Symbol"
   :added "1.0"
   :static true}
 symbol? (fn ^:static symbol? [x] (instance? :clojerl.Symbol x)))

(def
 ^{:arglists '([map key val] [map key val & kvs])
   :doc "assoc[iate]. When applied to a map, returns a new map of the
    same (hashed/sorted) type, that contains the mapping of key(s) to
    val(s). When applied to a vector, returns a new vector that
    contains val at index. Note - index must be <= (count vector)."
   :added "1.0"
   :static true}
 assoc
 (fn ^:static assoc
   ([map key val] (clj_core/assoc.e map key val))
   ([map key val & kvs]
    (let [ret (assoc map key val)]
      (if kvs
        (if (next kvs)
          (recur ret (first kvs) (second kvs) (nnext kvs))
          (throw "assoc expects even number of arguments after map/vector, found odd number"))
        ret)))))

;;;;;;;;;;;;;;;;; metadata ;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def
 ^{:arglists '([obj])
   :doc "Returns the metadata of obj, returns nil if there is no metadata."
   :added "1.0"
   :static true}
 meta (fn ^:static meta [x]
        (if (meta? x)
          (clj_core/meta.e x))))

(def
 ^{:arglists '([^clojure.lang.IObj obj m])
   :doc "Returns an object of the same type and value as obj, with
    map m as its metadata."
   :added "1.0"
   :static true}
 with-meta (fn ^:static with-meta [x m]
             (clj_core/with_meta.e x m)))

#_(def ^{:private true :dynamic true}
  assert-valid-fdecl (fn [fdecl]))

#_(def
 ^{:private true}
 sigs
 (fn [fdecl]
   (assert-valid-fdecl fdecl)
   (let [asig 
         (fn [fdecl]
           (let [arglist (first fdecl)
                 ;elide implicit macro args
                 arglist (if (clj_utils/equals.e '&form (first arglist)) 
                           (clj_core/subvec.e arglist 2 (clj_core/count.e arglist))
                           arglist)
                 body (next fdecl)]
             (if (map? (first body))
               (if (next body)
                 (with-meta arglist (conj (if (meta arglist) (meta arglist) {}) (first body)))
                 arglist)
               arglist)))]
     (if (seq? (first fdecl))
       (loop [ret [] fdecls fdecl]
         (if fdecls
           (recur (conj ret (asig (first fdecls))) (next fdecls))
           (seq ret)))
       (list (asig fdecl))))))

(def 
 ^{:arglists '([coll])
   :doc "Return the last item in coll, in linear time"
   :added "1.0"
   :static true}
 last (fn ^:static last [s]
        (if (next s)
          (recur (next s))
          (first s))))

(def 
 ^{:arglists '([coll])
   :doc "Return a seq of all but the last item in coll, in linear time"
   :added "1.0"
   :static true}
 butlast (fn ^:static butlast [s]
           (loop [ret [] s s]
             (if (next s)
               (recur (conj ret (first s)) (next s))
               (seq ret)))))

(def ^:macro defn
  (fn* [form env name & fdecl]
       (if (symbol? name)
         nil
         (throw "First argument to defn must be a symbol"))
       (let [m     (if (string? (first fdecl))
                     {:doc (first fdecl)}
                     {})
             fdecl (if (string? (first fdecl))
                     (next fdecl)
                     fdecl)
             m     (if (map? (first fdecl))
                     (conj m (first fdecl))
                     m)
             fdecl (if (map? (first fdecl))
                     (next fdecl)
                     fdecl)
             fdecl (if (vector? (first fdecl))
                     (list fdecl)
                     fdecl)
             m     (if (map? (last fdecl))
                     (conj m (last fdecl))
                     m)
             fdecl (if (map? (last fdecl))
                      (butlast fdecl)
                      fdecl)
             ;; m     (merge {:arglists (list 'quote (sigs fdecl))} m)
             m     (conj (if (meta name) (meta name) {}) m)]
         (list 'def (with-meta name m)
               ;;todo - restore propagation of fn name
               ;;must figure out how to convey primitive hints to self calls first
               (cons 'clojure.core/fn (list* (seq fdecl)))))))

;;;;; 

(def str
  (fn* [x] (clj_core/str.e x)))

