/*
   Copyright (C) 2010 Red Hat, Inc.
   Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
   Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.0 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
*/
#include "config.h"

#include "gio-coroutine.h"

typedef struct _GConditionWaitSource
{
    GSource parent; // this MUST be the first field
    GConditionWaitFunc func;
    gpointer data;
} GConditionWaitSource;

GCoroutine* g_coroutine_self(void)
{
    return (GCoroutine*)coroutine_self();
}

/* Main loop helper functions */
static gboolean g_io_wait_helper(GSocket *sock G_GNUC_UNUSED,
				 GIOCondition cond,
				 gpointer data)
{
    struct coroutine *to = data;
    coroutine_yieldto(to, &cond);
    return FALSE;
}

GIOCondition g_coroutine_socket_wait(GCoroutine *self,
                                     GSocket *sock,
                                     GIOCondition cond)
{
    GIOCondition *ret, val = 0;
    GSource *src;

    g_return_val_if_fail(self != NULL, 0);
    g_return_val_if_fail(self->wait_id == 0, 0);
    g_return_val_if_fail(sock != NULL, 0);

    src = g_socket_create_source(sock, cond | G_IO_HUP | G_IO_ERR | G_IO_NVAL, NULL);
    g_source_set_callback(src, (GSourceFunc)g_io_wait_helper, self, NULL);
    self->wait_id = g_source_attach(src, NULL);
    ret = coroutine_yield(NULL);
    g_source_unref(src);

    if (ret != NULL)
        val = *ret;
    else
        g_source_remove(self->wait_id);

    self->wait_id = 0;
    return val;
}

void g_coroutine_condition_cancel(GCoroutine *coroutine)
{
    g_return_if_fail(coroutine != NULL);

    if (coroutine->condition_id == 0)
        return;

    g_source_remove(coroutine->condition_id);
    coroutine->condition_id = 0;
}

void g_coroutine_wakeup(GCoroutine *coroutine)
{
    g_return_if_fail(coroutine != NULL);
    g_return_if_fail(coroutine != g_coroutine_self());

    if (coroutine->wait_id)
        coroutine_yieldto(&coroutine->coroutine, NULL);
}

/*
 * Call immediately before the main loop does an iteration. Returns
 * true if the condition we're checking is ready for dispatch
 */
static gboolean g_condition_wait_prepare(GSource *src,
					 int *timeout) {
    GConditionWaitSource *vsrc = (GConditionWaitSource *)src;
    *timeout = -1;
    return vsrc->func(vsrc->data);
}

/*
 * Call immediately after the main loop does an iteration. Returns
 * true if the condition we're checking is ready for dispatch
 */
static gboolean g_condition_wait_check(GSource *src)
{
    GConditionWaitSource *vsrc = (GConditionWaitSource *)src;
    return vsrc->func(vsrc->data);
}

static gboolean g_condition_wait_dispatch(GSource *src G_GNUC_UNUSED,
					  GSourceFunc cb,
					  gpointer data) {
    return cb(data);
}

static GSourceFuncs waitFuncs = {
    .prepare = g_condition_wait_prepare,
    .check = g_condition_wait_check,
    .dispatch = g_condition_wait_dispatch,
};

static gboolean g_condition_wait_helper(gpointer data)
{
    GCoroutine *self = (GCoroutine *)data;
    coroutine_yieldto(&self->coroutine, NULL);
    return FALSE;
}

/*
 * g_coroutine_condition_wait:
 * @coroutine: the coroutine to wait on
 * @func: the condition callback
 * @data: the user data passed to @func callback
 *
 * This function will wait on caller coroutine until @func returns %TRUE.
 *
 * @func is called when entering the main loop from the main context (coroutine).
 *
 * The condition can be cancelled by calling g_coroutine_wakeup()
 *
 * Returns: %TRUE if condition reached, %FALSE if not and cancelled
 */
gboolean g_coroutine_condition_wait(GCoroutine *self, GConditionWaitFunc func, gpointer data)
{
    GSource *src;
    GConditionWaitSource *vsrc;

    g_return_val_if_fail(self != NULL, FALSE);
    g_return_val_if_fail(self->condition_id == 0, FALSE);
    g_return_val_if_fail(func != NULL, FALSE);

    /* Short-circuit check in case we've got it ahead of time */
    if (func(data))
        return TRUE;

    /*
     * Don't have it, so yield to the main loop, checking the condition
     * on each iteration of the main loop
     */
    src = g_source_new(&waitFuncs, sizeof(GConditionWaitSource));
    vsrc = (GConditionWaitSource *)src;

    vsrc->func = func;
    vsrc->data = data;

    self->condition_id = g_source_attach(src, NULL);
    g_source_set_callback(src, g_condition_wait_helper, self, NULL);
    coroutine_yield(NULL);
    g_source_unref(src);

    /* it got woked up / cancelled? */
    if (self->condition_id == 0)
        return func(data);

    self->condition_id = 0;
    return TRUE;
}

struct signal_data
{
    gpointer instance;
    struct coroutine *caller;
    guint signal_id;
    GQuark detail;
    const gchar *propname;
    gboolean notified;
    va_list var_args;
};

static gboolean emit_main_context(gpointer opaque)
{
    struct signal_data *signal = opaque;

    g_signal_emit_valist(signal->instance, signal->signal_id,
                         signal->detail, signal->var_args);
    signal->notified = TRUE;

    coroutine_yieldto(signal->caller, NULL);

    return FALSE;
}

void
g_coroutine_signal_emit(gpointer instance, guint signal_id,
                        GQuark detail, ...)
{
    struct signal_data data = {
        .instance = instance,
        .signal_id = signal_id,
        .detail = detail,
        .caller = coroutine_self(),
    };

    va_start (data.var_args, detail);

    if (coroutine_self_is_main()) {
        g_signal_emit_valist(instance, signal_id, detail, data.var_args);
    } else {
        g_object_ref(instance);
        g_idle_add(emit_main_context, &data);
        coroutine_yield(NULL);
        g_warn_if_fail(data.notified);
        g_object_unref(instance);
    }

    va_end (data.var_args);
}


static gboolean notify_main_context(gpointer opaque)
{
    struct signal_data *signal = opaque;

    g_object_notify(signal->instance, signal->propname);
    signal->notified = TRUE;

    coroutine_yieldto(signal->caller, NULL);

    return FALSE;
}

/* coroutine -> main context */
void g_coroutine_object_notify(GObject *object,
                               const gchar *property_name)
{
    struct signal_data data;

    if (coroutine_self_is_main()) {
        g_object_notify(object, property_name);
    } else {

        data.instance = g_object_ref(object);
        data.caller = coroutine_self();
        data.propname = (gpointer)property_name;
        data.notified = FALSE;

        g_idle_add(notify_main_context, &data);

        /* This switches to the system coroutine context, lets
         * the idle function run to dispatch the signal, and
         * finally returns once complete. ie this is synchronous
         * from the POV of the coroutine despite there being
         * an idle function involved
         */
        coroutine_yield(NULL);
        g_warn_if_fail(data.notified);
        g_object_unref(object);
    }
}
