#lang racket/base

(require "../main.rkt")

(require racket/match)
(require racket/set)

(require rackunit)
(require racket/runtime-path)
(require syntax/srcloc)

(define (encodeBinary v)
  (preserve->bytes v #:encode-embedded values #:canonicalizing? #t))
(define (looseEncodeBinary v)
  (preserve->bytes v #:encode-embedded values #:canonicalizing? #f #:write-annotations? #t))
(define (annotatedBinary v)
  (preserve->bytes v #:encode-embedded values #:canonicalizing? #t #:write-annotations? #t))

(define (decodeBinary bs #:allow-invalid-prefix? [allow-invalid-prefix? #f])
  (for [(i (in-range 1 (- (bytes-length bs) 1)))]
    (define result (bytes->preserve (subbytes bs 0 i)
                                    #:decode-embedded strip-annotations
                                    #:on-short (lambda () 'short) void))
    (when (and (not (eq? result 'short))
               (not (and allow-invalid-prefix? (void? result))))
      (error 'decodeBinary "~a-byte prefix of ~v does not read as short; result: ~v" i bs result)))
  (bytes->preserve bs
                   #:read-syntax? #t
                   #:decode-embedded strip-annotations
                   #:on-short (lambda () 'short)
                   void))

(define (decodeBinary/strip bs)
  (strip-annotations (decodeBinary bs)))

(define (encodeText v)
  (preserve->string v #:encode-embedded values))
(define (decodeText s)
  (string->preserve s #:read-syntax? #t #:decode-embedded strip-annotations))

(define (run-test-case nondet? t-name loc binary annotatedValue)
  (define stripped (strip-annotations annotatedValue))
  (let ((roundtripped (decodeBinary (encodeBinary annotatedValue)))) ;; expectation 1
    (check-false (has-any-annotations? roundtripped) loc)
    (check-equal? (strip-annotations roundtripped) stripped loc))
  (check-equal? (decodeBinary/strip binary) stripped loc) ;; expectation 2
  (check-equal? (decodeBinary binary) annotatedValue loc) ;; expectation 3
  (check-equal? (decodeBinary (annotatedBinary annotatedValue)) annotatedValue loc) ;; expectation 4
  (let ((roundtripped (decodeText (encodeText stripped)))) ;; expectation 5
    (check-false (has-any-annotations? roundtripped) loc)
    (check-equal? (strip-annotations roundtripped) stripped loc))
  (check-equal? (decodeText (encodeText annotatedValue)) annotatedValue loc) ;; expectation 6
  (check-equal? (annotatedBinary annotatedValue) binary loc) ;; expectation 7
  (unless nondet? (check-equal? (looseEncodeBinary annotatedValue) binary loc)) ;; expectation 8
  )

(define-runtime-path samples-pr-path "./samples.pr")
(let* ((testfile (call-with-input-file samples-pr-path
                   (lambda (p)
                     (port-count-lines! p)
                     (read-preserve p
                                    #:read-syntax? #t
                                    #:decode-embedded strip-annotations
                                    #:source samples-pr-path)))))
  (match-define (peel-annotations `#s(TestCases ,tests)) testfile)
  (for [((t-name* t*) (in-hash (annotated-item tests)))]
    (define t-name (strip-annotations t-name*))
    (define loc (format "Test case '~a' (~a)" t-name (source-location->string (annotated-srcloc t*))))
    (define (fail-test fmt . args)
      (fail (format "~a: ~a" loc (apply format fmt args))))
    (log-debug "~a" loc)
    (match (peel-annotations t*)
      [`#s(Test ,(strip-annotations binary) ,annotatedValue)
       (run-test-case #f t-name loc binary annotatedValue)]
      [`#s(NondeterministicTest ,(strip-annotations binary) ,annotatedValue)
       (run-test-case #t t-name loc binary annotatedValue)]
      [`#s(ParseError ,(strip-annotations str))
       (with-handlers [(exn:fail:read:eof?
                        (lambda (e) (fail-test "Unexpected EOF: ~e" e)))
                       (exn:fail:read?
                        (lambda (e) 'ok))
                       ((lambda (e) #t)
                        (lambda (e) (fail-test "Unexpected exception: ~e" e)))]
         (string->preserve str)
         (fail-test "Unexpected success"))]
      [(or `#s(ParseShort ,(strip-annotations str))
           `#s(ParseEOF ,(strip-annotations str)))
       (with-handlers [(exn:fail:read:eof? (lambda (e) 'ok))
                       ((lambda (e) #t)
                        (lambda (e) (fail-test "Unexpected exception: ~e" e)))]
         (string->preserve str)
         (fail-test "Unexpected success"))]
      [(or `#s(DecodeShort ,(strip-annotations bs))
           `#s(DecodeEOF ,(strip-annotations bs)))
       (check-eq? (decodeBinary bs) 'short loc)]
      [`#s(DecodeError ,(strip-annotations bs))
       (check-true (void? (decodeBinary bs #:allow-invalid-prefix? #t)) loc)]
      [_
       (fail-test "Unknown test case kind")]))
  )
