/*
 * SwamiControl.c - Swami control base object
 *
 * Swami
 * Copyright (C) 1999-2014 Element Green <element@elementsofsound.org>
 *
 * 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; version 2
 * of the License only.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA or point your web browser to http://www.gnu.org.
 */
#include <stdio.h>
#include <glib.h>
#include <glib-object.h>
#include <libinstpatch/libinstpatch.h>

#include "SwamiControl.h"
#include "SwamiControlEvent.h"
#include "SwamiControlQueue.h"
#include "SwamiControlFunc.h"
#include "SwamiControlProp.h"
#include "SwamiLog.h"
#include "SwamiParam.h"
#include "swami_priv.h"
#include "util.h"
#include "marshals.h"
#include "config.h"

/* work around for bug in G_BREAKPOINT where SIGTRAP symbol is used on
 * some architectures without including signal.h header. */
#if DEBUG
#include <signal.h>
#endif

/* max number of destination connections per control (for mem optimizations) */
#define MAX_DEST_CONNECTIONS 64

enum
{
    CONNECT_SIGNAL,
    DISCONNECT_SIGNAL,
    SPEC_CHANGED_SIGNAL,
    SIGNAL_COUNT
};

/* a structure defining an endpoint of a connection */
typedef struct _SwamiControlConn
{
    guint flags;		/* SwamiControlConnPriority | SwamiControlConnFlags */
    SwamiControl *control;	/* connection control */

    /* for src -> dest connections only */
    SwamiValueTransform trans;	/* transform func */
    GDestroyNotify destroy;	/* function to call when connection is destroyed */
    gpointer data;		/* user data to pass to transform function */
} SwamiControlConn;

#define swami_control_conn_new()  g_slice_new0 (SwamiControlConn)
#define swami_control_conn_free(conn)  g_slice_free (SwamiControlConn, conn)

/* bag used for transmitting values to destination controls */
typedef struct
{
    SwamiControl *control;
    SwamiValueTransform trans;
    gpointer data;
} CtrlUpdateBag;


static void swami_control_class_init(SwamiControlClass *klass);
static void swami_control_init(SwamiControl *control);
static void swami_control_finalize(GObject *object);
static void
swami_control_connect_real(SwamiControl *src, SwamiControl *dest,
                           SwamiValueTransform trans,
                           gpointer data, GDestroyNotify destroy, guint flags);
static gint GCompare_func_conn_priority(gconstpointer a, gconstpointer b);
static void item_prop_value_transform(const GValue *src, GValue *dest,
                                      gpointer data);
static void swami_control_real_disconnect(SwamiControl *c1, SwamiControl *c2,
        guint flags);
static inline void swami_control_set_event_real(SwamiControl *control,
        SwamiControlEvent *event);
static inline gboolean swami_control_loop_check(SwamiControl *control,
        SwamiControlEvent *event);

/* a master list of all controls, used for doing periodic inactive event
   expiration cleanup */
G_LOCK_DEFINE_STATIC(control_list);
static GList *control_list = NULL;

static GObjectClass *parent_class = NULL;
static guint control_signals[SIGNAL_COUNT] = { 0 };

/* debug flag for enabling display of control operations */
#if DEBUG
gboolean swami_control_debug = FALSE;
SwamiControl *swami_control_break = NULL;

#define SWAMI_CONTROL_TEST_BREAK(a, b) \
  if (swami_control_break && (a == swami_control_break || b == swami_control_break)) \
    G_BREAKPOINT ()

/* generate a descriptive control description string, must be freed when
   finished */
static char *
pretty_control(SwamiControl *ctrl)
{
    char *s;

    if(!ctrl)
    {
        return (g_strdup(""));
    }

    if(SWAMI_IS_CONTROL_FUNC(ctrl))
    {
        SwamiControlFunc *fn = SWAMI_CONTROL_FUNC(ctrl);
        s = g_strdup_printf("<%s>%p (get=%p, set=%p)", G_OBJECT_TYPE_NAME(fn),
                            fn, fn->get_func, fn->set_func);
    }
    else if(SWAMI_IS_CONTROL_PROP(ctrl))
    {
        SwamiControlProp *pc = SWAMI_CONTROL_PROP(ctrl);
        s = g_strdup_printf("<%s>%p (object=<%s>%p, property='%s')",
                            G_OBJECT_TYPE_NAME(pc), pc,
                            pc->object ? G_OBJECT_TYPE_NAME(pc->object) : "",
                            pc->object, pc->spec ? pc->spec->name : "");
    }
    else
    {
        s = g_strdup_printf("<%s>%p", G_OBJECT_TYPE_NAME(ctrl), ctrl);
    }

    return (s);
}
#endif


GType
swami_control_get_type(void)
{
    static GType obj_type = 0;

    if(!obj_type)
    {
        static const GTypeInfo obj_info =
        {
            sizeof(SwamiControlClass), NULL, NULL,
            (GClassInitFunc) swami_control_class_init, NULL, NULL,
            sizeof(SwamiControl), 0,
            (GInstanceInitFunc) swami_control_init
        };

        obj_type = g_type_register_static(SWAMI_TYPE_LOCK, "SwamiControl",
                                          &obj_info, 0);
    }

    return (obj_type);
}

static void
swami_control_class_init(SwamiControlClass *klass)
{
    GObjectClass *obj_class = G_OBJECT_CLASS(klass);

    parent_class = g_type_class_peek_parent(klass);

    obj_class->finalize = swami_control_finalize;

    klass->connect = NULL;
    klass->disconnect = NULL;

    control_signals[CONNECT_SIGNAL] =
        g_signal_new("connect", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST,
                     G_STRUCT_OFFSET(SwamiControlClass, connect), NULL, NULL,
                     swami_marshal_VOID__OBJECT_UINT, G_TYPE_NONE,
                     2, G_TYPE_OBJECT, G_TYPE_UINT);
    control_signals[DISCONNECT_SIGNAL] =
        g_signal_new("disconnect", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST,
                     G_STRUCT_OFFSET(SwamiControlClass, disconnect), NULL, NULL,
                     swami_marshal_VOID__OBJECT_UINT, G_TYPE_NONE,
                     2, G_TYPE_OBJECT, G_TYPE_UINT);
    control_signals[SPEC_CHANGED_SIGNAL] =
        g_signal_new("spec-changed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST,
                     0, NULL, NULL,
                     g_cclosure_marshal_VOID__PARAM, G_TYPE_NONE, 1,
                     G_TYPE_PARAM);
}

/* creating an instance of SwamiControl creates a send only event control */
static void
swami_control_init(SwamiControl *control)
{
    control->flags = SWAMI_CONTROL_SENDS;

    /* prepend control to master list */
    G_LOCK(control_list);
    control_list = g_list_prepend(control_list, control);
    G_UNLOCK(control_list);
}

static void
swami_control_finalize(GObject *object)
{
    SwamiControl *control = SWAMI_CONTROL(object);

    /* free control queue (if any exist) */
    if (control->queue)
    {
        g_object_unref (control->queue); /* -- unref old queue */
    }

    G_LOCK(control_list);
    control_list = g_list_remove(control_list, object);
    G_UNLOCK(control_list);
}

/**
 * swami_control_new:
 *
 * Create a new #SwamiControl instance.  #SwamiControl is the base class for
 * other control types as well.  Creating an instance of a #SwamiControl
 * will create a send only event control.
 *
 * Returns: New #SwamiControl object, the caller owns a reference.
 */
SwamiControl *
swami_control_new(void)
{
    return (SWAMI_CONTROL(g_object_new(SWAMI_TYPE_CONTROL, NULL)));
}

/**
 * swami_control_connect:
 * @src: Source control to connect (readable)
 * @dest: Destination control to connect (writable)
 * @flags: Flags for this connection (#SwamiControlConnFlags)
 *
 * Connect two controls (i.e., when the @src control's value changes
 * the @dest control is set to this value). Useful flags include the
 * #SWAMI_CONTROL_CONN_INIT flag which will cause @dest to be set to
 * the current value of @src and #SWAMI_CONTROL_CONN_BIDIR which will
 * cause a bi-directional connection to be made (as if 2 calls where
 * made to this function with the @src and @dest swapped the second
 * time). The connection priority can also be set via the flags field
 * by or-ing in #SwamiControlConnPriority values (assumes default
 * priority if not specified). The priority determines the order in
 * which connections are processed.  The #SWAMI_CONTROL_CONN_SPEC flag will
 * cause the parameter spec of the @dest control to be set to that of @src.
 */
