/*
 * 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>
 *
 */

#include <string.h>
#include <gio/gio.h>

#include "log.h"
#include "settings.h"
#include "context-filter.h"

struct dleyna_settings_t_ {
	GKeyFile *keyfile;
	gchar *file_name;
	gchar *file_path;

	/* Global section */
	gboolean never_quit;
	gchar *connector_name;
	guint port;
	guint push_host_port;

	/* Log section */
	dleyna_log_type_t log_type;
	int log_level;

	/* netf section */
	gboolean netf_enabled;
	GVariant *netf_entries;
};

#define DLEYNA_SETTINGS_GROUP_GENERAL		"general"
#define DLEYNA_SETTINGS_KEY_NEVER_QUIT		"never-quit"
#define DLEYNA_SETTINGS_KEY_CONNECTOR_NAME	"connector-name"
#define DLEYNA_SETTINGS_KEY_PORT		"port"
#define DLEYNA_SETTINGS_KEY_PUSH_HOST_PORT	"push-host-port"

#define DLEYNA_SETTINGS_GROUP_LOG		"log"
#define DLEYNA_SETTINGS_KEY_LOG_TYPE		"log-type"
#define DLEYNA_SETTINGS_KEY_LOG_LEVEL		"log-level"

#define DLEYNA_SETTINGS_GROUP_NETF		"netf"
#define DLEYNA_SETTINGS_KEY_NETF_ENABLED	"netf-enabled"
#define DLEYNA_SETTINGS_KEY_NETF_LIST		"netf-list"

#define DLEYNA_SETTINGS_DEFAULT_NEVER_QUIT	DLEYNA_NEVER_QUIT
#define DLEYNA_SETTINGS_DEFAULT_CONNECTOR_NAME	DLEYNA_CONNECTOR_NAME
#define DLEYNA_SETTINGS_DEFAULT_LOG_TYPE	DLEYNA_LOG_TYPE
#define DLEYNA_SETTINGS_DEFAULT_LOG_LEVEL	DLEYNA_LOG_LEVEL

#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_INFO
#define DLEYNA_SETTINGS_LOG_KEYS(sys, loc, settings) \
do { \
	gchar *str = NULL; \
	\
	DLEYNA_LOG_DEBUG_NL(); \
	DLEYNA_LOG_INFO("Load file [%s]", (settings)->file_path); \
	DLEYNA_LOG_DEBUG_NL(); \
	\
	DLEYNA_LOG_INFO("[General settings]"); \
	DLEYNA_LOG_INFO("Never Quit: %s", (settings)->never_quit ? "T" : "F");\
	DLEYNA_LOG_DEBUG_NL(); \
	DLEYNA_LOG_INFO("Connector Name: %s", (settings)->connector_name);\
	DLEYNA_LOG_DEBUG_NL(); \
	DLEYNA_LOG_INFO("Port: %u", (settings)->port);\
	DLEYNA_LOG_DEBUG_NL(); \
	DLEYNA_LOG_INFO("Push host port: %u", (settings)->push_host_port);\
	DLEYNA_LOG_DEBUG_NL(); \
	\
	DLEYNA_LOG_INFO("[Logging settings]"); \
	DLEYNA_LOG_INFO("Log Type : %d", (settings)->log_type); \
	DLEYNA_LOG_INFO("Log Level: 0x%02X", (settings)->log_level); \
	DLEYNA_LOG_DEBUG_NL(); \
	\
	if ((settings)->netf_entries != NULL) \
		str = g_variant_print((settings)->netf_entries, FALSE); \
	DLEYNA_LOG_INFO("[Network filtering settings]"); \
	DLEYNA_LOG_INFO("Enabled : %s", (settings)->netf_enabled ? "T" : "F");\
	DLEYNA_LOG_INFO("Entries: %s", str); \
	g_free(str); \
	DLEYNA_LOG_DEBUG_NL(); \
} while (0)
#else
#define DLEYNA_SETTINGS_LOG_KEYS(sys, loc, settings)
#endif

static void prv_get_keyfile_path(const gchar *file, gchar **sys_path,
				 gchar **loc_path)
{
	const gchar *loc_dir;

	if (sys_path != NULL) {
		*sys_path = NULL;

		if (*SYS_CONFIG_DIR)
			*sys_path = g_strdup_printf("%s/%s", SYS_CONFIG_DIR,
						    file);
	}

	if (loc_path != NULL) {
		loc_dir =  g_get_user_config_dir();
		*loc_path = NULL;

		if (loc_dir && *loc_dir)
			*loc_path = g_strdup_printf("%s/%s", loc_dir,
						    file);
	}
}

