/*
 * Copyright 2011 Red Hat, Inc.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include <stdarg.h>

#include <libgen.h>
#include <sys/types.h>
#include <dirent.h>

#ifdef HAVE_PTHREAD
#include <pthread.h>
#endif

#include <verto-module.h>
#include "module.h"

#define  _str(s) # s
#define __str(s) _str(s)

#define MUTABLE(flags) (flags & _VERTO_EV_FLAG_MUTABLE_MASK)

/* Remove flags we can emulate */
#define make_actual(flags) ((flags) & ~(VERTO_EV_FLAG_PERSIST|VERTO_EV_FLAG_IO_CLOSE_FD))

struct verto_ctx {
    size_t ref;
    verto_mod_ctx *ctx;
    const verto_module *module;
    verto_ev *events;
    int deflt;
    int exit;
};

typedef struct {
    verto_proc proc;
    verto_proc_status status;
} verto_child;

typedef struct {
    int fd;
    verto_ev_flag state;
} verto_io;

struct verto_ev {
    verto_ev *next;
    verto_ctx *ctx;
    verto_ev_type type;
    verto_callback *callback;
    verto_callback *onfree;
    void *priv;
    verto_mod_ev *ev;
    verto_ev_flag flags;
    verto_ev_flag actual;
    size_t depth;
    int deleted;
    union {
        verto_io io;
        int signal;
        time_t interval;
        verto_child child;
    } option;
};

typedef struct module_record module_record;
struct module_record {
    module_record *next;
    const verto_module *module;
    void *dll;
    char *filename;
    verto_ctx *defctx;
};


#ifdef BUILTIN_MODULE
#define _MODTABLE(n) verto_module_table_ ## n
#define MODTABLE(n) _MODTABLE(n)
/*
 * This symbol can be used when embedding verto.c in a library along with a
 * built-in private module, to preload the module instead of dynamically
 * linking it in later.  Define to <modulename>.
 */
extern verto_module MODTABLE(BUILTIN_MODULE);
static module_record builtin_record = {
    NULL, &MODTABLE(BUILTIN_MODULE), NULL, "", NULL
};
static module_record *loaded_modules = &builtin_record;
#else
static module_record *loaded_modules;
#endif

static void *(*resize_cb)(void *mem, size_t size);
static int resize_cb_hierarchical;

#ifdef HAVE_PTHREAD
static pthread_mutex_t loaded_modules_mutex = PTHREAD_MUTEX_INITIALIZER;

#ifndef NDEBUG
#define mutex_lock(x) { \
        int c = pthread_mutex_lock(x); \
        if (c != 0) { \
            fprintf(stderr, "pthread_mutex_lock returned %d (%s) in %s", \
                    c, strerror(c), __FUNCTION__); \
        } \
        assert(c == 0); \
    }
#define mutex_unlock(x) { \
        int c = pthread_mutex_unlock(x); \
        if (c != 0) { \
            fprintf(stderr, "pthread_mutex_unlock returned %d (%s) in %s", \
                    c, strerror(c), __FUNCTION__); \
        } \
        assert(c == 0); \
    }
#define mutex_destroy(x) { \
        int c = pthread_mutex_destroy(x); \
        if (c != 0) { \
            fprintf(stderr, "pthread_mutex_destroy returned %d (%s) in %s", \
                    c, strerror(c), __FUNCTION__); \
        } \
        assert(c == 0); \
    }
#else /* NDEBUG */
#define mutex_lock pthread_mutex_lock
#define mutex_unlock pthread_mutex_unlock
#define mutex_destroy pthread_mutex_destroy
#endif /* NDEBUG */

#else /* HAVE_PTHREAD */
#define mutex_lock(x)
#define mutex_unlock(x)
#define mutex_destroy(x)
#endif /* HAVE_PTHREAD */

#define vfree(mem) vresize(mem, 0)
static void *
vresize(void *mem, size_t size)
{
    if (!resize_cb)
        resize_cb = &realloc;
    if (size == 0 && resize_cb == &realloc) {
        /* Avoid memleak as realloc(X, 0) can return a free-able pointer. */
        free(mem);
        return NULL;
    }
    return (*resize_cb)(mem, size);
}

