/*
 * 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_support.h"
#include "sml_error_internals.h"

#include "config.h"

#include <sys/types.h>
#include <unistd.h>

#ifndef _WIN32
#include <pthread.h>
#endif

GPrivate* thread_id = NULL;

GPrivate* current_tabs = NULL;

#define G_ERRORCHECK_MUTEXES

static const gchar*
smlCheckDebugDirectory (const gchar *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 gchar *logname,
        const gchar *data,
        gsize 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 gchar *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;
		gboolean 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
 * 
 */
gchar*
smlPrintBinary (const gchar *data,
                gsize 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
 * 
 */
gchar*
smlPrintHex (const gchar *data,
             gsize 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
 * 
 */
gchar*
smlRandStr (gsize maxlength,
            gboolean 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 (gsize n_bytes,
               GError **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 %ulld).", n_bytes);
		else
			msg = g_strdup_printf("Malloc of zero bytes requested.");

		/* publish error */
		if (error == NULL)
			g_error("%s", msg);
		else
			g_set_error(error, SML_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 gchar*
smlGetVersion (void)
{
	/**
	* @version $Id$
	*/ 
	return "$Id$";
}

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

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

const gchar*
smlGetLibraryVersion()
{
	return PACKAGE_VERSION;
}

const gchar*
smlGetLibrarySoName()
{
	return PACKAGE_SONAME;
}

/*@}*/
