;;; xdarcs.el --- darcs interface for dvc

;; Copyright (C) 2006, 2007, 2008 by all contributors

;; Author: Stefan Reichoer, <stefan@xsteve.at>

;; This file 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 2, or (at your option)
;; any later version.

;; This file 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 GNU Emacs; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:

;; The darcs interface for dvc

;;; History:

;;

;;; Code:

(require 'dvc-core)
(require 'dvc-utils)
(require 'dvc-diff)
(require 'xdarcs-core)

(defun xdarcs-initialize (&optional dir)
  "Run darcs initialize."
  (interactive
   (list (expand-file-name (dvc-read-directory-name "Directory for darcs initialize: "
                                                    (or default-directory
                                                        (getenv "HOME"))))))
  (let ((default-directory dir))
    (dvc-run-dvc-sync 'xdarcs (list "initialize")
                      :finished (dvc-capturing-lambda
                                    (output error status arguments)
                                  (message "darcs initialize finished")))))

;;;###autoload
(defun xdarcs-dvc-add-files (&rest files)
  "Run darcs add."
  (dvc-trace "xdarcs-add-files: %s" files)
  (dvc-run-dvc-sync 'xdarcs (append '("add") files)
                    :finished (dvc-capturing-lambda
                                  (output error status arguments)
                                (message "darcs add finished"))))

(defun xdarcs-command-version ()
  "Run darcs --version."
  (interactive)
  (let ((version (dvc-run-dvc-sync 'xdarcs '("--version")
                                   :finished 'dvc-output-buffer-handler)))
    (when (interactive-p)
      (message "darcs version: %s" version))
    version))

;; --------------------------------------------------------------------------------
;; whatsnew
;; --------------------------------------------------------------------------------
;;
;; (defun xdarcs-whatsnew ()
;;   "Run darcs whatsnew.
;; When called with a prefix argument, specify the --look-for-adds parameter."
;;   (interactive)
;;   (let ((param-list '("whatsnew")))
;;     (when current-prefix-arg
;;       (add-to-list 'param-list "--look-for-adds" t))
;;     (dvc-run-dvc-display-as-info 'xdarcs param-list)))
(defun xdarcs-parse-whatsnew  (changes-buffer)
  (dvc-trace "xdarcs-parse-whatsnew (dolist)")
  (let ((status-list
         (split-string (dvc-buffer-content (current-buffer)) "\n")))
    (with-current-buffer changes-buffer
      (setq dvc-header (format "darcs whatsnew --look-for-adds for %s\n" default-directory))
      (let ((buffer-read-only)
            status modif modif-char)
        (dolist (elem status-list)
          (unless (string= "" elem)
            (setq modif-char (aref elem 0))
            (cond ((eq modif-char ?M)
                   (setq status "M"
                         modif "M")
                   (when (or (string-match "\\(.+\\) -[0-9]+ \\+[0-9]+$"
                                           elem)
                             (string-match "\\(.+\\) [+-][0-9]+$"
                                           elem))
                     (setq elem (match-string 1 elem))))
                  ;; ???a
                  ((eq modif-char ?a)
                   (setq status "?"))
                  ((eq modif-char ?A)
                   (setq status "A"
                         modif " "))
                  ((eq modif-char ?R)
                   (setq status "D"))
                  ((eq modif-char ??)
                   (setq status "?"))
                  (t
                   (setq modif nil
                         status nil)))
            (when (or modif status)
              (ewoc-enter-last
               dvc-fileinfo-ewoc
               (make-dvc-fileinfo-legacy
                :data (list 'file
                            ;; Skip the status and "./" in the filename
                            (substring elem 4)
                            status
                            modif))))))))))

;;;###autoload
(defun xdarcs-whatsnew (&optional path)
  "Run darcs whatsnew."
  (interactive (list default-directory))
  (let* ((dir (or path default-directory))
         (root (xdarcs-tree-root dir))
         (buffer (dvc-prepare-changes-buffer
                  `(xdarcs (last-revision ,root 1))
                  `(xdarcs (local-tree ,root))
                  'status root 'xdarcs)))
    (dvc-switch-to-buffer-maybe buffer)
    (setq dvc-buffer-refresh-function 'xdarcs-whatsnew)
    (dvc-save-some-buffers root)
    (dvc-run-dvc-sync
     'xdarcs '("whatsnew" "--look-for-adds")
     :finished
     (dvc-capturing-lambda (output error status arguments)
       (with-current-buffer (capture buffer)
         (if (> (point-max) (point-min))
             (dvc-show-changes-buffer output 'xdarcs-parse-whatsnew
                                      (capture buffer))
           (dvc-diff-no-changes (capture buffer)
                                "No changes in %s"
                                (capture root))))
       :error
       (dvc-capturing-lambda (output error status arguments)
         (dvc-diff-error-in-process (capture buffer)
                                    "Error in diff process"
                                    output error))))))

;;;###autoload
(defun xdarcs-dvc-missing (&optional other)
  "Run 'darcs pull --dry-run -s -v' to see what's missing"
  (interactive)
  (let ((buffer (dvc-get-buffer-create 'xdarcs 'missing)))
    (dvc-run-dvc-async
     'xdarcs '("pull" "--dry-run" "-s" "-v")
     :finished
     (dvc-capturing-lambda (output error status arguments)
       (progn
         (with-current-buffer (capture buffer)
           (let ((inhibit-read-only t))
             (erase-buffer)
             (insert-buffer-substring output)
             (goto-char (point-min))
             (re-search-forward "^Would pull the following changes:" nil t)
             (xdarcs-missing-next 1)
             (xdarcs-missing-mode)))
         (goto-char (point-min))
         (dvc-switch-to-buffer (capture buffer)))))))

(defvar xdarcs-review-recenter-position-on-next-diff 5)

(defvar xdarcs-missing-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map (dvc-prefix-buffer ?L) 'dvc-open-internal-log-buffer)
    (define-key map dvc-keyvec-quit 'dvc-buffer-quit)
    (define-key map [?n] 'xdarcs-missing-next)
    (define-key map [?p] 'xdarcs-missing-previous)
    (define-key map [?\ ] 'xdarcs-missing-dwim-next)
    (define-key map (dvc-prefix-merge ?f) 'dvc-pull) ;; hint: fetch, p is reserved for push
    map)
  "Keymap used in a xdarcs missing buffer.")

(defvar xdarcs-missing-patch-start-regexp
  "^\\(Mon\\|Tue\\|Wed\\|Thu\\|Fri\\|Sat\\|Sun\\).+$")

(defvar xdarcs-missing-font-lock-keywords
  `((,xdarcs-missing-patch-start-regexp . font-lock-function-name-face)
    ("^hunk.+" . font-lock-variable-name-face))
  "Keywords in `xdarcs-missing-mode'.")

(define-derived-mode xdarcs-missing-mode fundamental-mode
  "xdarcs missing mode"
  "Major mode to show the output of a call to `xdarcs-missing'."
  (dvc-install-buffer-menu)
  (set (make-local-variable 'font-lock-defaults)
       (list 'xdarcs-missing-font-lock-keywords t nil nil))
  (toggle-read-only 1))

(defun xdarcs-missing-next (n)
  (interactive "p")
  (end-of-line)
  (re-search-forward xdarcs-missing-patch-start-regexp nil t n)
  (beginning-of-line)
  (when xdarcs-review-recenter-position-on-next-diff
    (recenter xdarcs-review-recenter-position-on-next-diff)))

(defun xdarcs-missing-previous (n)
  (interactive "p")
  (end-of-line)
  (re-search-backward xdarcs-missing-patch-start-regexp)
  (re-search-backward xdarcs-missing-patch-start-regexp nil t n)
  (when xdarcs-review-recenter-position-on-next-diff
    (recenter xdarcs-review-recenter-position-on-next-diff)))

(defun xdarcs-missing-dwim-next ()
  "Either move to the next changeset via `xdarcs-missing-next' or call `scroll-up'.
When the beginning of the next changeset is already visible, call `xdarcs-missing-next',
otherwise call `scroll-up'."
  (interactive)
  (let* ((start-pos (point))
         (window-line (count-lines (window-start) start-pos))
         (window-height (dvc-window-body-height))
         (distance-to-next-changeset (save-window-excursion (xdarcs-missing-next 1) (count-lines start-pos (point)))))
    (goto-char start-pos)
    (when (eq distance-to-next-changeset 0) ; last changeset
      (setq distance-to-next-changeset (count-lines start-pos (point-max))))
    (if (< (- window-height window-line) distance-to-next-changeset)
        (scroll-up)
      (xdarcs-missing-next 1))))


(defun xdarcs-pull-finish-function (output error status arguments)
  (let ((buffer (dvc-get-buffer-create 'xdarcs 'pull)))
    (with-current-buffer buffer
      (let ((inhibit-read-only t))
        (erase-buffer)
        (insert-buffer-substring output)
        (toggle-read-only 1)))
    (let ((dvc-switch-to-buffer-mode 'show-in-other-window))
      (dvc-switch-to-buffer buffer))))

;;;###autoload
(defun xdarcs-pull (&optional other)
  "Run darcs pull --all.
If OTHER is nil, pull from the repository most recently pulled
from or pushed to.  If OTHER is a string, pull from that
repository."
  (interactive)
  (dvc-run-dvc-async 'xdarcs (list "pull" "--all" other)
                     :error 'xdarcs-pull-finish-function
                     :finished 'xdarcs-pull-finish-function))

;; --------------------------------------------------------------------------------
;; diff
;; --------------------------------------------------------------------------------
(defun xdarcs-parse-diff (changes-buffer)
  (save-excursion
    (while (re-search-forward
            "^diff\\( -[^ ]*\\)* old-[^ ]* new-[^/]*/\\(.*\\)$" nil t)

      (let* ((name (match-string-no-properties 2))
             ; Darcs does not appear to provide any of this information via
             ; "darcs diff". But maybe that won't always be the case?
             ; Also, going forwards might help all the diffs to appear...
             (added (progn (forward-line 1)
                           (looking-at "^--- /dev/null")))
             (removed (progn (forward-line 1)
                             (looking-at "^\\+\\+\\+ /dev/null"))))

      ; Darcs 2.30, at least, is not putting any blank lines between diffs...
      (save-excursion
        (forward-line -2)
        (if (not (or (looking-back "^$")
                     (= (point) (point-min))))
            (insert "\n")))

        (with-current-buffer changes-buffer
          (ewoc-enter-last
           dvc-fileinfo-ewoc
           (make-dvc-fileinfo-legacy
            :data (list 'file
                        name
                        (cond (added   "A")
                              (removed "D")
                              (t " "))
                        (cond ((or added removed) " ")
                              (t "M"))
                        " "             ; dir. directories are not
                                        ; tracked in git
                        nil))))))))

;;;###autoload
(defun xdarcs-dvc-diff (&optional against path dont-switch)
  (interactive (list nil nil current-prefix-arg))
  (let* ((cur-dir (or path default-directory))
         (orig-buffer (current-buffer))
         (root (dvc-tree-root cur-dir))
         (buffer (dvc-prepare-changes-buffer
                  `(xdarcs (last-revision ,root 1))
                  `(xdarcs (local-tree ,root))
                  'diff root 'xdarcs))
         (command-list '("diff" "--unified")))
    (dvc-switch-to-buffer-maybe buffer)
    (when dont-switch (pop-to-buffer orig-buffer))
    (dvc-save-some-buffers root)
    (dvc-run-dvc-sync 'xdarcs command-list
                      :finished
                      (dvc-capturing-lambda (output error status arguments)
                        (dvc-show-changes-buffer output 'xdarcs-parse-diff
                                                 (capture buffer))))))
;; --------------------------------------------------------------------------------
;; dvc revision support
;; --------------------------------------------------------------------------------
;;
;; It seems that there if no subcommand in darcs to get specified
;; revision of a file. So I use following trick:
;; 1. Make a diff between the file in local copy and the last revision
;;    of file. Then
;; 2. Apply the diff as patch reversely(-R) to the file in the local
;;    copy with patch command. With -o option, patch command doesn't
;;    modify the file in local copy; patch command create the applied
;;    file at /tmp. Finally
;; 3. Do insert-file-contents to the current buffer.
;;
;; Darcs experts, if you know better way, please, let us know.
;;
;; - Masatake
;;
;;;###autoload
(defun xdarcs-revision-get-last-revision (file last-revision)
  "Insert the content of FILE in LAST-REVISION, in current buffer.

LAST-REVISION looks like
\(\"path\" NUM)"
  (dvc-trace "xdarcs-revision-get-last-revision file:%S last-revision:%S" file last-revision)
  (let* (;;(xdarcs-rev (int-to-string (nth 1 last-revision)))
         (default-directory (car last-revision))
         ;; TODO: support the last-revision parameter??
         (patch (dvc-run-dvc-sync
                 'xdarcs (list "diff" "--unified" file)
                 :finished 'dvc-output-buffer-handler))
         (output-buffer (current-buffer))
         (output-file   (dvc-make-temp-name "xdarcs-file-find"))
         (patch-cmdline (format "cd \"%s\"; patch -R -o \"%s\""
                                default-directory
                                output-file))
         ;; TODO: Use dvc's process/buffer management facility.
         (status (with-temp-buffer
                   (insert patch)
                   (shell-command-on-region (point-min)
                                            (point-max)
                                            patch-cmdline
                                            output-buffer))))
    (when (zerop status)
      (with-current-buffer output-buffer
        (insert-file-contents output-file)
        ;; TODO: remove output-file
        ))))

;;;###autoload
(defun xdarcs-dvc-revert-files (&rest files)
  "Run darcs revert."
  (dvc-trace "xdarcs-revert-files: %s" files)
  (let ((default-directory (xdarcs-tree-root)))
    (dvc-run-dvc-sync 'xdarcs (append '("revert" "-a") (mapcar #'file-relative-name files))
                      :finished (dvc-capturing-lambda
                                    (output error status arguments)
                                  (message "xdarcs revert finished")))))

;;;###autoload
(defun xdarcs-dvc-remove-files (&rest files)
  "Run darcs remove."
  (dvc-trace "xdarcs-remove-files: %s" files)
  (dvc-run-dvc-sync 'xdarcs (append '("remove" "-a") files)
                    :finished (dvc-capturing-lambda
                                  (output error status arguments)
                                (message "xdarcs remove finished"))))


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