#ifndef BUILTIN_MODULE
static char *
string_aconcat(const char *first, const char *second, const char *third) {
    char *ret;
    size_t len;

    len = strlen(first) + strlen(second);
    if (third)
        len += strlen(third);

    ret = malloc(len + 1);
    if (!ret)
        return NULL;

    strncpy(ret, first, strlen(first));
    strncpy(ret + strlen(first), second, strlen(second));
    if (third)
        strncpy(ret + strlen(first) + strlen(second), third, strlen(third));

    ret[len] = '\0';
    return ret;
}

static char *
int_get_table_name_from_filename(const char *filename)
{
    char *bn = NULL, *tmp = NULL;

    if (!filename)
        return NULL;

    tmp = strdup(filename);
    if (!tmp)
        return NULL;

    bn = basename(tmp);
    if (bn)
        bn = strdup(bn);
    free(tmp);
    if (!bn)
        return NULL;

    tmp = strchr(bn, '-');
    if (tmp) {
        if (strchr(tmp+1, '.')) {
            *strchr(tmp+1, '.') = '\0';
            tmp = string_aconcat(__str(VERTO_MODULE_TABLE()), tmp + 1, NULL);
        } else
            tmp = NULL;
    }

    free(bn);
    return tmp;
}

typedef struct {
    int reqsym;
    verto_ev_type reqtypes;
} shouldload_data;

static int
shouldload(void *symb, void *misc, char **err)
{
    verto_module *table = (verto_module*) symb;
    shouldload_data *data = (shouldload_data*) misc;

    /* Make sure we have the proper version */
    if (table->vers != VERTO_MODULE_VERSION) {
        if (err)
            *err = strdup("Invalid module version!");
        return 0;
    }

    /* Check to make sure that we have our required symbol if reqsym == true */
    if (table->symb && data->reqsym
            && !module_symbol_is_present(NULL, table->symb)) {
        if (err)
            *err = string_aconcat("Symbol not found: ", table->symb, "!");
        return 0;
    }

    /* Check to make sure that this module supports our required features */
    if (data->reqtypes != VERTO_EV_TYPE_NONE
            && (table->types & data->reqtypes) != data->reqtypes) {
        if (err)
            *err = strdup("Module does not support required features!");
        return 0;
    }

    return 1;
}

static int
do_load_file(const char *filename, int reqsym, verto_ev_type reqtypes,
             module_record **record)
{
    char *tblname = NULL, *error = NULL;
    module_record *tmp;
    shouldload_data data  = { reqsym, reqtypes };

    /* Check the loaded modules to see if we already loaded one */
    mutex_lock(&loaded_modules_mutex);
    for (*record = loaded_modules ; *record ; *record = (*record)->next) {
        if (!strcmp((*record)->filename, filename)) {
            mutex_unlock(&loaded_modules_mutex);
            return 1;
        }
    }
    mutex_unlock(&loaded_modules_mutex);

    /* Create our module record */
    tmp = *record = vresize(NULL, sizeof(module_record));
    if (!tmp)
        return 0;
    memset(tmp, 0, sizeof(module_record));
    tmp->filename = strdup(filename);
    if (!tmp->filename) {
        vfree(tmp);
        return 0;
    }

    /* Get the name of the module struct in the library */
    tblname = int_get_table_name_from_filename(filename);
    if (!tblname) {
        free(tblname);
        free(tmp->filename);
        vfree(tmp);
        return 0;
    }

    /* Load the module */
    error = module_load(filename, tblname, shouldload, &data, &tmp->dll,
                        (void **) &tmp->module);
    if (error || !tmp->dll || !tmp->module) {
        /*if (error)
            fprintf(stderr, "%s\n", error);*/
        free(error);
        module_close(tmp->dll);
        free(tblname);
        free(tmp->filename);
        vfree(tmp);
        return 0;
    }

    /* Append the new module to the end of the loaded modules */
    mutex_lock(&loaded_modules_mutex);
    for (tmp = loaded_modules ; tmp && tmp->next; tmp = tmp->next)
        continue;
    if (tmp)
        tmp->next = *record;
    else
        loaded_modules = *record;
    mutex_unlock(&loaded_modules_mutex);

    free(tblname);
    return 1;
}

