;-*- Mode:LISP; Package:STEVE; Readtable:CL; Base:10 -*-

;Copyright (c) May 1983 by Christopher Eliot
; and Massachusetts Institute of Technology.
;Permission to copy all or part of this material is granted, provided
; that the copies are not made or distributed for resale, the MIT
; copyright notice and reference to the source file and the software
; distribution version appear, and that notice is given that copying
; is by permission of Massachusetts Institute of Technology.

;
;New redisplay scheme.
;
;This will still use a screen image, but only one.
;

(defvar old-screen-image nil)

(defconstant line-not-modified 0) ;Also defined in EM.LSP
(defconstant *tab-spacing* 8)
(defvar creamed-tty-lines-to 0)
(defvar *overwrite-line*) ;Used by functions in OVER.LSP
(defvar re-generate-screen-really nil)

(defvar *window*)
(defvar *win-x-pos*)
(defvar *win-y-pos*)
(defvar *win-x-size*)
(defvar *win-y-size*)
(defvar *win-y-limit*)

;
;; These variables are vectors indexed by the physical tty line #.
;;  The buffer-line on this tty line.
(defvar *line-map*)
;;  The first buffer-line-index on this tty-line.
(defvar *p1-map*)
;;  The last buffer-line-index on this tty-line.
(defvar *p2-map*)
;

(defvar *editor-redisplay-count* 0)

;;; Assume the various TTY operations, and then notice when they fail.
;;; Be carefull to handle the case when they fail somehow, even
;;; if this means redisplaying the whole screen. This will happen
;;; only once per editing session anyhow.
(defvar ins&del-line-p t)
(defvar ins&del-char-p t)

(defun clear-one-screen-image (image)
  (loop for i from 0 below *tty-height*
	do (%string-replace (vref image i) 80spaces 0 0 *tty-width*)))

