;;; Commands to undo message attribute changes in VM
;;; Copyright (C) 1989, 1990 Kyle E. Jones
;;;
;;; 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 1, 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, write to the Free Software
;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

(defun vm-set-buffer-modified-p (flag &optional clear-modflags buffer)
  (save-excursion
    (and buffer (set-buffer buffer))
    (if (eq (setq flag (not (not flag))) vm-buffer-modified-p)
	()
      (set-buffer-modified-p flag)
      (vm-increment vm-modification-counter)
      (if (null flag)
	  (let ((mp vm-message-list))
	    (setq vm-messages-not-on-disk 0)
	    (if clear-modflags
		(while mp
		  (vm-set-modflag-of (car mp) nil)
		  (setq mp (cdr mp))))))
      (setq vm-buffer-modified-p (if flag "--**-")))))

(defun vm-sanity-check-modification-flag ()
  ;; this is possible if the user used a normal buffer save command instead of
  ;; vm-save-folder...
  (if (not (eq (not (not vm-buffer-modified-p)) (buffer-modified-p)))
      (vm-set-buffer-modified-p (buffer-modified-p))))

(defun vm-undo-boundary ()
  (if (car vm-undo-record-list)
      (setq vm-undo-record-list (cons nil vm-undo-record-list))))

(defun vm-clear-expunge-invalidated-undos (&optional real-folder)
  (let ((udp vm-undo-record-list) udp-prev)
    (while udp
      (cond ((null (car udp))
	     (setq udp-prev udp))
	    ((and (not (eq (car (car udp)) 'vm-set-buffer-modified-p))
		  (vm-deleted-flag (car (cdr (car udp))))
		  (or (null real-folder)
		      (eq real-folder (marker-buffer
				       (vm-start-of (car (cdr (car udp))))))))
	     (cond (udp-prev (setcdr udp-prev (cdr udp)))
		   (t (setq vm-undo-record-list (cdr udp)))))
	    (t (setq udp-prev udp)))
      (setq udp (cdr udp)))
    (and (null real-folder) vm-virtual-buffers
	 (let ((b-list vm-virtual-buffers)
	       (curbuf (current-buffer)))
	   (save-excursion
	     (while b-list
	       (set-buffer (car b-list))
	       (vm-clear-expunge-invalidated-undos curbuf)
	       (setq b-list (cdr b-list))))))
    (vm-clear-modification-flag-undos real-folder)))
	    
(defun vm-clear-modification-flag-undos (&optional real-folder)
  (let ((udp vm-undo-record-list) udp-prev)
    (while udp
      (cond ((null (car udp))
	     (setq udp-prev udp))
	    ((and (eq (car (car udp)) 'vm-set-buffer-modified-p)
		  (or (null real-folder)
		      (eq real-folder (vm-last (car udp)))))
	     (cond (udp-prev (setcdr udp-prev (cdr udp)))
		   (t (setq vm-undo-record-list (cdr udp)))))
	    (t (setq udp-prev udp)))
      (setq udp (cdr udp)))
    (vm-squeeze-consecutive-undo-boundaries)
    (and (null real-folder) vm-virtual-buffers
	 (let ((b-list vm-virtual-buffers)
	       (curbuf (current-buffer)))
	   (save-excursion
	     (while b-list
	       (set-buffer (car b-list))
	       (vm-clear-modification-flag-undos curbuf)
	       (setq b-list (cdr b-list))))))))

;; squeeze out consecutive record separators left by record deletions
(defun vm-squeeze-consecutive-undo-boundaries ()
  (let ((udp vm-undo-record-list) udp-prev)
    (while udp
      (cond ((and (null (car udp)) udp-prev (null (car udp-prev)))
	     (setcdr udp-prev (cdr udp)))
	    (t (setq udp-prev udp)))
      (setq udp (cdr udp)))
    (if (equal '(nil) vm-undo-record-list)
	(setq vm-undo-record-list nil))))
	    
(defun vm-undo-record (sexp)
  (setq vm-undo-record-list (cons sexp vm-undo-record-list)))

(defun vm-undo ()
  "Undo last change to message attributes in the current folder.
Consecutive invocations of this command cause sequentially earlier
changes to be undone.  After an intervening command between undos,
the undos themselves become undoable."
  (interactive)
  (vm-select-folder-buffer)
  (vm-check-for-killed-summary)
  (let ((inhibit-quit t)
	(modified (buffer-modified-p)))
    (if (not (eq last-command 'vm-undo))
	(setq vm-undo-record-pointer vm-undo-record-list))
    (if (not vm-undo-record-pointer)
	(error "No further VM undo information available"))
    ;; skip current record boundary
    (setq vm-undo-record-pointer (cdr vm-undo-record-pointer))
    (while (car vm-undo-record-pointer)
      (eval (car vm-undo-record-pointer))
      (setq vm-undo-record-pointer (cdr vm-undo-record-pointer)))
    (message "VM Undo!")
    (and modified (not (buffer-modified-p))
	 (delete-auto-save-file-if-necessary))
    (vm-update-summary-and-mode-line)))

(defun vm-set-xxxx-flag (m flag norecord function attr-index)
  (let* ((inhibit-quit t) m-list modflag-buffer mirror
	 (virtual (not (eq m (vm-real-message-of m))))
	 (read-only (save-excursion
		      (set-buffer
		       (marker-buffer (if virtual
					 (vm-su-start-of m)
				       (vm-start-of m))))
		      vm-folder-read-only)))
    (and (eq (vm-attributes-of m) (vm-attributes-of (vm-real-message-of m)))
	 (setq m (vm-real-message-of m)
	       mirror t))
    (cond ((not read-only)
	   (aset (vm-attributes-of m) attr-index flag)
	   (vm-mark-for-display-update m)))
    (if (and (not norecord) (not read-only))
	(progn
	  (save-excursion
	    (set-buffer 
	     (marker-buffer
	      (if (and virtual (not mirror))
		  (vm-su-start-of m)
		(vm-start-of m))))
	    (cond ((not vm-buffer-modified-p)
		   (setq modflag-buffer (current-buffer))
		   (vm-set-buffer-modified-p t)
		   (vm-undo-record (list 'vm-set-buffer-modified-p nil))
		   (setq vm-totals nil)))
	    (vm-undo-record (list function m (not flag)))
	    (vm-undo-boundary)
	    (vm-increment vm-modification-counter)
	    (if (eq vm-flush-interval t)
		(vm-stuff-virtual-attributes m)
	      (vm-set-modflag-of m t)))
	  (setq m-list (vm-virtual-messages-of m))
	  (and m-list
	       (save-excursion
		 (while m-list
		   (set-buffer (marker-buffer (vm-su-start-of (car m-list))))
		   (and modflag-buffer
			(vm-undo-record
			 (list 'vm-set-buffer-modified-p nil nil modflag-buffer)))
		   (vm-undo-record (list function m (not flag)))
		   (vm-undo-boundary)
		   (vm-increment vm-modification-counter)
		   (setq vm-totals nil)
		   (setq m-list (cdr m-list)))))))))

(defun vm-set-new-flag (m flag &optional norecord)
  (vm-set-xxxx-flag m flag norecord 'vm-set-new-flag 0))

(defun vm-set-unread-flag (m flag &optional norecord)
  (vm-set-xxxx-flag m flag norecord 'vm-set-unread-flag 1))

(defun vm-set-deleted-flag (m flag &optional norecord)
  (vm-set-xxxx-flag m flag norecord 'vm-set-deleted-flag 2))

(defun vm-set-filed-flag (m flag &optional norecord)
  (vm-set-xxxx-flag m flag norecord 'vm-set-filed-flag 3))

(defun vm-set-replied-flag (m flag &optional norecord)
  (vm-set-xxxx-flag m flag norecord 'vm-set-replied-flag 4))

(defun vm-set-written-flag (m flag &optional norecord)
  (vm-set-xxxx-flag m flag norecord 'vm-set-written-flag 5))

(defun vm-set-forwarded-flag (m flag &optional norecord)
  (vm-set-xxxx-flag m flag norecord 'vm-set-forwarded-flag 6))

;; this is solely for the use of vm-stuff-attributes and appears here
;; only because this function should be grouped with others of its kind
;; together for maintenance purposes.
(defun vm-set-deleted-flag-in-vector (v flag)
  (aset v 2 flag))