static int
do_load_dir(const char *dirname, const char *prefix, const char *suffix,
            int reqsym, verto_ev_type reqtypes, module_record **record)
{
    DIR *dir;
    struct dirent *ent = NULL;

    *record = NULL;
    dir = opendir(dirname);
    if (!dir)
        return 0;


    while ((ent = readdir(dir))) {
        char *tmp = NULL;
        int success;
        size_t flen, slen;

        flen = strlen(ent->d_name);
        slen = strlen(suffix);

        if (!strcmp(".", ent->d_name) || !strcmp("..", ent->d_name))
            continue;
        if (strstr(ent->d_name, prefix) != ent->d_name)
            continue;
        if (flen < slen || strcmp(ent->d_name + flen - slen, suffix))
            continue;

        tmp = string_aconcat(dirname, "/", ent->d_name);
        if (!tmp)
            continue;

        success = do_load_file(tmp, reqsym, reqtypes, record);
        free(tmp);
        if (success)
            break;
        *record = NULL;
    }

    closedir(dir);
    return *record != NULL;
}
#endif

static int
load_module(const char *impl, verto_ev_type reqtypes, module_record **record)
{
    int success = 0;
#ifndef BUILTIN_MODULE
    char *prefix = NULL;
    char *suffix = NULL;
    char *tmp = NULL;
#endif

    /* Check the cache */
    mutex_lock(&loaded_modules_mutex);
    if (impl) {
        for (*record = loaded_modules ; *record ; *record = (*record)->next) {
            if ((strchr(impl, '/') && !strcmp(impl, (*record)->filename))
                    || !strcmp(impl, (*record)->module->name)) {
                mutex_unlock(&loaded_modules_mutex);
                return 1;
            }
        }
    } else if (loaded_modules) {
        for (*record = loaded_modules ; *record ; *record = (*record)->next) {
            if (reqtypes == VERTO_EV_TYPE_NONE
                    || ((*record)->module->types & reqtypes) == reqtypes) {
                mutex_unlock(&loaded_modules_mutex);
                return 1;
            }
        }
    }
    mutex_unlock(&loaded_modules_mutex);

#ifndef BUILTIN_MODULE
    if (!module_get_filename_for_symbol(verto_convert_module, &prefix))
        return 0;

    /* Example output:
     *    prefix == /usr/lib/libverto-
     *    impl == glib
     *    suffix == .so.0
     * Put them all together: /usr/lib/libverto-glib.so.0 */
    tmp = strdup(prefix);
    if (!tmp) {
        free(prefix);
        return 0;
    }

    suffix = basename(tmp);
    suffix = strchr(suffix, '.');
    if (!suffix || strlen(suffix) < 1 || !(suffix = strdup(suffix))) {
        free(prefix);
        free(tmp);
        return 0;
    }
    strcpy(prefix + strlen(prefix) - strlen(suffix), "-");
    free(tmp);

    if (impl) {
        /* Try to do a load by the path */
        if (!success && strchr(impl, '/'))
            success = do_load_file(impl, 0, reqtypes, record);
        if (!success) {
            /* Try to do a load by the name */
            tmp = string_aconcat(prefix, impl, suffix);
            if (tmp) {
                success = do_load_file(tmp, 0, reqtypes, record);
                free(tmp);
            }
        }
    } else {
        /* NULL was passed, so we will use the dirname of
         * the prefix to try and find any possible plugins */
        tmp = strdup(prefix);
        if (tmp) {
            char *dname = strdup(dirname(tmp));
            free(tmp);

            tmp = strdup(basename(prefix));
            free(prefix);
            prefix = tmp;

            if (dname && prefix) {
                /* Attempt to find a module we are already linked to */
                success = do_load_dir(dname, prefix, suffix, 1, reqtypes,
                                      record);
                if (!success) {
#ifdef DEFAULT_MODULE
                    /* Attempt to find the default module */
                    success = load_module(DEFAULT_MODULE, reqtypes, record);
                    if (!success)
#endif /* DEFAULT_MODULE */
                        /* Attempt to load any plugin (we're desperate) */
                        success = do_load_dir(dname, prefix, suffix, 0,
                                              reqtypes, record);
                }
            }

            free(dname);
        }
    }

    free(suffix);
    free(prefix);
#endif /* BUILTIN_MODULE */
    return success;
}

