static const char version[] = "$Id$";

/*
* category.c
*
* Copyright 2001-2003, Meiosys (www.meiosys.com). All rights reserved.
*
* See the COPYING file for the terms of usage and distribution.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <string.h>
#ifdef  HAVE_ALLOCA_H
#include <alloca.h>
#endif
#include <sd/sprintf.h>
#include <sd/malloc.h>
#include <sd/factory.h>
#include <log4c/appender.h>
#include <log4c/priority.h>
#include <log4c/logging_event.h>
#include <log4c/category.h>
#include <log4c/rc.h>
#include <sd/error.h>
#include <sd/sd_xplatform.h>

struct __log4c_category {
	char*					cat_name;
	int						cat_priority;
	int						cat_additive;
	const log4c_category_t*	cat_parent;
	log4c_appender_t*		cat_appender;
};

sd_factory_t* log4c_category_factory = NULL;

static const char LOG4C_CATEGORY_DEFAULT[] = "root";


/**
* @bug the root category name should be "" not "root". *
*/

/*******************************************************************************/
extern log4c_category_t* log4c_category_get(const char* a_name)
{
	static const sd_factory_ops_t log4c_category_factory_ops = {
		/*(void*) log4c_category_new,
		(void*) log4c_category_delete,
		(void*) log4c_category_print,*/
		(void *( *)(const char *))log4c_category_new,
		(void ( *)(void *))log4c_category_delete,
		(void ( *)(void *,FILE *))log4c_category_print
	};

	if (!log4c_category_factory) {
		log4c_category_factory = sd_factory_new("log4c_category_factory",
			&log4c_category_factory_ops);
	}

	return (log4c_category_t *)sd_factory_get(log4c_category_factory, a_name);
}

/*******************************************************************************/
extern int log4c_category_list(log4c_category_t** a_cats, int a_ncats)
{
	return sd_factory_list(log4c_category_factory, (void**) a_cats, a_ncats);
}

/*******************************************************************************/
static const char* dot_dirname(char* a_string);

extern log4c_category_t* log4c_category_new(const char* a_name)
{
	log4c_category_t* ptrThis;

	if (!a_name)
		return NULL;

	ptrThis		= (log4c_category_t *)sd_calloc(1, sizeof(log4c_category_t));
	ptrThis->cat_name	= sd_strdup(a_name);
	ptrThis->cat_priority	= LOG4C_PRIORITY_NOTSET;
	ptrThis->cat_additive	= 1;
	ptrThis->cat_appender	= NULL;
	ptrThis->cat_parent	= NULL;

	/* skip root category because it has a NULL parent */
	if (strcmp(LOG4C_CATEGORY_DEFAULT, a_name)) {
		char* tmp = sd_strdup(ptrThis->cat_name);

		ptrThis->cat_parent = log4c_category_get(dot_dirname(tmp));
		free(tmp);
	}
	return ptrThis;
}

/*******************************************************************************/
extern void log4c_category_delete(log4c_category_t* ptrThis)
{
	if (!ptrThis) 
		return;

	free(ptrThis->cat_name);
	free(ptrThis);
}

/*******************************************************************************/
extern const char* log4c_category_get_name(const log4c_category_t* ptrThis)
{
	return (ptrThis ? ptrThis->cat_name : "(nil)");
}

/*******************************************************************************/
extern int log4c_category_get_priority(const log4c_category_t* ptrThis)
{
	return (ptrThis ? ptrThis->cat_priority : LOG4C_PRIORITY_UNKNOWN);
}

/*******************************************************************************/
extern int log4c_category_get_chainedpriority(const log4c_category_t* ptrThis)
{
	const log4c_category_t* cat = ptrThis;

	if (!ptrThis) 
		return LOG4C_PRIORITY_UNKNOWN;

	while (cat->cat_priority == LOG4C_PRIORITY_NOTSET && cat->cat_parent)
		cat = cat->cat_parent;

	return cat->cat_priority;
}

/*******************************************************************************/
extern const log4c_appender_t* log4c_category_get_appender(const log4c_category_t* ptrThis)
{
	return (ptrThis ? ptrThis->cat_appender : NULL);
}

/*******************************************************************************/
extern int log4c_category_get_additivity(const log4c_category_t* ptrThis)
{
	return (ptrThis ? ptrThis->cat_additive : -1);
}

/*******************************************************************************/
extern int log4c_category_set_priority(log4c_category_t* ptrThis, int a_priority)
{
	int previous;

	if (!ptrThis) 
		return LOG4C_PRIORITY_UNKNOWN;

	previous = ptrThis->cat_priority;
	ptrThis->cat_priority = a_priority;
	return previous;
}

/**
* @todo need multiple appenders per category
*/

/*******************************************************************************/
extern const log4c_appender_t* log4c_category_set_appender(log4c_category_t* ptrThis, log4c_appender_t* a_appender)
{
	log4c_appender_t* previous;

	if (!ptrThis) 
		return NULL;

	previous = ptrThis->cat_appender;
	ptrThis->cat_appender = a_appender;
	return previous;
}

/*******************************************************************************/
extern int log4c_category_set_additivity(log4c_category_t* ptrThis, int a_additivity)
{
	int previous;

	if (!ptrThis) 
		return -1;

	previous = ptrThis->cat_additive;
	ptrThis->cat_additive = a_additivity;
	return previous;
}

