#|

src/models/ygoprodeck-cardinfo.lisp

Version 2 JSON Downloader

The idea behind this code is you'll be able to one click button download and update the db:

    (cardinfo-update-and-cleanup t)

TODO Use INFERIOR-SHELL

|#

(in-package #:cl-deck-builder2.models.ygoprodeck.cardinfo)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Constants
(defparameter +ygoprodeck-api-uri+ "https://db.ygoprodeck.com/api/v7/cardinfo.php")
(defparameter +ygoprodeck-api-uri-misc+ :|misc|)
(defparameter +ygoprodeck-api-uri-tcgplayer-data+ :|tcgplayer_data|)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Specials
(defparameter *cardinfo* nil
  "The current CARDINFO object.")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Class Definitions
(defclass cardinfo ()
  ((input :initarg :input)
   (output :initarg :output)))

(defclass cardinfo-php (cardinfo)
  ()
  (:default-initargs
   :input  +ygoprodeck-api-uri+
   :output #P"/tmp/cardinfo.php"
   :tcgplayer-data "yes"
   :misc "yes"))

(defclass cardinfo-json (cardinfo)
  ()
  (:default-initargs
   :input #P"/tmp/cardinfo.php"
   :output #P"/tmp/cardinfo.json"))

;; Extract the card_images image_url from the cardinfo.json
;;
;; jq '.[].card_images[].image_url' < /tmp/cardinfo.json
(defclass cardinfo-list (cardinfo)
  ()
  (:default-initargs
   :input #P"/tmp/cardinfo.json"
   :output #P"/tmp/cardinfo.list"))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Generics
(defgeneric cardinfo-cleanup (obj))

(defgeneric cardinfo-convert (from to &optional force))

(defgeneric cardinfo-download (obj &optional force))

(defgeneric cardinfo-input (obj)
  (:method ((obj cardinfo))
    (let ((slot-value (slot-value obj 'input)))
      (typecase slot-value
        (pathname (namestring slot-value))
        (t slot-value)))))

(defgeneric cardinfo-output (obj)
  (:method ((obj cardinfo))
    (let ((slot-value (slot-value obj 'output)))
      (typecase slot-value
        (pathname (namestring slot-value))
        (t slot-value)))))

(defgeneric (setf cardinfo-input) (new-value obj)
  (:method (new-value (obj cardinfo))
    (setf (slot-value obj 'input) new-value)))

(defgeneric (setf cardinfo-output) (new-value obj)
  (:method (new-value (obj cardinfo))
    (setf (slot-value obj 'output) new-value)))

(defgeneric cardinfo-input-exists-p (obj)
  (:method ((obj cardinfo))
    (probe-file (cardinfo-output obj))))

(defgeneric cardinfo-output-exists-p (obj)
  (:method ((obj cardinfo))
    (probe-file (cardinfo-output obj))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Methods
(defmethod initialize-instance :after ((obj cardinfo-php) &rest initargs &key misc tcgplayer-data &allow-other-keys)
  (declare (ignore initargs))
  (let ((params '()))
    (when misc (setf (getf params +ygoprodeck-api-uri-misc+) misc))
    (when tcgplayer-data (setf (getf params +ygoprodeck-api-uri-tcgplayer-data+) tcgplayer-data))
    (when params
      (setf (cardinfo-input obj)
            (caveman2.helper::add-query-parameters
             (cardinfo-input obj) params))))
  obj)

(defmethod cardinfo-cleanup ((obj cardinfo))
  (v:info :ygoprodeck.json "CARDINFO-CLEANUP: ~a" obj)
  (uiop:delete-file-if-exists (cardinfo-output obj)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO This could probably be replaced with CL-JSON processor.
(defun cardinfo-run-program (command from to &optional force)
  (let* ((from-output (cardinfo-output from))
         (from-output-exists-p (probe-file from-output))
         ;; TO-INPUT is the same as FROM-OUTPUT
         (to-input (cardinfo-input to))
         (to-output (cardinfo-output to))
         (to-output-exists-p (probe-file to-output)))
    (if (and (string= from-output to-input)
             from-output-exists-p
             (or force
                 (not to-output-exists-p)))
        (let ((command (format nil command from-output to-output)))
          (v:info :ygoprodeck.cardinfo "Running command: \"~a\"" command)
          (if (uiop:run-program command :output '(:string :stripped t))
              (cardinfo-output-exists-p to)))
        (v:info :ygoprodeck.cardinfo "Detected existing file: \"~a\"" to-output))))

(defmethod cardinfo-convert ((from cardinfo-php) (to cardinfo-json) &optional force)
  (cardinfo-run-program "jq '.data' < \"~a\" > \"~a\"" from to force))

(defmethod cardinfo-convert ((from cardinfo-json) (to cardinfo-list) &optional force)
  (cardinfo-run-program "jq '.[].card_images[].image_url' < \"~a\" > \"~a\"" from to force))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod cardinfo-download ((obj cardinfo) &optional force)
  (v:info :ygoprodeck.json "CARDINFO-DOWNLOAD: ~a"
          (cardinfo-input obj))
  (let* ((input (cardinfo-input obj))
         (output (cardinfo-output obj))
         (output-exists-p (probe-file output)))
    (if (or force
            (not output-exists-p))
        (trivial-download:download input output)
        (cardinfo-output-exists-p obj))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun cardinfo-update-and-cleanup (&optional cleanup)
  (let ((cardinfo-php (make-instance 'cardinfo-php))
        (cardinfo-json (make-instance 'cardinfo-json))
        (cardinfo-list (make-instance 'cardinfo-list)))
    (cardinfo-download cardinfo-php)
    (cardinfo-convert cardinfo-php cardinfo-json)
    (cardinfo-convert cardinfo-json cardinfo-list)
    (when cleanup
      (mapcar #'cardinfo-cleanup (list cardinfo-php cardinfo-json cardinfo-list)))))
