#lang racket

(require "test-util.rkt"
         drracket/check-syntax
         redex/pict
         redex/reduction-semantics
         (for-syntax setup/path-to-relative)
         setup/path-to-relative)

(reset-count)

(define-syntax (identifier stx)
  (syntax-case stx ()
    [(_ x)
     (identifier? #'x)
     #`(let ([p (open-input-string (format "~s" 'x))])
         (port-count-lines! p)
         (set-port-next-location! 
          p
          #,(syntax-line #'x)
          #,(syntax-column #'x)
          #,(syntax-position #'x))
         (read-syntax '#,(and (path? (syntax-source #'x))
                              (path->relative-string/library (syntax-source #'x)))
                      p))]))

(define (source stx)
  (list (and (path? (syntax-source stx))
             (path->relative-string/library (syntax-source stx)))
        (syntax-line stx)
        (syntax-column stx)))

(define (expected-arrows bindings)
  (for/fold ([arrs (set)]) ([binding bindings])
            (for/fold ([arrs arrs]) ([bound (cdr binding)])
                      (set-add arrs
                               (list (source (car binding))
                                     (source bound))))))

(define (expected-rename-class binding)
  (apply set (map source binding)))

(define collector%
  (class (annotations-mixin object%)
    (super-new)
    (define/override (syncheck:find-source-object stx)
      stx)
    (define/override (syncheck:add-rename-menu id
                                               all-ids
                                               new-name-interferes?)
      (match all-ids
        [(list (list ids _ _) ...)
         (set! renames (cons ids renames))]))
    (define renames '())
    (define/public (collected-rename-class stx)
      (for/fold ([class (set)]) ([ids renames])
                (if (for/or ([id ids])
                            (equal? (source stx) (source id)))
                    (set-union class (apply set (map source ids)))
                    class)))
    (define/override (syncheck:add-arrow start-source-obj
                                         start-left
                                         start-right
                                         end-source-obj
                                         end-left
                                         end-right
                                         actual?
                                         phase-level)
      (set! arrows 
            (set-add arrows 
                     (list (source start-source-obj)
                           (source end-source-obj)))))
    (define arrows (set))
    (define/public (collected-arrows) arrows)))

(define-namespace-anchor module-anchor)
(define module-namespace 
  (namespace-anchor->namespace module-anchor))

;; judgment forms
(let ([annotations (new collector%)])
  (define-values (add-syntax done)
    (make-traversal module-namespace #f))
  
  (define language-def-name (identifier L))
  (define language-use-name (identifier L))
  
  (define mode-name (identifier J))
  (define contract-name (identifier J))
  (define conclusion-name (identifier J))
  (define premise-name (identifier J))
  (define render-name (identifier J))
  (define holds-name (identifier J))
  
  (define language-binding 
    (list language-def-name language-use-name))
  (define judgment-form-binding
    (list mode-name contract-name conclusion-name premise-name render-name holds-name))
  
  (parameterize ([current-annotations annotations]
                 [current-namespace module-namespace])
    (add-syntax
     (expand #`(let ()
                 (define-language #,language-def-name)
                 (define-judgment-form #,language-use-name
                   #:mode (#,mode-name)
                   #:contract (#,contract-name)
                   [(#,conclusion-name)
                    (#,premise-name)])
                 (render-judgment-form #,render-name)
                 (judgment-holds (#,holds-name)))))
    (done))
  
  (test (send annotations collected-arrows)
        (expected-arrows
         (list language-binding judgment-form-binding)))
  (test (send annotations collected-rename-class language-def-name)
        (expected-rename-class language-binding))
  (test (send annotations collected-rename-class mode-name)
        (expected-rename-class judgment-form-binding)))

;; metafunctions
(let ([annotations (new collector%)])
  (define-values (add-syntax done)
    (make-traversal module-namespace #f))
  
  (define language-def-name (identifier L))
  (define language-use-name (identifier L))
  
  (define contract-name (identifier f))
  (define lhs-name (identifier f))
  (define rhs-name (identifier f))
  (define render-name (identifier f))
  (define term-name (identifier f))
  
  (define language-binding
    (list language-def-name language-use-name))
  (define metafunction-binding
    (list contract-name lhs-name rhs-name render-name term-name))
  
  (parameterize ([current-annotations annotations]
                 [current-namespace module-namespace])
    (add-syntax
     (expand #`(let ()
                 (define-language #,language-def-name)
                 (define-metafunction #,language-use-name
                   #,contract-name : () -> ()
                   [(#,lhs-name) (#,rhs-name)])
                 (render-metafunction #,render-name)
                 (term (#,term-name)))))
    (done))
  
  (test (send annotations collected-arrows)
        (expected-arrows
         (list language-binding metafunction-binding)))
  (test (send annotations collected-rename-class language-def-name)
        (expected-rename-class language-binding))
  (test (send annotations collected-rename-class contract-name)
        (expected-rename-class metafunction-binding)))

;; define-term
(let ([annotations (new collector%)])
  (define-values (add-syntax done)
    (make-traversal module-namespace #f))
  
  (define def-name (identifier x))
  (define use-name (identifier x))
  
  (parameterize ([current-annotations annotations]
                 [current-namespace module-namespace])
    (add-syntax
     (expand #`(let ()
                 (define-term #,def-name a)
                 (term (#,use-name b)))))
    (done))
  
  (test (send annotations collected-rename-class def-name)
        (expected-rename-class (list def-name use-name)))
  (test (send annotations collected-rename-class use-name)
        (expected-rename-class (list def-name use-name))))

(print-tests-passed 'check-syntax-test.rkt)
