#lang racket/base

(require racket/system racket/port)

(provide run-pdflatex run-dvipdf-latex run-xelatex)

(define (run-pdflatex file [notify void]) (run file notify 'pdflatex))
(define (run-dvipdf-latex file [notify void]) 
  (parameterize ([function-name 'run-dvipdf-latex])
    (run file notify 'dvipdf)))
(define (run-xelatex file [notify void])
  (parameterize ([function-name 'run-xelatex])
    (run file notify 'xelatex)))

(define max-runs 5)
(define (run file notify type)
  (define latex-cmd-name (cond [(equal? type 'pdflatex) "pdflatex"]
                               [(equal? type 'dvipdf) "latex"]
                               [(equal? type 'xelatex) "xelatex"]
                               [else (err "unknown run type ~a" type)]))
  (define cmd
    (list (get-latex-binary latex-cmd-name)
          "-interaction=batchmode"
          (format "~a" file)))
  (define logfile (path-replace-suffix file #".log"))
  (define (run)
    (unless (parameterize ([current-output-port (open-output-nowhere)])
              (apply system* cmd))
      (unless (file-exists? logfile)
        (err "did not generate a log file at ~a" logfile))
      (call-with-input-file* logfile
        (lambda (log) (copy-port log (current-error-port))))
      (err "got error exit code")))
  (let loop ([n 0])
    (when (= n max-runs)
      (err "didn't get a stable result after ~a runs" n))
    (if (zero? n)
      (notify "running ~a on ~a" latex-cmd-name file)
      (notify " running ~a~a time"
              (add1 n) 
              (case (normalize-for-suffix (add1 n)) [(2) 'nd] [(3) 'rd] [else 'th])))
    (run)
    ;; see if we get a "Rerun" note, these seem to come in two flavors
    ;; * Label(s) may have changed. Rerun to get cross-references right.
    ;; * Package longtable Warning: Table widths have changed. Rerun LaTeX.
    (cond [(call-with-input-file* logfile
             (lambda (log) (regexp-match? #px#"changed\\.\\s+Rerun" log)))
           (loop (add1 n))]
          [(zero? n)
           (notify "WARNING: no \"Rerun\" found in first run of pdflatex for ~a"
                   file)]))
  (when (equal? type 'dvipdf)
    (define dvi-file (path-replace-suffix file #".dvi"))
    (define ps-file (path-replace-suffix file #".ps"))
    (unless (file-exists? dvi-file) (err "didn't find .dvi file"))
    (define dvips (get-latex-binary "dvips"))
    (define pstopdf (get-latex-binary "pstopdf"))
    (notify "running dvips on ~a" dvi-file)
    (define stderr (open-output-bytes))
    (unless (parameterize ([current-output-port (open-output-nowhere)]
                           [current-error-port stderr])
              (system* dvips dvi-file))
      (displayln (get-output-bytes stderr))
      (err "got error exit code"))
    (unless (parameterize ([current-output-port (open-output-nowhere)]
                           [current-error-port stderr])
              (system* pstopdf ps-file))
      (displayln (get-output-bytes stderr))
      (err "got error exit code")))
  (path-replace-suffix file #".pdf"))

(define (normalize-for-suffix n)
  (cond
    [(<= 10 n 20) 0]
    [else (modulo n 10)]))

(define (get-latex-binary name)
  (define ans
    (case (system-type)
      [(macosx) (or (find-executable-path name)
                    (for/or ([macosx-candidate-dir (in-list macosx-candidate-dirs)])
                      (define macosx-candidate (build-path macosx-candidate-dir name))
                      (and (file-exists? macosx-candidate)
                           macosx-candidate)))]
      [(windows) (or (find-executable-path name)
                     (find-executable-path (format "~a.exe" name)))]
      [(unix) (find-executable-path name)]))
  (unless ans
    (err (format "could not find a `~a' executable" name)))
  ans)

(define function-name (make-parameter 'run-pdflatex))
(define (err fmt . args) (apply error (function-name) fmt args))

;; under mac os x, gui apps do not get started with
;; a good path environment, so put likely candidates
;; for directories holding latex/pdflatex binaries
;; here so that the "scribble pdf" button is more 
;; likely to work in drracket
(define macosx-candidate-dirs
  '("/usr/texbin" "/Library/TeX/texbin"))
