;;;
;;; new-serve-event.lisp
;;;
;;; SBCL/Win32-specific sub-serve-event that allows for a window-message hook.
;;;

(cl:defpackage :new-serve-event
  (:use :common-lisp :sb-alien)
  (:export #:*message-function*))

(cl:in-package :new-serve-event)

#|
TODO:

  Take out the pipe detection in sub-serve-event and replace it with a
monitor thread based around an event.  Only needs to happen for stdin,
after all, and only when it's a thread.


STUPID TRICKS:

  You can't pass an event to _open_osfhandle() (sockint::handle->fd by
any other name), but what you can do is to open a dummy fd-stream (I
suggest using "NUL", with :if-does-not-exist :create in order to work
around the DOS device brain-damage), obtain the fd from the stream,
and the hande from the stream, then push a new mapping to
*fd-wait-handlers* saying that the handle for the stream is signalled
by your new event.  At this point, you can add an :input fd-handler
for the stream's fd and it will be called when the event is signalled.

|#

;;; External interface.

(defvar *message-function*)


;;; Win32 API defs.

(defconstant +qs-allevents+ #x4bf)
(defconstant +infinite+ #xffffffff)
(defconstant +wait-timeout+ #x102)
(defconstant +wait-abandoned-0+ #x80)
(defconstant +wait-failed+ #xffffffff)
(defconstant +fd-read+ 1)
(defconstant +fd-accept+ 8)
(defconstant +socket-error+ -1)

(load-shared-object "KERNEL32")
(load-shared-object "USER32")

(declaim (inline WaitForSingleObject))
(defun WaitForSingleObject (handle milliseconds)
  (alien-funcall (extern-alien "WaitForSingleObject"
			       (function unsigned-long
					 unsigned-long unsigned-long))
		 handle milliseconds))

(declaim (inline MsgWaitForMultipleObjects))
(defun MsgWaitForMultipleObjects (count handles waitall milliseconds wakemask)
  (alien-funcall (extern-alien "MsgWaitForMultipleObjects"
			       (function unsigned-long
					 unsigned-long (* unsigned-long)
					 long unsigned-long unsigned-long))
		 count handles waitall milliseconds wakemask))

(declaim (inline WaitForMultipleObjects))
(defun WaitForMultipleObjects (count handles waitall milliseconds)
  (alien-funcall (extern-alien "WaitForMultipleObjects"
			       (function unsigned-long
					 unsigned-long (* unsigned-long)
					 long unsigned-long))
		 count handles waitall milliseconds))

(declaim (inline wsa-create-event))
(defun wsa-create-event ()
  (alien-funcall (extern-alien "WSACreateEvent" (function unsigned-long))))

(declaim (inline wsa-event-select))
(defun wsa-event-select (socket event flags)
  (alien-funcall (extern-alien "WSAEventSelect"
			       (function int
					 unsigned-long unsigned-long
					 unsigned-long))
		 socket event flags))

(declaim (inline wsa-close-event))
(defun wsa-close-event (handle)
  (alien-funcall (extern-alien "WSACloseEvent" (function int unsigned-long))
		 handle))


;;; Hidden communication channel.

(defvar *fd-wait-handles* nil
  "A list of file-descriptor -> handle,type mappings for specialized handling in serve-event because of WaitForMultipleObject limitations.")


;;; Serve-event damage.

(sb-ext:without-package-locks
  (defun sb-impl::sub-serve-event (to-sec to-usec deadlinep)
    ;(format sb-sys:*stdout* "sub-serve: waithandles ~S~%" *fd-wait-handles*)
    ;(format sb-sys:*stdout* "sa: ~S~%" *standard-output*)
    (sb-alien:with-alien ((handles (array unsigned-long 63)))

      ;; NOTE: Can't have more than 63 handles (api limit).
      ;; FIXME: Enforce this.
      (let ((count 0)
	    (pipe-ready nil)
	    (timeout (cond ((or to-sec to-usec)
			    (+ (* (or to-sec 0) 1000)
			       (or to-usec 0)))
			   (t
			    (unless sb-sys:*interrupts-enabled*
			      (sb-unix::note-dangerous-select))
			    +infinite+))))

	(sb-impl::map-descriptor-handlers
	 (lambda (handler)
	   (unless (sb-impl::handler-bogus handler)
	     (ecase (sb-impl::handler-direction handler)
	       (:input
		(let* ((fd (sb-impl::handler-descriptor handler))
		       (handle (sockint::fd->handle fd))
		       (waithandle (find handle *fd-wait-handles* :key #'first)))
		  (with-alien ((avail unsigned-long))
		    (if (zerop (sb-win32:peek-named-pipe handle nil 0
							 nil (addr avail) nil))
		      (progn
			;(format sb-sys:*stdout* "count ~A: ~A (~A, ~A)~%" count handle fd waithandle)
			(if waithandle
			    (setf (deref handles count) (second waithandle))
			    (setf (deref handles count) handle))
			(incf count))
		      (setf pipe-ready t)))))
	       (:output
		;; Don't do anything here.
		)))))

	;(format sb-sys:*stdout* "count: ~X~%" count)
	(let ((result (if (zerop count)
			  ;; Either there are no descriptor handlers or they're
			  ;; all pipes.  We don't dare call a wait function.
			  (if (boundp '*message-function*)
			      ;; These results lose no matter what.
			      count
			      (progn
				(sleep 0.5)
				nil))
			  ;; There are valid descriptor handlers that aren't pipes.
			  (if (boundp '*message-function*)
			      (MsgWaitForMultipleObjects count (addr (deref handles 0))
							 0 timeout +qs-allevents+)
			      (WaitForMultipleObjects count (addr (deref handles 0))
						      0 timeout)))))
	  ;(format sb-sys:*stdout* "result: ~X~%" result)
	  (when pipe-ready
	    (dolist (handler (sb-impl::select-descriptor-handlers
			      (lambda (handler)
				(ecase (sb-impl::handler-direction handler)
				  (:input
				   (with-alien ((avail unsigned-long))
				     (and (not (zerop (sb-win32:peek-named-pipe (sockint::fd->handle (sb-impl::handler-descriptor handler)) nil 0 nil (addr avail) nil)))
					  (not (zerop avail)))))
				  (:output
				   ;; Handles are always ready for output.
				   t)))))
	      ;(format sb-sys:*stdout* "calling handler ~A~%" handler)
	      (funcall (sb-impl::handler-function handler)
		       (sb-impl::handler-descriptor handler))
	      t))
	  (cond ((eq result nil))
		((< result count)
		 ;; The result'th handle in handles is ready for reading.
		 (let* ((handle (deref handles result))
			(waithandle (find handle *fd-wait-handles* :key #'second)))
		   ;(format sb-sys:*stdout* "handle: ~A, waithandle: ~A~%" handle waithandle)
		   (dolist (handler (sb-impl::select-descriptor-handlers
				     (lambda (handler)
				       (ecase (sb-impl::handler-direction handler)
					 (:input
					  (or
					   (and waithandle
						(= (sockint::fd->handle
						    (sb-impl::handler-descriptor handler))
						   (first waithandle)))
					   (= (sockint::fd->handle
					       (sb-impl::handler-descriptor handler))
					     handle)))
					 (:output
					  ;; Handles are always ready for output.
					  t)))))
		     ;(format sb-sys:*stdout* "calling handler ~A~%" handler)
		     (funcall (sb-impl::handler-function handler)
			      (sb-impl::handler-descriptor handler))
		     t)))
		((= result count)
		 ;; There is a windows message ready.
		 (funcall *message-function*)
		 t)
		((= result +wait-timeout+)
		 ;(format sb-sys:*stdout* "bar~%")
		 ;; The timeout elapsed.
		 (when deadlinep
		   (sb-sys:signal-deadline))
		 nil)
		(t
		 ;(format sb-sys:*stdout* "foo~%")
		 ;; Something unexpected happened, should probably error.
		 (sb-impl::handler-descriptors-error) ;; ???
		 )))))))


;;; Sb-bsd-sockets damage.

(defun sockint::bind (fd &rest options)
  (let ((handle (sockint::fd->handle fd))
	(event (wsa-create-event)))
    (wsa-event-select handle event +fd-accept+)
    (push (list handle event :host-socket) *fd-wait-handles*)

    (apply #'sockint::win32-bind handle options)))

(defun sockint::close (fd &rest options)
  (let* ((handle (sockint::fd->handle fd))
	 (result (apply #'sockint::win32-close handle options)))

    (setf *fd-wait-handles*
	  (delete-if
	   (lambda (entry)
	     (when (= (first entry) handle)
	       (when (second entry)
		 (wsa-close-event (second entry))
		 (setf (second entry) nil))
	       t))
	   *fd-wait-handles*))

    result))

(defun sockint::accept (fd &rest options)
  (let ((handle (apply #'sockint::win32-accept (sockint::fd->handle fd) options))
	(event (wsa-create-event)))
    (wsa-event-select handle event +fd-read+)
    (push (list handle event :data-socket) *fd-wait-handles*)
    (sockint::handle->fd handle 0)))


;;; Sb-win32 damage.

(sb-ext:without-package-locks
  (defun sb-win32:handle-listen (handle)
    (with-alien ((avail sb-win32:dword)
		 (buf (array char #.sb-win32::input-record-size)))
      (unless (zerop (sb-win32:peek-named-pipe handle nil 0 nil (addr avail) nil))
	(return-from sb-win32:handle-listen (plusp avail)))
    
      (unless (zerop (sb-win32:peek-console-input handle
						  (cast buf (* t))
						  sb-win32::input-record-size (addr avail)))
	(return-from sb-win32:handle-listen (plusp avail)))

      (let ((waithandle (find handle *fd-wait-handles* :key #'first)))
	(when waithandle
	  (let ((result (WaitForSingleObject (second waithandle) 0)))
	    ;(format sb-sys:*stdout* "hl: ~S / ~S~%" waithandle result)
	    (when (or (zerop result)
		      (= result +wait-timeout+))
	      (return-from sb-win32:handle-listen (zerop result))))))

      t)))


;;; Sb-unix damage.

(sb-ext:without-package-locks
  (defun sb-unix:unix-read (fd buf len)
    (declare (type sb-unix::unix-fd fd)
	     (type (unsigned-byte 32) len))
    (let* ((handle (sb-win32:get-osfhandle fd))
	   (waithandle (find handle *fd-wait-handles* :key #'first)))
      (if (and waithandle (eq (third waithandle) :data-socket))
	  (progn
	    (alien-funcall (extern-alien "WSAResetEvent" (function int unsigned-long))
			   (second waithandle))
	    (let ((result (sockint::win32-recv handle buf len 0)))
	      (if (= result +socket-error+)
		  (values nil (if (= 10035 (sockint::wsa-get-last-error)) 'sb-unix:ewouldblock 0))
		  (values result 0))))
	  (sb-unix::int-syscall ("read" int (* char) int) fd buf len)))))


;; Fd-stream damage.

(sb-ext:without-package-locks
  (defun sb-impl::refill-input-buffer (stream)
    (let ((fd (sb-impl::fd-stream-fd stream))
	  (errno 0)
	  (count 0))
      (tagbody
	 ;; Check for blocking input before touching the stream, as if
	 ;; we happen to wait we are liable to be interrupted, and the
	 ;; interrupt handler may use the same stream.
	 (if (sb-impl::sysread-may-block-p stream)
	     (go :wait-for-input)
	     (go :main))
	 ;; These (:CLOSED-FLAME and :READ-ERROR) tags are here so what
	 ;; we can signal errors outside the WITHOUT-INTERRUPTS.
       :closed-flame
	 (sb-impl::closed-flame stream)
       :read-error
	 (sb-impl::simple-stream-perror "couldn't read from ~S" stream errno)
       :wait-for-input
	 ;; This tag is here so we can unwind outside the WITHOUT-INTERRUPTS
	 ;; to wait for input if read tells us EWOULDBLOCK.
	 (unless (sb-impl::wait-until-fd-usable fd :input (sb-impl::fd-stream-timeout stream))
	   (sb-impl::signal-timeout 'io-timeout :stream stream :direction :read
				    :seconds (sb-impl::fd-stream-timeout stream)))
       :main
	 ;; Since the read should not block, we'll disable the
	 ;; interrupts here, so that we don't accidentally unwind and
	 ;; leave the stream in an inconsistent state.
	 (sb-sys:without-interrupts
	   ;; Check the buffer: if it is null, then someone has closed
	   ;; the stream from underneath us. This is not ment to fix
	   ;; multithreaded races, but to deal with interrupt handlers
	   ;; closing the stream.
	   (let* ((ibuf (or (sb-impl::fd-stream-ibuf stream) (go :closed-flame)))
		  (sap (sb-impl::buffer-sap ibuf))
		  (length (sb-impl::buffer-length ibuf))
		  (head (sb-impl::buffer-head ibuf))
		  (tail (sb-impl::buffer-tail ibuf)))
	     (declare (sb-impl::index length head tail))
	     (unless (zerop head)
	       (cond ((eql head tail)
		      ;; Buffer is empty, but not at yet reset -- make it so.
		      (setf head 0
			    tail 0)
		      (sb-impl::reset-buffer ibuf))
		     (t
		      ;; Buffer has things in it, but they are not at the head
		      ;; -- move them there.
		      (let ((n (- tail head)))
			(sb-kernel:system-area-ub8-copy sap head sap 0 n)
			(setf head 0
			      (sb-impl::buffer-head ibuf) head
			      tail n
			      (sb-impl::buffer-tail ibuf) tail)))))
	     (setf (sb-impl::fd-stream-listen stream) nil)
	     (setf (values count errno)
		   (sb-unix:unix-read fd (sb-sys:sap+ sap tail) (- length tail)))
	     (cond ((null count)
		    (if (eql errno 'sb-unix:ewouldblock)
			(go :wait-for-input)
			(go :read-error)))
		   ((zerop count)
		    (setf (sb-impl::fd-stream-listen stream) :eof)
					;(/show0 "THROWing EOF-INPUT-CATCHER")
		    (throw 'sb-impl::eof-input-catcher nil))
		   (t
		    ;; Success! (Do not use INCF, for sake of other threads.)
		    (setf (sb-impl::buffer-tail ibuf) (+ count tail)))))))
      count)))


;;; EOF