static void prv_check_local_keyfile(const gchar *sys_path,
				    const gchar *loc_path)
{
	GFile *sys_file = NULL;
	GFile *loc_file;
	GFile *loc_dir;

	loc_file = g_file_new_for_path(loc_path);
	loc_dir = g_file_get_parent(loc_file);

	if (g_file_query_exists(loc_file, NULL) || (sys_path == NULL))
		goto exit;

	if (!g_file_query_exists(loc_dir, NULL)) {
		if (!g_file_make_directory(loc_dir, NULL, NULL))
			goto exit;
	}

	sys_file = g_file_new_for_path(sys_path);

	(void) g_file_copy(sys_file, loc_file, G_FILE_COPY_TARGET_DEFAULT_PERMS,
			   NULL, NULL, NULL, NULL);

exit:
	if (loc_dir)
		g_object_unref(loc_dir);

	if (loc_file)
		g_object_unref(loc_file);

	if (sys_file)
		g_object_unref(sys_file);
}

static GKeyFile *prv_load_keyfile(const gchar *filepath)
{
	GKeyFile *keyfile = NULL;

	if (filepath == NULL)
		goto exit;

	keyfile = g_key_file_new();

	if (!g_key_file_load_from_file(keyfile, filepath,
				       G_KEY_FILE_KEEP_COMMENTS,
				       NULL)) {
		g_key_file_free(keyfile);
		keyfile = NULL;
	}

exit:
	return keyfile;
}

static GVariant *prv_to_netf_entries(char **list)
{
	GVariantBuilder vb;
	GVariant *result = NULL;

	if ((list != NULL) && (*list != NULL)) {
		g_variant_builder_init(&vb, G_VARIANT_TYPE("as"));

		while (*list != NULL) {
			g_variant_builder_add(&vb, "s", *list);
			list++;
		}

		result = g_variant_ref_sink(g_variant_builder_end(&vb));
	}

	return result;
}

static int prv_to_log_level(gint *int_list, gsize length)
{
	gsize i;
	int log_level_value;
	int level;
	int log_level_array[] = {
		DLEYNA_LOG_LEVEL_DISABLED, DLEYNA_LOG_LEVEL_ERROR,
		DLEYNA_LOG_LEVEL_CRITICAL, DLEYNA_LOG_LEVEL_WARNING,
		DLEYNA_LOG_LEVEL_MESSAGE, DLEYNA_LOG_LEVEL_INFO,
		DLEYNA_LOG_LEVEL_DEBUG, DLEYNA_LOG_LEVEL_DEFAULT,
		DLEYNA_LOG_LEVEL_ALL };

	log_level_value = 0;

	/* Take all valid values, even duplicated ones, and skip all others.
	 * Priority is single value (0,7,8) over multi value (1..6)
	 * Priority for single values is first found */
	for (i = 0; i < length; ++i) {
		level = int_list[i];

		if (level > 0 && level < 7) {
			log_level_value |= log_level_array[level];
		} else if ((level == 0) || (level == 7) || (level == 8)) {
			log_level_value = log_level_array[level];
			break;
		}
	}

	return log_level_value;
}

static dleyna_log_type_t prv_to_log_type(int type)
{
	dleyna_log_type_t log_type = DLEYNA_LOG_TYPE_SYSLOG;

	switch (type) {
	case 0:
		break;
	case 1:
		log_type = DLEYNA_LOG_TYPE_GLIB;
		break;
	default:
		break;
	}

	return log_type;
}

