;;; kubernetes-deployments.el --- Rendering for Kubernetes deployments.  -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:

(require 'dash)
(require 's)

(require 'kubernetes-core)
(require 'kubernetes-kubectl)
(require 'kubernetes-modes)
(require 'kubernetes-state)
(require 'kubernetes-utils)
(require 'kubernetes-vars)
(require 'kubernetes-yaml)


;;;; Components

(defconst kubernetes-deployments--column-heading
  ["%-45s %10s %10s %10s %6s" "Name Replicas UpToDate Available Age"])

(kubernetes-ast-define-component deployment-detail (deployment)
  (-let [(&alist 'metadata (&alist 'namespace ns 'creationTimestamp time)
                 'spec (&alist 'selector (&alist 'matchLabels
                                                 (&alist 'name selector-name
                                                         'component component-name)
                                                 'matchExpressions match-expressions)))
         deployment]
    `(,(when selector-name
         `(section (selector nil)
                   (nav-prop (:selector ,selector-name)
                             (key-value 12 "Selector" ,(propertize selector-name 'face 'kubernetes-selector)))))
      ,(when component-name
         `(section (component nil)
                   (nav-prop (:component ,component-name)
                             (key-value 12 "Component" ,(propertize component-name 'face 'kubernetes-component)))))

      ,(when match-expressions
         `(section (expressions nil)
                   (heading "Match Expressions")
                   (indent ,(kubernetes-yaml-render match-expressions))))

      (section (namespace nil)
               (nav-prop (:namespace-name ,ns)
                         (key-value 12 "Namespace" ,(propertize ns 'face 'kubernetes-namespace))))
      (key-value 12 "Created" ,time))))

(kubernetes-ast-define-component deployment-line (state deployment)
  (-let* ((current-time (kubernetes-state--get state 'current-time))
          (pending-deletion (kubernetes-state--get state 'deployments-pending-deletion))
          (marked-deployments (kubernetes-state--get state 'marked-deployments))

          ((&alist 'metadata (&alist 'name name 'creationTimestamp created-time)

                   'spec (&alist 'replicas desired)

                   'status (&alist 'replicas current
                                   'availableReplicas available
                                   'updatedReplicas up-to-date))
           deployment)
          (current (or current 0))
          (desired (or desired 0))
          (available (or available 0))
          (up-to-date (or up-to-date 0))
          ([fmt] kubernetes-deployments--column-heading)
          (list-fmt (split-string fmt))
          (line `(line ,(concat
                         ;; Name
                         (format (pop list-fmt) (s-truncate 43 name))
                         " "
                         ;; Replicas (current/desired)
                         (let ((next (pop list-fmt))
                               (str (format "%s/%s" current desired)))
                           (cond
                            ((zerop desired)
                             (format next str))
                            ((zerop current)
                             (propertize (format next str) 'face 'warning))
                            ((/= current desired)
                             (format next str))
                            (t
                             (propertize (format next str) 'face 'kubernetes-dimmed))))
                         " "
                         ;; Up-to-date
                         (let ((next (pop list-fmt)))
                           (cond
                            ((zerop desired)
                             (format next up-to-date))
                            ((zerop up-to-date)
                             (propertize (format next up-to-date) 'face 'warning))
                            (t
                             (propertize (format next up-to-date) 'face 'kubernetes-dimmed))))
                         " "
                         ;; Available
                         (let ((next (pop list-fmt)))
                           (cond
                            ((zerop desired)
                             (format next available))
                            ((zerop available)
                             (propertize (format next available) 'face 'warning))
                            (t
                             (propertize (format next available) 'face 'kubernetes-dimmed))))
                         " "
                         ;; Age
                         (let ((start (apply #'encode-time (kubernetes-utils-parse-utc-timestamp created-time))))
                           (propertize (format (pop list-fmt) (kubernetes--time-diff-string start current-time))
                                       'face 'kubernetes-dimmed))))))
    `(nav-prop (:deployment-name ,name)
               (copy-prop ,name
                          ,(cond
                            ((member name pending-deletion)
                             `(propertize (face kubernetes-pending-deletion) ,line))
                            ((member name marked-deployments)
                             `(mark-for-delete ,line))
                            ((zerop desired)
                             `(propertize (face kubernetes-dimmed) ,line))
                            (t
                             line))))))

(kubernetes-ast-define-component deployment (state deployment)
  `(section (,(intern (kubernetes-state-resource-name deployment)) t)
            (heading (deployment-line ,state ,deployment))
            (section (details nil)
                     (indent
                      (deployment-detail ,deployment)
                      (padding)))))

(kubernetes-ast-define-component deployments-list (state &optional hidden)
  (-let (((state-set-p &as &alist 'items deployments) (kubernetes-state--get state 'deployments))
         ([fmt labels] kubernetes-deployments--column-heading))
    `(section (deployments-container ,hidden)
              (header-with-count "Deployments" ,deployments)
              (indent
               (columnar-loading-container ,deployments
                                           ,(propertize
                                             (apply #'format fmt (split-string labels))
                                             'face
                                             'magit-section-heading)
                                           ,(--map `(deployment ,state ,it) deployments)))
              (padding))))

;; Requests and state management

(kubernetes-state-define-refreshers deployments)

(defun kubernetes-deployments-delete-marked (state)
  (let ((names (kubernetes-state--get state 'marked-deployments)))
    (dolist (name names)
      (kubernetes-state-delete-deployment name)
      (kubernetes-kubectl-delete "deployment" name state
                                 (lambda (_)
                                   (message "Deleting deployment %s succeeded." name))
                                 (lambda (_)
                                   (message "Deleting deployment %s failed" name)
                                   (kubernetes-state-mark-deployment name))))
    (kubernetes-state-trigger-redraw)))


;;;; Displaying deployments

(defun kubernetes-deployments--read-name (state)
  "Read a deployment name from the user.

STATE is the current application state.

Update the deployment state if it not set yet."
  (-let* (((&alist 'items deployments)
           (or (kubernetes-state--get state 'deployments)
               (progn
                 (message "Getting deployments...")
                 (let ((response (kubernetes-kubectl-await-on-async state (-partial #'kubernetes-kubectl-get "deployments"))))
                   (kubernetes-state-update-deployments response)
                   response))))
          (deployments (append deployments nil))
          (names (-map #'kubernetes-state-resource-name deployments)))
    (completing-read "Deployment: " names nil t)))

;;###autoload
(defun kubernetes-display-deployment (deployment-name state)
  "Display information for a deployment in a new window.

STATE is the current application state.

DEPLOYMENT-NAME is the name of the deployment to display."
  (interactive (let ((state (kubernetes-state)))
                 (list (kubernetes-deployments--read-name state) state)))
  (if-let (deployment (kubernetes-state-lookup-deployment deployment-name state))
      (select-window
       (display-buffer
        (kubernetes-yaml-make-buffer kubernetes-display-deployment-buffer-name deployment)))
    (error "Unknown deployment: %s" deployment-name)))


(provide 'kubernetes-deployments)

;;; kubernetes-deployments.el ends here