static verto_ev *
make_ev(verto_ctx *ctx, verto_callback *callback,
        verto_ev_type type, verto_ev_flag flags)
{
    verto_ev *ev = NULL;

    if (!ctx || !callback)
        return NULL;

    ev = vresize(NULL, sizeof(verto_ev));
    if (ev) {
        memset(ev, 0, sizeof(verto_ev));
        ev->ctx        = ctx;
        ev->type       = type;
        ev->callback   = callback;
        ev->flags      = flags;
    }

    return ev;
}

static void
push_ev(verto_ctx *ctx, verto_ev *ev)
{
    verto_ev *tmp;

    if (!ctx || !ev)
        return;

    tmp = ctx->events;
    ctx->events = ev;
    ctx->events->next = tmp;
}

static void
remove_ev(verto_ev **origin, verto_ev *item)
{
    if (!origin || !*origin || !item)
        return;

    if (*origin == item)
        *origin = (*origin)->next;
    else
        remove_ev(&((*origin)->next), item);
}

static void
signal_ignore(verto_ctx *ctx, verto_ev *ev)
{
    (void) ctx;
    (void) ev;
}

verto_ctx *
verto_new(const char *impl, verto_ev_type reqtypes)
{
    module_record *mr = NULL;

    if (!load_module(impl, reqtypes, &mr))
        return NULL;

    return verto_convert_module(mr->module, 0, NULL);
}

verto_ctx *
verto_default(const char *impl, verto_ev_type reqtypes)
{
    module_record *mr = NULL;

    if (!load_module(impl, reqtypes, &mr))
        return NULL;

    return verto_convert_module(mr->module, 1, NULL);
}

int
verto_set_default(const char *impl, verto_ev_type reqtypes)
{
    module_record *mr;

    mutex_lock(&loaded_modules_mutex);
    if (loaded_modules || !impl) {
        mutex_unlock(&loaded_modules_mutex);
        return 0;
    }
    mutex_unlock(&loaded_modules_mutex);

    return load_module(impl, reqtypes, &mr);
}

int
verto_set_allocator(void *(*resize)(void *mem, size_t size),
                    int hierarchical)
{
    if (resize_cb || !resize)
        return 0;
    resize_cb = resize;
    resize_cb_hierarchical = hierarchical;
    return 1;
}

void
verto_free(verto_ctx *ctx)
{
    if (!ctx)
        return;

    ctx->ref = ctx->ref > 0 ? ctx->ref - 1 : 0;
    if (ctx->ref > 0)
        return;

    /* Cancel all pending events */
    while (ctx->events)
        verto_del(ctx->events);

    /* Free the private */
    if (!ctx->deflt || !ctx->module->funcs->ctx_default)
        ctx->module->funcs->ctx_free(ctx->ctx);

    vfree(ctx);
}

void
verto_cleanup(void)
{
    module_record *record;

    mutex_lock(&loaded_modules_mutex);

    for (record = loaded_modules; record; record = record->next) {
        module_close(record->dll);
        free(record->filename);
    }

    vfree(loaded_modules);
    loaded_modules = NULL;

    mutex_unlock(&loaded_modules_mutex);
    mutex_destroy(&loaded_modules_mutex);
}

void
verto_run(verto_ctx *ctx)
{
    if (!ctx)
        return;

    if (ctx->module->funcs->ctx_break && ctx->module->funcs->ctx_run)
        ctx->module->funcs->ctx_run(ctx->ctx);
    else {
        while (!ctx->exit)
            ctx->module->funcs->ctx_run_once(ctx->ctx);
        ctx->exit = 0;
    }
}

