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

(defvar *cluster-writes* 0)

(defmacro with-cluster ((cluster part cluster-number) &body body)
  `(let (,cluster)
     (unwind-protect
	 (progn
	   (setq ,cluster (get-cluster ,part ,cluster-number))
	   ,@body)
       (when ,cluster
	 (free-cluster ,cluster)))))

(defstruct (part (:type :named-array))
  name
  unit-name
  unit
  start-block
  last-block
  blocks-per-cluster
  n-clusters
  clusters
  clusters-scan-pointer
  cluster-usage-table
  string-table
  reverse-string-table
  cluster-usage-table-modified
  )

(defselect-incremental (:property part si:named-structure-invoke))

(defun (:select-method (:property part si:named-structure-invoke) :print-self) (ignore part stream ignore ignore)
  (si:printing-random-object (part stream :type)
    (format stream "Part ~s Unit ~s" (part-name part) (part-unit-name part))))
    
(defun create-part (name unit-name unit start-block last-block blocks-per-cluster)
  (make-part :name name
	     :unit-name unit-name
	     :unit unit
	     :start-block start-block
	     :last-block last-block
	     :blocks-per-cluster blocks-per-cluster
	     :n-clusters (floor (- last-block start-block) blocks-per-cluster)
	     :clusters nil
	     :clusters-scan-pointer nil
	     :cluster-usage-table nil
	     :string-table nil
	     :reverse-string-table nil
	     :cluster-usage-table-modified nil
	     ))

(defvar *part-list* nil)

(defun reset ()
  (setq *cluster-writes* 0)
  (setq *part-list* nil))

(defstruct (cluster (:type :named-array))
  part
  number
  modified-p
  rqb
  age
  )

(defselect-incremental (:property cluster si:named-structure-invoke))

(defun (:select-method (:property cluster si:named-structure-invoke) :print-self) (ignore cluster stream ignore ignore)
  (si:printing-random-object (cluster stream :type)
    (format stream "Cluster-number ~d." (cluster-number cluster))
    (when (cluster-modified-p cluster)
      (format stream " *"))
    (format stream " Age ~d." (cluster-age cluster))))

(defun create-cluster (part)
  (make-cluster :part part
		:number nil
		:modified-p nil
		:rqb (si:get-disk-rqb (part-blocks-per-cluster part))
		:age 0
		))

(defconst *clusters-per-part* 5.)

(defun find-part (name unit)
  (check-type name string)
  (check-type unit (or string integer))
  (labels ((same-unit-p (u1 u2)
			(cond ((stringp u1)
			       (cond ((stringp u2)
				      (string-equal u1 u2))
				     (t
				      nil)))
			      (t
			       (cond ((stringp u2)
				      nil)
				     (t
				      (= u1 u2)))))))
    (dolist (part *part-list*)
      (when (and (string-equal (part-name part) name)
		 (same-unit-p (part-unit-name part) unit))
	(return part)))))

(defconst *default-blocks-per-cluster* 4)

(defun start ()
  (let ((part (establish-part "nfil" 0)))
    (build-cluster-usage-table part)
    (make-initial-string-table part)
    part))

(defun establish-part (name unit)
  (check-type name string)
  (check-type unit (or string integer))
  (when (find-part name unit)
    (ferror nil "already know about part ~s on unit ~s" name unit))
  (let ((u (si:decode-unit-argument unit "New File System" nil t)))
    (multiple-value-bind (first-block n-blocks)
	(si:find-disk-partition-for-read name nil u nil nil)
      (when (null first-block)
	(si:dispose-of-unit u)
	(setq u nil)
	(ferror nil "can't find part ~s on unit ~s" name unit))
      (let ((part (create-part name unit u first-block (+ first-block n-blocks) *default-blocks-per-cluster*)))
	(setf (part-clusters part)
	      (loop for i from 0 below *clusters-per-part*
		    collect (create-cluster part)))
	(push part *part-list*)
	part))))

(defun validate-part-and-cluster-number (part cluster-number)
  (check-type part part)
  (when (null (memq part *part-list*))
    (ferror nil "obsolete part ~s" part))
  (when (or (< cluster-number 0)
	    (>= cluster-number (part-n-clusters part)))
    (ferror nil "out of range cluster number ~d" cluster-number)))

(defun validate (part cluster cluster-number)
  (validate-part-and-cluster-number part cluster-number)
  (when (null (memq cluster (part-clusters part)))
    (ferror nil "stray cluster ~s" cluster))
  (when (not (eq (cluster-part cluster) part))
    (ferror nil "unconnected cluster ~s" cluster)))

(defun get-cluster (part cluster-number)
  (validate-part-and-cluster-number part cluster-number)
  (let (cluster first-empty oldest)
    (when (null (part-clusters-scan-pointer part))
      (setf (part-clusters-scan-pointer part) (part-clusters part)))
    (do ((clusters (part-clusters-scan-pointer part) (or (cdr clusters) (part-clusters part)))
	 (first-time t nil))
	((and (not first-time)
	      (eq clusters (part-clusters-scan-pointer part))))
      (cond ((null (cluster-number (car clusters)))
	     (setq first-empty clusters))
	    ((= (cluster-number (car clusters)) cluster-number)
	     (when (not (numberp (cluster-age (car clusters))))
	       (ferror nil "alredy checked out"))
	     (setq cluster (car clusters))
	     (return nil))
	    ((numberp (cluster-age (car clusters)))
	     (incf (cluster-age (car clusters)))
	     (cond ((null oldest)
		    (setq oldest clusters))
		   ((> (cluster-age (car clusters)) (cluster-age (car oldest)))
		    (setq oldest clusters))))))
    (when (null cluster)
      (cond ((null first-empty)
	     (cond ((null oldest)
		    (ferror nil "no clusters"))
		   (t
		    (setq cluster (car oldest))
		    (setf (part-clusters-scan-pointer part) (cdr oldest))
		    (when (cluster-modified-p cluster)
		      (cluster-write part cluster))
		    (cluster-read part cluster cluster-number))))
	    (t
	     (setq cluster (car first-empty))
	     (setf (part-clusters-scan-pointer part) (cdr first-empty))
	     (cluster-read part cluster cluster-number))))
    (setf (cluster-age cluster) :user)
    cluster))

(defun free-cluster (cluster)
  (setf (cluster-age cluster) 0))

(defun cluster-read (part cluster cluster-number)
  (validate part cluster cluster-number)
  (setf (cluster-number cluster) nil)
  (si:disk-read (cluster-rqb cluster)
		(part-unit part)
		(+ (* cluster-number (part-blocks-per-cluster part))
		   (part-start-block part)))
  (setf (cluster-number cluster) cluster-number)
  (setf (cluster-modified-p cluster) nil)
  cluster)

(defun cluster-write (part cluster)
  (incf *cluster-writes*)
  (let ((cluster-number (cluster-number cluster)))
    (validate part cluster cluster-number)
    (let ((disk-address (+ (* cluster-number (part-blocks-per-cluster part))
			   (part-start-block part))))
      (when (or (< disk-address (part-start-block part))
		(> (+ disk-address (si:rqb-npages (cluster-rqb cluster))) (part-last-block part)))
	(ferror nil "out of bounds"))
      (si:disk-write (cluster-rqb cluster) (part-unit part) disk-address))
    (setf (cluster-modified-p cluster) nil)
    cluster))

(defun sync ()
  (dolist (part *part-list*)
    (dolist (cluster (part-clusters part))
      (when (cluster-modified-p cluster)
	(cluster-write part cluster)))))

(defun nfh-ref (array index)
  (let ((d (* index 2)))
    (without-interrupts
      (dpb (aref array (1+ d))
	   (byte 16. 16.)
	   (aref array d)))))

(defsetf nfh-ref (array index) (val)
  `(let ((d (* ,index 2)))
     (without-interrupts
       (aset (ldb (byte 16. 0) ,val) ,array d)
       (aset (ldb (byte 16. 16.) ,val) ,array (1+ d)))
     ,val))

