
(require 'find-func) ;; for find-library-name

(defvar pyflyby-bin-path
  ;; Try to find the bin path based on where pyflyby.el lives.
  (let* ((pyflyby-el-name
          (or load-file-name
              (ignore-errors (find-library-name "pyflyby"))
              "pyflyby.el"))
         (module-path (file-truename pyflyby-el-name))
         (binpath (file-truename
                   (concat (file-name-directory module-path) "../../bin"))))
    (and (file-directory-p binpath) binpath))
  "Path containing pyflyby executables.")


(defun pyflyby--pipe-to-command (start end program &rest args)
  "Send text from START to END to process running PROGRAM ARGS.

Returns (exit-value stdout stderr)."
  ;; Yuck: call-process-region can only redirect stderr to a file, not to a
  ;; buffer/string.
  (let* ((stdout-buffer (generate-new-buffer " *pyflyby stdout*"))
         (stderr-buffer (generate-new-buffer " *pyflyby stderr*"))
         (stderr-file (make-temp-file
                       (expand-file-name
                        "pyflyby-log."
                        (or small-temporary-file-directory
                            temporary-file-directory))))
         (exec-path (if pyflyby-bin-path
                        (cons pyflyby-bin-path exec-path) exec-path))
         (exit-value (apply 'call-process-region
                            start end program nil
                            (list stdout-buffer stderr-file)
                            nil args))
         (stdout-text (with-current-buffer stdout-buffer (buffer-string)))
         (stderr-text (with-current-buffer stderr-buffer
                        (format-insert-file stderr-file nil)
                        (buffer-string))))
    (delete-file stderr-file)
    (kill-buffer stdout-buffer)
    (kill-buffer stderr-buffer)
    (list exit-value stdout-text stderr-text)))


(defun pyflyby--replace-text (text)
  "Replace current buffer with TEXT.

Tries to keep point in the same place."
  (let* ((p (point))
         (ptext (buffer-substring-no-properties
                 p (min (point-max) (+ p 1000))))
         (size (buffer-size)))
    ;; Replace the buffer.
    (erase-buffer)
    (insert text)
    ;; Go to previously saved point.  We don't use save-excursion
    ;; since that doesn't work when the whole buffer is replaced,
    ;; destroying markers.  First see if the position matches when
    ;; counting from end of the buffer.
    (setq p (max 0 (+ p (buffer-size) (- size))))
    (goto-char p)
    (unless (search-forward ptext (+ p 1) t)
      ;; Next, try searching for the location based on text near old
      ;; point.
      (goto-char (- p 500))
      (if (search-forward ptext (+ p 1000) t)
          (goto-char (match-beginning 0))
        (goto-char p)))
    (recenter)))


(defun pyflyby-transform-region-with-command (command &rest args)
  (unless (eq major-mode 'python-mode)
    (error "Pyflyby should only be used on python buffers"))
  (let* ((result
          (apply 'pyflyby--pipe-to-command (point-min) (point-max) command args))
         (exit-value (nth 0 result))
         (newtext (nth 1 result))
         (logtext (nth 2 result))
         (logtext (replace-regexp-in-string "/dev/stdin: ?" "" logtext nil t))
         (nlogtext (if (= 0 (length logtext)) "" (concat "\n" logtext))))
    (if (= exit-value 0)
        ;; Process exited successfully.
        (if (string= newtext
                     (buffer-substring-no-properties (point-min) (point-max)))
            ;; No change.
            (message "No changes by `%s'.%s" command nlogtext)
          ;; There were changes.
          (pyflyby--replace-text newtext)
          (message logtext))
      ;; Process failed.
      (error "%s failed with exit code %d.%s" command exit-value logtext))))


(defun pyflyby-tidy-imports ()
  (interactive "*")
  (pyflyby-transform-region-with-command "tidy-imports"))

(defun pyflyby-reformat-imports ()
  (interactive "*")
  (pyflyby-transform-region-with-command "reformat-imports"))


(defalias 'tidy-imports 'pyflyby-tidy-imports)
(defalias 'reformat-imports 'pyflyby-reformat-imports)

(provide 'pyflyby)