void
swami_control_connect(SwamiControl *src, SwamiControl *dest, guint flags)
{
    swami_control_connect_transform(src, dest, flags, NULL, NULL, NULL, NULL,
                                    NULL, NULL);
}

/**
 * swami_control_connect_transform:
 * @src: Source control to connect (readable)
 * @dest: Destination control to connect (writable)
 * @flags: Flags for this connection (#SwamiControlConnFlags).
 * @trans1: Value transform function from @src to @dest (or %NULL for no transform)
 * @trans2: Value transform function from @dest to @src
 *   (#SWAMI_CONTROL_CONN_BIDIR only, %NULL for no transform).
 * @data1: User data to pass to @trans1 function.
 * @data2: User data to pass to @trans2 function.
 *   (#SWAMI_CONTROL_CONN_BIDIR only, %NULL for no transform).
 * @destroy1: Optional callback to free @data1 when @trans1 is disconnected.
 * @destroy2: Optional callback to free @data2 when @trans2 is disconnected.
 *
 * Like swami_control_connect() but transform functions can be specified
 * during connect, rather than having to call swami_control_set_transform()
 * later.
 */
void
swami_control_connect_transform(SwamiControl *src, SwamiControl *dest,
                                guint flags,
                                SwamiValueTransform trans1,
                                SwamiValueTransform trans2,
                                gpointer data1, gpointer data2,
                                GDestroyNotify destroy1,
                                GDestroyNotify destroy2)
{
    guint flags2;

    g_return_if_fail(SWAMI_IS_CONTROL(src));
    g_return_if_fail(SWAMI_IS_CONTROL(dest));

    if(flags & SWAMI_CONTROL_CONN_BIDIR)
    {
        swami_control_connect_real(src, dest, trans1, data1, destroy1, flags);

        flags2 = flags & ~(SWAMI_CONTROL_CONN_INIT | SWAMI_CONTROL_CONN_SPEC);
        swami_control_connect_real(dest, src, trans2, data2, destroy2, flags2);
    }
    else
    {
        swami_control_connect_real(src, dest, trans1, data1, destroy1, flags);
    }

#if DEBUG

    if(swami_control_debug)
    {
        char *s1, *s2;
        s1 = pretty_control(src);
        s2 = pretty_control(dest);
        g_message("Connect: %s %s %s", s1,
                  (flags & SWAMI_CONTROL_CONN_BIDIR) ? "<-->" : "-->", s2);
        g_free(s1);
        g_free(s2);

        SWAMI_CONTROL_TEST_BREAK(src, dest);
    }

#endif
}

static void
swami_control_connect_real(SwamiControl *src, SwamiControl *dest,
                           SwamiValueTransform trans,
                           gpointer data, GDestroyNotify destroy, guint flags)
{
    SwamiControlConn *sconn, *dconn;
    GValue value = { 0 }, transval = { 0 };

    /* allocate and init connections */
    sconn = swami_control_conn_new();
    sconn->flags = (flags & SWAMI_CONTROL_CONN_PRIORITY_MASK)
                   | SWAMI_CONTROL_CONN_OUTPUT;
    sconn->control = dest;
    sconn->trans = trans;
    sconn->data = data;
    sconn->destroy = destroy;

    dconn = swami_control_conn_new();
    dconn->flags = (flags & SWAMI_CONTROL_CONN_PRIORITY_MASK)
                   | SWAMI_CONTROL_CONN_INPUT;
    dconn->control = src;

    /* add output connection to source control */
    SWAMI_LOCK_WRITE(src);

    if(swami_log_if_fail(src->flags & SWAMI_CONTROL_SENDS))
    {
        SWAMI_UNLOCK_WRITE(src);
        goto err_src;
    }

    if(g_slist_length(src->outputs) >= MAX_DEST_CONNECTIONS)
    {
        SWAMI_UNLOCK_WRITE(src);
        g_critical("Maximum number of control connections reached!");
        goto err_src;
    }

    /* add connection to list */
    src->outputs = g_slist_insert_sorted(src->outputs, sconn,
                                         GCompare_func_conn_priority);
    g_object_ref(dest);	        /* ++ ref dest for source connection */
    SWAMI_UNLOCK_WRITE(src);


    /* add input connection to destination control */
    SWAMI_LOCK_WRITE(dest);

    if(swami_log_if_fail(dest->flags & SWAMI_CONTROL_RECVS))
    {
        SWAMI_UNLOCK_WRITE(dest);
        goto err_dest;
    }

    dest->inputs = g_slist_prepend(dest->inputs, dconn);
    g_object_ref(src);	        /* ++ ref src for destination connection */
    SWAMI_UNLOCK_WRITE(dest);

    /* check if connect parameter spec flag is set for src, and slave the
       parameter spec if so */
    if(flags & SWAMI_CONTROL_CONN_SPEC)
    {
        swami_control_sync_spec(dest, src, trans, data);
    }

    /* initialize destination control from current source value? */
    if(flags & SWAMI_CONTROL_CONN_INIT)
    {
        swami_control_get_value_native(src, &value);

        if(trans)	/* transform function provided? */
        {
            g_value_init(&transval, dest->value_type ? dest->value_type
                         : G_VALUE_TYPE(&value));
            trans(&value, &transval, data);
            swami_control_set_value(dest, &transval);
            g_value_unset(&transval);
        }
        else
        {
            swami_control_set_value(dest, &value);
        }

        g_value_unset(&value);
    }

    /* emit connect signals */
    g_signal_emit(src, control_signals[CONNECT_SIGNAL], 0, dest,
                  flags | SWAMI_CONTROL_CONN_OUTPUT);
    g_signal_emit(dest, control_signals[CONNECT_SIGNAL], 0, src,
                  flags | SWAMI_CONTROL_CONN_INPUT);
    return;

err_dest:
    /* error occured after src already connected, undo */
    SWAMI_LOCK_WRITE(src);
    {
        GSList **list, *p, *prev = NULL;

        list = &src->outputs;
        p = *list;

        while(p)
        {
            if(p->data == sconn)
            {
                if(!prev)
                {
                    *list = p->next;
                }
                else
                {
                    prev->next = p->next;
                }

                g_slist_free_1(p);
                g_object_unref(dest);  /* -- unref dest from source connection */
                break;
            }

            prev = p;
            p = g_slist_next(p);
        }
    }
    SWAMI_UNLOCK_WRITE(src);
    /* fall through */

err_src:
    /* OK to free connections unlocked since they aren't used anymore */
    swami_control_conn_free(sconn);
    swami_control_conn_free(dconn);
}

/* a priority comparison function for lists of #SwamiControlConn objects */
static gint
GCompare_func_conn_priority(gconstpointer a, gconstpointer b)
{
    SwamiControlConn *aconn = (SwamiControlConn *)a;
    SwamiControlConn *bconn = (SwamiControlConn *)b;
    return ((aconn->flags & SWAMI_CONTROL_CONN_PRIORITY_MASK)
            - (bconn->flags & SWAMI_CONTROL_CONN_PRIORITY_MASK));
}

/**
 * swami_control_connect_item_prop:
 * @dest: Destination control
 * @object: Object of source parameter to convert to user units
 * @pspec: Parameter spec of property of @object to connect to
 *
 * An ultra-convenience function to connect an existing control to a synthesis
 * property of an object (#IpatchItem property for example).  The connection is
 * bi-directional and transform functions are used to convert between the two
 * unit types as needed.
 */
