(ns wisp.sequence
  (:require [wisp.runtime :refer [nil? vector? fn? number? string? dictionary?
                                  key-values str dec inc merge dictionary]]))

;; Implementation of list

(defn- List
  "List type"
  [head tail]
  (set! this.head head)
  (set! this.tail (or tail (list)))
  (set! this.length (inc (count this.tail)))
  this)

(set! List.prototype.length 0)
(set! List.type "wisp.list")
(set! List.prototype.type List.type)
(set! List.prototype.tail (Object.create List.prototype))
(set! List.prototype.to-string
      (fn []
        (loop [result ""
               list this]
          (if (empty? list)
            (str "(" (.substr result 1) ")")
            (recur
             (str result
                  " "
                  (if (vector? (first list))
                    (str "[" (.join (first list) " ") "]")
                    (if (nil? (first list))
                      "nil"
                      (if (string? (first list))
                        (.stringify JSON (first list))
                        (if (number? (first list))
                          (.stringify JSON (first list))
                          (first list))))))
             (rest list))))))

(defn- lazy-seq-value [lazy-seq]
  (if (not (.-realized lazy-seq))
    (and (set! (.-realized lazy-seq) true)
         (set! (.-x lazy-seq) (.x lazy-seq)))
    (.-x lazy-seq)))

(defn- LazySeq [realized x]
  (set! (.-realized this) (or realized false))
  (set! (.-x this) x)
  this)
(set! LazySeq.type "wisp.lazy.seq")
(set! LazySeq.prototype.type LazySeq.type)

(defn lazy-seq
  [realized body]
  (LazySeq. realized body))

(defn lazy-seq?
  [value]
  (and value (identical? LazySeq.type value.type)))

