;;; Lisp Machine mail reader -*- Mode:LISP; Package:ZWEI; Base:8; Readtable:ZL -*- 
;;; Buffer handling, definition are in DEFS
;;; ** (c) Copyright 1981 Massachusetts Institute of Technology **
;;; An invalid Enhancements copyright notice on AI:ZMAIL;MFILES 236 removed on 5/7/82 by RG
;;;  This file had been installed as the official MIT source in direct contravention
;;;  to instructions to Symbolics personnel acting on MIT's behalf.

(TV:ADD-TYPEOUT-ITEM-TYPE *ZMAIL-TYPEOUT-ITEM-ALIST* ZMAIL-BUFFER "Select"
			  SELECT-ZMAIL-BUFFER T
			  "Select this buffer.")

(DEFUN SELECT-ZMAIL-BUFFER (ZMAIL-BUFFER &OPTIONAL PRIMARY-TOO (MSG-TOO T))
  "Make ZMAIL-BUFFER be ZMAIL's selected buffer (the value of *ZMAIL-BUFFER*).
MSG-TOO non-NIL (the default) means also reselect a message, *MSG* if possible.
PRIMARY-TOO non-NIL says make this buffer the primary buffer."
  (COND (ZMAIL-BUFFER
	 (COND ((AND *ZMAIL-BUFFER* *MSG*)
		(SETF (ZMAIL-BUFFER-SAVED-CURRENT-MSG *ZMAIL-BUFFER*) *MSG*)
		(MSG-POINT-PDL-PUSH *MSG* *ZMAIL-BUFFER*)))
	 (OR (TYPEP ZMAIL-BUFFER 'INBOX-BUFFER)
	     (SETQ *ZMAIL-BUFFER-LIST* (CONS ZMAIL-BUFFER (DELQ ZMAIL-BUFFER *ZMAIL-BUFFER-LIST*))))
	 (SETQ *ZMAIL-FILE-NAME* (ZMAIL-BUFFER-NAME ZMAIL-BUFFER)))
	(T
	 (SETQ *ZMAIL-FILE-NAME* "No current buffer")))
  (SEND *SUMMARY-WINDOW* :SET-CURRENT-ZMAIL-BUFFER ZMAIL-BUFFER)
  (AND PRIMARY-TOO (SETQ *PRIMARY-ZMAIL-BUFFER* ZMAIL-BUFFER))
  (SETQ *ZMAIL-BUFFER* ZMAIL-BUFFER)
  (SETQ *INTERVAL* ZMAIL-BUFFER)
  (DOLIST (COM '(COM-ZMAIL-SELECT COM-ZMAIL-SORT COM-ZMAIL-RENAME-BUFFER
		 SET-MSG-SUMMARY-LINE))
    (UPDATE-COMMAND-WHO-LINE-DOCUMENTATION COM))
  (AND MSG-TOO (ZMAIL-SELECT-MSG *MSG* T NIL)))

(DEFUN GET-ZMAIL-BUFFER-FROM-NAME (NAME &OPTIONAL CREATE-P)
  "Given a string, return ZMAIL buffer with that name.
CREATE-P says create one (parsing name as pathname) if none.
Otherwise return NIL in that case."
  (COND ((DOLIST (ZMAIL-BUFFER *ZMAIL-BUFFER-LIST*)
	   (AND (STRING-EQUAL NAME (ZMAIL-BUFFER-NAME ZMAIL-BUFFER))
		(RETURN ZMAIL-BUFFER))))
	(CREATE-P
	 (ZMAIL-FIND-FILE-NOSELECT
	   (LET ((FS:*ALWAYS-MERGE-TYPE-AND-VERSION* T))
	     (FS:MERGE-PATHNAME-DEFAULTS NAME *ZMAIL-PATHNAME-DEFAULTS*))))))

(DEFUN GET-ZMAIL-BUFFER-FROM-PATHNAME (PATHNAME &OPTIONAL CREATE-P)
  "Return the ZMAIL buffer visiting PATHNAME.
CREATE-P non-NIL says visit the file if there is none.
Otherwise return NIL in that case."
  (LET ((FS:*ALWAYS-MERGE-TYPE-AND-VERSION* T))
    (SETQ PATHNAME (FS:MERGE-PATHNAME-DEFAULTS PATHNAME *ZMAIL-PATHNAME-DEFAULTS*)))
  (COND ((DOLIST (ZMAIL-BUFFER *ZMAIL-BUFFER-LIST*)
	   (AND (ZMAIL-BUFFER-DISK-P ZMAIL-BUFFER)
		(EQ (FS:TRANSLATED-PATHNAME PATHNAME) (BUFFER-PATHNAME ZMAIL-BUFFER))
		(RETURN ZMAIL-BUFFER))))
	(CREATE-P
	 (ZMAIL-FIND-FILE-NOSELECT PATHNAME))))

(DEFUN MSG-IN-ZMAIL-BUFFER-P (MSG ZMAIL-BUFFER &AUX ARRAY)
  "T if MSG is one of the messages in ZMAIL-BUFFER.
Note that a message can be in more than one ZMAIL buffer;
only one non temporary one, but any number of temporaries."
  (SETQ ARRAY (ZMAIL-BUFFER-ARRAY ZMAIL-BUFFER))
  (DO ((I 0 (1+ I))
       (NMSGS (ARRAY-ACTIVE-LENGTH ARRAY)))
      (( I NMSGS))
    (AND (EQ MSG (AREF ARRAY I))
	 (RETURN I))))

(DEFUN LOCATE-MSG-IN-ZMAIL-BUFFER (MSG ZMAIL-BUFFER &AUX HINT)
  "Return the index of MSG in ZMAIL-BUFFER.  Error if not in that buffer."
  (COND ((AND (SETQ HINT (MSG-DISPLAYED-INDEX MSG))
	      (< HINT (ZMAIL-BUFFER-NMSGS ZMAIL-BUFFER))
	      (EQ MSG (AREF (ZMAIL-BUFFER-ARRAY ZMAIL-BUFFER) HINT)))
	 HINT)
	((MSG-IN-ZMAIL-BUFFER-P MSG ZMAIL-BUFFER))
	(T
	 (ZMAIL-ERROR "Cannot find ~S in ~S" MSG ZMAIL-BUFFER))))

(DEFUN MAKE-EMPTY-MSG (&AUX REAL-INTERVAL INTERVAL START-BP END-BP MSG)
  "Return a new empty MSG with no text in it."
  (SETQ REAL-INTERVAL (CREATE-INTERVAL)
	START-BP (COPY-BP (INTERVAL-FIRST-BP REAL-INTERVAL) :NORMAL)
	END-BP (COPY-BP (INTERVAL-LAST-BP REAL-INTERVAL) :MOVES)
	INTERVAL (CREATE-INTERVAL START-BP END-BP)
	MSG (MAKE-MSG :TICK (TICK)
		      :REAL-INTERVAL REAL-INTERVAL
		      :INTERVAL INTERVAL))
  (SETF (NODE-TICK (MSG-INTERVAL MSG)) (MSG-TICK MSG))
  (SETF (NODE-TICK (MSG-REAL-INTERVAL MSG)) (MSG-TICK MSG))
  (SETF (LINE-NODE (BP-LINE END-BP)) INTERVAL)
  (SETF (NODE-SUPERIOR INTERVAL) REAL-INTERVAL)
  (SETF (NODE-INFERIORS REAL-INTERVAL) (LIST INTERVAL))
  MSG)

