;;; jao-minibuffer.el --- using the minibuffer to report status  -*- lexical-binding: t; -*-

;; Copyright (C) 2020, 2021, 2022  jao

;; Author: jao <mail@jao.io>
;; Keywords: extensions

;; 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:

;; Simple asynchronous display of information in the minibuffer.

;;; Code:

(defvar jao-minibuffer-info ())
(defvar jao-minibuffer-msg-info '(""))
(defvar jao-minibuffer-align-right t)
(defvar jao-minibuffer-right-margin (if window-system 0 1))
(defvar jao-minibuffer-maximized-frames-p t)
(defvar jao-minibuffer-frame-width nil)
(defvar jao-minibuffer-active-buffer-line-color "azure4")
(defvar jao-minibuffer-inactive-buffer-line-color "grey25")

(defconst jao-minibuffer--name " *Minibuf-0*")

(defun jao-minibuffer--trim (s w)
  (if (< (string-width (or s "")) w)
      (format (format "%%%ds" (if jao-minibuffer-align-right w (- w))) s)
    (substring s 0 w)))

(defun jao-minibuffer--width ()
  (cond ((numberp jao-minibuffer-frame-width) jao-minibuffer-frame-width)
        (jao-minibuffer-maximized-frames-p (frame-width))
        (t (min (frame-width) (window-width (minibuffer-window))))))

(defun jao-minibuffer--format-info (&optional info)
  (let* ((info (or info jao-minibuffer-info))
         (info (if jao-minibuffer-align-right info (reverse info))))
    (mapconcat #'string-trim
               (seq-remove #'string-blank-p (mapcar 'format-mode-line info))
               " ")))

(defun jao-minibuffer--aligned (w)
  (let* ((msg (jao-minibuffer--format-info))
         (msg (cond (jao-minibuffer-align-right (string-trim msg))
                    (t (string-trim-left msg)))))
    (unless (string-empty-p msg)
      (let ((msg (propertize msg :minibuffer-message t))
            (w (- (jao-minibuffer--width) w jao-minibuffer-right-margin)))
        (if (> w 0) (jao-minibuffer--trim msg w) "")))))

(defun jao-minibuffer--insert (msg)
  (let ((hack (derived-mode-p 'pdf-view-mode 'doc-view-mode)))
    (with-current-buffer jao-minibuffer--name
      (delete-region (point-min) (point-max))
      (insert msg)
      (when hack (other-window 1) (other-window -1)))))

(defun jao-minibuffer--strip-prev (msg)
  (if-let ((n (text-property-any 0 (length msg) :minibuffer-message t msg)))
      (string-trim (substring msg 0 n))
    msg))

(defun jao-minibuffer--prefix (msgs)
  (when-let (p (string-join (butlast msgs) "\n"))
    (unless (string-blank-p p) (concat p "\n"))))

(defun jao-minibuffer--format-msg (msg)
   (let* ((msgs (mapcar #'jao-minibuffer--strip-prev (split-string msg "\n" t)))
          (msgs (cl-remove-if (lambda (s) (get-text-property 0 'invisible s)) msgs))
          (prefix (jao-minibuffer--prefix msgs))
          (msg (or (car (last msgs)) ""))
          (w (string-width msg)))
     (if jao-minibuffer-align-right
         (concat prefix msg (jao-minibuffer--aligned w))
       (concat prefix (jao-minibuffer--aligned (+ 3 w)) "   " msg))))

(defun jao-minibuffer--set-message (msg)
  (when jao-minibuffer-mode (jao-minibuffer--format-msg (or msg ""))))

(setq set-message-function #'jao-minibuffer--set-message)

(defun jao-minibuffer--add-variable (list-name variable-name &optional order)
  (let ((v `(:eval ,variable-name)))
    (set list-name (remove v (symbol-value list-name)))
    (add-to-ordered-list list-name v order)))

;;;###autoload
(defun jao-minibuffer-add-variable (variable-name &optional order)
  (jao-minibuffer--add-variable 'jao-minibuffer-info variable-name order))

;;;###autoload
(defun jao-minibuffer-add-msg-variable (variable-name &optional order)
  (jao-minibuffer--add-variable 'jao-minibuffer-msg-info variable-name order))

;;;###autoload
(defun jao-minibuffer-remove-variable (variable-name)
  (let ((v `(:eval ,variable-name)))
    (setq jao-minibuffer-info (remove v jao-minibuffer-info))
    (setq jao-minibuffer-msg-info (remove v jao-minibuffer-info))))

;;;###autoload
(define-minor-mode jao-minibuffer-mode
  "Show minibuffer status"
  :global t :lighter "" :group 'jao
  (if jao-minibuffer-mode
      (progn (advice-add 'select-window :after #'jao-minibuffer-refresh)
             (advice-add 'force-mode-line-update :after #'jao-minibuffer-refresh)
             (setq clear-message-function #'jao-minibuffer-refresh)
             (jao-minibuffer-refresh))
    (advice-remove 'select-window #'jao-minibuffer-refresh)
    (advice-remove 'force-mode-line-update #'jao-minibuffer-refresh)
    (setq clear-message-function nil)
    (jao-minibuffer--insert "")))

;;;###autoload
(defun jao-minibuffer-refresh (&rest _ignore)
  (interactive)
  (when jao-minibuffer-mode
    (let ((jao-minibuffer-mode nil)
          (msg (when jao-minibuffer-msg-info
                 (jao-minibuffer--format-info jao-minibuffer-msg-info))))
      (jao-minibuffer--insert (jao-minibuffer--format-msg (or msg ""))))))

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