(define-module (test-frame)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-64)
  #:use-module (ice-9 match)
  #:use-module (rnrs bytevectors)
  #:use-module (rnrs io ports)
  #:use-module (web socket frame))

(define (hex-string->bytevector str)
  "Convert the hexadecimal encoded string STR to a bytevector."
  (define hex-char->int
    (match-lambda
     (#\0 0)
     (#\1 1)
     (#\2 2)
     (#\3 3)
     (#\4 4)
     (#\5 5)
     (#\6 6)
     (#\7 7)
     (#\8 8)
     (#\9 9)
     (#\a 10)
     (#\b 11)
     (#\c 12)
     (#\d 13)
     (#\e 14)
     (#\f 15)))

  (define (read-byte i)
    (let ((j (* 2 i)))
      (+ (hex-char->int (string-ref str (1+ j)))
         (* (hex-char->int (string-ref str j)) 16))))

  (let* ((len (/ (string-length str) 2))
         (bv  (make-bytevector len)))
    (let loop ((i 0))
      (if (= i len)
          bv
          (begin
            (bytevector-u8-set! bv i (read-byte i))
            (loop (1+ i)))))))

(define (bytevector->hex-string bv)
  (define int->hex-char
    (match-lambda
      (0 #\0)
      (1 #\1)
      (2 #\2)
      (3 #\3)
      (4 #\4)
      (5 #\5)
      (6 #\6)
      (7 #\7)
      (8 #\8)
      (9 #\9)
      (10 #\a)
      (11 #\b)
      (12 #\c)
      (13 #\d)
      (14 #\e)
      (15 #\f)))

  (list->string
   (append-map (lambda (x)
                 (let ((high (ash (logand x #xf0) -4))
                       (low  (logand x #x0f)))
                   (list (int->hex-char high)
                         (int->hex-char low))))
               (bytevector->u8-list bv))))

(define (call-with-input-bytevector bv proc)
  (let ((port (open-bytevector-input-port bv)))
    (dynamic-wind
      (const #t)
      (lambda ()
        (proc port))
      (lambda ()
        (close-port port)))))

(define (frame->hex-string frame)
  (call-with-values open-bytevector-output-port
    (lambda (port get-bytevector)
      (write-frame frame port)
      (bytevector->hex-string (get-bytevector)))))

(define (bytevector->frame bv)
  (call-with-input-bytevector bv read-frame))

(define (hex-string->frame str)
  (bytevector->frame (hex-string->bytevector str)))

(test-begin "frame")

(test-assert "incomplete frame"
  (eof-object? (call-with-input-string "" read-frame)))

(test-equal "read unmasked text message"
  (hex-string->frame "810548656c6c6f")
  (make-text-frame "Hello"))

(test-equal "write unmasked text message"
  "810548656c6c6f"
  (frame->hex-string (make-text-frame "Hello")))

(test-equal "read masked text message"
  (hex-string->frame "818537fa213d7f9f4d5158")
  (make-text-frame "Hello" #vu8(#x37 #xfa #x21 #x3d)))

(test-equal "write masked text message"
  "818537fa213d7f9f4d5158"
  (frame->hex-string (make-text-frame "Hello" #vu8(#x37 #xfa #x21 #x3d))))

(test-equal "read fragmented umasked text message"
  (list (hex-string->frame "010348656c")
        (hex-string->frame "80026c6f"))
  (list (make-frame #f 'text #f (string->utf8 "Hel"))
        (make-frame #t 'continuation #f (string->utf8 "lo"))))

(test-equal "read unmasked ping frame"
  (hex-string->frame "890548656c6c6f")
  (make-ping-frame (string->utf8 "Hello")))

(test-equal "write unmasked ping frame"
  "890548656c6c6f"
  (frame->hex-string (make-ping-frame (string->utf8 "Hello"))))

(test-equal "read masked pong frame"
  (hex-string->frame "8a8537fa213d7f9f4d5158")
  (make-pong-frame (string->utf8 "Hello") #vu8(#x37 #xfa #x21 #x3d)))

(test-equal "write masked pong frame"
  "8a8537fa213d7f9f4d5158"
  (frame->hex-string
   (make-pong-frame (string->utf8 "Hello") #vu8(#x37 #xfa #x21 #x3d))))

(test-equal "read 256 bytes binary message in a single unmasked frame"
  (let* ((header (hex-string->bytevector "827e0100"))
         (len    (bytevector-length header))
         (frame  (make-bytevector (+ len 256) 170)))
    (bytevector-copy! header 0 frame 0 len)
    (bytevector->frame frame))
  (make-binary-frame (make-bytevector 256 170)))

(test-equal "read 64KiB binary message in a single unmasked frame"
  (let* ((header (hex-string->bytevector "827f0000000000010000"))
         (len    (bytevector-length header))
         (frame  (make-bytevector (+ len 65536) 170)))
    (bytevector-copy! header 0 frame 0 len)
    (bytevector->frame frame))
  (make-binary-frame (make-bytevector 65536 170)))

(test-equal "frame-length"
  10
  (frame-length (make-binary-frame (make-bytevector 10))))

(test-equal "frame-concatenate"
  #vu8(1 2 3 4 5)
  (frame-concatenate
   (list (make-binary-frame #vu8(1 2 3))
         (make-binary-frame #vu8(4 5)))))

(test-equal "text-frame->string"
  "Hello"
  (text-frame->string (make-text-frame "Hello")))

(test-equal "text-frames->string"
  "Hello"
  (text-frames->string
   (list (make-text-frame "Hel")
         (make-text-frame "lo"))))

(test-equal "emojos"
  "👋"
  (text-frame->string (make-text-frame "👋")))

(define test-runner (test-runner-current))

(test-end "frame")


(exit (= (test-runner-fail-count test-runner) 0))
