;;; helm-systemd.el --- helm's systemd interface        -*- lexical-binding: t; -*-

;; Copyright (C) 2016

;; Author:  <lompik@oriontabArch>
;; Package-Version: 20160517.2333
;; Package-X-Original-Version: 0.0.1
;; Package-Requires: ((emacs "24.4") (helm "1.9.2") (with-editor "2.5.0"))
;; Keywords: convenience

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;;

;;; Code:

(require 'cl-lib)
(require 'helm)
(require 'with-editor)
(require 'subr-x)

(defvar helm-systemd-command-types '("service" "timer" "mount" "target" "socket" "scope" "device"))
(defvar helm-systemd-list-all nil)
(defvar helm-systemd-list-not-loaded nil)
(defvar helm-systemd-buffer-name "*Helm systemd log*")
(defvar helm-systemd-status-mode-hook nil )

(defconst helm-systemd-actions-list
  '(("print". "Printed")
    ("restart". "Restarted")
    ("stop" ."Stopped")
    ("start". "Started")))

(defvar helm-systemd-status-font-lock-keywords
  `(("\\(Loaded\\|Active\\|Status\\|Docs\\|Process\\|Main PID\\|Tasks\\|CGroup\\):" (1 'helm-bookmark-gnus) )
    ("active (running)" 0 'hi-green)
    ("inactive (dead)" 0 'helm-bookmark-info)
    ("active (exited)" 0 'helm-bookmark-info)

    ("[fF]ailed" 0 'diredp-executable-tag)

    ("ā”€\\([0-9]+\\)"  (1 'helm-bookmark-info))     ; PIDs
    ("[ā—šŸ”œ] .*"  0 'helm-buffer-file) ; command lines ā—šŸ”œ
    "Default expressions to highlight in `helm systemd log'."))

(define-derived-mode helm-systemd-status-mode fundamental-mode "Systemd-log"
  "Major mode for viewing systemd status logs.
\\{helm-systemd-status-mode-map}"
  (setq-local font-lock-defaults '(helm-systemd-status-font-lock-keywords))
  (font-lock-mode t))

(add-to-list 'auto-mode-alist `(, (concat (regexp-quote helm-systemd-buffer-name) "\\'") . helm-systemd-status-mode))

(defun helm-systemd-command-line-option ()
  (concat "--no-pager --no-legend -t " (car helm-systemd-command-types) (if helm-systemd-list-all " --all")))

(defvar helm-systemd-map
  (let ((map (make-sparse-keymap)))
    (set-keymap-parent map helm-map)
    (define-key map (kbd "<C-return>")    'helm-cr-empty-string)
    (define-key map (kbd "<M-RET>")       'helm-cr-empty-string)
    (define-key map (kbd "C-]")           'helm-systemd-next-type)
    (define-key map (kbd "C-[")           'helm-systemd-prev-type)

    (delq nil map))
  "Keymap for `helm-systemd'.")

(defun helm-systemd-concatspace (word-list)
  "Concatenate list of string with spaces as separator"
  (mapconcat 'identity
             (delq nil word-list)
             " "))

(defun helm-systemd-systemctl-command (&rest args)
  "Construct string with: 'systemctl default-args' ARGS"
  (helm-systemd-concatspace (push (concat "systemctl " (helm-systemd-command-line-option))
                                  args) ))

(defun helm-systemd-get-canditates (sysd-options)
  "Return a list of systemd service unit"
  (let* ((result ())
         (leftcolumnwidth
          (number-to-string 25))
         (hash (make-hash-table
                :test 'equal))
         (sysd-lu (shell-command-to-string
                   (helm-systemd-systemctl-command " list-units " sysd-options)))
         (sysd-lu (delete ""
                          (split-string sysd-lu
                                        "\n"))))
    (mapc (lambda (line)
            (puthash (car (split-string line)) line hash))
          sysd-lu)
    (if helm-systemd-list-not-loaded
        (let* ((sysd-luf (shell-command-to-string
                          (helm-systemd-systemctl-command " list-unit-files " sysd-options)))
               (sysd-luf (delete ""
                                 (split-string sysd-luf "\n"))))
          (mapc (lambda (line-luf)
                  (let ((unit (car
                               (split-string line-luf))))
                    (unless (gethash unit hash nil)
                      (puthash unit line-luf hash)))) sysd-luf)))

    (let ((maxunitlength
           (string-to-number leftcolumnwidth)))
      (maphash (lambda (unit descr)
                 (setq maxunitlength
                       (max maxunitlength (length unit)))) hash)
      (setq leftcolumnwidth
            (number-to-string maxunitlength)))
    (maphash (lambda (unit descr)
               (let* ((unit_misc
                       (string-trim-left
                        (substring descr (length unit) (length descr))))
                      (formatted_output
                       (format
                        (concat "%-" leftcolumnwidth "s %s")
                        unit unit_misc)))
                 (push formatted_output result)) ) hash)

    result ))

(defun  helm-systemd-display (unit-command unit &optional isuser nodisplay)
  (with-current-buffer (get-buffer-create helm-systemd-buffer-name)
    (helm-systemd-status-mode)
    (let ((command
           (helm-systemd-systemctl-command (if isuser "--user") unit-command  unit)))
      (insert "\nšŸ”œ " command "\n")
      (if (or isuser (string= unit-command "status"))
          (insert  (shell-command-to-string command))
        (with-temp-buffer
          (cd "/sudo::/")
          (setq command (shell-command-to-string (concat "sudo " command))))
        (insert command)
        )
      (insert "\n"))
    ;;    (propertise-sysd-buffer )
    (unless nodisplay
      (display-buffer (current-buffer)))))

(defun helm-systemd-next-type ()
  (interactive)
  (setq helm-systemd-command-types
        (append (cdr helm-systemd-command-types)
                (list (car helm-systemd-command-types))))
  (with-helm-alive-p
    (helm-force-update )))

(defun helm-systemd-prev-type ()
  (interactive)
  (setq helm-systemd-command-types
        (append (last helm-systemd-command-types)
                (remove (car (last helm-systemd-command-types))
                        helm-systemd-command-types)))
  (with-helm-alive-p
    (helm-force-update )))

(defun helm-system-persis-action (_line &optional isuser)
  "Show unit status"
  (let ((units (helm-marked-candidates)))
    (mapc (lambda (line)
            (let ((unit (car (split-string line))))
              (helm-systemd-display "status" unit isuser )))
          units)))

(defun helm-systemd-transformer (candidates source)
  (let ((res candidates))
    (unless (string= (car helm-systemd-command-types) "device")

      (setq res (cl-loop for i in candidates
                         for split = (split-string i)
                         for unit = (car split)
                         for loaded = (nth 1 split)
                         for active = (nth 2 split)
                         for running = (nth 3 split)
                         for description = (if running (helm-systemd-concatspace (cl-subseq split 4)))
                         collect (let ((line i))
                                   (unless (and unit loaded active running description)
                                     line)
                                   (if (and loaded (not (string= (car helm-systemd-command-types) "mount")))
                                       (let* ((isenabled
                                               (car
                                                (split-string
                                                 (shell-command-to-string
                                                  (helm-systemd-concatspace `("systemctl" "is-enabled "
                                                                              ,(if (string-match "User"
                                                                                                 (cdr (assoc 'name source)))
                                                                                   "--user")
                                                                              ,unit))))))
                                              (propena (cond ((string= isenabled "enabled") 'helm-bookmark-info)
                                                             ((string= isenabled "static") 'helm-bookmark-gnus)
                                                             (t 'helm-bookmark-gnus)))
                                              (isenabled (format "%8s" isenabled) ))
                                         (setq line (if active
                                                        (replace-regexp-in-string loaded (concat (propertize isenabled 'face propena) " " loaded " ") line )
                                                      (replace-regexp-in-string loaded (concat (propertize isenabled 'face propena) " ") line ))))) ;; list-units case
                                   (if (string=  running "running")
                                       (setq line
                                             (replace-regexp-in-string running
                                                                       (propertize
                                                                        running
                                                                        'face
                                                                        'helm-ff-directory) line )))
                                   (if (string= running "exited")
                                       (setq line
                                             (replace-regexp-in-string running
                                                                       (propertize
                                                                        running
                                                                        'face
                                                                        'helm-bookmark-info) line )))
                                   (if (string= running "failed")
                                       (setq line
                                             (replace-regexp-in-string running
                                                                       (propertize
                                                                        running
                                                                        'face
                                                                        'diredp-executable-tag) line )))
                                   (if description
                                       (setq line
                                             (replace-regexp-in-string
                                              (regexp-quote description) (propertize
                                                                          description
                                                                          'face
                                                                          'helm-buffer-process) line t)))
                                   line ))))
    res))

(defmacro helm-systemd-make-actions (sysd-verb isuser)
  `(lambda (_ignore)
     (mapc (lambda (candidate)
             (helm-systemd-display ,sysd-verb (car (split-string candidate)) ,isuser t)
             (message (concat
                       (cdr (assoc ,sysd-verb helm-systemd-actions-list))
                       " "
                       (car (split-string candidate)))))
           (helm-marked-candidates))))



(defun helm-systemd-build-source ()
  (helm-build-sync-source "systemd"
    :candidates (lambda ()
                  (reverse (helm-systemd-get-canditates "") ))
    :action (helm-make-actions
             "Print"   (helm-systemd-make-actions "status" nil)
             "Restart" (helm-systemd-make-actions "restart" nil)
             "Stop"    (helm-systemd-make-actions "stop" nil)
             "Start"   (helm-systemd-make-actions "start" nil))
    :persistent-action #'helm-system-persis-action
    :persistent-help "Show unit status"
    :keymap helm-systemd-map
    :filtered-candidate-transformer #'helm-systemd-transformer))

(defun helm-systemd-build-source-user ()
  (helm-build-sync-source "Systemd User"
    :candidates   (lambda ()
                    (reverse (helm-systemd-get-canditates "--user")))
    :action (helm-make-actions
             "Print"   (helm-systemd-make-actions "status" t)
             "Restart" (helm-systemd-make-actions "restart" t)
             "Stop"    (helm-systemd-make-actions "stop" t)
             "Start"   (helm-systemd-make-actions "start" nil)
             "Edit with Emacs"   (lambda (candidate)
                                   (add-to-list 'with-editor-envvars "SYSTEMD_EDITOR" t)
                                   (with-editor-async-shell-command (concat "systemctl --user --full edit " (car (split-string candidate))) )))
    :persistent-action (lambda (line) (funcall #'helm-system-persis-action line t))
    :persistent-help "Show unit status"
    :keymap helm-systemd-map

    :filtered-candidate-transformer #'helm-systemd-transformer))

;;;###autoload
(defun helm-systemd ()
  (interactive)
  (helm
   :sources (mapcar (lambda (func)
                      (funcall func))
                    '(helm-systemd-build-source helm-systemd-build-source-user))
   :truncate-lines t
   :buffer
   (concat "*helm systemd*")) )

(provide 'helm-systemd)
;;; helm-systemd.el ends here