(DEFVAR *PROPERTIES-NOT-COPIED* '(DELETED FILED))

(DEFUN COPY-MSG (MSG &AUX NMSG)
  "Return a copy of MSG."
  (SETQ NMSG (MAKE-EMPTY-MSG))
  (SETF (MSG-STATUS NMSG)
	(SOME-PLIST-NOT (CAR (ASSURE-MSG-PARSED MSG)) *PROPERTIES-NOT-COPIED*))
  (SETF (MSG-PARSED-P NMSG) T)
  (LET* ((OLD-LINE (MSG-SUMMARY-LINE MSG))
	 (NEW-LINE (MAKE-SUMMARY-LINE :MAKE-ARRAY (:LENGTH (ARRAY-ACTIVE-LENGTH OLD-LINE)))))
    (SETF (MSG-SUMMARY-LINE NMSG) NEW-LINE)
    (COPY-ARRAY-CONTENTS-AND-LEADER OLD-LINE NEW-LINE))
  (SETF (NODE-UNDO-STATUS (MSG-REAL-INTERVAL NMSG)) :DONT)
  (INSERT-INTERVAL (MSG-END-BP NMSG) (MSG-INTERVAL MSG))
  ;; Mpdify the HEADERS-END-BP property in the MSG-STATUS
  ;; so that it points into the copied text.
  ;; Try to preserve where it points.
  (LET ((HEADERS-END-BP (GET (LOCF (MSG-STATUS MSG)) 'HEADERS-END-BP)))
    (WHEN HEADERS-END-BP
      (DO ((OLINE (BP-LINE (INTERVAL-FIRST-BP (MSG-INTERVAL MSG))) (LINE-NEXT OLINE))
	   (NLINE (BP-LINE (INTERVAL-FIRST-BP (MSG-INTERVAL NMSG))) (LINE-NEXT NLINE)))
	  ((OR (NULL OLINE) (NULL NLINE))
	   (REMPROP (LOCF (MSG-STATUS NMSG)) 'HEADERS-END-BP))
	;; When we find the line in the old interval that the old bp points to,
	;; make the new bp point at the corresponding new line.
	(WHEN (EQ OLINE (CAR HEADERS-END-BP))
	  (PUTPROP (LOCF (MSG-STATUS NMSG)) (CREATE-BP NLINE (BP-INDEX HEADERS-END-BP))
		   'HEADERS-END-BP)
	  (RETURN)))))
  NMSG)

;;; Changing the options of a mail file.

(DEFVAR *ZMAIL-BUFFER-FLAVOR-ALIST* NIL
  "Alist of pretty names of ZMAIL flavors vs actual names.
For user in choosing a flavor as part of setting file options.")

(DEFUN ADD-ZMAIL-BUFFER-FLAVOR (FLAVOR NAME &AUX ELEM)
  (IF (SETQ ELEM (ASS 'EQUALP NAME *ZMAIL-BUFFER-FLAVOR-ALIST*))
      (SETF (CDR ELEM) FLAVOR)
    (PUSH (CONS NAME FLAVOR) *ZMAIL-BUFFER-FLAVOR-ALIST*)))

(DEFVAR *ZMAIL-BUFFER-OPTION-ALIST* NIL)

(DEFMACRO DEFINE-SETTABLE-MAIL-FILE-OPTION (OPTION DEFAULT &OPTIONAL TYPE NAME &REST ARGS)
  `(TV:DEFINE-USER-OPTION-1 ',OPTION '*ZMAIL-BUFFER-OPTION-ALIST* ,DEFAULT
			    ',(OR TYPE :SEXP)
			    ',(OR NAME (MAKE-COMMAND-NAME OPTION)) . ,ARGS))

(DEFMACRO DEFINE-NOT-SETTABLE-MAIL-FILE-OPTION (OPTION)
  `(DEFINE-NOT-SETTABLE-MAIL-FILE-OPTION-1 ',OPTION))

(DEFUN DEFINE-NOT-SETTABLE-MAIL-FILE-OPTION-1 (OPTION)
  (OR (ASSQ OPTION *ZMAIL-BUFFER-OPTION-ALIST*)
      (PUSH (NCONS OPTION) *ZMAIL-BUFFER-OPTION-ALIST*)))

(DEFINE-SETTABLE-MAIL-FILE-OPTION :APPEND NIL :BOOLEAN
  "Append messages moved into file")

(DEFUN CHOOSE-MAIL-FILE-OPTIONS (ZMAIL-BUFFER &AUX OLD-FLAVOR OLD-APPEND-P PATHNAME FLAVOR
				 APPEND-P OPTIONS PLIST OLD-SUMMARY-FORMAT WANT-TO-REVERSE)
  (SETQ OLD-FLAVOR (TYPE-OF ZMAIL-BUFFER)
	PATHNAME (BUFFER-PATHNAME ZMAIL-BUFFER)
	OPTIONS (ZMAIL-BUFFER-OPTIONS ZMAIL-BUFFER)
	PLIST (LOCF OPTIONS)
	OLD-APPEND-P (GET PLIST :APPEND)
	OLD-SUMMARY-FORMAT (GET PLIST :SUMMARY-WINDOW-OPTIONS))
  (*CATCH 'ZWEI-COMMAND-LOOP			;In case aborted
    (MULTIPLE-VALUE (FLAVOR OPTIONS)
      (CHOOSE-MAIL-FILE-OPTIONS-1 PATHNAME OLD-FLAVOR OPTIONS))
    (SETQ APPEND-P (GET PLIST :APPEND))
    (AND (NEQ APPEND-P OLD-APPEND-P)
	 (> (ZMAIL-BUFFER-NMSGS ZMAIL-BUFFER) 1)
	 (TYPEOUT-BEEP-YES-OR-NO-P "Reverse the messages already in ~A? " PATHNAME)
	 (SETQ WANT-TO-REVERSE T))
    (COND ((NEQ FLAVOR OLD-FLAVOR)
	   (REMPROP PLIST :APPEND)
	   (LET ((NEW-ZMAIL-BUFFER (MAKE-ZMAIL-BUFFER FLAVOR
						      :PATHNAME PATHNAME :FILE-ID T
						      :OPTIONS OPTIONS :APPEND-P APPEND-P)))
	     ;; ADD-ZMAIL-BUFFER adds messages in forward order, so that if the file is
	     ;; prepended, they would be reversed accidentally.  Similary, if now in append
	     ;; mode, must reverse first to get old prepend mode messages right.
	     (AND (EQ WANT-TO-REVERSE APPEND-P)
		  (REVERSE-ZMAIL-BUFFER ZMAIL-BUFFER))
	     (SEND NEW-ZMAIL-BUFFER :ADD-ZMAIL-BUFFER ZMAIL-BUFFER)
	     (LET ((OLD-SELECTED-P (EQ ZMAIL-BUFFER *ZMAIL-BUFFER*))
		   (OLD-PRIMARY-P (EQ ZMAIL-BUFFER *PRIMARY-ZMAIL-BUFFER*)))
	       (KILL-ZMAIL-BUFFER ZMAIL-BUFFER)
	       (AND OLD-SELECTED-P (SELECT-ZMAIL-BUFFER NEW-ZMAIL-BUFFER OLD-PRIMARY-P)))))
	  (T
	   (AND WANT-TO-REVERSE (REVERSE-ZMAIL-BUFFER ZMAIL-BUFFER))
	   (SEND ZMAIL-BUFFER :SET-OPTIONS OPTIONS))))
  (OR (EQ OLD-SUMMARY-FORMAT (GET PLIST :SUMMARY-WINDOW-OPTIONS))
      (CHANGE-ZMAIL-BUFFER-MSGS-SUMMARY-LINES ZMAIL-BUFFER))
  (AND (EQ ZMAIL-BUFFER *ZMAIL-BUFFER*)
       (UPDATE-COMMAND-WHO-LINE-DOCUMENTATION 'COM-ZMAIL-SORT)))

(DEFUN CHOOSE-MAIL-FILE-OPTIONS-1 (PATHNAME FLAVOR OPTIONS &OPTIONAL (NEAR-MODE '(:MOUSE))
							   &AUX VARS VALS)
  (DECLARE (RETURN-LIST FLAVOR OPTIONS))
  (SETQ VARS (NCONS 'FLAVOR)
	VALS (NCONS FLAVOR))
  (LOOP FOR POSS IN *ZMAIL-BUFFER-OPTION-ALIST*
	AS VAR = (CAR POSS)
	AS TEM = (NCONS VAR)
	WITH PLIST = (LOCF OPTIONS)
	DO (SETQ VARS (NCONC VARS TEM))
	   (SETQ VALS (NCONC VALS (NCONS (GET PLIST (CAR TEM) (GET VAR 'TV:DEFAULT-VALUE))))))
  (LET ((*POSSIBLE-FLAVORS*
	  (LOOP FOR FLAVOR IN (SEND PATHNAME :POSSIBLE-MAIL-FILE-BUFFER-FLAVORS)
		COLLECT (RASSQ FLAVOR *ZMAIL-BUFFER-FLAVOR-ALIST*) INTO FOO
		FINALLY (RETURN (NCONC FOO (NCONS (RASSQ 'TEXT-MAIL-FILE-BUFFER
							 *ZMAIL-BUFFER-FLAVOR-ALIST*)))))))
    (DECLARE (SPECIAL *POSSIBLE-FLAVORS*))
    (PROGV VARS VALS
      (TV:CHOOSE-VARIABLE-VALUES (COMPUTE-ZMAIL-BUFFER-CHOICES FLAVOR)
				 :LABEL (FORMAT NIL "Options for ~A:" PATHNAME)
				 :NEAR-MODE NEAR-MODE
				 :MARGIN-CHOICES '("Do It"
						    ("Abort" (ABORT-CURRENT-COMMAND)))
				 :FUNCTION 'CHOOSE-MAIL-FILE-OPTIONS-FUNCTION)
      (SETQ FLAVOR (SYMEVAL 'FLAVOR))
      ;; Somewhat of a kludge
      (AND (MEMQ :MAIL VARS)
	   (DO L (SYMEVAL :MAIL) (CDR L) (NULL L)
	       (LET ((FS:*ALWAYS-MERGE-TYPE-AND-VERSION* T))
		 (SETF (CAR L) (FS:MERGE-PATHNAME-DEFAULTS (CAR L) *ZMAIL-PATHNAME-DEFAULTS*)))))
      (SETQ OPTIONS
	    (LOOP FOR VAR IN (LET (SELF)
			       (SEND (SI:GET-FLAVOR-HANDLER-FOR FLAVOR :POSSIBLE-OPTIONS)
					:POSSIBLE-OPTIONS))
		  NCONC `(,VAR ,(SYMEVAL VAR))))))
  (VALUES FLAVOR OPTIONS))

(DEFUN CHOOSE-MAIL-FILE-OPTIONS-FUNCTION (WINDOW VARIABLE OLDVAL NEWVAL)
  OLDVAL
  (COND ((EQ VARIABLE 'FLAVOR)
	 (TV:WITH-SHEET-DEEXPOSED (WINDOW)
	   (SEND WINDOW :SETUP (COMPUTE-ZMAIL-BUFFER-CHOICES NEWVAL)
		    (SEND WINDOW :LABEL)
		    (SEND WINDOW :FUNCTION)
		    (SYMEVAL-IN-INSTANCE WINDOW 'TV:MARGIN-CHOICES)))
	 T)))

(DEFUN COMPUTE-ZMAIL-BUFFER-CHOICES (FLAVOR)
  (DECLARE (SPECIAL *POSSIBLE-FLAVORS*))
  `((FLAVOR "Format" :ASSOC ,*POSSIBLE-FLAVORS*)
    . ,(MAPCAR #'(LAMBDA (X) (ASSQ X *ZMAIL-BUFFER-OPTION-ALIST*))
	       (LET (SELF)
		 (SEND (SI:GET-FLAVOR-HANDLER-FOR FLAVOR :SETTABLE-OPTIONS)
			  :SETTABLE-OPTIONS)))))

;;; Operating on ZMAIL buffers.

(DEFUN MAKE-ZMAIL-BUFFER (TYPE &REST OPTIONS)
  (APPLY 'MAKE-INSTANCE TYPE OPTIONS))

(DEFMETHOD (ZMAIL-BUFFER :AFTER :INIT) (PLIST)
  (SETQ ARRAY (MAKE-ARRAY 100 :FILL-POINTER 0 :area *zmail-msg-area*))
  (AND (GET PLIST :APPEND-P)
       (PUTPROP (LOCF OPTIONS) T :APPEND)))

(DEFMETHOD (ZMAIL-BUFFER :WINDOWS) ()
  (SUBSET #'(LAMBDA (WINDOW) (EQ SELF (WINDOW-INTERVAL WINDOW)))
	  *ALL-ZMAIL-WINDOWS*))

(DEFMETHOD (ZMAIL-BUFFER :OTHER-WINDOWS) (WINDOW)
  (DECLARE (SPECIAL WINDOW))
  (SUBSET #'(LAMBDA (W) (AND (NEQ W WINDOW) (EQ SELF (WINDOW-INTERVAL W))))
	  *ALL-ZMAIL-WINDOWS*))

(DEFMETHOD (ZMAIL-BUFFER :POSSIBLE-WINDOWS) ()
  *ALL-ZMAIL-WINDOWS*)

(DEFMETHOD (ZMAIL-BUFFER :POINT) ()
  (ZMAIL-ERROR "Should not get here."))

(DEFMETHOD (ZMAIL-BUFFER :MARK) ()
  (ZMAIL-ERROR "Should not get here."))

(DEFMETHOD (ZMAIL-BUFFER :RENAME) (NEW-NAME)
  (SETQ NAME NEW-NAME)
  (IF (EQ SELF *ZMAIL-BUFFER*)
      (SETQ *ZMAIL-FILE-NAME* NEW-NAME)))

(DEFMETHOD (ZMAIL-BUFFER :REVERT) (&REST IGNORE)
  (ZMAIL-ERROR "Should not get here."))

(DEFMETHOD (ZMAIL-BUFFER :ACTIVATE) ()
  (SETQ *ZMAIL-BUFFER-LIST* (NCONC *ZMAIL-BUFFER-LIST* (NCONS SELF)))
  ;; We may make another buffer available for left on Select.
  (UPDATE-COMMAND-WHO-LINE-DOCUMENTATION 'COM-ZMAIL-SELECT))

(DEFMETHOD (ZMAIL-BUFFER :SELECT) ()
  (ZMAIL-ERROR "Should not get here."))

(DEFMETHOD (ZMAIL-BUFFER :SECTIONIZE) ()
  (ZMAIL-ERROR "Should not get here."))

(DEFMETHOD (ZMAIL-BUFFER :RESECTIONIZE) ()
  (ZMAIL-ERROR "Should not get here."))

(DEFMETHOD (ZMAIL-BUFFER :SET-READ-ONLY-P) (IGNORE)
  (ZMAIL-ERROR "Should not get here."))

(DEFMETHOD (ZMAIL-BUFFER :MODIFIED-P) ()
  "T if ZMAIL-BUFFER has unsaved changes."
  (DOMSGS (MSG SELF)
    (SEND SELF :UPDATE-MSG-OPTIONS-IN-FILE-IF-NECESSARY MSG))
  (COND ((ZMAIL-BUFFER-DISK-P SELF)
	 (SETF (ZMAIL-DISK-BUFFER-MSG-UPDATE-TICK SELF) (TICK))
	 ;; This may require that the file be saved out.
	 (SEND SELF :UPDATE-OPTIONS-IN-FILE)
	 (OR (EQ (BUFFER-FILE-ID SELF) T)
	     (> (NODE-TICK SELF)
		(BUFFER-TICK SELF))))))

(DEFMETHOD (ZMAIL-BUFFER :KILL) ()
  (SETQ *ZMAIL-BUFFER-LIST* (DELQ SELF *ZMAIL-BUFFER-LIST*))
  (MSG-POINT-PDL-PURGE NIL SELF)
  (AND (EQ SELF *PRIMARY-ZMAIL-BUFFER*)
       (SETQ *PRIMARY-ZMAIL-BUFFER* NIL))
  (AND (EQ SELF *ZMAIL-BUFFER*)
       (SELECT-ZMAIL-BUFFER (CAR *ZMAIL-BUFFER-LIST*)))
  (UPDATE-COMMAND-WHO-LINE-DOCUMENTATION 'COM-ZMAIL-SELECT))

(DEFMETHOD (ZMAIL-BUFFER :ADD-MSG) (MSG &OPTIONAL AT-INDEX &AUX LEN)
  (SETQ LEN (ARRAY-ACTIVE-LENGTH ARRAY))
  (COND ((NOT (DOTIMES (I LEN) ;If not already in file
		(AND (EQ MSG (AREF ARRAY I)) (RETURN T))))
	 (OR AT-INDEX
	     (SETQ AT-INDEX (LET (PREDICATE APPEND-P)
			      (LET ((PLIST (LOCF OPTIONS)))
				(SETQ PREDICATE (GET PLIST :SORT)
				      APPEND-P (GET PLIST :APPEND)))
			      (IF (NULL PREDICATE)
				  (IF APPEND-P LEN 0)
				(LOOP FOR I FROM 0 TO LEN
				      WHEN (OR (= I LEN)
					       (EQ APPEND-P
						   (FUNCALL PREDICATE MSG (AREF ARRAY I))))
				      DO (RETURN I))))))		
	 (AND (< (ARRAY-LENGTH ARRAY) (1+ LEN))
	      (ADJUST-ARRAY-SIZE ARRAY (TRUNCATE (* LEN 5) 4)))
	 (SETF (FILL-POINTER ARRAY) (1+ LEN))
	 (SETQ MSG (SEND SELF :ADD-MSG-TEXT MSG AT-INDEX))
	 (DO ((I LEN (1- I))
	      (J (1- LEN) (1- J)))
	     ((< J AT-INDEX))
	   (ASET (AREF ARRAY J) ARRAY I))
	 (ASET MSG ARRAY AT-INDEX))))

(DEFMETHOD (ZMAIL-BUFFER :ADD-ZMAIL-BUFFER) (ZMAIL-BUFFER &AUX NARRAY NLENGTH PREDICATE APPEND-P)
  (SETQ NARRAY (ZMAIL-BUFFER-ARRAY ZMAIL-BUFFER)
	NLENGTH (ARRAY-ACTIVE-LENGTH NARRAY))
  (unless (zerop nlength)
    (LET ((PLIST (LOCF OPTIONS)))
      (SETQ PREDICATE (GET PLIST :SORT)
	    APPEND-P (GET PLIST :APPEND)))
    (AND (> NLENGTH 1)
	 (OR PREDICATE (NULL APPEND-P))
	 (LET ((NNARRAY (MAKE-ARRAY NLENGTH)))
	   (COPY-ARRAY-CONTENTS NARRAY NNARRAY)
	   (IF (NULL PREDICATE)
	       (NREVERSE NNARRAY)
	     (FUNCALL (IF APPEND-P 'STABLE-SORT 'REVERSE-STABLE-SORT) NNARRAY PREDICATE))
	   (SETQ NARRAY NNARRAY)))
    ;; Now merge
    (LOOP FOR OLD-IDX FROM 0
	  WITH LENGTH = (ARRAY-ACTIVE-LENGTH ARRAY)
	  AND NEW-IDX = 0
	  AS NMSG = (AREF NARRAY NEW-IDX)
	  WHEN (OR ( OLD-IDX LENGTH)
		   (EQ APPEND-P (AND PREDICATE (FUNCALL PREDICATE NMSG (AREF ARRAY OLD-IDX)))))
	  DO (SEND SELF :ADD-MSG NMSG OLD-IDX)
	  (INCF NEW-IDX)
	  (INCF LENGTH)
	  UNTIL ( NEW-IDX NLENGTH))))

(DEFMETHOD (ZMAIL-BUFFER :ADD-MSG-TEXT) (MSG AT-INDEX)
  AT-INDEX					;Not used
  MSG)

(DEFMETHOD (ZMAIL-BUFFER :READ-NEXT-MSG) (&OPTIONAL IGNORE)
  NIL)

(DEFMETHOD (ZMAIL-BUFFER :UPDATE-MSG-OPTIONS-IN-FILE-IF-NECESSARY) (MSG)
  MSG)

(DEFMETHOD (ZMAIL-BUFFER :FULL-NAME) ()
  (SEND SELF :NAME))

(DEFUN TEMP-ZMAIL-BUFFER-AVAILABLE-P (IGNORE ZMAIL-BUFFER IGNORE)
  (NOT (MEMQ ZMAIL-BUFFER *ZMAIL-BUFFER-LIST*)))

(DEFUN MAKE-TEMP-ZMAIL-BUFFER (IGNORE)
  (MAKE-ZMAIL-BUFFER 'TEMP-ZMAIL-BUFFER :APPEND-P T))

(DEFUN MAKE-NEW-TEMP-ZMAIL-BUFFER (NAME &OPTIONAL (FULL-NAME NAME) &AUX ZMAIL-BUFFER)
  (SETQ ZMAIL-BUFFER (ALLOCATE-RESOURCE 'TEMP-ZMAIL-BUFFER))
  (SETF (ZMAIL-BUFFER-NAME ZMAIL-BUFFER) NAME)
  (SEND ZMAIL-BUFFER :SET-FULL-NAME FULL-NAME)
  (SETF (ZMAIL-BUFFER-SAVED-CURRENT-MSG ZMAIL-BUFFER) NIL)
  (SETF (NODE-TICK ZMAIL-BUFFER) (TICK))
  (SEND ZMAIL-BUFFER :ACTIVATE)
  ZMAIL-BUFFER)

;;; Disk buffers
(DEFMETHOD (ZMAIL-DISK-BUFFER :BEFORE :KILL) ()
  (DOMSGS (MSG SELF)
    (SETF (MSG-PARSED-P MSG) :KILLED))
  ;; Get rid of any messages we killed this way
  (LOOP FOR ZMAIL-BUFFER IN *ZMAIL-BUFFER-LIST*
	WHEN (NOT (ZMAIL-BUFFER-DISK-P ZMAIL-BUFFER))
	DO (EXPUNGE-ZMAIL-BUFFER ZMAIL-BUFFER NIL)))

(DEFMETHOD (ZMAIL-DISK-BUFFER :PARSE-MSG) (MSG -STATUS-)
  (SET-PARSED-MSG-HEADERS MSG -STATUS-))

(DEFMETHOD (ZMAIL-DISK-BUFFER :PARSE-MSG-TEXT) (MSG IGNORE)
  (PARSE-HEADERS-INTERVAL (MSG-INTERVAL MSG) NIL NIL T))

(DEFMETHOD (ZMAIL-DISK-BUFFER :UPDATE-MSG-END) (MSG &OPTIONAL IGNORE)
  MSG)

;;; MAIL-FILE-BUFFERs.

;;; ALWAYS-NEW means do not read in any old file.  FLAVOR overrides any default
(DEFUN ZMAIL-FIND-FILE-NOSELECT (PATHNAME &OPTIONAL (NEAR-MODE '(:MOUSE)) FLAVOR ALWAYS-NEW
				 &AUX STREAM)
  (DECLARE (RETURN-LIST ZMAIL-BUFFER NEW-P))
  (LET ((FS:*ALWAYS-MERGE-TYPE-AND-VERSION* T))
    (SETQ PATHNAME (FS:MERGE-PATHNAME-DEFAULTS PATHNAME *ZMAIL-PATHNAME-DEFAULTS*)) )
  (SETQ PATHNAME (SEND PATHNAME :TRANSLATED-PATHNAME))
  (COND ((NOT (OR ALWAYS-NEW
		  (ERRORP (SETQ STREAM (OPEN PATHNAME '(:IN :NOERROR))))))
	 (GET-ZMAIL-FILE STREAM PATHNAME NIL FLAVOR))
	((AND STREAM
	      (NOT (CONDITION-TYPEP STREAM 'FS:FILE-NOT-FOUND)))
	 (BARF STREAM))
	(T
	 (FORMAT *QUERY-IO* "~&New file: ~A" PATHNAME)
	 (VALUES (ZMAIL-FIND-FILE-NOSELECT-1
		   PATHNAME
		   (COND ((OR ALWAYS-NEW (NULL *ZMAIL-BUFFER*)) NIL)
			 ((AND *DEFAULT-MOVE-ZMAIL-BUFFER*
			       (ZMAIL-BUFFER-DISK-P
				 *DEFAULT-MOVE-ZMAIL-BUFFER*))
			  *DEFAULT-MOVE-ZMAIL-BUFFER*)
			 ((ZMAIL-BUFFER-DISK-P *ZMAIL-BUFFER*)
			  *ZMAIL-BUFFER*)
			 (*PRIMARY-ZMAIL-BUFFER*))
		   NEAR-MODE
		   FLAVOR)
		 T))))

(DEFUN ZMAIL-FIND-FILE-NOSELECT-1 (PATHNAME &OPTIONAL ZMAIL-BUFFER NEAR-MODE FLAVOR PRIMARY-P
				   &AUX OPTIONS PLIST APPEND-P)
  (SETQ PLIST (LOCF OPTIONS))
  (COND (ZMAIL-BUFFER
	 (SETQ FLAVOR (TYPE-OF ZMAIL-BUFFER)
	       OPTIONS (SEND ZMAIL-BUFFER :STICKY-OPTIONS)
	       APPEND-P (GET PLIST :APPEND)))
	((NULL FLAVOR)
	 (MULTIPLE-VALUE (FLAVOR APPEND-P)
	   (SEND PATHNAME :MAIL-FILE-FORMAT-COMPUTER NIL))))
  (SETQ APPEND-P (CASE *NEW-MAIL-FILE-APPEND-P*
		   (:APPEND T)
		   (:PREPEND NIL)
		   (:STICKY APPEND-P)
		   (:ASK (IF (EQ *ZMAIL-COMMAND-BUTTON* :RIGHT)
			     APPEND-P
			   (LET ((CHOICE (TV:MENU-CHOOSE '(("Append" . :APPEND)
							   ("Prepend" . :PREPEND)))))
			     (OR CHOICE (ABORT-CURRENT-COMMAND))
			     (EQ CHOICE :APPEND))))))
  (COND ((EQ *ZMAIL-COMMAND-BUTTON* :RIGHT)
	 (PUTPROP PLIST APPEND-P :APPEND)
	 (OR NEAR-MODE (SETQ NEAR-MODE '(:MOUSE)))
	 (MULTIPLE-VALUE (FLAVOR OPTIONS)
	   (CHOOSE-MAIL-FILE-OPTIONS-1 PATHNAME FLAVOR OPTIONS NEAR-MODE))
	 (SETQ APPEND-P (CAR (REMPROP PLIST :APPEND))))
	(T
	 (REMPROP PLIST :APPEND)))			;Do not get confused in :INIT method
  (let ((buffer (MAKE-ZMAIL-BUFFER FLAVOR :PATHNAME PATHNAME :FILE-ID T
				   :OPTIONS OPTIONS :APPEND-P APPEND-P
				   :NEW-PRIMARY-P PRIMARY-P)))
    (setf (get buffer 'generation-retention-count) *default-mail-buffer-generation-retention-count*)
    buffer))

(DEFMETHOD (MAIL-FILE-BUFFER :SETTABLE-OPTIONS) ()
  NIL)

(DEFMETHOD (MAIL-FILE-BUFFER :POSSIBLE-OPTIONS) ()
  '(:APPEND))

(DEFMETHOD (MAIL-FILE-BUFFER :STICKY-OPTIONS) ()
  (SOME-PLIST OPTIONS '(:APPEND)))

(DEFMETHOD (MAIL-FILE-BUFFER :AFTER :INIT) (IGNORE)
  (SETQ NAME (SEND PATHNAME :STRING-FOR-EDITOR))
  (SETQ FILE-TICK TICK)
  (SETQ MSG-UPDATE-TICK TICK)
  (AND STREAM (SETQ STATUS :LOADING))
  (SEND SELF :ACTIVATE))

(DEFMETHOD (MAIL-FILE-BUFFER :INBOX-BUFFER) (&OPTIONAL NEW-PATHNAME DELETE-P &AUX LIST)
  (IF NEW-PATHNAME
      (SETQ LIST (LIST (LIST NEW-PATHNAME NIL DELETE-P)))
    (SETQ NEW-PATHNAME (SEND PATHNAME :NEW-MAIL-PATHNAME))
    (SETQ LIST (LIST (LIST NEW-PATHNAME
			   (SEND NEW-PATHNAME :NEW-TYPE
				 (SEND NEW-PATHNAME
				       :ZMAIL-TEMP-FILE-NAME))
			   T)))
    (COND (*RUN-GMSGS-P*
	   (SETQ NEW-PATHNAME (SEND NEW-PATHNAME :NEW-TYPE "GMSGS"))
	   (PUSH (LIST NEW-PATHNAME
		       (SEND NEW-PATHNAME :NEW-TYPE
			     (SEND NEW-PATHNAME
				   :ZMAIL-TEMP-FILE-NAME))
		       T)
		 LIST))))
  (MAKE-INBOX-BUFFER (SEND NEW-PATHNAME :INBOX-BUFFER-FLAVOR) LIST SELF))

;;; Update the buffer version of a message's status
(DEFMETHOD (MAIL-FILE-BUFFER :UPDATE-MSG-OPTIONS-IN-FILE-IF-NECESSARY) (MSG)
  (AND (> (MSG-TICK MSG) MSG-UPDATE-TICK)
       (SEND SELF :UPDATE-MSG-OPTIONS-IN-FILE MSG)))

(DEFMETHOD (MAIL-FILE-BUFFER :UPDATE-MSG-OPTIONS-IN-FILE) (MSG)
  MSG)

;; Mung the message MSG so that its contents are suitable for
;; being part of this mail file buffer.
;; The headers, as well as the invisible stuff at the front and end of the message,
;; are altered to fit.
;; Also, the MSG-MAIL-FILE-BUFFER is set.
(DEFMETHOD (MAIL-FILE-BUFFER :NEW-MSG) (MSG &AUX OLD-FILE)
  (SETQ OLD-FILE (MSG-MAIL-FILE-BUFFER MSG))
  (SETF (MSG-MAIL-FILE-BUFFER MSG) SELF)
  (MULTIPLE-VALUE-BIND (BEFORE AFTER)
      (SEND SELF :NEW-HEADER-AND-TRAILER)
    (REPLACE-REAL-HEADER-AREA MSG BEFORE)
    (REPLACE-REAL-TRAILER-AREA MSG AFTER))
  (SEND SELF :UPDATE-MSG-END MSG)
  ;; If the old mail file of this message wanted different style headers,
  ;; must reformat the headers so the message can be reparsed right in this file.
  (WHEN (AND OLD-FILE (GET (LOCF (MSG-STATUS MSG)) 'HEADERS-END-BP))
    (UNLESS (MEMBER (SEND OLD-FILE :FORMAT-NAME)
		    (SEND SELF :HEADER-COMPATIBLE-MAIL-FILE-FORMATS))
      (SEND SELF :REFORMAT-MSG-HEADER MSG)))
  MSG)

;; Should return a list of :FORMAT-NAMEs of formats whose
;; header standards are the same as this mail file's format's standards.
(DEFMETHOD (MAIL-FILE-BUFFER :HEADER-COMPATIBLE-MAIL-FILE-FORMATS) ()
  (LIST (SEND SELF :FORMAT-NAME)))

;; Replace the visible header of MSG with one which has the right format
;; to be parsed in the type of mail file which SELF is.
;; The default definition makes a standard-ish network header.
(DEFMETHOD (MAIL-FILE-BUFFER :REFORMAT-MSG-HEADER) (MSG)
  (WITH-BP (SEP (INTERVAL-FIRST-BP (MSG-INTERVAL MSG)) :MOVES)
    (LET ((STRM (INTERVAL-STREAM-INTO-BP (INTERVAL-FIRST-BP (MSG-INTERVAL MSG)))))
      (DO ((TAIL (MSG-STATUS MSG) (CDDR TAIL)))
	  ((NULL TAIL))
	(WHEN (RASSQ (CAR TAIL) *HEADER-NAME-ALIST*)
	  (PRINT-HEADER STRM (CADR TAIL) (CAR TAIL))))
      (TERPRI STRM))
    (DELETE-INTERVAL SEP (GET (LOCF (MSG-STATUS MSG)) 'HEADERS-END-BP))))

(DEFMETHOD (MAIL-FILE-BUFFER :NEW-HEADER-AND-TRAILER) ()
  (VALUES "" ""))

(DEFUN REPLACE-REAL-HEADER-AREA (MSG STRING &AUX START-BP MSG-REAL-INTERVAL
						 PREV-END-BP PREV-END-BP-1
						 MOVE-BP-P MOVE-BP-1-P
						 NEW-BP)
  (SETQ START-BP (MSG-START-BP MSG)
	MSG-REAL-INTERVAL (MSG-REAL-INTERVAL MSG))
  (DELETE-INTERVAL (INTERVAL-FIRST-BP MSG-REAL-INTERVAL) START-BP T)
  (AND (SETQ MOVE-BP-P (NODE-PREVIOUS MSG-REAL-INTERVAL))
       (SETQ PREV-END-BP (INTERVAL-LAST-BP MOVE-BP-P)
	     PREV-END-BP-1 (INTERVAL-LAST-BP (CAR (NODE-INFERIORS MOVE-BP-P)))
	     MOVE-BP-P (BP-= PREV-END-BP START-BP)
	     MOVE-BP-1-P (BP-= PREV-END-BP-1 START-BP)))
  (SETQ NEW-BP (INSERT START-BP STRING))
  ;; If we inserted text that made the end of the previous message move accidentally,
  ;; put it back where it was.
  (AND MOVE-BP-P
       (MOVE-BP PREV-END-BP START-BP))
  (AND MOVE-BP-1-P
       (MOVE-BP PREV-END-BP-1 START-BP))
  (MOVE-BP START-BP NEW-BP)
  (SEND SELF :UPDATE-MSG-OPTIONS-IN-FILE MSG))

(DEFUN REPLACE-REAL-TRAILER-AREA (MSG STRING &AUX END-BP REAL-INTERVAL
						  NEXT-START-BP NEXT-START-BP-1
						  MOVE-BP-P MOVE-BP-1-P)
  (SETQ END-BP (MSG-END-BP MSG)
	REAL-INTERVAL (MSG-REAL-INTERVAL MSG))
  (DELETE-INTERVAL END-BP (INTERVAL-LAST-BP REAL-INTERVAL) T)
  (AND (SETQ MOVE-BP-P (NODE-NEXT REAL-INTERVAL))
       (SETQ NEXT-START-BP (INTERVAL-FIRST-BP MOVE-BP-P)
	     NEXT-START-BP-1 (INTERVAL-FIRST-BP (CAR (NODE-INFERIORS MOVE-BP-P)))
	     MOVE-BP-P (BP-= NEXT-START-BP END-BP)
	     MOVE-BP-1-P (BP-= NEXT-START-BP-1 END-BP)))
  (WITH-BP (OLD-END-BP END-BP :NORMAL)
    (INSERT-MOVING END-BP STRING)
    (AND MOVE-BP-P
	 (MOVE-BP NEXT-START-BP END-BP))
    (AND MOVE-BP-1-P
	 (MOVE-BP NEXT-START-BP-1 END-BP))
    (MOVE-BP END-BP OLD-END-BP)))

;; Return a pointer to the beginning of the first message.
;; By default, that is the beginning of the interval's contents.
(DEFMETHOD (ZMAIL-DISK-BUFFER :FIRST-MSG-BP) ()
  FIRST-BP)

;;; The count of messages can be inconsistent while the text is being added, prevent lossage
;;; with background parsing
(DEFWRAPPER (ZMAIL-DISK-BUFFER :ADD-MSG) (IGNORE . BODY)
  `(LOCK-ZMAIL-BUFFER (SELF)
     . ,BODY))

(DEFWRAPPER (ZMAIL-DISK-BUFFER :ADD-ZMAIL-BUFFER) (IGNORE . BODY)
  `(LOCK-ZMAIL-BUFFER (SELF)
     . ,BODY))

;;; This actually links in the lines of a message
(DEFMETHOD (MAIL-FILE-BUFFER :ADD-MSG-TEXT) (MSG AT-INDEX)
  (COND ((MSG-MAIL-FILE-BUFFER MSG)
	 ;; This MSG-PUT and the COPY-MSG below insure that the message has been parsed
	 ;; if its text is going to be copied.  This is necessary so that the *** EOOH ***
	 ;; line isn't copied as well.
	 (MSG-PUT MSG T 'FILED)
	 (AND *DELETE-AFTER-MOVE-TO-FILE*
	      (ZMAIL-DELETE-MSG MSG))
	 (LET ((FILE (MSG-MAIL-FILE-BUFFER MSG)))
	   (SETQ MSG (COPY-MSG MSG))
	   (SETF (MSG-MAIL-FILE-BUFFER MSG) FILE))))
  (SEND SELF :NEW-MSG MSG)
  (LET* ((MSG-INT (MSG-REAL-INTERVAL MSG))
	 (MSG-REAL-START-BP (INTERVAL-FIRST-BP MSG-INT))
	 (MSG-REAL-END-BP (INTERVAL-LAST-BP MSG-INT))
	 (NMSGS (ARRAY-ACTIVE-LENGTH ARRAY))
	 (INFS (NODE-INFERIORS SELF))
	 (LINE2 (BP-LINE MSG-REAL-START-BP))
	 (LINE3 (BP-LINE MSG-REAL-END-BP))
	 PREV-MSG-END-BPS LINE1 LINE4)
    (SETF (NODE-SUPERIOR MSG-INT) SELF)
    (COND ((= AT-INDEX (1- NMSGS))		;Putting in at the end
	   (MULTIPLE-VALUE (LINE1 PREV-MSG-END-BPS)
	     (SEND SELF :LAST-LINE-FOR-APPEND (1- NMSGS))))
	  (T
	   (UNLESS (ZEROP AT-INDEX)
	     (MULTIPLE-VALUE (NIL PREV-MSG-END-BPS)
	       (SEND SELF :LAST-LINE-FOR-APPEND AT-INDEX)))
	   (LET ((NEXT-MSG (AREF ARRAY AT-INDEX)))
	     (LET ((NEXT-NODE (MSG-REAL-INTERVAL NEXT-MSG)))
	       (SETF (NODE-NEXT MSG-INT) NEXT-NODE)
	       (SETF (NODE-PREVIOUS NEXT-NODE) MSG-INT))
	     (SETQ LINE4 (BP-LINE (MSG-REAL-START-BP NEXT-MSG))
		   LINE1 (LINE-PREVIOUS LINE4)))))
    (COND (LINE1
	   (SETF (LINE-PREVIOUS LINE2) LINE1)
	   (SETF (LINE-NEXT LINE1) LINE2)
	   (DOLIST (BP PREV-MSG-END-BPS)
	     (MOVE-BP BP LINE2 0)))
	  (T
	   (MOVE-BP FIRST-BP MSG-REAL-START-BP)))
    (COND (LINE4
	   (AND (ZEROP (BP-INDEX MSG-REAL-END-BP))
		(SETQ LINE3 (LINE-PREVIOUS LINE3)))
	   (SETF (LINE-NEXT LINE3) LINE4)
	   (SETF (LINE-PREVIOUS LINE4) LINE3)
	   (LET ((MSG-END-BP (MSG-END-BP MSG)))
	     (AND (BP-= MSG-END-BP MSG-REAL-END-BP)
		  (MOVE-BP MSG-END-BP LINE4 0)))
	   (MOVE-BP MSG-REAL-END-BP LINE4 0))
	  (T
	   (MOVE-BP LAST-BP LINE3 (LINE-LENGTH LINE3))))
    (IF (ZEROP AT-INDEX)
	(SETQ INFERIORS (CONS MSG-INT INFS))
      (LET ((PREV-NODE (MSG-REAL-INTERVAL (AREF ARRAY (1- AT-INDEX)))))
	(SETF (NODE-NEXT PREV-NODE) MSG-INT)
	(SETF (NODE-PREVIOUS MSG-INT) PREV-NODE)
	(AND (SETQ INFS (MEMQ PREV-NODE INFS))
	     (PUSH MSG-INT (CDR INFS))))))
  (MUNG-NODE SELF)
  MSG)

;;; If there was a message in here before, it may not have had a  or whatever at
;;; the end of it.  Also, there may be an empty line at the end of the file
;;; which signifies a final CR, and that empty line will not be needed
;;; when more text is appended.

;;; The first value is the line to append the new text after.
;;; The second valus is a list of BPs to relocate to point at the
;;; beginning of the appended text.
(DEFMETHOD (MAIL-FILE-BUFFER :LAST-LINE-FOR-APPEND) (&OPTIONAL NMSGS
						     &AUX BP BP1 LINE PREV-END-BPS)
  (OR NMSGS (SETQ NMSGS (ARRAY-ACTIVE-LENGTH ARRAY)))
  (IF (PLUSP NMSGS)
      (LET ((MSG (AREF ARRAY (1- NMSGS))))
	(SEND SELF :UPDATE-MSG-END MSG T)
	(SETQ BP (MSG-REAL-END-BP MSG)
	      BP1 (MSG-END-BP MSG)))
    (SETQ BP (SEND SELF :FIRST-MSG-BP)))
  (SETQ LINE (BP-LINE BP))
  (AND (ZEROP (BP-INDEX BP))
       (SETQ PREV-END-BPS
	     (IF (EQ LINE (BP-LINE BP1))
		 (LIST BP BP1)
	       (LIST BP))
;	     LINE (LINE-PREVIOUS LINE) fixes bug 337 an additional line may appear in some files as a side effect. jwc
	     ))
  (VALUES LINE PREV-END-BPS))

;;; This makes sure that a mail file that corresponds to an actual file has
;;; its messages in the right order.
(DEFUN RESPLICE-ZMAIL-BUFFER (ZMAIL-BUFFER &AUX START-LINE START-BP ARRAY)
  (SETF (NODE-TICK ZMAIL-BUFFER) (TICK))
  (OR (SETQ START-LINE (LINE-PREVIOUS (BP-LINE (SEND ZMAIL-BUFFER :FIRST-MSG-BP))))
      (SETQ START-BP (ZMAIL-BUFFER-START-BP ZMAIL-BUFFER)))
  (SETQ ARRAY (ZMAIL-BUFFER-ARRAY ZMAIL-BUFFER))
  (DO ((I 0 (1+ I))
       (NMSGS (ARRAY-ACTIVE-LENGTH ARRAY))
       (LINE1 START-LINE (BP-LINE (MSG-END-BP MSG)))
       (MSG-END-BP NIL (MSG-END-BP MSG))
       (REAL-END-BP START-BP (MSG-REAL-END-BP MSG))
       (REAL-INT) (PREV-REAL-INT NIL REAL-INT)
       (LINE2) (MSG) (REAL-START-BP) (FLAG)
       (INFS NIL (CONS REAL-INT INFS)))
      (NIL)
    (AND (SETQ FLAG (AND MSG-END-BP REAL-END-BP (BP-= MSG-END-BP REAL-END-BP)))
	 (DO ((L (BP-LINE (MSG-START-BP MSG)) (LINE-NEXT L))
	      (PL LINE1 L))
	     ((EQ L LINE1) (SETQ LINE1 PL))))
    (COND (( I NMSGS)
	   (LET ((ZMAIL-BUFFER-END-BP (ZMAIL-BUFFER-END-BP ZMAIL-BUFFER))
		 (BP (END-OF-LINE LINE1)))
	     (SETF (LINE-NEXT LINE1) NIL)
	     (MOVE-BP ZMAIL-BUFFER-END-BP BP)
	     (MOVE-BP REAL-END-BP ZMAIL-BUFFER-END-BP)
	     (AND FLAG (MOVE-BP MSG-END-BP ZMAIL-BUFFER-END-BP)))
	   (AND MSG (SEND ZMAIL-BUFFER :UPDATE-MSG-END MSG))
	   (AND REAL-INT (SETF (NODE-NEXT REAL-INT) NIL))
	   (SETF (NODE-INFERIORS ZMAIL-BUFFER) (NREVERSE INFS))
	   (RETURN NIL)))
    (SETQ MSG (AREF ARRAY I)
	  REAL-INT (MSG-REAL-INTERVAL MSG)
	  REAL-START-BP (INTERVAL-FIRST-BP REAL-INT))
    (SETF (NODE-PREVIOUS REAL-INT) PREV-REAL-INT)
    (AND PREV-REAL-INT (SETF (NODE-NEXT PREV-REAL-INT) REAL-INT))
    (SEND ZMAIL-BUFFER :UPDATE-MSG-END MSG)
    (AND REAL-END-BP
	 (MOVE-BP REAL-END-BP REAL-START-BP))
    (AND FLAG (MOVE-BP MSG-END-BP REAL-START-BP))
    (SETQ LINE2 (BP-LINE REAL-START-BP))
    (AND LINE1 (SETF (LINE-NEXT LINE1) LINE2))
    (SETF (LINE-PREVIOUS LINE2) LINE1))
  (MUNG-NODE ZMAIL-BUFFER))

;;; Reverse the order of messages in a buffer
(DEFUN REVERSE-ZMAIL-BUFFER (ZMAIL-BUFFER)
  (NREVERSE (ZMAIL-BUFFER-ARRAY ZMAIL-BUFFER))
  (AND (ZMAIL-BUFFER-DISK-P ZMAIL-BUFFER)
       (RESPLICE-ZMAIL-BUFFER ZMAIL-BUFFER)))

;;; Update the list of options at the start of the file
(DEFMETHOD (MAIL-FILE-BUFFER :UPDATE-OPTIONS-IN-FILE) ())

;;; Simple write-only mail files without separators
(ADD-ZMAIL-BUFFER-FLAVOR 'TEXT-MAIL-FILE-BUFFER "Text")

(DEFFLAVOR TEXT-MAIL-FILE-BUFFER () (MAIL-FILE-BUFFER))

(DEFMETHOD (TEXT-MAIL-FILE-BUFFER :FORMAT-NAME) () "Text")

;; Don't change appearance of a header just to print it.
(DEFMETHOD (TEXT-MAIL-FILE-BUFFER :REFORMAT-MSG-HEADER) (IGNORE)
  NIL)

(DEFMETHOD (TEXT-MAIL-FILE-BUFFER :MAIL-FILE-REPARSABLE-P) () NIL)

;;; If we try to read one of these in, don't get any messages
(DEFMETHOD (TEXT-MAIL-FILE-BUFFER :LINE-END-OF-MSG-P) (&REST IGNORE)
  T)

(DEFMETHOD (TEXT-MAIL-FILE-BUFFER :UPDATE-MSG-END) (MSG &OPTIONAL IGNORE)
  (LET ((END-LINE (BP-LINE (MSG-END-BP MSG))))
    (SETF (LINE-LENGTH END-LINE) 0)
    (APPEND-TO-ARRAY END-LINE *TEXT-MAIL-FILE-SEPARATOR*)))

;;; I/O

(DEFUN STARTUP-ZMAIL-BUFFER (&OPTIONAL NEW-PATHNAME &AUX STREAM PATHNAME ZMAIL-BUFFER)
  (SET-ZMAIL-USER)
  ;; Do not let the background process run again until all file requests are pending.
  ;; Otherwise the RMAIL file may all get in before the call to get-new-mail below.
  (WITH-BACKGROUND-PROCESS-LOCKED
    (COND ((OR (NULL *PRIMARY-ZMAIL-BUFFER*) NEW-PATHNAME)
	   (SETQ PATHNAME (OR NEW-PATHNAME *ZMAIL-STARTUP-FILE-NAME*))
	   (AND PATHNAME
		(SETQ PATHNAME (FS:MERGE-PATHNAME-DEFAULTS PATHNAME
							   *ZMAIL-PATHNAME-DEFAULTS*)))
	   (DO ((LIST (AND (NULL PATHNAME)
			   (SEND (FS:USER-HOMEDIR) :POSSIBLE-MAIL-FILE-NAMES))))
	       (NIL)
	     (OR PATHNAME (POP LIST PATHNAME))	;Get a pathname to use
	     (SETQ STREAM (OPEN PATHNAME '(:IN :NOERROR)))
	     (COND ((NOT (ERRORP STREAM))
		    (SETQ ZMAIL-BUFFER (GET-ZMAIL-FILE STREAM PATHNAME T))
		    (RETURN NIL)))
	     (COND ((NULL LIST)		;Ran out of choices
		    (UNLESS (CONDITION-TYPEP STREAM 'FS:FILE-NOT-FOUND)
		      (BARF "Error: ~A" STREAM))
		    (LET ((TEM (READ-DEFAULTED-PATHNAME
				 (FORMAT NIL "~A, use what file (RETURN to create ~A)"
					 STREAM PATHNAME)
				 PATHNAME NIL NIL :NEW-OK)))
		      (IF (NEQ TEM PATHNAME)
			  (SETQ PATHNAME TEM)	;Gave a new file, try that, else make it
			(SETQ ZMAIL-BUFFER (ZMAIL-FIND-FILE-NOSELECT-1 PATHNAME NIL NIL NIL T))
			(RETURN NIL))))
		   (T
		    (SETQ PATHNAME NIL))))
	   (SELECT-ZMAIL-BUFFER ZMAIL-BUFFER (NULL NEW-PATHNAME) NIL)
	   (COND (NEW-PATHNAME			;Not the primary mail file
		  (SETQ *MSG-NO* -1)
		  (ZMAIL-SELECT-NEXT-MSG NIL T))	;Select the first message
		 (T
		  (COM-GET-NEW-MAIL-INTERNAL T))))
	  (T DIS-NONE))))

;;; Getting new mail.

(DEFINE-COMMAND-WHO-LINE-DOCUMENTATION COM-GET-NEW-MAIL
  "Merge any new mail:  L: selected or primary mail file; R: specify file.")

(DEFINE-ZMAIL-TOP-LEVEL-COMMAND COM-GET-NEW-MAIL "Read any new mail.
Click right to specify another new mail file.
The file is not deleted in this case." (NO-ZMAIL-BUFFER-OK)
  (COM-GET-NEW-MAIL-INTERNAL NIL))

(DEFUN COM-GET-NEW-MAIL-INTERNAL (FIRST-TIME-P &OPTIONAL FOR-BUFFER &AUX FROM-FILE)
  (OR FOR-BUFFER
      (SETQ FOR-BUFFER (COND ((AND *ZMAIL-BUFFER*
				   (GET (LOCF (ZMAIL-BUFFER-OPTIONS *ZMAIL-BUFFER*)) :MAIL))
			      *ZMAIL-BUFFER*)
			     ((NULL *PRIMARY-ZMAIL-BUFFER*)
			      NIL)
			     ((TYPEP *PRIMARY-ZMAIL-BUFFER* 'INBOX-BUFFER)
			      (SEND *PRIMARY-ZMAIL-BUFFER* :ASSOCIATED-MAIL-FILE-BUFFER))
			     (T
			      *PRIMARY-ZMAIL-BUFFER*))))
  (COND ((NULL FOR-BUFFER)
	 (STARTUP-ZMAIL-BUFFER))	;This will call COM-GET-NEW-MAIL-INTERNAL with FIRST-TIME-P
	((SEND FOR-BUFFER :ASSOCIATED-INBOX-BUFFER)
	 (IF (MEMQ (ZMAIL-DISK-BUFFER-STATUS FOR-BUFFER)
		   '(:LOADING :AWAITING-NEW-MAIL))
	     ;; If it's already reading in new mail, do nothing.
	     DIS-NONE
	   (IF (ZMAIL-DISK-BUFFER-STATUS FOR-BUFFER)
	       ;; Ordinary reading in of other file
	       (FOREGROUND-BACKGROUND-FINISH FOR-BUFFER NIL)
	     ;; Probably left around from an error
	     (SEND FOR-BUFFER :SET-ASSOCIATED-INBOX-BUFFER NIL))
	   (COM-GET-NEW-MAIL-INTERNAL NIL FOR-BUFFER)))
	(T
	 (AND (EQ *ZMAIL-COMMAND-BUTTON* :RIGHT)
	      (LET ((FS:*ALWAYS-MERGE-TYPE-AND-VERSION* T))
		(SETQ FROM-FILE (READ-DEFAULTED-PATHNAME
				  "Get new mail from file"
				  (DEFAULT-ZMAIL-MOVE-PATHNAME)))))
	 (AND (EQ FOR-BUFFER *PRIMARY-ZMAIL-BUFFER*)
	      (CASE *RUN-GMSGS-P*
		(:YES T)
		(:NO NIL)
		(:ONCE-ONLY FIRST-TIME-P))
	      (NULL FROM-FILE)		;The inbox-buffer won't look at us if this is set.
	      (GMSGS (ZMAIL-BUFFER-GMSGS-HOST FOR-BUFFER)))
	 ;; Possibly any mail file should be allowed that knows about getting new mail
	 (GET-NEW-MAIL-FOR-ZMAIL-BUFFER FOR-BUFFER FROM-FILE NIL FIRST-TIME-P))))

(DEFUN ZMAIL-BUFFER-GMSGS-HOST (ZMAIL-BUFFER)
  (LET ((HOSTNAME (CADR (MEMQ :GMSGS-HOST (SEND ZMAIL-BUFFER :OPTIONS)))))
    (IF HOSTNAME (FS:GET-PATHNAME-HOST HOSTNAME)
      (SEND (BUFFER-PATHNAME ZMAIL-BUFFER) :HOST))))

(DEFUN GET-NEW-MAIL-FOR-ZMAIL-BUFFER (ZMAIL-BUFFER FROM-FILE DELETE-P TELL-BACKGROUND-P
				   &AUX MOVE-P INBOX-BUFFER APPEND-P)
  (AND (SEND ZMAIL-BUFFER :ASSOCIATED-INBOX-BUFFER)
       (BARF "Already reading new mail into ~A" (ZMAIL-BUFFER-NAME ZMAIL-BUFFER)))
  ;; Don't start reading new mail while in middle of saving.
  (IF (EQ (ZMAIL-DISK-BUFFER-STATUS ZMAIL-BUFFER) :SAVING)
      (FOREGROUND-BACKGROUND-FINISH ZMAIL-BUFFER))
  (SETQ APPEND-P (ZMAIL-BUFFER-APPEND-P ZMAIL-BUFFER))
  (SELECT-ZMAIL-BUFFER ZMAIL-BUFFER)
  (SETQ INBOX-BUFFER (SEND *ZMAIL-BUFFER* :INBOX-BUFFER FROM-FILE DELETE-P))
  (AND TELL-BACKGROUND-P
       (ZMAIL-BACKGROUND-REQUEST-PUSH
	 `(ZMAIL-BACKGROUND-SET-INBOX-BUFFER ,INBOX-BUFFER)))
  (COND ((NULL (SEND INBOX-BUFFER :START-NEXT-FILE))
	 (FORMAT *QUERY-IO* "~&No new mail")
	 (SEND *ZMAIL-BUFFER* :SET-ASSOCIATED-INBOX-BUFFER NIL)
	 (SETQ MOVE-P (OR (NULL *MSG*) *ALWAYS-JUMP-AFTER-GET-NEW-MAIL*)))
	(T
	 (SETQ MOVE-P T)
	 ;; In case :START-NEXT-FILE changed this.
	 (SETQ INBOX-BUFFER (SEND *ZMAIL-BUFFER* :ASSOCIATED-INBOX-BUFFER))
	 ;; Make sure ZMAIL-BUFFER's status indicates it may be getting new mail now.
	 (UNLESS (EQ (ZMAIL-DISK-BUFFER-STATUS *ZMAIL-BUFFER*) :LOADING)
	   (SETF (ZMAIL-DISK-BUFFER-STATUS *ZMAIL-BUFFER*) :AWAITING-NEW-MAIL))
	 (SELECT-ZMAIL-BUFFER INBOX-BUFFER (EQ ZMAIL-BUFFER *PRIMARY-ZMAIL-BUFFER*) NIL)))
  (IF (NOT MOVE-P)
      DIS-TEXT
    (LOCK-ZMAIL-BUFFER (*ZMAIL-BUFFER*)		;Don't allow getting all messages
      (SETQ *MSG-NO* (IF (AND APPEND-P (EQ *ZMAIL-BUFFER* ZMAIL-BUFFER))
			 (1- (ZMAIL-BUFFER-NMSGS *ZMAIL-BUFFER*))	;but may have already.
		       -1))
      (ZMAIL-SELECT-NEXT-MSG NIL T))))

(DEFINE-ZMAIL-TOP-LEVEL-COMMAND COM-ZMAIL-GMSGS "Run gmsgs" ()
  (GET-NEW-MAIL-FOR-ZMAIL-BUFFER *PRIMARY-ZMAIL-BUFFER*
			      (GMSGS (ZMAIL-BUFFER-GMSGS-HOST *PRIMARY-ZMAIL-BUFFER*))
			      T NIL)
  ;; No need for out output of "(There are messages)" to prevent immediate redisplay
  (SEND *TYPEOUT-WINDOW* :MAKE-COMPLETE)
  DIS-TEXT)

(DEFUN GMSGS (&OPTIONAL (HOST FS:USER-LOGIN-MACHINE) (STREAM *STANDARD-OUTPUT*))
  (IF (STRINGP HOST) (SETQ HOST (SI:PARSE-HOST HOST)))
  (SEND HOST :DO-GMSGS STREAM))		;Methods are in MFHOST

;;; Setup for loading a mail file from stream, does not actually read any messages.
;;; FLAVOR is the parsing format to be forced on the file.  If not specified, it
;;; is computed by looking at the file.
(DEFUN GET-ZMAIL-FILE (STREAM PATHNAME &OPTIONAL BACKGROUND-P FLAVOR &AUX INFO SUCCESS)
  (SETQ INFO (SEND STREAM :INFO))
  (UNWIND-PROTECT
      (OR (LET ((ZMAIL-BUFFER (GET-ZMAIL-BUFFER-FROM-PATHNAME PATHNAME)))
	    (WHEN ZMAIL-BUFFER
	      (IF (AND (NOT (EQUAL INFO (BUFFER-FILE-ID ZMAIL-BUFFER)))
		       (YES-OR-NO-P
			 (FORMAT NIL "File ~A has been changed since you last read or saved it.
Read in the new version? " PATHNAME)))
		  (PROGN
		    (KILL-ZMAIL-BUFFER ZMAIL-BUFFER)
		    NIL)
		(CLOSE STREAM)
		  (SETQ SUCCESS T)
		(WHEN (ZMAIL-DISK-BUFFER-STREAM ZMAIL-BUFFER)
		  (ZMAIL-BACKGROUND-REQUEST-PUSH
		    (LIST 'ZMAIL-BACKGROUND-LOAD-FILE ZMAIL-BUFFER)))
		ZMAIL-BUFFER)))
	  (LET (APPEND-P ZMAIL-BUFFER)
	    (OR FLAVOR (MULTIPLE-VALUE (FLAVOR APPEND-P)
			 (SEND PATHNAME :MAIL-FILE-FORMAT-COMPUTER STREAM)))
	    (SETQ ZMAIL-BUFFER (MAKE-ZMAIL-BUFFER FLAVOR
						  :PATHNAME PATHNAME
						  :STREAM STREAM
						  :FILE-ID INFO
						  :APPEND-P APPEND-P))
	    (START-LOADING-ZMAIL-BUFFER ZMAIL-BUFFER STREAM BACKGROUND-P)
	    (SETQ SUCCESS T)
	    ZMAIL-BUFFER))
    (UNLESS SUCCESS (CLOSE STREAM))))

(DEFUN START-LOADING-ZMAIL-BUFFER (ZMAIL-BUFFER STREAM BACKGROUND-P &OPTIONAL TRUENAME)
  (FORMAT *QUERY-IO* "~&Reading ~A file ~A" (SEND ZMAIL-BUFFER :FORMAT-NAME)
	  (OR TRUENAME (SEND STREAM :TRUENAME)))
  (IF BACKGROUND-P
      (ZMAIL-BACKGROUND-REQUEST-PUSH (LIST 'ZMAIL-BACKGROUND-LOAD-FILE ZMAIL-BUFFER))
    (LET ((*ZMAIL-BACKGROUND-P* :DISABLE))
      (LOAD-ALL-MSGS ZMAIL-BUFFER))))

(DEFUN LOAD-ALL-MSGS (ZMAIL-BUFFER &AUX MAIL-FILE-BUFFER)
  (SETQ MAIL-FILE-BUFFER (AND (TYPEP ZMAIL-BUFFER 'INBOX-BUFFER)
			      (SEND ZMAIL-BUFFER :ASSOCIATED-MAIL-FILE-BUFFER)))
  (SEND ZMAIL-BUFFER :READ-NEXT-MSG 177777)	;Finish reading in foreground
  (COND (MAIL-FILE-BUFFER
	 (SETQ ZMAIL-BUFFER MAIL-FILE-BUFFER)
	 (SEND ZMAIL-BUFFER :READ-NEXT-MSG 177777)))
  ZMAIL-BUFFER)

;;; This is the default message loader.  Different formats provide two messages,
;;; :LINE-END-OF-MSG-P and :CANONICAL-LAST-LINE.
;;; :LINE-END-OF-MSG-P is passed a LINE, its LENGTH, a STATE, an EOF flag, and the START.
;;; START is the line on which this message started.
;;; It should return END-IDX and an updated STATE variable.
;;; END-IDX is T if this was the last line, or index of the end of the message
;;; within the line, or :START-NEXT if this line should also start next message, or NIL.
;;; If END-IDX is a number, it is the index of the end of the message's "contents"
;;; but the next message may not start until the following line.
;;; STATE is NIL for the first line of each message.
;;; Aside from this, the meaning of values of STATE is up to the
;;; individual format's :LINE-END-OF-MESSAGE-P method.
;;; :CANONICAL-LAST-LINE is called to make a dummy line for the end of
;;; the file if the normal format requires this.  This will default to
;;; an empty line.

(DEFMETHOD (ZMAIL-DISK-BUFFER :READ-NEXT-MSG) (&OPTIONAL (NMSGS 1) &AUX EOF)
  (LOCK-ZMAIL-BUFFER (SELF)
    (COND ((MEMQ STATUS '(:LOADING-NEW-MAIL :LOADING))
	   (WHEN STREAM
	     (LET* ((START (GET (LOCF OPTIONS) 'NEXT-MSG-START-LINE))
		    (LINE-WAITING-FLAG START))
	       (REMPROP (LOCF OPTIONS) 'NEXT-MSG-START-LINE)
	       (DO ((TEST-FUNCTION (GET-HANDLER-FOR SELF :LINE-END-OF-MSG-P))
		    (END-LINE) (LINE) (LENGTH) (END-IDX)
		    (MSG-REAL-START-BP) (STATE))	;One piece of state for test function
		   (EOF)
		 (LET ((DEFAULT-CONS-AREA *ZMAIL-MSG-LINE-AREA*))
		   (IF LINE-WAITING-FLAG
		       (SETQ LINE (IF (EQ LINE-WAITING-FLAG T)
				      (LINE-NEXT LINE)
				    LINE-WAITING-FLAG)
			     LINE-WAITING-FLAG NIL)
		     (MULTIPLE-VALUE (LINE EOF)
		       (SEND STREAM :LINE-IN LINE-LEADER-SIZE))))
		 (COND ((AND EOF (OR (NULL LINE) (ZEROP (LINE-LENGTH LINE))))
			(OR START (RETURN NIL))
			(SETQ LINE (SEND SELF :CANONICAL-LAST-LINE))))
		 (OR END-LINE (SETQ END-LINE (PROGN
					       (AND (NOT (ZEROP (BP-INDEX LAST-BP)))
						    (INSERT LAST-BP #\CR))
					       (BP-LINE LAST-BP))))
		 (UNLESS (LINE-NEXT LINE) (INSERT-LINE-WITH-LEADER LINE END-LINE))
		 (SETQ LENGTH (LINE-LENGTH LINE))
		 (AND (NULL START)
		      (PLUSP LENGTH)
		      (DO I 0 (1+ I) ( I LENGTH)
			  (OR (MEMQ (CHAR LINE I) '(#/SP #/TAB))
			      (RETURN T)))
		      (SETQ START LINE))
		 (MULTIPLE-VALUE (END-IDX STATE)
		   (FUNCALL TEST-FUNCTION :LINE-END-OF-MSG-P LINE LENGTH STATE EOF START))
		 (UNLESS (EQ END-LINE (LINE-NEXT LINE))
		   ;; :LINE-END-OF-MSG-P made a new line.
		   (SETQ LINE-WAITING-FLAG T))
		 (COND (END-IDX
			(SETQ MSG-REAL-START-BP (CREATE-BP START 0 :NORMAL))
			(LET ((MSG-REAL-INTERVAL (CREATE-INTERVAL
						   MSG-REAL-START-BP
						   (CREATE-BP (IF (EQ END-IDX :START-NEXT)
								  LINE
								(LINE-NEXT LINE))
							      0 :MOVES)))
			      (MSG-INTERVAL (CREATE-INTERVAL
					      (COPY-BP MSG-REAL-START-BP :NORMAL)
					      (COND ((EQ END-IDX T)
						     (CREATE-BP (LINE-NEXT LINE) 0 :MOVES))
						    ((EQ END-IDX :START-NEXT)
						     (CREATE-BP LINE 0 :MOVES))
						    (T
						     (CREATE-BP LINE END-IDX :MOVES))))))
			  (SETF (NODE-TICK MSG-INTERVAL) FILE-TICK)
			  (SETF (NODE-TICK MSG-REAL-INTERVAL) FILE-TICK)
			  (VECTOR-PUSH-EXTEND
			    (MAKE-MSG :REAL-INTERVAL MSG-REAL-INTERVAL
				      :INTERVAL MSG-INTERVAL
				      :TICK FILE-TICK
				      :MAIL-FILE-BUFFER SELF)
			    ARRAY)
			  (DO ((LINE START (LINE-NEXT LINE))
			       (LAST (LINE-NEXT LINE)))
			      ((EQ LINE LAST))
			    (SETF (LINE-NODE LINE) MSG-REAL-INTERVAL))
			  (SETF (NODE-SUPERIOR MSG-INTERVAL) MSG-REAL-INTERVAL)
			  (SETF (NODE-INFERIORS MSG-REAL-INTERVAL) (LIST MSG-INTERVAL))
			  (SETF (NODE-SUPERIOR MSG-REAL-INTERVAL) SELF)
			  (LET ((INFS (NODE-INFERIORS SELF)))
			    (LET ((LAST (CAR (LAST INFS))))
			      (SETF (NODE-PREVIOUS MSG-REAL-INTERVAL) LAST)
			      (COND (LAST
				     (SETF (NODE-NEXT LAST) MSG-REAL-INTERVAL)
				     ;;The last-bp of the previous interval is :MOVES
				     ;;but should have stayed at the start of this one.
				     (LET ((LAST-BP-0 (INTERVAL-LAST-BP LAST))
					   (LAST-BP-1 (INTERVAL-LAST-BP
							(CAR (NODE-INFERIORS LAST)))))
				       (AND (BP-= LAST-BP-0 LAST-BP-1)
					    (MOVE-BP LAST-BP-1 MSG-REAL-START-BP))
				       (MOVE-BP LAST-BP-0 MSG-REAL-START-BP)))))
			    (SETQ INFERIORS
				  (NCONC INFS (NCONS MSG-REAL-INTERVAL)))))
			(COND (( (SETQ NMSGS (1- NMSGS)) 0)
			       (AND (EQ END-IDX :START-NEXT)
				    (PUTPROP (LOCF OPTIONS) LINE 'NEXT-MSG-START-LINE))
			       (IF LINE-WAITING-FLAG
				   (PUTPROP (LOCF OPTIONS) (LINE-NEXT LINE)
					    'NEXT-MSG-START-LINE))
			       (RETURN NIL)))
			(SETQ START (IF (EQ END-IDX :START-NEXT) LINE)
			      STATE NIL))))))
	   (IF (AND STREAM (NOT EOF))
	       T
	     (SEND SELF :LOADING-DONE)
	     (AND STREAM (PLUSP NMSGS) (NOT *ZMAIL-BACKGROUND-P*)
		  (SEND SELF :READ-NEXT-MSG NMSGS)))))))

(DEFMETHOD (ZMAIL-DISK-BUFFER :CANONICAL-LAST-LINE) ()
  (CREATE-LINE 'ART-STRING 0 NIL))

(DEFWRAPPER (ZMAIL-DISK-BUFFER :LOADING-DONE) (IGNORE . BODY)
  `(IF (EQ *ZMAIL-BACKGROUND-P* T)
       (ZMAIL-BACKGROUND-RESPONSE-PUSH `(FILE-LOADED ,SELF))
     SI:.DAEMON-CALLER-ARGS.
     . ,BODY))

(DEFMETHOD (MAIL-FILE-BUFFER :LOADING-DONE) ()
  (AND STREAM (SEND STREAM :CLOSE))
  (SETQ STREAM NIL)
  ;; If no new mail or new mail is not in yet, wait.
  (COND ((NULL ASSOCIATED-INBOX-BUFFER)
	 (SETQ STATUS NIL))
	((NEQ (ZMAIL-DISK-BUFFER-STATUS ASSOCIATED-INBOX-BUFFER) :AWAITING-SAVE)
	 (SETQ STATUS :AWAITING-NEW-MAIL))
	(T
	 ;; If new mail all in may need to append it now.
	 (AND (ZMAIL-BUFFER-APPEND-P SELF)
	      (INSERT-NEW-MAIL SELF ASSOCIATED-INBOX-BUFFER))
	 ;; Now ready to save back out.
	 (SETQ STATUS :SAVING-REQUIRED)
	 (LET ((SORT (GET (LOCF OPTIONS) :SORT)))
	   (AND SORT (SORT-ZMAIL-BUFFER SELF SORT
					(GET (LOCF OPTIONS) :APPEND-P) T)))
	 (UNLESS *INHIBIT-BACKGROUND-SAVES*
	   (ZMAIL-BUFFER-BACKGROUND-SAVE SELF))))
  (ZMAIL-BACKGROUND-REQUEST-PUSH (LIST 'ZMAIL-BACKGROUND-PARSE-MSGS SELF 0))
  (AND (NEQ *MSG* :NO-SELECT)
       (COMPUTE-CURRENT-MSG-NAME)))		;We may now know how many messages

(DEFWRAPPER (ZMAIL-DISK-BUFFER :SAVING-DONE) (IGNORE . BODY)
  `(IF *ZMAIL-BACKGROUND-P*
       (ZMAIL-BACKGROUND-RESPONSE-PUSH `(FILE-SAVED ,SELF))
     SI:.DAEMON-CALLER-ARGS.			;Prevent compiler warnings
     . ,BODY))

(DEFMETHOD (MAIL-FILE-BUFFER :SAVING-DONE) (&OPTIONAL FORCING-OUT)
  (SETQ FILE-TICK (TICK))
  (SETQ FILE-ID (SEND STREAM :INFO))
  (when (get self 'generation-retention-count) ; Only gets put on for new files
    (send stream :change-properties nil
	  :generation-retentation-count (get self 'generation-retention-count))
    (setf (get self 'generation-retention-count) nil))
  (SEND STREAM :CLOSE)
  (FORMAT *QUERY-IO* "~&Written: ~A" (SEND STREAM :TRUENAME))
  (COND (ASSOCIATED-INBOX-BUFFER
	 (OR (EQ (ZMAIL-DISK-BUFFER-STATUS ASSOCIATED-INBOX-BUFFER) :AWAITING-SAVE)
	     (LET ((OMF ASSOCIATED-INBOX-BUFFER))
	       (SETQ ASSOCIATED-INBOX-BUFFER NIL STATUS NIL)
	       (ZMAIL-ERROR "Inbox buffer out of synch (~S), get a wizard"
		       (ZMAIL-DISK-BUFFER-STATUS OMF))))
	 (COND (ASSOCIATED-INBOX-BUFFER
		(OR (SEND ASSOCIATED-INBOX-BUFFER :SAVING-DONE FORCING-OUT)
		    ;; Returns T if starts to load another inbox file.
		    (SETQ ASSOCIATED-INBOX-BUFFER NIL))))))
  (SETQ STATUS NIL))

(DEFWRAPPER (ZMAIL-DISK-BUFFER :SAVING-ABORTED) (IGNORE . BODY)
  `(IF *ZMAIL-BACKGROUND-P*
       (ZMAIL-BACKGROUND-RESPONSE-PUSH `(FILE-SAVE-ABORTED ,SELF))
     SI:.DAEMON-CALLER-ARGS.			;Prevent compiler warnings
     . ,BODY))

;;;Called when saving in background process gets an error.
;;;The message is sent in the background process, intercepted by the wrapper above,
;;;and queued to be resent in the main process.
;;;Set status back to :SAVING-REQUIRED if there is an inbox buffer (in :AWAITING-SAVE).
(DEFMETHOD (MAIL-FILE-BUFFER :SAVING-ABORTED) ()
  (SEND STREAM :CLOSE :ABORT)
  (FORMAT *QUERY-IO* "~&Error saving ~A" (SEND STREAM :TRUENAME))
  (COND (ASSOCIATED-INBOX-BUFFER
	 (OR (EQ (ZMAIL-DISK-BUFFER-STATUS ASSOCIATED-INBOX-BUFFER) :AWAITING-SAVE)
	     (LET ((OMF ASSOCIATED-INBOX-BUFFER))
	       (SETQ ASSOCIATED-INBOX-BUFFER NIL STATUS :SAVING-REQUIRED)
	       (ZMAIL-ERROR "Inbox buffer out of synch (~S), get a wizard"
		       (ZMAIL-DISK-BUFFER-STATUS OMF))))
	 (SETQ STATUS :SAVING-REQUIRED))
	(T
	 (SETQ STATUS NIL))))

(DEFUN INSERT-NEW-MAIL (OLD-FILE NEW-FILE &AUX APPEND-P OLD-INT NEW-INT INT-APPEND-P)
  (COND ((GET (LOCF (ZMAIL-BUFFER-OPTIONS OLD-FILE)) :REVERSE-NEW-MAIL)
	 (REVERSE-ZMAIL-BUFFER NEW-FILE)
	 ;; Select the lowest numbered message
	 (AND (EQ NEW-FILE *ZMAIL-BUFFER*)
	      (LET ((NMSGS (ZMAIL-BUFFER-NMSGS NEW-FILE)))
		(AND ( NMSGS 2)
		     (MUST-REDISPLAY *MSG-WINDOW*
			 (ZMAIL-SELECT-MSG (AREF (ZMAIL-BUFFER-ARRAY NEW-FILE) 0) NIL NIL)))))))
  (SETQ APPEND-P (ZMAIL-BUFFER-APPEND-P OLD-FILE)
	OLD-INT (ZMAIL-DISK-BUFFER-INTERVAL OLD-FILE)
	NEW-INT (ZMAIL-DISK-BUFFER-INTERVAL NEW-FILE))
  (LOCK-ZMAIL-BUFFER (OLD-FILE)
    (LOCK-ZMAIL-BUFFER (NEW-FILE)
      (IF (SETQ INT-APPEND-P (OR APPEND-P (ZEROP (ZMAIL-BUFFER-NMSGS OLD-FILE))))
	  (MULTIPLE-VALUE-BIND (END-LINE PREV-MSG-END-BPS)
	      (SEND OLD-FILE :LAST-LINE-FOR-APPEND)
	    (IF END-LINE
		(LET ((START-LINE (BP-LINE (INTERVAL-FIRST-BP NEW-INT))))
		  (SETF (LINE-NEXT END-LINE) START-LINE)
		  (SETF (LINE-PREVIOUS START-LINE) END-LINE)
		  (DOLIST (BP PREV-MSG-END-BPS)
		    (MOVE-BP BP START-LINE 0)))
	      (MOVE-BP (INTERVAL-FIRST-BP OLD-INT) (INTERVAL-FIRST-BP NEW-INT)))
	    (MOVE-BP (INTERVAL-LAST-BP OLD-INT) (INTERVAL-LAST-BP NEW-INT)))
	(LET ((START-LINE (BP-LINE (SEND OLD-FILE :FIRST-MSG-BP))))
	  (LET ((PREV (LINE-PREVIOUS START-LINE))
		(NEW-START-LINE (BP-LINE (INTERVAL-FIRST-BP NEW-INT))))
	    (SETF (LINE-PREVIOUS NEW-START-LINE) PREV)
	    (IF PREV
		(SETF (LINE-NEXT PREV) NEW-START-LINE)
	      (MOVE-BP (INTERVAL-FIRST-BP OLD-INT) NEW-START-LINE 0)))
	  (LET ((NEW-END-LINE (BP-LINE (INTERVAL-LAST-BP NEW-INT))))
	    (AND (ZEROP (LINE-LENGTH NEW-END-LINE))
		 (LINE-PREVIOUS NEW-END-LINE)
		 (SETQ NEW-END-LINE (LINE-PREVIOUS NEW-END-LINE)))
	    (SETF (LINE-NEXT NEW-END-LINE) START-LINE)
	    (SETF (LINE-PREVIOUS START-LINE) NEW-END-LINE))))
      (LET ((NEW-INFS (NODE-INFERIORS NEW-INT))
	    (OLD-INFS (NODE-INFERIORS OLD-INT))
	    LAST-INT FIRST-INT)
	(DOLIST (INT NEW-INFS)
	  (SETF (NODE-SUPERIOR INT) OLD-INT))
	(IF INT-APPEND-P
	    (SETQ LAST-INT (CAR (LAST OLD-INFS))
		  FIRST-INT (CAR NEW-INFS)
		  OLD-INFS (NCONC OLD-INFS NEW-INFS))
	  (SETQ LAST-INT (CAR (LAST NEW-INFS))
		FIRST-INT (CAR OLD-INFS)
		OLD-INFS (NCONC NEW-INFS OLD-INFS)))
	(SETF (NODE-INFERIORS OLD-INT) OLD-INFS)
	(COND ((AND LAST-INT FIRST-INT)
	       (LET* ((LAST-INT-END (INTERVAL-LAST-BP LAST-INT))
		      (FIRST-INT-START (INTERVAL-FIRST-BP FIRST-INT))
		      (LAST-INT-END-1 (INTERVAL-LAST-BP (CAR (NODE-INFERIORS LAST-INT))))
		      (MOVE-1-P (BP-= LAST-INT-END LAST-INT-END-1)))
		 (MOVE-BP LAST-INT-END FIRST-INT-START)
		 (AND MOVE-1-P (MOVE-BP LAST-INT-END-1 FIRST-INT-START))) 
	       (SETF (NODE-NEXT LAST-INT) FIRST-INT)
	       (SETF (NODE-PREVIOUS FIRST-INT) LAST-INT))))
      (LET* ((NEW-ARRAY (ZMAIL-BUFFER-ARRAY NEW-FILE))
	     (OLD-ARRAY (ZMAIL-BUFFER-ARRAY OLD-FILE))
	     (NMSGS (ARRAY-ACTIVE-LENGTH NEW-ARRAY))
	     (OLDLEN (ARRAY-ACTIVE-LENGTH OLD-ARRAY))
	     (NEWLEN (+ NMSGS OLDLEN)))
	(AND (< (ARRAY-LENGTH OLD-ARRAY) NEWLEN)
	     (ADJUST-ARRAY-SIZE OLD-ARRAY (TRUNCATE (* NEWLEN 5) 4)))
	(OR APPEND-P
	    ;; If prepending, make space in the array
	    (DO ((I (1- OLDLEN) (1- I))
		 (J (1- NEWLEN) (1- J)))
		((< I 0))
	      (ASET (AREF OLD-ARRAY I) OLD-ARRAY J)))
	(SETF (ARRAY-LEADER OLD-ARRAY 0) NEWLEN)
	(DO ((I 0 (1+ I))
	     (J (IF APPEND-P OLDLEN 0) (1+ J))
	     (MSG))
	    (( I NMSGS))
	  (SETQ MSG (AREF NEW-ARRAY I))
	  ;; It is important that the message be parsed by the inbox buffer, so that UNSEEN
	  ;; properties get put on.  That is why this MSG-PUT is before the :NEW-MSG.
	  (MSG-PUT MSG T 'RECENT)
	  (SEND OLD-FILE :NEW-MSG MSG)
	  (ASET MSG OLD-ARRAY J))
	;; If this is new mail for a prepending BABYL file, and there is still some old mail
	;; to come in, the last new message won't get a formfeed.  Fix it now.
	(AND (NOT APPEND-P) (ZEROP OLDLEN) (PLUSP NEWLEN)
	     (EQ (ZMAIL-DISK-BUFFER-STATUS OLD-FILE) :LOADING)
	     (LET* ((MSG (AREF NEW-ARRAY (1- NEWLEN)))
		    (LAST-BP (INTERVAL-LAST-BP OLD-INT))
		    (MSG-LAST-BP (MSG-REAL-END-BP MSG))
		    (AT-END-P (BP-= LAST-BP MSG-LAST-BP)))
	       (SEND OLD-FILE :UPDATE-MSG-END MSG T)
	       (AND AT-END-P (MOVE-BP LAST-BP (END-LINE MSG-LAST-BP))))))))
  (COND ((EQ NEW-FILE *ZMAIL-BUFFER*)
	 (SELECT-ZMAIL-BUFFER OLD-FILE (EQ NEW-FILE *PRIMARY-ZMAIL-BUFFER*)))
	((EQ OLD-FILE *ZMAIL-BUFFER*)		;*MSG-NO* may need changing
	 (ZMAIL-SELECT-MSG *MSG* T NIL)))
  (MSG-POINT-PDL-FORWARD-ZMAIL-BUFFER NEW-FILE OLD-FILE))

(DEFUN ZMAIL-BUFFER-BACKGROUND-SAVE (ZMAIL-BUFFER)
  (COND ((AND (NEQ *ZMAIL-BACKGROUND-P* :DISABLE)
	      (SEND ZMAIL-BUFFER :ASSOCIATED-INBOX-BUFFER))
	 (ZMAIL-BUFFER-SAVE-SETUP ZMAIL-BUFFER)
	 (ZMAIL-BACKGROUND-REQUEST-PUSH
	   (LIST 'ZMAIL-BACKGROUND-SAVE-FILE
		 ZMAIL-BUFFER
		 (INTERVAL-STREAM (ZMAIL-DISK-BUFFER-INTERVAL ZMAIL-BUFFER)))))
	(T
	 (SETF (ZMAIL-DISK-BUFFER-STATUS ZMAIL-BUFFER) NIL))))

;;; New mail

;;; Note: all inbox buffer flavors must have the same instance variables,
;;; namely, those of INBOX-BUFFER.

(DEFUN MAKE-INBOX-BUFFER (TYPE LIST FROM-ZMAIL-BUFFER)
  (OR (ZMAIL-BUFFER-APPEND-P FROM-ZMAIL-BUFFER)
      (SETQ LIST (NREVERSE LIST)))
  (MAKE-ZMAIL-BUFFER TYPE
		     :ASSOCIATED-MAIL-FILE-BUFFER FROM-ZMAIL-BUFFER
		     :NAME (STRING-APPEND "New mail for " (STRING (SEND FROM-ZMAIL-BUFFER :NAME)))
		     :FILE-LIST LIST))

(DEFUN REPLACE-INBOX-BUFFER (FLAVOR)
  (DECLARE (:SELF-FLAVOR INBOX-BUFFER))
  ;; Parse all messages before replacing
  ;; since the way to parse depends on the flavor.
  (DOTIMES (I (ZMAIL-BUFFER-NMSGS SELF))
    (ASSURE-MSG-PARSED (AREF ARRAY I)))
  ;; Just magically change the flavor of the instance.
  (SI:ASSURE-FLAVOR-COMPOSED FLAVOR)
  (%P-STORE-POINTER SELF (GET FLAVOR 'SI:FLAVOR))
  SELF)

(DEFMETHOD (INBOX-BUFFER :AFTER :INIT) (IGNORE)
  (SEND ASSOCIATED-MAIL-FILE-BUFFER :SET-ASSOCIATED-INBOX-BUFFER SELF)
  (SETQ PENDING-FILE-LIST FILE-LIST
	STATUS :NEW-MAIL
	FILE-LIST-MAIL-CHECK-INFO (LOOP FOR X IN FILE-LIST
					COLLECT (LIST (CAR X) NIL))))

;;; This gets called when starting to get new mail or after one file of new mail
;;; has been read in.  It should return T if it has started something loading.
(DEFMETHOD (INBOX-BUFFER :START-NEXT-FILE) ()
  (DO ((FILE) (RENAME) (DELETE-P) (STR)
       (LOADING-NAME NIL NIL)
       (LOADING-TRUENAME NIL NIL))
      ((NULL PENDING-FILE-LIST) NIL)
    (SETF `(,FILE ,RENAME ,DELETE-P) (CAR PENDING-FILE-LIST))
    ;; If the next pending file is on a different host,
    ;; and needs a different flavor of INBOX-BUFFER,
    ;; replace this one with a suitable one and try again with that one.
    (UNLESS (EQ (TYPE-OF SELF) (SEND FILE :INBOX-BUFFER-FLAVOR))
      (RETURN (SEND (REPLACE-INBOX-BUFFER (SEND FILE :INBOX-BUFFER-FLAVOR))
		    :START-NEXT-FILE)))
    (COND ((NOT RENAME)
	   ;;No file to rename to, see if new file exists
	   (CONDITION-CASE ()
	       (SETQ STREAM (OPEN FILE :DIRECTION :INPUT))
	     (FS:FILE-NOT-FOUND (POP PENDING-FILE-LIST) NIL)
	     (:NO-ERROR (POP PENDING-FILE-LIST) NIL)
	     (ERROR)))
	  ((IGNORE-ERRORS
	     (SETQ STREAM (OPEN RENAME :DIRECTION :INPUT)))
	   (LET ((TEM (CAR PENDING-FILE-LIST)))
	     (POP PENDING-FILE-LIST)
	     ;;If file to rename to already exists,
	     ;;arrange for real file to get read in after saving done next time
	     (SETQ NEXT-PENDING-FILE-LIST
		   (NCONC NEXT-PENDING-FILE-LIST (NCONS TEM)))))
	  ((CONDITION-CASE ()
	       (SETQ STR (OPEN FILE :DIRECTION NIL))
	     (FS:FILE-NOT-FOUND (POP PENDING-FILE-LIST) NIL)
	     (ERROR))
	   (POP PENDING-FILE-LIST)
	   ;;Rename to new file.  This does not use the :RENAME stream operation since that
	   ;;doesn't work correctly on Tops-20 and does not return the TRUENAME.
	   (SETQ LOADING-NAME (SEND STR :PATHNAME)
		 LOADING-TRUENAME (SEND STR :TRUENAME))
	   (CONDITION-CASE ()
	       (SEND FILE :RENAME RENAME)
	     (FS:FILE-LOCKED
	      (FORMAT *QUERY-IO* "~&File ~A is being accessed so cannot be renamed.  Pausing."
		      LOADING-TRUENAME)
	      (PROCESS-SLEEP (* 10. 60.))
	      (DO ()
		  (())
		(CONDITION-CASE ()
		    (SEND FILE :RENAME RENAME)
		  (FS:FILE-LOCKED NIL)
		  (:NO-ERROR
		   (FORMAT *QUERY-IO* "~&File successfully renamed, continuing.")
		   (RETURN T)))
		(UNLESS (Y-OR-N-P "Try again?  You can also abort. ")
		  (SIGNAL-CONDITION EH:ABORT-OBJECT)))))

	   ;;Get the renamed file
	   (SETQ STREAM (OPEN RENAME '(:IN)))))
    (WHEN STREAM
      (SETQ PATHNAME (OR LOADING-NAME (SEND STREAM :PATHNAME))
	    NAME (SEND PATHNAME :STRING-FOR-PRINTING)
	    STATUS :LOADING-NEW-MAIL)
      (START-LOADING-ZMAIL-BUFFER SELF STREAM T LOADING-TRUENAME)
      (AND DELETE-P (PUSH (SEND STREAM :TRUENAME) PENDING-DELETION-LIST))
      (RETURN T))))

;;; This is called when the one file is all the way in
(DEFMETHOD (INBOX-BUFFER :LOADING-DONE) ()
  (COND (STREAM
	 (SEND STREAM :CLOSE)
	 (SETQ STREAM NIL)))
  (OR (SEND SELF :START-NEXT-FILE)		;We can still do more, continue
      (SEND (SEND ASSOCIATED-MAIL-FILE-BUFFER :ASSOCIATED-INBOX-BUFFER)
	    :LOADING-DONE-INTERNAL)))

(DEFMETHOD (INBOX-BUFFER :LOADING-DONE-INTERNAL) ()
  ;; If the other file is all in or we are prepending, can put together now
  (AND (OR (NOT (ZMAIL-BUFFER-APPEND-P ASSOCIATED-MAIL-FILE-BUFFER))
	   (MEMQ (ZMAIL-DISK-BUFFER-STATUS ASSOCIATED-MAIL-FILE-BUFFER)
		 '(NIL :AWAITING-NEW-MAIL :SAVING-REQUIRED)))
       (INSERT-NEW-MAIL ASSOCIATED-MAIL-FILE-BUFFER SELF))
  (SETQ STATUS :AWAITING-SAVE)
  (COND ((NOT (MEMQ (ZMAIL-DISK-BUFFER-STATUS ASSOCIATED-MAIL-FILE-BUFFER)
		    ;;Loading or already saving
		    '(NIL :AWAITING-NEW-MAIL :SAVING-REQUIRED))))
	(*INHIBIT-BACKGROUND-SAVES*
	 (SETF (ZMAIL-DISK-BUFFER-STATUS ASSOCIATED-MAIL-FILE-BUFFER) :SAVING-REQUIRED))
	(T
	 (ZMAIL-BUFFER-BACKGROUND-SAVE ASSOCIATED-MAIL-FILE-BUFFER))))

;;; This is called after the primary mail file has been saved out with us in it
;;; It should return T if it has started up again
(DEFMETHOD (INBOX-BUFFER :SAVING-DONE) (FORCING-OUT)
  (SETQ STATUS :NEW-MAIL)
  (DOLIST (FILE PENDING-DELETION-LIST)
    (CONDITION-CASE (ERROR)
	(SEND FILE :DELETE)
      (FS:FILE-ERROR
       (FORMAT *QUERY-IO* "~&Deletion error: ~A" ERROR))))
  (SETQ PENDING-DELETION-LIST NIL)
  (COND ((AND (NOT FORCING-OUT)
	      (SETQ PENDING-FILE-LIST NEXT-PENDING-FILE-LIST))
	 (SETQ NEXT-PENDING-FILE-LIST NIL)
	 ;; Start things over
	 (STORE-ARRAY-LEADER 0 ARRAY 0)
	 (SETQ INFERIORS NIL)
	 (LET ((LINE (CREATE-LINE 'ART-STRING 0 NIL)))
	   (SETF FIRST-BP (CREATE-BP LINE 0 :NORMAL))
	   (SETF LAST-BP (CREATE-BP LINE 0 :MOVES))
	   (SETF (LINE-NODE LINE) SELF))
	 (SEND SELF :START-NEXT-FILE))))

;;; This is called from the background process to see if there is new mail
(DEFMETHOD (INBOX-BUFFER :BACKGROUND-CHECK-FOR-NEW-MAIL) (&AUX -STREAM- CREATION-DATE)
  (AND (EQ STATUS :NEW-MAIL)			;Only if idle
       (DOLIST (ELEM FILE-LIST-MAIL-CHECK-INFO)
	 (IGNORE-ERRORS
	   ;; Don't wait a long time to bomb out if the host is down.
	   ;; It could hold up the ZMAIL process.
	   (WHEN (CHAOS:HOST-UP-P (PATHNAME-HOST (FIRST ELEM)) 60.)
	     (SETQ -STREAM- (OPEN (FIRST ELEM) :DIRECTION NIL))))
	 (COND ((AND -STREAM-
		     (NOT (EQUAL (SETQ CREATION-DATE (SEND -STREAM- :CREATION-DATE))
				 (SECOND ELEM))))
		(SETF (SECOND ELEM) CREATION-DATE)
		(MULTIPLE-VALUE-BIND (NIL MINUTES HOURS)
		    (TIME:DECODE-UNIVERSAL-TIME CREATION-DATE)
		  (ZMAIL-BACKGROUND-RESPONSE-PUSH
		    (LIST 'NEW-MAIL
			  "New mail in ~A at ~D:~2,'0D"
			  (FIRST ELEM)
			  HOURS MINUTES))))))))

;;; Hardcopy functions

(DEFVAR *HARDCOPY-DEVICE*)

;;; Options for all
(DEFVAR *INCLUDE-SUMMARY-ALIST*
	'(("Yes" :VALUE T :DOCUMENTATION "Print summary and messages.")
	  ("No" :VALUE NIL :DOCUMENTATION "Do not print a summary.")
	  ("Just summary" :VALUE :JUST-SUMMARY
	   :DOCUMENTATION "Print summary but not messages themselves.")))

(DEFINE-ZMAIL-HARDCOPY-OPTION *HARDCOPY-SUMMARY-P* T :MENU-ALIST
			      "Include summary" *INCLUDE-SUMMARY-ALIST*)
(DEFINE-ZMAIL-HARDCOPY-OPTION *HARDCOPY-SEPARATE-PAGES* NIL :BOOLEAN
			      "Print each message on a separate page")
(DEFINE-ZMAIL-HARDCOPY-OPTION *HARDCOPY-SEPARATOR-LINE* NIL :STRING-OR-NIL
			      "Line between messages (when not on separate pages)")

(DEFVAR *ZMAIL-DEVICE-INDEPENDENT-HARDCOPY-OPTIONS* NIL)

(DEFVAR *ZMAIL-SINGLE-MSG-DEVICE-INDEPENDENT-HARDCOPY-OPTIONS* NIL)

(DEFVAR *ZMAIL-WHOLE-FILE-DEVICE-INDEPENDENT-HARDCOPY-OPTIONS*
	'(*HARDCOPY-SUMMARY-P* *HARDCOPY-SEPARATE-PAGES* *HARDCOPY-SEPARATOR-LINE*))

(DEFSTRUCT (HARDCOPY-DEVICE :LIST :CONC-NAME)
  KEY
  FLAVOR
  OTHER-OPTIONS)

(DEFMACRO ADD-HARDCOPY-DEVICE (FLAVOR-NAME KEY NAME OTHER-OPTION-VARIABLES)
  `(ADD-HARDCOPY-DEVICE-1 ',FLAVOR-NAME ',KEY ',NAME ,OTHER-OPTION-VARIABLES))

(DEFVAR *HARDCOPY-DEVICE-ALIST* NIL)
(DEFVAR *HARDCOPY-DEVICE-MENU-ALIST* NIL)

(DEFUN ADD-HARDCOPY-DEVICE-1 (FLAVOR KEY NAME OTHER-OPTIONS &AUX DEVICE)
  (SETQ DEVICE (MAKE-HARDCOPY-DEVICE :KEY KEY
				     :FLAVOR FLAVOR
				     :OTHER-OPTIONS OTHER-OPTIONS))
  (SETQ *HARDCOPY-DEVICE-ALIST* (CONS DEVICE
				      (DELQ (ASSQ KEY *HARDCOPY-DEVICE-ALIST*)
					    *HARDCOPY-DEVICE-ALIST*)))
  (SETQ *HARDCOPY-DEVICE-MENU-ALIST* (CONS (CONS NAME KEY)
					   (DELQ (RASSQ KEY *HARDCOPY-DEVICE-MENU-ALIST*)
						 *HARDCOPY-DEVICE-MENU-ALIST*)))
  ;; If this is being added after normal initializations
  (LET ((INIT (ASSOC "SITE:*HARDCOPY-DEVICE*" SI:SITE-INITIALIZATION-LIST)))
    (AND INIT (EVAL (SI:INIT-FORM INIT)))))

(DEFVAR *HARDCOPY-WHOLE-FILE-P*)

(DEFUN COMPUTE-HARDCOPY-CHOICES (DEVICE &AUX ALIST)
  (OR *HARDCOPY-DEVICE* (BARF "No known hardcopy devices at this site"))
  (SETQ DEVICE (ASSQ DEVICE *HARDCOPY-DEVICE-ALIST*))
  (SETQ ALIST `(*HARDCOPY-DEVICE*
	        ,@(HARDCOPY-DEVICE-OTHER-OPTIONS DEVICE)
		,@*ZMAIL-DEVICE-INDEPENDENT-HARDCOPY-OPTIONS*
		,@(IF *HARDCOPY-WHOLE-FILE-P*
		      *ZMAIL-WHOLE-FILE-DEVICE-INDEPENDENT-HARDCOPY-OPTIONS*
		    *ZMAIL-SINGLE-MSG-DEVICE-INDEPENDENT-HARDCOPY-OPTIONS*)))
  (SETQ ALIST (MAPCAR #'(LAMBDA (X) (ASSQ X *ZMAIL-HARDCOPY-OPTION-ALIST*)) ALIST))
  (SETQ ALIST (TV:PRUNE-USER-OPTION-ALIST ALIST))
  ALIST)

(DEFUN CHOOSE-HARDCOPY-OPTIONS (NEAR-MODE *HARDCOPY-WHOLE-FILE-P*)
  (TV:CHOOSE-VARIABLE-VALUES (COMPUTE-HARDCOPY-CHOICES *HARDCOPY-DEVICE*)
			     :LABEL "Hardcopy options:"
			     :NEAR-MODE NEAR-MODE
			     :MARGIN-CHOICES '("Do It"
						("Abort" (ABORT-CURRENT-COMMAND)))
			     :FUNCTION 'CHOOSE-HARDCOPY-OPTIONS-FUNCTION))

(DEFUN CHOOSE-HARDCOPY-OPTIONS-FUNCTION (WINDOW VARIABLE OLDVAL NEWVAL)
  OLDVAL
  (SEND *PROFILE-WINDOW* :VARIABLE-TICK)
  (COND ((EQ VARIABLE '*HARDCOPY-DEVICE*)
	 (TV:WITH-SHEET-DEEXPOSED (WINDOW)
	   (SEND WINDOW :SETUP (COMPUTE-HARDCOPY-CHOICES NEWVAL)
		    (SEND WINDOW :LABEL)
		    (SEND WINDOW :FUNCTION)
		    (SYMEVAL-IN-INSTANCE WINDOW 'TV:MARGIN-CHOICES)))
	 T)))

;;; This makes hardcopy be a ZMAIL-BUFFER for GET-MOVE-ZMAIL-BUFFER
(DEFUN MAKE-HARDCOPY-ZMAIL-BUFFER (CHOOSE-OPTIONS-P FOR-WHOLE-FILE-P NEAR-MODE)
  (UNLESS *HARDCOPY-DEVICE* (BARF "No known hardcopy devices at this site"))
  (AND CHOOSE-OPTIONS-P (CHOOSE-HARDCOPY-OPTIONS NEAR-MODE FOR-WHOLE-FILE-P))
  (MAKE-INSTANCE (HARDCOPY-DEVICE-FLAVOR (ASSQ *HARDCOPY-DEVICE* *HARDCOPY-DEVICE-ALIST*))))

(DEFINE-ZMAIL-TOP-LEVEL-COMMAND COM-ZMAIL-HARDCOPY-MSG "Hardcopy the current message." ()
  (SEND (MAKE-HARDCOPY-ZMAIL-BUFFER (EQ *ZMAIL-COMMAND-BUTTON* :RIGHT) NIL
				    (RECTANGLE-NEAR-COMMAND-MENU))
	   :ADD-MSG *MSG*)
  DIS-NONE)

(DEFINE-ZMAIL-TOP-LEVEL-COMMAND COM-ZMAIL-HARDCOPY-ALL "Hardcopy the current buffer." ()
  (SEND (MAKE-HARDCOPY-ZMAIL-BUFFER (EQ *ZMAIL-COMMAND-BUTTON* :RIGHT) T
				    (RECTANGLE-NEAR-COMMAND-MENU))
	   :ADD-ZMAIL-BUFFER *ZMAIL-BUFFER*)
  DIS-NONE)

(DEFFLAVOR HARDCOPY-ZMAIL-BUFFER () ()
  (:INCLUDED-FLAVORS SI:LINE-OUTPUT-STREAM-MIXIN))

(DEFMETHOD (HARDCOPY-ZMAIL-BUFFER :PRINT-SELF) (STREAM &REST IGNORE)
  (SI:PRINTING-RANDOM-OBJECT (SELF STREAM :TYPEP)
    (PRIN1 (SEND SELF :NAME) STREAM)))

(DEFMETHOD (HARDCOPY-ZMAIL-BUFFER :DRAW-UNDERLINE) () NIL)

(DEFMACRO WITH-HARDCOPY-OUTPUT ((HARDCOPY-ZMAIL-BUFFER) &BODY BODY)
  `(UNWIND-PROTECT
     (PROGN ,@BODY
	    (SEND ,HARDCOPY-ZMAIL-BUFFER :CLOSE))
     (SEND ,HARDCOPY-ZMAIL-BUFFER :CLOSE :ABORT)))

(DEFMETHOD (HARDCOPY-ZMAIL-BUFFER :ADD-MSG) (MSG &OPTIONAL ALREADY-IN-OTHER-PROCESS-P)
  (IF ALREADY-IN-OTHER-PROCESS-P
      (WITH-HARDCOPY-OUTPUT (SELF)
	(SEND SELF :OPEN (ZMAIL-BUFFER-NAME (MSG-MAIL-FILE-BUFFER MSG))
	      (MSG-GET MSG :DATE))
	(HARDCOPY-ONE-MSG MSG SELF (MSG-DISPLAYED-INDEX MSG) NIL))
    (PROCESS-RUN-FUNCTION "ZMAIL HARDCOPY" SELF :ADD-MSG MSG T)))

;; Don't change appearance of a header just to print it.
(DEFMETHOD (HARDCOPY-ZMAIL-BUFFER :REFORMAT-MSG-HEADER) (IGNORE)
  NIL)

(DEFMETHOD (HARDCOPY-ZMAIL-BUFFER :ADD-ZMAIL-BUFFER) (ZMAIL-BUFFER
						      &OPTIONAL ALREADY-IN-OTHER-PROCESS-P)
  (IF ALREADY-IN-OTHER-PROCESS-P
      (LET (ARRAY NMSGS)
	(SETQ ARRAY (ZMAIL-BUFFER-ARRAY ZMAIL-BUFFER)
	      NMSGS (ARRAY-ACTIVE-LENGTH ARRAY))
	(WITH-HARDCOPY-OUTPUT (SELF)
	  (SEND SELF :OPEN (ZMAIL-BUFFER-NAME ZMAIL-BUFFER) NIL)
	  (COND (*HARDCOPY-SUMMARY-P*
		 (SEND SELF :LINE-OUT (SEND ZMAIL-BUFFER :FULL-NAME))
		 (SEND SELF :DRAW-UNDERLINE)
		 (SEND SELF :LINE-OUT *SUMMARY-WINDOW-LABEL*)
		 (DO ((I 0 (1+ I))
		      (MSG) (STATUS) (STRING))
		     (( I NMSGS))
		   (SETQ MSG (AREF ARRAY I)
			 ;; Maessage must be parsed before the MSG-SUMMARY-LINE is valid.
			 STATUS (ASSURE-MSG-PARSED MSG)
			 STRING (MSG-SUMMARY-LINE MSG))
		   (FORMAT SELF " ~3D~C" (1+ I) (STATUS-LETTER STATUS))
		   (SEND SELF :STRING-OUT STRING 0
			 (MIN (STRING-LENGTH STRING) 90.))
		   (SEND SELF :TYO #\CR))))
	  (COND ((NEQ *HARDCOPY-SUMMARY-P* :JUST-SUMMARY)
		 (DO ((I 0 (1+ I))
		      (MSG)
		      (FIRST-P T NIL))
		     (( I NMSGS))
		   (COND ((IF FIRST-P *HARDCOPY-SUMMARY-P*
			    *HARDCOPY-SEPARATE-PAGES*)
			  (SEND SELF :TYO #\PAGE))
			 ((AND *HARDCOPY-SEPARATOR-LINE* (NOT FIRST-P))
			  (SEND SELF :LINE-OUT *HARDCOPY-SEPARATOR-LINE*)))
		   (SETQ MSG (AREF ARRAY I))
		   (HARDCOPY-ONE-MSG MSG SELF I ZMAIL-BUFFER))))))
    (PROCESS-RUN-FUNCTION "ZMAIL HARDCOPY" SELF :ADD-ZMAIL-BUFFER ZMAIL-BUFFER T)))

(DEFUN HARDCOPY-ONE-MSG (MSG HARDCOPY-ZMAIL-BUFFER INDEX ZMAIL-BUFFER &AUX TEM)
  (FORMAT HARDCOPY-ZMAIL-BUFFER "Message #~D" (1+ INDEX))
  (AND (NEQ ZMAIL-BUFFER (SETQ TEM (MSG-MAIL-FILE-BUFFER MSG)))
       (FORMAT HARDCOPY-ZMAIL-BUFFER " (from ~A)" (ZMAIL-BUFFER-NAME TEM)))
  (COND ((SETQ TEM (MSG-GET MSG 'KEYWORDS-STRING))
	 (SEND HARDCOPY-ZMAIL-BUFFER :TYO #\SP)
	 (SEND HARDCOPY-ZMAIL-BUFFER :STRING-OUT TEM)))
  (SEND HARDCOPY-ZMAIL-BUFFER :TYO #\CR)
  (STREAM-OUT-INTERVAL HARDCOPY-ZMAIL-BUFFER (MSG-INTERVAL MSG)))

;;; Hardware specific

(DEFFLAVOR HARDCOPY-TO-FILE-MIXIN
	(*FILE-STREAM*)
	()
  (:INCLUDED-FLAVORS HARDCOPY-ZMAIL-BUFFER))

(DEFMETHOD (HARDCOPY-TO-FILE-MIXIN :TYO) PASS-MESSAGE-TO-FILE-STREAM)
(DEFMETHOD (HARDCOPY-TO-FILE-MIXIN :STRING-OUT) PASS-MESSAGE-TO-FILE-STREAM)
(DEFMETHOD (HARDCOPY-TO-FILE-MIXIN :LINE-OUT) PASS-MESSAGE-TO-FILE-STREAM)

(DEFUN PASS-MESSAGE-TO-FILE-STREAM (&REST ARGS)
  (DECLARE (:SELF-FLAVOR HARDCOPY-TO-FILE-MIXIN))
  (APPLY *FILE-STREAM* ARGS))

(DEFMETHOD (HARDCOPY-TO-FILE-MIXIN :CLOSE) (&OPTIONAL ABORT-P)
  (SEND *FILE-STREAM* :CLOSE ABORT-P))

;;; TPL
(DEFFLAVOR TPL-HARDCOPY-ZMAIL-BUFFER
	()
	(HARDCOPY-TO-FILE-MIXIN HARDCOPY-ZMAIL-BUFFER))

(ADD-HARDCOPY-DEVICE TPL-HARDCOPY-ZMAIL-BUFFER :TPL "TPL" NIL)

(DEFMETHOD (TPL-HARDCOPY-ZMAIL-BUFFER :NAME) () "TPL:")

(DEFMETHOD (TPL-HARDCOPY-ZMAIL-BUFFER :OPEN) (IGNORE IGNORE)
  (SETQ *FILE-STREAM* (OPEN "TPL:" '(:OUT))))

;;; This comes after all the hardware specific options, so that it gets the right
;;; possibilities.
(DEFINE-SITE-ALIST-USER-OPTION (*HARDCOPY-DEVICE* *ZMAIL-HARDCOPY-OPTION-ALIST*)
			       "Output device" *HARDCOPY-DEVICE-MENU-ALIST*
			       :DEFAULT-HARDCOPY-MODE)