void
verto_run_once(verto_ctx *ctx)
{
    if (!ctx)
        return;
    ctx->module->funcs->ctx_run_once(ctx->ctx);
}

void
verto_break(verto_ctx *ctx)
{
    if (!ctx)
        return;

    if (ctx->module->funcs->ctx_break && ctx->module->funcs->ctx_run)
        ctx->module->funcs->ctx_break(ctx->ctx);
    else
        ctx->exit = 1;
}

int
verto_reinitialize(verto_ctx *ctx)
{
    verto_ev *tmp, *next;
    int error = 1;

    if (!ctx)
        return 0;

    /* Delete all events, but keep around the forkable ev structs */
    for (tmp = ctx->events; tmp; tmp = next) {
        next = tmp->next;

        if (tmp->flags & VERTO_EV_FLAG_REINITIABLE)
            ctx->module->funcs->ctx_del(ctx->ctx, tmp, tmp->ev);
        else
            verto_del(tmp);
    }

    /* Reinit the loop */
    if (ctx->module->funcs->ctx_reinitialize)
        ctx->module->funcs->ctx_reinitialize(ctx->ctx);

    /* Recreate events that were marked forkable */
    for (tmp = ctx->events; tmp; tmp = tmp->next) {
        tmp->actual = make_actual(tmp->flags);
        tmp->ev = ctx->module->funcs->ctx_add(ctx->ctx, tmp, &tmp->actual);
        if (!tmp->ev)
            error = 0;
    }

    return error;
}

#define doadd(ev, set, type) \
    ev = make_ev(ctx, callback, type, flags); \
    if (ev) { \
        set; \
        ev->actual = make_actual(ev->flags); \
        ev->ev = ctx->module->funcs->ctx_add(ctx->ctx, ev, &ev->actual); \
        if (!ev->ev) { \
            vfree(ev); \
            return NULL; \
        } \
        push_ev(ctx, ev); \
    }

verto_ev *
verto_add_io(verto_ctx *ctx, verto_ev_flag flags,
             verto_callback *callback, int fd)
{
    verto_ev *ev;

    if (fd < 0 || !(flags & (VERTO_EV_FLAG_IO_READ | VERTO_EV_FLAG_IO_WRITE)))
        return NULL;

    doadd(ev, ev->option.io.fd = fd, VERTO_EV_TYPE_IO);
    return ev;
}

verto_ev *
verto_add_timeout(verto_ctx *ctx, verto_ev_flag flags,
                  verto_callback *callback, time_t interval)
{
    verto_ev *ev;
    doadd(ev, ev->option.interval = interval, VERTO_EV_TYPE_TIMEOUT);
    return ev;
}

verto_ev *
verto_add_idle(verto_ctx *ctx, verto_ev_flag flags,
               verto_callback *callback)
{
    verto_ev *ev;
    doadd(ev,, VERTO_EV_TYPE_IDLE);
    return ev;
}

verto_ev *
verto_add_signal(verto_ctx *ctx, verto_ev_flag flags,
                 verto_callback *callback, int signal)
{
    verto_ev *ev;

    if (signal < 0)
        return NULL;
#ifndef WIN32
    if (signal == SIGCHLD)
        return NULL;
#endif
    if (callback == VERTO_SIG_IGN) {
        callback = signal_ignore;
        if (!(flags & VERTO_EV_FLAG_PERSIST))
            return NULL;
    }
    doadd(ev, ev->option.signal = signal, VERTO_EV_TYPE_SIGNAL);
    return ev;
}

verto_ev *
verto_add_child(verto_ctx *ctx, verto_ev_flag flags,
                verto_callback *callback, verto_proc proc)
{
    verto_ev *ev;

    if (flags & VERTO_EV_FLAG_PERSIST) /* persist makes no sense */
        return NULL;
#ifdef WIN32
    if (proc == NULL)
#else
    if (proc < 1)
#endif
        return NULL;
    doadd(ev, ev->option.child.proc = proc, VERTO_EV_TYPE_CHILD);
    return ev;
}

