;;; -*- Mode:LISP; Package:UNIX; Base:8; Lowercase:T; Readtable:ZL -*-

;;; Copyright (c) 1984, Lisp Machine, Inc.
;;;   See the file "Copyright" for
;;; licensing and release information.

(defflavor unix-stream
	   (share-tty
	    buf-size
	    lisp-to-unix-array
	    unix-to-lisp-array
	    last-index-given-away
	    (port-number nil)
	    (ready-p nil)
	    shared-device
	    )
	   (si:buffered-stream)
  :settable-instance-variables
  )


(defmethod (unix-stream :after :describe) ()
  (declare (special si:share-tty-qs))
  (select-processor
    (:lambda
      (dolist (q si:share-tty-qs)
	(format t "~&~s:~40t~o" q (funcall q share-tty))))
    ((:explorer :cadr))))
  
(defmethod (unix-stream :after :init) (ignore)
  (send self :setup))

(defmethod (unix-stream :print-self) (stream &rest ignore)
  (format stream "#<UNIX PORT ~d>"
	  (send self ':port-number)))

(defmethod (unix-stream :clear-input) ()
  (setf (si:%share-tty-unix-to-lisp-out-ptr share-tty)
	(si:%share-tty-unix-to-lisp-in-ptr share-tty))
  (send self :interrupt-68000))

(defmethod (unix-stream :clear-output) ()
  (setf (si:%share-tty-lisp-to-unix-in-ptr share-tty)
	(si:%share-tty-lisp-to-unix-out-ptr share-tty))
  (send self :interrupt-68000))

(defmethod (unix-stream :close) (&optional abortp)
  (send self :clear-output)
  (send self :clear-input)
  (when abortp
    (send self :set-carrier nil)
    (sleep 1)
    (send self :set-carrier t))
  (send shared-device :close abortp))

(defmethod (unix-stream :setup) ()
  (select-processor
    ((:explorer :cadr)
     (ferror nil "only for lambda"))
    (:lambda))
  (setq ready-p nil)
  (cond ((not (si:share-mode-active-p))
	 (ferror nil "not in share mode")))
  (setq share-tty (get-share-tty-number port-number))
  (setq buf-size (si:%share-tty-buf-size share-tty))
  (cond ((zerop buf-size)
	 (ferror nil "share-tty not really set up")))
  (setq lisp-to-unix-array
	(make-array  buf-size
		     :type art-string
		     :displaced-to share-tty
		     :displaced-index-offset
		     (* 4 (si:%share-tty-lisp-to-unix-buffer share-tty))))
  (setq unix-to-lisp-array
	(make-array buf-size
		    :type art-string
		    :displaced-to share-tty
		    :displaced-index-offset
		    (* 4 (si:%share-tty-unix-to-lisp-buffer share-tty))))

  (send self :set-carrier t)
  (setq ready-p t))


(defmethod (unix-stream :carrier) ()
  (ldb-test si:%%share-tty-csr-carrier (si:%share-tty-lcsr share-tty)))

(defmethod (unix-stream :set-carrier) (on-p)
  (setf (ldb si:%%share-tty-csr-carrier (si:%share-tty-lcsr share-tty)) (if on-p 1 0))
  (send self :interrupt-68000))

(defmethod (unix-stream :space-available) ()
  "for output from lisp to unix"
;  (not (= 1
;	  (\ (+ (- (si:%share-tty-lisp-to-unix-out-ptr share-tty)
;		   (si:%share-tty-lisp-to-unix-in-ptr share-tty))
;		buf-size)
;	     buf-size)))
  (let ((in (si:%share-tty-lisp-to-unix-in-ptr share-tty))
	(out (si:%share-tty-lisp-to-unix-out-ptr share-tty)))
    (cond ((> out in)
	   (not (= out (1+ in))))
	  ((zerop out)
	   (not (= in (1- buf-size))))
	  (t
	   t)))
  )

;;; values are array, first index he is allowed to store in, and first index he
;;; is not allowed to store in
(defmethod (unix-stream :new-output-buffer) ()
  (process-wait "Unix Stream Output" #'(lambda (u) (send u :space-available)) self)
  (let ((in (si:%share-tty-lisp-to-unix-in-ptr share-tty))
	(out (si:%share-tty-lisp-to-unix-out-ptr share-tty)))
    (values lisp-to-unix-array in
	    (cond ((> out in)
		   (1- out))
		  ((zerop out)
		   (1- buf-size))
		  (t
		   buf-size)))))

;;; new-index is first index he didn't store in
(defmethod (unix-stream :send-output-buffer) (ignore new-index)
  (if (> new-index buf-size)
      (ferror nil "new-index is outside of buffer"))
  (if (= new-index buf-size)
      (setq new-index 0))
  (setf (si:%share-tty-lisp-to-unix-in-ptr share-tty) new-index)
  (send self :interrupt-68000)
  )

(defmethod (unix-stream :interrupt-68000) ()
  (let* ((intr (si:%share-tty-unix-intr share-tty))
	 (quad (ldb (byte 4 28.) intr)))
    (cond ((or (= quad #xe) (= quad #xf))
	   (%nubus-write (ldb (byte 8 24.) intr) (logand 77777777 intr) 1)))))

(defmethod (unix-stream :discard-output-buffer) (ignore)
  nil)

(defmethod (unix-stream :data-available) ()
  "data available from unix"
  (not (= (si:%share-tty-unix-to-lisp-in-ptr share-tty)
	  (si:%share-tty-unix-to-lisp-out-ptr share-tty))))

;;; values are array, first index that has data, and the first index that doesn't have data
(defmethod (unix-stream :next-input-buffer) (&optional no-hang-p)
  (let ((initial-data-available (send self :data-available)))
    (cond ((and (null initial-data-available) no-hang-p)
	   nil)
	  (t
	   (cond ((null initial-data-available)
		  (process-wait "Unix Stream Input"
				#'(lambda (u) (send u :data-available)) self)))
	   (let ((in (si:%share-tty-unix-to-lisp-in-ptr share-tty))
		 (out (si:%share-tty-unix-to-lisp-out-ptr share-tty)))
	     (setq last-index-given-away (if (< out in) in buf-size))
	     (values unix-to-lisp-array out last-index-given-away))))))

(defmethod (unix-stream :discard-input-buffer) (ignore)
  (setf (si:%share-tty-unix-to-lisp-out-ptr share-tty)
	(if (= last-index-given-away buf-size) 0 last-index-given-away))
  (send self :interrupt-68000))

(defmethod (unix-stream :after :tyo) (&rest ignore)
  (send self :force-output))

(defmethod (unix-stream :after :string-out) (&rest ignore)
  (send self :force-output))

(compile-flavor-methods unix-stream)

(defflavor unix-stream-shared-device
	   (share-tty)
	   (si:shared-device)
  )

(defvar *share-ttys* nil)

(defun assure-*share-ttys*-set-up ()
  (process-wait "Wait for *share-ttys* to get set up"
		#'(lambda () *share-ttys*)))

(defconst *max-share-ttys* 256
  "Maximum number of share-ttys that lisp will setup for unix.
   Introduced to workaround unix bug in Release 3.0.")

(defun make-all-share-ttys (&aux share-ttys)
  (select-processor
    (:lambda
      (setq *share-ttys* nil)
      (cond ((zerop (si:%system-configuration-number-of-share-ttys si:*sys-conf*))
	     (process-wait "unix share tty setup"
			   #'(lambda ()
			       (not (zerop (si:%system-configuration-number-of-share-ttys
					     si:*sys-conf*)))))
	     (process-sleep 60.)))
      (let ((base-adr (si:sdu-phys-to-virtual
			(si:%system-configuration-share-tty-0 si:*sys-conf*)))
	    (nttys (si:%system-configuration-number-of-share-ttys si:*sys-conf*))
	    share-tty-0)
	(setq share-tty-0 (si:make-share-tty-virtual base-adr))
	(dotimes (i (min *max-share-ttys* nttys))
	  (let* ((vaddr (+ base-adr
			   (* i (+ (max (si:%share-tty-lisp-to-unix-buffer share-tty-0)
					(si:%share-tty-unix-to-lisp-buffer share-tty-0))
				   (truncate (si:%share-tty-buf-size share-tty-0) 4)))))
		 (st (si:make-share-tty-virtual vaddr)))
	    (push st share-ttys)
	    (si:add-shared-device :name (format nil "UNIX-STREAM-~d" i)
				  :shared-device-flavor 'unix-stream-shared-device
				  :owner-virtual-address (+ vaddr si:%share-tty-owner)
				  :default-flavor-and-init-options `(unix-stream :port-number ,i)
				  :property-list `(share-tty ,st))
	    )))
      (setq *share-ttys* (reverse share-ttys)))
    ((:explorer :cadr))))

(defun get-share-tty-number (port-number)
  (assure-*share-ttys*-set-up)
  (let ((st (nth port-number *share-ttys*)))
    (if (null st)
	(ferror nil "bad port number ~d" port-number))
    st))

(defun find-unix-stream (with-login-p &aux str)
  (assure-*share-ttys*-set-up)
  (do-forever
    (catch-error-restart-explicit-if t (no-unix-stream nil "Try again.")
      (if (setq str (find-unix-stream-internal with-login-p))
	  (return-from find-unix-stream str))
      (ferror 'no-unix-stream
	      "couldn't find unix stream with~:[out~] login" with-login-p))))

(defun find-unix-stream-internal (with-login-p)
  (declare (values unix-stream-pathname port-number))
  (assure-*share-ttys*-set-up)
  (dotimes (i (length *share-ttys*))
    (let* ((shared-device-pathname (fs:parse-pathname (format nil "UNIX-STREAM-~d:" i)))
	   (shared-device (send shared-device-pathname :host)))
      (cond ((and (not (eq (car (send shared-device :lock)) current-process))
		  (send shared-device :get-lock nil))
	     (cond ((send shared-device :allocate-if-easy)
		    (cond ((eq (ldb-test si:%%share-tty-csr-opened
					 (si:%share-tty-ucsr
					   (get shared-device 'share-tty)))
			       with-login-p)
			   (return (values shared-device-pathname i))))
		    (send shared-device :deallocate)))
	     (send shared-device :free-lock))))))

(defun unix-stream-status ()
  (dotimes (i (length *share-ttys*))
    (format t "~2&Port ~d" i)
    (let* ((pathname (fs:parse-pathname (format nil "UNIX-STREAM-~d:" i)))
	   (shared-device (send pathname :host)))
      (print shared-device)
      (format t "~&Lock ~s" (car (send shared-device :lock)))
      (format t "~&Opened ~s" (ldb si:%%share-tty-csr-opened
				   (si:%share-tty-ucsr (get shared-device 'share-tty)))))))

(defun reset-unix-streams ()
  (dotimes (i (length *share-ttys*))
    (let* ((pathname (fs:parse-pathname (format nil "UNIX-STREAM-~d:" i)))
	   (shared-device (send pathname :host)))
      (rplaca (send shared-device :lock) nil)
      (send shared-device :deallocate))))

(add-initialization "Clear *share-ttys*"
		    '(setq *share-ttys* nil)
		    '(before-cold))

(add-initialization "Set up *share-ttys*"
		    '(process-run-function "Set up *share-ttys*" 'make-all-share-ttys)
		    '(warm now))

(defflavor simple-unix-window-mixin
	   ((unix-stream nil)
	    (inverse-video nil)
	    (insert-mode nil)
	    typein-process
	    typeout-process
	    (port-number nil)
	    )
	   ()
  (:default-init-plist :save-bits t)
  (:gettable-instance-variables)
  (:settable-instance-variables)
  )

(defflavor simple-unix-window
	   ()
	   (simple-unix-window-mixin
	    tv:list-mouse-buttons-mixin
	    tv:initially-invisible-mixin
	    tv:window)
  )

(defvar unix-stream-window-number 0)

(defmethod (simple-unix-window-mixin :before :init) (init-plist)
  (putprop init-plist nil :more-p))

(defmethod (simple-unix-window-mixin :set-more-p) ignore)

(defmethod (simple-unix-window-mixin :after :init) (init-plist)
  init-plist
  (setq typein-process (make-process "Simple Unix Stream Typein"))
  (send typein-process :preset 'simple-unix-typein-top-level self)
  (setq typeout-process (make-process "Simple Unix Stream Typeout"))
  (send typeout-process :preset 'simple-unix-typeout-top-level self)
  (send self :set-label (format nil "Unix Stream ~d" (incf unix-stream-window-number)))

  (setf (tv:io-buffer-last-output-process (send self :io-buffer))
	typein-process)
  )

;Delay starting up processes until they start to get used, to save paging on cold-boot
(defmethod (simple-unix-window-mixin :before :select) (&rest ignore)
  (maybe-reset-process typein-process)
  (maybe-reset-process typeout-process))

(defmethod (simple-unix-window-mixin :before :expose) (&rest ignore)
  (maybe-reset-process typein-process)
  (maybe-reset-process typeout-process))

(defun maybe-reset-process (process)
  (cond ((and process (typep process 'si:process))
	 (and (eq (process-wait-function process) 'si:flushed-process)
	      (send process :reset))
	 (send process :run-reason self))))

;; Return a list of our extra processes to be killed.
(defmethod (simple-unix-window-mixin :processes) ()
  (append (and typein-process (list typein-process))
	  (and typeout-process (list typeout-process))))

(defun simple-unix-typein-top-level (window)
  (let ((*terminal-io* window))
    (unless (send window :unix-stream)
      (setf (tv:io-buffer-last-output-process (send window :io-buffer))
	    (send window :typein-process))
      (when (not (si:share-mode-active-p))
	(process-wait "Share mode inactive" 'si:share-mode-active-p))
      (send window :clear-screen)
      (assure-*share-ttys*-set-up)
      (if (send window :port-number)
	  (send window :set-port-number (open (format nil "UNIX-STREAM-~d:"
						      (send window :port-number))))
	(multiple-value-bind (pathname port-number)
	    (find-unix-stream t)
	  (send window :set-unix-stream (open pathname))
	  (send window :set-port-number port-number))))
    (do-forever
      (send window :typein-top-level))))

;; crock to make CONTROL-{META-}ABORT handler not typeout to window
(defmethod (simple-unix-window-mixin :inhibit-output-for-abort-p) () t)

(defmethod (simple-unix-window-mixin :typein-top-level) ()
  (send self :tyo-lispm-char-to-unix (send terminal-io :tyi)))

(defmethod (simple-unix-window-mixin :tyo-lispm-char-to-unix) (c)
  (if (not (zerop (ldb %%kbd-meta c)))
      (send unix-stream :tyo 33))
  (selectq c
    (#\return
     (send unix-stream :tyo 15))
    (#\rubout
     (send unix-stream :tyo 177))
    (#\line
     (send unix-stream :tyo 12))
    (#\tab
     (send unix-stream :tyo 11))
    (t
     (send unix-stream :tyo (if (zerop (ldb %%kbd-control c))
				(logand c 177)
			      (logand c 37))))
    ))

(defun simple-unix-typeout-top-level (window)
  (cond ((si:share-mode-active-p)
	 (process-wait "Await initialization"
		       #'(lambda (window)
			   (and (send window :unix-stream)
				(send (send window :unix-stream) :ready-p)))
		       window)
	 (do-forever
	   (send window :typeout-top-level)))))

;not used
(defmethod (simple-unix-window-mixin :typeout-top-level-simple) ()
  (let ((c (send unix-stream :tyi)))
    (selectq c
      (10 (send self :tyo #\overstrike))
      (11 (send self :tyo #\tab))
      (12 (send self :tyo #\newline))
      (15)
      (t (send self :tyo c)))))

(defmethod (simple-unix-window-mixin :typeout-top-level) ()
  (multiple-value-bind (array starting ending)
      (send unix-stream :read-input-buffer)
    (do* ((search-start starting)
	  (first-hard (string-search-set '(10 11 12 15 33 #\space) array search-start ending)
		      (string-search-set '(10 11 12 15 33 #\space) array search-start ending))
	  )
	 ((null first-hard)
	  (send self :clear-then-string-out array starting ending)
	  (send unix-stream :advance-input-buffer ending))

      ;;; Hook so that other users can get at the characters as they are received from unix
      (send self :raw-unix-character-in first-hard)

      (selectq (aref array first-hard)
;	(10 (aset #\overstrike array first-hard)
;	    (setq search-start (1+ first-hard)))
	(10 (send self :clear-then-string-out array starting first-hard)
	    (setq starting (1+ first-hard))
	    (setq search-start starting)
	    (send self :tyo #\overstrike))
	(11 (aset #\tab array first-hard)
	    (setq search-start (1+ first-hard)))
	(12 (aset #\return array first-hard)
	    (setq search-start (1+ first-hard)))
	(15 (send self :clear-then-string-out array starting first-hard)
	    (setq starting (1+ first-hard))
	    (setq search-start starting))
	(#\space (send self :clear-then-string-out array starting first-hard)
		 (do ((n (1+ first-hard) (1+ n)))
		     ((or (= n ending)
			  (not (= (aref array n) #\space)))
		      (dotimes (x (- n first-hard))
			(send self :clear-char)
			(send self :forward-char))
		      (setq starting n)
		      (setq search-start n))))
	(33 (send self :clear-then-string-out array starting first-hard)
	    (send unix-stream :advance-input-buffer first-hard)
	    (send self :handle-escape)	   ;
	    (return nil))
	(t (ferror nil "internal bug: hard case not handled"))
	))))

;;; These methods don't actually do anything, but the user can attach daemons to them
(defmethod (simple-unix-window-mixin :raw-unix-character-in) (c) c)
(defmethod (simple-unix-window-mixin :unix-string-in) (str start end) str start end )

(defmethod (simple-unix-window-mixin :clear-then-string-out) (str &optional (start 0) end)
  (if (null end) (setq end (string-length str)))
  (cond ((null insert-mode)
	 (send self :clear-string str start end)
	 (send self :string-out str start end)

	 ;;; Hook so that other users can get at the strings as they are received from unix
	 (send self :unix-string-in str start end))


	(t
	 (send self :insert-string str start end))))

(defmethod (simple-unix-window-mixin :handle-escape) ()	   ;
  (if (not (= (send unix-stream :tyi) 33))
      (ferror nil "handle-escape called out of phase"))
  (selectq (send unix-stream :tyi)
    (#/L (send self :insert-line))		; termcap 'al'
    (#/J (send self :clear-eof))		;cd
    (#/K (send self :clear-eol))		;ce
    (#/E (send self :clear-screen))		;cl
    (#/Y (let* ((y (- (send unix-stream :tyi) #\space))	;cm
		(x (- (send unix-stream :tyi) #\space)))
	   (send self :set-cursorpos x y :character)))
    (#/N (send self :delete-char))		;dc
    (#/M (send self :delete-line))		;dl
    (#/B (send self :increment-cursorpos 0 1 :character))	;do
    (#/O (setq insert-mode nil))		;ei
    (#/H (send self :home-cursor))		;ho
    (#/@ (setq insert-mode t))			;im
    (#/C (send self :forward-char))		;nd
    (#/F nil)					;as - start alternate char set
    (#/G nil)					;ae - end alternate char set
    (#/I (multiple-value-bind (x y)		;sr
	     (send self :read-cursorpos)
	   (send self :home-cursor)
	   (send self :insert-line)
	   (send self :set-cursorpos x y)))
    (#/q (setq inverse-video nil))		;se
    (#/p (setq inverse-video t))		;so
    (#/A (send self :increment-cursorpos 0 -1 :character))	;up
    (#/x (send self :tyi))			;change cursor blinking mode
    (#/y (send self :tyi))			; dito
    ))

(tv:add-system-key #/U 'simple-unix-window "Login connection to Unix" t)

(compile-flavor-methods simple-unix-window)

