;;; dawn.el --- Do things at dawn (and dusk) -*- lexical-binding: t; -*-

;;; Commentary:

;; There is also circadian.el, but it doesn't quite work for me.
;; This code comes mostly from https://gnu.xyz/auto_theme.html, but also
;; somewhere else (which I've forgotten) and my own brain :)

;;; Code:

(require 'calendar)
(require 'cl-lib)
(require 'solar)

(defvar dawn--dawn-timer nil
  "Timer for dawn-command.")

(defvar dawn--dusk-timer nil
  "Timer for dusk-command.")

(defvar dawn--reset-timer nil
  "Timer to reset dawn at midnight.")

(defun dawn-encode-time (f)
  "Encode fractional time F."
  (let ((hhmm (cl-floor f))
        (date (cdddr (decode-time))))
    (encode-time
     (append (list 0
                   (round (* 60 (cadr hhmm)))
                   (car hhmm)
                   )
             date))))

(defun dawn-midnight ()
  "Return the time of the /next/ midnight."
  (let ((date (cdddr (decode-time))))
    (encode-time
     (append (list 0 0 0 (1+ (car date))) (cdr date)))))

(defun dawn-sunrise ()
  "Return the time of today's sunrise."
  (dawn-encode-time (caar (solar-sunrise-sunset (calendar-current-date)))))

(defun dawn-sunset ()
  "Return the time of today's sunset."
  (dawn-encode-time (caadr (solar-sunrise-sunset (calendar-current-date)))))

(defun dawn-schedule (dawn-command dusk-command)
  "Run DAWN-COMMAND at sunrise, and DUSK-COMMAND at dusk.
RESET is an argument for internal use."
  (let ((dawn (dawn-sunrise))
        (dusk (dawn-sunset)))
    (cond
     ((time-less-p nil dawn)
      ;; If it isn't dawn yet, it's still dark.  Run DUSK-COMMAND and schedule
      ;; DAWN-COMMAND and DUSK-COMMAND for later.
      (funcall dusk-command)
      (run-at-time dawn nil dawn-command)
      (run-at-time dusk nil dusk-command))
     ((time-less-p nil dusk)
      ;; If it isn't dusk yet, it's still light.  Run DAWN-COMMAND and schedule
      ;; DUSK-COMMAND.
      (funcall dawn-command)
      (run-at-time dusk nil dusk-command))
     (t ;; Otherwise, it's past dusk, so run DUSK-COMMAND.
      (funcall dusk-command)))
    ;; Schedule a reset at midnight, to re-calculate dawn/dusk times.
    ;(unless reset)
    (run-at-time (dawn-midnight) nil
                 #'dawn-schedule dawn-command dusk-command)))

(provide 'dawn)
;;; dawn.el ends here