void
swami_control_connect_item_prop(SwamiControl *dest, GObject *object,
                                GParamSpec *pspec)
{
    SwamiControl *src;
    gpointer data1, data2;
    guint src_unit, dest_unit;
    IpatchUnitInfo *info;
    GParamSpec *destspec;

    g_return_if_fail(SWAMI_IS_CONTROL(dest));
    g_return_if_fail(G_IS_OBJECT(object));
    g_return_if_fail(G_IS_PARAM_SPEC(pspec));

    /* get/create control for source item synthesis parameter */
    src = swami_get_control_prop(object, pspec);		/* ++ ref */
    g_return_if_fail(src != NULL);

    /* get the synthesis unit type for this parameter */
    ipatch_param_get(pspec, "unit-type", &src_unit, NULL);

    if(swami_log_if_fail(src_unit != 0))	/* error if no unit type */
    {
        g_object_unref(src);	/* -- unref */
        return;
    }

    /* get the user unit type to convert to */
    info = ipatch_unit_class_lookup_map(IPATCH_UNIT_CLASS_USER, src_unit);

    if(info)	/* use the user unit type if found and not the same as src type */
    {
        dest_unit = info->id;

        if(src_unit == dest_unit)
        {
            dest_unit = 0;
        }
    }
    else
    {
        dest_unit = 0;
    }

    if(dest_unit)
    {
        /* pass unit types to item_prop_value_transform */
        data1 = GUINT_TO_POINTER(src_unit | (dest_unit << 16));
        data2 = GUINT_TO_POINTER(dest_unit | (src_unit << 16));

        /* transform the parameter spec if necessary */
        destspec = swami_control_transform_spec(dest, src,   /* !! floating ref */
                                                item_prop_value_transform, data1);
        g_return_if_fail(destspec != NULL);

        ipatch_param_set(destspec, "unit-type", dest_unit, NULL);
        swami_control_set_spec(dest, destspec);

        swami_control_connect_transform(src, dest, SWAMI_CONTROL_CONN_BIDIR_INIT,
                                        item_prop_value_transform,
                                        item_prop_value_transform,
                                        data1, data2, NULL, NULL);
    }
    else
        swami_control_connect_transform(src, dest, SWAMI_CONTROL_CONN_BIDIR_SPEC_INIT,
                                        NULL, NULL, NULL, NULL, NULL, NULL);
}

/* value transform function for swami_control_connect_item_prop() */
static void
item_prop_value_transform(const GValue *src, GValue *dest, gpointer data)
{
    guint src_unit, dest_unit;

    src_unit = GPOINTER_TO_UINT(data);
    dest_unit = src_unit >> 16;
    src_unit &= 0xFFFF;

    /* do the unit conversion */
    ipatch_unit_convert(src_unit, dest_unit, src, dest);
}

/**
 * swami_control_disconnect:
 * @src: Source control of an existing connection
 * @dest: Destination control of an existing connection
 *
 * Disconnects a connection specified by its @src and @dest controls.
 */
void
swami_control_disconnect(SwamiControl *src, SwamiControl *dest)
{
    guint flags = 0;
    GSList *p;

    g_return_if_fail(SWAMI_IS_CONTROL(src));
    g_return_if_fail(SWAMI_IS_CONTROL(dest));

    /* check and see if connection exists */
    SWAMI_LOCK_READ(dest);
    p = dest->inputs; /* use single dest input list to simplify things */

    while(p)			/* look for matching connection */
    {
        SwamiControlConn *conn = (SwamiControlConn *)(p->data);

        if(src == conn->control)
        {
            flags = conn->flags;	/* get flags under lock */
            break;
        }

        p = g_slist_next(p);
    }

    SWAMI_UNLOCK_READ(dest);

    /* adjust flags for src control connection */
    flags &= ~SWAMI_CONTROL_CONN_INPUT;
    flags |= SWAMI_CONTROL_CONN_OUTPUT;

    if(p)	       /* connection found? - emit disconnect signal */
    {
        g_signal_emit(src, control_signals[DISCONNECT_SIGNAL], 0, dest, flags);
        swami_control_real_disconnect(src, dest, flags);
    }
}

/**
 * swami_control_disconnect_all:
 * @control: Swami control object
 *
 * Disconnect all connections from a control.
 */
void
swami_control_disconnect_all(SwamiControl *control)
{
    SwamiControl *src, *dest = NULL;
    SwamiControlConn *conn;
    guint flags = 0;

    g_return_if_fail(SWAMI_IS_CONTROL(control));

    do
    {
        /* look for a connection to disconnect */
        SWAMI_LOCK_READ(control);

        if(control->inputs)	/* any more input connections? */
        {
            conn = (SwamiControlConn *)(control->inputs->data);
            src = g_object_ref(conn->control);  /* ++ ref connection source */
            dest = control;
            flags = conn->flags;
        }
        else if(control->outputs)  /* any more direct output connections? */
        {
            conn = (SwamiControlConn *)(control->outputs->data);
            dest = g_object_ref(conn->control);  /* ++ ref connection dest */
            src = control;
            flags = conn->flags;
        }
        else
        {
            src = NULL;    /* no more connections */
        }

        SWAMI_UNLOCK_READ(control);

        if(src)
        {
            /* adjust flags for src control connection */
            flags &= ~SWAMI_CONTROL_CONN_INPUT;
            flags |= SWAMI_CONTROL_CONN_OUTPUT;

            g_signal_emit(src, control_signals[DISCONNECT_SIGNAL], 0,
                          dest, flags);
            swami_control_real_disconnect(src, dest, flags);

            /* -- unref connection control */
            if(control != src)
            {
                g_object_unref(src);
            }
            else
            {
                g_object_unref(dest);
            }
        }
    }
    while(src);
}

/**
 * swami_control_disconnect_unref:
 * @control: Swami control object
 *
 * A convenience function to disconnect all connections of a control and
 * unref it. This is often useful for GDestroyNotify callbacks where a
 * control's creator wishes to destroy the control. The control is only
 * destroyed if it is not referenced by anything else.
 */
void
swami_control_disconnect_unref(SwamiControl *control)
{
    g_return_if_fail(SWAMI_IS_CONTROL(control));
    swami_control_disconnect_all(control);
    g_object_unref(control);
}

/* real disconnect routine, the default class method */
static void
swami_control_real_disconnect(SwamiControl *c1, SwamiControl *c2, guint flags)
{
    GSList **list, *p, *prev = NULL;
    GDestroyNotify destroy = NULL;
    gpointer data = NULL;
    SwamiControlConn *conn;

    SWAMI_LOCK_WRITE(c1);

    if(flags & SWAMI_CONTROL_CONN_OUTPUT)  /* output conn (source control)? */
    {
        list = &c1->outputs;    /* use output connection list */
    }
    else
    {
        list = &c1->inputs;    /* destination control - use input conn list */
    }

    p = *list;

    while(p)			/* search for connection in list */
    {
        conn = (SwamiControlConn *)(p->data);

        if(c2 == conn->control)	/* connection found? destroy it */
        {
            if(prev)
            {
                prev->next = p->next;
            }
            else
            {
                *list = p->next;
            }

            g_slist_free_1(p);
            g_object_unref(conn->control);  /* -- unref control from conn */

            /* store destroy notify if output conn */
            if(flags & SWAMI_CONTROL_CONN_OUTPUT)
            {
                destroy = conn->destroy;
                data = conn->data;
            }

            swami_control_conn_free(conn);  /* free the connection */
            break;
        }

        prev = p;
        p = g_slist_next(p);
    }

    SWAMI_UNLOCK_WRITE(c1);

    /* call the destroy notify for the transform user data if any */
    if(destroy && data)
    {
        destroy(data);
    }

    /* chain disconnect signal to destination control (if source control) */
    if(flags & SWAMI_CONTROL_CONN_OUTPUT)
    {

#if DEBUG

        if(swami_control_debug)
        {
            char *s1, *s2;
            s1 = pretty_control(c1);
            s2 = pretty_control(c2);
            g_message("Disconnect: %s =X= %s", s1, s2);
            g_free(s1);
            g_free(s2);
        }

        SWAMI_CONTROL_TEST_BREAK(c1, c2);
#endif

        /* adjust flags for input connection (destination control) */
        flags &= ~SWAMI_CONTROL_CONN_OUTPUT;
        flags |= SWAMI_CONTROL_CONN_INPUT;
        g_signal_emit(c2, control_signals[DISCONNECT_SIGNAL], 0, c1, flags);
        swami_control_real_disconnect(c2, c1, flags);
    }
}

