;; Copyright 1995 by Brad Rhodes and MIT
;; Address questions, comments, etc. to Brad Rhodes (rhodes@media.mit.edu)

;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY.  No author or distributor
;; accepts responsibility to anyone for the conseqences of using it
;; or for whether it serves any particular purpose or works at all,
;; unless he says so in writing.  Refer to the GNU Emacs General Public
;; License for full details.

;; Everyone is granted permission to copy, modify and redistribute
;; this file, but only under the conditions described in the
;; GNU Emacs General Public License.   A copy of this license is
;; supposed to have been given to you along with GNU Emacs so you
;; can know your rights and responsibilities.  It should be in a
;; file named COPYING.  Among other things, the copyright notice
;; and this notice must be preserved on all copies.
;;; BUGS:
;;;  o Should do a remem AROUND point, not just above it
;;;  o Incompatible with other programs that want those bottom
;;;    lines, e.g. emacs-zwgc
;;;  o Doesn't use the confidence rating.  It'd be nice to be able
;;;    to specify a threshold, such that recomendations below that
;;;    won't clobber whatever was there before.
;;;  o Still just barely uses the context of the query document.  It'd
;;;    be much better if it could use some knowledge of your current task.

;;; (Elisp is like like friendship bread -- it just keeps spreading)

;; Version info:
;;   Num    Date     Who                     Comments
;;   ------ -------- ----------------------- -----------------
;;   alpha  12/3/95  rhodes@media.mit.edu    First version
;;   0.5    1/11/96  rhodes@media.mit.edu    change to using run-at-time
;;                                           and fixed kill-ring clobber
;;   0.5.1  1/16/96  rhodes@media.mit.edu    update recent-keys in remem-buffer
;;                                           and remem
;;   0.5.2  1/23/96  rhodes@media.mit.edu    Make generic/easily customizable
;;   0.5.3  1/24/96  foner@media.mit.edu     User-options & some cleanup.
;;   0.5.4  1/24/96  rhodes@media.mit.edu    Made it easier to call dummy
;;                                           wrappers around SMART (for
;;                                           SUN's)
;;   0.5.5  1/25/96  rhodes@media.mit.edu    Work with both mail & text 
;;   0.5.6  1/25/96  rhodes@media.mit.edu    Customizable update time &
;;                                           scope range
;;   0.5.7  1/26/96  rhodes@media.mit.edu    Fixed dumb bug where prog-
;;                                           dir got dirname twice
;;   0.5.8  1/25/96  rhodes@media.mit.edu    Removed the RMAIL hook
;;                                           (it's busted -- need to 
;;                                            make a new better one)
;;   0.9    3/28/96  cabbage@mit.edu         Made this work with savant 
;;                                           instead of smart, fixed a bug in 
;;                                           remem-relevant-insertion-filter
;;                                           which caused garbled query
;;                                           responses, many other small
;;                                           changes.
;;   0.91   4/19/96  cabbage@mit.edu         Reorganized code, threw out
;;                                           old unused code, made comments,
;;                                           added messages and made savant
;;                                           processes die without query
;;   0.92   4/26/96  cabbage@mit.edu         Excised yet more cancerous
;;                                           code, mostly destroying anything
;;                                           ever involved with the scratch
;;                                           buffers.  Also made it not send
;;                                           queries when it would be stupid 
;;                                           to do so.  The stage is now set
;;                                           for world domination, muhahahaha!
;;   0.93   5/8/96   cabbage@mit.edu         Fixed a bug in remem
;;   0.94   6/21/96  cabbage@mit.edu         Processes now will not start if 
;;                                           given no database or no lines.  
;;                                           Good for those short on memory.
;;                                           Also made summary window sticky.
;;   1.01   9/24/96  cabbage@mit.edu         Changed variables remem-relevant-*
;;                                           to remem-*
;;   1.1    10/17/96 cabbage@mit.edu         Added an rmail hook so mail 
;;                                           header lines aren't sent to
;;                                           savant.
;;   1.11   11/18/96 rhodes@media.mit.edu    Added a mail-mode check so
;;                                           header lines aren't sent there
;;                                           either
;;   1.12   11/25/96 rhodes@media.mit.edu    sped up remem-double-newline and 
;;                                           remem-split-lines
;;   1.13   2/6/97   rhodes@media.mit.edu    changed to use "retrieve" instead
;;                                           of "savant"
;;   1.14   2/16/97  rhodes@media.mit.edu    added "debug" variable
;;   1.15   3/30/97  rhodes@media.mit.edu    fixed duplicate title code 
;;   1.16   4/7/97   rhodes@media.mit.edu    no longer uses suggestions for
;;                                           the buffer you're in
;;   1.22   6/3/97   rhodes@media.mit.edu    Changed release number to match
;;                                           Savant's version number
;;          6/16/97  cabbage@media.mit.edu   Changed executable "retrieve" to
;;                                           "ra-retrieve" to avoid ambiguity; 
;;                                           also defined remem-test2 in 
;;                                           remem-discard-redundant-suggestions-internal 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;  


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; USER CUSTOMIZATIONS

;; Customizations for the relevancy program (currently savant)
;; these *must* be customized appropriately

(defvar remem-prog-dir ""
  "*Directory where savant lives (fully expanded).")

(defvar remem-database-dir ""
  "*Directory where savant's databases are kept (fully expanded).")

(defvar remem-display-scope-databases '(nil nil nil)
  "*database of scope 1, 2, and 3, respectively.")
; The idea is to have a database directory (e.g. ~cabbage/Database)
; and under this, directories called mail, text, etc.


;; Other customizations

(defvar remem-debug nil
  "Turns on extra debug information.")

(defvar remem-display-scope-number-lines '(1 1 1)
  "*Number of display lines for scope 1, 2, and 3, respectively.")

(defvar remem-display-scope-update-times '(10 10 10)
  "*Time between updates for scope 1, 2, and 3, respectively.")

(defvar remem-display-scope-range '(100 100 100)
  "*Words looked at for scope 1, 2, and 3, respectively.")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; internal variables - warning: no user serviceable parts!

;; if remem-use-full-display-area is t, use entire display area 
;; rather than just scope area
(defvar remem-use-full-display-area nil)
(defvar remem-display-buffer-height
  "height of the display buffer")

(defvar remem-running nil)		; t if currently running

(defvar remem-title-mappings
  '((1 . "") (2 . "") (3 . "") (4 . "") (5 . "") (6 . "") (7 . "") (8 . "") (9 . ""))
  "The doc titles shown on display lines 1-9")

(defvar remem-docnum-mappings
  '((1 . -1) (2 . -1) (3 . -1) (4 . -1) (5 . -1) (6 . -1) (7 . -1) (8 . -1) (9 . -1))
  "The doc numbers (stored in savant) shown on display lines 1-9")

(defvar remem-process-mappings
  '((1 . nil) (2 . nil) (3 . nil) (4 . nil) (5 . nil) 
    (6 . nil) (7 . nil) (8 . nil) (9 . nil))
  "The processes associated with display lines 1-9")

