/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 * Copyright (C) 2007-2008  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 "syncml.h"
#include "syncml_internals.h"

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

#include "config.h"

#include <glib.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <libxml/parser.h>

GPrivate* current_tabs = NULL;
GPrivate* thread_id = NULL;

#define G_ERRORCHECK_MUTEXES

static const char* smlCheckDebugDirectory(const char *env)
{
	const char *dirname = g_getenv(env);
	if (!dirname)
		return NULL;

	if (!g_file_test(dirname, G_FILE_TEST_EXISTS)) {
		if (g_mkdir_with_parents(dirname, 0700) != 0) {
			g_warning("The debug directory %s cannot be created.", dirname);
			return NULL;
		} 
	}

	if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) {
		g_warning("The debug directory %s is not a directory.", dirname);
		return NULL;
	}

	return dirname;
}

/**
 * @defgroup SmlDebugPrivate SyncML Debug
 * @ingroup PrivateLowLevelAPI
 * @brief Debug functions used by libsyncml
 * 
 */
/*@{*/


void smlLog(const char *logname, const char *data, unsigned int size)
{
#if defined ENABLE_TRACE

	const char *trace;
	if (!(trace = smlCheckDebugDirectory("SYNCML_LOG")))
		return;
	
	int i = 0;
	char *logfile = NULL;
	for (;;i = i + 1) {
		char *logfile_tmp = g_strdup_printf("%s/%s", trace, logname);
		logfile = g_strdup_printf(logfile_tmp, i);
		smlSafeCFree(&logfile_tmp);
		if (!g_file_test(logfile, G_FILE_TEST_EXISTS))
			break;
		smlSafeCFree(&logfile);
	}
	
	GError *error = NULL;
	GIOChannel *chan = g_io_channel_new_file(logfile, "w", &error);
	if (!chan) {
		printf("unable to open %s for writing: %s\n", logfile, error->message);
		return;
	}
	
	gsize writen;
	g_io_channel_set_encoding(chan, NULL, NULL);
	if (g_io_channel_write_chars(chan, data, size, &writen, NULL) != G_IO_STATUS_NORMAL) {
		printf("unable to write trace to %s\n", logfile);
	} else
		g_io_channel_flush(chan, NULL);

	g_io_channel_shutdown(chan, TRUE, NULL);
	g_io_channel_unref(chan);
#endif
}

/*! @brief Used for tracing the application
 * 
 * use this function to trace calls. The call graph will be saved into
 * the file that is given in the SYNCML_TRACE environment variable
 * 
 * @param type The type of the trace
 * @param message The message to save
 * 
 */
void smlTrace(SmlTraceType type, const char *message, ...)
{
#if defined ENABLE_TRACE
	
	const char *trace;
	if (!(trace = smlCheckDebugDirectory("SYNCML_TRACE")))
		return;
	
	if (!g_thread_supported ()) g_thread_init (NULL);
	int tabs = 0;
	if (!current_tabs)
		current_tabs = g_private_new (NULL);
	else
		tabs = GPOINTER_TO_INT(g_private_get(current_tabs));

	unsigned long int id;
	pid_t pid;
	const char *endline;
#ifdef _WIN32
#ifdef PTW32_VERSION 
	id = (unsigned long int)pthread_self().p;
#else
	if (!thread_id)
		thread_id = g_private_new (NULL);
	id = GPOINTER_TO_INT(thread_id);
#endif
	pid = _getpid();
	endline = "\r\n";
#else
	id = (unsigned long int)pthread_self();
	pid = getpid();
	endline = "\n";
#endif
	char *logfile = g_strdup_printf("%s/Thread%lu-%d.log", trace, id, pid);

	// create message
	va_list arglist;
	char *buffer = NULL;
	va_start(arglist, message);
	buffer = g_strdup_vprintf(message, arglist);
	va_end(arglist);

	// filter message if necessary
	const char *filter = g_getenv("SYNCML_TRACE_FILTER");
	if (filter)
	{
		gchar** filters = g_strsplit(filter, ",", 0);
		gint i;
		SmlBool matched = FALSE;
		for (i=0; filters[i] != NULL && !matched ; i++)
		{
			if (strstr(buffer, filters[i]))
				matched = TRUE;
		}
		g_strfreev(filters);
		if (!matched)
		{
			smlSafeCFree(&buffer);
			return;
		}
	}
	
	GString *tabstr = g_string_new("");
	long i = 0;
	for (i = 0; i < tabs; i++) {
		tabstr = g_string_append(tabstr, "\t");
	}

	GTimeVal curtime;
	g_get_current_time(&curtime);
	char *logmessage = NULL;
	switch (type) {
		case TRACE_ENTRY:
			logmessage = g_strdup_printf("[%li.%06li]\t%s>>>>>>>  %s%s", curtime.tv_sec, curtime.tv_usec, tabstr->str, buffer, endline);
			tabs++;
			break;
		case TRACE_INTERNAL:
			logmessage = g_strdup_printf("[%li.%06li]\t%s%s%s", curtime.tv_sec, curtime.tv_usec, tabstr->str, buffer, endline);
			break;
		case TRACE_ERROR:
			logmessage = g_strdup_printf("[%li.%06li]\t%sERROR: %s%s", curtime.tv_sec, curtime.tv_usec, tabstr->str, buffer, endline);
			break;
		case TRACE_EXIT:
			logmessage = g_strdup_printf("[%li.%06li]%s<<<<<<<  %s%s", curtime.tv_sec, curtime.tv_usec, tabstr->str, buffer, endline);
			tabs--;
			if (tabs < 0)
				tabs = 0;
			break;
		case TRACE_EXIT_ERROR:
			logmessage = g_strdup_printf("[%li.%06li]%s<--- ERROR --- %s%s", curtime.tv_sec, curtime.tv_usec, tabstr->str, buffer, endline);
			tabs--;
			if (tabs < 0)
				tabs = 0;
			break;
	}
	smlSafeCFree(&buffer);
	
	g_private_set(current_tabs, GINT_TO_POINTER(tabs));
	
	g_string_free(tabstr, TRUE);
	
	GError *error = NULL;
	GIOChannel *chan = g_io_channel_new_file(logfile, "a", &error);
	if (!chan) {
		printf("unable to open %s for writing: %s%s", logfile, error->message, endline);
		smlSafeCFree(&logfile);
		smlSafeCFree(&logmessage);
		return;
	}
	
	gsize writen;
	g_io_channel_set_encoding(chan, NULL, NULL);
	if (g_io_channel_write_chars(chan, logmessage, strlen(logmessage), &writen, NULL) != G_IO_STATUS_NORMAL) {
		printf("unable to write trace to %s%s", logfile, endline);
	} else
		g_io_channel_flush(chan, NULL);

	g_io_channel_shutdown(chan, TRUE, NULL);
	g_io_channel_unref(chan);
	smlSafeCFree(&logmessage);
	smlSafeCFree(&logfile);
#endif
}

