/*
 * Copyright (c) 2009 Mark Heily <mark@heily.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "sys/event.h"
#include "private.h"

int
evfilt_user_init(struct filter *filt)
{
    return (0);
}

void
evfilt_user_destroy(struct filter *filt)
{
    return;
}

int
evfilt_user_copyout(struct filter *filt, 
            struct kevent *dst, 
            int maxevents)
{
    struct knote *kn;
    int nevents = 0;
  
    for (kn = knote_dequeue(filt); kn != NULL; kn = knote_dequeue(filt)) {
        memcpy(dst, &kn->kev, sizeof(*dst));
        dst->fflags &= ~NOTE_FFCTRLMASK;     //FIXME: Not sure if needed
        dst->fflags &= ~NOTE_TRIGGER;
        if (kn->kev.flags & EV_ADD) {
            /* NOTE: True on FreeBSD but not consistent behavior with
                      other filters. */
            dst->flags &= ~EV_ADD;
        }
        if (kn->kev.flags & EV_CLEAR)
            kn->kev.fflags &= ~NOTE_TRIGGER;
        /* FIXME: This shouldn't be necessary in Solaris...
        if (kn->kev.flags & (EV_DISPATCH | EV_CLEAR | EV_ONESHOT))
            eventfd_lower(filt->kf_efd);
         */
        if (kn->kev.flags & EV_DISPATCH) {
            KNOTE_DISABLE(kn);
            kn->kev.fflags &= ~NOTE_TRIGGER;
        } else if (kn->kev.flags & EV_ONESHOT) {
            knote_free(filt, kn);
        }

        dst++;
        if (++nevents == maxevents)
            break;
    }

    /* This should normally never happen but is here for debugging */
    if (nevents == 0) {
        dbg_puts("spurious wakeup");
        /* FIXME: NOT IMPLEMENTED: eventfd_lower(filt->kf_efd); */
    }

    return (nevents);
}


int
evfilt_user_knote_create(struct filter *filt, struct knote *kn)
{
#if TODO
    u_int ffctrl;

    //determine if EV_ADD + NOTE_TRIGGER in the same kevent will cause a trigger */
    if ((!(dst->kev.flags & EV_DISABLE)) && src->fflags & NOTE_TRIGGER) {
        dst->kev.fflags |= NOTE_TRIGGER;
        eventfd_raise(filt->kf_pfd);
    }

#endif
    return (0);
}

int
evfilt_user_knote_modify(struct filter *filt, struct knote *kn, 
        const struct kevent *kev)
{
    unsigned int ffctrl;
    unsigned int fflags;

    /* Excerpted from sys/kern/kern_event.c in FreeBSD HEAD */
    ffctrl = kev->fflags & NOTE_FFCTRLMASK;
    fflags = kev->fflags & NOTE_FFLAGSMASK;
    switch (ffctrl) {
        case NOTE_FFNOP:
            break;

        case NOTE_FFAND:
            kn->kev.fflags &= fflags;
            break;

        case NOTE_FFOR:
            kn->kev.fflags |= fflags;
            break;

        case NOTE_FFCOPY:
            kn->kev.fflags = fflags;
            break;

        default:
            /* XXX Return error? */
            break;
    }

    if ((!(kn->kev.flags & EV_DISABLE)) && kev->fflags & NOTE_TRIGGER) {
        kn->kev.fflags |= NOTE_TRIGGER;
        knote_enqueue(filt, kn);
        return (port_send(filt->kf_kqueue->kq_port, X_PORT_SOURCE_USER, NULL)); 
    }

    return (0);
}

int
evfilt_user_knote_delete(struct filter *filt, struct knote *kn)
{
    return (0);
}

int
evfilt_user_knote_enable(struct filter *filt, struct knote *kn)
{
    /* FIXME: what happens if NOTE_TRIGGER is in fflags?
       should the event fire? */
    return (0);
}

int
evfilt_user_knote_disable(struct filter *filt, struct knote *kn)
{
    return (0);
}

const struct filter evfilt_user = {
    EVFILT_USER,
    evfilt_user_init,
    evfilt_user_destroy,
    evfilt_user_copyout,
    evfilt_user_knote_create,
    evfilt_user_knote_modify,
    evfilt_user_knote_delete,
    evfilt_user_knote_enable,
    evfilt_user_knote_disable,   
};
