/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 * Copyright (C) 2007-2009  Michael Bell <michael.bell@opensync.org>
 *
 * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 */

#include "sml_thread_internals.h"

#include "sml_support.h"
#include "sml_error_internals.h"



#define G_ERRORCHECK_MUTEXES

SmlThread*
smlThreadNew (GMainContext *context,
              GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, context, error);
	CHECK_ERROR_REF
	/* Never ever run on the default context because a library
	 * thread should never influence the behavior of an user
	 * application and should never be influence by an user
	 * application.
	 *
	 * This is highly critical especially if the user application
	 * user the default context to loop on smlDsSessionDispatch
	 * and smlManagerDispatch.
	 */
	smlAssert(context);
	
	SmlThread *thread = smlTryMalloc0(sizeof(SmlThread), error);
	if (!thread)
		goto error;

	if (!g_thread_supported ()) g_thread_init (NULL);
	if (!g_thread_supported ())
	{
		g_set_error(error, SML_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION,
			"Threads are not supported.");
		goto error;
	}
	
	thread->started = FALSE;
	thread->started_mutex = g_mutex_new();
	thread->started_cond = g_cond_new();
	thread->context = context;
	g_main_context_ref(thread->context);
	thread->loop = g_main_loop_new(thread->context, FALSE);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, thread);
	return thread;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

void
smlThreadFree (SmlThread *thread)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, thread);
	smlAssert(thread);
	
	if (thread->started_mutex)
		g_mutex_free(thread->started_mutex);

	if (thread->started_cond)
		g_cond_free(thread->started_cond);
	
	if (thread->loop)
		g_main_loop_unref(thread->loop);
	
	if (thread->context)
		g_main_context_unref(thread->context);
		
	smlSafeFree((gpointer *)&thread);
	smlTrace(TRACE_EXIT, "%s", __func__);
}

static gpointer
smlThreadStartCallback (gpointer data)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, data);
	SmlThread *thread = data;
	smlTrace(TRACE_INTERNAL, "%s: +++++++++ This is the worker thread of thread %p for context %p +++++++++", __func__, thread, thread->context);
	
	smlTrace(TRACE_INTERNAL, "%s: locking", __func__);
	g_mutex_lock(thread->started_mutex);
	thread->started = TRUE;
	smlTrace(TRACE_INTERNAL, "%s: sending condition", __func__);
	g_cond_signal(thread->started_cond);
	smlTrace(TRACE_INTERNAL, "%s: unlocking", __func__);
	g_mutex_unlock(thread->started_mutex);

	/* check main context ownership */
	if (!g_main_context_acquire(thread->context)) {
		smlAssertMsg(FALSE, "This thread is not the owner of the GMainContext.");
	} else {
		smlTrace(TRACE_INTERNAL, "%s: Thread is owner of the GMainContext.", __func__);
	}

	g_main_loop_run(thread->loop);

	/* release context ownership / decrement reference counter */
	g_main_context_release(thread->context);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return FALSE;
}

static gboolean
smlThreadStopCallback (gpointer data)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, data);
	SmlThread *thread = data;
	smlTrace(TRACE_INTERNAL, "%s: +++++++++ Quitting worker thread +++++++++", __func__);
	
	g_main_loop_quit(thread->loop);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return FALSE;
}

void
smlThreadStart (SmlThread *thread)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, thread);
	smlAssert(thread);
	
	//Start the thread
	smlTrace(TRACE_INTERNAL, "%s: locking", __func__);
	g_mutex_lock(thread->started_mutex);
	smlTrace(TRACE_INTERNAL, "%s: creating thread", __func__);
	thread->thread = g_thread_create (smlThreadStartCallback, thread, TRUE, NULL);
	smlAssert(thread->thread);
	smlTrace(TRACE_INTERNAL, "%s: waiting for start", __func__);
	while(!thread->started) {
		smlTrace(TRACE_INTERNAL, "%s: checking condition", __func__);
		g_cond_wait(thread->started_cond, thread->started_mutex);
	}
	smlTrace(TRACE_INTERNAL, "%s: condition received", __func__);
	g_mutex_unlock(thread->started_mutex);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void