/*******************************************************************************/
extern void log4c_category_print(const log4c_category_t* ptrThis, FILE* a_stream)
{
	if (!ptrThis) 
		return;

	fprintf(a_stream, "{ name:'%s' priority:%s additive:%d appender:'%s' parent:'%s' }",
		ptrThis->cat_name,
		log4c_priority_to_string(ptrThis->cat_priority),
		ptrThis->cat_additive,
		log4c_appender_get_name(ptrThis->cat_appender),
		log4c_category_get_name(ptrThis->cat_parent)
		);
}

/*******************************************************************************/
extern void __log4c_category_vlog(const log4c_category_t* ptrThis, 
								  const log4c_location_info_t* a_locinfo, 
								  int a_priority,
								  const char* a_format, 
								  va_list a_args)
{
	char* message;
	log4c_logging_event_t evt;
	const log4c_category_t* cat;
	int yes = 0;

	if (!ptrThis)
		return;

	/* check if an appender is defined in the category hierarchy */
	for (cat = ptrThis; cat; cat = cat->cat_parent) 
	{
		if (cat->cat_appender)
			yes++;
	}

	if (!yes)
		return;

	log4c_reread();

	/* when there is no limit on the buffer size, we use malloc() to
	* give the user the possiblity to reallocate if necessary. When
	* the buffer is limited in size, we use alloca() for more
	* efficiency.
	*/
	evt.evt_buffer.buf_maxsize = log4c_rc->config.bufsize;

	if (!evt.evt_buffer.buf_maxsize) 
	{
		evt.evt_buffer.buf_size = LOG4C_BUFFER_SIZE_DEFAULT;
		evt.evt_buffer.buf_data = (char*)sd_malloc(evt.evt_buffer.buf_size);
		message = sd_vsprintf(a_format, a_args);
	}
	else
	{
		size_t n;

		evt.evt_buffer.buf_size = evt.evt_buffer.buf_maxsize;
		evt.evt_buffer.buf_data = (char*)alloca(evt.evt_buffer.buf_size);
		message = (char*)alloca(evt.evt_buffer.buf_size);

		if ( (n = (size_t)_vsnprintf_s(message, evt.evt_buffer.buf_size, _TRUNCATE, a_format, a_args))
			>= evt.evt_buffer.buf_size)
			sd_error("truncating message of %d bytes (bufsize = %d)", n, 
			evt.evt_buffer.buf_size);
	}

	evt.evt_category	= ptrThis->cat_name;
	evt.evt_priority	= a_priority;
	evt.evt_msg	        = message;
	evt.evt_loc	        = a_locinfo;
	SD_GETTIMEOFDAY(&evt.evt_timestamp, NULL);

	for (cat = ptrThis; cat; cat = cat->cat_parent) 
	{
		if (cat->cat_appender)
			log4c_appender_append(cat->cat_appender, &evt);

		if (!cat->cat_additive) break;	
	}

	if (!evt.evt_buffer.buf_maxsize) 
	{
		free(message);
		free(evt.evt_buffer.buf_data);
	}
}

/*******************************************************************************/
extern void __log4c_category_vlog_no_file_num_no_layout(
	const log4c_category_t* ptrThis, 
	const log4c_location_info_t* a_locinfo, 
	int a_priority,
	const char* a_format, 
	va_list a_args)
{
	char* message;
	log4c_logging_event_t evt;
	const log4c_category_t* cat;
	int yes = 0;

	if (!ptrThis)
		return;

	/* check if an appender is defined in the category hierarchy */
	for (cat = ptrThis; cat; cat = cat->cat_parent) {
		if (cat->cat_appender)
			yes++;
	}

	if (!yes)
		return;

	log4c_reread();

	/* when there is no limit on the buffer size, we use malloc() to
	* give the user the possiblity to reallocate if necessary. When
	* the buffer is limited in size, we use alloca() for more
	* efficiency.
	*/
	evt.evt_buffer.buf_maxsize = log4c_rc->config.bufsize;

	if (!evt.evt_buffer.buf_maxsize) {
		evt.evt_buffer.buf_size = LOG4C_BUFFER_SIZE_DEFAULT;
		evt.evt_buffer.buf_data = (char*)sd_malloc(evt.evt_buffer.buf_size);
		message = sd_vsprintf(a_format, a_args);
	}
	else {
		size_t n;

		evt.evt_buffer.buf_size = evt.evt_buffer.buf_maxsize;
		evt.evt_buffer.buf_data = (char*)alloca(evt.evt_buffer.buf_size);
		message = (char*)alloca(evt.evt_buffer.buf_size);

		if ( (n = (size_t)_vsnprintf_s(message, evt.evt_buffer.buf_size, _TRUNCATE, a_format, a_args))
			>= evt.evt_buffer.buf_size)
			sd_error("truncating message of %d bytes (bufsize = %d)", n, 
			evt.evt_buffer.buf_size);
	}

	evt.evt_category	= ptrThis->cat_name;
	evt.evt_priority	= a_priority;
	evt.evt_msg	        = message;
	evt.evt_loc	        = a_locinfo;
	SD_GETTIMEOFDAY(&evt.evt_timestamp, NULL);

	for (cat = ptrThis; cat; cat = cat->cat_parent) {
		if (cat->cat_appender)
			log4c_appender_append_no_file_num_no_layout(cat->cat_appender, &evt);

		if (!cat->cat_additive) break;	
	}

	if (!evt.evt_buffer.buf_maxsize) {
		free(message);
		free(evt.evt_buffer.buf_data);
	}
}

/*******************************************************************************/
static const char* dot_dirname(char* a_string)
{
	char* p;

	if (!a_string)
		return NULL;

	if ( (p = strrchr(a_string, '.')) == NULL)
		return LOG4C_CATEGORY_DEFAULT;

	*p = '\0';
	return a_string;
}

