(define-library (picrin repl)
  (import (scheme base)
          (scheme read)
          (scheme write)
          (scheme eval)
          (picrin base))

  (cond-expand
   ((library (picrin readline))
    (import (picrin readline)
            (picrin readline history)))
   (else
    (begin
      (define (readline str)
        (when (tty?)
          (display str)
          (flush-output-port))
        (read-line))
      (define (add-history str)
        #f))))

  (define (init-env)
    (eval
     '(import (scheme base)
              (scheme load)
              (scheme process-context)
              (scheme read)
              (scheme write)
              (scheme file)
              (scheme inexact)
              (scheme cxr)
              (scheme lazy)
              (scheme time)
              (scheme eval)
              (scheme r5rs)
              (picrin macro))
     "picrin.user"))

  (define (repeat x)
    (let ((p (list x)))
      (set-cdr! p p)
      p))

  (define (join xs delim)
    (cdr (apply append (map list (repeat delim) xs))))

  (define (string-join strings delim)
    (apply string-append (join strings delim)))

  (define (->string x)
    (call-with-port (open-output-string)
      (lambda (port)
        (write x port)
        (get-output-string port))))

  (define (print-error-object e)
    (define type (error-object-type e))
    (unless (eq? type '||)
      (display type)
      (display "-"))
    (display "error: ")
    (display (error-object-message e))
    (display ".")
    (define irritants (error-object-irritants e))
    (unless (null? irritants)
      (display " (irritants: ")
      (display (string-join (map ->string irritants) ", "))
      (display ")"))
    (newline))

  (define (repl)
    (init-env)
    (let loop ((buf ""))
      (let ((line (readline (if (equal? buf "") "> " ""))))
        (if (eof-object? line)
            (newline)                   ; exit
            (let ((str (string-append buf line "\n")))
              (add-history line)
              (call/cc
               (lambda (exit)
                 (with-exception-handler
                  (lambda (condition)
                    (if (error-object? condition)
                        (unless (equal? (error-object-message condition) "unexpected EOF")
                          (print-error-object condition)
                          (set! str ""))
                        (begin
                          (display "raise: ")
                          (write condition)
                          (newline)
                          (set! str "")))
                    (exit))
                  (lambda ()
                    (call-with-port (open-input-string str)
                      (lambda (port)
                        (let next ((expr (read port)))
                          (unless (eof-object? expr)
                            (write (eval expr "picrin.user"))
                            (newline)
                            (set! str "")
                            (next (read port))))))))))
              (loop str))))))

  (export repl))