#include "xshelper.h"

static void
setup_sigset(pTHX_ sigset_t* const sigmask, SV* const arg) {
    SvGETMAGIC(arg);
#if PERL_BCDVERSION > 0x5015002
    if( sv_isobject(arg) && sv_derived_from(arg, "POSIX::SigSet") && SvPOK(SvRV(arg)) ) {
        *sigmask = *(sigset_t*)SvPV_nolen(SvRV(arg));
#else
    if( sv_isobject(arg) && sv_derived_from(arg, "POSIX::SigSet") && SvIOK(SvRV(arg)) ) {
        *sigmask = *(sigset_t*)SvIV( SvRV(arg) );
#endif
    }
    else if(SvOK(arg)) {
        if(SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
            AV* const av  = (AV*)SvRV(arg);
            I32 const len = av_len(av) + 1;
            I32 i;

            sigemptyset(sigmask);

            for(i = 0; i < len; i++) {
                SV** const svp = av_fetch(av, i, FALSE);
                if(svp) {
                    if(looks_like_number(*svp)) {
                        sigaddset(sigmask, (int)SvIV(*svp));
                    }
                    else {
                        int signum;
                        STRLEN len;
                        const char* name = SvPV_const(*svp, len);
                        if(len > 3 && strncmp(name, "SIG", 3) == 0) {
                            name += 3;
                        }
                        signum = whichsig( (char*)name);
                        if(signum < 0) {
                            if(ckWARN( packWARN(WARN_MISC) )) {
                                warner(packWARN(WARN_MISC),
                                    "POSIX::pselect: unrecognized signal name \"%s\"", name);
                            }
                        }
                        else {
                            sigaddset(sigmask, signum);
                        }
                    }
                }
            }
        }
        else {
            croak("POSIX::pselect: sigset must be an ARRAY reference or POSIX::SigSet object");
        }
    }
}

/* stolen from pp_sselect() at pp_sys.c */
static
XS(XS_POSIX__pselect)
{
    dVAR; dXSARGS; dXSTARG;
    I32 i;
    I32 j;
    char *s;
    SV *sv;
    NV value;
    I32 maxlen = 0;
    I32 nfound;
    struct timespec timebuf;
    struct timespec *tbuf = &timebuf;
    sigset_t sigmask;
    I32 growsize;
    char *fd_sets[4];
#if BYTEORDER != 0x1234 && BYTEORDER != 0x12345678
    I32 masksize;
    I32 offset;
    I32 k;

#   if BYTEORDER & 0xf0000
#        define ORDERBYTE (0x88888888 - BYTEORDER)
#   else
#        define ORDERBYTE (0x4444 - BYTEORDER)
#   endif

#endif

    if (items != 5)
       croak("Usage: pselect(rfdset, wfdset, efdset, timeout, sigmask)");

    SP -= 5; /* r, w, e, timeout, sigset */
    for (i = 1; i <= 3; i++) {
        SV * const sv = SP[i];
        if (!SvOK(sv))
            continue;
        if (SvREADONLY(sv)) {
            if (SvIsCOW(sv))
                sv_force_normal_flags(sv, 0);
            if (SvREADONLY(sv) && !(SvPOK(sv) && SvCUR(sv) == 0))
                croak("%s", PL_no_modify);
        }
        if (!SvPOK(sv)) {
            if(ckWARN( packWARN(WARN_MISC) )) {
                warner(packWARN(WARN_MISC),
                    "POSIX::pselect: Non-string passed as bitmask");
            }
            SvPV_force_nolen(sv);        /* force string conversion */
        }
        j = SvCUR(sv);
        if (maxlen < j)
            maxlen = j;
    }

/* little endians can use vecs directly */
#if BYTEORDER != 0x1234 && BYTEORDER != 0x12345678
#  ifdef NFDBITS

#    ifndef NBBY
#     define NBBY 8
#    endif

    masksize = NFDBITS / NBBY;
#  else
    masksize = sizeof(long);        /* documented int, everyone seems to use long */
#  endif
    Zero(&fd_sets[0], 4, char*);
#endif

#  if SELECT_MIN_BITS == 1
    growsize = sizeof(fd_set);
#  else
#   if defined(__GLIBC__) && defined(__FD_SETSIZE)
#      undef SELECT_MIN_BITS
#      define SELECT_MIN_BITS __FD_SETSIZE
#   endif
    /* If SELECT_MIN_BITS is greater than one we most probably will want
     * to align the sizes with SELECT_MIN_BITS/8 because for example
     * in many little-endian (Intel, Alpha) systems (Linux, OS/2, Digital
     * UNIX, Solaris, NeXT, Darwin) the smallest quantum select() operates
     * on (sets/tests/clears bits) is 32 bits.  */
    growsize = maxlen + (SELECT_MIN_BITS/8 - (maxlen % (SELECT_MIN_BITS/8)));
#  endif

    sv = SP[4];
    if (SvOK(sv)) {
        value = SvNV(sv);
        if (value < 0.0)
            value = 0.0;
        timebuf.tv_sec = (long)value;
        value -= (NV)timebuf.tv_sec;
        timebuf.tv_nsec = (long)(value * 1000000000.0);
        //timebuf.tv_usec = (long)(value * 1000000.0);
    }
    else
        tbuf = NULL;

    setup_sigset(aTHX_ &sigmask, SP[5]);

    for (i = 1; i <= 3; i++) {
        sv = SP[i];
        if (!SvOK(sv) || SvCUR(sv) == 0) {
            fd_sets[i] = 0;
            continue;
        }
        assert(SvPOK(sv));
        j = SvLEN(sv);
        if (j < growsize) {
            Sv_Grow(sv, growsize);
        }
        j = SvCUR(sv);
        s = SvPVX(sv) + j;
        while (++j <= growsize) {
            *s++ = '\0';
        }

#if BYTEORDER != 0x1234 && BYTEORDER != 0x12345678
        s = SvPVX(sv);
        Newx(fd_sets[i], growsize, char);
        for (offset = 0; offset < growsize; offset += masksize) {
            for (j = 0, k=ORDERBYTE; j < masksize; j++, (k >>= 4))
                fd_sets[i][j+offset] = s[(k % masksize) + offset];
        }
#else
        fd_sets[i] = SvPVX(sv);
#endif
    }

    nfound = pselect(
        maxlen * 8,
        (fd_set*) fd_sets[1],
        (fd_set*) fd_sets[2],
        (fd_set*) fd_sets[3],
        tbuf, &sigmask);

    for (i = 1; i <= 3; i++) {
        if (fd_sets[i]) {
            sv = SP[i];
#if BYTEORDER != 0x1234 && BYTEORDER != 0x12345678
            s = SvPVX(sv);
            for (offset = 0; offset < growsize; offset += masksize) {
                for (j = 0, k=ORDERBYTE; j < masksize; j++, (k >>= 4))
                    s[(k % masksize) + offset] = fd_sets[i][j+offset];
            }
            Safefree(fd_sets[i]);
#endif
            SvSETMAGIC(sv);
        }
    }

    PUSHi(nfound);
    if (GIMME == G_ARRAY && tbuf) {
        value = (NV)(timebuf.tv_sec) +
                (NV)(timebuf.tv_nsec) / 1000000000.0;
        mPUSHn(value);
    }
    PUTBACK;
}


MODULE = POSIX::pselect    PACKAGE = POSIX::pselect

PROTOTYPES: DISABLE

BOOT:
{
        newXS("POSIX::pselect::pselect",
            XS_POSIX__pselect, (char*)__FILE__);
}