/**
 * swami_control_get_connections:
 * @control: Control to get connections of
 * @dir: Direction of connections to get, #SWAMI_CONTROL_CONN_INPUT for incoming
 *   connections, #SWAMI_CONTROL_CONN_OUTPUT for outgoing (values can be OR'd
 *   to return all connections).
 *
 * Get a list of connections to a control.
 *
 * Returns: List of #SwamiControl objects connected to @control or %NULL if
 *   none. Caller owns reference to returned list and should unref when done.
 */
IpatchList *
swami_control_get_connections(SwamiControl *control, SwamiControlConnFlags dir)
{
    SwamiControlConn *conn;
    IpatchList *listobj;
    GList *list = NULL;
    GSList *p;

    g_return_val_if_fail(SWAMI_IS_CONTROL(control), NULL);

    SWAMI_LOCK_READ(control);

    if(dir & SWAMI_CONTROL_CONN_INPUT)
    {
        for(p = control->inputs; p; p = g_slist_next(p))
        {
            conn = (SwamiControlConn *)(p->data);
            list = g_list_prepend(list, g_object_ref(conn->control));
        }
    }

    if(dir & SWAMI_CONTROL_CONN_OUTPUT)
    {
        for(p = control->outputs; p; p = g_slist_next(p))
        {
            conn = (SwamiControlConn *)(p->data);
            list = g_list_prepend(list, g_object_ref(conn->control));
        }
    }

    SWAMI_UNLOCK_READ(control);

    if(list)
    {
        listobj = ipatch_list_new();  /* ++ ref new list object */
        listobj->items = g_list_reverse(list);  /* reverse to priority order */
        return (listobj);		/* !! caller takes over reference */
    }
    else
    {
        return (NULL);
    }
}

/**
 * swami_control_set_transform:
 * @src: Source control of the connection
 * @dest: Destination control of the connection
 * @trans: Transform function to assign to the connection (or %NULL for none)
 * @data: User data to pass to @trans function (or %NULL).
 * @destroy: Optional callback to free @data when @trans is disconnected.
 *
 * Assigns a value transform function to an existing control connection.  The
 * connection is specified by @src and @dest controls.  The transform function
 * will receive values from the @src control and should transform them
 * appropriately for the @dest.
 */
void
swami_control_set_transform(SwamiControl *src, SwamiControl *dest,
                            SwamiValueTransform trans, gpointer data,
                            GDestroyNotify destroy)
{
    gboolean conn_found = FALSE;
    GDestroyNotify oldnotify = NULL;
    gpointer olddata = NULL;
    GSList *p;

    g_return_if_fail(SWAMI_IS_CONTROL(src));
    g_return_if_fail(SWAMI_IS_CONTROL(dest));

    SWAMI_LOCK_READ(src);

    /* look for matching connection */
    for(p = src->outputs; p; p = p->next)
    {
        SwamiControlConn *conn = (SwamiControlConn *)(p->data);

        if(dest == conn->control)
        {
            oldnotify = conn->destroy;
            olddata = conn->data;

            conn->trans = trans;
            conn->data = data;
            conn->destroy = destroy;
            conn_found = TRUE;
            break;
        }
    }

    SWAMI_UNLOCK_READ(src);

    /* if there already was a transform with destroy function, call it on the user data */
    if(oldnotify && olddata)
    {
        oldnotify(olddata);
    }

    g_return_if_fail(conn_found);
}

/**
 * swami_control_get_transform:
 * @src: Source control of the connection
 * @dest: Destination control of the connection
 * @trans: Pointer to store transform function of the connection
 *
 * Gets the current value transform function to an existing control connection.
 * The connection is specified by @src and @dest controls.  The value stored to
 * @trans may be %NULL if no transform function is assigned.
 */
void
swami_control_get_transform(SwamiControl *src, SwamiControl *dest,
                            SwamiValueTransform *trans)
{
    gboolean conn_found = FALSE;
    GSList *p;

    g_return_if_fail(SWAMI_IS_CONTROL(src));
    g_return_if_fail(SWAMI_IS_CONTROL(dest));
    g_return_if_fail(trans != NULL);

    SWAMI_LOCK_READ(src);

    /* look for matching connection */
    for(p = src->outputs; p; p = p->next)
    {
        SwamiControlConn *conn = (SwamiControlConn *)(p->data);

        if(dest == conn->control)
        {
            *trans = conn->trans;
            conn_found = TRUE;
            break;
        }
    }

    SWAMI_UNLOCK_READ(src);

    g_return_if_fail(conn_found);
}

/**
 * swami_control_set_flags:
 * @control: Control object
 * @flags: Value to assign to control flags (#SwamiControlFlags)
 *
 * Set flags of a control object. Flags can only be set for controls
 * that don't have any connections yet.
 */
void
swami_control_set_flags(SwamiControl *control, int flags)
{
    g_return_if_fail(SWAMI_IS_CONTROL(control));

    SWAMI_LOCK_WRITE(control);

    if(swami_log_if_fail(!(control->inputs || control->outputs)))
    {
        SWAMI_UNLOCK_WRITE(control);
        return;
    }

    flags &= SWAMI_CONTROL_FLAGS_USER_MASK;
    control->flags &= ~SWAMI_CONTROL_FLAGS_USER_MASK;
    control->flags |= flags;

    SWAMI_UNLOCK_WRITE(control);
}

/**
 * swami_control_get_flags:
 * @control: Control object
 *
 * Get the flags from a control object.
 *
 * Returns: Flags value (#SwamiControlFlags) for @control
 */
int
swami_control_get_flags(SwamiControl *control)
{
    int flags;

    g_return_val_if_fail(SWAMI_IS_CONTROL(control), 0);

    SWAMI_LOCK_READ(control);
    flags = control->flags;
    SWAMI_UNLOCK_READ(control);

    return (flags);
}

/**
 * swami_control_set_queue:
 * @control: Control object
 * @queue: Queue to use for control or %NULL to disable queuing
 *
 * Set the queue used by a control object. When a control has a queue all
 * receive/set events are sent to the queue. This queue can then be processed
 * at a later time (a GUI thread for example).
 *
 * queue is onwned by the control(++ref).
 * The queue can be removed later by calling the function with NULL for queue
 * parameter.
 * Also, when a control is finalized, the queue is removed and finalized
 * if it isn't owned by another control.
*/
void
swami_control_set_queue(SwamiControl *control, SwamiControlQueue *queue)
{
    g_return_if_fail(SWAMI_IS_CONTROL(control));
    g_return_if_fail(!queue || SWAMI_IS_CONTROL_QUEUE(queue));

    SWAMI_LOCK_WRITE(control);

    if(queue)
    {
        g_object_ref(queue);    /* ++ ref new queue */
    }

    if(control->queue)
    {
        g_object_unref(control->queue);    /* -- unref old queue */
    }

    control->queue = queue;
    SWAMI_UNLOCK_WRITE(control);
}

/**
 * swami_control_get_queue:
 * @control: Control object
 *
 * Get the queue used by a control object.
 *
 * Returns: The queue assigned to a control or %NULL if it does not have one.
 *   The caller owns a reference to the returned queue object.
 */
SwamiControlQueue *
swami_control_get_queue(SwamiControl *control)
{
    SwamiControlQueue *queue = NULL;

    g_return_val_if_fail(SWAMI_IS_CONTROL(control), NULL);

    SWAMI_LOCK_READ(control);

    if(control->queue)
    {
        queue = g_object_ref(control->queue);    /* ++ ref queue */
    }

    SWAMI_UNLOCK_WRITE(control);

    return (queue);		/* !! caller takes over reference */
}

/**
 * swami_control_get_spec:
 * @control: Control object
 *
 * Get a control object's parameter specification.
 *
 * Returns: The parameter spec or %NULL on error or if the given
 * control is type independent. The returned spec is used internally
 * and should not be modified or freed, however the caller does own a
 * reference to it and should unref it with g_param_spec_unref when
 * finished.
 */