(defconst %nfh-data-start 64.)

(defconst nfh-fields '(nfh-cluster
			nfh-file-id
			nfh-cluster-of-file
			nfh-next-cluster
			nfh-prev-cluster
			nfh-file-size
			nfh-file-original-author
			nfh-file-author
			nfh-creation-date
			nfh-write-date
			nfh-access-date
			nfh-link-count
			nfh-cluster-checksum

			nfh-directory-id
			nfh-directory-name
			nfh-name
			nfh-type
			
			))

(defmacro create-nfh-macros ()
  (when (> (length nfh-fields) %nfh-data-start)
    (ferror nil "oops"))
  (cons 'progn
	(do ((i 0 (1+ i))
	     (fields nfh-fields (cdr fields))
	     result)
	    ((null fields)
	     (reverse result))
	  (push `(defmacro ,(car fields) (array) `(nfh-ref ,array ,,i)) result))))

(create-nfh-macros)

(defun cluster->nfh (cluster)
  (si:rqb-buffer (cluster-rqb cluster)))

(defun initialize-file-system (part)
  (dotimes (i (part-n-clusters part))
    (with-cluster (cluster part i)
      (let ((nfh (cluster->nfh cluster)))
	(setf (nfh-cluster nfh) i)
	(setf (nfh-file-id nfh) 0)
	(setf (nfh-cluster-checksum nfh) 0))
      (setf (cluster-modified-p cluster) t)))
  (sync))