(defun init-tty-for-editor ()
  (let ((h (send terminal-io :pagel))
	(w (send terminal-io :linel)))
    (when (or (not (=& h *tty-height*))
	      (not (=& w *tty-width*))
	      (not (boundp '*line-map*))
	      (null *line-map*)
	      (not (null *tty-init-flag*)))
      (setq old-screen-image (make-vector h))
      (loop for i from 0 below h
	    do (setf (svref old-screen-image i)
		     (make-string w :initial-element #\sp)))
      (setq *line-map* (make-vector h)) ;Initial values should not be critical.
      (setq *p1-map* (make-vector h :initial-element 0))
      (setq *p2-map* (make-vector h :initial-element (1-& w)))
      (setq *tty-height* h)
      (setq *tty-width* w)
      (unless (null *editor-cursor*)
	;;Has some side effects but that can't be helped.
	(one-window)))
    (setq *tty-height* h)
    (setq *tty-width* w)
    (setq ins&del-line-p t)
    (setq ins&del-char-p t)))

(defun make-screen-image (&optional unused)
  (unless (type-ahead-p)
    (when (+p creamed-tty-lines-to)
      (setq *overwrite-line* 0)
      (cond ((>& creamed-tty-lines-to *first-mode-line*)
	     (send terminal-io :clear-screen)
	     (setup-mode-area)
	     (loop for i from 0 below *tty-height*
		   do (setf (svref *line-map* i) nil)
		   do (%string-replace (svref old-screen-image i) 80spaces
				       0 0 *tty-width*)))
	    (t (loop for i from 0 below creamed-tty-lines-to
		     do (cursorpos i 0 terminal-io)
		     do (send terminal-io :clear-eol)
		     do (setf (svref *line-map* i) nil)
		     do (%string-replace (svref old-screen-image i) 80spaces
					 0 0 *tty-width*))))
      (setq creamed-tty-lines-to 0))
    (when re-generate-screen-really
      (loop for i from 0 below *tty-height*
	    do (setf (svref *line-map* i) nil))
      (setq re-generate-screen-really nil))
    (catch 'listen-tag
      (loop for curse in *all-edit-cursors*
	    for *window* = (edit-cursor-window curse)
	    with *win-x-size* = 0
	    with *win-y-size* = 0
	    with *win-y-pos* = 0
	    with *win-x-pos* = 0
	    with *win-y-limit* = 0
	    if (not (null (edit-cursor-window curse)))
	    do (progn (setq *win-x-size* (window-stream-x-size *window*)
			    *win-y-size* (window-stream-y-size *window*)
			    *win-x-pos* (window-stream-x-pos *window*)
			    *win-y-pos* (window-stream-y-pos *window*)
			    *win-y-limit* (+& *win-y-pos* *win-y-size*))
		      (when (not (point-on-screen? curse))
			(auto-recenter-window curse))
		      (redisplay-buffer-lines curse))
	    finally (setq *editor-redisplay-count*
			  (1+& *editor-redisplay-count*))
	    finally (return t)))))

(defsubst line-height* (len1 x-size)
  (let ((len len1))
    (if (0p len)
	1
	(1+& (/& (1-& len) (1-& x-size))))))

(defun aprox-line-height (line x-size)
  (line-height* (if (and (+p (line-graphic-length line))
			 (line-not-bashed? line))
		    (line-graphic-length line)
		    (line-char-count line))
		x-size))

;This function is a heuristic.  I used to think that it was accurate, and
;I believe there are bugs because of this.  All use of this function should
;be scrutinized.  This function estimates the width of a line by its character
;count.  If a line has special characters in it then this will be low, so
;this function is accurate when it claims that the cursor is not on the screen
;but may be wrong when it claims the cursor IS on the screen. (This is probably
;the less useful heuristic.)
(defun point-on-screen? (point)
  (setq *window* (edit-cursor-window point)
	*win-x-size* (window-x-size *window*)
	*win-y-size* (window-y-size *window*))
  (cond ((eq (edit-cursor-home-line point) (bp-line point))
	 (and (>=& (bp-position point) (edit-cursor-home-pos point))
	      (<=& (line-height* (-& (bp-position point)
				    (edit-cursor-home-pos point))
				*win-x-size*)
		  *win-y-size*)))
	(t (loop with lines = 0
		 with target = (bp-line point)
		 for line first (edit-cursor-home-line point)
		 then (line-next line)
		 while line
		 for start first (edit-cursor-home-pos point) then 0
		 for l = (cond ((eq line target) ;Start must = 0
				(bp-position point))
			       ((and (line-not-bashed? line)
				     (not (-p (line-graphic-length line)))
				     (0p start))
				(line-graphic-length line))
			       (t (-& (line-char-count line) start)))
		 summing (line-height* l *win-x-size*) into height
		 if (eq line target)
		 return (<=& height *win-y-size*)
		 while (<=& lines *win-y-size*)))))

;This returns the length of the graphic representation of a character.
;For most characters this is 1, but for special characters it may be longer.
;For example a <BELL> prints as #\BELL using 6 columns.
;A tab uses a variable number of columns, depending upon where it is.
;The second argument is the real column in which the character will be printed.
;See GRAPHIC-CHAR-LENGTH-CALC for more imformation.
(defsubst graphic-char-length (char col)
   (if (graphic-char-p char)
      1
      (simple-string-length (stringify-char char col))))

(defun position-from-graphic-position (line graphic-pos
				       &aux (line-string (line-chars line))
				       (line-length (line-char-count line)))
  (do ((i 0 (1+& i))
       (chr)
       (so-far 0))
      ((or (>=& i line-length) (>=& so-far graphic-pos)) i)
      (setq chr (char line-string i)
	    so-far (+& so-far (graphic-char-length chr so-far)))))

;This function is a good place to look for caching optimizations.
(defun position-really (line pos x-size  &optional (skip 0)
			     &aux (line-string (line-chars line)))
  ;;Values are relative X, Y.
  (loop with so-far = 0
	with ypos = 0
	for i from skip below pos
	for chr = (char line-string i)
	do (setq so-far (+& so-far (graphic-char-length chr so-far)))
	if (>=& so-far x-size)
	do (setq so-far (-& so-far x-size -1)
		 ypos (1+& ypos))
	finally (return (values so-far ypos))))

(defsubst edit-cursor-position-really (point)
  (position-really (bp-line point)
		   (bp-position point)
		   (window-stream-x-size (edit-cursor-window point))))

(defun real-line-height (line start end x-size)
  (line-height* (real-line-width line start end) x-size))

(defun real-line-width (line start end)
  (loop with chars = (line-chars line)
	with column = 0
	for i from start below end
	for char = (char chars i)
	for char-width = (graphic-char-length char column)
	do (setq column (+& column char-width))
	finally (return column)))

;;; This must be made to work perfectly, even if slow.
(defun auto-recenter-window (point)
  (setq *window* (edit-cursor-window point)
	*win-y-size* (window-stream-y-size *window*)
	*win-x-size* (window-stream-x-size *window*))
  ;;(ed-warning "%Recentering window")
  (loop with target = (ceiling (* *win-y-size* *recentering-fraction*))
	with lines = (real-line-height (bp-line point)
				       0
				       (bp-position point)
				       *win-x-size*)
	for line first (bp-line point) then previous
	for previous = (line-previous line)
	for height = (if previous
			 (real-line-height previous
					   0
					   (line-char-count previous)
					   *win-x-size*)
			 *win-y-size*)
	if (>& (+& lines height) target)
	return
	(cond ((<=& lines *win-y-size*)
	       (setf (edit-cursor-home-pos point) 0)
	       (setf (edit-cursor-home-line point) line)
	       t)
	      ;; First iteration only. Home-pos must be > 0.
	      ((not (eq line (bp-line point)))
	       (break "Implementation bug in recentering")
	       (setf (edit-cursor-home-line point) (bp-line point))
	       (setf (edit-cursor-home-pos point) (bp-position point)))
	      (t (setf (edit-cursor-home-line point) line)
		 (setf (edit-cursor-home-pos point)
		       (-& (bp-position point)
			   (*& target (1-& *win-x-size*))))
		 t))
	do (setq lines (+& lines height))))

(defsubst page-break? (line)
  (%string-posq #\page (line-chars line) 0 (line-char-count line)))

(defun redisplay-buffer-lines (point)
  (when (and (eq (bp-line point) (edit-cursor-home-line point))
	     (>& (edit-cursor-home-pos point) (bp-position point)))
    (auto-recenter-window point))
  (loop with cursor-on-screen = nil
	with cline = (bp-line point)
	;;Y is absolute now.
	with y = *win-y-pos*
	;;for y first *win-y-pos*
	;;then (+& (line-height* (line-graphic-length line) *win-x-size*) y)
	for line first (edit-cursor-home-line point) then (line-next line)
	for x-start first (edit-cursor-home-pos point) then 0
	if (null line) return (clear-rest-of-window y)
	if (eq line cline) ;Find real cursor position.
	do (multiple-value-bind (xpos ypos)
	     (position-really line (bp-position point) *win-x-size* x-start)
	     (when (<& (+& ypos y) *win-y-limit*) 
	       (setq cursor-on-screen t))
	     (cursorpos (-& (+& y ypos) *win-y-pos*) xpos *window*))
	do (setq y (+& y (expand-line line y point x-start)))
	while (<& y *win-y-limit*)
	finally (when (null cursor-on-screen)
		  (auto-recenter-window point)
		  (redisplay-buffer-lines point))))

(defun real-x-y-position (bp)
  ;; Used by underline feature.
  (loop with window = (edit-cursor-window *editor-cursor*)
	with x-size = (window-stream-x-size window)
	for line first (edit-cursor-home-line *editor-cursor*)
	then (line-next line)
	while line
	for y first (window-y-pos window)
	then (+& y (line-height* (line-graphic-length (line-previous line))
				 x-size))
	while (<=& y *tty-height*)
	if (eq line (bp-line bp))
	return (multiple-value-bind (xpos ypos)
		 (position-really line (bp-position bp) x-size)
		 (values xpos (+& ypos y)))))

(defun clear-rest-of-window (y)
  ;;Y is absolute here.
  (loop for i from y below *win-y-limit*
	do (setf (svref *line-map* i) nil)
	unless (%string-eqv (svref old-screen-image i) 80spaces
			    *win-x-pos* 0 *win-x-size*)
	do (progn (%string-replace (svref old-screen-image i) 80spaces
				   *win-x-pos* 0 *win-x-size*)
		  (cursorpos i *win-x-pos* terminal-io)
		  (send terminal-io :clear-eol))))

(defun i&d-line-too-slow-p (nlines)
  (< (* 40 (- *win-y-size* nlines))
     (* nlines (send terminal-io :insert-line-cost 3))))

;The basic function to display buffer lines.

(defun expand-line (line y point x-start &aux i&d-line-too-slow-flag)
 ;;The line is a buffer line.
 ;;Y is the screen line where the line should be displayed.
 ;;point contains the window and its line-map.
 ;;The VALUE must be a fixnum count of the number of tty lines used
 ;;to display the buffer line. (>= 1)
 (cond ((and (line-not-bashed? line)
	     (eq line (svref *line-map* y))
	     ;;We must make sure that the WHOLE line is displayed correctly.
	     (loop for i upfrom y 
		   for first first x-start then (1+& last)
		   for last = (svref *p2-map* i)
		   never (not (=& (svref *p1-map* i) first))
		   if (or (=& last (line-char-count line))
			  (>=& (1+& i) *win-y-limit*))
		   ;;The line is already displayed correctly.
		   return (1+& (-& i y)))))
       ;; Ins&del-line-p is set to NIL if operation fails.
       ((null ins&del-line-p) (just-expand-line line y point x-start))
       ;; Here we should handle the cases for insert-line and delete-line.
       ;; This is the check for deleted lines.
       ((and (or (not (eq line (svref *line-map* y)))
		 (not (0p (svref *p1-map* y))))
	     (loop for k from y below *win-y-limit*
		   if (and (eq (svref *line-map* k) line)
			   (0p (svref *p1-map* k)))
		   return (let ((nlines (-& k y)))
			    (unless (i&d-line-too-slow-p nlines)
			      (delete-old-lines y nlines)))))
	(just-expand-line line y point x-start))
       ;; Check here for possible inserted lines.
       ((and (not (eq line (svref *line-map* y)))
	     (0p (svref *p1-map* y))
	     (loop with line** = (svref *line-map* y)
		   for k from y below *win-y-limit*
		   for line* first line then (line-next line*)
		   while (not (null line*))
		   if (eq line** line*)
		   return (let ((nlines (-& k y)))
			    (unless (i&d-line-too-slow-p nlines)
			      (insert-new-lines y nlines)))))
	(just-expand-line line y point x-start))
       (t (just-expand-line line y point x-start))))

;;;These have to modify the maps.
(defun insert-tty-lines (line n)
  (cursorpos line 0 terminal-io)
  (cond ((null (send terminal-io :send-if-handles :insert-line n))
	 (setq ins&del-line-p nil)
	 nil)
	(t (loop for i from 1 to n	;Shift up.
		 for k = (-& *tty-height* i)
		 for hold = (svref old-screen-image k)
		 do (loop for j downfrom k by n
			  while (>=& j (+& line n))
			  do (move-screen-image-line (-& j n) j)
			  finally (%string-replace hold 80spaces
						   0 0 *tty-width*)
			  finally (setf (svref old-screen-image j) hold
					(svref *line-map* j) nil
					(svref *p1-map* j) 0
					(svref *p2-map* j) 0)))
	   t)))

(defun delete-tty-lines (line n)
  (cursorpos line 0 terminal-io)
  (cond ((null (send terminal-io :send-if-handles :delete-line n))
	 (setq ins&del-line-p nil)
	 nil)
	(t (loop for i from 0 below n
		 for k = (+& line i)
		 for hold = (svref old-screen-image k)
		 do (loop for j upfrom k by n
			  while (<& j (-& *tty-height* n))
			  do (move-screen-image-line (+& j n) j)
			  finally (%string-replace hold 80spaces
						   0 0 *tty-width*)
			  finally (setf (svref old-screen-image j) hold
					(svref *line-map* j) nil
					(svref *p1-map* j) 0
					(svref *p2-map* j) 0)))
	   t)))

(defun move-screen-image-line (from to)
  (setf (svref old-screen-image to) (svref old-screen-image from))
  (setf (svref *line-map* to) (svref *line-map* from))
  (setf (svref *p1-map* to) (svref *p1-map* from))
  (setf (svref *p2-map* to) (svref *p2-map* from)))

(defun delete-old-lines (ypos add)
  (and (delete-tty-lines ypos add)
       (insert-tty-lines (-& (+& *win-y-pos* *win-y-size*) add) add)))

(defun insert-new-lines (ypos add)
  (and (delete-tty-lines (-& (+& *win-y-pos* *win-y-size*) add) add)
       (insert-tty-lines ypos add)))

;;This function and macro are purely auxillaries for just-expand-line.
(defun redisplay-cont-line (point change1 change2 y
			    &aux (x-max (+& *win-x-pos* *win-x-size* -1)))
  (when (<& y *win-y-limit*)
    (let ((screen-line (svref old-screen-image y)))
      (unless (char= (char screen-line x-max) #\!)
	(setf (char screen-line x-max) #\!)
	(setq change1 (min& change1 x-max)
	      change2 x-max))
      (when (and (<=& change1 change2) ;output line to TTY.
		 (<& y *win-y-limit*))
	(cursorpos y change1 terminal-io)
	(send terminal-io :raw-oustr screen-line 
	      change1 (1+& (-& x-max change1)))
	(send terminal-io :set-cursor-fried-flag t)))))

;;Note that this macro is used to obtain call by reference.
(defmacro update-tty-map (y line p1 p2) 
  `(progn (when (<& y *win-y-limit*)
	    (setf (svref *line-map* ,y) ,line)
	    (setf (svref *p1-map* ,y) ,p1)
	    (setf (svref *p2-map* ,y) ,p2))
	  (setf ,p1 (1+& ,p2))))

;;Note that this macro is used to obtain call by reference.
(defmacro cont-line-reset (change1 change2 j y x-max screen-line gr-len)
  `(setq ,change1 ,x-max
	 ,change2 -1
	 ,gr-len (+& ,gr-len ,j)
	 ,j 0
	 ,y (1+& ,y)
	 ,screen-line (if (<& ,y *win-y-limit*) (svref old-screen-image ,y)
			  nil)))

;;;The value (if no throw done) is a fixnum count of the number of TTY lines
;;;used to display the buffer line. (Nessesarilly >= 1.)
(defun just-expand-line (line y point x-start &aux (y-initial y))
  (loop
   with g-len = (if (0p x-start) 0 (position-really line x-start *win-x-size*))
   with chrs = (line-chars line)
   with screen-line = (svref old-screen-image y)
   with x-max = (+& *win-x-pos* *win-x-size* -1)
   with change1 = x-max 
   with change2 = -1
   with line-char-count = (line-char-count line)
   ;;Steppers.
   with j = *win-x-pos*
   for i upfrom x-start while (<& i line-char-count)
   for chr = (char chrs i)
   if (>=& j x-max)
   do (progn (redisplay-cont-line point change1 change2 y)
	     (update-tty-map y line x-start i)
	     (cont-line-reset change1 change2 j y x-max screen-line g-len)
	     (when (and (0p (\\& y 4))
			(type-ahead-p))
	       (throw 'listen-tag nil)))
   do (cond ((graphic-char-p chr)
	     (cond ((null screen-line)) ;past screen. just count.
		   ((char= chr (char screen-line j))) ;already there.
		   (t (setq change1 (min& change1 j) ;change it.
			    change2 j)
		      (setf (char screen-line j) chr)))
	     (setq j (1+& j)))
	    (t (loop with string = (stringify-char chr j)
		     for k from 0 below (simple-string-length string)
		     for ch = (char string k)
		     if (>=& j x-max)
		     do (progn (redisplay-cont-line point change1 change2 y)
			       (update-tty-map y line x-start i)
			       (cont-line-reset change1 change2 j y x-max
						screen-line g-len)
			       (when (and (0p (\\& y 4))
					  (type-ahead-p))
				 (throw 'listen-tag nil)))
		     do (cond ((null screen-line)) ;past screen. just count.
			      ((char= ch (char screen-line j))) ;already there
			      (t (setq change1 (min& change1 j) ;change it.
				       change2 j)
				 (setf (char screen-line j) ch)))
		     do (setq j (1+& j)))))
   finally (setf (line-graphic-length line) (+& g-len j))
   finally (unless (<& y *win-y-limit*) (return (1+& (-& y y-initial))))
   finally (when (<=& change1 change2) ;output line to tty.
	     (cursorpos y change1 terminal-io)
	     (send terminal-io :raw-oustr screen-line
		   change1 (1+& (-& change2 change1)))
	     (send terminal-io :set-cursor-fried-flag t))
   finally (unless (%string-eqv screen-line 80spaces j 0 (1+& (-& x-max j)))
	     (%string-replace screen-line 80spaces j 0 (1+& (-& x-max j)))
	     (unless (=& change2 j) (cursorpos y j terminal-io))
	     (send terminal-io :clear-eol))
   finally (update-tty-map y line x-start line-char-count)
   finally (when (and (0p (\\& y 4))
		      (type-ahead-p))
	     (throw 'listen-tag nil))
   finally (return (1+& (-& y y-initial)))))