smlThreadStop (SmlThread *thread)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, thread);
	smlAssert(thread);
	
	GSource *source = g_idle_source_new();
	g_source_set_callback(source, smlThreadStopCallback, thread, NULL);
	g_source_attach(source, thread->context);

	g_thread_join(thread->thread);
	thread->thread = NULL;
	
	g_source_unref(source);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

/**
 * This context is used to cache the environment for direct function
 * calls within a special thread.
 * Please see smlThreadCallFunction for more details.
 */
typedef struct SmlThreadFunctionContext {
	GMutex *mutex;
	GCond *cond;
	SmlThreadCallFunctionType func;
	gpointer data;
	gboolean result;
	GError **error;
} SmlThreadFunctionContext;

/**
 * This callback is used to call a function within a special thread.
 * Please see smlThreadCallFunction for more details.
 */
gboolean
smlThreadCallFunctionCallback (gpointer data)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, data);
	smlAssert(data);

	SmlThreadFunctionContext *ctx = data;
	ctx->result = ctx->func(ctx->data, ctx->error);
	g_mutex_lock(ctx->mutex);
	g_cond_signal(ctx->cond);
	g_mutex_unlock(ctx->mutex);

	/* This function should only run once. */
	smlTrace(TRACE_EXIT, "%s", __func__);
	return FALSE;
}

/**
 * This function can be used to execute a function in a special thread.
 * Some libraries are designed for single-threaded applications.
 * They require that a set of commands is only executed in one thread.
 * These applications use g_main_loop too. So the functions must be
 * executed in this loop. This will be done synchronously by this
 * function.
 *
 * Please note: This function is blocking.
 * Please note: The glib source priority is IDLE.
 */
gboolean
smlThreadCallFunction(SmlThread *thread,
                      SmlThreadCallFunctionType func,
                      gpointer data,
                      GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p => %p, %p, %p, %p)", __func__, thread, thread?thread->context:NULL, func, data, error);
	CHECK_ERROR_REF
	smlAssert(func);

	/* initialize function context */
	smlTrace(TRACE_INTERNAL, "%s: preparing context", __func__);
	SmlThreadFunctionContext *ctx;
	ctx = smlTryMalloc0(sizeof(SmlThreadFunctionContext), error);
	if (!ctx)
		goto error;
	ctx->mutex = g_mutex_new();
	if (!ctx->mutex) {
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Cannot create new mutex.");
		goto error;
	}
	ctx->cond = g_cond_new();
	if (!ctx->cond) {
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Cannot create new condition.");
		goto error;
	}
	ctx->func = func;
	ctx->data = data;
	ctx->error = error;

	/* prepare glib source */
	smlTrace(TRACE_INTERNAL, "%s: preparing source", __func__);
	GSource *source = g_idle_source_new();
	g_source_set_callback(source, smlThreadCallFunctionCallback, ctx, NULL);

	/* call function "synchronously" */
	g_mutex_lock(ctx->mutex);
	smlTrace(TRACE_INTERNAL, "%s: attach source", __func__);
	g_source_attach(source, thread->context);
	smlTrace(TRACE_INTERNAL, "%s: wait for condition", __func__);
	g_cond_wait(ctx->cond, ctx->mutex);
	smlTrace(TRACE_INTERNAL, "%s: get condition", __func__);
	g_mutex_unlock(ctx->mutex);

	/* cleanup */
	smlTrace(TRACE_INTERNAL, "%s: cleanup", __func__);
	gboolean result = ctx->result;
	g_source_unref(source);
	g_mutex_free(ctx->mutex);
	g_cond_free(ctx->cond);
	smlSafeFree((gpointer *) &ctx);

	/* return result */
	smlTrace(TRACE_EXIT, "%s - %i", __func__, result);
	return result;
error:
	if (ctx->cond)
		g_cond_free(ctx->cond);
	if (ctx->mutex)
		g_mutex_free(ctx->mutex);
	if (ctx)
		smlSafeFree((gpointer *) &ctx);
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
	return FALSE;
}

/*@}*/