(defconst %cut-bad 0)
(defconst %cut-free 1)
(defconst %cut-reserved 2)
(defconst %cut-used 3)

(defun build-cluster-usage-table (part)
  (let ((cut (make-array (part-n-clusters part) :type :art-2b :initial-element %cut-bad)))
    (dotimes (i (part-n-clusters part))
      (with-cluster (cluster part i)
	(let ((nfh (cluster->nfh cluster)))
	  (cond ((zerop (nfh-file-id nfh))
		 (setf (aref cut i) %cut-free))
		(t
		 (setf (aref cut i) %cut-used))))))
    (setf (part-cluster-usage-table part) cut)
    (setf (part-cluster-usage-table-modified part) nil)))

(defun allocate-cluster (part status)
  (let ((cut (part-cluster-usage-table part)))
    (dotimes (i (array-length cut))
      (when (= (aref cut i) %cut-free)
	(setf (aref cut i) status)
	(setf (part-cluster-usage-table-modified part) t)
	(return i)))))

(defconst *initial-strings*
	  '("ROOT"
	    "TOP"
	    "BACKUP-TOP"
	    "STRING-TABLE"
	    "BACKUP-STRING-TABLE"
	    "CLUSTER-USAGE-TABLE"
	    "BACKUP-CLUSTER-USAGE-TABLE"
	    "DIRECTORY"
	    ))

