;;; -*- lisp -*-
;;;
;;; **********************************************************************
;;; This code was written by Paul Foley and has been placed in the public
;;; domain.
;;;

;;; Sbcl port by Rudi Schlatte.

(in-package "SB-SIMPLE-STREAMS")

;;;
;;; **********************************************************************
;;;
;;; Base class and generic function definitions for simple-streams

;;; See chapter
;;; 12.2 Strategy descriptions necessary for encapsulation
;;; in the Franz documentation for a description of the j-xxx-fn slots.

;;;; Types for buffer and strategy functions

(deftype simple-stream-buffer ()
  '(or sb-sys:system-area-pointer (sb-kernel:simple-unboxed-array (*))))

(deftype blocking ()
  '(member t nil :bnb))

(deftype j-listen-fn ()
  '(function (simple-stream) boolean))

(deftype j-read-char-fn ()
  '(function (simple-stream boolean t boolean) t)) ; may return EOF-VALUE

(deftype j-read-chars-fn ()
  '(function (simple-stream string (or character null) fixnum fixnum blocking)
             (values fixnum &optional (member nil t :eof))))

(deftype j-write-char-fn ()
  '(function ((or character null) simple-stream) (or character null)))

(deftype j-write-chars-fn ()
  '(function (string simple-stream fixnum fixnum) t)) ; return chars-written?

(deftype j-unread-char-fn ()
  '(function (simple-stream t) t)) ; "relaxed" arg is boolean?  what return?

;;;; Base simple-stream classes

(def-stream-class simple-stream (standard-object stream)
  (;; instance flags (not a normal slot in Allegro CL)
   (%flags :initform 0 :type fixnum)
   (plist :initform nil :type list :accessor stream-plist)

   ;; Strategy slots.  See section 12.2 of streams.htm for function
   ;; signatures and possible side-effects.

   ;; A function that determines if one character can be successfully
   ;; read from stream.
   (j-listen :initform #'sb-kernel:ill-in :type j-listen-fn)
   ;; A function that reads one character.
   (j-read-char :initform #'sb-kernel:ill-in :type j-read-char-fn)
   ;; A function that reads characters into a string.
   (j-read-chars :initform #'sb-kernel:ill-in :type j-read-chars-fn)
   ;; A function that writes one character.
   (j-write-char :initform #'sb-kernel:ill-out :type j-write-char-fn)
   ;; A function that writes characters from a string into the stream.
   (j-write-chars :initform #'sb-kernel:ill-out :type j-write-chars-fn)
   ;; A function that unreads the last character read.
   (j-unread-char :initform #'sb-kernel:ill-in :type j-unread-char-fn)

   ;; Other slots

   ;; TODO: find out what this one does
   (oc-state :initform nil)
   ;; TODO: find out what this one does
   (co-state :initform nil)
   (external-format :initform (find-external-format :default))

   ;; A fixnum (denoting a valid file descriptor), a stream, or nil if
   ;; the stream is not open for input.
   (input-handle :initform nil :initarg :input-handle
                 :type (or null fixnum stream)
                 :accessor stream-input-handle)
   ;; A fixnum (denoting a valid file descriptor), a stream, or nil if
   ;; the stream is not open for output.
   (output-handle :initform nil :initarg :output-handle
                  :type (or null fixnum stream)
                  :accessor stream-output-handle)
   (control-in :initform nil :type (or null simple-vector))
   (control-out :initform nil :type (or null simple-vector))

   ;; a stream, allowing for composing external formats (see
   ;; streams.htm, section 12.5) TODO: document this better
   (melded-stream :type (or null simple-stream))
   ;; a stream, allowing for composing external formats (see
   ;; streams.htm, section 12.5) TODO: document this better
   (melding-base :type (or null simple-stream))

   ;; Number of octets the last read-char operation consumed TODO:
   ;; document this better; what is the difference to
   ;; last-char-read-size ?
   (encapsulated-char-read-size :initform 0 :type fixnum)
   ;; Number of octets the last read-char operation consumed
   (last-char-read-size :initform 0 :type fixnum)
   (charpos :initform 0 :type (or null integer)
            :accessor stream-line-column)
   (record-end :initform nil :type (or null fixnum))

  ;; Input/output buffer.
   (buffer :initform nil :type (or simple-stream-buffer null))
   ;; Current position in buffer.
   (buffpos :initform 0 :type fixnum)
   ;; Maximum valid position in buffer, or -1 on eof.
   (buffer-ptr :initform 0 :type fixnum)
   (buf-len :initform 0 :type fixnum)

   (pending :initform nil :type list)
   (handler :initform nil :type (or null sb-impl::handler))))

(def-stream-class single-channel-simple-stream (simple-stream)
  (;; the "dirty" flag -- if this is > 0, write out buffer contents
   ;; before changing position; see flush-buffer
   (mode :initform 0 :type fixnum)))

(def-stream-class dual-channel-simple-stream (simple-stream)
  (;; Output buffer.
   (out-buffer :initform nil :type (or simple-stream-buffer null))
   ;; Current position in output buffer.
   (outpos :initform 0 :type fixnum)
   ;; Buffer length (one greater than maximum output buffer index)
   (max-out-pos :initform 0 :type fixnum)))

;;; A stream with a string as buffer.
(def-stream-class string-simple-stream (simple-stream string-stream)
  ())


;;; ======================================================


;;;
;;; DEVICE-LEVEL FUNCTIONS
;;;

(defgeneric device-open (stream options))

(defgeneric device-close (stream abort))

(defgeneric device-buffer-length (stream))

(defgeneric device-file-position (stream))

(defgeneric (setf device-file-position) (value stream))

(defgeneric device-file-length (stream))

(defgeneric device-read (stream buffer start end blocking))

(defgeneric device-clear-input (stream buffer-only))

(defgeneric device-write (stream buffer start end blocking))

(defgeneric device-clear-output (stream))

(defgeneric device-finish-record (stream blocking action))


(defmethod shared-initialize :after ((instance simple-stream) slot-names
                                     &rest initargs &key &allow-other-keys)
  (declare (ignore slot-names))
  (unless (slot-boundp instance 'melded-stream)
    (setf (slot-value instance 'melded-stream) instance)
    (setf (slot-value instance 'melding-base) instance))
  (unless (device-open instance initargs)
    (device-close instance t)))


(defmethod print-object ((object simple-stream) stream)
  (print-unreadable-object (object stream :type nil :identity nil)
    (cond ((not (any-stream-instance-flags object :simple))
           (princ "Invalid " stream))
          ((not (any-stream-instance-flags object :input :output))
           (princ "Closed " stream)))
    (format stream "~:(~A~)" (type-of object))))

;;; This takes care of the things all device-close methods have to do,
;;; regardless of the type of simple-stream
(defmethod device-close :around ((stream simple-stream) abort)
  (with-stream-class (simple-stream stream)
    (when (any-stream-instance-flags stream :input :output)
      (when (any-stream-instance-flags stream :output)
        (ignore-errors (if abort
                           (clear-output stream)
                           (finish-output stream))))
      (call-next-method)
      (setf (sm input-handle stream) nil
            (sm output-handle stream) nil)
      (remove-stream-instance-flags stream :input :output)
      (sb-ext:cancel-finalization stream)
      ;; This sets all readers and writers to error-raising functions
      (setf (stream-external-format stream) :void))))

(defmethod device-close ((stream simple-stream) abort)
  (declare (ignore abort))
  t)

(defmethod device-buffer-length ((stream simple-stream))
  4096)

(defmethod device-file-position ((stream simple-stream))
  (with-stream-class (simple-stream stream)
    (sm buffpos stream)))

(defmethod (setf device-file-position) (value (stream simple-stream))
  (with-stream-class (simple-stream stream)
    (setf (sm buffpos stream) value)))

(defmethod device-file-length ((stream simple-stream))
  nil)

(defgeneric (setf stream-external-format) (value stream))

(defmethod (setf stream-external-format) :before (value (stream simple-stream))
  ;; (unless (eq value (sm external-format stream))
  ;;   flush out the existing external-format
  )

(defmethod (setf stream-external-format) :after
    (ef (stream single-channel-simple-stream))
  (compose-encapsulating-streams stream ef)
  (install-single-channel-character-strategy (melding-stream stream) ef nil))

(defmethod (setf stream-external-format) :after
    (ef (stream dual-channel-simple-stream))
  (compose-encapsulating-streams stream ef)
  (install-dual-channel-character-strategy (melding-stream stream) ef))


(defmethod device-read ((stream single-channel-simple-stream) buffer
                        start end blocking)
  (read-octets stream buffer start end blocking))

(defmethod device-read ((stream dual-channel-simple-stream) buffer
                        start end blocking)
  (read-octets stream buffer start end blocking))

(defmethod device-clear-input ((stream simple-stream) buffer-only)
  (declare (ignore buffer-only))
  nil)

(defmethod device-write ((stream single-channel-simple-stream) buffer
                         start end blocking)
  ;; buffer may be :flush to force/finish-output
  (when (or (and (null buffer) (not (eql start end)))
            (eq buffer :flush))
    (with-stream-class (single-channel-simple-stream stream)
      (setf buffer (sm buffer stream))
      (setf end (sm buffpos stream))))
  (write-octets stream buffer start end blocking))

(defmethod device-write ((stream dual-channel-simple-stream) buffer
                         start end blocking)
  ;; buffer may be :flush to force/finish-output
  (when (or (and (null buffer) (not (eql start end)))
            (eq buffer :flush))
    (with-stream-class (dual-channel-simple-stream stream)
      (setf buffer (sm out-buffer stream))
      (setf end (sm outpos stream))))
  (write-octets stream buffer start end blocking))

(defmethod device-clear-output ((stream simple-stream))
  nil)
