(module syncheck-debug mzscheme
  (require (lib "pretty.ss")
           (lib "list.ss")
           (lib "class.ss")
           (lib "mred.ss" "mred"))
  
  (provide debug-origin)  ;; : syntax [syntax] -> void
                          ;; creates a frame for examining the 
                          ;; origin and source fields of an expanded sexp
                          ;; also the 'bound-in-source syntax property
  
  (define debug-origin
    (case-lambda 
     [(original-object) (debug-origin original-object (expand original-object))]
     [(original-object expanded-object)
      (define-values (expanded-datum stx-ht) (syntax-object->datum/ht expanded-object))
      
      (define output-text (make-object text%))
      (define output-port (make-text-port output-text))
      (define info-text (make-object text%))
      (define info-port (make-text-port info-text))
      
      ;; assume that there aren't any eq? sub structures, only eq? flat stuff (symbols, etc)
      ;; this is guaranteed by syntax-object->datum/ht
      (define range-start-ht (make-hash-table))
      (define range-ht (make-hash-table))
      (define original-output-port (current-output-port))
      (define (range-pretty-print-pre-hook x v)
        (hash-table-put! range-start-ht x (send output-text last-position)))
      (define (range-pretty-print-post-hook x v)
        (hash-table-put! range-ht x 
                         (cons
                          (cons
                           (hash-table-get range-start-ht x)
                           (send output-text last-position))
                          (hash-table-get range-ht x (λ () null)))))
      
      (define (make-modern text)
        (send text change-style
              (make-object style-delta% 'change-family 'modern)
              0
              (send text last-position)))
      
      (define dummy
        (begin (pretty-print (syntax-object->datum original-object) output-port)
               (newline output-port)
               (parameterize ([current-output-port output-port]
                              [pretty-print-pre-print-hook range-pretty-print-pre-hook]
                              [pretty-print-post-print-hook range-pretty-print-post-hook]
                              [pretty-print-columns 30])
                 (pretty-print expanded-datum))
               (make-modern output-text)))
      
      (define ranges 
        (quicksort 
         (apply append (hash-table-map range-ht (λ (k vs) (map (λ (v) (cons k v)) vs))))
         (λ (x y)
           (<= (- (car (cdr x)) (cdr (cdr x)))
               (- (car (cdr y)) (cdr (cdr y)))))))
      
      (define (show-info stx)
        (fprintf info-port "datum: ~s\nsource: ~a\nposition: ~s\noffset: ~s\noriginal: ~s\nbound-in-source: ~s\n\n"
                 (syntax-object->datum stx)
                 (syntax-source stx)
                 (syntax-position stx)
                 (syntax-span stx)
                 (syntax-original? stx)
                 (syntax-property stx 'bound-in-source))
        (let loop ([origin (syntax-property stx 'origin)])
          (cond
            [(pair? origin)
             (loop (car origin))
             (loop (cdr origin))]
            [(syntax? origin)
             (display "  " info-port)
             (display origin info-port)
             (newline info-port)
             (fprintf info-port
                      "  original? ~a\n  datum:\n  ~a\n\n"
                      (and (syntax? origin) (syntax-original? origin))
                      (and (syntax? origin) (syntax-object->datum origin)))]
            [else (void)])))
      
      (for-each
       (λ (range)
         (let* ([obj (car range)]
                [stx (hash-table-get stx-ht obj)]
                [start (cadr range)]
                [end (cddr range)])
           (when (syntax? stx)
             (send output-text set-clickback start end 
                   (λ _ 
                     (send info-text begin-edit-sequence)
                     (send info-text erase)
                     (show-info stx)
                     (make-modern info-text)
                     (send info-text end-edit-sequence))))))
       ranges)
      
      (newline output-port)
      (newline output-port)
      (let ([before (send output-text last-position)])
        (display "all" output-port)
        (send output-text set-clickback
              before
              (send output-text last-position)
              (λ _
                (send info-text begin-edit-sequence)
                (send info-text erase)
                (for-each (λ (rng)
                            (let ([stx (hash-table-get stx-ht (car rng))])
                              (when (syntax? stx)
                                (show-info stx))))
                          ranges)
                (make-modern info-text)
                (send info-text end-edit-sequence))))
      
      (let ()
        (define f (make-object frame% "Syntax 'origin Browser" #f 600 300))
        (define p (make-object horizontal-panel% f))
        (make-object editor-canvas% p output-text)
        (make-object editor-canvas% p info-text)
        (send f show #t))]))
  
  ;; build-ht : stx -> hash-table
  ;; the resulting hash-table maps from the each sub-object's to it's syntax.
  (define (syntax-object->datum/ht stx)
    (let ([ht (make-hash-table)])
      (values (let loop ([stx stx])
                (let ([obj (syntax-e stx)])
                  (cond
                    [(list? obj) 
                     (let ([res (map loop obj)])
                       (hash-table-put! ht res stx)
                       res)]
                    [(pair? obj) 
                     (let ([res (cons (loop (car obj))
                                      (loop (cdr obj)))])
                       (hash-table-put! ht res stx)
                       res)]
                    [(vector? obj) 
                     (let ([res (list->vector (map loop (vector->list obj)))])
                       (hash-table-put! ht res stx)
                       res)]
                    [else 
                     (let ([res (syntax-object->datum stx)])
                       (hash-table-put! ht res stx)
                       res)])))
              ht)))
  
  ;; make-text-port : text -> port
  ;; builds a port from a text object.  
  (define (make-text-port text)
    (let-values ([(in out) (make-pipe)])
      (thread
       (λ ()
         (let loop ()
           (let ([c (read-char in)])
             (unless (eof-object? c)
               (send text insert (string c)
                     (send text last-position)
                     (send text last-position))
               (loop))))))
      out)))
