;;; -*-  Mode: Lisp; Package: Maxima; Syntax: Common-Lisp; Base: 10 -*- ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;     The data in this file contains enhancments.                    ;;;;;
;;;                                                                    ;;;;;
;;;  Copyright (c) 1984,1987 by William Schelter,University of Texas   ;;;;;
;;;     All rights reserved                                            ;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(in-package "MAXIMA")

(defconstant *doc-start* (code-char 31))

(defun $example (item &optional (file
				 (merge-pathnames "manual.demo"
						  $describe_documentation))
		      )
  (and (symbolp file) (setq file (stripdollar file)))
  (or (probe-file file)
      (return-from $example "Please supply a file name as the second arg"))
  (and (symbolp item) (setq item (symbol-name item))
       (setq item (subseq item 1))
       (with-open-file
	(st file)
	(sloop with tem
	       while (setq tem (read-char st nil))
	       do
	       (cond ((and (eql tem #\&)
			   (eql (setq tem (read-char st nil)) #\&))
		      (cond
		       ((and (symbolp (setq tem (read st nil)))
			     (string-search item (symbol-name tem)))
			(format t "~%Examples for ~a :~%" tem)
			;; This code fulls maxima into thinking that it just
			;; started, by resetting the values of the special
			;; variables $labels and $linenum to their initial
			;; values. They will be reset just after $example
			;; is done. The d-labels will also not be disturbed
			;; by calling example.
		        (progv 
		         ;; Protect the user labels and variables 
			 ;; from being voerwritten by creating a new 
			 ;; binding.
		         (append '($linenum
				   $labels
				   $values)
				 (cdr $labels)
				 (cdr $values))
			 (list 1
			       '((mlist simp))
			       '((mlist simp)))
		         ;; Run the example.
			 (unwind-protect
			     (sloop until
				    (or (null (setq tem (peek-char nil st nil)))
					(eql tem #\&))
				    for expr = (mread st nil)
				    do
				    (let ($display2d) (displa (third  expr)))
				    ;; Make the c-label and d-label.
				    (let ((c-label (makelabel $inchar))
					  (d-label (makelabel $outchar)))
				      ;; Set the c-label to the input
				      ;; expression.
				      (set c-label (third expr))
				      (format t "<~d>==>" $linenum)
				      (displa (setq $% (meval* (third  expr))))
				      (set d-label $%)
				      (incf $linenum)
				      ))
			   ;; Clean-up form, which will be
			   ;; evaluated even if an error occurs,
			   ;; because of unwind-protect.  Kill
			   ;; all labels and values used the
			   ;; example. This is harmless, because
			   ;; the local binding established with
			   ;; progv is in effect.
			   (mapc #'makunbound
				 (append
				  (cdr $labels)
				  (cdr $values))
				 )))))))))))

  
(eval-when (compile eval load)

(defvar *pdp-describe* t "The file positioning is not so good for
 pdp type servers, because of character translation so we index more carefully.
 This should be ok for any file system")

;;this should really be in "macsyma-source:macsyma;macsym.doc" but I don't
;;want to copy it for here
     

(defmvar $describe_documentation #+lispm
  "maxima-documentation:maxima;macsym.doc"
  #-lispm "/usr/public/maxima/doc/macsym.doc"
  "This is the name of the main macsyma documentation file")



;;keep documentation on logical host maxima-documentation
(defmvar $all_macsyma_documentation
  (list'(mlist)
       $describe_documentation
       ))


;(defstruct (describe-index :named (:conc-name index-))
; documentation-file
;  entries)


(defstruct (describe-index (:type list) :named (:conc-name index-))
  documentation-file
  entries)

(defun make-entry (&key string file-pointer)
  (cons string file-pointer))

(defmacro key-string (x) `(car ,x))

(defmacro key-file-pointer (x) `(cdr , x))

;;The following assumes that :set-pointer will do something reasonable as
;;it does currently under chaos.  When and if we switch to tcp that will create
;;problems.  The reasonable strategy is then to switch to a line number
;;index and use a number of smaller files.  It took 37 seconds to read
;;once through the documentation file using tcp on the r20.

(defmacro read-1-char (st)
  `(let ((tem  (read-byte ,st nil nil)))
     (if tem (code-char tem))))


#+ti 
(defun file-position (a &optional b)
  (if b (send a :set-pointer b) (send a :read-pointer)))

(defun file-options (pathname) pathname
  #-lispm  (list  nil  8)
  #+lispm
   (case (send    (send pathname :host) :system-type)
	    (:lispm (list  nil  8))
	    (:tops-20 (error " can't use any more")(list  nil  7))
	    (t (format t "~%Assuming byte-size 8 for making index") (list  nil  8)  )))

(defun index-file  ( file &aux
			  word (ch 0) cha   fil options ind)
  (setq fil (probe-file file))
  (setq options (file-options fil))
  (with-open-file
      (st file :element-type #+symbolics '(unsigned-byte 8)
	  #-symbolics '(mod 256))
    (sloop while  ch
	   when (eql (setq ch  (read-1-char st)) *doc-start* )
	   do
	   ;;the first character after *doc-start* is used to say what
	   ;;kind of documentation this is.  This is compatible with
	   ;;the gnu doc string files.
	   (read-1-char st)
	   (setq ind (file-position st))
	   (setq word (with-output-to-string
			(strin )
			(sloop  until (zl-MEMBER
					(progn   
					  (setq cha (read-1-char st))
					  )
					'(#\space #\return #\newline nil)
					)
				do
				(tyo cha strin)
				finally  (if cha (unread-char cha st))
				)))
	   and
	   collecting   (make-entry :string
				    (cond (
					   (array-has-fill-pointer-p word)
					   (subseq word 0
						   (length (the string  word))))
					  (t word))
				    :file-pointer ind) into llist
	   finally
	   (print (length llist))
	   (return (make-describe-index
			     :documentation-file
			     (namestring file)
			     :entries  llist)))))

(defun font-number (ch)
  (cond ((char> ch #\*) (f- (char-code ch)#. (char-code #\0)))
	(t 0)))

(defun line-in (stream buffer end-char)
  (let ((tem (or buffer
		 (make-array 70 :fill-pointer 0 :element-type ' #.(array-element-type "abc")
			     :adjustable t
			     ))))
    (sloop with ch until
	  (eql (setq ch (read-1-char stream)) end-char)  
	  do (if (null ch) (loop-finish))
	  (vector-push-extend ch tem))
    tem))



(defun read-item (stream file-pos  &key (up-to   *doc-start*) item (out-stream *standard-output*) &aux (prev-ch) (ch 0) lin)
  ;;have to set pointer to 0 for non lispm server.
  (cond ( *pdp-describe* (file-position stream 0)))
  (file-position stream file-pos)
  (setq lin (line-in stream nil #\newline))
  (cond ((and item  (null (string-search item lin))
	      (y-or-n-p "~%Bad index file.  Try making a new one?"))
	 (set-up-index (pathname stream) :make-new-one t)
	 (error "now start over"))
	(t (format out-stream "~%~A~%" lin)))
  (sloop until (and (eql (setq ch (read-1-char stream))    ;(send stream :tyi))
		  up-to ) (eql prev-ch #\newline))
	 while ch
	do (setq prev-ch ch)
	#-symbolics
	(cond ((char= ch #\)
	       (send stream :set-current-font (setq prev-ch (tyi stream)))))
	(tyo ch out-stream) ; (send out-stream :tyo ch)
	))

(defvar *describe-indices* nil)
;;convention:use the same name for the documentation file as the index-name
(defun add-to-describe-indices (index-name describe-index &aux tem)
  (declare (special *index-path*))
  (setq index-name (string-downcase index-name))
  (and (boundp '*index-path*)
    (setf (index-documentation-file describe-index)
	(alter-pathname *index-path* :name index-name :type "doc")))
  (cond ((setq tem (zl-MEMBER index-name *describe-indices*))
	 (setf (second tem) describe-index))
	(t (push describe-index *describe-indices*)
	   (push index-name *describe-indices*))))

(defun set-up-index (file &key write-file-name make-new-one
			  &aux index-name index tem)
  (setq file (pathname file))
  #+kcl
  (or (probe-file file)
      (and (setq tem (alter-pathname file :directory
				     (append
				      (pathname-directory
				       (pathname si::*system-directory*))
				      (pathname-directory "../doc/"))))
	   (probe-file tem)
	   (setq file tem)
	   ))
  
;  (setq index-name (string-upcase (pathname-name file)))
  (cond ((null write-file-name)
	 (setq write-file-name
	       (format nil "~a~a-index.~a"
		       *describe-index-directory*
		       (pathname-name file) *index-file-type*)))
	(t (setq write-file-name (alter-pathname write-file-name :type *index-file-type*))))
  (cond ((or make-new-one (null  (probe-file write-file-name)))
	 (format t "~%Having to make a new index...")
	 (setq index  (index-file file))
	 (add-to-describe-indices index-name  index)
	 (write-forms-to-file write-file-name 
			      (lisp:LIST (lisp:LIST 'add-to-describe-indices
							index-name
							(lisp:LIST
							  'QUOTE index)))
			      :in-package "MAXIMA" :type *index-file-type*))
	(t
	 (let ((*index-path* write-file-name))
	   (declare (special *index-path*))
	   (load write-file-name)))))




(defun write-forms-to-file
  (file-name forms   &key
	     ( in-package *package*) (type :lisp) &aux #+lispm tem) 
  (cond 
    ((eq type :lisp)
     (with-open-file (st (alter-pathname file-name :type "LISP") :direction :output)
       (format st ";;; - * - Mode:Lisp; Package:~A;Syntax:common-lisp  - * -~%" in-package)
       (format st "~%(in-package \"~a\")~%" in-package)
       (prin1 (cons 'progn forms) st) (pathname st)))
    #+lispm
    ((member type '(:bin :xfasl))
     (si::dump-forms-to-file (setq tem (alter-pathname file-name :type type))
       forms (list :package in-package)) tem)))


(defmvar *index-file-type* :lisp)

(defvar *describe-index-directory* #-lispm "/usr/public/maxima/doc/"
    #+lispm "cl-maxima-object:maxima;")


(defun cl-string (zl-string &aux (leng (length zl-string))  answ)
       (cond ((and  (> leng 0)
	      (not (integerp(aref zl-string 0)))) zl-string)
	     (t (setq answ (make-array leng :element-type ' #.(array-element-type "abc")))
		(sloop for i below leng
		      do (setf (aref answ i) (code-char (aref zl-string i))))
		answ)))

(defun add-main-macsyma-documentation ( &aux  file-name)
	 (sloop for v in (cdr $all_macsyma_documentation)
	       do
	       (setq file-name (pathname v) )
	       (cond ((not (MEMBER (pathname-name file-name)
			       *describe-indices* :test 'equalp))
		      (set-up-index file-name)))))
								       
(defun maxima-union (&rest lists &aux test)
  (setq test (or (second (memq :test lists)) #'eq))
  (sloop for v in lists until (eql v :test)
	with answ = nil
	do (sloop for vv in v do (pushnew vv answ :test test))
 (return answ)))

(defun $describe ( items-to-describe &key editor
				     (index-names 'use-all)
				     &aux items done zl-SOME  all-files)
  (cond ((not (consp items-to-describe))
	 (setq items-to-describe (list items-to-describe))))
  (setq items-to-describe (sloop for v in items-to-describe
				 collecting (string-trim "&$" v)))
  (add-main-macsyma-documentation)
  (setq items (sloop for (name this-ind) on *describe-indices* by 'cddr  
		     when (or (eq index-names 'use-all)
			      (zl-MEMBER name index-names))
		     appending
		     (sloop for u in (index-entries this-ind)
			    when (sloop for v in items-to-describe
					when (string-search v (car u))
					do (loop-return t))
			    collecting
			    (cons (index-documentation-file this-ind) u))))

  (sloop for v in items
	 for i from 0
	 do (format *terminal-io* "~%~3D: ~A" i (key-string (cdr v))))
  (cond (items
	 (sloop until done
		do
		(let ((*standard-input* *query-io*))
		  #+lispm(send *query-io* :send-if-handles :fresh-line)
		  #-lispm (terpri)
		  (format *query-io* "Enter a number, or a Maxima list of numbers,  all or none:")
;		  (setq zl-SOME (mread-noprompt ))
		  (setq zl-SOME (let ((*mread-prompt* ""))
				  (dbm-read *standard-input* nil nil)))
		  (print (list 'zl-some zl-some))
		  )
		(cond ((atom zl-SOME)
		       (cond ((numberp zl-SOME) (setq zl-SOME (list zl-SOME)))
			     ((eq zl-some '$all)
			      (setq zl-SOME (sloop for i below
						   (length items)
						   collecting i)))
			     ((eq '$none zl-some) (setq zl-SOME nil))))
		      (($listp zl-SOME)(setq zl-SOME (cdr zl-SOME))))
		(cond ((null zl-SOME) (setq done t))
		      ((numberp (car zl-SOME))
		       (setq items (sloop for i in zl-SOME
					  collecting  (nth i items)))
		       (setq all-files (maxima-union (mapcar 'car items)  :test 'equalp))
		       (sloop for fil in all-files
			      do (read-documentation
				  (sloop for it in items
					 when (equal (car it) fil)
					 collecting (cdr it))
				  fil :editor editor))
		       (setq done t)))))
	(t (format t "~%No keys contain the strings ~A" items-to-describe)))
  '$done)

(setf (symbol-function '$apropos) #'$describe)
(defun read-documentation (items file &key editor) editor
  (cond
    ((null items) nil)
    (t
     (with-open-file (stream file :element-type #+symbolics '(unsigned-byte 8) #-symbolics '(mod 256)
			     ;:byte-size 8.
			     )
       (let ((output (cond #+lispm
			   (editor
			    (zwei::REST-OF-INTERVAL-STREAM (zwei::POINT)))
			   (t *standard-output*))))
	 (sloop for v in items
	       do (read-item  stream (cdr v)
			      :out-stream output :item (car v))))))))

	
(defun search-file ( expr &optional (file $describe_documentation) &aux (ch 0) cha leng)
  (with-open-file (st file)
    (setq leng   (send st :length))
    (sloop while ch
	  when (eql  (setq ch(send st :tyi)) *doc-start*)
	  do (setf (fill-pointer *word* )0)
	  (with-output-to-string
	    (strin *word* )
	    (sloop  until (zl-MEMBER (setq cha (send st :tyi))
				   '(#\space #\return #\newline nil))
		   do
		  (send strin :tyo cha)))
	  and
	  when(string-search expr *word*)
	  do
	  (sloop while cha until (eql (setq cha (send st :tyi)) *doc-start*)
		do
		(send *standard-output* :tyo cha)))))

#+cl
(defun get-next-keyword (stream &aux cha (ch 0))
  (setf (fill-pointer *word*) 0)
  (sloop while ch when 
	(eql (setq ch (tyi stream )) *doc-start*)
	    do 
	    (with-output-to-string
	      (strin *word* )
	      (sloop until (zl-MEMBER (setq cha (tyi stream))
				     '(#\space #\return #\newline))
		    do
		    (tyo cha strin)
		    ))
	    (return (values *word*))))
#-cl
(defun get-next-keyword (stream &aux cha (ch 0))
  (setf (fill-pointer *word*) 0)
  (sloop while ch when 
	(eql (setq ch (send stream :tyi)) *doc-start* )
	do 
	(with-output-to-string
	  (strin *word* )
	  (sloop until (zl-MEMBER (setq cha (send st :tyi))
			      '(#\space #\return #\newline))
		do
		(send strin :tyo cha)))
	 (return (values *word*))))

)



(defun mread-noprompt (&rest read-args)
  (let ((*mread-prompt* ""))
    (declare (special *mread-prompt*))
    (or read-args (setq read-args (list *query-io*)))
  (caddr (apply #'mread read-args))))


;;;some functions for converting from the DOE style documentation file
;;;to the type we use. DO-IT is the function that does it.
;;in our documentation files the key always follows a a \n *doc-start*


;(defun my-alphabetp (ch)
;  (or (maxima:alphabetp ch)
;      (memq ch '(#/")
;	    )))
;(defun read-from-stream-for-fix (stream to-string prev-name left-over &aux (from-file t) in-white info (spaces 0) tem eof ch)
;  (setf (fill-pointer to-string) 0)
;  (cond (prev-name
;	 (with-output-to-string (str to-string)
;	   (cond ((and left-over (> (length (the string  left-over)) 0)
;		       (eql (aref left-over 0) #/())
;		     (format str "~%~%&~s" prev-name))
;		 (t   (format str "~%~%&~s " prev-name))))))
;  (cond (left-over
;	 (with-output-to-string (str to-string)
;	   (format str "~A" left-over))))
;;  (cond ((setq tem (string-search "                               " to-string))
;;	 (show to-string)
;;	  (setq spaces 20)(setq from-file nil)(setf (fill-pointer to-string) tem)))
;  (sloop named sue
;	when (> spaces 40)
;	  do  (setq in-white (fill-pointer to-string))
;	      (cond (from-file
;		     (sloop with past-space
;			   while (setq ch (send stream :tyi))
;			   do (vector-push-extend  ch to-string)
;			   until (memq ch  '(#\linefeed #\newline))
;			   when (not (eql ch #\space))
;			     do(setq past-space t)
;			   until (and past-space (eql ch #\space))))
;		    (t (setq from-file t)))
;	      (multiple-value-bind (answ left-over end) (check-for-end to-string in-white)
;		(cond (answ (setf (fill-pointer to-string) end)
;			    (return-from sue  (values answ left-over)))))
;	while (setq ch (send stream :tyi))
;	do (vector-push-extend  ch to-string)
;	when (eql ch #\space)
;	  do
;	    (incf spaces)
;	else do (setq spaces 0)))
;
;;;for copying documentation file
;
;(defun do-it (strin in-file out-file &aux next-name left-over )
;  (with-open-file
;    (st  in-file
;;        (st  "isaac:>wfs>doc.tem")
;    (with-open-file (st1 out-file '(:out))
;    (sloop ;for i below n
;	  do
;      (multiple-value-setq (next-name left-over)
;	  (read-from-stream-for-fix st strin next-name left-over ))
;	  while next-name do
;;      (format t "~%~S" next-name)
;      (format st1 "~A" strin)
;             ))))
;;(do-it *strin*)
;(defvar *strin* (make-array 100 :type 'art-string :fill-pointer 0))
;
;(defun not-white-space-p (n) (cond ((not (memq n '(#\space #\newline ))) n)))
;(defun advance-over-white-space (string start &optional reversep &aux ch )
;  (cond (reversep (sloop for i downfrom start to 0
;				until (setq ch (not-white-space-p (aref string i)))
;	finally (return (and ch (add1 i)))))
;	(t (sloop for  i from start below (length (the string  string))
;	until (setq ch (not-white-space-p (aref string i)))
;	finally (return (and ch i))))))
;(defun read-atom-from-string (string start)
;  (declare (values contents end-char-position))
;  (condition-case (condit)
;      (progn (read-from-string string nil start))
;      (sys:read-package-not-found nil)))
;


;; Some list creation utilities.


(defmacro $create_list(form &rest l)
  `(create-list2 ',form ',l))

(defun create-list2 (form l)
  (cons '(mlist) (apply 'create-list1 form l)))

(defun create-list1(form &rest l &aux lis var1 top)
  (cond ((null l)(list (meval* form)))
	(t
	 (setq var1 (car l)
	       lis (second l)
	       l (cddr l))
	 (or (symbolp var1) (merror "~a not a symbol" var1))
 	 (setq lis (meval* lis))
	 (progv (list var1)
		(list nil)
		(cond ((and (numberp lis)
			    (progn
			      (setq top (car l) l (cdr l))
			      (setq top (meval* top))
			      (numberp top)))
		       (sloop for i from lis to top
			      nodeclare t
			      do (set var1 i)
			      append
			      (apply 'create-list1
				     form l)))
		      (($listp lis)
		       (sloop for v in (cdr lis)
			      do (set var1 v)
			      append
			      (apply 'create-list1
				     form l)
			      ))
		      (T (merror "BAD ARG")))))))