(defmacro lazy-seq
  "Takes a body of expressions that returns an ISeq or nil, and yields
  a Seqable object that will invoke the body only the first time seq
  is called, and will cache the result and return it on all subsequent
  seq calls. See also - realized?"
  {:added "1.0"}
  [& body]
  `(.call lazy-seq nil false (fn [] ~@body)))

(defn list?
  "Returns true if list"
  [value]
  (and value (identical? List.type value.type)))

(defn list
  "Creates list of the given items"
  []
  (if (identical? (.-length arguments) 0)
    (Object.create List.prototype)
    (.reduce-right (.call Array.prototype.slice arguments)
                   (fn [tail head] (cons head tail))
                   (list))))

(defn cons
  "Creates list with `head` as first item and `tail` as rest"
  [head tail]
  (new List head tail))

(defn- reverse-list
  [sequence]
  (loop [items []
           source sequence]
      (if (empty? source)
        (apply list items)
        (recur (.concat [(first source)] items)
               (rest source)))))

(defn ^boolean sequential?
  "Returns true if coll satisfies ISequential"
  [x] (or (list? x)
          (vector? x)
          (lazy-seq? x)
          (dictionary? x)
          (string? x)))


(defn reverse
  "Reverse order of items in the sequence"
  [sequence]
  (cond (list? sequence) (reverse-list sequence)
        (vector? sequence) (.reverse sequence)
        (nil? sequence) '()
        :else (reverse (seq sequence))))

(defn map
  "Returns a sequence consisting of the result of applying `f` to the
  first item, followed by applying f to the second items, until sequence is
  exhausted."
  [f sequence]
  (cond (vector? sequence) (.map sequence #(f %))
        (list? sequence) (map-list f sequence)
        (nil? sequence) '()
        :else (map f (seq sequence))))

(defn- map-list
  "Like map but optimized for lists"
  [f sequence]
  (loop [result '()
         items sequence]
    (if (empty? items)
      (reverse result)
      (recur (cons (f (first items)) result) (rest items)))))

(defn filter
  "Returns a sequence of the items in coll for which (f? item) returns true.
  f? must be free of side-effects."
  [f? sequence]
  (cond (vector? sequence) (.filter sequence f?)
        (list? sequence) (filter-list f? sequence)
        (nil? sequence) '()
        :else (filter f? (seq sequence))))

(defn- filter-list
  "Like filter but for lists"
  [f? sequence]
  (loop [result '()
         items sequence]
    (if (empty? items)
      (reverse result)
      (recur (if (f? (first items))
              (cons (first items) result)
              result)
              (rest items)))))

(defn reduce
  [f & params]
  (let [has-initial (>= (count params) 2)
        initial (if has-initial (first params))
        sequence (if has-initial (second params) (first params))]
    (cond (nil? sequence) initial
          (vector? sequence) (if has-initial
                              (.reduce sequence f initial)
                              (.reduce sequence f))
          (list? sequence) (if has-initial
                            (reduce-list f initial sequence)
                            (reduce-list f (first sequence) (rest sequence)))
          :else (reduce f initial (seq sequence)))))

(defn- reduce-list
  [f initial sequence]
  (loop [result initial
         items sequence]
    (if (empty? items)
      result
      (recur (f result (first items)) (rest items)))))

(defn count
  "Returns number of elements in list"
  [sequence]
  (if (nil? sequence)
    0
    (.-length (seq sequence))))

(defn empty?
  "Returns true if list is empty"
  [sequence]
  (identical? (count sequence) 0))

(defn first
  "Return first item in a list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (.-head sequence)
        (or (vector? sequence) (string? sequence)) (get sequence 0)
        (lazy-seq? sequence) (first (lazy-seq-value sequence))
        :else (first (seq sequence))))

(defn second
  "Returns second item of the list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (first (rest sequence))
        (or (vector? sequence) (string? sequence)) (get sequence 1)
        (lazy-seq? sequence) (second (lazy-seq-value sequence))
        :else (first (rest (seq sequence)))))

(defn third
  "Returns third item of the list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (first (rest (rest sequence)))
        (or (vector? sequence) (string? sequence)) (get sequence 2)
        (lazy-seq? sequence) (third (lazy-seq-value sequence))
        :else (second (rest (seq sequence)))))

(defn rest
  "Returns list of all items except first one"
  [sequence]
  (cond (nil? sequence) '()
        (list? sequence) (.-tail sequence)
        (or (vector? sequence) (string? sequence)) (.slice sequence 1)
        (lazy-seq? sequence) (rest (lazy-seq-value sequence))
        :else (rest (seq sequence))))

(defn- last-of-list
  [list]
  (loop [item (first list)
         items (rest list)]
    (if (empty? items)
      item
      (recur (first items) (rest items)))))

(defn last
  "Return the last item in coll, in linear time"
  [sequence]
  (cond (or (vector? sequence)
            (string? sequence)) (get sequence (dec (count sequence)))
        (list? sequence) (last-of-list sequence)
        (nil? sequence) nil
        (lazy-seq? sequence) (last (lazy-seq-value sequence))
        :else (last (seq sequence))))

(defn butlast
  "Return a seq of all but the last item in coll, in linear time"
  [sequence]
  (let [items (cond (nil? sequence) nil
                    (string? sequence) (subs sequence 0 (dec (count sequence)))
                    (vector? sequence) (.slice sequence 0 (dec (count sequence)))
                    (list? sequence) (apply list (butlast (vec sequence)))
                    (lazy-seq? sequence) (butlast (lazy-seq-value sequence))
                    :else (butlast (seq sequence)))]
    (if (not (or (nil? items) (empty? items)))
        items)))

(defn take
  "Returns a sequence of the first `n` items, or all items if
  there are fewer than `n`."
  [n sequence]
  (cond (nil? sequence) '()
        (vector? sequence) (take-from-vector n sequence)
        (list? sequence) (take-from-list n sequence)
        (lazy-seq? sequence) (take n (lazy-seq-value sequence))
        :else (take n (seq sequence))))

(defn- take-vector-while
  [predicate vector]
  (loop [result []
         tail vector
         head (first vector)]
    (if (and (not (empty? tail))
             (predicate head))
      (recur (conj result head)
             (rest tail)
             (first tail))
      result)))

(defn- take-list-while
  [predicate items]
  (loop [result []
         tail items
         head (first items)]
    (if (and (not (empty? tail))
             (predicate? head))
      (recur (conj result head)
             (rest tail)
             (first tail))
      (apply list result))))


(defn take-while
  [predicate sequence]
  (cond (nil? sequence) '()
        (vector? sequence) (take-vector-while predicate sequence)
        (list? sequence) (take-vector-while predicate sequence)
        :else (take-while predicate
                          (lazy-seq-value sequence))))


(defn- take-from-vector
  "Like take but optimized for vectors"
  [n vector]
  (.slice vector 0 n))

(defn- take-from-list
  "Like take but for lists"
  [n sequence]
  (loop [taken '()
         items sequence
         n n]
    (if (or (identical? n 0) (empty? items))
      (reverse taken)
      (recur (cons (first items) taken)
             (rest items)
             (dec n)))))




(defn- drop-from-list [n sequence]
  (loop [left n
         items sequence]
    (if (or (< left 1) (empty? items))
      items
      (recur (dec left) (rest items)))))

(defn drop
  [n sequence]
  (if (<= n 0)
    sequence
    (cond (string? sequence) (.substr sequence n)
          (vector? sequence) (.slice sequence n)
          (list? sequence) (drop-from-list n sequence)
          (nil? sequence) '()
          (lazy-seq? sequence) (drop n (lazy-seq-value sequence))
          :else (drop n (seq sequence)))))


(defn- conj-list
  [sequence items]
  (reduce (fn [result item] (cons item result)) sequence items))

(defn conj
  [sequence & items]
  (cond (vector? sequence) (.concat sequence items)
        (string? sequence) (str sequence (apply str items))
        (nil? sequence) (apply list (reverse items))
        (or (list? sequence)
            (lazy-seq?)) (conj-list sequence items)
        (dictionary? sequence) (merge sequence (apply merge items))
        :else (throw (TypeError (str "Type can't be conjoined " sequence)))))

(defn assoc
  [source & key-values]
  ;(assert (even? (count key-values)) "Wrong number of arguments")
  ;(assert (and (not (seq? source))
  ;             (not (vector? source))
  ;             (object? source)) "Can only assoc on dictionaries")
  (conj source (apply dictionary key-values)))

(defn concat
  "Returns list representing the concatenation of the elements in the
  supplied lists."
  [& sequences]
  (reverse
    (reduce
      (fn [result sequence]
        (reduce
          (fn [result item] (cons item result))
          result
          (seq sequence)))
      '()
      sequences)))

(defn seq [sequence]
  (cond (nil? sequence) nil
        (or (vector? sequence) (list? sequence) (lazy-seq? sequence)) sequence
        (string? sequence) (.call Array.prototype.slice sequence)
        (dictionary? sequence) (key-values sequence)
        :default (throw (TypeError (str "Can not seq " sequence)))))

(defn seq? [sequence]
  (or (list? sequence)
      (lazy-seq? sequence)))

(defn- list->vector [source]
  (loop [result []
         list source]
    (if (empty? list)
      result
      (recur
        (do (.push result (first list)) result)
        (rest list)))))

(defn vec
  "Creates a new vector containing the contents of sequence"
  [sequence]
  (cond (nil? sequence) []
        (vector? sequence) sequence
        (list? sequence) (list->vector sequence)
        :else (vec (seq sequence))))

(defn sort
  "Returns a sorted sequence of the items in coll.
  If no comparator is supplied, uses compare."
  [f items]
  (let [has-comparator (fn? f)
        items (if (and (not has-comparator) (nil? items)) f items)
        compare (if has-comparator (fn [a b] (if (f a b) 0 1)))]
    (cond (nil? items) '()
          (vector? items) (.sort items compare)
          (list? items) (apply list (.sort (vec items) compare))
          (dictionary? items) (.sort (seq items) compare)
          :else (sort f (seq items)))))


(defn repeat
  "Returns a vector of given `n` length with of given `x`
  items. Not compatible with clojure as it's not a lazy
  and only finite repeats are supported"
  [n x]
  (loop [n n
         result []]
    (if (<= n 0)
      result
      (recur (dec n)
             (conj result x)))))

(defn every?
  [predicate sequence]
  (.every (vec sequence) #(predicate %)))

(defn some
  "Returns the first logical true value of (pred x) for any x in coll,
  else nil.  One common idiom is to use a set as pred, for example
  this will return :fred if :fred is in the sequence, otherwise nil:
  (some even? [1 3]) => false
  (some even? [1 2 3 4] => true"
  [predicate sequence]
  (loop [items sequence]
    (cond (empty? items) false
          (predicate (first items)) true
          :else (recur (rest items)))))


(defn partition
  ([n coll] (partition n n coll))
  ([n step coll] (partition n step [] coll))
  ([n step pad coll]
   (loop [result []
          items (seq coll)]
     (let [chunk (take n items)
           size (count chunk)]
       (cond (identical? size n) (recur (conj result chunk)
                                        (drop step items))
             (identical? 0 size) result
             (> n (+ size (count pad))) result
             :else (conj result
                         (take n (vec (concat chunk
                                              pad)))))))))

(defn interleave
  ([ax bx]
   (loop [cx []
          ax ax
          bx bx]
     (if (or (empty? ax)
             (empty? bx))
       (seq cx)
       (recur (conj cx
                    (first ax)
                    (first bx))
              (rest ax)
              (rest bx)))))
  ([& sequences]
   (loop [result []
          sequences sequences]
     (if (some empty? sequences)
       result
       (recur (concat result (map first sequences))
              (map rest sequences))))))

(defn nth
  "Returns nth item of the sequence"
  [sequence index not-found]
  (cond (nil? sequence) not-found
        (list? sequence) (if (< index (count sequence))
                           (first (drop index sequence))
                           not-found)
        (or (vector? sequence)
            (string? sequence)) (if (< index (count sequence))
                                  (aget sequence index)
                                  not-found)
        (lazy-seq? sequence) (nth (lazy-seq-value sequence) index not-found)
        :else (throw (TypeError "Unsupported type"))))