(defvar remem-document-mappings
  '((1 . 0) (2 . 0) (3 . 0) (4 . 0) (5 . 0) (6 . 0) (7 . 0) (8 . 0) (9 . 0))
  "The record to retrieve associated with lines 1-9")

(defvar remem-display-scope-start-lines [0 0 0])
(defvar remem-display-scope-end-lines [0 0 0])
(defvar remem-last-recent-keys [nil nil nil])
(defvar remem-query-results [nil nil nil])
(defvar remem-waiting-for-query-return-vector [0 0 0])
(defvar remem-waiting-for-query-return-timeout 10)

(defvar timer-1 nil) ; timers for the processes
(defvar timer-2 nil)
(defvar timer-3 nil)
(defvar remem-proc-1 nil) ; the processes finding relevant files 
(defvar remem-proc-2 nil)
(defvar remem-proc-3 nil)
(defvar remem-active-1 nil) ; so we know which processes are to be started
(defvar remem-active-2 nil)
(defvar remem-active-3 nil)

(defvar remem-document-buffer-name nil) ; "*remem-document-output: n"
(defvar remem-old-C-x1 nil) ; holds function bound to C-x-1
(defvar remem-mail-msg-start nil) ; beginning or real message in rmail
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; some useful utilities

(defun remem-cadr (l) (car (cdr l)))
(defun remem-caddr (l) (car (cdr (cdr l))))
(defun remem-rassoc-title (item list)
  (cond ((not list) 
	 nil)
	((remem-title-eq
	  (cdr (car list)) item)
	 (car list))
	(t (remem-rassoc-title item (cdr list)))))

(defun remem-waiting-for-query-return (proc-num)
  (cond ((eq (aref remem-waiting-for-query-return-vector (- proc-num 1))
	     0)
	 nil)
	(t t)))

(defun remem-set-waiting-for-query-return (proc-num value)
;  (cond ((eq value 0) (message "rswfqr: 0"))
;	(t (message "rswfqr: other")))
  (aset remem-waiting-for-query-return-vector (- proc-num 1) 
	value))

(defun remem-decrement-waiting-for-query-return-timeout (proc-num)
  (let ((qtime (aref remem-waiting-for-query-return-vector (- proc-num 1))))
    (cond ((> qtime 0)
	   (aset remem-waiting-for-query-return-vector (- proc-num 1)
		 (- qtime 1)))
	  (t (aset remem-waiting-for-query-return-vector (- proc-num 1)
		   0)))))


