/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2011-2014  BMW Car IT GmbH.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU 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
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <errno.h>

#include <gdbus.h>

#include "session-test.h"

static enum connman_session_state string2state(const char *state)
{
	if (g_strcmp0(state, "connected") == 0)
		return CONNMAN_SESSION_STATE_CONNECTED;
	if (g_strcmp0(state, "online") == 0)
		return CONNMAN_SESSION_STATE_ONLINE;

	return CONNMAN_SESSION_STATE_DISCONNECTED;
}

static enum connman_session_type string2type(const char *type)
{
	if (g_strcmp0(type, "any") == 0)
		return CONNMAN_SESSION_TYPE_ANY;
	if (g_strcmp0(type, "local") == 0)
		return CONNMAN_SESSION_TYPE_LOCAL;
	if (g_strcmp0(type, "internet") == 0)
		return CONNMAN_SESSION_TYPE_INTERNET;

	return CONNMAN_SESSION_TYPE_UNKNOWN;
}

void bearer_info_cleanup(gpointer data, gpointer user_data)
{
	struct test_bearer_info *info = data;

	g_free(info->name);
	g_free(info);
}

static GSList *session_parse_allowed_bearers(DBusMessageIter *iter)
{
	struct test_bearer_info *info;
	DBusMessageIter array;
	GSList *list = NULL;

	dbus_message_iter_recurse(iter, &array);

	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
		char *bearer = NULL;

		dbus_message_iter_get_basic(&array, &bearer);

		info = g_try_new0(struct test_bearer_info, 1);
		if (!info) {
			g_slist_foreach(list, bearer_info_cleanup, NULL);
			g_slist_free(list);

			return NULL;
		}

		info->name = g_strdup(bearer);

		list = g_slist_append(list, info);

		dbus_message_iter_next(&array);
	}

	return list;
}

static DBusMessage *notify_release(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	struct test_session *session = user_data;

	LOG("session %p", session);

	if (session->notify)
		session->notify(session);

	return NULL;
}

static DBusMessage *notify_update(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	struct test_session *session = user_data;
	struct test_session_info *info = session->info;
	DBusMessageIter iter, array;
	GSList *allowed_bearers;

	LOG("session %p notify %s", session, session->notify_path);

	dbus_message_iter_init(msg, &iter);
	dbus_message_iter_recurse(&iter, &array);

	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter entry, value;
		const char *key;

		dbus_message_iter_recurse(&array, &entry);
		dbus_message_iter_get_basic(&entry, &key);

		dbus_message_iter_next(&entry);
		dbus_message_iter_recurse(&entry, &value);

		switch (dbus_message_iter_get_arg_type(&value)) {
		case DBUS_TYPE_ARRAY:
			if (g_str_equal(key, "AllowedBearers")) {
				allowed_bearers = session_parse_allowed_bearers(&value);

				g_slist_foreach(info->allowed_bearers,
						bearer_info_cleanup, NULL);
				g_slist_free(info->allowed_bearers);

				info->allowed_bearers = allowed_bearers;

			} else if (g_str_equal(key, "IPv4")) {
				/* XXX */

			} else if (g_str_equal(key, "IPv6")) {
				/* XXX */

			} else {
				g_assert(FALSE);
				return __connman_error_invalid_arguments(msg);
			}
			break;
		case DBUS_TYPE_STRING:
			if (g_str_equal(key, "State")) {
				const char *val;
				dbus_message_iter_get_basic(&value, &val);

				info->state = string2state(val);
			} else if (g_str_equal(key, "Bearer")) {
				const char *val;
				dbus_message_iter_get_basic(&value, &val);

				g_free(info->bearer);
				info->bearer = g_strdup(val);

			} else if (g_str_equal(key, "Name")) {
				const char *val;
				dbus_message_iter_get_basic(&value, &val);

				g_free(info->name);
				info->name = g_strdup(val);

			} else if (g_str_equal(key, "Interface")) {
				const char *val;
				dbus_message_iter_get_basic(&value, &val);

				g_free(info->interface);
				info->interface = g_strdup(val);

			} else if (g_str_equal(key, "ConnectionType")) {
				const char *val;
				dbus_message_iter_get_basic(&value, &val);

				info->type = string2type(val);

			} else if (g_str_equal(key, "Allowedinterface")) {
				const char *val;
				dbus_message_iter_get_basic(&value, &val);

				g_free(info->allowed_interface);
				info->allowed_interface = g_strdup(val);

			} else if (g_str_equal(key, "ContextIdentifier")) {
				const char *val;
				dbus_message_iter_get_basic(&value, &val);

				g_free(info->context_identifier);
				info->context_identifier = g_strdup(val);

			} else {
				g_assert(FALSE);
				return __connman_error_invalid_arguments(msg);
			}
			break;
		case DBUS_TYPE_BOOLEAN:
			if (g_str_equal(key, "SourceIPRule")) {
				dbus_bool_t val;
				dbus_message_iter_get_basic(&value, &val);

				info->source_ip_rule = val;

			} else {
				g_assert(FALSE);
				return __connman_error_invalid_arguments(msg);
			}
			break;
		default:
			g_assert(FALSE);
			return __connman_error_invalid_arguments(msg);
		}
		dbus_message_iter_next(&array);
	}

	if (session->notify)
		session->notify(session);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static const GDBusMethodTable notify_methods[] = {
	{ GDBUS_METHOD("Release", NULL, NULL, notify_release) },
	{ GDBUS_METHOD("Update",
			GDBUS_ARGS({ "settings", "a{sv}" }), NULL,
			notify_update) },
	{ },
};