void
verto_set_private(verto_ev *ev, void *priv, verto_callback *free)
{
    if (!ev)
        return;
    if (ev->onfree && free)
        ev->onfree(ev->ctx, ev);
    ev->priv = priv;
    ev->onfree = free;
}

void *
verto_get_private(const verto_ev *ev)
{
    return ev->priv;
}

verto_ev_type
verto_get_type(const verto_ev *ev)
{
    return ev->type;
}

verto_ev_flag
verto_get_flags(const verto_ev *ev)
{
    return ev->flags;
}

void
verto_set_flags(verto_ev *ev, verto_ev_flag flags)
{
    if (!ev)
        return;

    /* No modification is needed, so do nothing. */
    if (MUTABLE(ev->flags) == MUTABLE(flags))
        return;

    ev->flags  &= ~_VERTO_EV_FLAG_MUTABLE_MASK;
    ev->flags  |= MUTABLE(flags);

    /* If setting flags isn't supported, just rebuild the event */
    if (!ev->ctx->module->funcs->ctx_set_flags) {
        ev->ctx->module->funcs->ctx_del(ev->ctx->ctx, ev, ev->ev);
        ev->actual = make_actual(ev->flags);
        ev->ev = ev->ctx->module->funcs->ctx_add(ev->ctx->ctx, ev, &ev->actual);
        assert(ev->ev); /* Here is the main reason why modules should */
        return;         /* implement set_flags(): we cannot fail gracefully. */
    }

    ev->actual &= ~_VERTO_EV_FLAG_MUTABLE_MASK;
    ev->actual |= MUTABLE(flags);
    ev->ctx->module->funcs->ctx_set_flags(ev->ctx->ctx, ev, ev->ev);
}

int
verto_get_fd(const verto_ev *ev)
{
    if (ev && (ev->type == VERTO_EV_TYPE_IO))
        return ev->option.io.fd;
    return -1;
}

verto_ev_flag
verto_get_fd_state(const verto_ev *ev)
{
    return ev->option.io.state;
}

time_t
verto_get_interval(const verto_ev *ev)
{
    if (ev && (ev->type == VERTO_EV_TYPE_TIMEOUT))
        return ev->option.interval;
    return 0;
}

int
verto_get_signal(const verto_ev *ev)
{
    if (ev && (ev->type == VERTO_EV_TYPE_SIGNAL))
        return ev->option.signal;
    return -1;
}

verto_proc
verto_get_proc(const verto_ev *ev) {
    if (ev && ev->type == VERTO_EV_TYPE_CHILD)
        return ev->option.child.proc;
    return (verto_proc) 0;
}

verto_proc_status
verto_get_proc_status(const verto_ev *ev)
{
    return ev->option.child.status;
}

verto_ctx *
verto_get_ctx(const verto_ev *ev)
{
    return ev->ctx;
}

void
verto_del(verto_ev *ev)
{
    if (!ev)
        return;

    /* If the event is freed in the callback, we just set a flag so that
     * verto_fire() can actually do the delete when the callback completes.
     *
     * If we don't do this, than verto_fire() will access freed memory. */
    if (ev->depth > 0) {
        ev->deleted = 1;
        return;
    }

    if (ev->onfree)
        ev->onfree(ev->ctx, ev);
    ev->ctx->module->funcs->ctx_del(ev->ctx->ctx, ev, ev->ev);
    remove_ev(&(ev->ctx->events), ev);

    if ((ev->type == VERTO_EV_TYPE_IO) &&
        (ev->flags & VERTO_EV_FLAG_IO_CLOSE_FD) &&
        !(ev->actual & VERTO_EV_FLAG_IO_CLOSE_FD))
        close(ev->option.io.fd);

    vfree(ev);
}

verto_ev_type
verto_get_supported_types(verto_ctx *ctx)
{
    return ctx->module->types;
}

/*** THE FOLLOWING ARE FOR IMPLEMENTATION MODULES ONLY ***/

