;;; email/mu4e/autoload/mu-lock.el -*- lexical-binding: t; -*-
(autoload 'file-notify-rm-watch "filenotify")
(autoload 'file-notify-add-watch "filenotify")

(defvar +mu4e-lock-file (expand-file-name "mu4e_lock" temporary-file-directory)
  "Location of the lock file which stores the PID of the process currenty running mu4e")
(defvar +mu4e-lock-request-file (expand-file-name "mu4e_lock_request" temporary-file-directory)
  "Location of the lock file for which creating indicated that another process wants the lock to be released")

(defvar +mu4e-lock-greedy nil
  "Whether to 'grab' the `+mu4e-lock-file' if nobody else has it, i.e. start Mu4e")
(defvar +mu4e-lock-relaxed t
  "Whether if someone else wants the lock (signaled via `+mu4e-lock-request-file'), we should stop Mu4e and let go of it")

(defun +mu4e-lock-pid-info ()
  "Get info on the PID refered to in `+mu4e-lock-file' in the form (pid . process-attributes)
 If the file or process do not exist, the lock file is deleted an nil returned."
  (when (file-exists-p +mu4e-lock-file)
    (let* ((coding-system-for-read 'utf-8)
           (pid (string-to-number
                 (with-temp-buffer
                   (insert-file-contents +mu4e-lock-file)
                   (buffer-string))))
           (process (process-attributes pid)))
      (if (and process (string-match-p "[Ee]macs" (alist-get 'comm process)))
          (cons pid process)
        (delete-file +mu4e-lock-file) nil))))

;;;###autoload
(defun +mu4e-lock-available (&optional strict)
  "If the `+mu4e-lock-file' is available (unset or owned by this emacs) return t.
If STRICT only accept an unset lock file."
  (not (when-let* ((lock-info (+mu4e-lock-pid-info))
                   (pid (car lock-info)))
         (when (or strict (/= (emacs-pid) pid)) t))))

;;;###autoload
(defun +mu4e-lock-file-delete-maybe (&optional bury)
  "Check `+mu4e-lock-file', and delete it if this process is responsible for it."
  (when (and (+mu4e-lock-available) (not bury))
    (delete-file +mu4e-lock-file)
    (file-notify-rm-watch +mu4e-lock--request-watcher)))

;;;###autoload
(defun +mu4e-lock-start (orig-fun &optional callback)
  "Check `+mu4e-lock-file', and if another process is responsible for it, abort starting.
Else, write to this process' PID to the lock file"
  (unless (+mu4e-lock-available)
    (call-process "touch" nil nil nil +mu4e-lock-request-file)
    (message "Lock file exists, requesting that it be given up")
    (sleep-for 0.1)
    (delete-file +mu4e-lock-request-file))
  (if (not (+mu4e-lock-available))
      (user-error "Unfortunately another Emacs is already doing stuff with Mu4e, and you can only have one at a time")
    (write-region (number-to-string (emacs-pid)) nil +mu4e-lock-file)
    (delete-file +mu4e-lock-request-file)
    (call-process "touch" nil nil nil +mu4e-lock-request-file)
    (funcall orig-fun callback)
    (when +mu4e-lock--request-watcher
      (file-notify-rm-watch +mu4e-lock--request-watcher))
    (file-notify-add-watch +mu4e-lock-request-file
                           '(change)
                           #'+mu4e-lock--request)))

(defvar +mu4e-lock--file-watcher nil)
(defvar +mu4e-lock--file-just-deleted nil)
(defvar +mu4e-lock--request-watcher nil)

(defun +mu4e-lock-add-watcher ()
  (setq +mu4e-lock--file-just-deleted nil)
  (file-notify-rm-watch +mu4e-lock--file-watcher)
  (setq +mu4e-lock--file-watcher
        (file-notify-add-watch +mu4e-lock-file
                               '(change)
                               #'+mu4e-lock--file-updated)))

(defun +mu4e-lock--request (event)
  "Handle another process requesting the Mu4e lock."
  (when (eq (nth 1 event) 'created)
    (when +mu4e-lock-relaxed
      (mu4e--stop)
      (file-notify-rm-watch +mu4e-lock--file-watcher)
      (file-notify-rm-watch +mu4e-lock--request-watcher)
      (message "Someone else wants to use Mu4e, releasing lock")
      (delete-file +mu4e-lock-file)
      (run-at-time 0.2 nil #'+mu4e-lock-add-watcher))
    (delete-file +mu4e-lock-request-file)))

(defun +mu4e-lock--file-updated (event)
  (if +mu4e-lock--file-just-deleted
      (+mu4e-lock-add-watcher)
    (when (eq (nth 1 event) 'deleted)
      (setq +mu4e-lock--file-just-deleted t)
      (when (and +mu4e-lock-greedy (+mu4e-lock-available t))
        (message "Noticed Mu4e lock was available, grabbed it")
        (run-at-time 0.2 nil #'mu4e)))))