GParamSpec *
swami_control_get_spec(SwamiControl *control)
{
    SwamiControlClass *klass;
    GParamSpec *pspec = NULL;

    g_return_val_if_fail(SWAMI_IS_CONTROL(control), NULL);

    klass = SWAMI_CONTROL_GET_CLASS(control);
    g_return_val_if_fail(klass->get_spec != NULL, NULL);

    SWAMI_LOCK_READ(control);

    if(klass->get_spec)
    {
        pspec = (*klass->get_spec)(control);
    }

    if(pspec)
    {
        g_param_spec_ref(pspec);
    }

    SWAMI_UNLOCK_READ(control);

    return (pspec);
}

/**
 * swami_control_set_spec:
 * @control: Control object
 * @pspec: The parameter specification to assign (used directly)
 *
 * Set a control object's parameter specification. This function uses
 * the @pspec directly (its refcount is incremented and ownership is
 * taken). The parameter spec is not permitted to be of a different
 * type than the previous parameter spec.
 *
 * Returns: %TRUE if parameter specification successfully set, %FALSE
 *   otherwise (in which case the @pspec is unreferenced).
 */
gboolean
swami_control_set_spec(SwamiControl *control, GParamSpec *pspec)
{
    SwamiControlClass *klass;
    GParamSpec *newspec;
    GType value_type;
    gboolean retval;

    g_return_val_if_fail(SWAMI_IS_CONTROL(control), FALSE);
    g_return_val_if_fail(G_IS_PARAM_SPEC(pspec), FALSE);

    klass = SWAMI_CONTROL_GET_CLASS(control);
    g_return_val_if_fail(klass->get_spec != NULL, FALSE);
    g_return_val_if_fail(klass->set_spec != NULL, FALSE);

    value_type = G_PARAM_SPEC_VALUE_TYPE(pspec);

    /* use derived type if GBoxed or GObject type */
    if(value_type == G_TYPE_BOXED || value_type == G_TYPE_OBJECT)
    {
        value_type = pspec->value_type;
    }

    /* if control's value type doesn't match the param spec value type and
       "no conversion" flag isn't set, then convert parameter spec */
    if(control->value_type && control->value_type != value_type
            && !(control->flags & SWAMI_CONTROL_SPEC_NO_CONV))
    {
        newspec = swami_param_convert_new(pspec, control->value_type);

        /* take control of the old pspec and then destroy it */
        g_param_spec_ref(pspec);
        g_param_spec_sink(pspec);
        g_param_spec_unref(pspec);

        if(!newspec)
        {
            return (FALSE);
        }

        pspec = newspec;
    }

    SWAMI_LOCK_WRITE(control);
    retval = (*klass->set_spec)(control, pspec);
    SWAMI_UNLOCK_WRITE(control);

    if(retval)
    {
        g_signal_emit(control, control_signals[SPEC_CHANGED_SIGNAL], 0, pspec);
    }

    return (retval);
}

/**
 * swami_control_set_value_type:
 * @control: Control to set value type of
 * @type: Value type to assign to control
 *
 * Usually only called by #SwamiControl derived types from within
 * their instance init function. Sets the parameter spec value type
 * for @control and should be called once for specific type value
 * based controls before being used and cannot be changed
 * afterwards. If the type is not set then the control is a wild card
 * control (can handle any value). For GBoxed and GObject based values
 * use the specific GType for @type.
 */
void
swami_control_set_value_type(SwamiControl *control, GType type)
{
    g_return_if_fail(SWAMI_IS_CONTROL(control));
    g_return_if_fail(type != 0);

    SWAMI_LOCK_WRITE(control);

    /* make sure type is not already set */
    if(control->value_type)
    {
        if(swami_log_if_fail(control->value_type == type))
        {
            SWAMI_UNLOCK_WRITE(control);
            return;
        }
    }
    else
    {
        control->value_type = type;
    }

    SWAMI_UNLOCK_WRITE(control);
}

/**
 * swami_control_sync_spec:
 * @control: Control to set parameter spec control of
 * @source: The source control to get the parameter spec from
 * @trans: Optional value transform function
 * @data: User data to pass to transform function
 *
 * Synchronizes a @control object's parameter spec to the @source control.
 * The @source control must already have an assigned parameter spec and the
 * @control should have an assigned value type.  The optional @trans parameter
 * can be used to specify a transform function, which is executed on the
 * min, max and default components of the parameter spec when creating the new
 * param spec.
 *
 * Returns: %TRUE on success, %FALSE otherwise (param spec conversion error)
 */
gboolean
swami_control_sync_spec(SwamiControl *control, SwamiControl *source,
                        SwamiValueTransform trans, gpointer data)
{
    GParamSpec *pspec;
    int retval;

    g_return_val_if_fail(SWAMI_IS_CONTROL(control), FALSE);
    g_return_val_if_fail(SWAMI_IS_CONTROL(source), FALSE);

    if(trans)	/* !! floating reference */
    {
        pspec = swami_control_transform_spec(control, source, trans, data);
    }
    else
    {
        pspec = swami_control_get_spec(source);    /* ++ ref spec */
    }

    if(!pspec)
    {
        g_debug("pspec == NULL");
        return (FALSE);
    }

    /* set the param spec for the control */
    retval = swami_control_set_spec(control, pspec);

    /* only unref if swami_control_get_spec() was used, was floating otherwise */
    if(!trans)
    {
        g_param_spec_unref(pspec);    /* -- unref */
    }

    return (retval);
}

/**
 * swami_control_transform_spec:
 * @control: Destination control for the transformed parameter spec
 * @source: Source control with the parameter spec to transform
 * @trans: Transform function
 * @data: User defined data to pass to transform function
 *
 * Transforms a parameter spec from a @source control in preperation for
 * assignment to @control (but doesn't actually assign it).
 *
 * Returns: Transformed parameter spec or %NULL on error, the reference count
 *   is 1 and floating, which means it should be taken over by calling
 *   g_param_spec_ref() followed by g_param_spec_sink().
 */
GParamSpec *
swami_control_transform_spec(SwamiControl *control, SwamiControl *source,
                             SwamiValueTransform trans, gpointer data)
{
    GParamSpec *srcspec, *transform_spec;
    GType type;

    g_return_val_if_fail(SWAMI_IS_CONTROL(control), NULL);
    g_return_val_if_fail(SWAMI_IS_CONTROL(source), NULL);
    g_return_val_if_fail(trans != NULL, NULL);

    /* get the master control parameter spec */
    srcspec = swami_control_get_spec(source);  /* ++ ref spec */
    g_return_val_if_fail(srcspec != NULL, NULL);

    type = control->value_type ? control->value_type
           : G_PARAM_SPEC_VALUE_TYPE(srcspec);

    /* !! floating ref, transform the parameter spec */
    transform_spec = swami_param_transform_new(srcspec, type, trans, data);
    g_param_spec_unref(srcspec);		/* -- unref */

    g_return_val_if_fail(transform_spec != NULL, NULL);

    return (transform_spec);	/* !! caller takes over floating reference */
}

/**
 * swami_control_get_value:
 * @control: Control object of type #SWAMI_CONTROL_VALUE
 * @value: Caller supplied initialized GValue structure to store the value in.
 *
 * Get the current value of a value control object. The @control must
 * have the #SWAMI_CONTROL_SENDS flag set and be a value type control
 * with an assigned parameter spec. The @value parameter should be
 * initialized to the type of the control or a transformable type.
 */