static void prv_read_keys(dleyna_settings_t *settings)
{
	GError *error = NULL;
	GKeyFile *keyfile = settings->keyfile;
	gboolean b_val;
	gint int_val;
	gchar *s_val;
	guint64 u_val;
	gint *int_star;
	gsize length;
	gchar **list;

	b_val = g_key_file_get_boolean(keyfile,
				       DLEYNA_SETTINGS_GROUP_GENERAL,
				       DLEYNA_SETTINGS_KEY_NEVER_QUIT,
				       &error);

	if (error == NULL) {
		settings->never_quit = b_val;
	} else {
		g_error_free(error);
		error = NULL;
	}

	s_val = g_key_file_get_string(keyfile,
				      DLEYNA_SETTINGS_GROUP_GENERAL,
				      DLEYNA_SETTINGS_KEY_CONNECTOR_NAME,
				       &error);

	if (error == NULL) {
		g_free(settings->connector_name);
		settings->connector_name = s_val;
	} else {
		g_error_free(error);
		error = NULL;
	}

	u_val = g_key_file_get_uint64(keyfile,
				      DLEYNA_SETTINGS_GROUP_GENERAL,
				      DLEYNA_SETTINGS_KEY_PORT,
				      &error);

	if (error == NULL) {
		settings->port = (guint)u_val;
	} else {
		g_error_free(error);
		error = NULL;
	}

	u_val = g_key_file_get_uint64(keyfile,
				      DLEYNA_SETTINGS_GROUP_GENERAL,
				      DLEYNA_SETTINGS_KEY_PUSH_HOST_PORT,
				      &error);

	if (error == NULL) {
		settings->push_host_port = (guint)u_val;
	} else {
		g_error_free(error);
		error = NULL;
	}

	int_val = g_key_file_get_integer(keyfile,
					 DLEYNA_SETTINGS_GROUP_LOG,
					 DLEYNA_SETTINGS_KEY_LOG_TYPE,
					 &error);

	if (error == NULL) {
		settings->log_type = prv_to_log_type(int_val);
	} else {
		g_error_free(error);
		error = NULL;
	}

	g_key_file_set_list_separator(keyfile, ',');

	int_star = g_key_file_get_integer_list(keyfile,
					       DLEYNA_SETTINGS_GROUP_LOG,
					       DLEYNA_SETTINGS_KEY_LOG_LEVEL,
					       &length,
					       &error);

	if (error == NULL) {
		settings->log_level = prv_to_log_level(int_star, length);
		g_free(int_star);
	} else {
		g_error_free(error);
		error = NULL;
	}

	b_val = g_key_file_get_boolean(keyfile,
				       DLEYNA_SETTINGS_GROUP_NETF,
				       DLEYNA_SETTINGS_KEY_NETF_ENABLED,
				       &error);

	if (error == NULL) {
		settings->netf_enabled = b_val;
	} else {
		g_error_free(error);
		error = NULL;
	}

	list = g_key_file_get_string_list(keyfile,
					  DLEYNA_SETTINGS_GROUP_NETF,
					  DLEYNA_SETTINGS_KEY_NETF_LIST,
					  NULL,
					  &error);

	if (error == NULL) {
		settings->netf_entries = prv_to_netf_entries(list);
		g_strfreev(list);
	} else {
		g_error_free(error);
		error = NULL;
	}
}

static void prv_init_default(dleyna_settings_t *settings)
{
	settings->never_quit = DLEYNA_SETTINGS_DEFAULT_NEVER_QUIT;
	settings->connector_name = g_strdup(
					DLEYNA_SETTINGS_DEFAULT_CONNECTOR_NAME);
	settings->port = 0;
	settings->push_host_port = 0;

	settings->log_type = DLEYNA_SETTINGS_DEFAULT_LOG_TYPE;
	settings->log_level = DLEYNA_SETTINGS_DEFAULT_LOG_LEVEL;

	settings->netf_enabled = FALSE;
	settings->netf_entries = NULL;
}

static void prv_keyfile_init(dleyna_settings_t *settings,
			     const gchar *sys_path,
			     const gchar *loc_path)
{
	const gchar *path = loc_path;

	settings->keyfile = prv_load_keyfile(loc_path);

	if (settings->keyfile == NULL) {
		path = sys_path;
		settings->keyfile = prv_load_keyfile(sys_path);
	}

	if (settings->keyfile != NULL) {
		prv_read_keys(settings);
		dleyna_log_update_type_level(settings->log_type,
					     settings->log_level);
		settings->file_path = g_strdup(path);
	}
}

static void prv_keyfile_finalize(dleyna_settings_t *settings)
{
	if (settings->netf_entries != NULL) {
		g_variant_unref(settings->netf_entries);
		settings->netf_entries = NULL;
	}

	if (settings->keyfile != NULL) {
		g_key_file_free(settings->keyfile);
		settings->keyfile = NULL;
	}

	g_free(settings->file_path);
	settings->file_path = NULL;
}

void dleyna_settings_new(const gchar *server, dleyna_settings_t **settings)
{
	gchar *sys_path = NULL;
	gchar *loc_path = NULL;
	const gchar *server_name;

	*settings = g_malloc0(sizeof(**settings));

	server_name = strrchr(server, '/');
	if (server_name)
		server_name++;
	else
		server_name = server;

	(*settings)->file_name = g_strdup_printf("%s%s", server_name, ".conf");

	prv_init_default(*settings);

	prv_get_keyfile_path((*settings)->file_name, &sys_path, &loc_path);

	if (loc_path)
		prv_check_local_keyfile(sys_path, loc_path);

	if (sys_path || loc_path)
		prv_keyfile_init(*settings, sys_path, loc_path);

	DLEYNA_SETTINGS_LOG_KEYS(sys_path, loc_path, *settings);

	g_free(sys_path);
	g_free(loc_path);
}

