;;; -*- Mode:LISP; Package:SYSTEM-INTERNALS; Readtable:ZL; Base:10 -*-

(defun map-over-stream-lines (f from-stream)
  "Function F gets called on STRING START END for each line in the stream.
This is highly bummed, sends :read-input-buffer and :advance-input-buffer
messages, which is better than using :string-line-in because we only do
copying of data when a end-of-line boundary crosses an i-o buffer boundary."
  (declare (values total-lines easy-lines n-buffers n-chars))
  (prog (offset limit start end leftover leftoverp easy-lines hard-lines buf
	 n-buffers n-chars)
	(setq easy-lines 0 hard-lines 0 n-buffers 0 n-chars 0)
     read-buffer
	(multiple-value (buf offset limit)
	  (send from-stream :read-input-buffer))
	(or buf (return (values (+ easy-lines hard-lines) easy-lines n-buffers n-chars)))
	(incf n-buffers)
	(incf n-chars (- limit offset))
	(setq start offset)
     next-line
	(setq end (%string-search-char #\RETURN buf start limit))
	(cond ((null end)
	       (when (or (not leftover)
			 (> (- limit start) (array-length leftover)))
		 (setq leftover (make-array (max 300 (fix (* 1.2 (- limit start))))
					    :type 'art-string
					    :fill-pointer 0)))
	       (setf (fill-pointer leftover) (- limit start))
	       (copy-array-portion buf start limit
				   leftover 0 (- limit start))
	       (send from-stream :advance-input-buffer)
	       (setq leftoverp t)
	       (go read-buffer))
	      ((not leftoverp)
	       (funcall f buf start end)
	       (setq start (1+ end))
	       (incf easy-lines)
	       (go next-line))
	      ('else
	       (let* ((old-fp (fill-pointer leftover))
		      (new-fp (+ old-fp (- end start))))
		 (when (> new-fp (array-length leftover))
		   (let ((new-leftover (make-array (fix (* 1.2 new-fp))
					    :type 'art-string
					    :fill-pointer 0)))
		     (copy-array-portion leftover 0 old-fp
					 new-leftover 0 old-fp)
		     (setq leftover new-leftover)))
		 (setf (fill-pointer leftover)  new-fp)
		 (copy-array-portion buf start end
				     leftover old-fp new-fp)
		 (funcall f leftover 0 new-fp)
		 (setq leftoverp nil)
		 (setq start (1+ end))
		 (incf hard-lines)
		 (go next-line))))))


(defun type-file (filename &optional (stream standard-output) &aux tm)
  "Type the file to stream using the :line-out operation."
  (terpri)
  (with-open-file (input filename)
    (setq tm (time))
    (multiple-value-bind (nil nil n-buffers n-chars)
	(map-over-stream-lines #'(lambda (string start end)
				   (send stream :line-out string start end))
			       input)
      (setq tm (quotient (time-difference (time) tm) 60.0))
      (list :time tm :buffers n-buffers :characters n-chars
	    :characters-per-buffer (quotient n-chars (float n-buffers))
	    :characters-per-second (quotient n-chars tm)))))


(defun file-line-search (filename keys &optional &key
			 (output standard-output)
			 (window 3)
			 (starlines t)
			 &aux
			 (line-number 0)
			 (display-next 0)
			 (found-alist nil)
			 (head))
  (if (atom keys) (setq keys (list keys)))
  (with-open-file (stream filename)
    (when starlines
      (format output "~&**** Searching ~S for keys: ~S~%"
	      (setq head (send (send stream :truename) :string-for-editor)) keys))
    (map-over-stream-lines
      #'(lambda (string start end)
	  (incf line-number)
	  (LET ((FOUND (DO ((L KEYS (CDR L)))
			   ((NULL L) NIL)
			 (AND (string-search (CAR L) string start end)
			      (RETURN (CAR L))))))
	    (cond (FOUND
		   (incf (cdr (or (assq found found-alist)
				  (car (push (cons found 0) found-alist)))))
		   (when starlines
		     (format output "**** Line ~D of ~S contains ~S ****~%"
			     line-number
			     head
			     found)
		     (format output "~D " line-number))
		   (send output :string-out string start end)
		   (terpri output)
		   (setq display-next window))
		  ((> display-next 0)
		   (when starlines
		     (format output "~D " line-number))
		   (send output :string-out string start end)
		   (terpri output)
		   (decf display-next)
		   (when (and starlines (zerop display-next))
		     (format output "*************~%"))))))
      stream))
  (cond ((not starlines))
	((null found-alist)
	 (format output "*** NO KEYS FOUND IN ~S~%" HEAD))
	('else
	 (format output "*** SUMMARY OF KEYS FOUND IN ~S~%" HEAD)
	 (dolist (k found-alist)
	   (format output "~S found ~D time~p~%" (car k) (cdr k) (cdr k))))))

(defun system-source-search (system keys &optional &key output-filename (window 3)
			     (starlines t))
  (let ((files (delq nil (mapcar #'probe-file (si:system-source-files system))))
	(tm (time))
	(dw (read-meter 'si:%disk-wait-time)))
    (format t "~&~D files to search in ~S~%" (length files) system)
    (cond (output-filename
	   (with-open-file (o output-filename :out)
	     (do ((j 1 (1+ j))
		  (l files (cdr l)))
		 ((null l))
	       (format t "~&~D of ~D~%" j (length files))
	       (file-line-search (car l) keys :output o :window window :starlines starlines))
	     (setq tm (quotient (time-difference (time) tm) 60.0))
	     (setq dw (quotient (- (read-meter 'si:%disk-wait-time) dw) 1.0e6))
	     (format o "~%%%%%%%% SEARCH OF ~D FILES TOOK ~A ~D seconds disk wait~%"
		     (length files)
		     (time:print-interval-or-never (fix tm) nil)
		     dw)))
	  ('else
	   (dolist (f files)
	     (file-line-search f keys :window window :starlines starlines))
	   (setq tm (quotient (time-difference (time) tm) 60.0))
	     (setq dw (quotient (- (read-meter 'si:%disk-wait-time) dw) 1.0e6))
	   (format t "~%%%%%%%% SEARCH OF ~S FILES TOOK ~A ~D seconds disk wait~%"
		   (length files)
		   (time:print-interval-or-never (fix tm) nil)
		   dw)))))
