;;; -*- Mode:LISP; Package:TIGER; Base:8; Fonts:(CPTFONT TR12I); Readtable:T -*-

; Copyright LISP Machine, Inc. 1984
;   See filename "Copyright" for
; licensing and release information.

;1;; Routines for *ASCII1 printers.*
;1;; User-callable functions are in the file *USER.LISP1.*
;1;; Functions used by the *TIGER1 process and the *TIGER-SERVER1 processes are in the file *SERVER.LISP1.*
;1;; Definitions of variables and structures are in the file *DEFS.LISP1.*
;1;; Methods of *BASIC-PRINTER-STREAM-MIXIN1 are in the file *STREAM.LISP1.*
;1;; Methods of *TOSHIBA-STREAM1 (for the Toshiba P1350 printer) are in the file *TOSHIBA.LISP1.*
;1;; Internal functions of the TIGER process.*
;1;; *TIGER-FILE-INTERNAL1 is the top-level function of that process.*

(defun tiger-file-internal ()
  (error-restart ((sys:abort error) "Return to printer specification command level.")
    (do-forever
      (process-wait "Queue Empty" #'(lambda () tiger-queue))
      ;; if remote machine then we must do the following
      ;; if it is a file, just send the file name to the tiger host
      ;; otherwise write the file somewhere, and then send this temporary
      ;; filename to the tiger host.
      (let* ((queue-object (first tiger-queue))
	     (user-id "Tiger"))			;1Fool* FORCE-USER-TO-LOGIN
	(multiple-value-bind (tiger-type tiger-host)
	    (figure-out-printer-type-and-host (figure-out-printer queue-object))
	  (cond ((host-equal tiger-host si:local-host)
		 (setq handshake-type
		       (or (get tiger-type 'tiger-serial-handshake-type)
			   :default))
		 (multiple-value-bind (device flavor-and-inits)
		     (serial-flavor-requirements)
		   (WITH-OPEN-FILE (x device :flavor-and-init-options flavor-and-inits)
		     (setq serial-stream x)
		     (let ((aborted-p t))
		       (unwind-protect
			   (progn (tiger-process-immediate)
				  (setq aborted-p nil))
			 (when aborted-p
			   (handle-aborted-tiger-process-immediate)))))))
		(t (selectq (tq-type queue-object)
		     ((:file :array-file :aray-file :raw-file)
		      (cond ((stringp (tq-object queue-object))
			     (tiger-notify-user (format nil "Spooling to ~A" tiger-host)
						(tq-sender queue-object))
			     (tiger-send-it tiger-host queue-object))
			    (t (tiger-notify-user
				 (format nil "~A is not a valid tiger file queue entry."
					 queue-object)
				 (tq-sender queue-object)))))
		     (:array (tiger-array-entry queue-object t))
		     (:otherwise (tiger-notify-user
				   (format nil "Error Printing ~A, ~A is not a known type"
					   (tq-object queue-object)
					   (tq-type queue-object))
				   (tq-sender queue-object))))
		   (pop-tiger-queue))))))))


(defun handle-aborted-tiger-process-immediate ()
  (send serial-stream :send-if-handles :clear-input)
  (send serial-stream :send-if-handles :clear-output)
  (send serial-stream :send-if-handles :tyo (ascii-code #\form)))
		       


(defun get-or-instantiate-printer-stream (printer)
  (let ((obj (or (get printer 'tiger-stream)
		 (putprop printer
			  (make-instance (get printer 'tiger-stream-flavor))
			  'tiger-stream))))
    (send obj :set-destination serial-stream)
    obj))

(defun figure-out-printer (entry)
  (cond ((cadr (memq :printer (tq-options entry))))
	((eq (tq-type entry) :file)
	 si:*default-printer*)
	((memq (tq-type entry) '(:array :array-file))
	 si:*default-bit-array-printer*)
	(t si:*default-printer*)))

(defun setup-tiger-normal (tiger-stream)
  (send tiger-stream :setup-normal)
  (send tiger-stream :send-if-handles :set-tab-stops 8.)
  (send tiger-stream :send-if-handles :set-elongated-printing nil)
  ;1; These should default per printer -- therefore should be handled in the *:SETUP-NORMAL1 message.*
; (send tiger-stream :send-if-handles :set-characters-per-inch 10.)
; (send tiger-stream :send-if-handles :set-lines-per-inch 6.)
; (send tiger-stream :send-if-handles :set-form-length 11.)
  (send tiger-stream :send-if-handles :set-font 0 t))

(defun array-file-p (filename stream)
  (or (equal "TIGER" (send (fs:merge-pathname-defaults filename) :type))
      (send stream :send-if-handles :get :tiger)))

(defun tiger-process-immediate ()
  (selectq (tq-version queue-object)
    ((3 4) (selectq (tq-type queue-object)
	     ((:file :array-file :raw-file) (tiger-file-entry queue-object))
	     (:aray-file (tiger-dumped-array queue-object))
	     (:array (tiger-array-entry queue-object nil))
	     (:otherwise (tiger-notify-user
			   (format nil "Error Printing ~A, ~A is not a known type"
				   (tq-object queue-object)
				   (tq-type queue-object))
			   (tq-sender queue-object)))))
    (:otherwise (tiger-notify-user
		  (format nil "Error Printing ~A, queue entry version ~D not supported."
			  queue-object
			  (tq-version queue-object))
		  (tq-sender queue-object))))
  (pop-tiger-queue))

(defun tiger-file-entry (queue-entry)
  (lexpr-funcall 'tiger-file-entry-1
		 (tq-object queue-entry)
		 (tq-sender queue-entry)
		 (tq-user queue-entry)
		 (tq-options queue-entry)))

(defun tiger-file-entry-1 (file sender user &rest options &optional &key
;			   (printer si:*default-printer*)
			   (delete-after nil)
			   (copies 1)
			   (font nil)
			   (font-list nil)
			   characters-per-inch
			   lines-per-inch
			   (page-headings t)
			   (title-page nil)
			   (file-heading t)
			   &allow-other-keys)
  (declare (special queue-object tiger-type tiger-host))
  sender
  (let ((tiger-stream (get-or-instantiate-printer-stream tiger-type)))
    (setup-tiger-normal tiger-stream)
    (with-open-file (in file :direction :input :error nil)
      (cond ((errorp in)
	     (tiger-notify-user (format nil "Error Printing ~A: ~A" file in) sender))
	    (t (tiger-notify-user (format nil "Now Printing ~A" file) sender)
	       (dotimes (i copies)
		 (when (plusp i) (send in :set-pointer 0))	;reset file stream
		 (cond ((or (memq (tq-type queue-object) '(:raw-file :array-file))
			    (array-file-p file in))
			;1; Binary Image file*
			(stream-copy-until-eof in (send tiger-stream :destination)))
		       (t 
			;1; otherwise regular text file.*
			(cond (font-list
			       (send tiger-stream :send-if-handles :set-font-list font-list)
			       (send tiger-stream :send-if-handles :set-font 0)
			       (send tiger-stream :set-process-font-changes t))
			      (font
			       (send tiger-stream :send-if-handles 
						  :set-font-list (circular-list font))
			       (send tiger-stream :set-font font)
			       (send tiger-stream :set-process-font-changes nil))
			      (t (send tiger-stream :send-if-handles :set-font-list nil)
				 (send tiger-stream :send-if-handles :set-font font)
				 (send tiger-stream :set-process-font-changes t)))
			(send tiger-stream :send-if-handles 
					   :set-characters-per-inch characters-per-inch)
			(send tiger-stream :send-if-handles
					   :set-lines-per-inch lines-per-inch)
			(send tiger-stream :set-page-headings page-headings)
			(send tiger-stream :set-heading-line
			      (format nil "~A  ~\time\"
				      (send in :truename)
				      (send in :creation-date)))
			(send tiger-stream :set-page-number 1)
			(when (or title-page page-headings file-heading)
			  (send tiger-stream :send-if-handles :set-elongated-printing t)
			  (format tiger-stream "  for ~A on ~A at ~\datime\~&" user sender)
			  (send tiger-stream :print-heading))
			(when title-page
			  (send tiger-stream :tyo #\form))
			(stream-copy-until-eof in tiger-stream)))
		 (send tiger-stream :set-page-headings nil)
		 (send tiger-stream :tyo #\form)))))
    (when delete-after
      (deletef file)
      (fs:expunge-directory (send (fs:parse-pathname file) :new-pathname
			       :name :wild
			       :type :wild
			       :version :wild)))))

(defunp tiger-array-entry (queue-entry &optional (always-spool-p t)
			  &aux array (q-obj (tq-object queue-entry)))
  (cond ((typep q-obj :array)
	 (setq array q-obj))
	((typep q-obj :instance)
	 (setq array (tv:sheet-screen-array q-obj)))
	(t (tiger-notify-user (format nil "~A not a valid array type." q-obj)
			      (tq-sender queue-entry))
	   (return nil)))
  (lexpr-funcall 'tiger-array-entry-1 array
				      (tq-sender queue-entry)
				      (tq-user queue-entry)
				      always-spool-p
				      (tq-options queue-entry))
  (free-queue-entry-array queue-entry))


;;; tiger-array-entry-1 doesn't free the queue-entry-array anymore.
;;; It must be done by the caller.

(defun tiger-array-entry-1 (array sender user always-spool-p &rest options &optional &key
			    (printer si:*default-bit-array-printer*)
			    (copies 1)
			    (left 0)
			    (top 0)
			    (bottom (pixel-array-height array))
			    (right (pixel-array-width array))
			    (rotation nil)
			    (delete-after t)
			    return-array x-scale y-scale
			    &allow-other-keys)
  (declare (special tiger-type tiger-host))
  user
  (let ((tiger-stream (get-or-instantiate-printer-stream tiger-type))
	(temp-file-p (or always-spool-p ( 1 copies)))
	(temp-file-stream nil)
	(user-id "Tiger")			;1Fool *FORCE-USER-TO-LOGIN
	old-destination truename)
    (unwind-protect
	(progn
	  (cond (temp-file-p
		 (setq old-destination (send tiger-stream :destination))
		 (send tiger-stream :set-destination
		       (setq temp-file-stream
			     (open (make-tiger-queue-directory-filename
				     tiger-host
				     "TEMPORARY-SCREEN-ARRAY"
				     "TIGER")
				   :direction :output :byte-size 8. :characters nil)))
		 (tiger-notify-user (format nil "Spooling temporary file to ~A" tiger-host)
				    sender)
		 (send temp-file-stream :putprop t :tiger)
		 (setq truename (send (send temp-file-stream :truename)
				      :string-for-printing)))
		(t (tiger-notify-user (format nil "Now printing array ~A" array)
				      sender)))
	  (setup-tiger-normal tiger-stream)
	  (send tiger-stream :set-x-scale (or x-scale (get tiger-type 'default-x-scale)))
	  (send tiger-stream :set-y-scale (or y-scale (get tiger-type 'default-y-scale)))
	  (tiger-bit-array-to-stream array tiger-stream rotation left top right bottom))
      (when temp-file-stream (send temp-file-stream :close)))
    (cond (temp-file-p (tiger-send-it tiger-host
				      (make-tiger-queue-entry
					:type :array-file
					:object truename
					:sender sender
					:options
					`(:printer ,printer
					  :copies ,copies
					  :delete-after ,delete-after)))
		       (send tiger-stream :set-destination old-destination))
	  (t (send tiger-stream :set-page-headings nil)
	     (send tiger-stream :tyo #\form)))
    (when return-array array)))

(defun tiger-dumped-array (queue-entry)
  (let ((file (tq-object queue-entry)))
    (wait-for-tiger-array)
    (multiple-value-bind (array left top right bottom)
	(restore-pixel-array-from-file file tiger-temporary-bit-array)
      (when (cadr (memq :delete-after (tq-options queue-entry)))
	(deletef file))
      (push-tiger-queue-entry
	(make-tiger-queue-entry
	  :type :array
	  :object array
	  :sender (tq-sender queue-entry)
	  :user (tq-user queue-entry)
	  :options (append (tq-options queue-entry)
			    `(:left ,left :top ,top :right ,right :bottom ,bottom)))))
    (setq tiger-array-free t)))

(defun tiger-bit-array-to-stream (array stream &optional
				  (rotation nil)
				  (left 0)
				  (top 0)
				  (right (pixel-array-width array))
				  (bottom (pixel-array-height array)))
  (send stream :set-up-for-graphics rotation)
  (let ((width (- right left))
	(height (- bottom top))
	(x left)
	(y top))
    ;; i'm trying to make this be as consistent with the tv:sheet :BITBLT
    ;; operation as possible. dropping some arguments that arent needed.
    ;; (tv:bitblt alu width height from-array from-x from-y to-array to-x to-y)
    (cond ((send stream :get-handler-for :bitblt)
	   (send stream :bitblt width height array x y))
	  ((and (= 1 (floor (send stream :dots-per-line) (send stream :y-scale)))
		(= 1 (send stream :x-scale)))
	   (bitblt-to-stream-simple-case width height array x y stream))
	  ('else
	   (bitblt-to-stream-hairy-case width height array x y stream)))))

(defun bitblt-to-stream-simple-case (width height array x y stream &aux left-bit right-bit)
  ;; This is for printers that just want to see a string of bits but
  ;; haven't implemented the :BITBLT message themselves. In this case we do
  ;; some whitespace optimization on a row-by-row basis and do a SEND
  ;; for each bit in the non-optimized-away region.
  ;; Since this is so primitive we print messages as we go along to aid in debugging.
  (format t "~&Simple array case is ~D by ~D" width height)
  (dotimes (j height)
    (format t " ~D" j)
    (cond ((null (setq left-bit (row-bit-search array (+ y j) x width)))
	   (format t "@"))
	  ('else
	   (send stream :position-horizontal (- left-bit x))
	   (setq right-bit (row-reverse-bit-search array (+ y j) x width))
	   (format t "[~D,~D]" left-bit right-bit)
	   (do ((k left-bit (1+ k)))
	       ((> k right-bit))
	     (send stream :print-bit-graphics (aref array (+ y j) k)))))
    (send stream :new-graphics-line)))

(defun row-bit-search (array y x count)
  (do ((j 0 (1+ j)))
      ((= j count)
       nil)
    (or (zerop (aref array y (+ x j)))
	(return (+ x j)))))

(defun row-reverse-bit-search (array y x count)
  (do ((position (+ x (1- count)) (1- position)))
      ((< position x)
       nil)
    (or (zerop (aref array y position))
	(return position))))


(defun bitblt-to-stream-hairy-case (width height array x y stream &aux left right top bottom)
  ;; this is for printers that take <BYTES> and stack them horizontally. They do things
  ;; a line-at-a-time. This is the original tiger code for arrays, the dots-per-line
  ;; figure is the actual number of solenoid driven wires in the IMPACT-PRINTER head,
  ;; modified by number of quality-passes perhaps. Or perhaps the number of thermally
  ;; active areas on a thermal printer head.
  (setq left x right (+ left width))
  (setq left 0) ;; this code doesnt work unless LEFT = 0
  (setq top y bottom (+ top height))
  ;1; This loop iterates by printer lines.  For each, we print some graphics, then move on to next line.*
  ;1; This loop iterates by printer lines.  For each, we print some graphics, then move on to next line.*
  (loop with dots-per-line = (send stream :dots-per-line)
       	with x-scale = (send stream :x-scale)
	with y-scale = (send stream :y-scale)
	with pixels-per-line = (floor dots-per-line y-scale)
	with start-bp = (dpb (1- pixels-per-line) si:%%byte-specifier-position 1)
	with height = (- bottom top)
;	with width = (- right left)
	with real-width = (pixel-array-width array)
	with init-positioned-p = (zerop left)
	for current-row from top below bottom by pixels-per-line
	for first-pixel = (* current-row real-width)
	for rows-remaining from height downto 0 by pixels-per-line
	do ;1; This loop iterates by columns.  For each, we gather up information and print it.*
	   (loop with positioned-p = init-positioned-p
		 for x from left below right
		 for char = ;1; This loop copies the current column, one row at a time.*
			    (loop with character = 0
				  for bp = start-bp then (- bp 100)
				  for count from 0 below (min pixels-per-line rows-remaining)
				  for i from (+ first-pixel x) by real-width
				  do (setq character (%logdpb (ar-1-force array i) bp character))
				  finally (return character))
		 do (cond ((zerop char) (setq positioned-p nil))
			  (t (unless positioned-p
			       (send stream :position-horizontal (* x x-scale))
			       (setq positioned-p t))
			     (let ((transformed-char
				     (fast-perform-y-scaling char y-scale pixels-per-line)))
			       (dotimes (foo x-scale)
				 (send stream :print-bit-graphics transformed-char))))))

	   ;1; Now we move on to the next line.*
	   (send stream :new-graphics-line)))


;1; The Tiger server is a Chaosnet server which accepts the printed representation of the lists which are*
;1; queue entries.  The lists are printed and read in the *TIGER1 package, in base 10.,  to make sure*
;1; the reading happens correctly.*
;1;*
;1; The serving machine assumes that the requested file already exists.  An error notification will be sent*
;1; if it is not found.*

(defvar tiger-server-connections nil)

(defun tiger-server-function (&aux conn)
  (setq conn (chaos:listen "TIGER"))
  (chaos:accept conn)
  (send tv:who-line-file-state-sheet :add-server conn "TIGER")
  (unwind-protect
      (let* ((base 10.) (ibase 10.) stream object)
	(unwind-protect
	    (progn
	      (setq stream (chaos:make-stream conn)
		    object (pkg-bind "TIGER"
			     (read stream)))
	      (selectq (tq-type object)
		(:notification
		 (notify-local-tiger-error (tq-object object))
		 (send stream :line-out "+Notification performed."))
		((:file :array :array-file :raw-file :aray-file)
		 (push-tiger-queue-entry object)
		 (send stream :line-out "+Request accepted."))
		(:otherwise (send stream :line-out "-Unknown queue entry type.")))
	      (send stream :force-output))
	  (when stream (send stream :finish))))
    (when conn
      (chaos:remove-conn conn)
      (send tv:who-line-file-state-sheet :delete-server conn)))
  (start-tiger)
  nil)

(add-initialization "TIGER"
		    '(process-run-function "TIGER Server" 'tiger-server-function)
		    nil
		    'chaos:server-alist)

(defun tiger-status-server-function ()
  (condition-case ()
      (with-open-stream (stream (chaos:open-stream nil "TIGER-STATUS"))
	(send stream :accept)
	(tiger-local-status-description stream))
    (SYS:REMOTE-NETWORK-ERROR NIL)))

(add-initialization "TIGER-STATUS"
		    '(process-run-function "TIGER Status Server"
					   'tiger-status-server-function)
		    nil
		    'chaos:server-alist)

(defun tiger-send-it (tiger-host queue-entry)
  ;1; queue-entry should be a file-type queue so send appropriate info*
  (fs:force-user-to-login)
  (condition-case (conn-error)
      (with-open-stream (stream (chaos:open-stream tiger-host "TIGER"))
	(let ((base 10.) (ibase 10.))
	  (pkg-bind "TIGER" (print-queue-entry queue-entry stream)))
	(send stream :force-output)
	(check-chaos-tiger-response stream queue-entry)
	(send stream :finish))
    (sys:network-error (tv:notify nil "Error ~A while trying to queue to host ~A."
				  conn-error tiger-host))))

(defun print-queue-entry (entry &optional (stream standard-output))
  (cond ((eq (car entry) 'tiger-queue-entry)
	 (format stream "(TIGER:TIGER-QUEUE-ENTRY ~D. :~A ~S ~S ~S "
		 (tq-version entry) (tq-type entry) (tq-object entry)
		 (tq-sender entry) (tq-user entry))
	 (print-option-list (tq-options entry) stream)
	 (format stream " NO-JOB-NUMBER-YET)"))
	(t (ferror nil "Illegal queue entry ~A encountered." entry))))

(defun print-option-list (list &optional (stream standard-output))
  (send stream :tyo #/()
  (do ((first-time-p t)
       (tail list))
      (())
    ;1; First read an option name.  It must be a symbol.*
    (unless tail (return))
    (cond (first-time-p (setq first-time-p nil))
	  (t (send stream :tyo #\Space)))
    (let ((option (pop tail)))
      (cond ((symbolp option)
	     (unless (keywordp option)
	       (cerror t nil nil
		       "The option name ~S in the option list ~S does not appear to be a keyword."
		       option tail))
	     (format stream ":~A" option))
	    (t (ferror nil "The option name ~S in the option list ~S is not a symbol."
		       option tail)))
      (unless tail
	(cerror t nil nil "The option name ~S in the option list ~S has no value."
		option tail))
      (send stream :tyo #\Space)
      (prin1 (pop tail) stream)))
  (send stream :tyo #/)))
  
(defun check-chaos-tiger-response (stream queue-entry)
  (let ((line (send stream :line-in)))
    (selectq (aref line 0)
      (#/+)					;AOK
      (#/-
       (tv:notify nil "Negative response from host for ~A: ~A" (tq-object queue-entry) line))
      (:otherwise
       (tv:notify nil "Unknown response from host for ~A: ~A"
		  (tq-object queue-entry) line)))))

(defun tiger-notify-user (message host)
  (cond ((null host) (tv:notify nil message))
	((or (typep host 'si:host) (stringp host))
	 (notify-tiger-error host message))
	(t (tv:notify nil "Error sending message ~A to incorrect host ~A." message host))))

(defun notify-tiger-error (host error-message)
  (if (host-equal host si:local-host) (notify-local-tiger-error error-message)
    (notify-chaos-tiger-error host error-message)))

(defun notify-chaos-tiger-error (host error-message &aux keyword-response)
  (condition-case (conn-error)
      (with-open-stream (stream (chaos:open-stream host "TIGER"))
	(pkg-bind "TIGER"
	  (print (make-tiger-queue-entry :type :notification :object error-message) stream))
	(send stream :force-output)
	(setq keyword-response (send stream :line-in))
	(when (neq #/+ (aref keyword-response 0))
	  (tv:notify nil "Error ~A sending notification ~A to host ~A." 
		     keyword-response error-message host))
	(send stream :finish))
    (sys:network-error (tv:notify nil "Error ~A while trying to send notification ~A to host ~A."
				  conn-error error-message host))))