;;; yeetube-mpv.el --- Provide yeetube mpv functionality  -*- lexical-binding: t; -*-

;; Copyright (C) 2023  Thanos Apollo

;; Author: Thanos Apollo <public@thanosapollo.org>
;; Keywords: extensions youtube videos
;; URL: https://git.thanosapollo.org/yeetube

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

;; This package is a yeetube extension, to start an mpv process &
;; remotely control it.

;;; Code:

(require 'cl-lib)

;; Variables defined in yeetube.el (via keymap-popup-define)
(defvar yeetube-ytdlp-program)
(defvar yeetube-torsocks-program)
(defvar yeetube-mpv-video-quality)
(defvar yeetube-mpv-enable-torsocks)
(defvar yeetube-mpv-no-video)

(defconst yeetube-mpv--process-name "yeetube-mpv"
  "Name of the mpv subprocess.")

(defcustom yeetube-mpv-program (and (executable-find "mpv") "mpv")
  "Path for mpv executable."
  :type '(choice (const :tag "Not found" nil) string)
  :group 'yeetube)

(defcustom yeetube-mpv-additional-flags nil
  "Additional flags to pass to mpv."
  :type '(repeat string)
  :group 'yeetube)

(defvar yeetube-mpv-currently-playing nil
  "Currently playing information.")

(defun yeetube-mpv-modeline-string ()
  "Return modeline string for yeetube-mpv."
  (or yeetube-mpv-currently-playing ""))

(defun yeetube-mpv-check ()
  "Check if mpv and yt-dlp is installed."
  (unless (and yeetube-mpv-program yeetube-ytdlp-program)
    (error "Unable to play video.  Please install `yt-dlp' and `mpv'")))

(defun yeetube-mpv--idle-p ()
  "Return non-nil when no mpv process is running."
  (not (get-process yeetube-mpv--process-name)))

(defun yeetube-mpv--sentinel (proc _event)
  "Clear modeline state when mpv process PROC exits."
  (when (memq (process-status proc) '(exit signal))
    (setq yeetube-mpv-currently-playing nil)
    (force-mode-line-update)))

(defun yeetube-mpv-process (command)
  "Start yeetube process for shell COMMAND."
  (yeetube-mpv-check)
  (when-let* ((old (get-process yeetube-mpv--process-name)))
    (delete-process old))
  (let ((proc (start-process-shell-command
               yeetube-mpv--process-name "*yeetube-mpv-output*" command)))
    (set-process-sentinel proc #'yeetube-mpv--sentinel)
    proc))

(defun yeetube-mpv-ytdl-format-video-quality (resolution)
  "Return shell quoted argument for ytdlp with RESOLUTION.

Use the default 720p quality when RESOLUTION is nil."
  (shell-quote-argument
   (format "bestvideo[height<=?%s]+bestaudio/best"
           (or resolution "720"))))

(defun yeetube-mpv-play (input &optional info)
  "Start yeetube process to play INPUT using mpv.

INPUT can be a URL or a local file path.
INFO is optional information to display with `yeetube-mpv-modeline-mode'.

This function is not specific to just playing URLs.  Feel free to use
it to play local files."
  (when (and yeetube-mpv-enable-torsocks
             (or (null yeetube-torsocks-program)
                 (string= yeetube-torsocks-program "")))
    (user-error "Torsocks program not found; install torsocks or disable torsocks"))
  (yeetube-mpv-check)
  (let* ((base-flags (remove "--no-video" yeetube-mpv-additional-flags))
         (flags (append (when yeetube-mpv-no-video '("--no-video"))
                        base-flags))
         (command
          (concat (when yeetube-mpv-enable-torsocks
                    (concat (shell-quote-argument yeetube-torsocks-program)
                            " "))
                  (shell-quote-argument yeetube-mpv-program)
                  " --ytdl-format="
                  (yeetube-mpv-ytdl-format-video-quality yeetube-mpv-video-quality)
                  " "
                  (shell-quote-argument input)
                  (when flags
                    (concat " " (mapconcat #'shell-quote-argument flags " ")))))
         (proc (yeetube-mpv-process command)))
    (message "Yeetube command: %s" command)
    (message (if yeetube-mpv-enable-torsocks
                 "yeetube: Starting mpv process (Using Torsocks)"
               "yeetube: Starting mpv process"))
    (setf yeetube-mpv-currently-playing (and info (format "[%s]" info)))
    proc))

(defconst yeetube-mpv--modeline-format
  '(:eval (format " ♫:%s" (yeetube-mpv-modeline-string))))

(define-minor-mode yeetube-mpv-modeline-mode
  "Minor mode for showing currently playing information on the modeline.

To use this mode, you should set `yeetube-play-function' to
`yeetube-mpv-play'."
  :global t
  :group 'yeetube
  :lighter nil
  (if yeetube-mpv-modeline-mode
      (add-to-list 'global-mode-string yeetube-mpv--modeline-format)
    (cl-callf2 delq yeetube-mpv--modeline-format global-mode-string))
  (force-mode-line-update))

(defun yeetube-mpv-send-keypress (key)
  "Send KEY to `yeetube-mpv-process'."
  (interactive "sKey: ")
  (unless (get-process yeetube-mpv--process-name)
    (user-error "No mpv process running"))
  (process-send-string yeetube-mpv--process-name key))

(defun yeetube-mpv-toggle-pause ()
  "Toggle pause mpv."
  (interactive)
  (yeetube-mpv-send-keypress "p")
  (message "yeetube: toggle pause"))

(defun yeetube-mpv-toggle-fullscreen ()
  "Toggle fullscreen."
  (interactive)
  (yeetube-mpv-send-keypress "f")
  (message "toggle fullscreen"))

(defun yeetube-mpv-toggle-video ()
  "Toggle video mpv."
  (interactive)
  (yeetube-mpv-send-keypress "_")
  (message "yeetube: toggle video"))

(defun yeetube-mpv-forward ()
  "Forward video."
  (interactive)
  (yeetube-mpv-send-keypress "[C"))

(defun yeetube-mpv-backward ()
  "Go backwards in video."
  (interactive)
  (yeetube-mpv-send-keypress "[D"))

(defun yeetube-mpv-quit ()
  "Quit mpv."
  (interactive)
  (yeetube-mpv-send-keypress "q")
  (message "yeetube: quit"))

(provide 'yeetube-mpv)
;;; yeetube-mpv.el ends here
