/*
 * dLeyna
 *
 * Copyright (C) 2012-2017 Intel Corporation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Ludovic Ferrandis <ludovic.ferrandis@intel.com>
 *
 */

#define _GNU_SOURCE

#include <config.h>

#include <stdarg.h>
#include <string.h>

#include "log.h"

typedef struct dleyna_log_t_ dleyna_log_t;
struct dleyna_log_t_ {
	int old_mask;
	int mask;
	dleyna_log_type_t log_type;
	GLogLevelFlags flags;
	GLogFunc old_handler;
	gchar *log_domain;
};

static dleyna_log_t s_log_context;

static void prv_get_mf(int log_level, int *mask, GLogLevelFlags *flags)
{
	*mask = 0;
	*flags = 0;

	if (log_level & DLEYNA_LOG_LEVEL_ERROR) {
		*mask |= LOG_MASK(LOG_ERR);
		*flags |= G_LOG_LEVEL_ERROR;
	}

	if (log_level & DLEYNA_LOG_LEVEL_CRITICAL) {
		*mask |= LOG_MASK(LOG_CRIT);
		*flags |= G_LOG_LEVEL_CRITICAL;
	}

	if (log_level & DLEYNA_LOG_LEVEL_WARNING) {
		*mask |= LOG_MASK(LOG_WARNING);
		*flags |= G_LOG_LEVEL_WARNING;
	}

	if (log_level & DLEYNA_LOG_LEVEL_MESSAGE) {
		*mask |= LOG_MASK(LOG_NOTICE);
		*flags |= G_LOG_LEVEL_MESSAGE;
	}

	if (log_level & DLEYNA_LOG_LEVEL_INFO) {
		*mask |= LOG_MASK(LOG_INFO);
		*flags |= G_LOG_LEVEL_INFO;
	}

	if (log_level & DLEYNA_LOG_LEVEL_DEBUG) {
		*mask |= LOG_MASK(LOG_DEBUG);
		*flags |= G_LOG_LEVEL_DEBUG;
	}

	if (*flags)
		*flags |= G_LOG_FLAG_RECURSION | G_LOG_FLAG_FATAL;
}

static void prv_set_flags_from_param(void)
{
	int mask;
	GLogLevelFlags flags;

	prv_get_mf(DLEYNA_LOG_LEVEL, &mask, &flags);

	s_log_context.mask = mask;
	s_log_context.flags = flags;
	s_log_context.log_type = DLEYNA_LOG_TYPE;
}

static void prv_handler(const gchar *log_domain,
			GLogLevelFlags log_level,
			const gchar *message,
			gpointer data)
{
	dleyna_log_t *log_context = (dleyna_log_t *)(data);

	// FIXME: If doing debug logging, do not filter out other domain's log
	// messages.
	// The whole logging mechanism is a bit meh with its compile time levels
	// the legacy syslog support etc. This should be switched at some point
	if ((log_context->flags & DLEYNA_LOG_LEVEL_DEBUG) == 0) {
		if (g_strcmp0(log_domain, s_log_context.log_domain))
			return;
	}

	if (log_context->flags & log_level)
		g_log_default_handler(log_domain, log_level, message, data);
}

void dleyna_log_update_type_level(dleyna_log_type_t log_type, int log_level)
{
	int mask;
	int compile_mask;
	GLogLevelFlags flags;
	GLogLevelFlags compile_flags;

	s_log_context.log_type = log_type;

	prv_get_mf(log_level, &mask, &flags);
	prv_get_mf(DLEYNA_LOG_LEVEL, &compile_mask, &compile_flags);

	/* log level read from conf file is a subset of log level
	 * set at compile time.
	 * Only keep subset flags from compile flags */

	mask &= compile_mask;
	flags &= compile_flags;

	s_log_context.mask = mask;
	s_log_context.flags = flags;

	DLEYNA_LOG_INFO("Type[%d] Level[0x%02X] Mask[0x%02X] Flags[0x%02X]",
			log_type, log_level, mask, flags);

	(void) setlogmask(mask);
}

void dleyna_log_init(const char *program, const char *version)
{
	int option = LOG_NDELAY | LOG_PID;
	int old;

#ifdef DLEYNA_DEBUG_ENABLED
	option |= LOG_PERROR | LOG_CONS;
#endif

	memset(&s_log_context, 0, sizeof(s_log_context));
	s_log_context.log_domain = g_strdup(program);
	prv_set_flags_from_param();

	openlog(basename(program), option, LOG_DAEMON);

	old = setlogmask(LOG_MASK(LOG_INFO));
	syslog(LOG_INFO, "dLeyna core version %s", VERSION);
	syslog(LOG_INFO, "%s version %s", program, version);
	(void) setlogmask(s_log_context.mask);

	s_log_context.old_mask = old;
	s_log_context.old_handler = g_log_set_default_handler(
							prv_handler,
							&s_log_context);

#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_INFO
	if (s_log_context.log_type != DLEYNA_LOG_TYPE_SYSLOG) {
		DLEYNA_LOG_INFO("dLeyna core version %s", VERSION);
		DLEYNA_LOG_INFO("%s version %s", program, version);
	}
#endif
}

void dleyna_log_finalize(void)
{
	(void) setlogmask(LOG_MASK(LOG_INFO));
	syslog(LOG_INFO, "dLeyna: Exit");

#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_INFO
	if (s_log_context.log_type != DLEYNA_LOG_TYPE_SYSLOG)
		DLEYNA_LOG_INFO("dLeyna: Exit");
#endif

	(void) g_log_set_default_handler(s_log_context.old_handler, NULL);

	(void) setlogmask(s_log_context.old_mask);
	closelog();

	g_free(s_log_context.log_domain);

	memset(&s_log_context, 0, sizeof(s_log_context));
}

void dleyna_log_trace(int priority, GLogLevelFlags flags,
		      const char *format, ...)
{
	va_list args;

	va_start(args, format);

	switch (s_log_context.log_type) {
	case DLEYNA_LOG_TYPE_SYSLOG:
		if (s_log_context.mask)
			vsyslog(priority, format, args);
		break;
	case DLEYNA_LOG_TYPE_GLIB:
		if (s_log_context.flags)
			g_logv(s_log_context.log_domain, flags, format, args);
		break;
	case DLEYNA_LOG_TYPE_FILE:
		break;
	default:
		break;
	}

	va_end(args);
}
