;;; nrmail.el --- main code of "Nrmail-mode" mail-reading interface for Emacs.

;; Copyright (C) 1996 Eric Mumpower

;; Maintainer: Eric Mumpower (nocturne@mit.edu)
;; Keywords: mail;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; This mode was created for the purpose of helping me deal
;;; with my email. I get way too much email.
;;;
;;; Hm. Looks like this has become "interesting mode hacked on top of
;;; rmail"-mode. I used discuss-mtgs-mode as a model for my work; the
;;; structure of this code is derived from it.
;;;
;;; first revision of this code was brutally stolen from Athena's discuss.el
;;;
;;; $Id: nrmail.el,v 1.14 1996/02/08 03:22:44 nocturne Exp nocturne $
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Well, duh. This code is a wrapper for rmail-mode, after all.
(require 'rmail)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Various nrmail-specific variables.
;;;

(defvar nrmail-DWIM nil
  "If true, enable Do_What_I_Mean mode.  Allows the user to read nrmail by
repeatedly hitting the space bar.  For the truly discrimining and lazy 
user. This feature is not particularly supported, and may never be,
depending on how much I decide to hack on rmail-mode.")

(defvar nrmail-file-list (list "~/RMAIL")
  "List of user's rmail files.")

(defvar nrmail-incoming-mail-filename "~/.nrmail-incoming-mail"
  "Rmail file which will be used as a temporary holding place for
incoming mail before being refiled into various nrmail folders")

(defun  nrmail-default-sorting-heuristic (message)
  nil)

(defvar nrmail-sorting-heuristic nrmail-default-sorting-heuristic
  "*Specifies the symbol of the function used to sort incoming email by nrmail.
This function should accept a rmail message as its only argument, and
should return a filename specifying the rmail folder into which to
output the message, or else \"nil\" if it is unable to determine which
folder the message should go to.

Example of user-level usage of this variable:

   (defun my-nrmail-sorting-heuristic (message)
     (if (let ((case-fold-search t))
	   (rmail-message-recipients-p message \"nocturne@mit.edu\"))
	 \"~/nrmail-personal\"
       nil))
   (setq nrmail-sorting-heuristic 'my-nrmail-sorting-heuristic)

It should be noted that this example is wholly mindless.
")


(defvar nrmail-main-buffer "*nrmail*"
  "Name of main buffer for nrmail, which holds a list of the current
folders.")

(defvar nrmail-cur-folder-buf nil
  "Name of buffer for current Nrmail folder.")

(defvar nrmail-current-folder nil
  "Key of current nrmail folder (as indexed in nrmail-folder-list).")

(defvar nrmail-folders-mode-map nil
  "Keymap used by the folders-list mode of the nrmail subsystem.")

(defvar nrmail-folder-list nil
  "Folder list.")

(defvar nrmail-folder-completion-list nil
  "Folder list changed into the right format for completion-read.")

(defvar nrmail-folderobj-next-key 0
  "variable internal to nrmail-mode. Should never be modified except by
code internal to nrmail-mode.")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Entry-level code and core routines.
;;;

(defun nrmail (&optional arg)
  "Enter nrmail mode for emacs and list rmail files."
  (interactive "P")
  (message "Starting nrmail....")
  (if (not (and (get-buffer nrmail-main-buffer)
		(buffer-name (get-buffer nrmail-main-buffer))
		(> (length nrmail-folder-list) 0)))
      (progn
	(switch-to-buffer (get-buffer-create nrmail-main-buffer))
	(nrmail-folders-mode)
	(setq nrmail-folder-list nil
	      nrmail-folderobj-next-key 0)
	(if arg
	    (message "Arguments to nrmail are unsupported.")	     
	  (nrmail-list-folders)))
    (switch-to-buffer nrmail-main-buffer)))

(defun nrmail-folders-mode ()
  "Major mode for providing an emacs nrmail subsystem.
This looks a lot like RMAIL and discuss.

The following commands are available:
SPC,n   go to next folder that has unread messages
DEL,p   go to previous folder has unread messages
l	list folders
g	go to folder listed on line
c	mark a folder as read (catch up)
q	Quit nrmail mode."
  (interactive)
  (kill-all-local-variables)
  (setq major-mode 'nrmail-folders-mode)
  (setq mode-name "NRmail (folders)")
  (use-local-map nrmail-folders-mode-map)
  (setq buffer-read-only t)
  (run-hooks 'nrmail-mode-hooks))

(defun nrmail-list-folders ()
  "List nrmail folders."
  (interactive)
  (message "Listing folders...")
  (switch-to-buffer (get-buffer nrmail-main-buffer))
  (message "Don't forget to nuke the line with the \"XXX\" comment in nrmail-list-folders")
  (setq nrmail-folder-list nil
	nrmail-folderobj-next-key 0) ; XXX
  (if (eq nil nrmail-folder-list)
      (setq nrmail-folder-list (nrmail-folder-list-init)))
  (let ((buffer-read-only nil))
    (erase-buffer)
    (insert " Flags   Folder name\n -----   -----------")
    (mapcar 'insert (mapcar 'nrmail-folder-line-info nrmail-folder-list))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;  folder-object-list implementation

(defun nrmail-folder-list-init ()
  "nrmail folder-object-list creator"
  (mapcar 'nrmail-folderobj-new nrmail-file-list))

(defun nrmail-folder-list-find (key)
  "nrmail folder-object-list accessor"
  (nth key nrmail-folder-list))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;  folderobj (i.e. folder-object) implementation

(defun nrmail-folderobj-new (filename)
  "nrmail folder-object creator"
  (let ((current-key nrmail-folderobj-next-key))
    (setq nrmail-folderobj-next-key (1+ nrmail-folderobj-next-key))
    (list (list 'key          current-key)
	  (list 'filename     filename)
	  (list 'name         (file-name-nondirectory filename))
	  (list 'unread-flag  nil))))

(defun nrmail-folderobj-get-key (folderobj)
  "nrmail folder-object accessor"
  (car (cdr (assq 'key folderobj))))

(defun nrmail-folderobj-get-filename (folderobj)
  "nrmail folder-object accessor"
  (car (cdr (assq 'filename folderobj))))

(defun nrmail-folderobj-get-name (folderobj)
  "nrmail folder-object accessor"
  (car (cdr (assq 'name folderobj))))

(defun nrmail-folderobj-unread-p (folderobj)
  "nrmail folder-object accessor"
  (if (> 2 (car (cdr (assq 'key folderobj))))
      t
    nil))
;;;  (car (cdr (assq 'unread-flag folderobj))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;  folder-line-info abstraction

(defun nrmail-folder-line-info (folder)
  (let ((finfo (format "\n %c       %s" 
		       ;; "? " is the space character.)
		       (if (nrmail-folderobj-unread-p folder) ?u ? )
		       (nrmail-folderobj-get-name folder))))
    (set-text-properties 0 (length finfo)
			 (list 'key (nrmail-folderobj-get-key folder))
			 finfo)
    finfo))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; nrmail-mode keymap stuff. Glue code for the mode.
;;;

(if nrmail-folders-mode-map
    nil
  (setq nrmail-folders-mode-map (make-keymap))
  (suppress-keymap nrmail-folders-mode-map)
  (define-key nrmail-folders-mode-map "n" 'nrmail-forward-folder)
  (define-key nrmail-folders-mode-map "p" 'nrmail-prev-folder)
  (define-key nrmail-folders-mode-map "g" 'nrmail-get-new-mail)
  (define-key nrmail-folders-mode-map "i" 'rmail-input)
  (define-key nrmail-folders-mode-map " " 'nrmail-next-folder)
  (define-key nrmail-folders-mode-map "\177" 'nrmail-prev-folder)
  (define-key nrmail-folders-mode-map "l" 'nrmail-list-folders)
  (define-key nrmail-folders-mode-map "g" 'nrmail-goto)
  (define-key nrmail-folders-mode-map "q" 'nrmail-quit)
  (define-key nrmail-folders-mode-map "c" 'nrmail-catchup)
  (define-key nrmail-folders-mode-map "?" 'describe-mode))

;
; This should be re-implemented using text properties. This is sufficient
; for now; it works. Modifications elsewhere might break it.
;
(defun nrmail-next-folder (&optional quiet dontgo)
  "Find the next unread folder in the nrmail *nrmail* buffer, or wrap."
  (interactive)
  (let ((buffer (current-buffer)))
    (set-buffer nrmail-main-buffer)
    ;; If we're in DWIM mode, and we're currently looking at a changed
    ;; folder, go to it.
    (if (and nrmail-DWIM
	     (not dontgo)
	     (re-search-backward "^ u" (save-excursion (beginning-of-line)
						       (point))
				 t))
	(nrmail-goto)
      (if (not (re-search-forward "^ u " nil t))
	  (progn
	    (goto-char (point-min))
	    (if (not (re-search-forward "^ u " nil t))
		(if (not quiet)
		    (message "No unread messages, try nrmail-list-folders instead."))
	      ))))
    (set-buffer buffer)))

(defun nrmail-forward-folder (&optional quiet)
  "Find the next unread folder in the nrmail *nrmail* buffer, or wrap."
  (interactive)
  (let ((nrmail-DWIM nil))
    (nrmail-next-folder quiet)))

(defun nrmail-prev-folder ()
  "Find the previous unread folder in the nrmail *nrmail* buffer, or wrap."
  (interactive)
  (beginning-of-line)
  (if (not (re-search-backward "^ u " nil t))
      (progn
	(goto-char (point-max))
	(if (not (re-search-backward "^ u " nil t))
	    (message "No unread messages, try nrmail-list-folders instead.")
	  (forward-char 3)
	  ))
    (forward-char 3)))

(defun nrmail-goto (&optional folder)
  "Go to a folder."
  (interactive (list (if (or current-prefix-arg
			     (not (equal (buffer-name) nrmail-main-buffer))
			     (= (point) 1))
			 (rmail-input))))
  (if (not folder)
      (if (not (setq folder (get-text-property (point) 'key)))
	  (error "Not looking at a folder.")))
  (message (format "%s" folder))
  (rmail (nrmail-folderobj-get-filename
	  (nrmail-folder-list-find folder))))

(defun nrmail-quit ()
  "Exits Nrmail mode."
  ;; this needs to be extended so it knows what rmail buffers are its children
  ;; and will go to them and close them if this function is run.
  (interactive)
  (if nrmail-cur-folder-buf
      (rmail-quit))
  (switch-to-buffer (other-buffer))
  (bury-buffer nrmail-main-buffer))

(defun nrmail-catchup (&optional folder)
  "Mark all messages in the current folder as read."
  (interactive
   (list (or nrmail-cur-mtg-buf
	     (if (or current-prefix-arg (= (point) 1))
		 (completing-read "Folder name:  "
				  nrmail-folder-completion-list
				  nil t "")))))
  (if (or (not folder) (stringp folder))
      (progn
	(if (not (setq folder (get-text-property (point) 'key)))
	    (error "Not looking at a folder."))
	(message "Catching up in %s" folder)
	(error "Don't know how to catch up a folder yet.") ; XXX
	(discuss-next-meeting t)
	(message "Done."))
    ;; Otherwise just set discuss-highest-seen.
    (error "Still don't know how to catch up a folder yet.") ; XXX
    ))

(defun nrmail-get-new-mail ()
  (interactive)
  (let ((rmail-file-name nrmail-incoming-mail-filename))
    (rmail-get-new-mail)))

(defun nrmail-autosort-current-message ()
  (interactive)
  (let ((rmail-delete-after-output t)
	(nrmail-folder-to-use (funcall nrmail-sorting-heuristic
				       rmail-current-message)))
    (if (not (equal nrmail-folder-to-use nil))
	(rmail-output-to-rmail-file nrmail-folder-to-use)
      (message "Unable to determine appropriate folder for message.")
      ; XXX - this should probably do more. It works for now.
)))

(defun nrmail-regexp-in-header-p (regexp)
  ;; this function can be called by user-level sorting heuristics.
  ;; This function modifies the point. This may be a problem later. We'll
  ;; deal if it should become a problem.
  (goto-char (point-min))
  (search-forward-regexp "^$" (point-max) t)
  (if (search-backward-regexp regexp (point-min) t)
      t
    nil))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Random utility functions I've written along the way. Might be useful.
;;;

(defun show-text-properties ()
  (interactive)
  (princ (format "%s" (get-text-property (point) 'key))))

(defun pick-to (addressee)
  (interactive "sAddress to search for? ")
  ;; should be something here about checking to see if we're already in
  ;; rmail mode and not working otherwise.
  (rmail-summary-by-recipients addressee))

(defun refile-pick-to (addressee refile-to)
  (interactive "sAddress to search for? \nFRmail file to ouput to? ")
  (save-excursion
					; (pop-to-buffer rmail-buffer)
    (pick-to addressee)
    (beginning-of-buffer) ;;; this is bad for use in lisp programs but
                        ;;; this lisp program modifies the pointer/mark
                        ;;; inside the summary; this is okay.
    (while (not (= (point) (point-max)))
      (save-excursion
	(set-buffer rmail-buffer)
	(let ((rmail-delete-after-output nil))
	  (rmail-output-to-rmail-file refile-to)))
      (rmail-summary-delete-forward nil))))

(defun nrmail-file-has-unseen-messages-p (file)
  (let ((buf (generate-new-buffer " *nrmail-unseen-p*")))
    (unwind-protect
	(save-excursion
	  (set-buffer buf)
	  (insert-file-contents file)
	  (looking-at "BABYL OPTIONS:"))
      (kill-buffer buf))))

(defun rmail-file-p (file)
  (let ((buf (generate-new-buffer " *rmail-file-p*")))
    (unwind-protect
	(save-excursion
	  (set-buffer buf)
	  (insert-file-contents file nil 0 100)
	  (looking-at "BABYL OPTIONS:"))
      (kill-buffer buf))))

;;(refile-pick-to "sdf" "~/.anyone")
;;(pick-to "sipb")

;;; Working ideas...
;;;  * rmail.el     - rmail-set-attribute rmail-message-labels-p
;;;                   rmail-what-message  rmail-message-regexp-p
;;;                   rmail-first-unseen-message                      
;;;  * rmailedit.el - [useless]
;;;  * rmailkwd.el  - [mostly useless]
;;;  * rmailmsc.el  - [useless]
;;;  * rmailout.el  - rmail-file-p
;;;  * rmailsort.el - rmail-fetch-field
;;;  * rmailsum.el  - rmail-summary-by-labels

;;; rmail-file-p may be a very useful template for visiting an rmail
;;; file and checking to see if it contains unseen messages.