int session_notify_register(struct test_session *session,
				const char *notify_path)
{
	if (!g_dbus_register_interface(session->connection, notify_path, CONNMAN_NOTIFICATION_INTERFACE, notify_methods, NULL, NULL, session, NULL)) {
		return -EINVAL;
	}

	return 0;
}

int session_notify_unregister(struct test_session *session,
				const char *notify_path)
{
	if (!g_dbus_unregister_interface(session->connection, notify_path, CONNMAN_NOTIFICATION_INTERFACE)) {
		return -EINVAL;
	}

	return 0;
}

static void append_allowed_bearers(DBusMessageIter *iter, void *user_data)
{
	struct test_session_info *info = user_data;
	GSList *list;

	for (list = info->allowed_bearers;
			list; list = list->next) {
		struct test_bearer_info *bearer_info = list->data;

		dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
						&bearer_info->name);
	}
}

void session_append_settings(DBusMessageIter *dict,
				struct test_session_info *info)
{
	if (!info->allowed_bearers)
		return;

	connman_dbus_dict_append_array(dict, "AllowedBearers",
						DBUS_TYPE_STRING,
						append_allowed_bearers,
						info);
}

DBusMessage *session_connect(DBusConnection *connection,
				struct test_session *session)
{
	DBusMessage *message, *reply;
	DBusError error;

	message = dbus_message_new_method_call(CONNMAN_SERVICE,
						session->session_path,
						CONNMAN_SESSION_INTERFACE,
							"Connect");
	if (!message)
		return NULL;

	dbus_error_init(&error);

	reply = dbus_connection_send_with_reply_and_block(connection,
							message, -1, &error);
	if (!reply) {
		if (dbus_error_is_set(&error)) {
			LOG("%s", error.message);
			dbus_error_free(&error);
		} else {
			LOG("Failed to get properties");
		}
		dbus_message_unref(message);
		return NULL;
	}

	dbus_message_unref(message);

	return reply;
}

DBusMessage *session_disconnect(DBusConnection *connection,
					struct test_session *session)
{
	DBusMessage *message, *reply;
	DBusError error;

	message = dbus_message_new_method_call(CONNMAN_SERVICE,
						session->session_path,
						CONNMAN_SESSION_INTERFACE,
							"Disconnect");
	if (!message)
		return NULL;

	dbus_error_init(&error);

	reply = dbus_connection_send_with_reply_and_block(connection,
							message, -1, &error);
	if (!reply) {
		if (dbus_error_is_set(&error)) {
			LOG("%s", error.message);
			dbus_error_free(&error);
		} else {
			LOG("Failed to get properties");
		}
		dbus_message_unref(message);
		return NULL;
	}

	dbus_message_unref(message);

	return reply;
}
