/*
 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
 * specific language governing rights and limitations under the License.
 */


//
// selector - I/O stream multiplexing
//
#include "selector.h"
#include <Security/debugging.h>
#include <algorithm>	// min/max


namespace Security {
namespace UnixPlusPlus {


//
// construct a Selector object.
//
Selector::Selector() : fdMin(INT_MAX), fdMax(-1)
{
    // initially allocate room for FD_SETSIZE file descriptors (usually good enough)
    fdSetSize = FD_SETSIZE / NFDBITS;
    inSet.grow(0, fdSetSize);
    outSet.grow(0, fdSetSize);
    errSet.grow(0, fdSetSize);
}

Selector::~Selector()
{ }


//
// Add a Client to a Selector
//
void Selector::add(int fd, Client &client, Type type)
{
    // plausibility checks
    assert(!client.isActive());		// one Selector per client, and no re-adding
    assert(fd >= 0);
    
    debug("selector", "add client %p fd %d type=%d", &client, fd, type);
    
    // grow FDSets if needed
    unsigned int pos = fd / NFDBITS;
    if (pos >= fdSetSize) {
        int newSize = (fd - 1) / NFDBITS + 2;	// as much as needed + 1 spare word
        inSet.grow(fdSetSize, newSize);
        outSet.grow(fdSetSize, newSize);
        errSet.grow(fdSetSize, newSize);
    }
    
    // adjust boundaries
    if (fd < fdMin)
        fdMin = fd;
    if (fd > fdMax)
        fdMax = fd;

    // add client
    Client * &slot = clientMap[fd];
    assert(!slot);
    slot = &client;
    client.mFd = fd;
    client.mSelector = this;
    client.mEvents = type;
    set(fd, type);
}    


//
// Remove a Client from a Selector
//
void Selector::remove(int fd)
{
    // sanity checks
    assert(fd >= 0);
    ClientMap::iterator it = clientMap.find(fd);
    assert(it != clientMap.end());
    assert(it->second->mSelector == this);

    debug("selector", "remove client %p fd %d", it->second, fd);

    // remove from FDSets
    set(fd, none);
    
    // remove client
    it->second->mSelector = NULL;
    clientMap.erase(it);
    
    // recompute fdMin/fdMax if needed
    if (isEmpty()) {
        fdMin = INT_MAX;
        fdMax = -1;
    } else if (fd == fdMin) {
        fdMin = clientMap.begin()->first;
    } else if (fd == fdMax) {
        fdMax = clientMap.rbegin()->first;
    }
}


//
// Adjust the FDSets for a single given Client according to a new event Type mask.
//
void Selector::set(int fd, Type type)
{
    assert(fd >= 0);
    inSet.set(fd, type & input);
    outSet.set(fd, type & output);
    errSet.set(fd, type & critical);
    debug("selector", "fd %d notifications 0x%x", fd, type);
}


void Selector::operator () ()
{
    if (!clientMap.empty())
        singleStep(0);
}


void Selector::operator () (Time::Absolute stopTime)
{
    if (!clientMap.empty())
        singleStep(stopTime - Time::now());
}


//
// Perform a single pass through the Selector and notify all clients
// that have selected I/O pending at this time.
// There is not time limit on how long this may take; if the clients
// are well written, it won't be too long.
//
void Selector::singleStep(Time::Interval maxWait)
{
    assert(!clientMap.empty());
    IFDEBUG(debug("selector", "select(%d) [%d-%d] for %ld clients",
        fdMax + 1, fdMin, fdMax, clientMap.size()));
    for (;;) {	// pseudo-loop - only retries
        struct timeval duration = maxWait.timevalInterval();
#if defined(__APPLE__)
        // ad-hoc fix: MacOS X's BSD rejects times of more than 100E6 seconds
        if (duration.tv_sec > 100000000)
            duration.tv_sec = 100000000;
#endif
        const int size = FDSet::words(fdMax);		// number of active words in sets
        switch (int hits = ::select(fdMax + 1,
                inSet.make(size), outSet.make(size), errSet.make(size),
                &duration)) {
        case -1:		// error
            if (errno == EINTR)
                continue;
            debug("selector", "select failed: errno=%d", errno);
            UnixError::throwMe();
        case 0:			// no events
            debug("selector", "select returned nothing");
            return;
        default:		// some events
            debug("selector", "%d pending descriptors", hits);
            //@@@ This could be optimized as a word-merge scan.
            //@@@ The typical case doesn't benefit from this though, though browsers might
            //@@@ and integrated servers definitely would.
            for (int fd = fdMin; fd <= fdMax && hits > 0; fd++) {
                int types = 0;
                if (inSet[fd])  types |= input;
                if (outSet[fd]) types |= output;
                if (errSet[fd]) types |= critical;
                if (types) {
                    debug("selector", "notify fd %d client %p type %d",
                        fd, clientMap[fd], types);
                    clientMap[fd]->notify(fd, types);
                    hits--;
                }
            }
            return;
        }
    }
}


}	// end namespace IPPlusPlus
}	// end namespace Security
