
/*
 * Dirty little hack to make threading GTK+ possible on Win32.
 * Simply sits in the main thread's main loop and waits for commands.
 *
 * Patrik Fimml, 2006
 */

#include "thread-guard.h"
#include "marshallers.h"
#include <string.h>

static GStaticMutex guard_mutex = G_STATIC_MUTEX_INIT;
static GAsyncQueue *guard_pending_invocations = NULL; /* polled by main thread */

typedef struct {
    GClosure *closure;
    GValue *return_value;
    guint n_param_values;
    const GValue *param_values;
    gpointer invocation_hint;
    GCond *done;
} GuardedInvocation;

typedef struct {
    GSource parent;
    GuardedInvocation *received;
} GuardSource;

/**
 * guarded_invoke:
 *
 * See g_closure_invoke()
 */
void guarded_invoke(GClosure *closure, GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint)
{
    GuardedInvocation invocation = (GuardedInvocation){closure, return_value, n_param_values, param_values, invocation_hint, g_cond_new()};

    g_static_mutex_lock(&guard_mutex);

    g_async_queue_push(guard_pending_invocations, &invocation);
    g_main_context_wakeup(NULL);
    g_cond_wait(invocation.done, g_static_mutex_get_mutex(&guard_mutex));

    g_static_mutex_unlock(&guard_mutex);
}

gpointer guarded_call(GuardedCallback function, gpointer argument)
{
    GClosure *closure;
    GValue arg[2];

    memset(arg, 0, sizeof(GValue) * 2);
    g_value_init(&(arg[0]), G_TYPE_POINTER);
    g_value_init(&(arg[1]), G_TYPE_POINTER);
    g_value_set_pointer(&(arg[1]), argument);
    closure = g_cclosure_new(G_CALLBACK(function), NULL, NULL);
    g_closure_set_marshal(closure, g_cclosure_user_marshal_POINTER__VOID);
    guarded_invoke(closure, &(arg[0]), 1, &(arg[1]), NULL);
    g_closure_sink(closure);

    return g_value_get_pointer(&(arg[0]));
}

static gboolean guard_src_check(GuardSource *source)
{
    if (source->received == NULL)
	source->received = g_async_queue_try_pop(guard_pending_invocations);
    return (source->received != NULL);
}

static gboolean guard_src_prepare(GuardSource *source, gint *timeout)
{
    *timeout = -1;
    return guard_src_check(source);
}

static gboolean guard_src_dispatch(GuardSource *source, GSourceFunc callback, gpointer user_data)
{
    for (; source->received != NULL; source->received = g_async_queue_try_pop(guard_pending_invocations))
    {
	g_closure_invoke(source->received->closure, source->received->return_value, source->received->n_param_values, source->received->param_values, source->received->invocation_hint);
	g_static_mutex_lock(&guard_mutex);
	g_cond_signal(source->received->done);
	g_static_mutex_unlock(&guard_mutex);
    }

    return TRUE;
}

static GSourceFuncs guard_source_funcs = {guard_src_prepare, guard_src_check, guard_src_dispatch, NULL, NULL, NULL};

/**
 * guard_init:
 *
 * Sets up the GSource in main context.
 */
void guard_init(void)
{
    g_assert(guard_pending_invocations == NULL);
    guard_pending_invocations = g_async_queue_new();

    GuardSource *source = (GuardSource *)g_source_new(&guard_source_funcs, sizeof(GuardSource));
    source->received = NULL;
    g_source_attach((GSource *)source, NULL);
}