void
swami_control_get_value(SwamiControl *control, GValue *value)
{
    SwamiControlClass *klass;
    GValue *get_value, tmp_value = { 0 };

    g_return_if_fail(SWAMI_IS_CONTROL(control));
    g_return_if_fail(G_IS_VALUE(value));

    klass = SWAMI_CONTROL_GET_CLASS(control);

    /* some sanity checking */
    g_return_if_fail(klass->get_value != NULL);
    g_return_if_fail(control->flags & SWAMI_CONTROL_SENDS);
    g_return_if_fail(control->value_type != 0);

    if(G_VALUE_HOLDS(value, control->value_type))
    {
        /* same type, just reset value and use it */
        g_value_reset(value);
        get_value = value;
    }
    else if(!g_value_type_transformable(control->value_type,
                                        G_VALUE_TYPE(value)))
    {
        g_critical("%s: Failed to transform value type '%s' to type '%s'",
                   G_STRLOC, g_type_name(control->value_type),
                   g_type_name(G_VALUE_TYPE(value)));
        return;
    }
    else	/* @value is not the same type, but is transformable */
    {
        g_value_init(&tmp_value, control->value_type);
        get_value = &tmp_value;
    }

    /* get_value method responsible for locking, if needed */
    (*klass->get_value)(control, get_value);

    if(get_value == &tmp_value)	/* transform the value if needed */
    {
        g_value_transform(get_value, value);
        g_value_unset(&tmp_value);
    }
}

/**
 * swami_control_get_value_native:
 * @control: Control object of type #SWAMI_CONTROL_VALUE
 * @value: Caller supplied uninitalized GValue structure to store the value in.
 *
 * Like swami_control_get_value() but forces @value to be the native type of
 * the control, rather than transforming the value to the initialized @value
 * type. Therefore @value should be un-initialized, contrary to
 * swami_control_get_value().
 */
void
swami_control_get_value_native(SwamiControl *control, GValue *value)
{
    SwamiControlClass *klass;

    g_return_if_fail(SWAMI_IS_CONTROL(control));
    g_return_if_fail(value != NULL);

    klass = SWAMI_CONTROL_GET_CLASS(control);

    /* some sanity checking */
    g_return_if_fail(klass->get_value != NULL);
    g_return_if_fail(control->flags & SWAMI_CONTROL_SENDS);
    g_return_if_fail(control->value_type != 0);

    g_value_init(value, control->value_type);

    /* get_value method responsible for locking, if needed */
    (*klass->get_value)(control, value);
}

/**
 * swami_control_set_value:
 * @control: Control object
 * @value: Value to set control to
 *
 * Sets/sends a value to a control object. If the control has a queue
 * assigned to it then the value is queued. The @value parameter
 * should be of a type transformable to the type used by @control (see
 * g_value_transformable). The @control must have the
 * #SWAMI_CONTROL_RECVS flag set.
 */
void
swami_control_set_value(SwamiControl *control, const GValue *value)
{
    SwamiControlEvent *event;
    SwamiControlQueue *queue;
    SwamiControlQueueTestFunc test_func;

    g_return_if_fail(SWAMI_IS_CONTROL(control));
    g_return_if_fail(G_IS_VALUE(value));

    event = swami_control_new_event(control, NULL, value);  /* ++ ref new */

    swami_control_event_active_ref(event);  /* ++ active ref the event */
    swami_control_event_ref(event);  /* ++ ref event for control active list */

    /* prepend the event to the active list */
    SWAMI_LOCK_WRITE(control);
    control->active = g_list_prepend(control->active, event);
    SWAMI_UNLOCK_WRITE(control);

    queue = swami_control_get_queue(control);  /* ++ ref queue */

    if(queue)	    /* if queue, then add event to the queue */
    {
        /* run queue test function (if any) */
        test_func = queue->test_func; /* should be atomic */

        if(!test_func || test_func(queue, control, event))
        {
            swami_control_queue_add_event(queue, control, event);
            g_object_unref(queue);	/* -- unref queue */
            swami_control_event_active_unref(event);  /* -- active unref event */
            swami_control_event_unref(event);  /* -- unref creator's ref */
            return;
        }

        /* queue has a test function and it returned FALSE (no queue) */

        g_object_unref(queue);	/* -- unref queue */
    }

    swami_control_set_event_real(control, event);

    swami_control_event_active_unref(event);  /* -- active unref the event */
    swami_control_event_unref(event);  /* -- unref creator's ref */
}

/**
 * swami_control_set_value_no_queue:
 * @control: Control object
 * @value: Value to set control to
 *
 * Sets/sends a value to a control object bypassing the control's
 * queue if it has one assigned to it. The @value parameter should be
 * of a type transformable to the type used by @control (see
 * g_value_transformable). The @control must have the
 * #SWAMI_CONTROL_RECVS flag set. Normally the swami_control_set_value()
 * function should be used instead to send a value to a control, use this
 * only if you know what you are doing since some controls are sensitive to
 * when they are processed (within a GUI thread for instance).
 */
void
swami_control_set_value_no_queue(SwamiControl *control, const GValue *value)
{
    SwamiControlEvent *event;

    g_return_if_fail(SWAMI_IS_CONTROL(control));
    g_return_if_fail(G_IS_VALUE(value));

    event = swami_control_new_event(control, NULL, value);  /* ++ ref new */

    swami_control_event_active_ref(event);  /* ++ active ref the event */
    swami_control_event_ref(event);  /* ++ ref event for control active list */

    /* prepend the event to the active list */
    SWAMI_LOCK_WRITE(control);
    control->active = g_list_prepend(control->active, event);
    SWAMI_UNLOCK_WRITE(control);

    swami_control_set_event_real(control, event);

    swami_control_event_active_unref(event);  /* -- active unref the event */
    swami_control_event_unref(event);  /* -- unref creator's ref */
}

/**
 * swami_control_set_event:
 * @control: Control object
 * @event: Event to set control to
 *
 * Sets the value of a control object (value controls) or sends an
 * event (stream controls). This is like swami_control_set_value() but
 * uses an existing event, rather than creating a new one. The
 * @control must have the #SWAMI_CONTROL_RECVS flag set. If the
 * control has a queue then the event will be added to the queue.
 */
void
swami_control_set_event(SwamiControl *control, SwamiControlEvent *event)
{
    SwamiControlEvent *origin;
    SwamiControlQueue *queue;
    SwamiControlQueueTestFunc test_func;

    g_return_if_fail(SWAMI_IS_CONTROL(control));
    g_return_if_fail(event != NULL);

    origin = event->origin ? event->origin : event;
    swami_control_event_active_ref(event);  /* ++ active ref the event */

    SWAMI_LOCK_WRITE(control);

    /* check for event looping (only if control can send) */
    if(!swami_control_loop_check(control, event))
    {
        SWAMI_UNLOCK_WRITE(control);
        swami_control_event_active_unref(event);  /* -- decrement active ref */
        return;
    }

    /* prepend the event origin to the active list */
    control->active = g_list_prepend(control->active, origin);

    SWAMI_UNLOCK_WRITE(control);

    swami_control_event_ref(origin);  /* ++ ref event for control active list */

    queue = swami_control_get_queue(control);  /* ++ ref queue */

    if(queue)	    /* if queue, then add event to the queue */
    {
        /* run queue test function (if any) */
        test_func = queue->test_func; /* should be atomic */

        if(!test_func || test_func(queue, control, event))
        {
            swami_control_queue_add_event(queue, control, event);
            g_object_unref(queue);	/* -- unref queue */
            swami_control_event_active_unref(event);  /* -- active unref */
            return;
        }

        /* queue has a test function and it returned FALSE (no queue) */
        g_object_unref(queue);	/* -- unref queue */
    }

    swami_control_set_event_real(control, event);

    swami_control_event_active_unref(event);  /* -- decrement active ref */
}

/**
 * swami_control_set_event_no_queue:
 * @control: Control object
 * @event: Event to set control to
 *
 * Like swami_control_set_event() but bypasses any queue that a
 * control might have. The @control must have the #SWAMI_CONTROL_RECVS
 * flag set.
 */
void
swami_control_set_event_no_queue(SwamiControl *control,
                                 SwamiControlEvent *event)
{
    SwamiControlEvent *origin;

    g_return_if_fail(SWAMI_IS_CONTROL(control));
    g_return_if_fail(event != NULL);
    g_return_if_fail(event->active > 0);

    origin = event->origin ? event->origin : event;
    swami_control_event_active_ref(event);  /* ++ active ref the event */

    SWAMI_LOCK_WRITE(control);

    /* check for event looping (only if control can send) */
    if(!swami_control_loop_check(control, event))
    {
        SWAMI_UNLOCK_WRITE(control);
        swami_control_event_active_unref(event);  /* -- decrement active ref */
        return;
    }

    /* prepend the event origin to the active list */
    control->active = g_list_prepend(control->active, origin);

    SWAMI_UNLOCK_WRITE(control);

    swami_control_event_ref(origin);  /* ++ ref event for control active list */

    swami_control_set_event_real(control, event);

    swami_control_event_active_unref(event);  /* -- decrement active ref */
}

