#! /usr/local/bin/guile3 \
-e main -s
!#

(use-modules (ice-9 exceptions))
(use-modules (srfi srfi-19))
(use-modules (sxml simple))
(use-modules (json))

(define guest-book-json-file "guest-book.json")

(define (decode-hex hex)
  (string (integer->char
           (string->number (string-append "#x"
                                          hex)))))

;; Handle query string encoding
(define (format-query-string item)
  (letrec ((decode-query
            (lambda (value)
              (let ((index (string-index value #\%))
                    (value-length (string-length value)))
                (cond ((and index value-length)
                       (string-append (substring value 0 index)
                                      (decode-hex (substring value
                                                             (+ index 1)
                                                             (+ index 3)))
                                      (cond ((< index (- value-length 3))
                                             (decode-query
                                              (substring value
                                                         (+ index 3)
                                                         value-length)))
                                            (else ""))))
                      (else value))))))
    (decode-query (string-map (lambda (character)
                                (case character
                                  ((#\+) #\space)
                                  (else character)))
                              item))))

;; Progress form data
(define (get-form)
  (let ((query (getenv "QUERY_STRING")))
    (cond ((string= query "") #f)
          (else
           (map (lambda (item)
                  (let ((pairs (string-split item #\=)))
                    (set-cdr! pairs (format-query-string (cadr pairs)))
                    pairs))
                (string-split query #\&))))))

(define (sanitize-input value)
  (let ((output (open-output-string))
        (sanitized ""))
    (sxml->xml `(*TOP* ,value) output)
    (set! sanitized (get-output-string output))
    (close-output-port output)
    sanitized))

(define (write-guest-to-json name url message)
  (let ((guest-book '())
        (guest `(("name" . ,name)
                 ("url" . ,url)
                 ("message" . ,message)
                 ("date" . ,(date->string (current-date) "~B, ~d ~Y"))))
        (not-already-added #t))
    
    (if (file-exists? guest-book-json-file)
        (call-with-input-file guest-book-json-file
          (lambda (fp)
            (set! guest-book (vector->list (json->scm fp)))
            ;; Handle order thingy
            (set! guest-book (map (lambda (item)
                                    (reverse item))
                                  guest-book)))))

    ;; Guest already added.
    (letrec ((check-if-already-added
              (lambda (guests)
                (unless (eq? guests '())
                  (cond ((compare-guests 0 (car guests) guest)
                         (set! not-already-added #f))
                        (else
                         (check-if-already-added (cdr guests)))))))
             (compare-guests
              (lambda (index guest1 guest2)
                (and (string= (cdr (list-ref guest1 index))
                              (cdr (list-ref guest2 index)))
                     (cond ((< index 3)
                            (compare-guests (+ index 1) guest1 guest2))
                           (else #t))))))
      (check-if-already-added guest-book))

    ;; Add guest to guest-book
    (cond (not-already-added
           (cond ((eq? guest-book '())
                  (set! guest-book (list guest)))
                 (else
                  (append! guest-book (list guest))))

           ;; Write guest book to json
           (call-with-output-file guest-book-json-file
             (lambda (fp)
               (scm->json (list->vector guest-book) fp)))

           (display "<p>You been added to the guest book yippe (:</p>\
<img src=\"../images/yippee.gif\" alt=\"yippee!\"/>\n"))
           (else
            (display "You already been added\n")))))

(define (handle-fields)
  (let ((form (get-form)))
    (if form
        (let ((name (sanitize-input (cdr (list-ref form 0))))
              (url (sanitize-input (cdr (list-ref form 1))))
              (message (sanitize-input (cdr (list-ref form 2)))))
          (cond ((string= name "") ;; No name given
                 (display "<p>name is required</p>"))
                ((string= message "") ;; No message given
                 (display "<p>please write a silly something (:</p>"))
                (else
                 (write-guest-to-json name url message)))))))

;; Display a guest in the guest book
(define (display-guest guest)
  (let ((date (cdr (list-ref guest 0)))
        (message (cdr (list-ref guest 1)))
        (url (cdr (list-ref guest 2)))
        (name (cdr (list-ref guest 3))))
    (format #t "  <hr />\n  <div class=\"container\">
    <b>~a</b> <a href=\"~a\" target=\"_blank\">~a</a> --- signed ~a\n\
    <div class=\"silly-something\"><p>~a</p></div>\n\
  </div>\n  <br />\n" name url url date message)))

;; Display pretty much everything
(define (display-guest-book)
  (display "Content-Type: text/html\n\n\
<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n\
\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\
<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">\n\
<head>\n\
  <title>Guest Book</title>\n\
  <link rel=\"icon\" type=\"image/x-icon\" href=\"../images/icon.png\" />
  <link rel=\"stylesheet\" type=\"text/css\" href=\"../css/guest-book.css\" />\n\
</head>\n\
<body>\n\
  <a href=\"../index.xhtml\"><img src=\"../images/back_home.png\"\n\
  alt=\"Back to home page\"/></a>\n\
  <div class=\"container\">\n\
    <h2>Sign my fucking guest book</h2>\n\
    <form action = \"guest-book.cgi\" method = \"get\">\n\
      <lable for = \"name\" maxlength=\"100\">Name</lable>\n\
      <input type = \"text\" name = \"name\"/>\n\
      <br />\n\
      <lable for = \"url\" maxlength=\"256\">Website (Optional)\n\
      </lable>\n\
      <input type = \"text\" name = \"url\"/>\n\
      <br /><br />\n\
      <lable for = \"message\">Write a little silly something</lable>\n\
      <br />\n\
      <textarea type = \"text\" name = \"message\" rows = \"4\"\n\
      cols = \"40\" maxlength=\"512\"></textarea>\n\
      <br />\n\
      <input type = \"submit\" value = \"Fucking submit\">\n\
      <b>You can't delete/edit it afterwards</b>\n\
    </form>\n\
    <p>\n\
      <b>Be nice! Dont be a fucking ass.</b>\n\
    </p>\n")
  (handle-fields)
  (display "  </div>\n  <br />\n")

  ;; Try to read the guest book json
  (with-exception-handler
      (lambda (error)
        (display "  <div class=\"container\">\n\
      <p>No guest(s) have been added ): But you can be the first!</p>\n\
  </div>\n"))
    (lambda ()
      (call-with-input-file guest-book-json-file
        (lambda (fp)
          (letrec ((guest-book (json->scm fp))
                   (guest-loop
                    (lambda (index)
                      (display-guest (vector-ref guest-book index))
                      (if (> index 0)
                          (guest-loop (- index 1))))))
            (guest-loop (- (vector-length guest-book) 1))))))
    #:unwind? #t)

  (display "</body>\n</html>\n"))

(define (main args)
  (display-guest-book))