;;; -*- 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")

;;;
;;; **********************************************************************
;;;
;;; Socket-simple-stream and socket-base-simple-stream

(def-stream-class socket-simple-stream (dual-channel-simple-stream)
  (;; keep the socket around; it could be handy e.g. for querying peer
   ;; host/port
   (socket :initform nil :type (or sb-bsd-sockets:socket null)
           :initarg :socket)))

(defmethod print-object ((object socket-simple-stream) stream)
  (print-unreadable-object (object stream :type nil :identity nil)
    (with-stream-class (socket-simple-stream object)
      (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))
      (when (any-stream-instance-flags object :input :output)
        (multiple-value-bind (host port)
            (sb-bsd-sockets:socket-peername (sm socket object))
          (format stream " connected to host ~S, port ~S" host port))))))

(def-stream-class socket-base-simple-stream (dual-channel-simple-stream)
  ())

(defmethod device-open ((stream socket-simple-stream) options)
  (let* ((remote-host (getf options :remote-host))
         (remote-port (getf options :remote-port))
         (socket (make-instance 'sb-bsd-sockets:inet-socket
                                :type :stream :protocol :tcp)))
    (unless (and remote-host remote-port)
      (error "device-open on ~S requires :remote-host and :remote-port arguments"
             'socket-simple-stream))
    (with-stream-class (socket-simple-stream stream)
      (ecase (getf options :direction :input)
        (:input (add-stream-instance-flags stream :input))
        (:output (add-stream-instance-flags stream :output))
        (:io (add-stream-instance-flags stream :input :output)))
      (setf (sm socket stream) socket)
      (sb-bsd-sockets:socket-connect socket remote-host remote-port)
      (let ((fd (sb-bsd-sockets:socket-file-descriptor socket)))
        (when fd
          (add-stream-instance-flags stream :dual :simple)
          (when (any-stream-instance-flags stream :input)
            (setf (sm input-handle stream) fd)
            (unless (sm buffer stream)
              (let ((length (device-buffer-length stream)))
                (setf (sm buffer stream) (allocate-buffer length)
                      (sm buffpos stream) 0
                      (sm buffer-ptr stream) 0
                      (sm buf-len stream) length))))
          (when (any-stream-instance-flags stream :output)
            (setf (sm output-handle stream) fd)
            (unless (sm out-buffer stream)
              (let ((length (device-buffer-length stream)))
                (setf (sm out-buffer stream) (allocate-buffer length)
                      (sm outpos stream) 0
                      (sm max-out-pos stream) length)))
            (setf (sm control-out stream) *std-control-out-table*))
          (sb-ext:cancel-finalization socket)
          (sb-ext:finalize stream
                           (lambda ()
                             (sb-unix:unix-close fd)
                             (format *debug-io*
                                     "~&;;; ** closed socket (fd ~D)~%" fd)))
          ;; this should be done with (setf stream-external-format)
          (let ((efmt (getf options :external-format :default)))
            (compose-encapsulating-streams stream efmt)
            (install-dual-channel-character-strategy (melding-stream stream)
                                                     efmt))
          stream)))))

(defmethod device-close ((stream socket-simple-stream) abort)
  (with-stream-class (socket-simple-stream stream)
    (sb-unix:unix-close (or (sm input-handle stream)
                            (sm output-handle stream)))
    (when (sm buffer stream)
      (free-buffer (sm buffer stream))
      (setf (sm buffer stream) nil))
    (when (sm out-buffer stream)
      (free-buffer (sm out-buffer stream))
      (setf (sm out-buffer stream) nil))
    (sb-ext:cancel-finalization stream)
    t))

(defmethod device-open ((stream socket-base-simple-stream) options)
  #| do something |#
  stream)

(defmethod device-write ((stream socket-base-simple-stream) buffer
                         start end blocking)
  ;; @@2
  (call-next-method))