;; Copyright (c) 2015 dinky's evil twin sone://EWtk1limedjBM2LnGE3~z98tC8bLTu9ryLIMcFgg8PI
;; License: LGPL

(use-modules
 (rnrs bytevectors)
 (rnrs io ports) ;; get/put-bytevector bytevector->string
 (ice-9 rw) ;; write-string
 (ice-9 rdelim)
 (ice-9 pretty-print)
 (ice-9 vlist)
 (srfi srfi-11)) ; let-values

(define (println s)
  (pretty-print s))

(define (copy-port in out)
  (let ((buf (make-bytevector #x1000)))
    (let loop ()
      (let ((amt (get-bytevector-n! in buf 0 #x1000)))
        (when amt
          (put-bytevector out buf 0 amt)
          (loop))))))

(define (find-identifier name opts)
  (let ((ret
  (let ((identifier (assq 'Identifier opts)))
    (if identifier
        (string->symbol (cdr identifier))
        name))))
    (println (list 'find-id name opts ret))
    ret))

(define (string-splitonce s delim)
  (let ((where (string-index s delim)))
    (if where
        (values (substring/shared s 0 where) (substring/shared s (+ where 1) (string-length s)))
        (values s ""))))

(define (vhash-keys v)
  (vhash-fold (lambda (name value l) (cons name l)) '() v))

(define (fcp-loop app)
  (define waiters vlist-null)
  (define aliases vlist-null)
  (define sock #f)

  (define data-buf (make-bytevector #x1000))

  (define (write-line s)
    (write-string/partial s sock)
    (newline sock))

  (letrec* ((send
         (lambda* (name opts #:optional (data #f) (data-length 0))
                  (println (symbol->string name))
                  (write-line (symbol->string name))
                  (for-each
                   (λ (pair)
                     (let ((name (car pair))
                           (value (cdr pair)))
                       (let ((line (string-append (symbol->string name)
                                                  "=" (cond
                                                       ((symbol? value) (symbol->string value))
                                                       ((string? value) value)
                                                       ((bytevector? value) (bytevector->string
                                                                             value "utf-8"))
                                                       ((number? value) (number->string value))
                                                       ((eq? value #f) "false")
                                                       ((eq? value #t) "true")
                                                       (else
                                                        (error "wat is ~s" value))))))
                         (println line)
                         (write-line line))))
                   opts)
                  (if data
                      (begin
                        (write-line (string-append "Data-Length=" (number->string data-length)))
                        (write-line "Data")
                        (cond
                         ((procedure? data)
                          (data (λ (chunk) (put-bytevector sock chunk))))
                         ((input-port? data)
                          (copy-port data sock))
                         ((bytevector? data)
                          (put-bytevector sock data))
                         ((string? data)
                          (put-bytevector sock (string->bytevector data "utf-8")))
                         (else
                          (error "How to write this data?" data))))
                      (begin
                        (println "EndMessage")
                        (write-line "EndMessage")
                        (newline sock)))))
         (expect
         (case-lambda
          ((identifier newaliases waiter)
           (set! aliases
                 (let loop ((result aliases) (newaliases newaliases))
                   (if (null? newaliases)
                       result
                       (let ((alias (car newaliases)))
                         (when (vhash-assq alias aliases)
                               (error "Already waiting on alias" alias identifier))
                         (loop (vhash-consq identifier (car newaliases) result) (cdr newaliases))))))
           (expect identifier waiter))
          ((identifier waiter)
           (if (list? identifier)
               (expect (car identifier) (cdr identifier) waiter)
               (begin
                 (println (list 'consq identifier waiter (vhash-keys waiters)))
                 (set! waiters (vhash-consq identifier waiter waiters))
                 (println (list 'consq identifier waiter (vhash-keys waiters))))))))

        (doit (lambda (shutdown)
                (app send expect shutdown)
                (let read-a-message ()
                  (define name (string->symbol
                                (let ((line (read-line sock 'trim)))
                                  (println (list 'line line))
                                  (when (eof-object? line)
                                        (error "Fffail"))
                                  line)))
                  (let properties ((opts '()))
                    (define line (read-line sock 'trim))
                    (println (list 'line line))
                    (if (or (equal? line "Data")
                            (equal? line "EndMessage"))
                        (begin
                          (println 'woo)
                          (let* ((name
                                  (let ((derp (vhash-assq name aliases)))
                                    (if derp
                                        (cdr derp)
                                        name)))
                                 (identifier (find-identifier name opts))
                                 (waiter (let ((waiter
                                                (or
                                                 (vhash-assq identifier waiters)
                                                 (vhash-assq name waiters))))
                                           (when (not waiter)
                                                 (println (list identifier name 'not-iny (vhash-keys waiters)))
                                                 (error "waugh"))
                                           (cdr waiter))))
                            (println (list 'waiteruh waiter))
                         (if (equal? line "Data")
                             (let-values (((feed finished) (waiter name identifier opts))
                                          ((total) (string->number (cdr (assoc 'DataLength opts)))))
                               (let reading-data ((left total))
                                 (if (<= left 0)
                                     (finished total)
                                     (let* ((max-to-read (min left (bytevector-length data-buf)))
                                            (amount (get-bytevector-n! sock data-buf 0 max-to-read)))
                                       (when (eof-object? amount)
                                             (error "FCP server closed connection"))
                                       (cond
                                        ((procedure? feed)
                                         (feed data-buf amount left total))
                                        ((output-port? feed)
                                         (put-bytevector feed data-buf amount))
                                        (else
                                         (error "How the heay ~s" feed)))
                                       (reading-data (- left amount))))))
                             (waiter name identifier opts)))
                       (read-a-message))
                        (call-with-values
                            (lambda ()
                              (string-splitonce line #\=))
                          (lambda (name value)
                            (println (list 'pair name value))
                            (properties (cons (cons (string->symbol name) value) opts))))))
                  (read-a-message)))))
    (dynamic-wind
        (λ ()
          (set! sock (let* ((addrs (getaddrinfo "127.0.0.1" "9481"))
                            (addr (car addrs))
                            (s (socket (addrinfo:fam addr)
                                       (addrinfo:socktype addr)
                                       (addrinfo:protocol addr))))
                       (connect s (addrinfo:addr addr))
                       s)))
        (λ ()
          (call/cc doit))
        (λ ()
          (close-port sock)
          (set! sock #f)))))

(define make-identifier (let ((counter 0))

                          (λ (sym)
                            (let ((result
                                   (string-append (symbol->string sym)
                                                  "-"
                                                  (number->string counter))))
                              (set! counter (+ counter 1))
                              result))))

(define uri (let ((uri (getenv "URI")))
              (if (or (not uri) (= 0 (string-length uri)))
                  "KSK@gpl.txt"
                  uri)))

(fcp-loop
 (λ (send expect shutdown)
   (expect 'NodeHello
           (λ (name identifier opts)
             (pretty-print (list 'got name opts))
             (expect '(SimpleProgress) ; ProtocolError)
                     (λ (name identifier opts)
                       (pretty-print (list 'progress name opts))))
             (expect '(DataFound)
                     (λ (name identifier opts)
                       (println "Found it!")));
             (expect 'AllData
                     (λ (name identifier opts)
                       (pretty-print (list 'receiving-data name opts))
                       (values
                        (λ (buf amount left total)
                          (println (list 'got-data amount left total)))
                        (λ (total)
                          (println 'all-done)
                          (shutdown)))))
             (expect 'GetFailed
                     (λ (name identifier opts)
                       (pretty-print (list "Aww! It didn't come" uri opts))
                       (shutdown)))
             (send 'ClientGet `((Identifier . ,(make-identifier 'get))
                                (URI . ,uri)
                                (Verbosity . 1)
                                (ReturnType . direct)))))
   (send 'ClientHello '((Name . "Racket FCP")
                        (ExpectedVersion . 2.0)))))