#lang racket/base
(require rackunit
         racket/math
         racket/format)

(define-syntax-rule (tc expr expected)
  (test-equal? (format "~s" 'expr) expr expected))

(define-syntax-rule (tcrx expr len rx)
  (test-case (format "~s" 'expr)
    (let ([v expr])
      (when len (check-equal? (string-length v) len))
      (check-regexp-match rx v))))

;; ~a

(tc (~a "north")
    "north")
(tc (~a 'south)
    "south")
(tc (~a #"east")
    "east")
(tc (~a #\w "e" 'st)
    "west")
(tc (~a (list "red" 'green #"blue"))
    "(red green blue)")
(tc (~a 17)
    "17")
(tc (~a #e1e20)
    (number->string #e1e20))
(tc (~a pi)
    (number->string pi))
(tc (~a (expt 6.1 87))
    (number->string (expt 6.1 87)))

(tc (~a "a" "b" "c" #:width 5)
    "abc  ")

(tc (~a "abcde" #:max-width 5)
    "abcde")
(tc (~a "abcde" #:max-width 4)
    "abcd")
(tc (~a "abcde" #:max-width 4 #:limit-marker "...")
    "a...")
(tc (~a "abcde" #:max-width 4 #:limit-marker "*")
    "abc*")
(tc (~a "abcde" #:max-width 4 #:limit-marker "")
    "abcd")
(tc (~a "The quick brown fox" #:max-width 15 #:limit-marker "")
    "The quick brown")
(tc (~a "The quick brown fox" #:max-width 15 #:limit-marker "...")
    "The quick br...")

(tcrx (~a "apple" #:min-width 20 #:align 'left)
      20 #rx"^apple( )*$")
(tcrx (~a "pear" #:min-width 20 #:align 'left #:right-pad-string " x")
      20 #rx"^pear(x)?( x)*$")
(tcrx (~a "plum" #:min-width 20 #:align 'right #:left-pad-string "x ")
      20 #rx"^(x )*(x)?plum$")
(tcrx (~a "orange" #:min-width 20 #:align 'center
           #:left-pad-string "- " #:right-pad-string " -")
      20 #rx"^(- )*(-)?orange(-)?( -)*$")

(tc (~a "short" #:width 6)
    "short ")
(tc (~a "loquacious" #:width 6 #:limit-marker "...")
    "loq...")

;; ~v

(tc (~v "north")
    "\"north\"")
(tc (~v 'south)
    "'south")
(tc (~v #"east")
    "#\"east\"")
(tc (~v #\w)
    "#\\w")
(tc (~v (list "red" 'green #"blue"))
    "'(\"red\" green #\"blue\")")

(tc (~v '(123456) #:max-width 5)
    "'(...")

;; ~s

(tc (~s "north")
    "\"north\"")
(tc (~s 'south)
    "south")
(tc (~s #"east")
    "#\"east\"")
(tc (~s #\w)
    "#\\w")
(tc (~s (list "red" 'green #"blue"))
    "(\"red\" green #\"blue\")")

(tc (~s 123456 #:max-width 5)
    "12...")

;; ~r

(tc (~r 0)
    "0")
(tc (~r pi)
    "3.141593")
(tc (~r pi #:precision 4)
    "3.1416")
(tc (~r pi #:precision 0)
    "3")
(tc (~r 1.5 #:precision 4)
    "1.5")
(tc (~r 1.5 #:precision '(= 4))
    "1.5000")
(tc (~r 50 #:precision 2)
    "50")
(tc (~r 50 #:precision '(= 2))
    "50.00")
(tc (~r 50 #:precision '(= 0))
    "50.")

(tc (~r 17)
    "17")
(tc (~r 17 #:min-width 4)
    "  17")
(tc (~r -42 #:min-width 4)
    "-  42")
(tc (~r 1.5 #:min-width 4)
    " 1.5")
(tc (~r 1.5 #:precision 4 #:min-width 10)
    "       1.5")
(tc (~r 1.5 #:precision '(= 4) #:min-width 10)
    "    1.5000")

(tc (~r -42 #:min-width 4 #:pad-string "0")
    "-0042")

(tc (~r 17 #:min-width 4 #:pad-string "0")
    "0017")
(tc (~r -42 #:min-width 4 #:pad-string "0")
    "-0042")

(tc (for/list ([x '(17 0 -42)]) (~r x))
    '("17" "0" "-42"))
(tc (for/list ([x '(17 0 -42)]) (~r x #:sign '+))
    '("+17" "0" "-42"))
(tc (for/list ([x '(17 0 -42)]) (~r x #:sign '++))
    '("+17" "+0" "-42"))
(tc (for/list ([x '(17 0 -42)]) (~r x #:sign 'parens))
    '("17" "0" "(42)"))
(tc (let ([sign-table '(("" " up") "an even " ("" " down"))])
      (for/list ([x '(17 0 -42)]) (~r x #:sign sign-table)))
    '("17 up" "an even 0" "42 down"))

(tc (~r 100 #:base 7)
    "202")
(tc (~r 4.5 #:base 2)
    "100.1")
(tc (~r 3735928559 #:base 16)
    "deadbeef")
(tc (~r 3735928559 #:base '(up 16))
    "DEADBEEF")

(tc (~r 999 #:precision 3)
    "999")
(tc (~r 1000 #:precision 3)
    "1000")

;; ~r #:notation 'positional

(tc (~r #:notation 'positional pi)
    "3.141593")
(tc (~r #:notation 'positional pi #:precision 4)
    "3.1416")
(tc (~r #:notation 'positional pi #:precision 0)
    "3")
(tc (~r #:notation 'positional 1.5 #:precision 4)
    "1.5")
(tc (~r #:notation 'positional 1.5 #:precision '(= 4))
    "1.5000")
(tc (~r #:notation 'positional 50 #:precision 2)
    "50")
(tc (~r #:notation 'positional 50 #:precision '(= 2))
    "50.00")
(tc (~r #:notation 'positional 50 #:precision '(= 0))
    "50.")

(tc (~r #:notation 'positional 17)
    "17")
(tc (~r #:notation 'positional 17 #:min-width 4)
    "  17")
(tc (~r #:notation 'positional -42 #:min-width 4)
    "-  42")
(tc (~r #:notation 'positional 1.5 #:min-width 4)
    " 1.5")
(tc (~r #:notation 'positional 1.5 #:precision 4 #:min-width 10)
    "       1.5")
(tc (~r #:notation 'positional 1.5 #:precision '(= 4) #:min-width 10)
    "    1.5000")

(tc (~r #:notation 'positional -42 #:min-width 4 #:pad-string "0")
    "-0042")

(tc (~r #:notation 'positional 17 #:min-width 4 #:pad-string "0")
    "0017")
(tc (~r #:notation 'positional -42 #:min-width 4 #:pad-string "0")
    "-0042")

(tc (for/list ([x '(17 0 -42)]) (~r #:notation 'positional x))
    '("17" "0" "-42"))
(tc (for/list ([x '(17 0 -42)]) (~r #:notation 'positional x #:sign '+))
    '("+17" "0" "-42"))
(tc (for/list ([x '(17 0 -42)]) (~r #:notation 'positional x #:sign '++))
    '("+17" "+0" "-42"))
(tc (for/list ([x '(17 0 -42)]) (~r #:notation 'positional x #:sign 'parens))
    '("17" "0" "(42)"))
(tc (let ([sign-table '(("" " up") "an even " ("" " down"))])
      (for/list ([x '(17 0 -42)]) (~r #:notation 'positional x #:sign sign-table)))
    '("17 up" "an even 0" "42 down"))

(tc (~r #:notation 'positional 100 #:base 7)
    "202")
(tc (~r #:notation 'positional 4.5 #:base 2)
    "100.1")
(tc (~r #:notation 'positional 3735928559 #:base 16)
    "deadbeef")
(tc (~r #:notation 'positional 3735928559 #:base '(up 16))
    "DEADBEEF")

;; ~r #:notation 'exponential

(tc (~r 12345 #:precision 3 #:notation 'exponential)
    "1.235e+04")
(tc (~r 12345 #:precision 2 #:notation 'exponential)
    "1.23e+04")
(tc (~r 10000 #:precision 2 #:notation 'exponential)
    "1e+04")
(tc (~r 10000 #:precision '(= 2) #:notation 'exponential)
    "1.00e+04")

(tc (~r 12345 #:precision 4 #:min-width 12 #:notation 'exponential)
    "  1.2345e+04")

(tc (~r #:notation 'exponential 1000)
    "1e+03")
(tc (~r #:notation 'exponential 0.9876)
    "9.876e-01")

(tc (~r #:notation 'exponential 100 #:base 2)
    "1.1001×2^+06")

(tc (~r #:notation 'exponential 1234 #:format-exponent "E")
    "1.234E+03")

(tc (~r #:notation 'exponential 12345 #:precision 3)
    ;; note rounding!
    "1.235e+04")
(tc (~r #:notation 'exponential 12345 #:precision 2)
    "1.23e+04")
(tc (~r #:notation 'exponential 10000 #:precision 2)
    "1e+04")
(tc (~r #:notation 'exponential 10000 #:precision '(= 2))
    "1.00e+04")

(tc (~r #:notation 'exponential 12345 #:min-width 12)
    "  1.2345e+04")

(tc (~r 3735928559 #:base '(up 16) #:precision 6 #:notation 'exponential)
    ;; note rounding!
    "D.EADBEF×16^+07")
