;;; ess.el --- Emacs Speaks Statistics  -*- lexical-binding: t; -*-

;; Copyright (C) 1997-2024 Free Software Foundation, Inc.

;; Author: David Smith <dsmith@stats.adelaide.edu.au>
;;         A.J. Rossini <blindglobe@gmail.com>
;;         Richard M. Heiberger <rmh@temple.edu>
;;         Kurt Hornik <Kurt.Hornik@R-project.org>
;;         Martin Maechler <maechler@stat.math.ethz.ch>
;;         Rodney A. Sparapani <rsparapa@mcw.edu>
;;         Stephen Eglen <stephen@gnu.org>
;;         Sebastian P. Luque <spluque@gmail.com>
;;         Henning Redestig <henning.red@googlemail.com>
;;         Vitalie Spinu <spinuvit@gmail.com>
;;         Lionel Henry <lionel.hry@gmail.com>
;;         J. Alexander Branham <alex.branham@gmail.com>
;;
;; Maintainer: ESS Core Team <ESS-core@r-project.org>
;; Created: 7 Jan 1994
;; Version: 25.01.0
;; URL: https://ess.r-project.org/
;; Package-Requires: ((emacs "25.1"))
;; ESSR-Version: 1.8

;; This file is part of GNU Emacs.

;;; License:
;;
;; 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
;; <http://www.gnu.org/licenses/>

;;; Commentary:
;;
;; Emacs Speaks Statistics (ESS) is a package designed to support editing of
;; scripts and interaction with various statistical analysis programs such as R,
;; S-Plus, SAS, Stata and OpenBUGS/JAGS. For more details please visit ESS home
;; page at https://ess.r-project.org/

;;; Code:

(require 'ess-utils)
(require 'cl-generic)
(require 'lisp-mnt)
(defvar reporter-prompt-for-summary-p)


;; Versions

(defconst ess-version (eval-when-compile
                        (lm-version (or (and (boundp 'load-true-file-name)
                                             load-true-file-name)
                                        load-file-name
                                        buffer-file-name)))
  "Version of ESS currently loaded.")

(defconst essr-version (eval-when-compile
                         (lm-with-file (or (and (boundp 'load-true-file-name)
                                                load-true-file-name)
                                           load-file-name
                                           buffer-file-name)
                           (lm-header "ESSR-Version")))
  "Version of ESSR package.")

(defvar ess-revision nil
  "The revision and date of ESS.
Is set  by \\[ess-version-string].")

;;;###autoload
(defun ess-version ()
  "Return a string with ESS version information."
  (interactive)
  (message (format "ess-version: %s (loaded from %s)"
                   (ess-version-string)
                   (file-name-directory ess-lisp-directory))))

(defun ess-version-string ()
  (let* ((ess-dir (file-name-directory ess-lisp-directory)) ; if(<from source>) the top-level 'ess/'
         (is-release (file-exists-p (concat ess-etc-directory ".IS.RELEASE")))
         (rel-string (if is-release "Released "))
         (git-ref-fn (concat ess-dir ".git/HEAD"))
         (git-ref (when (file-exists-p git-ref-fn)
                    (with-current-buffer (find-file-noselect git-ref-fn)
                      (goto-char (point-min))
                      (when (re-search-forward "ref: \\(.*\\)\n" nil t)
                        (match-string 1)))))
         (git-fname (if git-ref
                        (concat ess-dir ".git/" git-ref)
                      ;; For release
                      (concat ess-etc-directory "git-ref")))
         (git-rev (when (file-exists-p git-fname)
                    (with-current-buffer (find-file-noselect git-fname)
                      (goto-char (point-min))
                      (concat "git: "(buffer-substring
                                      (point-min) (line-end-position))))))
         (elpa-fname (concat ess-dir "ess-pkg.el"))
         (elpa-rev (when (file-exists-p elpa-fname)
                     ;; Get it from ELPA dir name, (probably won't work if installed manually)
                     (concat "elpa: "
                             (replace-regexp-in-string "ess-" ""
                                                       (file-name-nondirectory
                                                        (substring ess-dir 1 -1)))))))
    ;; Set the "global" ess-revision:
    (setq ess-revision (format "%s%s%s"
                               (or rel-string "")
                               (or git-rev "")
                               (or elpa-rev "")))
    (when (string= ess-revision "")
      (setq ess-revision "<unknown>"))
    (concat ess-version " [" ess-revision "]")))


;;; Bug Reporting

;;;###autoload
(defun ess-submit-bug-report ()
  "Submit a bug report to the ESS maintainers."
  (interactive)
  (let ((reporter-prompt-for-summary-p 't))
    (reporter-submit-bug-report
     "ess-bugs@r-project.org"
     (concat "ess-mode " (ess-version-string))
     (list 'ess-language
           'ess-dialect
           'ess-ask-for-ess-directory
           'ess-ask-about-transfile
           'default-directory
           'ess-keep-dump-files
           'ess-source-directory
           'ess-use-ido
           'ess-use-eldoc
           'ess-use-tracebug
           'ess-use-auto-complete
           'ess-use-company
           'ess-eval-visibly-p
           'ess-can-eval-in-background
           'ess-local-process-name)
     nil
     (lambda ()
       ;;(goto-char (point-max))
       (rfc822-goto-eoh)
       (forward-line 1)
       (insert "\n\n-------------------------------------------------------\n")
       (insert "This bug report will be sent to the ESS bugs email list\n")
       (insert "Press C-c C-c when you are ready to send your message.\n")
       (insert "-------------------------------------------------------\n\n")
       (insert (with-current-buffer ess-dribble-buffer
                 (goto-char (point-max))
                 (forward-line -100)
                 (buffer-substring-no-properties (point) (point-max))))))))



;;; Timer

(defcustom ess-idle-timer-interval 1
  "Number of seconds to wait before running function in `ess-idle-timer-functions'."
  :type '(integer)
  :group 'ess)

(defvar ess-idle-timer-functions nil
  "A list of functions to run each `ess-idle-timer-interval' idle seconds.
If your function calls the process, you better use
`ess-when-new-input' to wrap your call. If you call the
subprocess please respect `ess-can-eval-in-background' variable.

These functions are run with `run-hooks'. Use `add-hook' to add
symbols to this variable.

Most likely you will need a local hook. Then you should specify
the LOCAL argument to `add-hook' and initialize it in
`ess-mode-hook' or `ess-post-run-hook', or one of the more
specialized hooks `ess-r-post-run-hook',`ess-stata-post-run-hook'
etc.")

(defun ess--idle-timer-function nil
  "Internal function executed by `ess--idle-timer'."
  (run-hooks 'ess-idle-timer-functions))

(require 'timer)
(defvar ess--idle-timer
  (run-with-idle-timer ess-idle-timer-interval 'repeat
                       #'ess--idle-timer-function)
  "Timer used to run `ess-idle-timer-functions'.")


;;; Dispatch on ess-dialect
;; Inspired by major-mode specializer in cl-generic.el

;; FIXME: Implement a notion of derived dialects. major-mode specializer cannot
;; be used here as we need same dialect in different major-modes.

;; Two parts:
;; 1) first define a specializer (ess-dialect= DIALECT) to match symbols
;;    representing ess dialects.
;; 2) then define a context-rewriter so you can write
;;   `&context (ess-dialect "R")` rather than
;;   `&context (ess-dialect (ess-dialect= "R"))`.

(cl-generic-define-generalizer ess--generic-dialect-generalizer
  95
  (lambda (name &rest _) `(if (stringp ,name) (intern ,name)
                            (if (symbolp ,name) ,name)))
  (lambda (tag &rest _) `((ess-dialect= ,tag))))

(cl-defmethod cl-generic-generalizers ((_specializer (head ess-dialect=)))
  "Support for (ess-dialect DIALECT) context specializer."
  (list ess--generic-dialect-generalizer))

(cl-generic-define-context-rewriter ess-dialect (dialect)
  `(ess-dialect (ess-dialect= ,(if (stringp dialect)
                                   (intern dialect)
                                 dialect))))

;; (cl-defgeneric ess-print-dialect ()
;;   (error "unknown dialect %s" ess-dialect))
;; (cl-defmethod ess-print-dialect (&context (ess-dialect "R"))
;;   (message "dialect: R"))
;; (cl-defmethod ess-print-dialect (&context (ess-dialect "julia"))
;;   (message "dialect: julia"))

(provide 'ess)

;;; ess.el ends here