/**
 * swami_control_set_event_no_queue_loop:
 * @control: Control object
 * @event: Event to set control to
 *
 * Like swami_control_set_event_no_queue() but doesn't do an event
 * loop check. This function is usually only used by
 * #SwamiControlQueue objects.  The @control must have the
 * #SWAMI_CONTROL_RECVS flag set.
 */
void
swami_control_set_event_no_queue_loop(SwamiControl *control,
                                      SwamiControlEvent *event)
{
    g_return_if_fail(SWAMI_IS_CONTROL(control));
    g_return_if_fail(event != NULL);
    g_return_if_fail(event->active > 0);

    swami_control_event_active_ref(event);  /* ++ active ref the event */
    swami_control_set_event_real(control, event);
    swami_control_event_active_unref(event);  /* -- decrement active ref */
}

/* the real set event routine */
static inline void
swami_control_set_event_real(SwamiControl *control, SwamiControlEvent *event)
{
    SwamiControlClass *klass;
    GValue temp = { 0 }, *value;

    klass = SWAMI_CONTROL_GET_CLASS(control);

    /* some sanity checking */
    g_return_if_fail(klass->set_value != NULL);
    g_return_if_fail(control->flags & SWAMI_CONTROL_RECVS);

    /* parameter conversion or specific type required? */
    if(klass->get_spec && control->value_type != 0
            && (!(control->flags & SWAMI_CONTROL_NO_CONV)
                || (control->flags & SWAMI_CONTROL_NATIVE)))
    {
        value = &event->value;

        if(control->flags & SWAMI_CONTROL_NATIVE)  /* native type only? */
        {
            if(!G_VALUE_HOLDS(value, control->value_type))
            {
                g_critical("%s: Control requires value type '%s' got '%s'",
                           G_STRLOC, g_type_name(control->value_type),
                           g_type_name(G_VALUE_TYPE(value)));
                return;
            }
        } /* transform the value if needed */
        else if(!G_VALUE_HOLDS(value, control->value_type))
        {
            g_value_init(&temp, control->value_type);

            if(!g_value_transform(value, &temp))
            {
                g_value_unset(&temp);

                /* FIXME - probably should just set a flag or inc a counter */

                g_critical("%s: Failed to transform value type '%s' to"
                           " type '%s'", G_STRLOC,
                           g_type_name(G_VALUE_TYPE(value)),
                           g_type_name(control->value_type));
                return;
            }

            value = &temp;
        }
    }
    else
    {
        value = &event->value;    /* No conversion necessary */
    }

#if DEBUG

    if(swami_control_debug)
    {
        char *valstr = g_strdup_value_contents(value);
        char *s1 = pretty_control(control);
        g_message("Set: %s EV:%p ORIGIN:%p VAL:<%s>='%s'",
                  s1, event, event->origin, G_VALUE_TYPE_NAME(value), valstr);
        g_free(s1);
        g_free(valstr);

        SWAMI_CONTROL_TEST_BREAK(control, NULL);
    }

#endif

    /* set_value method is responsible for locking, if needed */
    (*klass->set_value)(control, event, value);

    if(value == &temp)
    {
        g_value_unset(&temp);
    }
}

/* Check if an event is already visited a control. Also purges old active
   events. Control must be locked by caller.
   Returns: TRUE if not looped, FALSE otherwise */
static inline gboolean
swami_control_loop_check(SwamiControl *control, SwamiControlEvent *event)
{
    SwamiControlEvent *ev, *origin;
    GList *p, *temp;

    /* if control only sends or only receives, don't do loop check.
     * FIXME - Is that right? */
    if((control->flags & SWAMI_CONTROL_SENDRECV) != SWAMI_CONTROL_SENDRECV)
    {
        return (TRUE);
    }

    origin = event->origin ? event->origin : event;

    /* look through active events to stop loops and to cleanup old entries */
    p = control->active;

    while(p)
    {
        ev = (SwamiControlEvent *)(p->data);

        if(ev == origin)		/* event loop catch */
        {

#if DEBUG

            if(swami_control_debug)
            {
                char *s1 = pretty_control(control);
                g_message("Loop killer: %s EV:%p ORIGIN:%p", s1, event, origin);
                g_free(s1);
            }

            SWAMI_CONTROL_TEST_BREAK(control, NULL);
#endif

            return (FALSE);	/* return immediately, looped */
        }

        if(!ev->active)		/* event still active? */
        {
            /* no, remove from list */
            temp = p;
            p = g_list_next(p);
            control->active = g_list_delete_link(control->active, temp);
            swami_control_event_unref(ev);  /* -- unref inactive event */
        }
        else
        {
            p = g_list_next(p);
        }
    }

    return (TRUE);
}

/**
 * swami_control_transmit_value:
 * @control: Control object
 * @value: Value to transmit or %NULL (readable value controls only, sends
 *    value changed event).
 *
 * Sends a value to all output connections of @control. Usually only
 * used by #SwamiControl object implementations. The @value is used if
 * not %NULL or the control's value is used (readable value controls
 * only). This is a convenience function that creates a
 * #SwamiControlEvent based on the new transmit value, use
 * swami_control_transmit_event() to send an existing event.
 */
void
swami_control_transmit_value(SwamiControl *control, const GValue *value)
{
    CtrlUpdateBag update_ctrls[MAX_DEST_CONNECTIONS + 1];
    CtrlUpdateBag *bag;
    SwamiControlEvent *event, *transevent;
    SwamiControlConn *conn;
    GSList *p;
    int uc = 0;

    g_return_if_fail(SWAMI_IS_CONTROL(control));

    event = swami_control_new_event(control, NULL, value);  /* ++ ref new */

    swami_control_event_active_ref(event);  /* ++ active ref event */
    swami_control_event_ref(event);  /* ++ ref event for control active list */

    SWAMI_LOCK_WRITE(control);

    /* prepend the event origin to the active list */
    control->active = g_list_prepend(control->active, event);

    /* copy destination controls to an array under lock, which is then used
       outside of lock to avoid recursive dead locks */

    /* loop over destination connections */
    for(p = control->outputs; p; p = p->next)
    {
        conn = (SwamiControlConn *)(p->data);

        /* ++ ref dest control and add to array, copy transform func also */
        update_ctrls[uc].control = g_object_ref(conn->control);
        update_ctrls[uc].trans = conn->trans;
        update_ctrls[uc++].data = conn->data;
    }

    update_ctrls[uc].control = NULL;

    SWAMI_UNLOCK_WRITE(control);

#if DEBUG

    if(swami_control_debug)
    {
        char *s1 = pretty_control(control);
        g_message("Transmit to %d dests: %s EV:%p", uc, s1, event);
        g_free(s1);
    }

    SWAMI_CONTROL_TEST_BREAK(control, NULL);
#endif

    bag = update_ctrls;

    while(bag->control)   /* send event to destination controls */
    {
        if(bag->trans)
        {
            /* transform event using transform function */
            transevent = swami_control_event_transform		/* ++ ref */
                         (event, bag->control->value_type, bag->trans, bag->data);

            swami_control_set_event(bag->control, transevent);
            swami_control_event_unref(transevent);	/* -- unref */
        }
        else
        {
            swami_control_set_event(bag->control, event);
        }

        g_object_unref(bag->control);	/* -- unref control from update array */
        bag++;
    }

    swami_control_event_active_unref(event);  /* -- active unref */
    swami_control_event_unref(event);  /* -- unref creator's reference */
}

/**
 * swami_control_transmit_event:
 * @control: Control object
 * @event: Event to send to output connections of @control
 *
 * This function sends an event to all destination connected
 * controls. Usually only used by #SwamiControl object
 * implementations.
 */
