/*
 * call-seq:
 *    conn.wait_for_notify( [ timeout ] ) -> String
 *    conn.wait_for_notify( [ timeout ] ) { |event, pid| block }
 *
 * Blocks while waiting for notification(s), or until the optional
 * _timeout_ is reached, whichever comes first.  _timeout_ is
 * measured in seconds and can be fractional.
 *
 * Returns +nil+ if _timeout_ is reached, the name of the NOTIFY
 * event otherwise.  If used in block form, passes the name of the
 * NOTIFY +event+ and the generating +pid+ into the block.
 * 
 */
static VALUE
pgconn_wait_for_notify(int argc, VALUE *argv, VALUE self)
{
    PGconn *conn = get_pgconn( self );
    PGnotify *notification;
    int sd = PQsocket( conn );
    int ret;
    struct timeval timeout;
    struct timeval *ptimeout = NULL;
    VALUE timeout_in, relname = Qnil, be_pid = Qnil, extra = Qnil;
    double timeout_sec;
    fd_set sd_rset;
#ifdef _WIN32
    fd_set crt_sd_rset;
#endif

    if ( sd < 0 )
        rb_bug( "PQsocket(conn): couldn't fetch the connection's socket!" );

    if ( rb_scan_args(argc, argv, "01", &timeout_in) == 1 ) {
        timeout_sec = NUM2DBL( timeout_in );
        timeout.tv_sec = (long)timeout_sec;
        timeout.tv_usec = (long)( (timeout_sec - (long)timeout_sec) * 1e6 );
        ptimeout = &timeout;
    }

    /* Check for notifications */
    while ( (notification = PQnotifies(conn)) == NULL ) {
        FD_ZERO( &sd_rset );
        FD_SET( sd, &sd_rset );

#ifdef _WIN32
        create_crt_fd(&sd_rset, &crt_sd_rset);
#endif

        /* Wait for the socket to become readable before checking again */
        ret = rb_thread_select( sd+1, &sd_rset, NULL, NULL, ptimeout );

#ifdef _WIN32
        cleanup_crt_fd(&sd_rset, &crt_sd_rset);
#endif

        if ( ret < 0 )
            rb_sys_fail( 0 );

        /* Return nil if the select timed out */
        if ( ret == 0 ) return Qnil;

        /* Read the socket */
        if ( (ret = PQconsumeInput(conn)) != 1 )
            rb_raise( rb_ePGError, "PQconsumeInput == %d: %s", ret, PQerrorMessage(conn) );
    }

    relname = rb_tainted_str_new2( notification->relname );
    be_pid = INT2NUM( notification->be_pid );
#ifdef HAVE_ST_NOTIFY_EXTRA
    extra = rb_str_new2( notification->extra );
#endif
    PQfreemem( notification );

    if ( rb_block_given_p() )
        rb_yield_values( 3, relname, be_pid, extra );

    return relname;
}