;(defun remem-rassoc-title (item list)
;  (cond ((not list) 
;	 nil)
;	((equal (cdr (car list)) item)
;	 (car list))
;	(t (remem-rassoc item (cdr list)))))

(defun remem-remove-member (item list)
  (cond ((not list)
	 nil)
	((equal (car list) item)
	 (cdr list))
	(t (cons (car list) (remem-remove-member item (cdr list))))))

(defun remem-member-title (item list)
  "Return t if title is in list"
; (setq remem-temp item)
  (cond ((equal list '()) nil)
	((remem-title-eq item (car list)) t)
	(t (remem-member-title item (cdr list)))))

(defun remem-member-title-in-mappings (item list)
  "Return t if title is in remem-title-mappings"
  (cond ((equal list '()) nil)
	((remem-title-eq item (cdr (car list))) t)
	(t (remem-member-title-in-mappings item (cdr list)))))

(defun remem-delete-line (&optional arg)
  "Delete the rest of the current line; if no nonblanks there, delete thru newline.
With prefix argument, delete that many lines from point.
Negative arguments delete lines backward.

When calling from a program, nil means \"no arg\",
a number counts as a prefix arg.

If `kill-whole-line' is non-nil, then delete the whole line
when given no argument at the beginning of a line."
  (interactive "P")
  (delete-region 
   ;; FOLLOWING COMMENT FROM "KILL-LINE"
   ;; It is better to move point to the other end of the kill
   ;; before killing.  That way, in a read-only buffer, point
   ;; moves across the text that is copied to the kill ring.
   ;; The choice has no effect on undo now that undo records
   ;; the value of point from before the command was run.
   (point) (progn
	     (if arg
		 (forward-line (prefix-numeric-value arg))
	       (if (eobp)
		   (signal 'end-of-buffer nil))
	       (if (or (looking-at "[ \t]*$") (and kill-whole-line (bolp)))
		   (forward-line 1)
		 (end-of-line)))
		 (point))))

(defun remem-last-several-words (count)
  "String from count words back to current point.  (word = 5 chars)"
  (save-excursion
    (let ((end) (beg))
      ; place start of sample count words before current position
      ; or as far back as possible
      (setq beg (- (point) (* 5 count)))
      (if (string= major-mode "rmail-mode")
	  ; in rmail mode, use the variable pointing to start of message
	  (if (< beg remem-mail-msg-start) 
	      (setq beg remem-mail-msg-start))
	; in other modes, just use point-min
	(if (< beg (point-min)) 
	    (setq beg (point-min))))
      ; and place end of sample count words ahead of beginning
      (setq end (+ beg (* 5 count)))
      (if (> end (point-max)) 
	  (setq end (point-max)))
      (buffer-substring beg end))))

;(defun remem-double-newline (string)
;  (let ((work-string string)
;	(dn nil))
;    (while (not (= 1 (length work-string)))
;      (cond ((and (= (aref work-string 0) 10)
;		  (= (aref work-string 1) 10))
;	     (setq dn t)
;	     (setq work-string (substring work-string 0 1)))
;	    (t
;	     (setq work-string (substring work-string 1)))))
;    dn))

(defun remem-double-newline (string)
  (let* ((dn nil)
	 (len (- (length string) 1))
	 (i len))
    (while (> i 0)
      (cond ((not (= (aref string (- i 1)) 10))
	     (setq i (- i 2)))
	    ((= (aref string i) 10)
	     (setq dn t)
	     (setq i 0))
	    (t
	     (setq i (- i 1)))))
    dn))
      
;(defun remem-split-lines (string)
;  (let ((in-string string)
;	(out-string ""))
;    (while (not (or (= 0 (length in-string))
;		    (= 10 (aref in-string 0)))) 
;      ; while not end of line or end of string
;      (setq out-string (concat out-string 
;			       (substring in-string 0 1)))
;      (setq in-string (substring in-string 1)))
;    (cond ((= 0 (length in-string))
;	   (cond ((= 0 (length out-string))
;		  '())
;		 (t
;		  (list out-string))))
;	  (t
;	   (cons out-string
;		 (remem-split-lines (substring in-string 1)))))))

(defun remem-split-lines (string)
  "Split a string up into a list of strings, one line per element"
  (let ((i 0)
	(len (length string)))
    (while (not (or (>= i len)
		    (= 10 (aref string i))))
      ; while not end of line or end of string
      (setq i (+ i 1)))
    (cond ((= 0 len)
	   '())
	  ((= i len)
	   (list string))
	  (t
	   (cons (substring string 0 i)
		 (remem-split-lines (substring string (+ i 1))))))))

(defun remem-delete-other-windows (&rest args)
  "Replacement for delete-other-windows that won't delete the 
*remem-buffer* window."
  (interactive)
  (let ((sw (selected-window))
	(rw (get-buffer-window "*remem-display*"))
	(nw (next-window (selected-window) 1))
	w)
    (if (eq sw rw) 
	(progn (other-window 1)
	       (setq sw (selected-window))
	       (setq nw (next-window (selected-window) 1))))
    (while (not (eq nw sw))
      (setq w nw)
      (setq nw (next-window nw 1))
      (or (eq w rw)
	  (eq w sw)
	  (delete-window w))))
  (message "Use C-c r r to quit the Remembrance Agent"))

(defun remem-mail-hook ()
  (save-excursion
    (goto-char (point-min))
    (setq remem-mail-msg-start (search-forward "\n\n"))))

(add-hook 'rmail-show-message-hook 'remem-mail-hook)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Creation and setup stuff

(global-set-key "\C-crr" 'remem-toggle)

(defun remem-toggle ()
  "If remem is running, kill it.  If not, start it"
  (interactive)
  (cond (remem-running 
	 (remem-kill))
	((not remem-running)
	 (remem-start))))

(defun remem-start ()
  "Start doing remembrance on all buffers."
  (interactive)

  ;; Set variables based on user customization variables
  (setq remem-active-1 
	(and (car remem-display-scope-databases)
	     (> (car remem-display-scope-number-lines) 0)))
  (setq remem-active-2 
	(and (remem-cadr remem-display-scope-databases)
	     (> (remem-cadr remem-display-scope-number-lines) 0)))
  (setq remem-active-3 
	(and (remem-caddr remem-display-scope-databases)
	     (> (remem-caddr remem-display-scope-number-lines) 0)))

  ;; Start displaying and set timers so it'll update
  (remem-create-display)
  (remem-proc-start)		; Start the relevancy program
  (setq remem-running t)

  (if remem-active-1
      (setq timer-1 
	    (run-at-time 1 (car remem-display-scope-update-times) 
			 'remem-around-point 1 
			 (car remem-display-scope-range))))
  (if remem-active-2
      (setq timer-2 
	    (run-at-time 2 (remem-cadr remem-display-scope-update-times)
			 'remem-around-point 2
			 (remem-cadr remem-display-scope-range))))
  (if remem-active-3
      (setq timer-3 
	    (run-at-time 4 (remem-caddr remem-display-scope-update-times) 
			 'remem-around-point 3
			 (remem-caddr remem-display-scope-range))))

  ;; remap keys
  (global-set-key "\C-c1" 'remem-display-line-1)
  (global-set-key "\C-c2" 'remem-display-line-2)
  (global-set-key "\C-c3" 'remem-display-line-3)
  (global-set-key "\C-c4" 'remem-display-line-4)
  (global-set-key "\C-c5" 'remem-display-line-5)
  (global-set-key "\C-c6" 'remem-display-line-6)
  (global-set-key "\C-c7" 'remem-display-line-7)
  (global-set-key "\C-c8" 'remem-display-line-8)
  (global-set-key "\C-c9" 'remem-display-line-9)
  ; this is to make the remem buffer sticky
  (setq remem-old-C-x1 (global-key-binding "\C-x1"))
  (global-set-key "\C-x1" 'remem-delete-other-windows)
  (message "remembrance agent started."))

(defun remem-create-display ()  
  "Make the remem display buffer."
  (interactive)

  (let ((temp-lines-1 (car remem-display-scope-number-lines))
	(temp-lines-2 (remem-cadr remem-display-scope-number-lines))
	(temp-lines-3 (remem-caddr remem-display-scope-number-lines)))

    (if (not remem-active-1) (setq temp-lines-1 0))
    (if (not remem-active-2) (setq temp-lines-2 0))
    (if (not remem-active-3) (setq temp-lines-3 0))

    (setq remem-display-buffer-height
	  (+ 1 temp-lines-1 temp-lines-2 temp-lines-3))

    (aset remem-display-scope-start-lines 0 1)
    (aset remem-display-scope-start-lines 1 (+ 1 temp-lines-1))
    (aset remem-display-scope-start-lines 2 
	  (+ (aref remem-display-scope-start-lines 1) temp-lines-2))

    (aset remem-display-scope-end-lines 0 temp-lines-1)
    (aset remem-display-scope-end-lines 1 (+ temp-lines-1 temp-lines-2))
    (aset remem-display-scope-end-lines 2 
	  (+ (aref remem-display-scope-start-lines 1) temp-lines-2))

    ;; Call the function to make the buffer
    (remem-show-display)))		; Creates if needed
    
(defun remem-show-display ()
  "Bring up the display buffer and window.  Creates them if necessary"
  (interactive) 
  (cond ((not (get-buffer-window "*remem-display*"))     
					; If not currently displayed
	 (let ((w (window-at 1 (- (frame-height) 2)))
	       (display-buf (get-buffer "*remem-display*"))
	       (buf (current-buffer)))

	   ;; Make the new window
	   (if (not (< (window-height w) 8))
	       (setq w (split-window w)))
	   (select-window w)
	   (enlarge-window (- remem-display-buffer-height (window-height)))

	   ;; Make new buffer if doesn't exist
	   (cond (display-buf (set-buffer display-buf))
		 ((not display-buf) 
		  (set-buffer (get-buffer-create "*remem-display*"))
		  (let ((line 1))
		    (while (<= line (aref remem-display-scope-end-lines 2))
		      (insert-string "Reading database...\n")
		      (setq line (+ 1 line))))))
					; So can replace them later

	   (switch-to-buffer "*remem-display*")
	   (set-window-buffer w (current-buffer))

	   ;; Take care of the details
	   (set-window-start (selected-window) (point-min)) ; Display from top
	   (setq mode-name "*remem-display*")
	   (setq major-mode 'remem-display-mode)
	   (remem-set-mode-line)
	   (set-window-dedicated-p w t)	        ; So it doesn't get clobbered
	   (setq buffer-read-only t)		; Make it read only
	   (pop-to-buffer buf)			; Switch back to orig buffer
	   ))))

(defun remem-set-mode-line ()
  "Sets the mode line"
  (setq mode-line-format "  *remem-display*    ")
  (save-excursion (set-buffer (other-buffer)))
  (set-buffer-modified-p (buffer-modified-p))
  (sit-for 0))

;; Start up the retrieval processes, only if each has a database
;; specified and more than zero lines
(defun remem-proc-start ()	
  (if remem-active-1 (remem-proc-start-1))
  (if remem-active-2 (remem-proc-start-2))
  (if remem-active-3 (remem-proc-start-3)))

(defun remem-proc-start-1 ()	; start process #1
  (setq remem-proc-1
	(start-process "remem-inter-1" 
		       nil  ;; No buffer for this process
		       (concat remem-prog-dir "/ra-retrieve")
		       (concat remem-database-dir "/" 
			       (car remem-display-scope-databases))
		       ))
  (aset remem-query-results 0 nil)
  (process-kill-without-query remem-proc-1)
  (let ((line 1))
    (while (<= line (aref remem-display-scope-end-lines 0))
      (rplacd (assoc line remem-process-mappings) remem-proc-1)
      (setq line (+ 1 line)))))

(defun remem-proc-start-2 ()	; start process #2
  (setq remem-proc-2
	(start-process "remem-inter-2" 
		       nil  ;; No buffer for this process
		       (concat remem-prog-dir "/ra-retrieve")
		       (concat remem-database-dir "/" 
			       (remem-cadr remem-display-scope-databases))
		       ))
  (aset remem-query-results 1 nil)
  (process-kill-without-query remem-proc-2)
  (let ((line (aref remem-display-scope-start-lines 1)))
    (while (<= line (aref remem-display-scope-end-lines 1))
      (rplacd (assoc line remem-process-mappings) remem-proc-2)
      (setq line (+ 1 line)))))

(defun remem-proc-start-3 ()	; start process #3
  (setq remem-proc-3
	(start-process "remem-inter-3" 
		       nil  ;; No buffer for this process
		       (concat remem-prog-dir "/ra-retrieve")
		       (concat remem-database-dir "/" 
			       (remem-caddr remem-display-scope-databases))
		       ))
  (aset remem-query-results 2 nil)
  (process-kill-without-query remem-proc-3)
  (let ((line (aref remem-display-scope-start-lines 2)))
    (while (<= line (aref remem-display-scope-end-lines 2))
      (rplacd (assoc line remem-process-mappings) remem-proc-3)
      (setq line (+ 1 line)))))

  
(defun remem-kill (&optional arg)
  "kills the remembrance buffer, window, and processes."
  (interactive "P")
  (if timer-1 (cancel-timer timer-1))
  (if timer-2 (cancel-timer timer-2))
  (if timer-3 (cancel-timer timer-3))
  (global-unset-key "\C-c1")		; Unsets the key-map
  (global-unset-key "\C-c2")		; Unsets the key-map
  (global-unset-key "\C-c3")		; Unsets the key-map
  (global-unset-key "\C-c4")		; Unsets the key-map
  (global-unset-key "\C-c5")		; Unsets the key-map
  (global-unset-key "\C-c6")		; Unsets the key-map
  (global-unset-key "\C-c7")		; Unsets the key-map
  (global-unset-key "\C-c8")		; Unsets the key-map
  (global-unset-key "\C-c9")		; Unsets the key-map
  (global-set-key "\C-x1" remem-old-C-x1)
  (setq remem-use-full-display-area nil)
  (setq remem-running nil)
  (cond ((get-buffer-window "*remem-display*")
	 (delete-window (get-buffer-window "*remem-display*"))
	 (kill-buffer "*remem-display*")))
  (remem-proc-stop)	; Stop the process running in the buffer
  (message "remembrance agent stopped."))

;; Stop the programs getting relevant files, if they exist
(defun remem-proc-stop ()	
  (remem-set-waiting-for-query-return 1 0)
  (remem-set-waiting-for-query-return 2 0)
  (remem-set-waiting-for-query-return 3 0)
  (cond (remem-active-1
	 (process-send-string (process-name remem-proc-1) "quit\n")
	 (delete-process (process-name remem-proc-1))))
  (cond (remem-active-2
	 (process-send-string (process-name remem-proc-2) "quit\n")
	 (delete-process (process-name remem-proc-2))))
  (cond (remem-active-3
	 (process-send-string (process-name remem-proc-3) "quit\n")
	 (delete-process (process-name remem-proc-3)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; remembrance functions

(defun remem (query)
  (interactive "sRemembrance on string: ")
  (setq remem-use-full-display-area t)	
  (aset remem-last-recent-keys 0 (recent-keys))
  (aset remem-last-recent-keys 1 (recent-keys))
  (aset remem-last-recent-keys 2 (recent-keys))
  (remem-set-waiting-for-query-return 1 remem-waiting-for-query-return-timeout)
  (process-send-string (process-name remem-proc-1)
		       (concat "keyquery\n" query "\n.\n")))

(defun remem-buffer ()
  "*remem on the contents of entire buffer"
  (interactive)
  (save-excursion
    (goto-line (+ (point-min) 1000))
    (aset remem-last-recent-keys 0 (recent-keys))
    (aset remem-last-recent-keys 1 (recent-keys))
    (aset remem-last-recent-keys 2 (recent-keys))
    (remem (buffer-substring (point-min) (point)))))

(defun remem-around-point (proc-number range)
  "remem based on range words previous to point"
  (remem-decrement-waiting-for-query-return-timeout proc-number)
  (let ((proc nil))
    (cond ((and remem-running  ; only remem if remem-running
		(not (remem-waiting-for-query-return proc-number))
					; don't remem in these buffers 
		(or (< (length (buffer-name (current-buffer))) 25)
		    (not (string= (substring 
				   (buffer-name (current-buffer)) 0 24)
				"*remem-document-output: ")))
		(not (and (> (length (buffer-name (current-buffer))) 9)
			  (string= 
			   (substring (buffer-name (current-buffer)) 0 9)
			   " *Minibuf")))
		(not (equal (aref remem-last-recent-keys (- proc-number 1))
			    (recent-keys))) ; don't remem if no keystrokes
		(not (aref remem-query-results (- proc-number 1))))
	   
	   ; Set some stuff
	   (aset remem-last-recent-keys (- proc-number 1) (recent-keys))
	   (setq remem-use-full-display-area nil)
	   (cond ((or (not proc-number) (equal proc-number 1))
		  (setq proc-number 1)
		  (setq proc remem-proc-1))
		 ((equal proc-number 2)
		  (setq proc remem-proc-2))
		 ((equal proc-number 3)
		  (setq proc remem-proc-3)))
	   (remem-set-waiting-for-query-return proc-number 
					       remem-waiting-for-query-return-timeout)
	   (set-process-filter
	    proc (symbol-function 'remem-query-filter))

	   ; and send the query
	   (process-send-string 
	    (process-name proc)
	    (concat "query\n" (remem-last-several-words range) "\n.\n"))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; functions that deal with savant's output

(defun remem-query-filter (proc string)
  (let ((proc-num nil))
    (cond ((equal proc remem-proc-1)
	   (setq proc-num 0))
	  ((equal proc remem-proc-2)
	   (setq proc-num 1))
	  ((equal proc remem-proc-3)
	   (setq proc-num 2)))

    ;; tack this string onto the rest of the results
    (aset remem-query-results proc-num
	  (concat (aref remem-query-results proc-num)
		  string))
    ;; if we've got them all, go to work. 
    (cond ((remem-double-newline (aref remem-query-results proc-num))
	   (remem-resolve-query-results proc-num)
	   (remem-set-waiting-for-query-return (+ proc-num 1) 0)
	   (aset remem-query-results proc-num nil))
	  ((string= "\n" 
		    (aref remem-query-results proc-num)) ;no results
;	   (cond (remem-debug (setq remem-hit t)))
	   (aset remem-query-results proc-num 
		 "\n\n\n\n\n\n\n\n\n\n\n")
	   (remem-resolve-query-results proc-num)
	   (remem-set-waiting-for-query-return (+ proc-num 1) 0)
	   (aset remem-query-results proc-num nil)))))
   

(defun remem-string-index-of (string n char)
  "return the index of the nth character that matches char in string, or nil"
  (let ((len (- (length string) 1))
	(start 0)
	(count 0)
	(index nil)
	(i 0))
    (while (< i len)
      (cond ((= (aref string i) char)
	     (setq count (+ count 1))
	     (cond ((= count n)
		    (setq index i)
		    (setq i len)))))
      (setq i (+ i 1)))
    index))
  
(defun remem-string-in-brackets (string mchar)
  "return the string surrounded by the MCHAR character, or nil if none"
  (let* ((len (- (length string) 1))
	 (start 0)
	 (end 0)
	 (inmark nil)
	 (i 0))
    (while (< i len)
      (cond ((= (aref string i) mchar)
	     (cond ((not inmark)
		    (setq start i)
		    (setq inmark t))
		   (inmark
		    (setq end i)
		    (setq i (+ len 1))
		    (setq inmark nil)))))
      (setq i (+ i 1)))
;   (message "start = %s, end = %s, len = %s, i = %s" start end len i)
    (cond ((< 0 end) 
	   (substring string (+ start 1) end))
	  (t nil))
))
  
(defun remem-get-docnum-from-line (string)
  (let ((docnum-str (remem-string-in-brackets string ?|)))
    (cond (docnum-str (string-to-int docnum-str))
	  (t 0))))

(defun remem-resolve-query-results (proc-num)
  (let ((current-line nil)
	(end-line nil)
	(result-lines 
	 (remem-discard-redundant-suggestions 
	  (remem-split-lines (aref remem-query-results proc-num))
	  (remem-remove-title-mappings proc-num)
;;;	  remem-title-mappings
	  )))
    (cond (remem-use-full-display-area
	   (setq current-line 1) 
	   (setq end-line (aref remem-display-scope-end-lines 2)))
	  (t
	   (setq current-line (aref remem-display-scope-start-lines proc-num))
	   (setq end-line (aref remem-display-scope-end-lines proc-num))))

    (while (<= current-line end-line)
      (cond (result-lines
	     (remem-write-string-to-line 
	      current-line (car result-lines) "*remem-display*")
	     (rplacd (assoc current-line remem-document-mappings) 
		     (string-to-int (substring (car result-lines) 0 4)))
	     (rplacd (assoc current-line remem-title-mappings) 
		     (substring (car result-lines) 8))
	     (rplacd (assoc current-line remem-docnum-mappings) 
		     (remem-get-docnum-from-line (car result-lines)))
	     (setq result-lines (cdr result-lines)))
	    (t
;	     (setq remem-hit current-line)
	     (remem-write-string-to-line 
	      current-line "     no suggestion" "*remem-display*")
	     (rplacd (assoc current-line remem-document-mappings) -1)
	     (rplacd (assoc current-line remem-title-mappings) "")))
      (setq current-line (+ 1 current-line)))))

(defun remem-title-filename (titleline)
  (substring titleline (+ 2 (remem-string-index-of titleline 4 ?|))))

(defun remem-title-string (titleline)
  (substring titleline (+ 1 (remem-string-index-of titleline 2 ?|))))

(defun remem-title-eq (title1 title2)
  (cond ((string= title1 title2) t)
	((string= title1 "") nil)
	((string= title2 "") nil)
	((string= (remem-title-string title1)
		  (remem-title-string title2))
	 t)))

;(defun remem-discard-redundant-suggestions (suggestions title-mappings)
;  (cond ((= 0 (length (car suggestions))) ; last line from savant is empty
;	 '())
;	((remem-rassoc-title
;	  (car suggestions)   ; avoids the "nnn 0.xx"
;	  title-mappings
;	  )
;	 (remem-discard-redundant-suggestions 
;	  (cdr suggestions) title-mappings))
;	(t
;	 (cons (car suggestions)
;	       (remem-discard-redundant-suggestions 
;		(cdr suggestions) title-mappings))))) 

(defun remem-remove-title-mappings (proc-num)
; We only want to discard suggestions duplicating those from
; other scopes, so we remove the mappings belonging to this scope
  (let ((mappings remem-title-mappings)
	(current-line (aref remem-display-scope-start-lines proc-num))
	(end-line (aref remem-display-scope-end-lines proc-num)))
    (while (<= current-line end-line)
      (setq mappings 
	    (remem-remove-member (assoc current-line mappings) mappings))
      (setq current-line (+ 1 current-line)))
    mappings))

(defun remem-discard-redundant-suggestions-internal 
  (keepers rest remem-partial-title-mappings)
  ;; A redundant suggestion has an identical line as a previous one,
  ;; or is the same file as the current buffer.
  (let ((remem-test2 nil))
    (setq remem-test2 (buffer-name (current-buffer)))
    (cond ((equal rest '()) keepers)
	  ((or (string= (car rest) "")    ;; discard it
	       (string= (buffer-name (current-buffer)) 
			(remem-title-filename (car rest)))
	       (remem-member-title (car rest) keepers)
	       (remem-member-title-in-mappings 
		(car rest) remem-partial-title-mappings)
	       )
	   (remem-discard-redundant-suggestions-internal 
	    keepers (cdr rest) remem-partial-title-mappings))
	  (t (remem-discard-redundant-suggestions-internal   ;; keep it
	      (append keepers (list (car rest)))
	      (cdr rest)
	      remem-partial-title-mappings)))))

(defun remem-discard-redundant-suggestions (suggestions 
					    remem-partial-title-mappings)
  (remem-discard-redundant-suggestions-internal '() suggestions
						remem-partial-title-mappings))


(defun remem-write-string-to-line (lineno suggestion buf)
  "Replace line <lineno> of buffer <buf> with suggestion <suggestion>"
  (let* ((width (window-width (get-buffer-window buf)))
	 (display-line 
	  (cond ((or remem-debug 
		     (string= suggestion "     no suggestion"))
		 (concat (int-to-string lineno) ": "
			 (substring suggestion 4)))
		(t (concat (int-to-string lineno) ": "
			   (substring suggestion 4 9)
			   (substring suggestion 
				      (+ 1 (remem-string-index-of 
					    suggestion 2 ?|))))))))
;    (if (not (get-buffer "*remem-display*"))
;	(remem-create-display)
    (remem-show-display)
    (save-excursion
      (set-buffer buf)
      (goto-line lineno)
      (setq inhibit-read-only t)
      (remem-delete-line)
      (cond ((> (length display-line) width)
	     (insert (substring display-line 0 (- width 1))))
	    (t
	     (insert display-line)))
      (setq inhibit-read-only nil))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Document display functions

(defun remem-display-filter (proc string)
  (save-excursion
    (set-buffer remem-document-buffer-name)
    (goto-char (process-mark proc))
    (insert string)
    (set-marker (process-mark proc) (point))))  

(defun remem-display-line (&optional lineno)
  "Display the output for the relevant document displayed in the given line"
  (let ((proc (cdr (assoc lineno remem-process-mappings)))
	(retrieve-command "retrieve ")
	(docnum (cdr (assoc lineno remem-docnum-mappings)))
;	(retrieve-command "print ")
;	(docnum (cdr (assoc lineno remem-document-mappings)))
	doc-pos)
    (message "Retrieving docnum %d" docnum)
    (cond ((not (<= docnum 0))
	   (setq remem-document-buffer-name 
		 (concat "*remem-document-output: " 
			 (int-to-string lineno) "*"))
	   (switch-to-buffer remem-document-buffer-name)
	   (erase-buffer)

	   (set-marker (process-mark proc) (point))
	   (set-process-filter 
	    proc (symbol-function 'remem-display-filter))
	   (process-send-string (process-name proc)
				(concat retrieve-command
					(int-to-string docnum)
					"\n"))
	   (goto-char (point-min))
	   (while (< (marker-position (process-mark proc)) 22)
	     (sit-for .001))
					; loop until ready
	   (end-of-line)
	   (setq doc-pos (string-to-int (buffer-substring (point-min) (point))))
	   (print doc-pos (get-buffer "*log*"))
	   (print (point-min) (get-buffer "*log*"))
	   (forward-char)
	   (delete-region (point-min) (point))
	   (while (< (marker-position (process-mark proc)) doc-pos)
	     (sit-for .001))
	   (goto-char doc-pos)
	   (beginning-of-line)
	   (recenter 0)))))


(defun remem-display-line-1 (&optional args) 
  (interactive "P")
  (remem-display-line 1))

(defun remem-display-line-2 (&optional args) 
  (interactive "P")
  (remem-display-line 2))

(defun remem-display-line-3 (&optional args) 
  (interactive "P")
  (remem-display-line 3))

(defun remem-display-line-4 (&optional args) 
  (interactive "P")
  (remem-display-line 4))

(defun remem-display-line-5 (&optional args) 
  (interactive "P")
  (remem-display-line 5))

(defun remem-display-line-6 (&optional args) 
  (interactive "P")
  (remem-display-line 6))

(defun remem-display-line-7 (&optional args) 
  (interactive "P")
  (remem-display-line 7))

(defun remem-display-line-8 (&optional args) 
  (interactive "P")
  (remem-display-line 8))

(defun remem-display-line-9 (&optional args) 
  (interactive "P")
  (remem-display-line 9))


