#lang racket/base
(require racket/file
         syntax/parse
         syntax/srcloc
         (planet neil/overeasy:2)
         "progedit.rkt")
(test-section 'progedit
  (test-section 'sanity-check-positions
    (test 'source-location-position
          (source-location-position (read-syntax 'foo (open-input-string "(aaa)")))
          1)
    (test 'file-position
          (let ((p (open-input-string "(aaa)"))) (port-count-lines! p) (read-char p) (file-position p))
          1))
  (test-section 'jane
    (define (%define-name-as-jane in-str)
      (let ((in (open-input-string in-str)))
        (let loop ((name-stx #f))
          (let ((stx (read-syntax 'my-source in)))
            (if (eof-object? stx)
                (let-values (((inserts replaces)
                              (if name-stx
                                  (values '()
                                          `((,name-stx ,#'"Jane")))
                                  (values `((#f #\newline
                                                ,#'(define name "Jane")
                                                #\newline))
                                          '())))
                             ((out) (open-output-string)))
                  (progedit (open-input-string in-str)
                            out
                            #:inserts  inserts
                            #:replaces replaces)
                  (get-output-string out))
                (syntax-parse stx
                  (((~datum define) (~datum name) VAL)
                   (if name-stx
                       (raise-syntax-error
                        '%define-name-as-jane
                        "(define name VAL) occurred multiple times"
                        stx
                        #f
                        (list name-stx))
                       (loop #'VAL)))
                  (_ (loop name-stx))))))))
    (test 'name-is-defined
          (%define-name-as-jane
           "(define honorific \"Dr.\")\n;; House.\n\n(define name \"John\")\n\n(define age 29)\n")
          "(define honorific \"Dr.\")\n;; House.\n\n(define name \"Jane\")\n\n(define age 29)\n")
    (test 'name-is-not-defined
          (%define-name-as-jane
           "(define honorific \"Dr.\")\n;; House.\n\n(define age 29)\n")
          "(define honorific \"Dr.\")\n;; House.\n\n(define age 29)\n\n(define name \"Jane\")\n"))
  (test-section 'tricky-ordering
    (define (%aaa-bbb-ccc proc)
      (let ((in-str "(aaa bbb ccc)"))
        (let ((stx (read-syntax 'my-source (open-input-string in-str))))
          (syntax-parse stx
            ((A B C)
             (let ((out (open-output-string)))
               (proc (open-input-string in-str) out #'A #'B #'C)
               (get-output-string out)))))))
    (test 'insert-after-replace
          (%aaa-bbb-ccc
           (lambda (in out a-stx b-stx c-stx)
             (progedit in
                       out
                       #:inserts  `(((after ,b-stx) " After"))
                       #:replaces `((,b-stx "Bee")))))
          "(aaa Bee After ccc)")
    (test 'insert-before-replace
          (%aaa-bbb-ccc
           (lambda (in out a-stx b-stx c-stx)
             (progedit in
                       out
                       #:inserts  `(((before ,b-stx) "Before "))
                       #:replaces `((,b-stx "Bee")))))
          "(aaa Before Bee ccc)")
    (test 'inserts-before-after-delete
          (%aaa-bbb-ccc
           (lambda (in out a-stx b-stx c-stx)
             (progedit in
                       out
                       #:deletes `(,b-stx)
                       #:inserts `(((before ,b-stx) "Before ")
                                   ((after  ,b-stx) "After")))))
          "(aaa Before After ccc)")
    (test 'inserts-after-before-delete
          (%aaa-bbb-ccc
           (lambda (in out a-stx b-stx c-stx)
             (progedit in
                       out
                       #:deletes `(,b-stx)
                       #:inserts `(((after  ,b-stx) "After")
                                   ((before ,b-stx) "Before ")))))
          "(aaa Before After ccc)"))
  (test-section 'progedit-file
    (test 'basic
          (let ((actual-path "temporary-progedit-test-1")
                (backup-path "temporary-progedit-test-1-backup"))
            (dynamic-wind
              void
              (lambda ()
                (display-to-file ";; Comment\n(aaa bbb ccc)\n" actual-path #:exists 'replace)
                (with-handlers ((exn:fail? void)) (delete-file backup-path))
                (let ((result (progedit-file
                               actual-path
                               #:read
                               (lambda (in)
                                 (syntax-parse (read-syntax 'my-source in)
                                   ((A B C)
                                    (let ((out (open-output-string)))
                                      (values #'A #'B #'C)))))
                               #:write
                               (lambda (in out a-stx b-stx c-stx)
                                 (progedit in
                                           out
                                           #:inserts  `(((after  ,b-stx) " After")
                                                        ((before ,b-stx) "Before ")
                                                        (1 "#lang foo" #\newline #\newline)
                                                        (#f "\n;; EOF\n"))
                                           #:replaces `((,b-stx "Replaced")))
                                 42)
                               #:backup (lambda args backup-path))))
                  (values result
                          (file->string actual-path))))
              (lambda ()
                (with-handlers ((exn:fail? void)) (delete-file actual-path))
                (with-handlers ((exn:fail? void)) (delete-file backup-path)))))
          (values 42
                  "#lang foo\n\n;; Comment\n(aaa Before Replaced After ccc)\n\n;; EOF\n"))
    (test 'aborted-without-writing
          (let ((actual-path "temporary-progedit-test-1")
                (backup-path "temporary-progedit-test-1-backup"))
            (dynamic-wind
              void
              (lambda ()
                (display-to-file "original" actual-path #:exists 'replace)
                (let ((identity-before (file-or-directory-identity actual-path)))
                  (with-handlers ((exn:fail? void)) (delete-file backup-path))
                  (let/ec abort-the-progedit-ec
                    (progedit-file actual-path
                                   #:read
                                   (lambda (in)
                                     (abort-the-progedit-ec))
                                   #:write
                                   (lambda (in out . args)
                                     (display "stomped" out #:exists 'replace)
                                     (flush-output out))
                                   #:backup
                                   (lambda args backup-path)))
                  (values (if (equal? (file-or-directory-identity actual-path)
                                      identity-before)
                              'identity-same
                              'identity-changed)
                          (file->string actual-path))))
              (lambda ()
                (with-handlers ((exn:fail? void)) (delete-file actual-path))
                (with-handlers ((exn:fail? void)) (delete-file backup-path)))))
          (values 'identity-same
                  "original"))
      
  ))