/* vim: ts=4 sw=4 sts=4 ff=unix fenc=utf-8 :
 * =====================================================================
 *  sc_logger.c
 *  Copyright (c)  2003 - 2011  sys0tem
 *  LICENSE :
 *	LGPL (GNU Lesser General Public License - Version 3,29 June 2007)
 *	http://www.gnu.org/copyleft/lesser.html
 *	or
 *	EPL (Eclipse Public License - v1.0)
 *	http://www.eclipse.org/legal/epl-v10.html
 * =====================================================================
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>

#include <sc_string.h>
#include <sc_stdbool.h>
#include <sc_map.h>
#include <sc_logger.h>

#define SC_LOGGER_HEADER_SIZE       (1024)
#define SC_LOGGER_CATEGORY_MAP_SIZE (32)

static SC_Map* SC_Logger_categoryMap = NULL;
static bool SC_Logger_isInit         = false;


/* --------------------------------------------------------------------
 *  定数定義
 * --------------------------------------------------------------------
 */


/* --------------------------------------------------------------------
 *  内部変数
 * --------------------------------------------------------------------
 */


/* --------------------------------------------------------------------
 *  内部関数 プロトタイプ宣言
 * --------------------------------------------------------------------
 */
static SC_Logger_Config* SC_Logger_getConfig(const char* category);
static void SC_Logger_getDate(char* buff);
static const char* SC_Logger_getLevelString(SC_LogLevel level);
static void SC_Logger_logprint(SC_Logger_Config* config,
	const char* file, int line, const char* format, va_list ap);

/**
 * ログの基本設定を行います。
 *
 * @param category カテゴリ
 * @param config   設定構造体
 * @return true/false (設定完了/設定失敗)
 */
bool SC_Logger_setLogConfig(const char* category, SC_Logger_Config* config)
{
	if (!SC_Logger_isInit)
	{	/* 初期化	*/
		SC_Logger_categoryMap = SC_Map_new(SC_LOGGER_CATEGORY_MAP_SIZE);
		if (SC_Logger_categoryMap == NULL)
		{
			return false;
		}
		SC_Logger_isInit = true;
	}

	SC_Logger_categoryMap->put(SC_Logger_categoryMap,
		category, config, sizeof(SC_Logger_Config));

	return true;
}


/**
 * ログをクリーンアップ(停止)します。
 */
void SC_Logger_cleanup(void)
{
	if (SC_Logger_isInit)
	{	/* 初期化している場合のみクリーンアップする	*/
		SC_Map_delete(SC_Logger_categoryMap);
		SC_Logger_isInit = false;
	}
}


/**
 * ログを書き出します。
 */
void SC_Logger_log_(const char* category, SC_LogLevel level,
		const char* file, int line, const char* format, ...)
{
	va_list ap;
	va_start(ap, format);
	SC_Logger_Config* config;

	if (SC_Logger_isInit)
	{	/* 初期化されている場合のみログ出力する	*/
		config = SC_Logger_getConfig(category);
		if (config->level >= level)
		{	/* ログ出力							*/
			SC_Logger_logprint(config, file, line, format, ap);
		}
	}
	va_end(ap);
}


/**
 * 指定されたカテゴリに対応する SC_Logger_Config を取得します。
 *
 * @param category カテゴリ
 * @return SC_Logger_Config
 */
static
SC_Logger_Config* SC_Logger_getConfig(const char* category)
{
	SC_String*        key;
	size_t            len;
	int               pos;
	SC_Logger_Config* config = NULL;

	len = strlen(category) + 1;
	key = SC_String_new(len);
	key->append(key, category);

	config = SC_Logger_categoryMap->get(SC_Logger_categoryMap, key->buffer, NULL);
	while ((config == NULL) && (key->buffer[0] != '\0'))
	{
		pos = SC_lastIndexOf(key->buffer, "/");
		if (pos < 0)
		{
			key->setLength(key, 0);
		}
		config = SC_Logger_categoryMap->get(SC_Logger_categoryMap, key->buffer, NULL);
	}
	return config;
}


/**
 * ログを出力します。
 *
 * @param config ログ設定
 * @param file   ファイル
 * @param line   行番号
 * @param format フォーマット
 * @param ap     引数
 */
static
void SC_Logger_logprint(SC_Logger_Config* config,
	const char* file, int line, const char* format, va_list ap)
{
	char logBuffer[SC_LOGGER_HEADER_SIZE];
	int  len, tmpLen;

	memset(logBuffer, '\0', sizeof(logBuffer));
	SC_Logger_getDate(logBuffer);
	strcat(logBuffer, " ");
	strcat(logBuffer, SC_Logger_getLevelString(config->level));
	len    = strlen(logBuffer);
	tmpLen = snprintf(&logBuffer[len], SC_LOGGER_HEADER_SIZE - len - 1, " %s:%d ", file, line);
	if (tmpLen > 0)
	{
		len += tmpLen;
	}
	vsnprintf(&logBuffer[len], SC_LOGGER_HEADER_SIZE - len - 1, format, ap);
	logBuffer[SC_LOGGER_HEADER_SIZE - 1] = '\0';

	switch (config->type)
	{
		case LOGGER_STDOUT:
			printf("%s\n", logBuffer);
			break;
		case LOGGER_STDERR:
			fprintf(stderr, "%s\n", logBuffer);
			break;
		case LOGGER_MEMORY:
			break;
		case LOGGER_FILE:
			break;
		case LOGGER_SYSLOG:
			break;
		default:
			break;
	}
}


/**
 * 現在時刻を取得します。
 * バッファサイズは、21 byte 以上必要です。
 *
 * @param buff 時刻を格納するバッファ
 */
static
void SC_Logger_getDate(char* buff)
{
	time_t nowTime;
	struct tm *t_st;

	/* 現在時刻取得							*/
	time(&nowTime);

	/* 現在時刻を構造体に変換				*/
	t_st = localtime(&nowTime);

	/* フォーマットしてバッファに格納する	*/
	sprintf(buff, "%04d/%02d/%02d %02d:%02d:%02d",
			t_st->tm_year + 1900,
			t_st->tm_mon + 1,
			t_st->tm_mday,
			t_st->tm_hour,
			t_st->tm_min,
			t_st->tm_sec);
}


/**
 * level の文字列表現を返します。
 *
 * @param level レベル
 * @return 文字列表現
 */
static
const char* SC_Logger_getLevelString(SC_LogLevel level)
{
	switch (level)
	{
		case LOGGER_FATAL:		return "[FATAL  ]";
		case LOGGER_ALERT:		return "[ALERT  ]";
		case LOGGER_CRIT:		return "[CRIT   ]";
		case LOGGER_ERR:		return "[ERR    ]";
		case LOGGER_WARNING:	return "[WARNING]";
		case LOGGER_NOTICE:		return "[NOTICE ]";
		case LOGGER_INFO:		return "[INFO   ]";
		case LOGGER_DEBUG:		return "[DEBUG  ]";
		default:                return "[DEFAULT]";
	}
}

