#lang scheme
(require
 scheme/foreign)

(unsafe!)

(define strerror
  (get-ffi-obj
   "strerror" #f
   (_fun [errno : _int] -> [desc : _string])))

(define dup2
  (get-ffi-obj
   "dup2" #f
   (_fun #:save-errno 'posix
         [from : _int] [to : _int]
         -> [ok : _int]
         -> (when (negative? ok)
              (let ([errno (saved-errno)])
                (error 'dup2 "~a (~v)" (strerror errno) errno))))))

(define pipe
  (get-ffi-obj
   "pipe" #f
   (_fun #:save-errno 'posix
         [fds : (_ptr o (_list-struct _int _int))]
         -> [ok : _int]
         -> (if (not (negative? ok))
                (apply values fds)
                (let ([errno (saved-errno)])
                  (error 'pipe "~a (~v)" (strerror errno) errno))))))

(define make-fd-input-port
  (get-ffi-obj
   "scheme_make_fd_input_port" #f
   (_fun [fd : _int] [name : _scheme] [regular-file? : _bool] [win-textmode? : _bool]
         -> [port : _scheme])))

(define (capture-stdout!)
  (let-values ([(fd-in fd-out) (pipe)])
    (dup2 fd-out 1)
    (make-fd-input-port fd-in 'stdout #f #t)))

(define (redirect-stdout! [out (current-output-port)])
  (let ([in (capture-stdout!)])
    (thread (λ () (copy-port in out)))
    (void)))

(define puts
  (get-ffi-obj
   "puts" #f
   (_fun #:save-errno 'posix
         [msg : _string]
         -> [ok : _int]
         -> (when (negative? ok)
              (let ([errno (saved-errno)])
                (error 'puts "~a (~v)" (strerror errno) errno))))))

(provide
 capture-stdout!
 redirect-stdout!
 puts)
