#|-*- mode:lisp -*-|#
#|
exec ros -Q -- $0 "$@"
|#

;; Woo is a fast non-blocking HTTP server built on top of
;; libev. Although Woo is written in Common Lisp, it aims
;; to be the fastest web server written in any programming
;; language.

;; https://github.com/fukamachi/woo

;; Quicklisp is a library manager for Common Lisp. Use
;; QuickLisp's quickload function to retrieve external
;; packages. These packages are automatically curl'd when
;; the program runs.

;; Woo - https://github.com/fukamachi/woo
;; Jonathan - https://github.com/fukamachi/jonathan
;; CL-MARKUP - https://github.com/arielnetworks/cl-markup
;; Postmodern - https://github.com/marijnh/Postmodern
;; QURI - https://github.com/fukamachi/quri

(ql:quickload '(:cl-markup :jonathan :postmodern :quri :uiop :woo) :silent t)


(declaim (optimize (debug 0) (safety 0) (speed 3)))


(load "./helpers/starts-with.lisp")
(load "./helpers/parse-argv.lisp")


;; Initialize the global random state by "some means" (e.g. current time)
(setf *random-state* (make-random-state t))


(defun plaintext ()
  "Plaintext handler."
  '(200 (:content-type "text/plain" :server "Woo") ("Hello, World!")))

(defun json ()
  "JSON handler using Jonathan to encode JSON"
  `(200 (:content-type "application/json" :server "Woo") (,(jonathan:to-json '(:message "Hello, World!")))))

(defun get-a-random-record (id)
  (declare (integer id))
  `(:|id| ,id :|randomNumber| ,(postmodern:query (:select 'randomnumber :from 'world :where (:= 'id id)) :single!)))

(defun db ()
  "DB handler using Jonathan to encode JSON and Postmodern to access PostgreSQL"
  (let ((id (+ 1 (random 10000))))
    `(
      200
      (:content-type "application/json" :server "Woo")
      (,(jonathan:to-json (get-a-random-record id)))
     )))

(defun ensure-integer-is-between-one-and-five-hundreds (n)
  (declare (integer n))
  (if (< n 1)
    (values 1 nil)
    (if (> n 500)
      (values 500 nil)
      (values n t))))

(defun extract-number-of-records-to-fetch (env)
  (let ((n (handler-case
            (parse-integer (cdr (assoc "queries" (quri:url-decode-params (getf env :query-string)) :test #'equal)))
            (error (c) (values 1 c)))))
    (ensure-integer-is-between-one-and-five-hundreds n)))

(defun get-some-random-integers-between-one-and-ten-thousand (n)
  (declare (integer n))
  (loop :repeat n
        :collect (+ 1 (random 10000))))

(defun get-some-random-records (n)
  (declare (integer n))
  (let ((ids (get-some-random-integers-between-one-and-ten-thousand n)))
    (mapcar #'get-a-random-record ids)))

(defun queries (env)
  "QUERIES handler using Jonathan to encode JSON and Postmodern to access PostgreSQL"
  `(
    200
    (:content-type "application/json" :server "Woo")
    (,(jonathan:to-json (get-some-random-records (extract-number-of-records-to-fetch env))))
   ))

(defun get-all-fortunes ()
  (postmodern:query (:select 'id 'message :from 'fortune) :rows))

(defun get-all-fortunes-plus-one ()
  (let* ((records       (get-all-fortunes))
         (records-p-one (append records '((0 "Additional fortune added at request time.")))))
    (sort (copy-list records-p-one) #'string-lessp :key #'second)))

(defun fortunes ()
  "FORTUNES handler using Jonathan to encode JSON, Postmodern to access PostgreSQL and CL-Markup to build the HTML"
  `(
     200
     (:content-type "text/html; charset=UTF-8" :server "Woo")
     (,(cl-markup:html5
       (:head
         (:title "Fortunes"))
       (:body
         (:table
           (:tr
             (:th "id")
             (:th "message"))
           (loop for fortune-row in (get-all-fortunes-plus-one)
                 collect (cl-markup:markup
                           (:tr
                             (:td (format nil "~d" (first fortune-row)))
                             (:td (second fortune-row)))))))))
   ))

(defun get-and-update-some-random-records (n)
  (declare (integer n))
  (let* ((random-records (get-some-random-records n))
         (random-numbers (get-some-random-integers-between-one-and-ten-thousand n))
         (index -1)
         (updated-records (map 'list
                               (lambda (row)
                                       (incf index)
                                       (list :|id|           (getf row :|id|          )
                                             :|randomNumber| (nth index random-numbers)))
                               random-records))
         (record-list     (map 'list
                               (lambda (row)
                                       (list (nth 1 row)
                                             (nth 3 row)))
                               updated-records)))
    (postmodern:query (format nil "UPDATE world AS ori SET randomnumber = new.randomnumber FROM (VALUES ~{(~{~a~^, ~})~^, ~}) AS new (id, randomnumber) WHERE ori.id = new.id" record-list))
    (values updated-records)))

(defun updates (env)
  "UPDATES handler using Jonathan to encode JSON and Postmodern to access PostgreSQL"
  `(
    200
    (:content-type "application/json" :server "Woo")
    (,(jonathan:to-json (get-and-update-some-random-records (extract-number-of-records-to-fetch env))))
   ))

(defun handler (env)
  "Router"
  (let ((path (getf env :path-info)))
    (cond ((starts-with path "/plaintext") (funcall 'plaintext  ))
          ((starts-with path "/json"     ) (funcall 'json       ))
          ((starts-with path "/db"       ) (funcall 'db         ))
          ((starts-with path "/queries"  ) (funcall 'queries env))
          ((starts-with path "/fortunes" ) (funcall 'fortunes   ))
          ((starts-with path "/updates"  ) (funcall 'updates env)))))

(defun main (&rest argv)
  "Create and start the server, applying argv to the env"
  (let ((args (parse-argv argv)))
    (apply #'woo:run
      (lambda (env)
        ;; preprocessing
        (let ((res (postmodern:with-connection '("hello_world" "benchmarkdbuser" "benchmarkdbpass" "tfb-database" :pooled-p t)
                     (funcall 'handler env))))
          ;; postprocessing
          res))
      :debug nil
      args)))