void
swami_control_transmit_event(SwamiControl *control, SwamiControlEvent *event)
{
    CtrlUpdateBag update_ctrls[MAX_DEST_CONNECTIONS + 1];
    SwamiControlEvent *transevent;
    CtrlUpdateBag *bag;

    g_return_if_fail(SWAMI_IS_CONTROL(control));
    g_return_if_fail(event != NULL);

    swami_control_event_active_ref(event);  /* ++ inc active ref count */

    {
        /* recursive function, save on stack space */
        SwamiControlConn *conn;
        SwamiControlEvent *origin;
        GSList *p;
        int uc = 0;

        origin = event->origin ? event->origin : event;

        swami_control_event_ref(origin);  /* ++ ref event for active list */

        SWAMI_LOCK_WRITE(control);

        /* check for event looping (only if control can send) */
        if(!swami_control_loop_check(control, event))
        {
            SWAMI_UNLOCK_WRITE(control);
            swami_control_event_active_unref(event);  /* -- decrement active ref */
            return;
        }

        control->active = g_list_prepend(control->active, origin);

        /* copy destination controls to an array under lock, which is then used
           outside of lock to avoid recursive dead locks */

        /* loop over destination connections */
        for(p = control->outputs; p; p = p->next)
        {
            conn = (SwamiControlConn *)(p->data);

            /* ++ ref dest control and add to array, copy transform func also */
            update_ctrls[uc].control = g_object_ref(conn->control);
            update_ctrls[uc].trans = conn->trans;
            update_ctrls[uc++].data = conn->data;
        }

        update_ctrls[uc].control = NULL;

        SWAMI_UNLOCK_WRITE(control);

    } /* stack space saver */

#if DEBUG

    if(swami_control_debug)
    {
        char *s1 = pretty_control(control);
        g_message("Transmit: %s EV:%p ORIGIN:%p", s1, event, event->origin);
        g_free(s1);
    }

    SWAMI_CONTROL_TEST_BREAK(control, NULL);
#endif

    bag = update_ctrls;

    while(bag->control)   /* send event to destination controls */
    {
        if(bag->trans)
        {
            /* transform event using transform function */
            transevent = swami_control_event_transform		/* ++ ref */
                         (event, bag->control->value_type, bag->trans, bag->data);

            swami_control_set_event(bag->control, transevent);
            swami_control_event_unref(transevent);	/* -- unref */
        }
        else
        {
            swami_control_set_event(bag->control, event);
        }

        g_object_unref(bag->control);	/* -- unref control from update array */
        bag++;
    }

    swami_control_event_active_unref(event);  /* -- decrement active ref */
}

/**
 * swami_control_transmit_event_loop:
 * @control: Control object
 * @event: Event to send to output connections of @control
 *
 * Like swami_control_transmit_event() but doesn't do an event loop check.
 * This is useful for control implementations that receive an event and
 * want to re-transmit it (swami_control_transmit_event() would stop it,
 * since the event is already in the active list for the control).
 */
void
swami_control_transmit_event_loop(SwamiControl *control,
                                  SwamiControlEvent *event)
{
    CtrlUpdateBag update_ctrls[MAX_DEST_CONNECTIONS + 1];
    SwamiControlEvent *transevent;
    CtrlUpdateBag *bag;

    g_return_if_fail(SWAMI_IS_CONTROL(control));
    g_return_if_fail(event != NULL);

    swami_control_event_active_ref(event);  /* ++ inc active ref count */

    {
        /* recursive function, save on stack space */
        SwamiControlConn *conn;
        SwamiControlEvent *origin;
        GSList *p;
        int uc = 0;

        origin = event->origin ? event->origin : event;

        SWAMI_LOCK_WRITE(control);

        /* check for event in active list (only if control can send) */
        if(swami_control_loop_check(control, event))
        {
            /* not already in list, prepend the event origin to the active list */
            control->active = g_list_prepend(control->active, origin);
            swami_control_event_ref(origin);  /* ++ ref event for active list */
        }

        /* copy destination controls to an array under lock, which is then used
           outside of lock to avoid recursive dead locks */

        /* loop over destination connections */
        for(p = control->outputs; p; p = p->next)
        {
            conn = (SwamiControlConn *)(p->data);

            /* ++ ref dest control and add to array, copy transform func also */
            update_ctrls[uc].control = g_object_ref(conn->control);
            update_ctrls[uc].trans = conn->trans;
            update_ctrls[uc++].data = conn->data;
        }

        update_ctrls[uc].control = NULL;

        SWAMI_UNLOCK_WRITE(control);

    } /* stack space saver */

#if DEBUG

    if(swami_control_debug)
    {
        char *s1 = pretty_control(control);
        g_message("Transmit: %s EV:%p ORIGIN:%p", s1, event, event->origin);
        g_free(s1);
    }

    SWAMI_CONTROL_TEST_BREAK(control, NULL);
#endif

    bag = update_ctrls;

    while(bag->control)   /* send event to destination controls */
    {
        if(bag->trans)
        {
            /* transform event using transform function */
            transevent = swami_control_event_transform		/* ++ ref */
                         (event, bag->control->value_type, bag->trans, bag->data);

            swami_control_set_event(bag->control, transevent);
            swami_control_event_unref(transevent);	/* -- unref */
        }
        else
        {
            swami_control_set_event(bag->control, event);
        }

        g_object_unref(bag->control);	/* -- unref control from update array */
        bag++;
    }

    swami_control_event_active_unref(event);  /* -- decrement active ref */
}

/**
 * swami_control_do_event_expiration:
 *
 * Processes all controls in search of inactive expired events.  This should
 * be called periodically to expire events in controls that don't receive any
 * events for a long period after previous activity.  Note that currently this
 * is handled automatically if swami_init() is called.
 */
void
swami_control_do_event_expiration(void)
{
    GList *cp, *ep, *temp;
    SwamiControlEvent *ev;
    SwamiControl *control;

    G_LOCK(control_list);

    for(cp = control_list; cp; cp = cp->next)  /* loop over controls */
    {
        control = (SwamiControl *)(cp->data);

        SWAMI_LOCK_WRITE(control);

        ep = control->active;

        while(ep)		/* loop over active event list */
        {
            ev = (SwamiControlEvent *)(ep->data);

            if(!ev->active)		/* event still active? */
            {
                /* no, remove from list */
                temp = ep;
                ep = g_list_next(ep);
                control->active = g_list_delete_link(control->active, temp);
                swami_control_event_unref(ev);  /* -- unref inactive event */
            }
            else
            {
                ep = g_list_next(ep);
            }
        }

        SWAMI_UNLOCK_WRITE(control);
    }

    G_UNLOCK(control_list);
}

/**
 * swami_control_new_event:
 * @control: Control object
 * @origin: Origin event or %NULL if new event is origin
 * @value: Value to use as event value or %NULL to use @control as the
 *   value (a value change event)
 *
 * Create an event for @control. If @value is non %NULL then
 * it is used as the value of the event otherwise the @control is used as
 * the value of the event (a value changed event).
 *
 * Returns: New event with a refcount of 1 which the caller owns. Remember
 *   that a #SwamiControlEvent is not a GObject and has its own reference
 *   counting routines swami_control_event_ref() and
 *   swami_control_event_unref().
 */
SwamiControlEvent *
swami_control_new_event(SwamiControl *control, SwamiControlEvent *origin,
                        const GValue *value)
{
    SwamiControlEvent *event;

    g_return_val_if_fail(SWAMI_IS_CONTROL(control), NULL);

    event = swami_control_event_new(TRUE);  /* ++ ref new event */

    if(origin)
    {
        swami_control_event_set_origin(event, origin);
    }

    if(value)			/* if value supplied, use it */
    {
        g_value_init(&event->value, G_VALUE_TYPE(value));
        g_value_copy(value, &event->value);
    }
    else			    /* create a value change event */
    {
        g_value_init(&event->value, G_TYPE_OBJECT);
        g_value_set_object(&event->value, control);
    }

    return (event);		/* !! caller owns reference */
}
