;;; jao-borgmatic.el --- Simple access to borgmatic  -*- lexical-binding: t; -*-

;; Copyright (C) 2022  jao

;; Author: jao <mail@jao.io>
;; 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 <https://www.gnu.org/licenses/>.

;;; Commentary:

;; An emacs front-end for borgmatic

;;; Code:

(defconst jao-borgmatic--buffer "*borgmatic*")
(defun jao-borgmatic--buffer ()
  (get-buffer-create jao-borgmatic--buffer))

(defvar jao-borgmatic-extract-path "/tmp/borgmatic")

(defvar jao-borgmatic-mode-map
  (let ((map (make-keymap)))
    (suppress-keymap map)
    (define-key map [?q] 'bury-buffer)
    (define-key map [?n] 'next-line)
    (define-key map [?p] 'previous-line)
    (define-key map [?g] 'jao-borgmatic-list)
    (define-key map [?x] 'jao-borgmatic-extract)
    (define-key map [?c] 'jao-borgmatic-check)
    (define-key map [?d] 'jao-borgmatic-delete)
    map))

;;;###autoload
(defun jao-borgmatic-mode ()
  "A very simple mode to show the output of borgmatic commands."
  (interactive)
  (kill-all-local-variables)
  (buffer-disable-undo)
  (use-local-map jao-borgmatic-mode-map)
  ;; (setq-local font-lock-defaults '(jao-borgmatic-font-lock-keywords))
  (setq-local truncate-lines t)
  (setq-local next-line-add-newlines nil)
  (setq major-mode 'jao-borgmatic-mode)
  (setq mode-name "borgmatic")
  (read-only-mode 1))

(defun jao-borgmatic--do (things &optional buffer)
  "Execute a borgmatic command THINGS in the given BUFFER."
  (let ((b (or buffer (pop-to-buffer (get-buffer-create jao-borgmatic--buffer)))))
    (let ((inhibit-read-only t)
          (cmd (format "borgmatic %s" things)))
      (unless buffer
        (with-current-buffer b (delete-region (point-min) (point-max))))
      (message "Running: %s ...." cmd)
      (shell-command cmd b)
      (message ""))))

(defun jao-borgmatic--archive-at-point ()
  (save-excursion
    (beginning-of-line)
    (when (looking-at "- \\([^ ]+\\)")
      (match-string-no-properties 1))))

(defvar jao-borgmatic--archive-history nil)
(defvar-local jao-borgmatic--archives nil)

(defun jao-borgmatic--archives (&optional retrieve)
  (or (and (not retrieve) jao-borgmatic--archives)
      (let ((c (shell-command-to-string
                "borgmatic list --format '{archive}  {time}*'")))
        (setq jao-borgmatic--archives (cdr (split-string c  "\\*\\|\n"))))))

(defun jao-borgmatic--read-archive (&optional retrieve)
  (car (split-string (completing-read "Archive: "
                                      (jao-borgmatic--archives retrieve)
                                      nil
                                      nil
                                      nil
                                      'jao-borgmatic--archive-history
                                      (jao-borgmatic--archive-at-point)))))

;;;###autoload
(defun jao-borgmatic-list ()
  (interactive)
  (with-current-buffer (jao-borgmatic--buffer)
    (jao-borgmatic-mode)
    (let ((inhibit-read-only t)
          (archives (with-temp-message "Retrieving archives list..."
                      (jao-borgmatic--archives t))))
      (delete-region (point-min) (point-max))
      (insert "Archives: \n\n")
      (dolist (a archives)
        (unless (string-blank-p a) (insert "- " a "\n")))
      (insert "\n" (with-temp-buffer
                     (jao-borgmatic--do "info" (current-buffer))
                     (buffer-string)))))
  (pop-to-buffer (jao-borgmatic--buffer))
  (goto-line 3))

(defconst jao-borgmatic--extract-output " *borgmatic output*")

(defun jao-borgmatic--extract-sentinel (paths)
  (lambda (proc &rest _)
    (when (and (eq (process-status proc) 'exit)
               (y-or-n-p "Borg extraction finished, show?"))
      (if (eq (process-exit-status proc) 0)
          (let ((d (file-name-directory (car paths))))
            (dired (expand-file-name d jao-borgmatic-extract-path)))
        (pop-to-buffer jao-borgmatic--extract-output)))))

(defvar jao-borgmatic--extract-history nil)

;;;###autoload
(defun jao-borgmatic-extract ()
  (interactive)
  (let* ((archive (jao-borgmatic--read-archive))
         (path (read-string "Path(s) (e.g. home/jao/emacs.d/init.el): "
                            nil 'jao-borgmatic--extract-history))
         (paths (if (string-blank-p path)
                    (not (or (y-or-n-p "Extract the full archive?")
                             (error "Cancelled")))
                  (split-string path)))
         (default-directory jao-borgmatic-extract-path))
    (make-directory default-directory t)
    (message "Extracting %s/%s to directory %s in the background"
             archive path default-directory)
    (make-process :name "borgmatic-extract"
                  :buffer jao-borgmatic--extract-output
                  :command (append `("borgmatic" "extract"
                                     "--archive" ,archive
                                     "--destination" ,default-directory)
                                   (when paths (cons "--path" paths)))
                  :sentinel (jao-borgmatic--extract-sentinel paths)
                  :stderr nil)))

(provide 'jao-borgmatic)
;;; jao-borgmatic.el ends here