void dleyna_settings_delete(dleyna_settings_t *settings)
{
	g_free(settings->connector_name);
	g_free(settings->file_name);

	prv_keyfile_finalize(settings);

	g_free(settings);
}

const gchar *dleyna_settings_connector_name(dleyna_settings_t *settings)
{
	return settings->connector_name;
}

guint dleyna_settings_port(dleyna_settings_t *settings)
{
	return settings->port;
}

guint dleyna_settings_push_host_port(dleyna_settings_t *settings)
{
	return settings->push_host_port;
}

static void prv_save_settings_to_file(dleyna_settings_t *settings,
				      GError **error)
{
	gchar *data;
	gsize length;

	DLEYNA_LOG_DEBUG("Enter");

	data = g_key_file_to_data(settings->keyfile, &length , NULL);

	DLEYNA_LOG_DEBUG_NL();
	DLEYNA_LOG_DEBUG("\n%s", data);
	DLEYNA_LOG_DEBUG_NL();

	(void) g_file_set_contents(settings->file_path, data, length, error);

	g_free(data);
}

gboolean dleyna_settings_is_never_quit(dleyna_settings_t *settings)
{
	return settings->never_quit;
}

void dleyna_settings_set_never_quit(dleyna_settings_t *settings,
				    gboolean never_quit,
				    GError **error)
{
	DLEYNA_LOG_DEBUG("Enter");

	g_key_file_set_boolean(settings->keyfile,
			       DLEYNA_SETTINGS_GROUP_GENERAL,
			       DLEYNA_SETTINGS_KEY_NEVER_QUIT,
			       never_quit);

	prv_save_settings_to_file(settings, error);

	if (*error == NULL)
		settings->never_quit = never_quit;

	DLEYNA_LOG_DEBUG("Exit");
}

gboolean dleyna_settings_is_context_filter_enabled(dleyna_settings_t *settings)
{
	return settings->netf_enabled;
}

void dleyna_settings_set_context_filter_enabled(dleyna_settings_t *settings,
					    gboolean enabled,
					    GError **error)
{
	DLEYNA_LOG_DEBUG("Enter");

	g_key_file_set_boolean(settings->keyfile,
			       DLEYNA_SETTINGS_GROUP_NETF,
			       DLEYNA_SETTINGS_KEY_NETF_ENABLED,
			       enabled);

	prv_save_settings_to_file(settings, error);

	if (*error == NULL)
		settings->netf_enabled = enabled;

	DLEYNA_LOG_DEBUG("Exit");
}

GVariant *dleyna_settings_context_filter_entries(dleyna_settings_t *settings)
{
	return settings->netf_entries;
}

static const gchar **prv_filter_null_str(const gchar **orig_strv,
					 gsize orig_len,
					 gsize *new_len)
{
	const gchar **strv = NULL;
	gsize i;
	gsize n = 0;

	if (orig_strv && orig_len) {
		strv = g_new(const gchar *, orig_len + 1);

		for (i = 0; i < orig_len; i++) {
			if (*orig_strv[i])
				strv[n++] = orig_strv[i];
		}

		strv[n] = NULL;
	}

	*new_len = n;

	return strv;
}

void dleyna_settings_set_context_filter_entries(dleyna_settings_t *settings,
					    GVariant *entries,
					    GError **error)
{
	const gchar **list;
	gsize length;
	const gchar **list2;
	gsize length2;

	DLEYNA_LOG_DEBUG("Enter");

	list = g_variant_get_strv(entries, &length);

	list2 = prv_filter_null_str(list, length, &length2);

	g_key_file_set_string_list(settings->keyfile,
				   DLEYNA_SETTINGS_GROUP_NETF,
				   DLEYNA_SETTINGS_KEY_NETF_LIST,
				   list2, length2);

	prv_save_settings_to_file(settings, error);

	if (*error == NULL) {
		g_variant_unref(settings->netf_entries);
		settings->netf_entries = g_variant_ref(entries);
	}

	g_free(list);
	g_free(list2);

	DLEYNA_LOG_DEBUG("Exit");
}
