/*
** SellaNMS
** $Id: trace.cxx,v 1.4 2005/05/22 00:36:44 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

static char rcsid[] = "$Id: trace.cxx,v 1.4 2005/05/22 00:36:44 sella Exp $";

#include "trace.h"
#include "util.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/time.h>

bool initialized = false;

unsigned long severity = LOG_DEBUG;

unsigned long traceoptions;
pthread_mutex_t traceoptions_mutex;

hash_map<string, time_clock_t *>	timers;
pthread_mutex_t timers_mutex;

char* file_line = NULL;
const size_t file_line_len = 64;
pthread_mutex_t file_line_mutex;

bool is_initialized() {
	return initialized;
}

void __set_file_line(const char* file, unsigned long line) {
	assert(initialized == true);
	assert(file != NULL);

	pthread_mutex_lock(&file_line_mutex);

	SAFE_FREE(file_line);

	if ((file_line = (char*) malloc(file_line_len)) == NULL) {
		syslog(severity, "failed to allocate memory for file_line in __set_file_line() [trace.c]");
		pthread_mutex_unlock(&file_line_mutex);

		return;
	}

	snprintf(file_line, file_line_len, "%s +%lu", file, line);
}

void __trace_init() {
	assert(initialized == false);

	timers.clear();
	memset(&traceoptions, 0, sizeof(unsigned long));
	file_line = NULL;

	pthread_mutex_init(&traceoptions_mutex, NULL);
	pthread_mutex_init(&timers_mutex, NULL);
	pthread_mutex_init(&file_line_mutex, NULL);

	initialized = true;
}

void __trace_free() {
	hash_map<string, time_clock_t*>::iterator it;

	assert(initialized == true);

	if (file_line != NULL) {
		pthread_mutex_trylock(&file_line_mutex);
		free(file_line);
		file_line = NULL;
		pthread_mutex_unlock(&file_line_mutex);
	}

	while ((it = timers.begin()) != timers.end()) {
		__time_stop(it->first.c_str());
	}
	timers.clear();

	memset(&traceoptions, 0, sizeof(unsigned long));

	pthread_mutex_destroy(&traceoptions_mutex);
	pthread_mutex_destroy(&timers_mutex);
	pthread_mutex_destroy(&file_line_mutex);

	initialized = false;
}

void __trace(unsigned long flags, const char *format, ...) {
	va_list args;

	assert(initialized == true);

	if (traceoptions & flags) {
		va_start(args, format);
		vsyslog(severity, format, args);
		va_end(args);
	}
}

void __trace_debug(unsigned long flags, const char *format, ...) {
	char* ptr;
	size_t len;
	va_list args;

	assert(initialized == true);

	/* Just incase it wasn't locked. */
	pthread_mutex_trylock(&file_line_mutex);

	if (traceoptions & flags) {
		len = strlen(format) + file_line_len + 4;

		if (file_line == NULL) {
			file_line = strdup("-");
		}

		va_start(args, format);
		
		if ((ptr = (char*) malloc(len)) != NULL) {
			snprintf(ptr, len, "%s [%s]", format, file_line);
			vsyslog(severity, ptr, args);
			free(ptr);
		}

		va_end(args);
	}
	SAFE_FREE(file_line);

	pthread_mutex_unlock(&file_line_mutex);
}

void __lmsg(const char *format, ...) {
	va_list	args;

	assert(initialized == true);

	va_start(args, format);
	vsyslog(severity, format, args);
	va_end(args);
}

void __lmsg_debug(const char *format, ...) {
	char* ptr;
	size_t len;
	va_list args;

	assert(initialized == true);

	/* Just incase it wasn't locked. */
	pthread_mutex_trylock(&file_line_mutex);

	len = strlen(format) + file_line_len + 4;

	if (file_line == NULL) {
		file_line = strdup("-:0");
	}

	va_start(args, format);
	
	if ((ptr = (char*) malloc(len)) != NULL && file_line != NULL) {
		snprintf(ptr, len, "%s [%s]", format, file_line);
		vsyslog(severity, ptr, args);
		free(ptr);
	}

	va_end(args);

	SAFE_FREE(file_line);

	pthread_mutex_unlock(&file_line_mutex);
}

void __set_traceoption(unsigned long flags) {
	assert(initialized == true);

	pthread_mutex_lock(&traceoptions_mutex);
	traceoptions |= flags;
	pthread_mutex_unlock(&traceoptions_mutex);
}

void __unset_traceoption(unsigned long flags) {
	assert(initialized == true);

	pthread_mutex_lock(&traceoptions_mutex);
	traceoptions &= ~flags;
	pthread_mutex_unlock(&traceoptions_mutex);
}

bool __isset_traceoption(unsigned long flags) {
	assert(initialized == true);

	return (traceoptions & flags) ? true : false;
}

void __time_start(const char *name) {
	time_clock_t	*timer = new time_clock_t;

	assert(initialized == true);

	if (timers.find(name) == timers.end()) {
		memset(timer, 0, sizeof(time_clock_t));
		gettimeofday(&(timer->start), NULL);

		pthread_mutex_lock(&timers_mutex);
		timers[name] = timer;
		pthread_mutex_unlock(&timers_mutex);
	} else {
		TRACE(TRACE_DEBUG, "Timer start failed: attempted to start timer on an already existing timer name, '%s'.", name);
	}
}

void __time_stop(const char *name) {
	time_clock_t	*timer = NULL;

	assert(initialized == true);

	if (timers.find(name) != timers.end()) {
		timer = timers.find(name)->second;
		gettimeofday(&(timer->stop), NULL);

		pthread_mutex_lock(&timers_mutex);
		timers.erase(name);
		pthread_mutex_unlock(&timers_mutex);
	} else {
		TRACE(TRACE_DEBUG, "Timer stop failed: attempted to stop timer on a non-existant timer name, '%s'.", name);
		return;
	}
	if (timer != NULL) {
		subtract_timeval(timer->stop, timer->start);
		TRACE(TRACE_TIMER, "Timer '%s' completed in %d.%06d seconds.", name, timer->stop.tv_sec, timer->stop.tv_usec);

		delete timer;
	}
}

void __time(const char *name) {
	assert(initialized == true);

	if (timers.find(name) == timers.end()) {
		TIME_START(name);
	} else {
		TIME_STOP(name);
		TIME_START(name);
	}
}

void __set_severity(unsigned long s) {
	severity = s;
}

unsigned long __get_severity() {
	return severity;
}

unsigned long __get_traceoptions() {
	return traceoptions;
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim600: fdm=marker
** vim: noet ts=3 sw=3
*/
