/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

#include "networkio.h"
#include "apr_network_io.h"
#include "apr_general.h"
#include "apr_lib.h"
#include <errno.h>
#include <time.h>


APR_DECLARE(apr_status_t) apr_poll_setup(apr_pollfd_t **new, apr_int32_t num,
                                         apr_pool_t *cont)
{
    (*new) = (apr_pollfd_t *)apr_palloc(cont, sizeof(apr_pollfd_t) * num);
    if ((*new) == NULL) {
        return APR_ENOMEM;
    }
    (*new)->cntxt = cont;
    (*new)->read = (fd_set *)apr_palloc(cont, sizeof(fd_set));
    (*new)->write = (fd_set *)apr_palloc(cont, sizeof(fd_set));
    (*new)->except = (fd_set *)apr_palloc(cont, sizeof(fd_set));
    FD_ZERO((*new)->read);
    (*new)->numread = 0;
    FD_ZERO((*new)->write);
    (*new)->numwrite = 0;
    FD_ZERO((*new)->except);
    (*new)->numexcept = 0;
    return APR_SUCCESS;
}

APR_DECLARE(apr_status_t) apr_poll_socket_add(apr_pollfd_t *aprset,
                                              apr_socket_t *sock,
                                              apr_int16_t event)
{
    if (event & APR_POLLIN) {
        FD_SET(sock->sock, aprset->read);
        aprset->numread++;
    }
    if (event & APR_POLLPRI) {
        FD_SET(sock->sock, aprset->read);
        aprset->numexcept++;
    }
    if (event & APR_POLLOUT) {
        FD_SET(sock->sock, aprset->write);
        aprset->numwrite++;
    }
    return APR_SUCCESS;
}

APR_DECLARE(apr_status_t) apr_poll(apr_pollfd_t *aprset, apr_int32_t *nsds, 
                                   apr_interval_time_t timeout)
{
    int rv;
    struct timeval tv, *tvptr;
    fd_set *newread = NULL;
    fd_set *newwrite = NULL;
    fd_set *newexcept = NULL;

    if (aprset->numread != 0) {
        newread = aprset->read;
    }
    if (aprset->numwrite != 0) {
        newwrite = aprset->write;
    }
    if (aprset->numexcept != 0) {
        newexcept = aprset->except;
    }

    if (newread == NULL && newwrite == NULL && newexcept == NULL) {
#ifdef NETWARE
        delay((DWORD)(timeout / 1000)); /* convert microseconds into milliseconds */
#else
        Sleep((DWORD)(timeout / 1000)); /* convert microseconds into milliseconds */
#endif
        return APR_TIMEUP; /* TODO - get everybody in synch with Win32 apr_status_t */
    }
    else {
        if (timeout < 0) {
            tvptr = NULL;
        }
        else {
            tv.tv_sec = (long)(timeout / APR_USEC_PER_SEC);
            tv.tv_usec = (long)(timeout % APR_USEC_PER_SEC);
            tvptr = &tv;
        }
        rv = select(500, /* ignored on Windows */
                    newread, newwrite, newexcept, tvptr);
    }

    (*nsds) = rv;    
    if ((*nsds) < 0) {
        return apr_get_netos_error();
    }
    return APR_SUCCESS;
}

APR_DECLARE(apr_status_t) apr_poll_revents_get(apr_int16_t *event,
                                          apr_socket_t *sock,
                                          apr_pollfd_t *aprset)
{
    apr_int16_t revents = 0;
    WSABUF data;
    char buf[256];
    DWORD dummy;
    DWORD flags = MSG_PEEK;

    /* We just want to PEEK at the data, so I am setting up a dummy WSABUF
     * variable here.
     */
    data.len = sizeof(buf);
    data.buf = buf;

    if (FD_ISSET(sock->sock, aprset->read)) {
        revents |= APR_POLLIN;
        if (WSARecv(sock->sock, &data, 1, &dummy, &flags, NULL, 
                    NULL) == SOCKET_ERROR) {
            /* This is only legit since we don't return the error */
            dummy = WSAGetLastError();
            switch (dummy) {
                case WSAECONNRESET:
                case WSAECONNABORTED:
                case WSAESHUTDOWN:
                case WSAENETRESET: {
                    revents ^= APR_POLLIN;
                    revents |= APR_POLLHUP;
                    break;
                }
                case WSAENOTSOCK: {
                    revents ^= APR_POLLIN;
                    revents |= APR_POLLNVAL;
                }
                default: {
                    revents ^= APR_POLLIN;
                    revents |= APR_POLLERR;
                }
            }
        }
    }
    if (FD_ISSET(sock->sock, aprset->write)) {
        revents |= APR_POLLOUT;
    }
    /* I am assuming that the except is for out of band data, not a failed
     * connection on a non-blocking socket.  Might be a bad assumption, but
     * it works for now. rbb.
     */
    if (FD_ISSET(sock->sock, aprset->except)) {
        revents |= APR_POLLPRI;
    }

    (*event) = revents;
    return APR_SUCCESS;
}

APR_DECLARE(apr_status_t) apr_poll_data_get(apr_pollfd_t *pollfd, 
                                           const char *key, void *data)
{
    return apr_pool_userdata_get(data, key, pollfd->cntxt);
}

APR_DECLARE(apr_status_t) apr_poll_data_set(apr_pollfd_t *pollfd, void *data,
                                           const char *key,
                                           apr_status_t (*cleanup)(void *))
{
    return apr_pool_userdata_set(data, key, cleanup, pollfd->cntxt);
}

APR_DECLARE(apr_status_t) apr_poll_socket_mask(apr_pollfd_t *aprset, 
                                               apr_socket_t *sock,
                                               apr_int16_t events)
{
    if (events & APR_POLLIN) {
        FD_CLR(sock->sock, aprset->read);
        aprset->numread--;
    }
    if (events & APR_POLLPRI) {
        FD_CLR(sock->sock, aprset->except);
        aprset->numexcept--;
    }
    if (events & APR_POLLOUT) {
        FD_CLR(sock->sock, aprset->write);
        aprset->numwrite--;
    }
    return APR_SUCCESS;
}

APR_DECLARE(apr_status_t) apr_poll_socket_remove(apr_pollfd_t *aprset,
                                                 apr_socket_t *sock)
{
    return apr_poll_socket_mask(aprset, sock, ~0);
}

APR_DECLARE(apr_status_t) apr_poll_socket_clear(apr_pollfd_t *aprset,
                                                 apr_int16_t events)
{
    if (events & APR_POLLIN) {
        FD_ZERO(aprset->read);
        aprset->numread = 0;
    }
    if (events & APR_POLLPRI) {
        FD_ZERO(aprset->read);
        aprset->numexcept = 0;
    }
    if (events & APR_POLLOUT) {
        FD_ZERO(aprset->write);
        aprset->numwrite = 0;
    }
    return APR_SUCCESS;
}
