;;; -*- Mode:LISP; Package:TAPE; Readtable:CL; Base:10 -*-
;;
;; Copyright GigaMos Systems, Inc. 1988
;;   See filename "Copyright" for
;; licensing and release information.
;;
;;;
;;; Format to implement a TAPETEST tape stream.
;;;
;;; The TAPETEST format tests scratch tapes for read/write errors.
;;; It does *not* test the tape hardware!

(eval-when(compile eval load)
  (defconst *tapetest-max-blocking-factor* 63.)
  (defconst *tapetest-max-record-size* (* *tapetest-max-blocking-factor* si:page-size 4)))

(defconst *tapetest-pattern-zeros*
	  (make-array *tapetest-max-record-size* :element-type '(mod 256) :initial-element 0.))

(defconst *tapetest-pattern-ones*
	  (make-array *tapetest-max-record-size* :element-type '(mod 256) :initial-element #xFF))

(defconst *tapetest-pattern-buffer*
	  (make-array *tapetest-max-record-size* :element-type '(mod 256) :initial-element #xAA))

(defflavor tapetest-format
	 ((blocking-factor 32.)
	  (record-size     (* 32. si:page-size 4.))
	  (file-stream #'ignore))
	 (basic-tape-format)
  (:gettable-instance-variables)
  (:inittable-instance-variables blocking-factor))

(defun tapetest-blocking(blocking-factor)
  (declare(values record-size blocking-factor))
  (check-type blocking-factor (integer 1 #.*tapetest-max-blocking-factor*))
  (values (* blocking-factor si:page-size 4.)
	  blocking-factor))

(defmethod (tapetest-format :set-blocking) ()
  (multiple-value-setq (record-size blocking-factor)
    (tapetest-blocking blocking-factor)))

(defmethod (tapetest-format :after :init) (&rest ignore)
  (send self :set-blocking))

(defmethod (tapetest-format :set-blocking-factor) (new-blocking-factor)
  (setq blocking-factor new-blocking-factor)
  (send self :set-blocking))

(defmethod (tapetest-format :initialize) (&rest init-options)
  (check-attribute-list init-options)
  (when init-options
    (lexpr-send self :set-options init-options)))

(defmethod (tapetest-format :set-options) (&rest options)
  (check-attribute-list options)
  (if options
      (do* ((l options (cddr l))
	    (option (car l) (car l))
	    (value (cadr l) (cadr l)))
	   ((null l))
	(case option
	  (:blocking-factor
	   (setq blocking-factor value)
	   (send self :set-blocking))
	  (t (signal 'invalid-option :object self :option option :value value))))
    (progn
      (tv:choose-variable-values
	`((,(locf blocking-factor) "Blocking Factor" :number))
	:label '(:string "Options for the TAPETEST tape format" :font fonts:tr12b))
      (send self :set-blocking))))

(defmethod (tapetest-format :open-file) (&rest ignore)
  (signal 'not-supported :device-object self :operation :open-file))

(defmethod (tapetest-format :read-tape-header) (&rest ignore)
  (signal 'not-supported :device-object self :operation :read-tape-header))

(defmethod (tapetest-format :write-tape-header) (&rest ignore)
  (signal 'not-supported :device-object self :operation :write-tape-header))

(defmethod (tapetest-format :tape-is-your-format-p) (&rest ignore)
  nil)

(defmethod (tapetest-format :restore-file) (&rest ignore)
  (signal 'not-supported :device-object self :operation :restore-file))

;;;The real stuff starts here

(defun tapetest-compare-pattern(pat1 pat2 &optional max)
  "Compare test pattern elements of arrays PAT1 and PAT2.
Returns T if all elements match.
All elements must be NUMBERP, and the arrays must have the same ARRAY-ACTIVE-LENGTH.
If the comparison fails, the second value returned will be the index of the failing entry."
  (declare(values compared-p bad-index))
  (unless (eql(array-active-length pat1)(array-active-length pat2))
    (error "Cannot compare pattern arrays - different lengths for ~s and ~s"))
  (do*((i 0 (1+ i)))
      ((= i (or max (array-active-length pat1)))
       t)
    (or (eql (aref pat1 i) (aref pat2 i))
	(return (values nil i)))))

(defun tapetest-compare-patterns(pattern
				 &key
				 (device *selected-device*)
				 (format *selected-format*)
				 &aux
				 (record-size (send format :record-size)))
  (check-type record-size integer)
  (send device :rewind)
  (do((rec 0)
      flag index done)
     (done
       (format t "~%Comparison done, ~d records OK" rec))
    (condition-case()
	(progn
	 (setq done t)
	 (send device :read-array *tapetest-pattern-buffer* 1 record-size)
	 (incf rec)
	 (multiple-value-setq(flag index)
	   (tapetest-compare-pattern pattern *tapetest-pattern-buffer* record-size))
	 (if flag
	     (format t ".")
	   (error (format nil "~&Comparison failure, byte ~d record ~d" index rec))))
      (filemark-encountered)
      (:no-error (setq done nil)))))

(defun tapetest-test-pattern(pattern
			     &key
			     (device *selected-device*)
			     (format *selected-format*)
			     (offset-records 0.)
			     (max-records -1.)
			     &aux
			     (record-size (send format :record-size)))
  (check-type record-size integer)
  (send device :rewind)
  (unless (zerop offset-records)
    (send device :write-array pattern 1 (* offset-records 1024.)))
  (condition-case()
      (do((i 1 (1+ i)))
	 ((and (plusp max-records) (> i max-records)))
	(send device :write-array pattern 1 record-size)
	(format t "."))
    (physical-end-of-tape
     (format t "~2%Physical end of tape encountered.")
     (send device :space-reverse 1)
     (send device :space-reverse 1))
    (:no-error
     (or (plusp max-records)
	 (error "~%How did we get here? Should have gotten PHYSICAL-END-OF-TAPE error!"))))
  (format t "~%Done writing test patterns.")
  (format t "~%Writing EOF.")
  (send format :finish-tape device)
  t)

(defun tapetest-test-patterns(&key
			      (device *selected-device*)
			      (format *selected-format*)
			      (max-records -1.)
			      &aux time-start time-end)
  (condition-case()
      (progn
       (send device :rewind)
       (setq time-start (time:get-universal-time))
       (format t "~2%Pass 1: write zeros with small record offset ")
       (tapetest-test-pattern *tapetest-pattern-zeros*
			      :device device
			      :format format
			      :offset-records 1
			      :max-records max-records)
       (format t "~2%Pass 2: write ones evenly spaced ")
       (tapetest-test-pattern *tapetest-pattern-ones*
			      :device device
			      :format format
			      :max-records max-records)
       (format t "~2%Pass 3: read/verify ")
       (tapetest-compare-patterns *tapetest-pattern-ones*
				  :device device
				  :format format)
       (setq time-end (time:get-universal-time))
       (format t "~2%Processing time: ")
       (time:print-interval-or-never (- time-end time-start))
       (send device :unload))
    (sys:abort
     (beep)
     (format t "~%Bad tape???")
     (send device :rewind))))

;;;The only standard format method that is useful is COMPARE-FILE.  This
;;;allows the tape frame user to use 'verify files' to test a tape.
       
(defmethod (tapetest-format :compare-file) (device &rest ignore)
  (tapetest-test-patterns :device device
			  :format self)
  ;;Kludge to stop COMPARE-FILES from looping on this method:
  (signal 'logical-end-of-tape :device-object device))

;;;More unsupported/irrelevant methods:

(defmethod (tapetest-format :write-file) (&rest ignore)
  (signal 'not-supported :device-object self :operation :write-file))

(defmethod (tapetest-format :write-partition) (&rest ignore)
  (signal 'not-supported :device-object self :operation :write-partition))

(defmethod (tapetest-format :write-file) (&rest ignore)
  (signal 'not-supported :device-object self :operation :write-file))

(defmethod (tapetest-format :beginning-of-file) (&rest ignore)
  (signal 'not-supported :device-object self :operation :beginning-of-file))

(defmethod (tapetest-format :next-file) (&rest ignore)
  (signal 'not-supported :device-object self :operation :next-file))

(defmethod (tapetest-format :previous-file) (&rest ignore)
  (signal 'not-supported :device-object self :operation :previous-file))

(defmethod (tapetest-format :find-file) (&rest ignore)
  (signal 'not-supported :device-object self :operation :find-file))

(defmethod (tapetest-format :find-file-reverse) (&rest ignore)
  (signal 'not-supported :device-object self :operation :find-file-reverse))

(defmethod (tapetest-format :list-files) (&rest ignore)
  (signal 'not-supported :device-object self :operation :list-files))

;;;We do use FINISH-TAPE to mark the end of tape.

(defmethod (tapetest-format :finish-tape) (device)
  (check-device device)
  (condition-case ()
      (send device :write-filemark)
    (physical-end-of-tape)
    (:no-error
     (condition-case ()
	 (send device :space-reverse 1)
       (driver-error)))))

(defmethod (tapetest-format :rewind) (device &optional (wait-p t))
  (send device :rewind wait-p))

(defmethod (tapetest-format :unload) (device)
  (send device :unload))

(defmethod (tapetest-format :position-to-append) (&rest ignore)
  (signal 'not-supported :device-object self :operation :position-to-append))

(define-tape-format tapetest-format "TAPETEST")

(compile-flavor-methods tapetest-format)
