;;; OpenMCL layer for ACL sockets.
;;; Most everything is already there, just needs to be in the socket package.
;;;
;;; John DeSoi, Ph.D. desoi@users.sourceforget.net

(in-package :acl-compat.socket)

(eval-when (:compile-toplevel :load-toplevel :execute)
  (shadowing-import
   '(;ccl:make-socket                    ; use our own version
     ccl:accept-connection
     ccl:dotted-to-ipaddr 
     ccl:ipaddr-to-hostname
     ccl:lookup-hostname
     ccl:remote-host 
     ccl:remote-port 
     ccl:local-host 
     ccl:local-port))
)

(eval-when (:compile-toplevel :load-toplevel :execute)
  (export
   '(accept-connection
     ipaddr-to-dotted 
     dotted-to-ipaddr 
     ipaddr-to-hostname
     lookup-hostname
     remote-host 
     remote-port 
     local-host 
     local-port
     socket-control))
  )


(defclass server-socket ()
  ((socket :initarg :socket :reader socket
           :initform (error "No value supplied for socket"))
   (port :initarg :port
	 :reader port
         :initform (error "No value supplied for port"))))


(defmethod print-object ((socket server-socket) stream)
  (print-unreadable-object (socket stream :type t :identity nil)
    (format stream "listening on port ~d" (port socket))))


(defmethod accept-connection ((server-socket server-socket)
                              &key (wait t) stream-args)
  "Return a bidirectional stream connected to socket."
  (let ((stream (accept-connection (socket server-socket)
                                   :wait wait :stream-args stream-args)))
    (when stream (make-chunked-stream stream))))


(defun make-socket (&rest args
                    &key (connect :active) port
                    &allow-other-keys)
  "Return a stream connected to remote-host if connect is :active, or
something listening on local-port that can be fed to accept-connection
if connect is :passive.
"
  (let ((socket-or-stream (apply #'ccl:make-socket args)))
    (if (eq connect :active)
        (make-chunked-stream socket-or-stream)
        (make-instance 'server-socket :socket socket-or-stream :port port))))


(defmethod close ((server-socket server-socket) &key abort)
  "Kill a passive (listening) socket.  (Active sockets are actually
streams and handled by their close methods."
  (declare (ignore abort))
  (close (socket server-socket)))

(defmethod local-host ((server-socket server-socket))
  (local-host (socket server-socket)))

(defmethod local-port ((server-socket server-socket))
  (local-port (socket server-socket)))

(defmethod ccl:stream-write-vector
    ((stream gray-stream::buffered-bivalent-stream) vector start end)
  (declare (fixnum start end))
  (let ((fn (gray-stream::%writer-function-for-sequence vector)))
    (do* ((i start (1+ i)))
         ((= i end))
      (declare (fixnum i))
      (funcall fn stream (ccl:uvref vector i)))))

(defmethod ccl:stream-read-vector
    ((stream gray-stream::buffered-bivalent-stream) vector start end)
  (declare (fixnum start end))
  (let ((fn (gray-stream::%reader-function-for-sequence vector)))
    (do* ((i start (1+ i)))
         ((= i end) end)
      (declare (fixnum i))
      (let* ((b (funcall fn stream)))
        (if (eq b :eof)
            (return i)
            (setf (ccl:uvref vector i) b))))))

(defclass chunked-stream (de.dataheaven.chunked-stream-mixin::chunked-stream-mixin
                          gray-stream::buffered-bivalent-stream)
  ((plist :initarg :plist :accessor stream-plist)))

(defun make-chunked-stream (lisp-stream &key plist)
  (make-instance 'chunked-stream :lisp-stream lisp-stream :plist plist))

(defmethod local-host ((chunked-stream chunked-stream))
  (local-host (gray-stream::native-lisp-stream chunked-stream)))

(defmethod local-port ((chunked-stream chunked-stream))
  (local-port (gray-stream::native-lisp-stream chunked-stream)))

(defmethod remote-host ((chunked-stream chunked-stream))
  (remote-host (gray-stream::native-lisp-stream chunked-stream)))

(defmethod remote-port ((chunked-stream chunked-stream))
  (remote-port (gray-stream::native-lisp-stream chunked-stream)))


(defun socket-control (stream &key (output-chunking nil oc-p) output-chunking-eof (input-chunking nil ic-p))
  (when oc-p
    (when output-chunking
      (de.dataheaven.chunked-stream-mixin::initialize-output-chunking stream))
    (setf (de.dataheaven.chunked-stream-mixin::output-chunking-p stream)
          output-chunking))
  (when output-chunking-eof
    (de.dataheaven.chunked-stream-mixin::disable-output-chunking stream))
  (when ic-p
    (when input-chunking
      (de.dataheaven.chunked-stream-mixin::initialize-input-chunking stream))
    (setf (de.dataheaven.chunked-stream-mixin::input-chunking-p stream)
          input-chunking)))

; OpenMCL has a built-in ipaddr-to-dotted. But it appears that sometimes
; the log function is being called after the connection is closed and
; it causes nil to be passed to ipaddr-to-dotted. So we wrap ipaddr-to-dotten
; to ensure only non-nil values are passed.

(defun ipaddr-to-dotted (ipaddr &key values)
  (unless (null ipaddr)
    (ccl:ipaddr-to-dotted ipaddr :values values)))

(provide 'acl-socket)
