;; jao-minibuffer-tracking.el --- Tracking notifications -*- lexical-binding: t; -*-

;; Copyright (C) 2021, 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/>.

;;; require

(require 'tracking)
(require 'shorten)
(require 'jao-minibuffer)
(require 'jao-afio)

;;; shorten
;;;###autoload
(defun jao-shorten-modes (&rest modes)
  (dolist (m modes) (add-to-list 'tracking-shorten-modes m)))

(defvar jao-tracking-cleaners '(("^[^a-zA-Z#@]+" . "#")))

;;;###autoload
(defun jao-tracking-cleaner (rx subst)
  (add-to-list 'jao-tracking-cleaners (cons rx subst)))

(defun jao-tracking-shorten-aggressively (lst tail-count)
  (let ((s (shorten-join-sans-tail lst tail-count)))
    (if (string-match-p "^#" s) (substring s 1 nil) s)))

(defun jao-tracking-split-clean (s)
  (dolist (cln jao-tracking-cleaners)
    (when (string-match (car cln) s)
      (setq s (replace-match (cdr cln) nil nil s))))
  (shorten-split s))

(defun jao-tracking-shorten (old-func &rest args)
  (let ((shorten-join-function #'jao-tracking-shorten-aggressively)
        (shorten-split-function #'jao-tracking-split-clean))
    (apply old-func args)))

(advice-add #'tracking-shorten :around #'jao-tracking-shorten)

;;; additional highlighting
(defvar jao-tracking-highlight-rx "$^")

;;;###autoload
(defun jao-tracking-faces (&rest faces)
  (dolist (face faces)
    (add-to-list 'tracking-faces-priorities face)))

;;;###autoload
(defun jao-tracking-add-buffer (old-func &rest args)
  (let* ((buffer (car args))
         (faces (if (and buffer
                         (string-match-p jao-tracking-highlight-rx
                                         (buffer-name buffer)))
                    (cons 'lui-highlight-face (cadr args))
                  (cadr args))))
    (funcall old-func buffer faces)))

(advice-add 'tracking-add-buffer :around #'jao-tracking-add-buffer)
(jao-tracking-faces 'lui-highlight-face)

;;; minibuffer
(defvar jao-tracking-string "")
(defvar jao-tracking-bkg "grey93")

(defface jao-tracking-minibuffer `((t :background ,jao-tracking-bkg)) ""
  :group 'faces)
(defface jao-tracking-minibuffer-sep
  `((t :foreground ,jao-tracking-bkg :background ,jao-tracking-bkg)) ""
  :group 'faces)

(defvar jao-tracking--pipe
  (let ((name "/tmp/emacs.status"))
    (unless (file-exists-p name)
      (shell-command (format "mkfifo %s" name name)))
    name))

(defun jao-tracking-set-log (v)
  (when (member window-system '(x))
    (x-change-window-property "_EMACS_LOG" v nil nil nil nil 0))
  (if jao-wayland-enabled
      (let ((inhibit-message t))
        (shell-command (format "echo \"%s\" > %s" v jao-tracking--pipe)))
    (let* ((action (if (string-blank-p v) "remove" "add"))
           (cmd (format "wmctrl -r emacs -b %s,demands_attention" action)))
      (shell-command-to-string cmd))))

(defun jao-tracking--buffer-str (s)
  (if (listp s)
      `(:propertize ,(plist-get s :propertize)
                    face
                    (jao-tracking-minibuffer
                     ,@(when-let ((f (plist-get s 'face)))
                         (jao-tracking-set-log " * ")
                         (list f))))
    `(:propertize "|" face jao-tracking-minibuffer-sep)))

(defun jao-tracking-build-str (new-val)
  (jao-tracking-set-log "")
  (if (listp new-val)
      (mapcar #'jao-tracking--buffer-str new-val)
    new-val))

(defun jao-tracking-update-minibuffer (&rest _)
  (setq jao-tracking-string (jao-tracking-build-str (tracking-status)))
  (jao-minibuffer-refresh))

(defun jao-tracking-echo (_sym new-val _op _where)
  (setq jao-tracking-string (jao-tracking-build-str new-val))
  (jao-minibuffer-refresh))

(defvar jao-tracking-use-scratch 5)
(defvar jao-tracking--start-frame nil)

(defun jao-tracking--remove-visible-buffers ()
  (unless (and jao-afio-use-frames jao-tracking-use-scratch)
    (tracking-remove-visible-buffers)))

;;; package setup
;;;###autoload
(defun jao-tracking-go-to-chats ()
  (interactive)
  (when jao-tracking-use-scratch
    (jao-afio-goto-nth jao-tracking-use-scratch)))

;;;###autoload
(defun jao-tracking-next-buffer ()
  (interactive)
  (if jao-tracking-use-scratch
      (let ((k (if (numberp jao-tracking-use-scratch) jao-tracking-use-scratch 0))
            (n (jao-afio-frame-no)))
        (unless (eq k n) (setq jao-tracking--start-frame n))
        (cond (tracking-buffers
               (let ((bs tracking-buffers))
                 (if (eq k n)
                     (tracking-next-buffer)
                   (jao-afio-goto-nth k)
                   (when (and (car bs) (not (memq (current-buffer) bs)))
                     (pop-to-buffer (car bs)))
                   (tracking-remove-visible-buffers))))
              (jao-tracking--start-frame
               (jao-afio-goto-nth jao-tracking--start-frame)
               (setq jao-tracking--start-frame nil))))
    (tracking-next-buffer))
  (jao-tracking-update-minibuffer))

;;;###autoload
(defun jao-tracking-setup ()
  (jao-minibuffer-add-variable 'jao-tracking-string -10)
  (add-variable-watcher 'tracking-mode-line-buffers #'jao-tracking-echo)
  ;; since we're using the minibuffer, forget the mode line
  (advice-add #'tracking-mode :override (lambda (&optional _) (interactive)))
  (add-hook 'jao-afio-switch-hook #'jao-tracking--remove-visible-buffers)
  (global-set-key (kbd "C-c C-SPC") #'jao-tracking-next-buffer)
  (define-key tracking-mode-map (kbd "C-c C-SPC") #'jao-tracking-next-buffer))

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