/*
 * common/signal.h - Signal handling functions
 *
 * Copyright © 2009 Julien Danjou <julien@danjou.info>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

#ifndef AWESOME_COMMON_SIGNAL
#define AWESOME_COMMON_SIGNAL

#include "common/array.h"

DO_ARRAY(const void *, cptr, DO_NOTHING)

typedef struct
{
    unsigned long id;
    cptr_array_t sigfuncs;
} signal_t;

static inline int
signal_cmp(const void *a, const void *b)
{
    const signal_t *x = a, *y = b;
    return x->id > y->id ? 1 : (x->id < y->id ? -1 : 0);
}

static inline void
signal_wipe(signal_t *sig)
{
    cptr_array_wipe(&sig->sigfuncs);
}

ARRAY_TYPE_EXTRA(signal_t, signal, struct signal_array_t *inherits_from)
BARRAY_FUNCS(signal_t, signal, signal_wipe, signal_cmp)

static inline signal_t *
signal_array_getbyid(signal_array_t *arr, unsigned long id)
{
    signal_t sig = { .id = id };
    signal_t *result;

    result = signal_array_lookup(arr, &sig);
    if(result)
        return result;

    /* The signal doesn't exist yet. Check if some of our inherits_from have the
     * signal and if yes, add it.
     */
    signal_array_t *inherit = arr->inherits_from;
    while(inherit != NULL)
    {
        if(signal_array_lookup(inherit, &sig))
            break;
        inherit = inherit->inherits_from;
    }
    if(inherit)
    {
        /* Add the signal to this array to pretend it always existed */
        signal_array_insert(arr, sig);
        result = signal_array_lookup(arr, &sig);
        assert(result != NULL);
    }
    return result;
}

/** Add a signal to a signal array.
 * Signals have to be added before they can be added or emitted.
 * \param arr The signal array.
 * \param name The signal name.
 */
static inline void
signal_add(signal_array_t *arr, const char *name)
{
    unsigned long tok = a_strhash((const unsigned char *) name);
    signal_t *sigfound = signal_array_getbyid(arr, tok);
    if(!sigfound)
    {
        signal_t sig = { .id = tok };
        signal_array_insert(arr, sig);
    }
}

/** Connect a signal inside a signal array.
 * You are in charge of reference counting.
 * \param arr The signal array.
 * \param name The signal name.
 * \param ref The reference to add.
 */
static inline void
signal_connect(signal_array_t *arr, const char *name, const void *ref)
{
    unsigned long tok = a_strhash((const unsigned char *) name);
    signal_t *sigfound = signal_array_getbyid(arr, tok);
    if(sigfound)
        cptr_array_append(&sigfound->sigfuncs, ref);
    else
        warn("Trying to connect to unknown signal '%s'", name);
}

/** Disconnect a signal inside a signal array.
 * You are in charge of reference counting.
 * \param arr The signal array.
 * \param name The signal name.
 * \param ref The reference to remove.
 */
static inline void
signal_disconnect(signal_array_t *arr, const char *name, const void *ref)
{
    signal_t *sigfound = signal_array_getbyid(arr,
                                              a_strhash((const unsigned char *) name));
    if(sigfound)
    {
        foreach(func, sigfound->sigfuncs)
            if(ref == *func)
            {
                cptr_array_remove(&sigfound->sigfuncs, func);
                break;
            }
    } else
        warn("Trying to disconnect from unknown signal '%s'", name);
}

#endif

// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80