;;; -*- Syntax: Ansi-Common-Lisp; Base: 10; Mode: lisp -*-
;;;; *************************************************************************
;;;; FILE IDENTIFICATION
;;;;
;;;; Name:          byte-stream.lisp
;;;; Purpose:       Byte array input/output streams
;;;; Programmer:    Kevin M. Rosenberg
;;;; Date Started:  June 2003
;;;;
;;;; Works for CMUCL, SBCL, and AllergoCL only
;;;;
;;;; This file, part of KMRCL, is Copyright (c) 2003 by Kevin M. Rosenberg
;;;; and by onShore Development, Inc.
;;;;
;;;; KMRCL users are granted the rights to distribute and use this software
;;;; as governed by the terms of the Lisp Lesser GNU Public License
;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
;;;; *************************************************************************

(in-package #:kmrcl)

;; Intial CMUCL version by OnShored. Ported to AllegroCL, SBCL by Kevin Rosenberg

#+sbcl
(eval-when (:compile-toplevel :load-toplevel :execute)
  (when (sb-ext:without-package-locks
            (sb-pcl::structure-class-p
             (find-class (intern "FILE-STREAM" "SB-IMPL"))))
    (push :old-sb-file-stream cl:*features*)))

#+(or cmu sbcl)
(progn
(defstruct (byte-array-output-stream
             (:include #+cmu system:lisp-stream
                       #+(and sbcl old-sb-file-stream) sb-impl::file-stream
                       #+(and sbcl (not old-sb-file-stream)) sb-sys:fd-stream
                       (bout #'byte-array-bout)
                       (misc #'byte-array-out-misc))
             (:print-function %print-byte-array-output-stream)
             (:constructor make-byte-array-output-stream ()))
  ;; The buffer we throw stuff in.
  (buffer (make-array 128 :element-type '(unsigned-byte 8)))
  ;; Index of the next location to use.
  (index 0 :type fixnum))

(defun %print-byte-array-output-stream (s stream d)
  (declare (ignore s d))
  (write-string "#<Byte-Array-Output Stream>" stream))

(setf (documentation 'make-binary-output-stream 'function)
  "Returns an Output stream which will accumulate all output given it for
   the benefit of the function Get-Output-Stream-Data.")

(defun byte-array-bout (stream byte)
  (let ((current (byte-array-output-stream-index stream))
        (workspace (byte-array-output-stream-buffer stream)))
    (if (= current (length workspace))
        (let ((new-workspace (make-array (* current 2) :element-type '(unsigned-byte 8))))
          (replace new-workspace workspace)
          (setf (aref new-workspace current) byte)
          (setf (byte-array-output-stream-buffer stream) new-workspace))
        (setf (aref workspace current) byte))
    (setf (byte-array-output-stream-index stream) (1+ current))))

(defun byte-array-out-misc (stream operation &optional arg1 arg2)
  (declare (ignore arg2))
  (case operation
    (:file-position
     (if (null arg1)
         (byte-array-output-stream-index stream)))
    (:element-type '(unsigned-byte 8))))

(defun get-output-stream-data (stream)
  "Returns an array of all data sent to a stream made by
Make-Byte-Array-Output-Stream since the last call to this function and
clears buffer."
  (declare (type byte-array-output-stream stream))
    (prog1
        (dump-output-stream-data stream)
      (setf (byte-array-output-stream-index stream) 0)))

(defun dump-output-stream-data (stream)
  "Returns an array of all data sent to a stream made by
Make-Byte-Array-Output-Stream since the last call to this function."
  (declare (type byte-array-output-stream stream))
  (let* ((length (byte-array-output-stream-index stream))
         (result (make-array length :element-type '(unsigned-byte 8))))
    (replace result (byte-array-output-stream-buffer stream))
    result))

) ; progn


#+sbcl
(eval-when (:compile-toplevel :load-toplevel :execute)
  (sb-ext:without-package-locks
      (sb-ext:without-package-locks
          (defvar *system-copy-fn* (intern "SYSTEM-AREA-UB8-COPY" "SB-KERNEL"))
          (defconstant +system-copy-multiplier+
            (if (fboundp (intern "COPY-UB8-FROM-SYSTEM-AREA" "SB-KERNEL"))
                sb-vm:n-byte-bits
                1)))))

#+(or cmu sbcl)
(progn
  (defstruct (byte-array-input-stream
             (:include #+cmu system:lisp-stream
                       ;;#+sbcl sb-impl::file-stream
                       #+(and sbcl old-sb-file-stream) sb-impl::file-stream
                       #+(and sbcl (not old-sb-file-stream)) sb-sys:fd-stream
                       (in #'byte-array-inch)
                       (bin #'byte-array-binch)
                       (n-bin #'byte-array-stream-read-n-bytes)
                       (misc #'byte-array-in-misc))
             (:print-function %print-byte-array-input-stream)
                                        ;(:constructor nil)
             (:constructor internal-make-byte-array-input-stream
                           (byte-array current end)))
  (byte-array nil :type vector)
  (current nil)
  (end nil))


(defun %print-byte-array-input-stream (s stream d)
  (declare (ignore s d))
  (write-string "#<Byte-Array-Input Stream>" stream))

(defun byte-array-inch (stream eof-errorp eof-value)
  (let ((byte-array (byte-array-input-stream-byte-array stream))
        (index (byte-array-input-stream-current stream)))
    (cond ((= index (byte-array-input-stream-end stream))
           #+cmu
           (eof-or-lose stream eof-errorp eof-value)
           #+sbcl
           (sb-impl::eof-or-lose stream eof-errorp eof-value)
           )
          (t
           (setf (byte-array-input-stream-current stream) (1+ index))
           (aref byte-array index)))))

(defun byte-array-binch (stream eof-errorp eof-value)
  (let ((byte-array (byte-array-input-stream-byte-array stream))
        (index (byte-array-input-stream-current stream)))
    (cond ((= index (byte-array-input-stream-end stream))
           #+cmu
           (eof-or-lose stream eof-errorp eof-value)
           #+sbcl
           (sb-impl::eof-or-lose stream eof-errorp eof-value)
           )
          (t
           (setf (byte-array-input-stream-current stream) (1+ index))
           (aref byte-array index)))))

(defun byte-array-stream-read-n-bytes (stream buffer start requested eof-errorp)
  (declare (type byte-array-input-stream stream))
  (let* ((byte-array (byte-array-input-stream-byte-array stream))
         (index (byte-array-input-stream-current stream))
         (available (- (byte-array-input-stream-end stream) index))
         (copy (min available requested)))
    (when (plusp copy)
      (setf (byte-array-input-stream-current stream)
        (+ index copy))
      #+cmu
      (system:without-gcing
       (system::system-area-copy (system:vector-sap byte-array)
                         (* index vm:byte-bits)
                         (if (typep buffer 'system::system-area-pointer)
                             buffer
                             (system:vector-sap buffer))
                         (* start vm:byte-bits)
                         (* copy vm:byte-bits)))
      #+sbcl
      (sb-sys:without-gcing
       (funcall *system-copy-fn* (sb-sys:vector-sap byte-array)
                         (* index +system-copy-multiplier+)
                         (if (typep buffer 'sb-sys::system-area-pointer)
                             buffer
                             (sb-sys:vector-sap buffer))
                         (* start +system-copy-multiplier+)
                         (* copy +system-copy-multiplier+))))
    (if (and (> requested copy) eof-errorp)
        (error 'end-of-file :stream stream)
        copy)))

(defun byte-array-in-misc (stream operation &optional arg1 arg2)
  (declare (ignore arg2))
  (case operation
    (:file-position
     (if arg1
         (setf (byte-array-input-stream-current stream) arg1)
         (byte-array-input-stream-current stream)))
    (:file-length (length (byte-array-input-stream-byte-array stream)))
    (:unread (decf (byte-array-input-stream-current stream)))
    (:listen (or (/= (the fixnum (byte-array-input-stream-current stream))
                     (the fixnum (byte-array-input-stream-end stream)))
                 :eof))
    (:element-type 'base-char)))

(defun make-byte-array-input-stream (buffer &optional (start 0) (end (length buffer)))
  "Returns an input stream which will supply the bytes of BUFFER between
  Start and End in order."
  (internal-make-byte-array-input-stream buffer start end))

) ;; progn

(eval-when (:compile-toplevel :load-toplevel :execute)
  (setq cl:*features* (delete :old-sb-file-stream cl:*features*)))

;;; Simple streams implementation by Kevin Rosenberg

#+allegro
(progn

  (defclass extendable-buffer-output-stream (excl:buffer-output-simple-stream)
    ()
    )

  (defun make-byte-array-output-stream ()
    "Returns an Output stream which will accumulate all output given it for
   the benefit of the function Get-Output-Stream-Data."
    (make-instance 'extendable-buffer-output-stream
      :buffer (make-array 128 :element-type '(unsigned-byte 8))
      :external-form :octets))

  (defun get-output-stream-data (stream)
    "Returns an array of all data sent to a stream made by
Make-Byte-Array-Output-Stream since the last call to this function
and clears buffer."
    (prog1
        (dump-output-stream-data stream)
      (file-position stream 0)))

  (defun dump-output-stream-data (stream)
    "Returns an array of all data sent to a stream made by
Make-Byte-Array-Output-Stream since the last call to this function."
    (force-output stream)
    (let* ((length (file-position stream))
           (result (make-array length :element-type '(unsigned-byte 8))))
      (replace result (slot-value stream 'excl::buffer))
      result))

  (excl::without-package-locks
   (defmethod excl:device-extend ((stream extendable-buffer-output-stream)
                                  need action)
     (declare (ignore action))
     (let* ((len (file-position stream))
            (new-len (max (+ len need) (* 2 len)))
            (old-buf (slot-value stream 'excl::buffer))
            (new-buf (make-array new-len :element-type '(unsigned-byte 8))))
       (declare (fixnum len)
                (optimize (speed 3) (safety 0)))
       (dotimes (i len)
         (setf (aref new-buf i) (aref old-buf i)))
       (setf (slot-value stream 'excl::buffer) new-buf)
       (setf (slot-value stream 'excl::buffer-ptr) new-len)
       )
     t))

)

#+allegro
(progn
  (defun make-byte-array-input-stream (buffer &optional (start 0)
                                                        (end (length buffer)))
    (excl:make-buffer-input-stream buffer start end :octets))
  ) ;; progn