verto_ctx *
verto_convert_module(const verto_module *module, int deflt, verto_mod_ctx *mctx)
{
    verto_ctx *ctx = NULL;
    module_record *mr;

    if (!module)
        return NULL;

    if (deflt) {
        mutex_lock(&loaded_modules_mutex);
        for (mr = loaded_modules ; mr ; mr = mr->next) {
            verto_ctx *tmp;
            if (mr->module == module && mr->defctx) {
                if (mctx)
                    module->funcs->ctx_free(mctx);
                tmp = mr->defctx;
                tmp->ref++;
                mutex_unlock(&loaded_modules_mutex);
                return tmp;
            }
        }
        mutex_unlock(&loaded_modules_mutex);
    }

    if (!mctx) {
        mctx = deflt
                    ? (module->funcs->ctx_default
                        ? module->funcs->ctx_default()
                        : module->funcs->ctx_new())
                    : module->funcs->ctx_new();
        if (!mctx)
            goto error;
    }

    ctx = vresize(NULL, sizeof(verto_ctx));
    if (!ctx)
        goto error;
    memset(ctx, 0, sizeof(verto_ctx));

    ctx->ref = 1;
    ctx->ctx = mctx;
    ctx->module = module;
    ctx->deflt = deflt;

    if (deflt) {
        module_record **tmp;

        mutex_lock(&loaded_modules_mutex);
        tmp = &loaded_modules;
        for (mr = loaded_modules ; mr ; mr = mr->next) {
            if (mr->module == module) {
                assert(mr->defctx == NULL);
                mr->defctx = ctx;
                mutex_unlock(&loaded_modules_mutex);
                return ctx;
            }

            if (!mr->next) {
                tmp = &mr->next;
                break;
            }
        }
        mutex_unlock(&loaded_modules_mutex);

        *tmp = vresize(NULL, sizeof(module_record));
        if (!*tmp) {
            vfree(ctx);
            goto error;
        }

        memset(*tmp, 0, sizeof(module_record));
        (*tmp)->defctx = ctx;
        (*tmp)->module = module;
    }

    return ctx;

error:
    if (mctx)
        module->funcs->ctx_free(mctx);
    return NULL;
}

void
verto_fire(verto_ev *ev)
{
    void *priv;

    ev->depth++;
    ev->callback(ev->ctx, ev);
    ev->depth--;

    if (ev->depth == 0) {
        if (!(ev->flags & VERTO_EV_FLAG_PERSIST) || ev->deleted)
            verto_del(ev);
        else {
            if (!(ev->actual & VERTO_EV_FLAG_PERSIST)) {
                ev->actual = make_actual(ev->flags);
                priv = ev->ctx->module->funcs->ctx_add(ev->ctx->ctx, ev, &ev->actual);
                assert(priv); /* TODO: create an error callback */
                ev->ctx->module->funcs->ctx_del(ev->ctx->ctx, ev, ev->ev);
                ev->ev = priv;
            }

            if (ev->type == VERTO_EV_TYPE_IO)
                ev->option.io.state = VERTO_EV_FLAG_NONE;
            if (ev->type == VERTO_EV_TYPE_CHILD)
                ev->option.child.status = 0;
        }
    }
}

void
verto_set_proc_status(verto_ev *ev, verto_proc_status status)
{
    if (ev && ev->type == VERTO_EV_TYPE_CHILD)
        ev->option.child.status = status;
}

void
verto_set_fd_state(verto_ev *ev, verto_ev_flag state)
{
    /* Filter out only the io flags */
    state = state & (VERTO_EV_FLAG_IO_READ |
                     VERTO_EV_FLAG_IO_WRITE |
                     VERTO_EV_FLAG_IO_ERROR);

    /* Don't report read/write if the socket is closed */
    if (state & VERTO_EV_FLAG_IO_ERROR)
        state = VERTO_EV_FLAG_IO_ERROR;

    if (ev && ev->type == VERTO_EV_TYPE_IO)
        ev->option.io.state = state;
}