/*! @brief Used for printing binary data
 * 
 * Unprintable character will be printed in hex, printable are just printed
 * 
 * @param data The data to print
 * @param len The length to print
 * 
 */
char *smlPrintBinary(const char *data, int len)
{
  int t;
  GString *str = g_string_new("");
  for (t = 0; t < len; t++) {
    if (data[t] >= ' ' && data[t] <= 'z')
      g_string_append_c(str, data[t]);
    else
      g_string_append_printf(str, " %02x ", (unsigned char) data[t]);
  }
  return g_string_free(str, FALSE);
}

/*! @brief Used for printing binary data in just hex
 * 
 * @param data The data to print
 * @param len The length to print
 * 
 */
char *smlPrintHex(const char *data, int len)
{
  int t;
  GString *str = g_string_new("");
  for (t = 0; t < len; t++) {
    g_string_append_printf(str, " %02x", (unsigned char) data[t]);
    if (data[t] >= ' ' && data[t] <= 'z')
      g_string_append_printf(str, "(%c)", data[t]);
    g_string_append_c(str, ' ');
  }
  return g_string_free(str, FALSE);
}

/*@}*/ /* End of debug API */

/**
 * @defgroup SmlSupportPrivate SyncML Support
 * @ingroup PrivateLowLevelAPI
 * @brief Support functions used by libsyncml
 * 
 */
/*@{*/

/*! @brief Creates a random string
 * 
 * Creates a random string of given length or less
 * 
 * @param maxlength The maximum length of the string
 * @returns The random string
 * 
 */
char *smlRandStr(int maxlength, SmlBool exact)
{
	const char *randchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIKLMNOPQRSTUVWXYZ1234567890";
	
	int length;
	char *retchar;
	int i = 0;

	if (exact)
		length = maxlength;
	else
		length = g_random_int_range(1, maxlength + 1);
	
	retchar = malloc(length * sizeof(char) + 1);
	retchar[0] = 0;

	for (i = 0; i < length; i++) {
		retchar[i] = randchars[g_random_int_range(0, strlen(randchars))];
		retchar[i + 1] = 0;
	}

	return retchar;
}

/*! @brief Safely mallocs
 * 
 * Mallocs or returns an error if OOM
 * 
 * @param n_bytes The size in bytes to malloc
 * @param error Pointer to a error struct
 * @returns The newly allocated memory or NULL in case of error
 * 
 */
void *smlTryMalloc0(long n_bytes, SmlError **error)
{
	CHECK_ERROR_REF

	void *result = g_try_malloc(n_bytes);
	if (!result) {
		/* set error */
		char *msg = NULL;
		if (n_bytes > 0)
			msg = g_strdup_printf("No memory left (needed %ld).", n_bytes);
		else
			msg = g_strdup_printf("Malloc of zero bytes requested.");

		/* publish error */
		if (error == NULL)
			g_error("%s", msg);
		else
			smlErrorSet(error, SML_ERROR_INTERNAL_NO_MEMORY, "%s", msg);
		return NULL;
	}
	memset(result, 0, n_bytes);
	return result;
}

/*! @brief Gets the version of libsyncml as string
 * 
 * @returns The version as const string
 * 
 */
const char *smlGetVersion(void)
{
	/**
	* @version $Id$
	*/ 
	return "$Id$";
}

SmlThread *smlThreadNew(GMainContext *context, SmlError **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 ())
	{
		smlErrorSet(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__, smlErrorPrint(error));
	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;
	SmlBool result;
	SmlError **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.
 */
SmlBool smlThreadCallFunction(
		SmlThread *thread,
		SmlThreadCallFunctionType func,
		gpointer data,
		SmlError **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) {
		smlErrorSet(error, SML_ERROR_GENERIC, "Cannot create new mutex.");
		goto error;
	}
	ctx->cond = g_cond_new();
	if (!ctx->cond) {
		smlErrorSet(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__);
	SmlBool 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__, smlErrorPrint(error));
	return FALSE;
}

void smlSafeFree(gpointer *address)
{
	smlAssert(address);
	smlAssert(*address);
	g_free(*address);
	*address = NULL;
}

void smlSafeCFree(char **address)
{
	smlSafeFree((gpointer *)address);
}

const char *smlGetLibraryVersion()
{
	return PACKAGE_VERSION;
}

const char *smlGetLibrarySoName()
{
	return PACKAGE_SONAME;
}

/*@}*/
