/*
 *  call-seq:
 *     IO.select(read_array 
 *               [, write_array 
 *               [, error_array 
 *               [, timeout]]] ) =>  array  or  nil
 *  
 *  See <code>Kernel#select</code>.
 */

static VALUE
rb_f_select(argc, argv, obj)
    int argc;
    VALUE *argv;
    VALUE obj;
{
    VALUE read, write, except, timeout, res, list;
    fd_set rset, wset, eset, pset;
    fd_set *rp, *wp, *ep;
    struct timeval *tp, timerec;
    OpenFile *fptr;
    long i;
    int max = 0, n;
    int interrupt_flag = 0;
    int pending = 0;

    rb_scan_args(argc, argv, "13", &read, &write, &except, &timeout);
    if (NIL_P(timeout)) {
        tp = 0;
    }
    else {
        timerec = rb_time_interval(timeout);
        tp = &timerec;
    }

    FD_ZERO(&pset);
    if (!NIL_P(read)) {
        Check_Type(read, T_ARRAY);
        rp = &rset;
        FD_ZERO(rp);
        for (i=0; i<RARRAY(read)->len; i++) {
            GetOpenFile(rb_io_get_io(RARRAY(read)->ptr[i]), fptr);
            FD_SET(fileno(fptr->f), rp);
            if (READ_DATA_PENDING(fptr->f)) { /* check for buffered data */
                pending++;
                FD_SET(fileno(fptr->f), &pset);
            }
            if (max < fileno(fptr->f)) max = fileno(fptr->f);
        }
        if (pending) {         /* no blocking if there's buffered data */
            timerec.tv_sec = timerec.tv_usec = 0;
            tp = &timerec;
        }
    }
    else
        rp = 0;

    if (!NIL_P(write)) {
        Check_Type(write, T_ARRAY);
        wp = &wset;
        FD_ZERO(wp);
        for (i=0; i<RARRAY(write)->len; i++) {
            GetOpenFile(rb_io_get_io(RARRAY(write)->ptr[i]), fptr);
            FD_SET(fileno(fptr->f), wp);
            if (max < fileno(fptr->f)) max = fileno(fptr->f);
            if (fptr->f2) {
                FD_SET(fileno(fptr->f2), wp);
                if (max < fileno(fptr->f2)) max = fileno(fptr->f2);
            }
        }
    }
    else
        wp = 0;

    if (!NIL_P(except)) {
        Check_Type(except, T_ARRAY);
        ep = &eset;
        FD_ZERO(ep);
        for (i=0; i<RARRAY(except)->len; i++) {
            GetOpenFile(rb_io_get_io(RARRAY(except)->ptr[i]), fptr);
            FD_SET(fileno(fptr->f), ep);
            if (max < fileno(fptr->f)) max = fileno(fptr->f);
            if (fptr->f2) {
                FD_SET(fileno(fptr->f2), ep);
                if (max < fileno(fptr->f2)) max = fileno(fptr->f2);
            }
        }
    }
    else {
        ep = 0;
    }

    max++;

    n = rb_thread_select(max, rp, wp, ep, tp);
    if (n < 0) {
        rb_sys_fail(0);
    }
    if (!pending && n == 0) return Qnil; /* returns nil on timeout */

    res = rb_ary_new2(3);
    rb_ary_push(res, rp?rb_ary_new():rb_ary_new2(0));
    rb_ary_push(res, wp?rb_ary_new():rb_ary_new2(0));
    rb_ary_push(res, ep?rb_ary_new():rb_ary_new2(0));

    if (interrupt_flag == 0) {
        if (rp) {
            list = RARRAY(res)->ptr[0];
            for (i=0; i< RARRAY(read)->len; i++) {
                GetOpenFile(rb_io_get_io(RARRAY(read)->ptr[i]), fptr);
                if (FD_ISSET(fileno(fptr->f), rp)
                    || FD_ISSET(fileno(fptr->f), &pset)) {
                    rb_ary_push(list, rb_ary_entry(read, i));
                }
            }
        }

        if (wp) {
            list = RARRAY(res)->ptr[1];
            for (i=0; i< RARRAY(write)->len; i++) {
                GetOpenFile(rb_io_get_io(RARRAY(write)->ptr[i]), fptr);
                if (FD_ISSET(fileno(fptr->f), wp)) {
                    rb_ary_push(list, rb_ary_entry(write, i));
                }
                else if (fptr->f2 && FD_ISSET(fileno(fptr->f2), wp)) {
                    rb_ary_push(list, rb_ary_entry(write, i));
                }
            }
        }

        if (ep) {
            list = RARRAY(res)->ptr[2];
            for (i=0; i< RARRAY(except)->len; i++) {
                GetOpenFile(rb_io_get_io(RARRAY(except)->ptr[i]), fptr);
                if (FD_ISSET(fileno(fptr->f), ep)) {
                    rb_ary_push(list, rb_ary_entry(except, i));
                }
                else if (fptr->f2 && FD_ISSET(fileno(fptr->f2), ep)) {
                    rb_ary_push(list, rb_ary_entry(except, i));
                }
            }
        }
    }

    return res;                 /* returns an empty array on interrupt */
}