; Convenience flow control macros
(defmacro when (condition body-head &rest body)
  "If condition is true, evaluates the expressions in body, otherwise returns nil"
  `(if ,condition (progn ,body-head ,@body))
  )

(defmacro unless (condition body-head &rest body)
  "If condition is false, evaluates the expressions in body, otherwise returns nil"
  `(if (not ,condition) (progn ,body-head ,@body))
  )

(defmacro when-let (bindings &rest body)
  "If expression evaluates to a trueish value, evaluates (let (binding expression) body...)"
  (let (output nil binding-names nil)
    (while bindings
      (setq binding-names (cons (car bindings) binding-names))
      (setq output (append output (list (car bindings) (cadr bindings))))
      (setq bindings (cddr bindings))
      )
    (unless binding-names
      (error "No bindings provided in the (when-let ...) form"))
    `(let ,output
       (if (and ,@binding-names)
         ,@body
         )
       )
    )
  )

; List functions
; TODO most of those could be ported to Rust for performance
; TODO tail recursion
(defun filter (f xs)
  "Returns a new list, formed from elements of xs matching predicate f"
  (cond
    ((nil? xs) nil)
    ((f (car xs)) (cons (car xs) (filter f (cdr xs))))
    (&otherwise (filter f (cdr xs)))
    )
  )
(defun map (f xs)
  "Returns a new list, with f applied to elements of xs"
  (if (cons? xs)
    (cons (f (car xs)) (map f (cdr xs)))
    nil
    )
  )
(defun flatmap (f xs)
  "Returns a new list, with f applied to list elements of xs, then appended together"
  (let (ys nil)
    (while (cons? xs)
      (setq ys (append ys (f (car xs))))
      (setq xs (cdr xs)))
    ys))
(defun fold (f acc xs)
  "Returns the value calculated by sequentially applying f to acc and each element of xs"
  (while (cons? xs)
    (setq acc (f acc (car xs)))
    (setq xs (cdr xs)))
  acc)

(defun reverse (xs)
  "Returns the reversed list"
  (let (ys nil)
    (while (cons? xs)
      (setq ys (cons (car xs) ys))
      (setq xs (cdr xs)))
    ys))
(defun unzip (xs)
  "Converts a list of lists into a single-level list"
  (flatmap identity xs))

; Logic functions
(defun or_ (forms)
  (cond
    ((nil? forms) #F)
    ((nil? (cdr forms)) (car forms))
    (&otherwise
      (let (tmp (gensym))
        `(let (,tmp ,(car forms))
           (if ,tmp ,tmp ,(or_ (cdr forms)))
           )
        )
      )
    ))
(defmacro or (&rest forms)
  "Sequentially evaluates the forms, stopping and returning a form if it's trueish"
  ;; (or x y z) -> (cond (x x) (y y) (z z) (&otherwise #t))
  (or_ forms))
(defun and_ (forms)
  (cond
    ((nil? forms) #T)
    ((nil? (cdr forms)) (car forms))
    (&otherwise `(if ,(car forms) ,(and_ (cdr forms))))
    ))
(defmacro and (&rest forms)
  "Sequentially evaluates the forms, returning the last form if all the forms are trueish"
  ;; (and x y z) -> (if x (if y (if z z)))
  (and_ forms)
  )

; Result handling functions
(defun result/ok? (x)
  "Returns #t if x is an ok"
  (= 'ok (car x))
  )
(defun result/err? (x)
  "Returns #t if x is an error"
  (= 'err (car x))
  )
(defun result/map (f x)
  "If x is an ok, applies f to its value, otherwise does nothing"
  (if (result/ok? x)
    `(ok ,(f (cadr x)))
    x
    )
  )
(defun result/map-err (f x)
  "If x is an error, applies f to its value, otherwise does nothing"
  (if (result/err? x)
    `(err ,(f (cadr x)))
    x
    )
  )

; Convenience error handling macros
(defmacro catch (expression error-symbol handler)
  "Evaluates expression, running the handler if evaluation fails, introducing error-symbol as error"
  (let (eval-result-symbol (gensym))
    `(let (,eval-result-symbol (eval (quote ,expression)))
       (cond
         ((result/ok? ,eval-result-symbol) (cadr ,eval-result-symbol))
         (&otherwise
           (let (,error-symbol (cadr ,eval-result-symbol))
             ,handler
             )
           )
         )
       )
    )
  )

(defmacro compile-debug (expression)
  "Prints the input expression during macro expansion/compile time and evaluates the expression in runtime"
  (eprint expression)
  expression
  )
(defmacro runtime-debug (expression)
  "Prints the input expression and evaluates it in runtime"
  `(progn
     (eprint (quote ,expression))
     ,expression
     )
  )

; Convenience list functions
(defun caar (x) "Alias for (car (car x))" (car (car x)))
(defun cadr (x) "Alias for (car (cdr x))" (car (cdr x)))
(defun cdar (x) "Alias for (cdr (car x))" (cdr (car x)))
(defun cddr (x) "Alias for (cdr (cdr x))" (cdr (cdr x)))

(defun caddr (x) "Alias for (car (cdr (cdr x)))" (car (cdr (cdr x))))
(defun cadar (x) "Alias for (car (cdr (car x)))" (car (cdr (car x))))
