/*
 * Worldvisions Weaver Software:
 *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
 * 
 * Wraps another WvStream and attaches it to the normal Qt
 * event loop.  If you are using this object to manage all of your
 * streams, then you do not need to have a normal WvStreams
 * select()/callback() loop in your application at all.
 *
 * However, should you leave the Qt event loop and wish to continue
 * using this WvStream, call qt_detach() first, then run a normal
 * WvStreams event loop.  If you do not do this, events may be
 * lost!!  You may resume the Qt event loop at any time after the
 * WvStreams event loop has exited by calling qt_attach().
 *
 * Note: You do not need to add all of the WvStreams used in a Qt
 *       application to a single WvStreamList wrapped by a
 *       WvQtStreamClone so long as each top-level stream is wrapped
 *       by a WvQtStreamClone to take care of calling select()
 *       and callback() from within the Qt event loop.
 */
#include "wvqtstreamclone.moc"

// number of slots used by the separate chaining hashtable
// note: can store more than this number of elements in the table
#define NUM_SLOTS 41 // must be prime

WvQtStreamClone::WvQtStreamClone(IWvStream *_cloned, int msec_timeout) :
    WvStreamClone(_cloned), msec_timeout(msec_timeout),
    pending_callback(false), first_time(true), select_in_progress(false),
    last_max_fd(-1),
    notify_readable(NUM_SLOTS),
    notify_writable(NUM_SLOTS),
    notify_exception(NUM_SLOTS)
{
    _cloned->addRef();
    setclone(_cloned);
    notify_readable.setAutoDelete(true);
    notify_writable.setAutoDelete(true);
    notify_exception.setAutoDelete(true);
    qt_attach();
}


WvQtStreamClone::~WvQtStreamClone()
{
}


void WvQtStreamClone::pre_poll()
{
    // prepare lists of file descriptors
    _build_selectinfo(si, msec_timeout, 
                      false, false, false, true);

    // set up a timer to wake us up to poll again (for alarms)
    // we don't try to catch the timer signal; we use it only to force
    // Qt's event loop to restart so our hook gets called again
    select_timer.stop();
    if (si.msec_timeout >= 0)
        select_timer.start(si.msec_timeout, true /*singleshot*/);

    // set up necessary QSocketNotifiers, unfortunately there is no
    // better way to iterate over the set of file descriptors
    for (int fd = 0; fd <= si.max_fd; ++fd)
    {
        if (FD_ISSET(fd, &si.read))
        {
            QSocketNotifier *n = notify_readable.find(fd);
            if (! n)
            {
                n = new QSocketNotifier(fd, QSocketNotifier::Read);
                notify_readable.insert(fd, n);
                QObject::connect(n, SIGNAL(activated(int)),
                    this, SLOT(fd_readable(int)));
            }
        } else
            notify_readable.remove(fd);
        
        if (FD_ISSET(fd, &si.write))
        {
            QSocketNotifier *n = notify_writable.find(fd);
            if (! n)
            {
                n = new QSocketNotifier(fd, QSocketNotifier::Write);
                notify_writable.insert(fd, n);
                QObject::connect(n, SIGNAL(activated(int)),
                    this, SLOT(fd_writable(int)));
            }
        } else
            notify_writable.remove(fd);
        
        if (FD_ISSET(fd, &si.except))
        {
            QSocketNotifier *n = notify_exception.find(fd);
            if (! n)
            {
                n = new QSocketNotifier(fd, QSocketNotifier::Exception);
                notify_exception.insert(fd, n);
                QObject::connect(n, SIGNAL(activated(int)),
                    this, SLOT(fd_exception(int)));
            }
        } else
            notify_exception.remove(fd);
    }

    // remove stale notifiers
    for (int fd = si.max_fd + 1; fd <= last_max_fd; ++fd)
    {
        notify_readable.remove(fd);
        notify_writable.remove(fd);
        notify_exception.remove(fd);
    }
    last_max_fd = si.max_fd;

    // clear select lists
    FD_ZERO(&si.read);
    FD_ZERO(&si.write);
    FD_ZERO(&si.except);
}


void WvQtStreamClone::post_poll()
{
    // cleanup and invoke callbacks
    bool sure = _process_selectinfo(si, true);
    if (sure || pending_callback)
    {
        pending_callback = false;
        callback();
        if (globalstream) globalstream->callback();
    }
}


void WvQtStreamClone::set_timeout(int msec_timeout)
{
    this->msec_timeout = msec_timeout;
}


void WvQtStreamClone::qt_begin_event_loop_hook()
{
    // select not done yet?
    if (select_in_progress) return;

    // finish the last polling stage
    if (! first_time)
        post_poll();
    else
        first_time = false;
    // start the next polling stage
    pre_poll();
    select_in_progress = true;
}


void WvQtStreamClone::qt_detach()
{
    // finish the last polling stage
    if (! first_time)
    {
        select_in_progress = false;
        post_poll();
        last_max_fd = -1;
        first_time = true;
    }
    // remove any remaining Qt objects
    select_timer.stop();
    notify_readable.clear();
    notify_writable.clear();
    notify_exception.clear();
    QObject::disconnect(qApp, SIGNAL(guiThreadAwake()),
        this, SLOT(qt_begin_event_loop_hook()));
    QObject::disconnect(& select_timer, SIGNAL(timeout()),
        this, SLOT(select_timer_expired()));
}


void WvQtStreamClone::qt_attach()
{
    // hook into the Qt event loop before each iteration
    QObject::connect(qApp, SIGNAL(guiThreadAwake()),
        this, SLOT(qt_begin_event_loop_hook()));
    QObject::connect(& select_timer, SIGNAL(timeout()),
        this, SLOT(select_timer_expired()));
}


void WvQtStreamClone::select_timer_expired()
{
    select_in_progress = false;
}


void WvQtStreamClone::fd_readable(int fd)
{
    FD_SET(fd, &si.read);
    pending_callback = true;
    select_in_progress = false;
}


void WvQtStreamClone::fd_writable(int fd)
{
    FD_SET(fd, &si.write);
    pending_callback = true;
    select_in_progress = false;
}


void WvQtStreamClone::fd_exception(int fd)
{
    FD_SET(fd, &si.except);
    pending_callback = true;
    select_in_progress = false;
}

void WvQtStreamClone::execute()
{
    WvStreamClone::execute();
}


void WvQtStreamClone::setclone(IWvStream *newclone)
{
    WvStreamClone::setclone(newclone);
    
    if (newclone != NULL)
        my_type = WvString("WvQtStreamClone:%s", newclone->wstype());
    else
        my_type = "WvQtStreamClone:(none)";
}
