#|

src/web/decks.lisp

Deck Overview Web Interface

Here we see the cards in the deck, we have the option to generate
deck images, and we have the option to pull the deck from
inventory.

|#

(in-package #:cl-deck-builder2.web)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO both YDK-DATA-TO-TEMPLATE and RENDER-YDK desperately need an overhaul.
(defun ydk-to-template-data (ydk)
  "Transform information from YDK-QUERY on YDK into: main, side, extra decks, with RLE encodings of all three, plus metadata."
  (let ((data (ydk-query ydk)))
    (when data
      (labels ((filter-kind (lst kind)
                 (remove-if
                  (lambda (card)
                    (if (eq (getf card :kind) kind)
                        nil
                        t))
                  (reverse lst))))
        (let ((main-deck (filter-kind data 0))
              (extra-deck (filter-kind data 1))
              (side-deck (filter-kind data 2)))
          (list
           :name (ydk-name-of ydk)
           :created-by (ydk-created-by ydk)

           :main-deck main-deck
           :extra-deck extra-deck
           :side-deck side-deck

           :main-deck-rle (rle-encode-plist main-deck :key (lambda (plist) (getf plist :name)) :test #'string=)
           :extra-deck-rle (rle-encode-plist extra-deck :key (lambda (plist) (getf plist :name)) :test #'string=)
           :side-deck-rle (rle-encode-plist side-deck :key (lambda (plist) (getf plist :name)) :test #'string=)))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO This is ugly still. Fix it.
(defun draw-deck-image-by-id (id)
  (let ((found (find-dao 'ydk-deck :id id)))
    (when found
      (let ((ydk (ydk-sync found)))
        (draw-deck-image ydk (ydk-name-of ydk))))))

;; TODO this only works with decks from the database as it uses
;; YDK-DECK-INFO-BY-ID, making the name of this function a
;; misnomer. It should be RENDER-YDK-BY-DECK-ID. I'd use YDK-QUERY for
;; now beacuse that nets you INNER-JOIN with fields coming from
;; datafly as an associated list... sloppy work.
(defun render-ydk (id tpl)
  "Render a YDK-DECK from the databse with ID using template TPL. We also use YDK-TO-TEMPLATE-DATA to query additional deck information."
  (let ((found (ydk-deck-by-id id)))
    (if found
        (render-with-env
         tpl
         (append
          (list :active "/decks"
                :id id
                :category (with-connection (db)
                            (ydk-category-of found))
                :saved-deck-list (select-ydk-deck)
                :saved-category-list (select-category)
                :files (probe-image-files-list
                        (ydk-name-of found)))
          (ydk-to-template-data found)))
        (_ "Nothing to see here..."))))

(defgeneric ydk-listing-as (id tpl)
  (:documentation "Looks like this is a kludge to accept ID as a string or integer. TODO Move to generics.lisp")
  (:method ((id string) (tpl pathname))
    (ydk-listing-as (parse-integer id) tpl))
  (:method ((id integer) (tpl pathname))
    (render-ydk id tpl)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defroute ("/decks" :method :GET) (&key _parsed)
  "Deck List Main Route. Login Required."
  (with-logged-in-user
    (let ((id (query-param "deck-load-id" _parsed)))
      (if id
          (let ((found (ydk-deck-by-id id)))
            (render-with-env #P"decks/index.html"
                             `(:active "/decks"
                               :files ,(probe-image-files-list (ydk-name-of found))
                               :id ,id
                               :category ,(ydk-category-of found)
                               :saved-deck-list ,(select-ydk-deck)
                               :saved-category-list ,(select-category))))
          (render-with-env #P"decks/index.html"
                           `(:active "/decks"
                             :saved-deck-list ,(select-ydk-deck)
                             :saved-category-list ,(select-category)))))))

(defroute ("/decks/:id/view" :method :GET) (&key id)
  "Deck Viewer Main Route. Login Required. View YDK-DECK with YDK-DECK-BY-ID. Uses RENDER-YDK."
  (with-logged-in-user
    (handler-case
        (ratify:with-parsed-forms
            ((:integer id))
          (let ((found (ydk-deck-by-id id)))
            (if found
                (render-ydk id #P"decks/index.html")
                (progn
                  (flash-error (format nil (_ "No Deck ID ~d") id))
                  (render-with-env #P"decks/index.html"
                                   `(:active "/decks"
                                     :saved-deck-list ,(select-ydk-deck)
                                     :saved-category-list ,(select-category)))))))
      (ratify:combined-error (e)
        (flash-error (format nil "~a~%" e))
        (render-with-env #P"decks/index.html"
                         `(:active "/decks"
                           :saved-deck-list ,(select-ydk-deck)
                           :saved-category-list ,(select-category)))))))

(defroute ("/decks/:id/generate-cover-images" :method :GET) (&key id)
  "Deck Viewer - Generate Cover Image. Login Required. Render a static image and display it with DECK-IMAGE-LISTING template."
  (v:info :decks "GET /decks/~d/generate-cover-images" id)

  (with-logged-in-user
    (handler-case
        (ratify:with-parsed-forms
            ((:integer id))
          (if (draw-deck-image-by-id id)
              (ydk-listing-as id #P"decks/deck-image-listing.html")
              (_ "Something went wrong. Try again?")))
      (ratify:combined-error (e)
        (flash-error e)))))

(defroute ("/decks/:id/delete-generated-images" :method :DELETE) (&key id)
  "Deck Viewer - Delete Generate Cover Image. Login Required."
  (v:info :decks "DELETE /decks/~d/delete-generated-images" id)

  (with-logged-in-user
    (handler-case
        (ratify:with-parsed-forms
            ((:integer id))
          (let ((found (ydk-deck-by-id id)))
            (when found
              (if (notany #'null
                          (mapcar #'uiop:delete-file-if-exists
                                  (probe-image-files-list (ydk-name-of found) nil)))
                  (flash-message (_ "Success!"))
                  (flash-error (_ "Something went wrong. Try again?"))))))
      (ratify:combined-error (e)
        (flash-error e)))))

(defroute ("/decks/:id/to-category" :method :POST) (&key id _parsed)
  "POST route to send deck ID to category ID."
  (v:info :decks "POST /decks/~d/to-category._parsed = ~a~%" id _parsed)

  (with-logged-in-user
    (let ((cat-id (query-param "category-id" _parsed)))
      (handler-case
          (ratify:with-parsed-forms
              ((:integer id)
               (:integer cat-id))
            (let ((found (ydk-deck-by-id id)))
              (when found
                (setf (ydk-category-of found)
                      (find-dao 'category :id cat-id))
                (with-connection (db)
                  (mito:save-dao found))
                (render-with-env #P"decks/category-select.html"
                                 `(:category-id ,(mito:object-id
                                                  (ydk-category-of found))
                                   :deck-id ,(mito:object-id found)
                                   :categories ,(select-category))))))
        (ratify:combined-error (e)
          (flash-error e))))))

(defroute ("/decks/:id/rename" :method :POST) (&key id _parsed)
  "Deck Viewer - Rename deck ID with new name. Will also rename the generated image files, if they exist."
  (v:info :decks "GET /decks/~d/rename._parsed = ~a~%" id _parsed)

  (with-logged-in-user
    (let ((name (query-param "name" _parsed)))
      ;; We want to change the name of LOAD-NAME to CREATE-NAME.
      (handler-case
          (ratify:with-parsed-forms
              ((:integer id)
               (:string name))
            (let ((found (ydk-deck-by-id id)))
              ;; Ahaha that was a fun hunt. Using YDK-DECK-RENAME
              ;; before RENAME-FILES caused the OLD-NAME in
              ;; RENAME-FILES to be NEW-NAME... Rename the files
              ;; before renaming the database entry.
              (v:info :decks "RENAME ~a => ~a" (ydk-name-of found) name)
              (ydk-rename-files (ydk-name-of found) name)
              (ydk-rename-deck found name)
              (flash-message
               (format nil "Found entry ~A (~d); renaming to ~a.~%"
                       (ydk-name-of found) id name))
              (redirect (format nil "/decks/~d/view" id) 302)))
        (ratify:combined-error (e)
          (flash-error e))))))

(defroute ("/decks/:id/pull" :method :GET) (&key id)
  "Pull deck ID using YDK-DECK-PULL-FROM-INVENTORY.

TODO I think This is outdated."
  (v:info :decks "GET /decks/~d/pull" id)

  ;; We want to "pull" the deck from inventory. That is, take the list
  ;; of cards in the deck and remove them from the inventory count.
  
  (with-logged-in-user
    (handler-case
        (ratify:with-parsed-forms
            ((:integer id))
          (let ((found (ydk-deck-by-id id)))
            ;; The deck exists by ID
            (when found
              (let ((pulled (ydk-deck-pull-from-inventory found)))
                (if pulled
                    (progn
                      (flash-message
                       (format nil "Found entry ~A (~d); pulling cards.~%"
                               (ydk-name-of found) id))
                      (redirect (format nil "/construct/~d/view"
                                        (mito:object-id pulled))
                                302))
                    (flash-message
                     (format nil "Unable to pull deck ~d?~%" id)))))))
      (ratify:combined-error (e)
        (flash-error e)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defroute ("/decks/cards-in-decks" :method :GET) (&key _parsed)
  "Route to display all the unique cards in all decks."
  (v:info :decks "GET /decks/cards-in-decks => ~a" _parsed)
  
  (render #P"decks/cards-in-decks.html"
          (list :active "/decks"
                :table (cards-in-all-decks))))

(defroute ("/decks/:id/name" :method :GET) (&key id)
  "Route to display the name of the YDK-DECK by this ID.

TODO This should be a batch request."
  (with-logged-in-user
    (handler-case
        (ratify:with-parsed-forms
            ((:integer id))
          (let ((found (ydk-deck-by-id id)))
            (if found
                ;; Deck names like "29036" are coming back as INTEGER
                ;; which is causing issues with Caveman2. It seems to
                ;; use REDUCE #'LENGTH to measure the content length.
                ;; It didn't like: (LENGTH 29036)
                (princ-to-string (ydk-name-of found))
                (_ "No Name")))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defroute ("/decks/by-category/:id" :method :GET) (&key id _parsed)
  "Query a list of decks by category."
  (v:info :decks "GET /decks/by-category/~d => ~a" id _parsed)
  
  (with-logged-in-user
    (render #P"decks/decks-by-category.html"
            (list :active "/decks"
                  :table (by-category 'ydk-deck id)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; All the various kinds of deck representations: HTML, HTML as Text,
;; Text as Text Area, Resulting Deck File Images
(defroute ("/decks/:id/html-listing" :method :GET) (&key id _parsed)
  "Helper route. Get the builder HTML listing using YDK-LISTING-AS and DECK-HTML-RESULTS template."
  (v:info :decks "GET /decks/~d/html-listing => ~a" id _parsed)

  (with-logged-in-user
    (ydk-listing-as id #P"decks/deck-html-results.html")))

(defroute ("/decks/:id/html-text-listing" :method :GET) (&key id _parsed)
  "Helper route. Get the HTML listing using YDK-LISTING-AS and DECK-TEXT-THREE-COLUMN template."
  (v:info :decks "GET /decks/~d/html-text-listing => ~a" id _parsed)

  (with-logged-in-user
    (ydk-listing-as id #P"decks/deck-text-three-column.html")))

(defroute ("/decks/:id/text-listing" :method :GET) (&key id _parsed)
  "Helper route. Get the text listing using YDK-LISTING-AS and DECK-TEXT-TEXTAREA template."
  (v:info :decks "GET /decks/~d/text-listing => ~a" id _parsed)

  (with-logged-in-user
    (ydk-listing-as id #P"decks/deck-text-textarea.html")))

(defroute ("/decks/:id/text-listing-with-brs" :method :GET) (&key id _parsed)
  "Helper route. Get the text listing using YDK-LISTING-AS and DECK-TEXT-TEXTAREA template with <brs>."
  (v:info :decks "GET /decks/~d/text-listing-with-brs => ~a" id _parsed)

  (with-logged-in-user
    (ydk-listing-as id #P"decks/deck-text-textarea-with-brs.html")))

(defroute ("/decks/:id/image-listing" :method :GET) (&key id _parsed)
  "Helper route. Get the image listing using YDK-LISTING-AS and DECK-IMAGE-LISTING template."
  (v:info :decks "GET /decks/~d/text-listing => ~a" id _parsed)

  (with-logged-in-user
    (ydk-listing-as id #P"decks/deck-image-listing.html")))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defroute ("/decks/:id/delete" :method :DELETE) (&key id)
  "Delete Deck by ID."
  (v:info :builder "DELETE /decks/:id/delete => ~a" id)

  (with-logged-in-user
    (handler-case
        (ratify:with-parsed-forms
            ((:integer id))
          (ydk-deck-delete-by-id id)
          (flash-message (format nil (_ "Deck ~a deleted success!" id))))
      (ratify:combined-error (e)
        (flash-error e)))

    (redirect "/decks")))

(defroute ("/decks/search" :method :GET) (&key _parsed)
  "Deck Viewer Search Main Route."
  (v:info :builder "GET /decks/search => ~a" _parsed)

  (with-logged-in-user
    (render-with-env #P"decks/search.html"
                     (list :active "/decks"))))

(defroute ("/decks/search" :method :POST) (&key _parsed)
  "Deck Viewer Search POST route.

TODO Ensure this is functional."
  (v:info :builder "GET /decks/search => ~a" _parsed)

  (with-logged-in-user
    (let* ((name (query-param "name" _parsed))
           (decks (select-ydk-deck
                    (sxql:where (%sxql-like :name name)))))

      (render-with-env #P"decks/search.html"
                       (list :active "/decks"
                             :decks decks)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; XXX Duplicate code from /builder/saved-deck-list
(defroute ("/decks/deck-select" :method :GET) (&key |id|)
  "Deck Select Route Helper."
  (with-logged-in-user
    (render-with-env #P"decks/deck-select.html"
                     (list :active "/decks"
                           :id (ignore-errors
                                (parse-integer |id|))
                           :decks (select-ydk-deck)))))

(defroute ("/decks/category-select" :method :GET) (&key |deck-id| |category-id|)
  "Category Select Route Helper.
TODO Return Sub-categories."
  (with-logged-in-user
    (render-with-env #P"decks/category-select.html"
                     (list :active "/decks"
                           :category-id (ignore-errors
                                         (parse-integer |category-id|))
                           :deck-id (ignore-errors
                                     (parse-integer |deck-id|))
                           :categories (select-category)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defroute ("/decks/kde/:id" :method :GET) (&key id)
  "KDE Team Deck List Viewer. View Deck by ID"
  (render-with-env #P"kde-team.html"
                   (ydk-to-kde
                    (ydk-sync
                     (ydk-deck-by-id id)))))
