#lang at-exp racket/base
(require rackunit
         syntax-color/racket-lexer
         syntax-color/scribble-lexer
         syntax-color/lexer-contract
         unstable/options
         unstable/2d/private/lexer)

(check-equal? (cropped-regions 0 10 '()) '())
(check-equal? (cropped-regions 0 10 '((0 . 10))) '((0 . 10)))
(check-equal? (cropped-regions 0 10 '((0 . 5) (7 . 10))) '((7 . 10) (0 . 5)))
(check-equal? (cropped-regions 0 10 '((-1 . 4))) '((0 . 4)))
(check-equal? (cropped-regions 0 10 '((-4 . -3))) '())
(check-equal? (cropped-regions 0 10 '((20 . 30))) '())
(check-equal? (cropped-regions 0 10 '((1 . 4) (5 . 20))) '((5 . 10) (1 . 4)))
(check-equal? (cropped-regions 0 10 '((-5 . 10))) '((0 . 10)))
(check-equal? (cropped-regions 13 37 '((11 . 13))) '())

(define (run-lexer #:sub-lexer [sub-lexer/no-ex racket-lexer] . strs)
  (define sub-lexer (if (has-option? sub-lexer/no-ex)
                        (exercise-option sub-lexer/no-ex)
                        sub-lexer/no-ex))
  (define port (open-input-string (apply string-append strs)))
  (port-count-lines! port)
  (define the-lexer (exercise-option (2d-lexer sub-lexer)))
  (let loop ([mode #f])
    (define-values (val tok paren start end backup new-mode) 
      (the-lexer port 0 mode))
    (cons (list val tok paren start end backup)
          (cond
            [(equal? tok 'eof) '()]
            [else (loop (if (dont-stop? new-mode)
                            (dont-stop-val new-mode)
                            new-mode))]))))

(check-equal?
 (run-lexer "1234\n#2d\n")
 `(("1234" constant #f 1 5 0)
   ("\n" white-space #f 5 6 0) 
   ("#2d\n" error #f 6 10 0) 
   (,eof eof #f #f #f 0)))

(check-equal?
 (run-lexer "#2dsomething")
 `(("#2dsomething" error #f 1 13 0)
   (,eof eof #f #f #f 0)))

(check-equal?
 (run-lexer "#2dsomething\n")
 `(("#2dsomething\n" error #f 1 14 0)
   (,eof eof #f #f #f 0)))

(check-equal?
 (run-lexer "#2dsomething\n╔═══╗\n║   ║")
 `(("#2dsomething\n╔═══╗\n║   ║" error #f 1 25 0)
   (,eof eof #f #f #f 0)))

(check-equal?
 (run-lexer "#2dsomething\n  \n")
 `(("#2dsomething" hash-colon-keyword #f 1 13 0)
   ("\n" white-space #f 13 14 13) 
   ("  \n" error #f 14 17 14)
   (,eof eof #f #f #f 0)))

(check-equal?
 @run-lexer{#2d
            ╔══╦═══╗
            ║+ ║"a"║
            ╠══╬═══╣
            ║34║"b"║
            ╚══╩═══╝}
 `(("#2d" hash-colon-keyword #f 1 4 0)
   ("\n" white-space #f 4 5 4)
   ("╔══╦═══╗" parenthesis #f 5 13 5)
   (" " white-space #f 13 14 13)
   ("║" parenthesis #f 14 15 14)
   ("+" symbol #f 15 16 15)
   (" " white-space #f 16 17 16)
   ("║" parenthesis #f 17 18 17)
   ("\"a\"" string #f 18 21 18)
   ("║" parenthesis #f 21 22 21)
   (" " white-space #f 22 23 22)
   ("╠══╬═══╣" parenthesis #f 23 31 23)
   (" " white-space #f 31 32 31)
   ("║" parenthesis #f 32 33 32)
   ("34" constant #f 33 35 33)
   ("║" parenthesis #f 35 36 35)
   ("\"b\"" string #f 36 39 36)
   ("║" parenthesis #f 39 40 39)
   (" " white-space #f 40 41 40)
   ("╚══╩═══╝" parenthesis #f 41 49 41)
   (,eof eof #f #f #f 0)))

(check-equal?
 @run-lexer["#2d\r\n"]{╔══╦═══╗
                       ║+ ║"a"║
                       ╠══╬═══╣
                       ║34║"b"║
                       ╚══╩═══╝}
 `(("#2d" hash-colon-keyword #f 1 4 0)
   ("\r\n" white-space #f 4 5 4)
   ("╔══╦═══╗" parenthesis #f 5 13 5)
   (" " white-space #f 13 14 13)
   ("║" parenthesis #f 14 15 14)
   ("+" symbol #f 15 16 15)
   (" " white-space #f 16 17 16)
   ("║" parenthesis #f 17 18 17)
   ("\"a\"" string #f 18 21 18)
   ("║" parenthesis #f 21 22 21)
   (" " white-space #f 22 23 22)
   ("╠══╬═══╣" parenthesis #f 23 31 23)
   (" " white-space #f 31 32 31)
   ("║" parenthesis #f 32 33 32)
   ("34" constant #f 33 35 33)
   ("║" parenthesis #f 35 36 35)
   ("\"b\"" string #f 36 39 36)
   ("║" parenthesis #f 39 40 39)
   (" " white-space #f 40 41 40)
   ("╚══╩═══╝" parenthesis #f 41 49 41)
   (,eof eof #f #f #f 0)))

(printf "skipping the \\r\\n test: see the cracks-filled-in-tokens definition for where the bug lies\n")
#;
(check-equal?
 (run-lexer "#2d\r\n"
            "╔══╦═══╗\r\n"
            "║+ ║abc║\r\n"
            "╠══╬═══╣\r\n"
            "║34║def║\r\n"
            "╚══╩═══╝\r\n")
 `(("#2d" hash-colon-keyword #f 1 4 0)
   ("\r\n" white-space #f 4 5 4)
   ("╔══╦═══╗" parenthesis #f 5 13 5)
   (" " white-space #f 13 14 13)
   ("║" parenthesis #f 14 15 14)
   ("+" symbol #f 15 16 15)
   (" " white-space #f 16 17 16)
   ("║" parenthesis #f 17 18 17)
   ("\"a\"" string #f 18 21 18)
   ("║" parenthesis #f 21 22 21)
   (" " white-space #f 22 23 22)
   ("╠══╬═══╣" parenthesis #f 23 31 23)
   ("  " white-space #f 31 32 31)
   ("║" parenthesis #f 32 33 32)
   ("34" constant #f 33 35 33)
   ("║" parenthesis #f 35 36 35)
   ("\"b\"" string #f 36 39 36)
   ("║" parenthesis #f 39 40 39)
   (" " white-space #f 40 41 40)
   ("╚══╩═══╝" parenthesis #f 41 49 41)
   (,eof eof #f #f #f 0)))

;; test tokens that cross lines (and thus need cropping)
(check-equal?
 @run-lexer{#2d
            ╔══╦═══╗
            ║+ ║"a ║
            ║+ ║ a"║
            ╠══╬═══╣
            ║34║"b"║
            ╚══╩═══╝}
 `(("#2d" hash-colon-keyword #f 1 4 0)
   ("\n" white-space #f 4 5 4)
   ("╔══╦═══╗" parenthesis #f 5 13 5)
   (" " white-space #f 13 14 13)
   ("║" parenthesis #f 14 15 14)
   ("+" symbol #f 15 16 15)
   (" " white-space #f 16 17 16)
   ("║" parenthesis #f 17 18 17)
   ("\"a " string #f 18 21 18)
   ("║" parenthesis #f 21 22 21)
   (" " white-space #f 22 23 22)
   ("║" parenthesis #f 23 24 23)
   ("+" symbol #f 24 25 24)
   (" " white-space #f 25 26 25)
   ("║" parenthesis #f 26 27 26)
   (" a\"" string #f 27 30 27)
   ("║" parenthesis #f 30 31 30)
   (" " white-space #f 31 32 31)
   ("╠══╬═══╣" parenthesis #f 32 40 32)
   (" " white-space #f 40 41 40)
   ("║" parenthesis #f 41 42 41)
   ("34" constant #f 42 44 42)
   ("║" parenthesis #f 44 45 44)
   ("\"b\"" string #f 45 48 45)
   ("║" parenthesis #f 48 49 48)
   (" " white-space #f 49 50 49)
   ("╚══╩═══╝" parenthesis #f 50 58 50)
   (,eof eof #f #f #f 0)))

(check-equal?
 @run-lexer{#2d
            ╔══╦═══╗
            ║+ ║ "a"║
            ╠══╬═══╣
            ║34║"b"║
            ╚══╩═══╝}
 `(("#2d" hash-colon-keyword #f 1 4 0)
   ("\n" white-space #f 4 5 4)
   ("╔══╦═══╗\n║+ ║ \"a" no-color #f 5 21 5)
   ("\"║\n╠══╬═══╣\n║34║\"b\"║\n╚══╩═══╝" error #f 21 50 21)
   (,eof eof #f #f #f 0)))

(check-equal?
 @run-lexer["#2d\r\n"]{╔══╦═══╗
                       ║+ ║ "a"║
                       ╠══╬═══╣
                       ║34║"b"║
                       ╚══╩═══╝}
 `(("#2d" hash-colon-keyword #f 1 4 0)
   ("\r\n" white-space #f 4 5 4)
   ("╔══╦═══╗\n║+ ║ \"a" no-color #f 5 21 5)
   ("\"║\n╠══╬═══╣\n║34║\"b\"║\n╚══╩═══╝" error #f 21 50 21)
   (,eof eof #f #f #f 0)))

(check-equal?
 (run-lexer "   #2d\n"
            "   ╔═╦═╗\n"
            "   ║1║2║\n"
            "   ╠═╬═╣\n"
            "   ║3║4║\n"
            "   ╚═╩═╝\n")
  `(("   " white-space #f 1 4 0)
    ("#2d" hash-colon-keyword #f 4 7 0)
    ("\n" white-space #f 7 8 7)
    ("   " white-space #f 8 11 8)
    ("╔═╦═╗" parenthesis #f 11 16 11)
    ("    " white-space #f 16 20 16)
    ("║" parenthesis #f 20 21 20)
    ("1" constant #f 21 22 21)
    ("║" parenthesis #f 22 23 22)
    ("2" constant #f 23 24 23)
    ("║" parenthesis #f 24 25 24)
    ("    " white-space #f 25 29 25)
    ("╠═╬═╣" parenthesis #f 29 34 29)
    ("    " white-space #f 34 38 34)
    ("║" parenthesis #f 38 39 38)
    ("3" constant #f 39 40 39)
    ("║" parenthesis #f 40 41 40)
    ("4" constant #f 41 42 41)
    ("║" parenthesis #f 42 43 42)
    ("    " white-space #f 43 47 43) ("╚═╩═╝" parenthesis #f 47 52 47)
    ("\n" white-space #f 52 53 0)
    (,eof eof #f #f #f 0)))

(define-values (dont-care dont-care?)
  (let ()
    (struct dont-care ())
    (values (dont-care) dont-care?)))

(define (equal?/dont-care x y)
  (let loop ([x x][y y])
    (cond
      [(or (dont-care? x) (dont-care? y))
       #t]
      [(and (pair? x) (pair? y))
       (and (loop (car x) (car y))
            (loop (cdr x) (cdr y)))]
      [else (equal? x y)])))

(check-pred
 (λ (x) 
   (equal?/dont-care 
    x
    `(("#2d" hash-colon-keyword #f 1 4 0)
      ("\n" white-space #f 4 5 4)
      ("╔═════╦═══════╗" parenthesis #f 5 20 5)
      (" " white-space #f 20 21 20)
      ("║" parenthesis #f 21 22 21)
      ("@" parenthesis #f 22 23 22)
      ("f" symbol #f 23 24 23)
      ("{" parenthesis |{| 24 25 24)
      (,dont-care string #f 25 26 25)
      ("}" parenthesis |}| 26 27 26)
      ("║" parenthesis #f 27 28 27)
      (" " white-space #f 28 29 28)
      ("@" parenthesis #f 29 30 29)
      ("g" symbol #f 30 31 30)
      ("{" parenthesis |{| 31 32 31)
      (,dont-care string #f 32 33 32)
      ("}" parenthesis |}| 33 34 33)
      (" " white-space #f 34 35 34)
      ("║" parenthesis #f 35 36 35)
      (" " white-space #f 36 37 36)
      ("╠═════╬═══════╣" parenthesis #f 37 52 37)
      (" " white-space #f 52 53 52)
      ("║" parenthesis #f 53 54 53)
      ("@" parenthesis #f 54 55 54)
      ("h" symbol #f 55 56 55)
      ("{" parenthesis |{| 56 57 56)
      (,dont-care string #f 57 58 57)
      ("}" parenthesis |}| 58 59 58)
      ("║" parenthesis #f 59 60 59)
      (" " white-space #f 60 61 60)
      ("@" parenthesis #f 61 62 61)
      ("i" symbol #f 62 63 62)
      ("{" parenthesis |{| 63 64 63)
      (,dont-care string #f 64 65 64)
      ("}" parenthesis |}| 65 66 65)
      (" " white-space #f 66 67 66)
      ("║" parenthesis #f 67 68 67)
      (" " white-space #f 68 69 68)
      ("╚═════╩═══════╝" parenthesis #f 69 84 69)
      ("\n" white-space #f 84 85 0)
      (,eof eof #f 85 85 0))))
 (run-lexer #:sub-lexer scribble-lexer
            "#2d\n"
            "╔═════╦═══════╗\n"
            "║@f{x}║ @g{y} ║\n"
            "╠═════╬═══════╣\n"
            "║@h{z}║ @i{w} ║\n"
            "╚═════╩═══════╝\n"))
