;1;; -*- Mode:LISP; Package:LASER2; Base:8; Fonts:(CPTFONT HL12B) -*-*
;1;; Copyright (c) 1985, LISP Machine, Inc.*

;1;; Description: Laser2 server*
;1;; Programmer: James M. Turner*
;1;; Last Modification: 11-Jan-1985*
;1;;*
;1;; Change History:*
;1;;*

;1;; Abandon ship!*

(defmethod (laser2-stream :reset-laser2) ()
  (send server-process ':reset))

(defun reset-laser2 (&optional (save-current nil))
  (send *laser2-stream* :reset-laser2)
  (if save-current
      (push (send *laser2-stream* :current-job) (send *laser2-stream* :printer-queue))))

(defun start-laser2-processes-if-needed ()
  (if *laser2-stream*
      (progn
	(if (not (equal (send (send *laser2-stream* :server-process) :name)
			"Laser2 Server Process"))
	    (start-laser2-server)))
    (setq *laser2-stream* (make-instance 'laser2-stream))
    (start-laser2-server)))

(defun start-laser2-server ()
  (send *laser2-stream*
	':set-server-process
	(process-run-restartable-function "Laser2 Server Process"
					  #'laser2-server)))
(defun laser2-server ()
  (send *laser2-stream* ':run-server))


;1;; Checks queue for jobs waiting, then prints them.*

(defmethod (laser2-stream :run-server) ()
  ;1; So we can open files*
  (setq tcp-stream nil)
  (let ((user-id "LASER2") rmname fndname)
    (do-forever
      ;1; Wait for job.*
      (process-wait "No Jobs" #'(lambda (x) (send x ':printer-queue)) self)
      (setq current-job (pop printer-queue))
      (unwind-protect
	  (*catch 'no-printer
	    (setq fndname nil)
	    (cond ((equal laser2-remote :NONE)
		   (tv:background-notify "No TCP Bridge Available, Sorry")
		   (*throw 'no-printer t)))
	    (setq fndname (second (second current-job)))
	    (cond 
	      ((listp laser2-remote)
	       (*catch 'open
		 (do-forever
		   (dolist (hst laser2-remote)
		     (setq rmname hst)
		     (setq tcp-stream (catch-error
					    (chaos:connect
					      hst
					      (string-append "LASER2-PIFILE "
							     fndname)) nil))
			 (if tcp-stream (*throw 'open nil)))
		       (process-sleep 600. "No Printers Available")))
		   (setq tcp-stream (chaos:make-stream tcp-stream)))
		  (t
		   (setq tcp-stream (open (setq rmname
						(string-append "TCP-HOST:" fndname))
					  :direction :output))
		   (send tcp-stream :set-force-output-p nil)))
	    (tv:background-notify "Now Printing ~A on ~A" (car current-job)
				  (second (second current-job)))
	    (cond
	      ((equal (car current-job) ':FILE)
	       (apply self :print-file (cddr current-job)))
	      ((equal (car current-job) ':STREAM)
	       (apply self :print-stream (cddr current-job)))
	      ((equal (car current-job) ':ARRAY)
	       (apply self :print-bit-array (cddr (butlast current-job)))
	       (if (not (last current-job))
		   (deallocate-resource 'tv:hardcopy-bit-array-resource (caddr current-job))))))
	(if tcp-stream
	    (close tcp-stream))
	(setq tcp-stream nil))
      (setq current-job nil))))

(defun print-laser2-queue (&optional (stream terminal-io) (count 1))
  (dolist (item (reverse (send *laser2-stream* :printer-queue)))
    (selectq (car item)
      (:FILE
       (format stream "~%Item ~D: File ~A" count (cadr item)))
      (:STREAM
       (format stream "~%Item ~D: Stream ~A" count (cadr item)))
      (:DPRINT
       (format stream "~%Item ~D: Raw Laser2 File ~A" count (cadr item)))
      (:ARRAY
       (format stream "~%Item ~D: A bit array" count)))
    (incf count)))

(defun remove-queue-item (n &aux queue)
  (setq queue (send *laser2-stream* :printer-queue))
  (setq n (- (length queue) n))
  (if (or (minusp n) ( n (length queue))) ()
    (if (= n 0)
	(send *laser2-stream* :set-printer-queue (cdr queue))
      (rplacd (nthcdr (1- n) queue)
	      (nthcdr (1+ n) queue))))
  (print-laser2-queue))

;1;; Get things running.*

(defun laser2-start-processes ()
  (start-laser2-processes-if-needed))


;1;; The various things that can be done remotely.*

(add-initialization "LASER2-PIFILE" '(process-run-function "Remote Imagen Server"
							  'laser2-remote-server)
		    nil 'chaos:server-alist)

(add-initialization "LASER2-SEARCH" '(process-run-function "Reply to Broadcast"
							   'laser2-find-answer)
		    nil 'chaos:server-alist)

;1;; And the functions that go with them.*

(defun laser2-find-server ()
  (let ((responded nil))
    (chaos:poll-hosts (chaos:list-all-net-machines :lispm) ; don't bother everybody
		      "LASER2-SEARCH" nil #'ignore
		      #'(lambda (ignore n y)
			  (if (string-equal (chaos:pkt-string y) "HAVE-TCP")
			      (push (multiple-value-bind (ignore b)
					(chaos:address-parse n) b)
				    responded)))
		      :ignore-states '(chaos:rfc-sent-state))
    responded))

(defun laser2-find-answer ()
  (start-laser2-processes-if-needed)
  (if (send *laser2-stream* :laser2-remote)
      (chaos:fast-answer-string "LASER2-SEARCH" "NO-TCP")
    (chaos:fast-answer-string "LASER2-SEARCH" "HAVE-TCP")))

(defun laser2-remote-server (&aux address conn stream status add)
  (let ((user-id "LASER2"))
    (setq conn (chaos:listen "LASER2-PIFILE"))
    (setq add (chaos:pkt-string (chaos:read-pkts-last conn)))
    (if ( (string-length add) 15.)
	(setq address (substring (chaos:pkt-string (chaos:read-pkts-last conn)) 14.)))
    (unwind-protect
	(catch-error
	  (if (progn
		(setq status (get-imagen-status address))
		(not (and status (plusp (getf status :protocols-accepted)))))
	      (chaos:reject conn "Printer Busy")
	    (chaos:accept conn)
	    (setq stream (chaos:make-stream conn))
	    (with-open-file (tcp-stream (string-append "TCP-HOST:" address))
	      (stream-copy-until-eof stream tcp-stream))) nil))))

(defun get-imagen-status (address &key &optional
			  (tries 10)
			  (try-pause 0.01)
			  (wait-pause 0.01))
  "Returns a plist describing the status of the imagen at the internet ADDRESS"
  (check-type address string)
  (check-type tries (fixnum 1))
  (check-type try-pause number)
  (check-type wait-pause number)
  (let (result)
    (dotimes (j tries)
      (if (setq result (get-imagen-status-1 address (ROUND (* wait-pause 60))))
	  (return-from get-imagen-status result))
      (process-sleep (ROUND (* try-pause 60)) "imagen status sleep"))))

(defconst imagen-udp-status-pkt-size 256)

(defun get-imagen-status-1 (address pause)
  (with-open-file (stream (string-append "TCP-HOST:" address "#IMAGEN")
			  :for-udp t)
    (using-resource (array fs:simple-string-buffer imagen-udp-status-pkt-size)
      (fill array 0)
      (send stream :write-packet array 0 imagen-udp-status-pkt-size)
      (dotimes (j pause)
	;; :listen cant be use inside a process-wait function because
	;; it sends a message to the device driver which needs a process
	;; to run in order to get the reply.
	(if (send stream :listen) (return))
	(process-sleep 1 "imagen status reply"))
      (WHEN (SEND STREAM :LISTEN)
	(SEND STREAM :READ-PACKET ARRAY)
	(let ((plist (PARSE-IMAGEN-STATUS-PACKET ARRAY)))
	  (setf (getf plist :address) address)
	  plist)))))

(DEFUN BIG-ENDIAN-BYTES (ARRAY &REST BYTES)
  "Combine bytes into an integer, most significant come first"
  (DO ((NUMBER 0 (+ (* NUMBER 256) (AREF ARRAY (CAR L))))
       (L BYTES (CDR L)))
      ((NULL L) NUMBER)))

(DEFUN PARSE-IMAGEN-STATUS-PACKET (ARRAY)
  (DO ((PLIST (LIST :STATUS-BYTE (BIG-ENDIAN-BYTES ARRAY 2)
		    :AGE (BIG-ENDIAN-BYTES ARRAY 4 5 6 7)
		    :PROTOCOLS-SUPPORTED (BIG-ENDIAN-BYTES ARRAY 8 9 10 11)
		    :PROTOCOLS-ACCEPTED  (BIG-ENDIAN-BYTES ARRAY 12 13 14 15)
		    :ENGINE-STATUS (SUBSTRING ARRAY
					      (BIG-ENDIAN-BYTES ARRAY 16 1)
					      (+ (BIG-ENDIAN-BYTES ARRAY 16 1)
						 (BIG-ENDIAN-BYTES ARRAY 18 19)))))
       (PROBLEMS '((#x01 :OTHER)
		   (#x02 :NO-PAPER)
		   (#x04 :PAPER-JAM)
		   (#x08 :LACKING-CONSUMABLES)
		   (#xF0 :JOB-IN-PROGRESS))
		 (CDR PROBLEMS)))
      ((NULL PROBLEMS) PLIST)
    (IF (BIT-TEST (CAAR PROBLEMS) (GETF PLIST :STATUS-BYTE))
	(PUSH (CADAR PROBLEMS) (GETF PLIST :PROBLEMS)))))
