;; Copyright (c) 2016-2020 Eric Bailey
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
;; You may obtain a copy of the License at
;;
;;     http://www.apache.org/licenses/LICENSE-2.0
;;
;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;; See the License for the specific language governing permissions and
;; limitations under the License.

;; File    : clj-tests.lfe
;; Author  : Eric Bailey
;; Purpose : Test clj exports.

(defmodule clj-tests
  "Test clj exports.")

(include-file "ltest-macros.lfe")

(defmacro ok? (x) `(=:= 'ok ,x))

;; HACK
(defmacro IFF-MAPS expr `(andalso (erl_internal:bif 'is_map 1) ,@expr))

;;; defn

(defun test-defn (f a def)
  `#(ok #"doc"))
  ;; (let* ((forms                      `((defmodule dummy) ,def))
  ;;        (`#(ok (#(ok dummy ,beam)))  (lfe_comp:forms forms))
  ;;        (`#(ok ,docs)                (lfe_doc:get_module_docs beam)))
  ;;   (lfe_io:format "~s/~w ~p~n" (list f a docs))
  ;;   (lfe_doc:function_docs f a docs)))

(deftest defn
  (are* [f a def] (ok? (is-match `#(ok ,_doc) (test-defn f a def)))
        'foo 1 '(clj:defn foo [bar] bar)
        'foo 1 '(clj:defn foo [bar] "doc" bar)
        'foo 2 '(clj:defn foo [_bar baz] baz)
        'foo 2 '(clj:defn foo [_bar baz] "doc" baz)
        'foo 3 '(clj:defn foo ([_bar _baz qux] qux))
        'foo 3 '(clj:defn foo "doc" ([_bar _baz qux] qux))
        'foo 3 '(clj:defn foo ([`#(bar ,_bar) _baz qux] qux))
        'foo 3 '(clj:defn foo "doc" ([`#(bar ,_bar) _baz qux] qux))))

;;; Threading macros.

(deftest ->
  (are* [x y] (ok? (is-match x y))

        '(#(a 1) #(b 2) #(c 3) #(d 4) #(e 5) #(f 6))
        (clj:-> '(#(a 1) #(b 2) #(c 3))
                (++ '(#(d 4)))
                (++ '(#(e 5)))
                (++ '(#(f 6))))

        '("L" "F" "E")
        (clj:-> "a b c d e"
                (string:to_upper)
                (string:tokens " ")
                (lists:merge '("X" "F" "L"))
                (lists:sort)
                (lists:reverse)
                (lists:sublist 2 3))))

(deftest ->>
  (are* [x y] (ok? (is-match x y))
        '(#(f 6) #(e 5) #(d 4) #(a 1) #(b 2) #(c 3))
        (clj:->> '(#(a 1) #(b 2) #(c 3))
                 (++ '(#(d 4)))
                 (++ '(#(e 5)))
                 (++ '(#(f 6))))
        1540.0
        (clj:->> (clj:seq 42)
                 (lists:map (lambda (x) (math:pow x 2)))
                 (lists:filter (clj:comp #'even?/1 #'round/1))
                 (clj:take 10)
                 (lists:foldl (fun + 2) 0))))

;; Ported from #'clojure.test-clojure.macros/as->test
(deftest as->
  (flet ((inc (x) (+ x 1)))
    (is-match 0 (clj:as-> 0 x))
    (is-match 1 (clj:as-> 0 x (inc x)))
    (is-match 2 (clj:as-> '[0 1] x
                  (lists:map #'inc/1 x)
                  (lists:reverse x)
                  (car x)))))

;; Ported from #'clojure.test-clojure.macros/cond->test
(deftest cond->
  (is-match  0 (clj:cond-> 0))
  (is-match -1 (clj:cond-> 0 'true  (+ 1) 'true  (- 2)))
  (is-match  0 (clj:cond-> 0 'false (+ 1)))
  (is-match -1 (clj:cond-> 1 'true  (- 2) 'false (+ 1))))

;; Ported from #'clojure.test-clojure.macros/cond->>test
(deftest cond->>
  (is-match 0 (clj:cond->> 0))
  (is-match 1 (clj:cond->> 0 'true  (+ 1) 'true  (- 2)))
  (is-match 0 (clj:cond->> 0 'false (+ 1)))
  (is-match 1 (clj:cond->> 1 'true  (- 2) 'false (+ 1))))

;; Ported from #'clojure.test-clojure.macros/some->test
(deftest some->test
  (is-match 'undefined (clj:some-> 'undefined))
  (is-match  0         (clj:some-> 0))
  (is-match -1         (clj:some-> 1 (- 2)))
  (is-match 'undefined (clj:some-> 1 (progn 'undefined) (- 2))))

;; Ported from #'clojure.test-clojure.macros/some->>test
(deftest some->>test
  (is-match 'undefined (clj:some->> 'undefined))
  (is-match 0 (clj:some->> 0))
  (is-match 1 (clj:some->> 1 (- 2)))
  (flet ((constantly-undefined (_) 'undefined))
    (is-match 'undefined (clj:some->> 1 (constantly-undefined) (- 2)))))

(deftest doto
  (is-not-match 6 (clj:doto 42 (/ 7)))
  (let ((answer 42))
    (is-match answer (clj:doto answer (+ 1) (- 10) (progn 'undefined) (list))))
  (let ((bin #"never you change"))
    (is-match bin (clj:doto bin (io:put_chars)))))

;;; Conditional macros

;; Ported from #'clojure.test-clojure.control/test-condp
(deftest condp
  (are* [x] (ok? (is-match 'pass x))
        (clj:condp #'=:=/2 1
          1 'pass
          2 'fail)
        (clj:condp #'=:=/2 1
          2 'fail
          1 'pass)
        (clj:condp #'=:=/2 1
          2 'fail
          'pass)
        (clj:condp #'=:=/2 1
          'pass)
        (clj:condp #'=:=/2 1
          2 'fail
          (clj:identity 'pass))
        (clj:condp #'+/2 1
          1 >> (lambda (y) (if (=:= y 2) 'pass 'fail)))
        (clj:condp #'+/2 1
          1 >> (lambda (y) (if (=:= y 3) 'fail 'pass))))
  (is-error 'no-matching-clause (clj:condp #'=:=/2 1))
  (is-error 'no-matching-clause (clj:condp #'=:=/2 1 2 'fail)))

(deftest if-not
  (is-not (clj:if-not 'true 'then 'false))
  (are* [x] (clj:true? x)
        (clj:if-not 'false 'true)
        (clj:if-not 'false 'true 'else)))

;; Ported from #'clojure.test-clojure.control/test-when
(deftest iff
  (are* [x y] (ok? (is-match x y))
        1 (clj:iff 'true 1)
        () (clj:iff 'true)
        'undefined (clj:iff 'false)
        'undefined (clj:iff 'undefined)
        'undefined (clj:iff 'false (error 'bad-iff))))

(deftest when-not
  (is-match 'undefined (clj:when-not 'true 'ok))
  (is-match 'true (clj:when-not 'false 'true))
  (is-match 42 (clj:when-not 'undefined 42)))

(deftest not=
  (is-not (clj:not= 42))
  (is-not (clj:not= 42 42))
  (is (clj:not= 42 123)))

;;; clj-comp-tests

(deftest comp
  (flet ((c0 (x) (funcall (clj:comp) x)))
    (are* [x] (=:= (clj:identity x) (c0 x))
          'atom
          0 42
          0.0 3.14
          #\c
          "" "abc"
          #"" #"abc"
          () (1 2)
          #() #(1 2)
          (IFF-MAPS (call 'maps 'new))
          (IFF-MAPS (call 'maps 'from_list '(#(a 1) #(b 2)))))
    (is-equal (clj:identity (+ 1 2 3)) (c0 6))
    (is-equal (clj:identity (quote foo)) (c0 'foo)))
  (let ((asin-result (funcall (clj:comp #'math:sin/1 #'math:asin/1) 0.5)))
    (is-equal "0.5" (lists:flatten (io_lib:format "~.1f" `(,asin-result)))))
  (is-equal 1.5
            (funcall (clj:comp `(,(lambda (x) (+ x 1))
                                 ,#'math:sin/1
                                 ,#'math:asin/1)) 0.5))
  (is-equal '(1 2 3 4)
            (lists:filter (clj:comp #'not/1 #'zero?/1)
              '(0 1 0 2 0 3 0 4)))
  (let ((asin-result (clj:comp #'math:sin/1 #'math:asin/1 0.5)))
    (is-equal "0.5" (lists:flatten (io_lib:format "~.1f" `(,asin-result))))))

(deftest partial
  (flet (;; (p0 (x) (funcall (clj:partial inc) x))
         (p1 (x) (funcall (clj:partial (fun + 2) 20) x))
         ;; (p2 (x) (funcall (clj:partial conj #(1 2)) x))
         )
    ;; (is-equal 41 (p0 40))
    (is-equal 40 (p1 20))
    ;; (is-equal #(1 2 3) (p2 3))
    (is-equal 3 (funcall (clj:partial (fun + 2) 1) 2))
    (is-equal 6 (funcall (clj:partial (fun + 3) 1) '(2 3)))))

;;; clj-p-tests

(deftest string?
  (is-not (clj:string? (list "my" "string" "data")))
  (is (clj:string? "string data! yaya!")))

(deftest unicode?
  (is-not (clj:unicode? (lists:seq 1 7)))
  (are* [s] (clj:unicode? s)
        "simple"
        "Schrödinger"
        "Umeå Vråljazz Giganter"))

(deftest list?
  (is-not (clj:list? "string data! yaya!"))
  (is (clj:list? (list "my" "string" "data"))))

(deftest tuple?
  (is-not (clj:tuple? "string data! yaya!"))
  (is (clj:tuple? (tuple "my" "string" "data"))))

(deftest atom?
  (is-not (clj:atom? "string data! yaya!"))
  (is (clj:atom? 'my-atom))
  (is (clj:atom? '|more atom data|)))

(deftest binary?
  (is-not (clj:binary? "string"))
  (is-not (clj:binary? (binary (42 (size 6)))))
  (is (clj:binary? #"binary"))
  (is (clj:binary? #b(125 114 196))))

(deftest bitstring?
  (is-not (clj:bitstring? "string"))
  (are* [x] (clj:bitstring? x)
        (binary (42 (size 6)))
        #"binary"
        #b(125 114 196)))

(deftest boolean?
  (are* [x] (not (clj:boolean? x))
        42
        (andalso 'true 42)
        (orelse  'false 42))
  (are* [x] (clj:boolean? x)
        'true
        'false
        (=/= 42 123)))

(deftest bool?
  (are* [x] (not (clj:bool? x))
        42
        (andalso 'true 42)
        (orelse  'false 42))
  (are* [x] (clj:bool? x)
        'true
        'false
        (=/= 42 123)))

(deftest float?
  (is-not (clj:float? 42))
  (is (clj:float? 42.0)))

(deftest function?
  (are* [x] (not (clj:function? x))
        '|#'+/2|
        '(fun + 2)
        'function
        42)
  (are* [x] (clj:function? x)
        #'+/2
        (fun + 2)
        (lambda (y) (+ y 1))
        (flet ((id (x) x)) #'id/1)))

(deftest func?
  (are* [x] (not (clj:func? x))
        '|#'+/2|
        '(fun + 2)
        'func
        42)
  (are* [x] (clj:func? x)
        #'+/2
        (fun + 2)
        (lambda (y) (+ y 1))
        (flet ((id (x) x)) #'id/1)))

(deftest integer?
  (is-not (clj:integer? 42.0))
  (is (clj:integer? 42)))

(deftest int?
  (is-not (clj:int? 42.0))
  (is (clj:int? 42)))

(deftest number?
  (are* [x] (not (clj:number? x))
        'forty-two
        '(42)
        #(40 2)
        "42"
        #"42")
  (are* [x] (clj:number? x)
        42
        42.0
        (/ 4 2)
        (+ 1.0 2 3)))

;; Based on record_SUITE.
(defrecord foo a b c d)
(deftest record?
  (is (clj:record? (make-foo) 'foo))
  (is-not (clj:record? (make-foo) 'barf))
  ;; This fails due a bug: https://github.com/lfe/lfe/issues/266
  ;; (is-not (clj:record? #(foo) 'foo))
  (is-not (clj:record? [] 'foo))
  (is-not (clj:record? 'a 'foo)))

(deftest reference?
  (is-not (clj:reference? 42))
  (is (clj:reference? (make_ref))))

(deftest map?
  (is-not (clj:map? 42))
  (IFF-MAPS (is (clj:map? (call 'maps 'new)))))

(deftest dict?
  (are* [x] (not (clj:dict? x))
        "a string"
        '("a" "list")
        #b("a binary")
        #("a" "tuple")
        '(#("a" "tuple")))
  (is (clj:dict? (dict:from_list '(#("a" "tuple"))))))

(deftest set?
  (is-not (clj:set? '(c a b)))
  (are* [x] (clj:set? x)
        '(a b c)
        ()
        (sets:new)
        (ordsets:new)))

(deftest proplist?
  (are* [x] (not (clj:proplist? x))
        1
        '(1)
        '(1 2)
        '((1 2))
        '(#(1 2))
        '(#(a 1) #(2 b) #(c 3)))
  (are* [x] (clj:proplist? x)
        '(a)
        '(a b c)
        '(#(a 1) b c)
        '(#(a 1) #(b 2) c)
        '(#(a 1) #(b 2) #(c 3))))

(deftest proplist-kv?
  (are* [x] (not (clj:proplist-kv? x))
        "a"
        1
        '(a b))
  (is (clj:proplist-kv? 'a))
  (is (clj:proplist-kv? '#(a b))))

(deftest undefined?
  (is-not (clj:undefined? 42))
  (is-not (clj:undefined? 'undef))
  (is (clj:undefined? 'undefined)))

(deftest undef?
  (is-not (clj:undef? 42))
  (is-not (clj:undef? 'undef))
  (is (clj:undef? 'undefined)))

(deftest nil?
  (is-not (clj:nil? 32))
  (is-not (clj:nil? 'undefined))
  (is (clj:nil? 'nil))
  (is (clj:nil? ())))

(deftest true?
  (is-not (clj:true? 'false))
  (is (clj:true? 'true)))

(deftest false?
  (is-not (clj:false? 'true))
  (is (clj:false? 'false)))

(deftest falsy?
  (is-not (clj:falsy? 'true))
  (is-not (clj:falsy? 42))
  (is-not (clj:falsy? ()))
  (is (clj:falsy? 'false))
  (is (clj:falsy? 'undefined))
  (is (clj:falsy? (proplists:get_value 42 ()))))

(deftest odd?
  (is-not (clj:odd? 42))
  (is (clj:odd? 333)))

(deftest even?
  (is-not (clj:even? 333))
  (is (clj:even? 42)))

(deftest zero?
  (is-not (clj:zero? 42))
  (is (clj:zero? 0)))

(deftest pos?
  (is-not (clj:pos? -42))
  (is (clj:pos? 42)))

(deftest neg?
  (is-not (clj:neg? 42))
  (is (clj:neg? -42)))

(deftest identical?
  (is-not (clj:identical? '(a b c) '(a b d)))
  (is (clj:identical? '(a b c) '(a b c))))

;;; Other macros.

(deftest str
  (are* [x y] (ok? (is-match x y))
        "" (clj:str)
        "123" (clj:str 1 2 3)
        "abc" (clj:str "a" "b" "c")
        "abc" (clj:str 'a 'b "c")
        "1a2b" (clj:str 1 'a 2 "b")
        "2.0c" (clj:str 2.0 'c)
        "roughly 3.14"
        (let ((pi-string (clj:str "roughly" " " 3.14)))
          pi-string)
        "200 and a half"
        (let ((number-str (clj:str 200 " " 'and " " 'a " " 'half)))
          number-str)
        "eighty plus 1000"
        (let ((x "eighty ")
              (y "plus ")
              (z 1000))
          (clj:str x y z))
        "a1b2"
        (let ((a 'a)
              (b 'b)
              (one 1)
              (two 2))
          (clj:str a one b two))))

;; Based on OTP's queue_SUITE.
(deftest queue?
  (is-not (clj:queue? '[1 2 3 4 5]))
  (clj:as-> (queue:new) q
    (queue:in 1 q)
    (queue:in 2 q)
    (queue:in 3 q)
    (let ((`#(#(value 1) ,q1) (queue:out q)))
      q1)
    (queue:in 4 q)
    (queue:in 5 q)
    (is (clj:queue? q))))

(deftest empty?
  (is-not (clj:empty? '(1 2 3)))
  (IFF-MAPS (is-not (clj:empty? (call 'maps 'from_list '[#(a 1) #(b 2)]))))
  (is (clj:empty? ()))
  (IFF-MAPS (is (clj:empty? (call 'maps 'new)))))

(deftest every?
  (is-not (clj:every? #'zero?/1 '(0 0 0 0 1)))
  (is (clj:every? #'zero?/1 '(0 0 0 0 0))))

;; Based on lists_SUITE.
(deftest all?
  (is-error (lists:all 'func []))
  (flet ((pred (_a) 'true))
    (is (lists:all #'pred/1 [])))
  (let ((l '[1 2 3]))
    (is (lists:all (lambda (n) (clj:integer? n)) l))
    (is-not (lists:all (lambda (n) (=:= (rem n 2) 0)) l))))

(deftest any?
  (is-not (clj:any? #'zero?/1 '(1 1 1 1 1)))
  (is (clj:any? #'zero?/1 '(0 1 1 1 1))))

(deftest not-any?
  (is-not (clj:not-any? #'zero?/1 '(0 1 1 1 1)))
  (is (clj:not-any? #'zero?/1 '(1 1 1 1 1))))

;; Functional forms of clj module macros.
(defun zero? (x) (clj:zero? x))
(defun even? (x) (clj:even? x))

(deftest element?
  (are* [data] (not (clj:element? 'z data))
        '(a b c)
        (sets:from_list '(a b c))
        (ordsets:from_list '(a b c)))
  (are* [data] (clj:element? 'a data)
        '(a b c)
        (sets:from_list '(a b c))
        (ordsets:from_list '(a b c))))

;;; clj-seq-tests

(deftest seq
  (are* [x y] (ok? (is-match x y))
        '(1 2 3 4)    (clj:seq 4)
        '(2 3 4)      (clj:seq 2 4)
        '(2 4 6 8 10) (clj:seq 2 10 2)))

(deftest conj
  (is-match '(1 2 3 4) (clj:conj '(2 3 4) 1))
  (is-match '((1) 2 3 4) (clj:conj '(2 3 4) '(1)))
  (is-match '(1 2 3 4) (clj:conj '(4) 3 2 1))
  (is-match #(a b c d) (clj:conj #(a b c) 'd))
  (is-match #(a b c d) (clj:conj #(a) 'b 'c 'd))
  (is-match #(a b c #(d)) (clj:conj #(a b c) #(d)))
  (IFF-MAPS
   (is-equal (maps:from_list '(#(a 1) #(b 2)))
             (clj:conj (maps:from_list '(#(b 2)))
                       (maps:from_list '(#(a 1)))))))

(deftest drop
  (are* [x y] (ok? (is-match x y))
        '(6 7 8 9 10 11 12) (clj:drop 5    (lists:seq 1 12))
        ()                  (clj:drop 'all (lists:seq 1 12))))

(deftest take
  (are* [n xs] (ok? (is-match xs (clj:take n (lists:seq 1 12))))
        5    '(1 2 3 4 5)
        'all '(1 2 3 4 5 6 7 8 9 10 11 12))
  (is-equal '(1 2 3 4) (clj:take 4 (clj:range)))
  (is-error 'function_clause (clj:take -1 (clj:range))))

(deftest next-and-take
  (are* [x y] (ok? (is-match x y))

        '(1 6 21 66 201 606 1821 5466 16401 49206)
        (clj:take 10 (clj:next (lambda (x y) (* 3 (+ x y))) 1 1))

        '(1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536)
        (clj:take 17 (clj:next (lambda (x _) (* 2 x)) 1 1))

        '(1 4.0 25.0 676.0 458329.0 210066388900.0 4.4127887745906175e22)
        (clj:take 7 (clj:next (lambda (x _) (math:pow (+ x 1) 2)) 1 1)))
  (is-not-error (clj:take 3 (clj:next (match-lambda
                                         ([1 _] 2)
                                         ([2 _] 3)
                                         ([3 _] 4)
                                         ([4 _] (error 'overzealous-take)))))))

(deftest lazy-seq-and-take-and-drop
  (are* [x y] (ok? (is-match x y))

        ()
        (clj:lazy-seq ())

        ()
        (clj:take 1 (clj:lazy-seq ()))

        '(1 2)
        (clj:take 2 (clj:lazy-seq '(1 2 3)))

        '(1 2 3)
        (clj:take 3 (clj:lazy-seq '(1 2 3)))

        '(1 2 3)
        (clj:take 4 (clj:lazy-seq '(1 2 3))))

  (are* [x y] (ok? (is-match x y))

        ()
        (clj:take 1 (clj:drop 1 (clj:lazy-seq ())))

        ()
        (clj:take 1 (clj:drop 1 (clj:lazy-seq '(1))))

        ()
        (clj:take 1 (clj:drop 4 (clj:lazy-seq '(1 2 3))))

        '(2 3)
        (clj:take 2 (clj:drop 1 (clj:lazy-seq '(1 2 3))))

        '(2 3)
        (clj:take 2 (clj:drop 1 (clj:range 1)))))

(deftest lazy-seq-evaluation
  (flet ((form-list (last-element)
                    `(lists:map #'funcall/1
                                '((lambda () 1)
                                  (lambda () ,last-element)))))

    (let ((bad-list (form-list '(error 'evaluation_error)))
          (good-list (form-list 2)))

      (is-error 'evaluation_error (eval bad-list))
      (is-error 'evaluation_error (eval `(clj:take 1 (clj:lazy-seq ,bad-list))))

      (is-not-error (eval `(clj:lazy-seq ,bad-list)))
      (is-not-error (eval `(clj:lazy-seq (clj:lazy-seq ,bad-list))))

      (is-match '(1 2) (eval `(clj:take 3 (clj:lazy-seq ,good-list)))))))

(deftest cycle-and-take
  (are* [x y] (ok? (is-match x y))

        ()
        (clj:cycle ())

        '(1 1 1)
        (clj:take 3 (clj:cycle '(1)))

        '(1 2 3)
        (clj:take 3 (clj:cycle '(1 2 3)))

        '(1 2)
        (clj:take 2 (clj:cycle '(1 2 3)))

        '(1 2 3 1)
        (clj:take 4 (clj:cycle '(1 2 3)))

        '(1 2 3 1 2 3 1)
        (clj:take 7 (clj:cycle '(1 2 3))))

  (are* [x y] (ok? (is-match x y))

        '(3 3 3 3)
        (clj:take 4 (clj:cycle (clj:repeat 3)))

        '(3 4 5 6)
        (clj:take 4 (clj:cycle (clj:range 3)))

        '(1 2 3 1)
        (clj:take 4 (clj:cycle (clj:cycle '(1 2 3)))))

  (are* [x y] (ok? (is-match x y))

        ()
        (clj:cycle (clj:lazy-seq ()))

        '(1 1 1)
        (clj:take 3 (clj:cycle (clj:lazy-seq '(1))))

        '(1 2 3 1 2 3 1)
        (clj:take 7 (clj:cycle (clj:lazy-seq '(1 2 3))))

        '(1 2 3 1 2 3 1)
        (clj:take 7 (clj:lazy-seq (clj:cycle '(1 2 3))))

        '(2 3 2 3 2)
        (clj:take 5 (clj:cycle (clj:drop 1 (clj:lazy-seq '(1 2 3)))))))

(deftest range
  (are* [x y] (ok? (is-match x y))
        1 (car (funcall (clj:range)))
        2 (car (funcall (cdr (funcall (clj:range)))))
        3 (car (funcall (cdr (funcall (cdr (funcall (clj:range)))))))
        4 (clj:->> (funcall (clj:range))
                   (cdr) (funcall) (cdr) (funcall) (cdr) (funcall) (car))))

(deftest split-at
  (is-equal
   #((1 2 3) (4 5 6 7 8 9 10 11 12))
   (clj:split-at 3 '(1 2 3 4 5 6 7 8 9 10 11 12))))

(deftest partition
  (are* [n parts] (ok? (is-match parts (clj:partition n ())))
        0   ()
        1   ()
        100 ())
  (are* [n parts] (ok? (is-match parts (clj:partition n (lists:seq 1 12))))
        0 '(1 2 3 4 5 6 7 8 9 10 11 12)
        1 '((1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12))
        2 '((1 2) (3 4) (5 6) (7 8) (9 10) (11 12))
        5 '((1 2 3 4 5) (6 7 8 9 10))
        7 '((1 2 3 4 5 6 7))
        11 '((1 2 3 4 5 6 7 8 9 10 11))
        12 '((1 2 3 4 5 6 7 8 9 10 11 12))))

(deftest partition-all
  (are* [n pts] (ok? (is-match pts (clj:partition-all n (lists:seq 1 12))))
        5 '((1 2 3 4 5) (6 7 8 9 10) (11 12))
        7 '((1 2 3 4 5 6 7) (8 9 10 11 12))
        11 '((1 2 3 4 5 6 7 8 9 10 11) (12))))

(deftest interleave
  (is-equal '(a 1 b 2 c 3) (clj:interleave '(a b c) '(1 2 3))))

(deftest get-in-nth
  (let ((data '((1)
                (1 2 3)
                (1 2 (3 4 (5 6 (7 8 9)))))))
    (are* [val keys] (ok? (is-match val (clj:get-in data keys)))
          1          '(1 1)
          3          '(2 3)
          9          '(3 3 3 3 3)
          'undefined '(4)
          'undefined '(4 3 3 3))))

(deftest get-in-keys
  (let ((data '(#(key-1 val-1)
                #(key-2 val-2)
                #(key-3 (#(key-4 val-4)
                         #(key-5 val-5)
                         #(key-6 (#(key-7 val-7)
                                  #(key-8 val-8)
                                  #(key-9 val-9))))))))
    (are* [val keys] (ok? (is-match val (clj:get-in data keys)))
          'val-1     '(key-1)
          'val-5     '(key-3 key-5)
          'val-9     '(key-3 key-6 key-9)
          'undefined '(key-18)
          'undefined '(key-3 key-6 key-89)
          'undefined '(key-3 key-6 key-89 key-100))))

(deftest get-in-default
  (let ((data '(#(key-1 val-1)
                #(key-2 val-2)
                #(key-3 (#(key-4 val-4)
                         #(key-5 val-5)
                         #(key-6 (#(key-7 val-7)
                                  #(key-8 val-8)
                                  #(key-9 val-9))))))))
    (are* [keys] (=/= 'default (clj:get-in data keys 'default))
          '(key-1)
          '(key-3 key-5)
          '(key-3 key-6 key-9))
    (are* [keys] (ok? (is-match 'default (clj:get-in data keys 'default)))
          '(key-18)
          '(key-3 key-6 key-89)
          '(key-3 key-6 key-89 key-100))))

(deftest get-in-map
  (IFF-MAPS
   (let* ((c (call 'maps 'from_list '[#(c 123)]))
          (b (call 'maps 'from_list `[#(b ,c)]))
          (m (call 'maps 'from_list `[#(a ,b)])))
     (are* [data keys] (ok? (is-match data (clj:get-in m keys)))
           b          '(a)
           c          '(a b)
           123        '(a b c)
           'undefined '(x y z)))))

(deftest reduce
  (let ((lst '(1 2 3)))
    (are* [f] (ok? (is-match 6 (clj:reduce f lst)))
          (lambda (x acc) (+ x acc))
          (fun + 2)
          #'+/2)
    (are* [f] (ok? (is-match 6 (clj:reduce f 0 lst)))
          (lambda (x acc) (+ x acc))
          (fun + 2)
          #'+/2)))

(deftest repeat
  (are* [x y] (ok? (is-match x y))
        '(1 1 1)       (clj:repeat 3 1)
        '("xo" "xo")   (clj:repeat 2 "xo")
        ()             (clj:repeat 0 "oh noes")
        '(ok ok ok ok) (clj:repeat 4 'ok)
        '(1 1 1)       (clj:repeat 3 (lambda () 1))
        2              (length (clj:repeat 2 #'random:uniform/0)))
  (is-error 'function_clause (clj:repeat -1 0))
  (is-error 'function_clause (clj:repeat -1 (lambda () 1))))

;;; Other functions.

(deftest identity
  (are* [x] (ok? (is-match x (clj:identity x)))
        'atom
        0 42
        0.0 3.14
        #\c
        "" "abc"
        #"" #"abc"
        () (1 2)
        #() #(1 2))
  (are* [x] (ok? (is-equal x (clj:identity x)))
        (IFF-MAPS (call 'maps 'new))
        (IFF-MAPS (call 'maps 'from_list '[#(a 1) #(b 2)])))
  (is-equal 3 (clj:identity (+ 1 2)))
  (is (clj:identity (> 5 0))))

(deftest constantly
  (flet ((c0 (x) (funcall (clj:constantly 10) x)))
    (are* [x] (ok? (is-match 10 (c0 x)))
          'nil
          42
          "foo")))

(deftest inc
  (is-match 2 (clj:inc 1))
  (is-match 4.0 (clj:inc 3.0)))

(deftest dec
  (is-match 2 (clj:dec 3))
  (is-match 4.0 (clj:dec 5.0)))
