#lang racket

(require racket/place typed-racket/optimizer/logging
         syntax/modcode data/queue)
(provide start-worker dr serialize-exn deserialize-exn s-exn? generate-log/place compile-path/place verbose?)

(define verbose? (make-parameter #f))

(struct s-exn (message) #:prefab)
(struct s-exn:fail s-exn () #:prefab)
(struct s-exn:fail:syntax s-exn:fail (exprs) #:prefab)
(struct s-exn:fail:contract s-exn:fail () #:prefab)

(define (serialize-exn e)
  (match e
    [(exn:fail:syntax msg _ exprs)
     (s-exn:fail:syntax msg (map syntax->datum exprs))]
    [(exn:fail:contract msg _)
     (s-exn:fail:contract msg)]
    [(exn:fail msg _)
     (s-exn:fail msg)]
    [(exn msg _)
     (s-exn msg)]))
(define (deserialize-exn e)
  (match e
    [(s-exn:fail:syntax msg exprs)
     (exn:fail:syntax msg (current-continuation-marks) 
                      (map (λ (e) (datum->syntax #f e)) exprs))]
    [(s-exn:fail:contract m)
     (exn:fail:contract m (current-continuation-marks))]    
    [(s-exn:fail m)
     (exn:fail m (current-continuation-marks))]
    [(s-exn m)
     (exn m (current-continuation-marks))]))

(define (dr p)
  (parameterize ([current-namespace (make-base-empty-namespace)])
    (dynamic-require `(file ,(if (string? p) p (path->string p))) #f)))


(define (start-worker get-ch name)
  (define verb (verbose?))
  (place/context ch
    (let loop ()
      (match (place-channel-get get-ch)                
        [(vector 'log name dir res)
         (dynamic-require 'typed-racket/core #f)
         (with-handlers ([exn:fail? 
                          (λ (e) (place-channel-put res (serialize-exn e)))])
           (define lg (generate-log/place name dir))
           (place-channel-put res lg))
         (loop)]
        [(vector 'compile path res)
         (with-handlers ([exn:fail? 
                          (λ (e) (place-channel-put res (serialize-exn e)))])
            (compile-path/place path)
            (place-channel-put res (void)))
         (loop)]
        [(vector p* res error?) 
         (define-values (path p b) (split-path p*))
         (with-handlers ([exn? (λ (e) (place-channel-put res (serialize-exn e)))])
           (parameterize ([read-accept-reader #t]
                          [current-load-relative-directory
                           (path->complete-path path)]
                          [current-directory path]
                          [current-output-port (open-output-nowhere)]                        
                          [error-display-handler (if error? void (error-display-handler))]) 
             (dr p)
             (place-channel-put res #t)))
         (loop)]))))

(define (compile-path/place path)
  (get-module-code
    path
    #:choose (lambda (src zo so) 'src)))


(define-namespace-anchor anchor)

(define (generate-log/place name dir)
  ;; some tests require other tests, so some fiddling is required
  (define file (simplify-path (build-path dir name)))
  (define orig-load/use-compiled (current-load/use-compiled))
  (define orig-use-compiled-file-paths (use-compiled-file-paths))
  (define full-tr-logs (make-queue))
  (define sub-tr-logs (make-queue))

  (define (test-load/use-compiled path name)
    (parameterize [(use-compiled-file-paths null)
                   (current-load/use-compiled reset-load/use-compiled)]
      (orig-load/use-compiled path name)))
  (define (reset-load/use-compiled path name)
    (parameterize [(use-compiled-file-paths orig-use-compiled-file-paths)
                   (current-load/use-compiled orig-load/use-compiled)]
        (with-tr-logging-to-queue
          sub-tr-logs
          (thunk
            (orig-load/use-compiled path name)))))

  (define regular-output
    (with-output-to-string
      (lambda ()
        (with-tr-logging-to-queue
          full-tr-logs
          (thunk
            (parameterize ([current-namespace (make-base-empty-namespace)]
                           [current-load/use-compiled test-load/use-compiled])
              (define orig-namespace (namespace-anchor->namespace anchor))
              (namespace-attach-module orig-namespace 'racket)
              (namespace-attach-module orig-namespace 'typed-racket/core)
              (dynamic-require file #f)))))))

  (define tr-logs
    (let ((tr-logs (queue->list full-tr-logs)))
      (sort
        (for/fold ((tr-logs tr-logs)) ((entry (in-queue sub-tr-logs)))
          (remove entry tr-logs))
        string<?)))

  (list tr-logs regular-output))
