(module
 bokbok-packet
 (make-core-object
  core-object?
  core-object-tag
  core-object-value

  max-receive-depth
  max-receive-length

  read-binary
  write-binary

  split-packet
  join-packet)

 (import chicken scheme)
 (use extras)
 (use ports)
 (use numbers)

   (define-record-type core-object
    (make-core-object tag value)
    core-object?
    (tag core-object-tag)
    (value core-object-value))

   ;; Literals in code can't be this big, so we have to cheat.
  (define two63 (expt 2 63))
  (define two64 (expt 2 64))

  (define (read-s8 port)
    (let ((v (read-byte port)))
      (if (eof-object? v)
          #!eof
          (if (< v 128)
              v
              (- v 256)))))

  (define (read-u16 port)
    (let* ((b1 (read-byte port))
           (b2 (read-byte port)))
      (if (or (eof-object? b1) (eof-object? b2))
          #!eof
          (+ (* b1 256) b2))))

  (define (write-u16 i port)
    (write-byte (quotient i 256) port)
    (write-byte (modulo i 256) port))

  (define (read-s16 port)
    (let ((v (read-u16 port)))
      (if (eof-object? v)
          #!eof
          (if (< v 32768)
              v
              (- v 65536)))))

  (define (write-s16 i port)
    (if (negative? i)
        (write-u16 (+ 65536 i) port)
        (write-u16 i port)))

  (define (read-u32 port)
    (let ((w1 (read-u16 port))
          (w2 (read-u16 port)))
      (if (or (eof-object? w1) (eof-object? w2))
          #!eof
          (+ (* w1 65536) w2))))

  (define (write-u32 i port)
    (write-byte (quotient i 16777216) port)
    (write-byte (quotient (modulo i 16777216) 65536) port)
    (write-byte (quotient (modulo i 65536) 256) port)
    (write-byte (modulo i 256) port))

  (define (read-s32 port)
    (let ((v (read-u32 port)))
      (if (eof-object? v)
          #!eof
          (if (< v 2147483648)
              v
              (- v 4294967296)))))

  (define (write-s32 i port)
    (if (negative? i)
        (write-u32 (+ 4294967296 i) port)
        (write-u32 i port)))

  (define (read-u64 port)
    (let ((w1 (read-u32 port))
          (w2 (read-u32 port)))
      (if (or (eof-object? w1) (eof-object? w2))
          #!eof
          (+ (* w1 4294967296) w2))))

  (define (write-u64 i port)
    (write-u32 (quotient i 4294967296) port)
    (write-u32 (modulo i 4294967296) port))

  (define (read-s64 port)
    (let ((v (read-u64 port)))
      (if (eof-object? v)
          #!eof
          (if (< v two63)
              v
              (- v two64)))))

  (define (write-s64 i port)
    (if (negative? i)
        (write-u64 (+ two64 i) port)
        (write-u64 i port)))

  (define (read-length port)
    (let ((b (read-byte port)))
      (cond
       ((eof-object? b)
        #!eof)
       ((= b #x80)
        'variable)
       ((< b #x80)
        b)
       ((eq? b #x82)
        (read-u16 port))
       ((eq? b #x84)
        (read-u32 port))
       ((eq? b #x88)
        (read-u64 port))
       (else
        (error "Unknown length prefix" b)))))

  (define (write-length l port)
    (cond
     ((< l #x80)
      (write-byte l port))
     ((< l #x8000)
      (write-byte #x82)
      (write-u16 l port))
     ((< l #x80000000)
      (write-byte #x84)
      (write-u32 l port))
     ((< l two63)
      (write-byte #x88)
      (write-u64 l port))
     (else
      (error "Size too big" l))))

   (define (write-binary obj port)
    (cond
     ((list? obj)
      (write-byte #xe0 port)
      (write-byte #x80 port)
      (for-each
       (lambda (o)
         (write-binary o port))
       obj)
      (write-byte #x00 port)
      (write-byte #x00 port))
     ((symbol? obj)
      (let ((s (symbol->string obj)))
        (write-byte #xdd port)
        (write-length (string-length s) port)
        (write-string s #f port)))
     ((string? obj)
      (write-byte #x0c port)
      (write-length (string-length obj) port)
      (write-string obj #f port))
     ((boolean? obj)
      (write-byte #x01 port)
      (write-byte #x01 port)
      (write-byte (if obj #xff #x00) port))
     ((vector? obj)
      (write-byte #x30 port)
      (write-byte #x80 port)
      (let ((final (vector-length obj)))
        (let loop
            ((idx 0))
          (if (>= idx final)
              (begin
                (write-byte #x00 port)
                (write-byte #x00 port))
              (begin
                (write-binary (vector-ref obj idx) port)
                (loop (+ idx 1)))))))
     ((and (integer? obj) (exact? obj))
      (write-byte #x02 port)
      (cond
       ((zero? obj)
        (write-byte #x00 port))
       ((<= -128 obj 127)
        (write-byte #x01 port)
        (write-byte obj port))
       ((<= -32768 obj 32767)
        (write-byte #x02 port)
        (write-s16 obj port))
       ((<= -2147483648 obj 2147483647)
        (write-byte #x04 port)
        (write-s32 obj port))
       ((<= (- two63) obj (- two63 1))
        (write-byte #x08 port)
        (write-s64 obj port))
       (else
        ;; Chop into 64-bit chunks
        (let loop
            ((parts '())
             (n obj))
          (if (<= (- two63) n (- two63 1))
              (begin
                (write-length (* (+ 1 (length parts)) 8) port)
                (if (negative? obj)
                    (if (zero? (car parts))
                        (write-s64 -1 port)
                        (write-s64 (- n 1) port))
                    (write-s64 n port))
                (for-each
                 (lambda (p)
                   (write-u64 p port))
                 parts))
              (loop
               (cons (modulo n two64) parts)
               (quotient n two64)))))))
     ((number? obj)
      (write-byte #xfe port)
      (let ((s (number->string obj)))
        (write-length (string-length s) port)
        (write-string s #f port)))
     ((core-object? obj)
      (if (number? (core-object-tag obj))
          (begin
            (write-byte (core-object-tag obj) port)
            (write-byte #x80 port))
          (begin
            (let ((t (symbol->string (core-object-tag obj))))
              (write-byte #xFF port)
              (write-byte #x80 port)
              (write-byte #xdd port)
              (write-length (string-length t) port)
              (write-string t #f port))))
      (write-binary (core-object-value obj) port)
      (write-byte #x00 port)
      (write-byte #x00 port))
     (else
      (error "Unsupported kind of thing to write" obj))))

   (define (join-packet ps)
     (with-output-to-string
       (lambda ()
         (write-binary ps (current-output-port)))))

 (define max-receive-depth (make-parameter +inf.0))
 (define max-receive-length (make-parameter +inf.0))

 (define (read-binary* port level)
   (when (>= level (max-receive-depth))
     (error "Maximum receive depth exceeded"))
   (let* ((t (read-byte port))
          (l (read-length port)))
     (assert (or (eq? l 'variable) (<= l (max-receive-length))))
     (if (or (eof-object? t) (eof-object? l))
         #!eof
         (case t
           ((#xe0) ;; List
            (assert (eq? l 'variable))
            (let loop
                ((result '())
                 (length 0))
              (when (>= length (max-receive-length))
                (error "Maximum receive length exceeded in list"))
              (let ((v (read-binary* port (+ level 1))))
                (if (eof-object? v)
                    (reverse result)
                    (loop (cons v result)
                          (+ length 1))))))
           ((#xdd) ;; Symbol
            (string->symbol (read-string l port)))
           ((#x0c) ;; String
            (read-string l port))
           ((#x01) ;; Boolean
            (let ((v (read-byte port)))
              (case v
                ((#x00) #f)
                ((#xff) #t)
                (else (error "Invalid boolean value" v)))))
           ((#x30) ;; Vector
            (assert (eq? l 'variable))
            (let loop
                ((result '())
                 (length 0))
              (when (>= length (max-receive-length))
                (error "Maximum receive length exceeded in vector"))
              (let ((v (read-binary* port (+ level 1))))
                (if (eof-object? v)
                    (list->vector (reverse result))
                    (loop (cons v result)
                          (+ length 1))))))
           ((#x02) ;; Integer
            (case l
              ((#x00) 0)
              ((#x01) (read-s8 port))
              ((#x02) (read-s16 port))
              ((#x04) (read-s32 port))
              ((#x08) (read-s64 port))
              (else
               (assert (zero? (modulo l 8)))
               (let loop
                   ((result (read-s64 port))
                    (parts-left (- (/ l 8) 1)))
                 (if (zero? parts-left)
                     result
                     (let ((v (read-u64 port)))
                       (loop
                        (+ (* two64 result) v)
                        (- parts-left 1))))))))
           ((#xfe) ;; Arbitrary Scheme number
            (let ((s (read-string l port)))
              (string->number s)))
           ((#xff) ;; Symbol-tagged core object
            (assert (eq? l 'variable))
            (assert (= (read-byte port) #xdd))
            (let ((tl (read-length port)))
              (assert (<= tl (max-receive-length)))
              (let* ((tag (string->symbol (read-string tl port)))
                     (inner-value (read-binary* port (+ level 1))))
                (assert (= (read-byte port) #x00))
                (assert (= (read-byte port) #x00))
                (make-core-object tag inner-value))))
           ((#x00) ;; EOC
            (assert (= l 0))
            #!eof)
           (else ;; Number-tagged core object
            (assert (eq? l 'variable))
            (let ((inner-value (read-binary* port (+ level 1))))
              (assert (= (read-byte port) #x00))
              (assert (= (read-byte port) #x00))
             (make-core-object t inner-value)))))))

 (define (read-binary port)
   (read-binary* port 0))

 (define (split-packet ps)
   (with-input-from-string ps
       (lambda ()
         (read-binary (current-input-port)))))

 )