(defun make-initial-string-table (part)
  (let ((table (make-hash-table :test 'eq))
	(rtable (make-hash-table :test 'string=)))
    (do ((i 1 (1+ i))
	 (strings *initial-strings* (cdr strings)))
	((null strings))
      (puthash i (car strings) table)
      (puthash (car strings) i rtable))
    (setf (part-string-table part) table)
    (setf (part-reverse-string-table part) rtable)))

(defun string-number->string (part string-number)
  (let ((string (gethash string-number (part-string-table part))))
    (when (null string)
      (ferror nil "unknown string number ~d." string-number))
    string))

(defun string->string-number (part string create-p)
  (let ((n (gethash string (part-reverse-string-table part))))
    (cond ((null n)
	   (when (null create-p)
	     (ferror nil "can't find string ~s" string))
	   (ferror nil "don't know how to create yet"))
	  (t
	   n))))

;file id 1 is the root directory, and it starts in block 0
;it is only allowed to contain the following files:
;        2 -> top directory
;        3 -> backup top directory
;        4 -> string table
;        5 -> backup string table
;        6 -> cluster usage table
;        7 -> backup cluster usage table
;        8 -> file-id to first cluster
;        9 -> backup file-id to first cluster

(defun create-root-directory (part)
  (setf (aref (part-cluster-usage-table part) 0) %cut-used)
  (let ((cluster-number 1))
    (setf (aref (part-cluster-usage-table part) cluster-number) %cut-used)
    (with-cluster (cluster part cluster-number)
      (let ((nfh (cluster->nfh cluster)))
	(array-initialize nfh 0)
	(setf (nfh-cluster nfh) 1)
	(setf (nfh-file-id nfh) 1)
	(setf (nfh-cluster-of-file nfh) 0)
	(setf (nfh-next-cluster nfh) 0)
	(setf (nfh-prev-cluster nfh) 0)
	(setf (nfh-file-size nfh) 0)
	(setf (nfh-file-original-author nfh) 0)
	(setf (nfh-file-author nfh) 0)
	(let ((time (get-universal-time)))
	  (setf (nfh-creation-date nfh) time)
	  (setf (nfh-write-date nfh) time)
	  (setf (nfh-access-date nfh) time))
	(setf (nfh-link-count nfh) 1)
	(setf (nfh-cluster-checksum nfh) 0)

	(setf (nfh-directory-id nfh) 1)
	(setf (nfh-directory-name nfh) 0)
	(setf (nfh-name nfh) 0)
	(setf (nfh-type nfh) 0))
      (setf (cluster-modified-p cluster) t)))
  (sync))

(defun print-cluster (part cluster-number)
  (format t "~&Cluster ~d. has:" cluster-number)
  (with-cluster (cluster part cluster-number)
    (print-cluster-data cluster)))

(defun print-cluster-data (cluster)
  (let ((nfh (cluster->nfh cluster)))
    (format t "~&  cluster: ~s" (nfh-cluster nfh))
    (format t "~&  file-id: ~s" (nfh-file-id nfh))
    (format t "~&  next-cluster: ~s" (nfh-next-cluster nfh))
    (format t "~&  prev-cluster: ~s" (nfh-prev-cluster nfh))
    (format t "~&  file-size: ~s" (nfh-file-size nfh))
    (format t "~&  original-author: ~s" (nfh-file-original-author nfh))
    (format t "~&  author: ~s" (nfh-file-author nfh))
    (format t "~&  creation-date: ~\\time\\" (nfh-creation-date nfh))
    (format t "~&  write-date: ~\\time\\" (nfh-write-date nfh))
    (format t "~&  access-date: ~\\time\\" (nfh-access-date nfh))
    (format t "~&  link-count: ~s" (nfh-link-count nfh))
    (format t "~&  checksum: ~s" (nfh-cluster-checksum nfh))
    (format t "~&  directory-id: ~s" (nfh-directory-id nfh))
    (format t "~&  directory-name: ~s" (nfh-directory-name nfh))
    (format t "~&  name: ~s" (nfh-name nfh))
    (format t "~&  type: ~s" (nfh-type nfh))
    ))


;;;;


(defun file-offset->cluster-number (part file-cluster-number file-offset)
  (let ((cluster-number file-cluster-number)
	(prev-cluster-number nil))
    (dotimes (i file-offset)
      (with-cluster (cluster part cluster-number)
	(setq prev-cluster-number cluster-number)
	(setq cluster-number (nfh-next-cluster (cluster->nfh cluster))))
      (cond ((zerop cluster-number)
	     (setq cluster-number nil)
	     (return nil))
	    (t
	     (ferror nil "file doesn't have enough blocks"))))
    (values cluster-number file-cluster-number)))

(defflavor cluster-stream
	 (part
	  data-bytes-per-cluster
	  file-cluster-number
	  (file-size 0)
	  (modified-p nil)
	  (current-cluster nil)
	  (file-offset 0)
	  buffer
	  (buffer-base 0)
	  buffer-limit
	  (buffer-pointer (* %nfh-data-start 4))
	  )
	 ()
  :settable-instance-variables)

(defun make-cluster-stream (part file-cluster-number)
  (make-instance 'cluster-stream
		 :part part
		 :file-cluster-number file-cluster-number))

(defmethod (cluster-stream :after :init) (ignore)
  (when (< file-cluster-number 1)
    (ferror nil "bad first-cluster-number"))
  (setq data-bytes-per-cluster (- (* 1024. (part-blocks-per-cluster part)) (* 4 %nfh-data-start)))
  (with-cluster (cluster part file-cluster-number)
    (setq file-size (nfh-file-size (cluster->nfh cluster))))
  (send self :setup-for-file-offset nil)
  )

(defmethod (cluster-stream :flush-cluster) ()
  (when current-cluster
    (when modified-p
      (cluster-write part current-cluster)
      (setq modified-p nil))
    (free-cluster (prog1 current-cluster (setq current-cluster nil)))))

(defmethod (cluster-stream :sync) ()
  (when (and current-cluster modified-p)
    (setf (cluster-modified-p current-cluster) t)
    (cluster-write part current-cluster)
    (setq modified-p nil)))
  
(defun cluster->user-data (cluster)
  (si:rqb-8-bit-buffer (cluster-rqb cluster)))

(defmethod (cluster-stream :setup-for-file-offset) (extend)
  (send self :sync)
  (when current-cluster
    (free-cluster (prog1 current-cluster (setq current-cluster nil))))
  (multiple-value-bind (offset byte-offset)
      (floor (+ buffer-base buffer-pointer (- (* 4 %nfh-data-start))) data-bytes-per-cluster)
    (setq file-offset offset)
    (setq buffer-base (* file-offset data-bytes-per-cluster))
    (setq buffer-pointer (+ (* 4 %nfh-data-start) byte-offset)))
  (multiple-value-bind (cluster-number prev-cluster-number)
      (file-offset->cluster-number part file-cluster-number file-offset)
    (when (null cluster-number)
      prev-cluster-number
      (when (null extend)
	(ferror nil "ran off end of file"))
      ;;allocate a new cluster, attach it to prev-cluster-number
      ;;and set cluster-number to it
      (ferror nil "foo"))
    (setq current-cluster (get-cluster part cluster-number))
    (setq buffer (cluster->user-data current-cluster))
    (setq buffer-limit (+ (* 4 %nfh-data-start)
			  (min data-bytes-per-cluster
			       (- file-size buffer-base))))))

(defmethod (cluster-stream :seek) (n)
  (when (> n file-size)
    (ferror nil "seek too far"))
  (send self :flush-cluster)
  (setq buffer-base n)
  (setq buffer-pointer (* 4 %nfh-data-start))
  (send self :setup-for-file-offset nil))

(defmethod (cluster-stream :tyi-32) ()
  (let* ((b0 (send self :tyi))
	 (b1 (send self :tyi))
	 (b2 (send self :tyi))
	 (b3 (send self :tyi)))
    (+ b0
       (dpb b1 (byte 8 8) 0)
       (dpb b2 (byte 8 16.) 0)
       (dpb b3 (byte 8 24.) 0))))
	 

(defmethod (cluster-stream :tyi) (&optional eof-action)
  (cond ((>= (+ buffer-base buffer-pointer (- (* 4 %nfh-data-start))) file-size)
	 (when eof-action
	   (ferror 'sys:end-of-file-1 "End of file on ~s." self))
	 nil)
	(t
	 (when (or (null current-cluster)
		   (>= buffer-pointer buffer-limit))
	   (send self :setup-for-file-offset nil))
	 (when (>= buffer-pointer buffer-limit)
	   (ferror nil "didn't get any new data"))
	 (aref buffer (prog1 buffer-pointer (incf buffer-pointer))))))

(defmethod (cluster-stream :listen) ()
  (< (+ buffer-base buffer-pointer) file-size))

(defmethod (cluster-stream :read-until-eof) ()
  (send self :seek file-size))

(defmethod (cluster-stream :clear-input) ()
  )

(defmethod (cluster-stream :close) (&optional abort)
  abort
  (send self :flush-cluster)
  (with-cluster (cluster part file-cluster-number)
    (let ((nfh (cluster->nfh cluster)))
      (let ((old-file-size (nfh-file-size nfh)))
	(cond ((< file-size old-file-size)
	       (ferror nil "file-size shrunk??"))
	      ((= file-size old-file-size))
	      (t
	       (setf (nfh-file-size nfh) file-size)
	       (setf (cluster-modified-p cluster) t)))))))

(defmethod (cluster-stream :get-buffer) (&optional eof-action extend)
  (cond ((>= (+ buffer-base buffer-pointer (- (* 4 %nfh-data-start))) file-size)
	 (when eof-action
	   (ferror 'sys:end-of-file-1 "End of file on ~s." self))
	 nil)
	(t
	 (when (or (null current-cluster)
		   (>= buffer-pointer buffer-limit))
	   (send self :setup-for-file-offset extend))
	 (when (>= buffer-pointer buffer-limit)
	   (ferror nil "didn't get any new data"))
	 (values buffer buffer-pointer buffer-limit))))

(defmethod (cluster-stream :advance-pointer) (pointer)
  (when (or (< pointer buffer-pointer)
	    (> pointer buffer-limit))
    (ferror nil "out of range"))
  (setq buffer-pointer pointer))

(defmethod (cluster-stream :read-pointer) ()
  (+ buffer-base buffer-pointer (- (* 4 %nfh-data-start))))

(defmethod (cluster-stream :return-buffer) (buf end modified)
  buf
  (when modified
    (setq modified-p t))
  (when (or (< end buffer-pointer)
	    (> end buffer-limit))
    (ferror nil "out of range"))
  (setq buffer-pointer end))

(defmethod (cluster-stream :string-in) (eof string &optional (start 0) end)
  (when (null end)
    (setq end (array-length string)))
  (do ()
      (())
    (when (>= start end)
      (when (array-has-fill-pointer-p string)
	(setf (fill-pointer string) start))
      (return (values start (not (= start end)))))
    (cond ((>= (+ buffer-base buffer-pointer (- (* 4 %nfh-data-start))) file-size)
	   (when eof
	     (ferror 'sys:end-of-file-1 "End of file on ~s." self))
	   (when (array-has-fill-pointer-p string)
	     (setf (fill-pointer string) start))
	   (return (values nil t)))
	  (t
	   (when (or (null current-cluster)
		     (>= buffer-pointer buffer-limit))
	     (send self :setup-for-file-offset nil))
	   (when (>= buffer-pointer buffer-limit)
	     (ferror nil "didn't get any new data"))
	   (let ((bytes (min (- end start)
			     (- buffer-limit buffer-pointer))))
	     (copy-array-portion buffer buffer-pointer (setq buffer-pointer (+ buffer-pointer bytes))
				 string start (setq start (+ start bytes))))))))

(defmethod (cluster-stream :tyo-32) (word)
  (send self :tyo (ldb (byte 8 0) word))
  (send self :tyo (ldb (byte 8 8) word))
  (send self :tyo (ldb (byte 8 16.) word))
  (send self :tyo (ldb (byte 8 24.) word))
  word)
  

(defmethod (cluster-stream :tyo) (c)
  (when (null current-cluster)
    (send self :setup-for-file-offset t))
  (when (>= buffer-pointer buffer-limit)
    (cond ((< buffer-limit (* 1024. (part-blocks-per-cluster part)))
	   (incf buffer-limit))
	  (t
	   (ferror nil "didn't get any space")))
    (incf file-size))
  (when (>= buffer-pointer buffer-limit)
    (ferror nil "didn't get any room"))
  (setf (aref buffer (prog1 buffer-pointer (incf buffer-pointer))) c)
  (setq modified-p t)
  c)

(defmethod (cluster-stream :force-output) ()
  )

(defmethod (cluster-stream :string-out) (string &optional (start 0) end)
  (when (null end)
    (setq end (array-active-length string)))
  (when (null current-cluster)
    (send self :setup-for-file-offset t))
  (do ()
      ((< start end))
    (when (>= buffer-pointer buffer-limit)
      (cond ((< buffer-limit (* 1024. (part-blocks-per-cluster part)))
	     (let ((new-limit (min (+ buffer-limit (- end start))
				   (* 1024. (part-blocks-per-cluster part)))))
	       (incf file-size (- new-limit buffer-limit))
	       (setq buffer-limit new-limit)))
	    (t
	     (send self :setup-for-file-offset t)
	     (incf file-size (min (- end start)
				  (- buffer-limit buffer-pointer)))
	     )))
    (when (>= buffer-pointer buffer-limit)
      (ferror nil "didn't get any room"))
    (let ((bytes (min (- end start)
		      (- buffer-limit buffer-pointer))))
      (copy-array-portion string start (setq start (+ start bytes))
			  buffer buffer-pointer (setq buffer-pointer (+ buffer-pointer bytes)))
      ))
  (setq modified-p t))

(defun get-32-bits-from-string (string index)
  (+ (aref string index)
     (dpb (aref string (+ index 1)) (byte 8 8) 0)
     (dpb (aref string (+ index 2)) (byte 8 16.) 0)
     (dpb (aref string (+ index 3)) (byte 8 24.) 0)))

(defsetf get-32-bits-from-string (string index) (val)
  (once-only (string index val)
    `(progn
       (aset (ldb (byte 8 0) ,val) ,string ,index)
       (aset (ldb (byte 8 8) ,val) ,string (+ ,index 1))
       (aset (ldb (byte 8 16.) ,val) ,string (+ ,index 2))
       (aset (ldb (byte 8 24.) ,val) ,string (+ ,index 3)))))

(defun primitive-lookup (op file-id part dir-cluster-number name type version)
  (when (and (eq version :next)
	     (not (eq op :create)))
    (ferror nil "version :NEXT must only be used with :CREATE"))
  (with-open-stream (dir-stream (make-cluster-stream part dir-cluster-number))
    (when (not (zerop (remainder (send dir-stream :file-size) 16.)))
      (ferror nil "odd length directory"))
    (let (first-empty found-it (max-version 0) position)
      (do-named top
		()
		(())
	(multiple-value-bind (buffer start end)
	    (send dir-stream :get-buffer)
	  (cond ((null buffer)
		 (return-from top nil))
		((or (not (zerop (remainder start 16.)))
		     (not (zerop (remainder end 16.))))
		 (ferror nil "bad directory buffer"))
		(t
		 (do ((es start (+ es 16.)))
		     ((>= es end)
		      (send dir-stream :advance-pointer end))
		   (cond ((zerop (get-32-bits-from-string buffer es))
			  (when (null first-empty)
			    (send dir-stream :advance-pointer es)
			    (setq first-empty (send dir-stream :read-pointer))))
			 ((and (= (get-32-bits-from-string buffer (+ es 4)) name)
			       (= (get-32-bits-from-string buffer (+ es 8)) type))
			  (let ((this-version (get-32-bits-from-string buffer (+ es 12.))))
			    (cond ((memq version '(:newest :next))
				   (when (> this-version max-version)
				     (setq max-version this-version)
				     (send dir-stream :advance-pointer es)
				     (setq position (send dir-stream :read-pointer))))
				  ((= this-version version)
				   (setq found-it t)
				   (send dir-stream :advance-pointer es)
				   (setq position (send dir-stream :read-pointer))
				   (return-from top nil)))))))))))

      (ecase op
	(:delete
	 (when found-it
	   (send dir-stream :seek position)
	   (send dir-stream :tyo-32 0))
	 found-it)
	(:find
	 (when position
	   (let (id version)
	     (send dir-stream :seek position)
	     (setq id (send dir-stream :tyi-32))
	     (send dir-stream :seek (+ position 12.))
	     (setq version (send dir-stream :tyi-32))
	     (values id version))))
	(:create
	 (cond ((not found-it)
		(cond ((null first-empty)
		       (send dir-stream :seek (send dir-stream :file-size)))
		      (t
		       (send dir-stream :seek first-empty)))
		(when (eq version :newest)
		  (setq version max-version))
		(when (eq version :next)
		  (when (> max-version 16000.)
		    (ferror nil "version number too big"))
		  (setq version (+ max-version 1)))
		(send dir-stream :tyo-32 file-id)
		(send dir-stream :tyo-32 name)
		(send dir-stream :tyo-32 type)
		(send dir-stream :tyo-32 version)
		(values file-id version))
	       (t
		nil)))))))

(defun print-directory (part cluster-number)
  (with-open-stream (dir-stream (make-cluster-stream part cluster-number))
    (let* ((dirent (make-array 8 :type :art-16b))
	   (dirent-8 (make-array 16. :type :art-8b :displaced-to dirent)))
      (do ()
	  (())
	(multiple-value-bind (nil eof-p)
	    (send dir-stream :string-in nil dirent-8)
	  (when eof-p (return nil))
	  (format t "~&~8d ~8d ~8d ~8d"
		  (nfh-ref dirent 0)
		  (nfh-ref dirent 1)
		  (nfh-ref dirent 2)
		  (nfh-ref dirent 3)))))))

(defun lookup-internal (part start-dir-number dirs name type version)
  (do ((cd start-dir-number)
       (files dirs (cdr files)))
      ((null files)
       )
    (let ((file-id (primitive-lookup :find nil part cd (car files) 0 0)))
      
      
       
    