;;; python-settings.el --- Python settings  -*- mode: emacs-lisp; lexical-binding: t -*-

;;; Commentary:

;; Requirements:
;;   program: python
;;   Emacs packages: anaconda-mode company-anaconda sphinx-doc

;;; Code:
(use-package python
  :delight (python-mode "Py") ;; "Py " "\u1F15F"
  :commands (python-mode)
  :bind (:map python-mode-map
         ;; swap C-u C-c C-c with C-c C-c, so C-c C-c sends __main__
         ("C-c C-c" . my-python-shell-send-buffer-w-main)
         ("C-u C-c C-c" . my-python-shell-send-buffer)
         ("M-m m c c" . my-python-execute-file)
         ("M-m m c C" . my-python-execute-file-switch)
         ("M-m m s b" . my-python-shell-send-buffer-w-main))
  :config
  (setq python-indent-offset 2)                         ; python-indent is obsolete since 24.3; use python-indent-offset instead.
  (setq python-indent-guess-indent-offset nil)          ; default t. nil to use the config value python-indent-offset
  (setq python-indent-guess-indent-offset-verbose nil)  ; hide message like: Can’t guess python-indent-offset, using defaults: 2
  ;;(setq python-shell-interpreter "ipython")
  ;;(setq python-shell-interpreter-args "-i")

  (defun my-pyenv-executable-find (command)
    "Find executable taking pyenv shims into account.
If the executable is a system executable and not in the same path
as the pyenv version then also return nil. This works around https://github.com/pyenv/pyenv-which-ext
"
    (if (executable-find "pyenv")
        (progn
          (let ((pyenv-string (shell-command-to-string (concat "pyenv which " command)))
                (pyenv-version-names (split-string (string-trim (shell-command-to-string "pyenv version-name")) ":"))
                (executable nil)
                (i 0))
            (if (not (string-match "not found" pyenv-string))
                (while (and (not executable)
                            (< i (length pyenv-version-names)))
                  (if (string-match (elt pyenv-version-names i) (string-trim pyenv-string))
                      (setq executable (string-trim pyenv-string)))
                  (if (string-match (elt pyenv-version-names i) "system")
                      (setq executable (string-trim (executable-find command))))
                  (setq i (1+ i))))
            executable))
    (executable-find command)))
  
  (defun my-python-execute-file (arg)
    "Execute a python script in a shell."
    (interactive "P")
    ;; set compile command to buffer-file-name
    ;; universal argument put compile buffer in comint mode
    (let ((universal-argument t)
          (compile-command (format "%s %s"
                                   (my-pyenv-executable-find python-shell-interpreter)
                                   (shell-quote-argument (file-name-nondirectory buffer-file-name)))))
      (if arg
          (call-interactively 'compile)
        (compile compile-command t)
        (with-current-buffer (get-buffer "*compilation*")
          (inferior-python-mode)))))

  (defun my-python-execute-file-switch (arg)
    "Execute a python script in a shell and switch to the shell buffer in `insert state'."
    (interactive "P")
    (my-python-execute-file arg)
    (switch-to-buffer-other-window "*compilation*")
    (end-of-buffer)
    (evil-insert-state))

  (defun my-python-shell-send-buffer (&optional send-main)
    "Send buffer content to shell."
    (interactive)
    (let ((python-mode-hook nil))  ;; TODO: why?
      (python-shell-send-buffer send-main)))

  (defun my-python-shell-send-buffer-w-main ()
    "Send buffer content to shell with main."
    (interactive)
    (my-python-shell-send-buffer t))

  (defadvice run-python
      (after my-advice-run-python activate)
    "set flag to allow exit without query on any active python
process."
    (set-process-query-on-exit-flag ad-return-value nil))

  (defun my-python-shell-send-buffer-w-args (args)
    "Send buffer content to shell with main and arguments."
    (interactive "sPython arguments: ")
    (let ((source-buffer (current-buffer)))
      (with-temp-buffer
        (insert "import sys; sys.argv = '''" args "'''.split()\n")
        (insert-buffer-substring source-buffer)
        (my-python-shell-send-buffer-w-main))))
  )

;; needs system package python-jedi
(use-package anaconda-mode  ;; works with company-mode via company-anaconda
  :after python
  :delight (anaconda-mode "A")  ;; \u24B6 a
  :bind (([remap anaconda-mode-show-doc] . my-anaconda-mode-show-doc))  ;; M-?
  :hook (python-mode
         (python-mode . anaconda-eldoc-mode))  ;; if cursor is between function's parenthesis show parameter in echo area
  :config
  (setq anaconda-mode-installation-directory (concat user-cache-directory "anaconda-mode"))
  ;; compared to `anaconda-mode-show-doc' using `my-anaconda-mode-show-doc-callback'
  (defun my-anaconda-mode-show-doc ()
    "Show documentation for context at point."
    (interactive)
    ;;(anaconda-mode-call "show_doc" 'anaconda-mode-show-doc-callback)   ;; removed
    (anaconda-mode-call "show_doc" 'my-anaconda-mode-show-doc-callback)  ;; instead
    )
  ;; compared to `anaconda-mode-show-doc-callback' no change of focus
  (defun my-anaconda-mode-show-doc-callback (result)
    "Process view doc RESULT."
    (if (> (length result) 0)
        (if (and anaconda-mode-use-posframe-show-doc
                 (require 'posframe nil 'noerror)
                 (posframe-workable-p))
            (anaconda-mode-documentation-posframe-view result)
          ;;(pop-to-buffer (anaconda-mode-documentation-view result) t)  ;; removed
          (anaconda-mode-documentation-view result)                      ;; instead
          )
      (message "No documentation available")))
  )

(use-package company-anaconda
  :after (python anaconda-mode company)
  :bind (([remap anaconda-mode-complete] . company-anaconda))
  :config
  (add-to-list 'company-backends '(company-anaconda :with company-capf)))  ;; to see anaconda-mode completions together with ones comes from inferior python process

;; https://melpa.org/#/sphinx-doc
;; https://github.com/naiquevin/sphinx-doc.el
;; enable the sphinx-doc-mode and bind the interactive function sphinx-doc to C-c M-d.
(use-package sphinx-doc
  :delight (sphinx-doc-mode "Sph")  ;; \u24C8 s
  :hook (python-mode . sphinx-doc-mode))

(provide 'python-settings)
;;; python-settings.el ends here
