/*
 * Copyright 2018 Konsulko Group
 * Author: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>

#include <glib.h>
#include <stdlib.h>
#include <gio/gio.h>
#include <glib-object.h>

#include <json-c/json.h>

#define AFB_BINDING_VERSION 3
#include <afb/afb-binding.h>

#include "network-api.h"
#include "network-common.h"

/**
 * The global thread
 */
static GThread *global_thread;

struct init_data {
	GCond cond;
	GMutex mutex;
	gboolean init_done;
	afb_api_t api;
	struct network_state *ns; /* before setting afb_api_set_userdata() */
	int rc;
};

static void signal_init_done(struct init_data *id, int rc);

static struct network_state *network_get_userdata(afb_req_t request) {
	afb_api_t api = afb_req_get_api(request);
	return afb_api_get_userdata(api);
}

static void call_work_lock(struct network_state *ns)
{
	g_mutex_lock(&ns->cw_mutex);
}

static void call_work_unlock(struct network_state *ns)
{
	g_mutex_unlock(&ns->cw_mutex);
}

struct call_work *call_work_lookup_unlocked(
		struct network_state *ns,
		const char *access_type, const char *type_arg,
		const char *method)
{
	struct call_work *cw;
	GSList *list;

	/* we can only allow a single pending call */
	for (list = ns->cw_pending; list; list = g_slist_next(list)) {
		cw = list->data;
		if (!g_strcmp0(access_type, cw->access_type) &&
		    !g_strcmp0(type_arg, cw->type_arg) &&
		    !g_strcmp0(method, cw->method))
			return cw;
	}
	return NULL;
}

struct call_work *call_work_lookup(
		struct network_state *ns,
		const char *access_type, const char *type_arg,
		const char *method)
{
	struct call_work *cw;

	g_mutex_lock(&ns->cw_mutex);
	cw = call_work_lookup_unlocked(ns, access_type, type_arg, method);
	g_mutex_unlock(&ns->cw_mutex);

	return cw;
}

int call_work_pending_id(
		struct network_state *ns,
		const char *access_type, const char *type_arg,
		const char *method)
{
	struct call_work *cw;
	int id = -1;

	g_mutex_lock(&ns->cw_mutex);
	cw = call_work_lookup_unlocked(ns, access_type, type_arg, method);
	if (cw)
		id = cw->id;
	g_mutex_unlock(&ns->cw_mutex);

	return id;
}

struct call_work *call_work_lookup_by_id_unlocked(
		struct network_state *ns, int id)
{
	struct call_work *cw;
	GSList *list;

	/* we can only allow a single pending call */
	for (list = ns->cw_pending; list; list = g_slist_next(list)) {
		cw = list->data;
		if (cw->id == id)
			return cw;
	}
	return NULL;
}

struct call_work *call_work_lookup_by_id(
		struct network_state *ns, int id)
{
	struct call_work *cw;

	g_mutex_lock(&ns->cw_mutex);
	cw = call_work_lookup_by_id_unlocked(ns, id);
	g_mutex_unlock(&ns->cw_mutex);

	return cw;
}

struct call_work *call_work_create_unlocked(struct network_state *ns,
		const char *access_type, const char *type_arg,
		const char *method, const char *connman_method,
		GError **error)
{

	struct call_work *cw = NULL;

	cw = call_work_lookup_unlocked(ns, access_type, type_arg, method);
	if (cw) {
		g_set_error(error, NB_ERROR, NB_ERROR_CALL_IN_PROGRESS,
				"another call in progress (%s/%s/%s)",
				access_type, type_arg, method);
		return NULL;
	}

	/* no other pending; allocate */
	cw = g_malloc0(sizeof(*cw));
	cw->ns = ns;
	do {
		cw->id = ns->next_cw_id;
		if (++ns->next_cw_id < 0)
			ns->next_cw_id = 1;
	} while (call_work_lookup_by_id_unlocked(ns, cw->id));

	cw->access_type = g_strdup(access_type);
	cw->type_arg = g_strdup(type_arg);
	cw->method = g_strdup(method);
	cw->connman_method = g_strdup(connman_method);

	ns->cw_pending = g_slist_prepend(ns->cw_pending, cw);

	return cw;
}

struct call_work *call_work_create(struct network_state *ns,
		const char *access_type, const char *type_arg,
		const char *method, const char *connman_method,
		GError **error)
{

	struct call_work *cw;

	g_mutex_lock(&ns->cw_mutex);
	cw = call_work_create_unlocked(ns,
			access_type, type_arg, method, connman_method,
			error);
	g_mutex_unlock(&ns->cw_mutex);

	return cw;
}

void call_work_destroy_unlocked(struct call_work *cw)
{
	struct network_state *ns = cw->ns;
	struct call_work *cw2;

	/* verify that it's something we know about */
	cw2 = call_work_lookup_by_id_unlocked(ns, cw->id);
	if (cw2 != cw) {
		AFB_ERROR("Bad call work to destroy");
		return;
	}

	/* remove it */
	ns->cw_pending = g_slist_remove(ns->cw_pending, cw);

	g_free(cw->access_type);
	g_free(cw->type_arg);
	g_free(cw->method);
	g_free(cw->connman_method);
}

void call_work_destroy(struct call_work *cw)
{
	struct network_state *ns = cw->ns;

	g_mutex_lock(&ns->cw_mutex);
	call_work_destroy_unlocked(cw);
	g_mutex_unlock(&ns->cw_mutex);
}

static afb_event_t get_event_from_value(struct network_state *ns,
			const char *value)
{
	if (!g_strcmp0(value, "global_state"))
		return ns->global_state_event;

	if (!g_strcmp0(value, "technologies"))
		return ns->technologies_event;

	if (!g_strcmp0(value, "technology_properties"))
		return ns->technology_properties_event;

	if (!g_strcmp0(value, "services"))
		return ns->services_event;

	if (!g_strcmp0(value, "service_properties"))
		return ns->service_properties_event;

	if (!g_strcmp0(value, "agent"))
		return ns->agent_event;

	return NULL;
}

static void network_manager_signal_callback(
	GDBusConnection *connection,
	const gchar *sender_name,
	const gchar *object_path,
	const gchar *interface_name,
	const gchar *signal_name,
	GVariant *parameters,
	gpointer user_data)
{
	struct network_state *ns = user_data;
	GVariantIter *array1, *array2, *array3;
	GError *error = NULL;
	GVariant *var = NULL;
	const gchar *path = NULL;
	const gchar *key = NULL;
	const gchar *basename;
	json_object *jarray = NULL, *jresp = NULL, *jobj, *jprop;
	afb_event_t event = NULL;
	GVariantIter *array;
	gboolean is_config, ret;

	/* AFB_INFO("sender=%s", sender_name);
	AFB_INFO("object_path=%s", object_path);
	AFB_INFO("interface=%s", interface_name);
	AFB_INFO("signal=%s", signal_name); */

	if (!g_strcmp0(signal_name, "TechnologyAdded")) {

		g_variant_get(parameters, "(oa{sv})", &path, &array);

		basename = connman_strip_path(path);
		g_assert(basename);	/* guaranteed by dbus */

		jresp = json_object_new_object();

		json_object_object_add(jresp, "technology",
				json_object_new_string(basename));
		json_object_object_add(jresp, "action",
				json_object_new_string("added"));

		jobj = json_object_new_object();
		while (g_variant_iter_loop(array, "{sv}", &key, &var)) {
			ret = technology_property_dbus2json(jobj,
					key, var, &is_config, &error);
			if (!ret) {
				AFB_WARNING("%s property %s - %s",
						"technology",
						key, error->message);
				g_clear_error(&error);
			}
		}
		g_variant_iter_free(array);

		json_object_object_add(jresp, "properties", jobj);

		event = ns->technologies_event;


	} else if (!g_strcmp0(signal_name, "TechnologyRemoved")) {

		g_variant_get(parameters, "(o)", &path);

		basename = connman_strip_path(path);
		g_assert(basename);	/* guaranteed by dbus */

		jresp = json_object_new_object();

		json_object_object_add(jresp, "technology",
				json_object_new_string(basename));
		json_object_object_add(jresp, "action",
				json_object_new_string("removed"));

		event = ns->technologies_event;

	} else if (!g_strcmp0(signal_name, "ServicesChanged")) {

		jresp = json_object_new_object();
		jarray = json_object_new_array();
		json_object_object_add(jresp, "values", jarray);

		g_variant_get(parameters, "(a(oa{sv})ao)", &array1, &array2);

		while (g_variant_iter_loop(array1, "(oa{sv})", &path, &array3)) {

			basename = connman_strip_path(path);
			g_assert(basename);	/* guaranteed by dbus */

			jobj = json_object_new_object();

			json_object_object_add(jobj, "service",
					json_object_new_string(basename));

			jprop = NULL;
			while (g_variant_iter_loop(array3,
						"{sv}", &key, &var)) {
				if (!jprop)
					jprop = json_object_new_object();
				ret = service_property_dbus2json(jprop, key, var,
						&is_config, &error);
				if (!ret) {
					AFB_WARNING("%s property %s - %s",
							"service",
							key, error->message);
					g_clear_error(&error);
				}
			}

			json_object_object_add(jobj, "action",
					json_object_new_string(jprop ?
						"changed" : "unchanged"));

			if (jprop)
				json_object_object_add(jobj, "properties",
						jprop);

			json_object_array_add(jarray, jobj);
		}

		while (g_variant_iter_loop(array2, "o", &path)) {

			basename = connman_strip_path(path);
			g_assert(basename);	/* guaranteed by dbus */

			jobj = json_object_new_object();

			json_object_object_add(jobj, "service",
					json_object_new_string(basename));

			json_object_object_add(jobj, "action",
					json_object_new_string("removed"));

			json_object_array_add(jarray, jobj);
		}

		g_variant_iter_free(array2);
		g_variant_iter_free(array1);

		event = ns->services_event;

	} else if (!g_strcmp0(signal_name, "PropertyChanged")) {

		jresp = json_object_new_object();

		g_variant_get(parameters, "(sv)", &key, &var);
		g_clear_error(&error);
		ret = manager_property_dbus2json(jresp,
				key, var, &is_config, &error);
		if (!ret) {
			AFB_WARNING("%s property %s - %s",
					"manager",
					key, error->message);
			g_clear_error(&error);
			json_object_put(jresp);
			jresp = NULL;
		} else
			event = ns->global_state_event;
	}

	/* if (jresp)
		printf("manager-signal\n%s\n",
			json_object_to_json_string_ext(jresp,
					JSON_C_TO_STRING_PRETTY)); */

	if (event && jresp) {
		afb_event_push(event, jresp);
		jresp = NULL;
	}

	json_object_put(jresp);
}

static void network_technology_signal_callback(
	GDBusConnection *connection,
	const gchar *sender_name,
	const gchar *object_path,
	const gchar *interface_name,
	const gchar *signal_name,
	GVariant *parameters,
	gpointer user_data)
{
	struct network_state *ns = user_data;
	GError *error = NULL;
	GVariant *var = NULL;
	const gchar *key = NULL;
	const gchar *basename;
	json_object *jresp = NULL, *jobj;
	afb_event_t event = NULL;
	gboolean is_config, ret;

	/* AFB_INFO("sender=%s", sender_name);
	AFB_INFO("object_path=%s", object_path);
	AFB_INFO("interface=%s", interface_name);
	AFB_INFO("signal=%s", signal_name); */

	/* a basename must exist and be at least 1 character wide */
	basename = connman_strip_path(object_path);
	g_assert(basename);

	if (!g_strcmp0(signal_name, "PropertyChanged")) {

		jobj = json_object_new_object();

		g_variant_get(parameters, "(sv)", &key, &var);

		g_clear_error(&error);
		ret = technology_property_dbus2json(jobj, key, var, &is_config,
					&error) ;
		g_variant_unref(var);
		var = NULL;

		if (!ret) {
			AFB_ERROR("unhandled manager property %s - %s",
				key, error->message);
			json_object_put(jobj);
			return;
		}

		jresp = json_object_new_object();

		json_object_object_add(jresp, "technology",
				json_object_new_string(basename));

		json_object_object_add(jresp, "properties", jobj);
		event = ns->technology_properties_event;
	}

	/* if (jresp)
		printf("technology-signal\n%s\n",
				json_object_to_json_string_ext(jresp,
					JSON_C_TO_STRING_PRETTY)); */

	if (event && jresp) {
		afb_event_push(event, jresp);
		jresp = NULL;
	}

	json_object_put(jresp);
}

static void network_service_signal_callback(
	GDBusConnection *connection,
	const gchar *sender_name,
	const gchar *object_path,
	const gchar *interface_name,
	const gchar *signal_name,
	GVariant *parameters,
	gpointer user_data)
{
	struct network_state *ns = user_data;
	GError *error = NULL;
	GVariant *var = NULL;
	const gchar *key = NULL;
	const gchar *basename;
	json_object *jresp = NULL, *jobj;
	afb_event_t event = NULL;
	gboolean is_config, ret;

	/* AFB_INFO("sender=%s", sender_name);
	AFB_INFO("object_path=%s", object_path);
	AFB_INFO("interface=%s", interface_name);
	AFB_INFO("signal=%s", signal_name); */

	/* a basename must exist and be at least 1 character wide */
	basename = connman_strip_path(object_path);
	g_assert(basename);

	if (!g_strcmp0(signal_name, "PropertyChanged")) {

		g_variant_get(parameters, "(sv)", &key, &var);

		jobj = json_object_new_object();
		ret = service_property_dbus2json(jobj,
				key, var, &is_config, &error);

		g_variant_unref(var);
		var = NULL;

		if (!ret) {
			AFB_ERROR("unhandled %s property %s - %s",
				"service", key, error->message);
			json_object_put(jobj);
			return;
		}

		jresp = json_object_new_object();

		json_object_object_add(jresp, "service",
				json_object_new_string(basename));

		json_object_object_add(jresp, "properties",
				jobj);

		event = ns->service_properties_event;
	}

	/* if (jresp)
		printf("service-signal\n%s\n",
				json_object_to_json_string_ext(jresp,
					JSON_C_TO_STRING_PRETTY)); */

	if (event && jresp) {
		afb_event_push(event, jresp);
		jresp = NULL;
	}

	json_object_put(jresp);
}

/* Introspection data for the agent service */
static const gchar introspection_xml[] =
"<node>"
"  <interface name='net.connman.Agent'>"
"    <method name='RequestInput'>"
"	   <arg type='o' name='service' direction='in'/>"
"	   <arg type='a{sv}' name='fields' direction='in'/>"
"	   <arg type='a{sv}' name='fields' direction='out'/>"
"    </method>"
"    <method name='ReportError'>"
"	   <arg type='o' name='service' direction='in'/>"
"	   <arg type='s' name='error' direction='in'/>"
"    </method>"
"  </interface>"
"</node>";

static const struct property_info agent_request_input_props[] = {
	{
		.name = "Name",
		.fmt = "{sv}",
		.sub	= (const struct property_info []) {
			{ .name = "Type",		.fmt = "s", },
			{ .name = "Requirement",	.fmt = "s", },
			{ .name = "Alternates",		.fmt = "s", },
			{ },
		},
	}, {
		.name = "SSID",
		.fmt = "{sv}",
		.sub	= (const struct property_info []) {
			{ .name = "Type",		.fmt = "s", },
			{ .name = "Requirement",	.fmt = "s", },
			{ },
		},
	}, {
		.name = "Identity",
		.fmt = "{sv}",
		.sub	= (const struct property_info []) {
			{ .name = "Type",		.fmt = "s", },
			{ .name = "Requirement",	.fmt = "s", },
			{ },
		},
	}, {
		.name = "Passphrase",
		.fmt = "{sv}",
		.sub	= (const struct property_info []) {
			{ .name = "Type",		.fmt = "s", },
			{ .name = "Requirement",	.fmt = "s", },
			{ },
		},
	}, {
		.name = "WPS",
		.fmt = "{sv}",
		.sub	= (const struct property_info []) {
			{ .name = "Type",		.fmt = "s", },
			{ .name = "Requirement",	.fmt = "s", },
			{ },
		},
	},
	{ }
};

static const struct property_info agent_request_input_out_props[] = {
	{
		.name = "Name",
		.fmt = "s",
	}, {
		.name = "SSID",
		.fmt = "s",
	}, {
		.name = "Identity",
		.fmt = "s",
	}, {
		.name = "Passphrase",
		.fmt = "s",
	}, {
		.name = "WPS",
		.fmt = "s",
	},
	{ }
};

static void handle_method_call(
		GDBusConnection *connection,
		const gchar *sender_name,
		const gchar *object_path,
		const gchar *interface_name,
		const gchar *method_name,
		GVariant *parameters,
		GDBusMethodInvocation *invocation,
		gpointer user_data)
{
	struct network_state *ns = user_data;
	struct call_work *cw;
	json_object *jev = NULL, *jprop;
	GVariantIter *array;
	const gchar *path = NULL;
	const gchar *service = NULL;
	const gchar *key = NULL;
	const gchar *strerr = NULL;
	GVariant *var = NULL;
	gboolean is_config;

	/* AFB_INFO("sender=%s", sender_name);
	AFB_INFO("object_path=%s", object_path);
	AFB_INFO("interface=%s", interface_name);
	AFB_INFO("method=%s", method_name); */

	if (!g_strcmp0(method_name, "RequestInput")) {

		jev = json_object_new_object();
		jprop = json_object_new_object();
		g_variant_get(parameters, "(oa{sv})", &path, &array);
		while (g_variant_iter_loop(array, "{sv}", &key, &var)) {
			root_property_dbus2json(jprop, agent_request_input_props,
					key, var, &is_config);
		}
		g_variant_iter_free(array);

		service = connman_strip_path(path);

		call_work_lock(ns);

		/* can only occur while a connect is issued */
		cw = call_work_lookup_unlocked(ns, "service", service,
				"connect_service");

		/* if nothing is pending return an error */
		if (!cw) {
			call_work_unlock(ns);
			json_object_put(jprop);
			g_dbus_method_invocation_return_dbus_error(invocation,
					"net.connman.Agent.Error.Canceled",
					"No connection pending");
			return;
		}

		json_object_object_add(jev, "id",
			json_object_new_int(cw->id));
		json_object_object_add(jev, "method",
				json_object_new_string("request-input"));
		json_object_object_add(jev, "service",
				json_object_new_string(service));
		json_object_object_add(jev, "fields", jprop);

		cw->agent_method = "RequestInput";
		cw->invocation = invocation;

		call_work_unlock(ns);

		/* AFB_INFO("request-input: jev=%s",
				json_object_to_json_string(jev)); */

		afb_event_push(ns->agent_event, jev);

		return;
	}

	if (!g_strcmp0(method_name, "ReportError")) {

		g_variant_get(parameters, "(os)", &path, &strerr);

		AFB_INFO("report-error: service_path=%s error=%s",
				path, strerr);

		return g_dbus_method_invocation_return_value(invocation, NULL);
	}

	g_dbus_method_invocation_return_dbus_error(invocation,
			"org.freedesktop.DBus.Error.UnknownMethod",
			"Uknown method");
}

static const GDBusInterfaceVTable interface_vtable = {
	.method_call  = handle_method_call,
	.get_property = NULL,
	.set_property = NULL,
};

static void on_bus_acquired(GDBusConnection *connection,
			    const gchar *name, gpointer user_data)
{
	struct init_data *id = user_data;
	struct network_state *ns = id->ns;
	GVariant *result;
	GError *error = NULL;

	AFB_INFO("agent bus acquired - registering %s", ns->agent_path);

	ns->registration_id = g_dbus_connection_register_object(connection,
			ns->agent_path,
			ns->introspection_data->interfaces[0],
			&interface_vtable,
			ns,	/* user data */
			NULL,	/* user_data_free_func */
			NULL);

	if (!ns->registration_id) {
		AFB_ERROR("failed to register agent to dbus");
		goto err_unable_to_register_bus;

	}

	result = manager_call(ns, "RegisterAgent",
			g_variant_new("(o)", ns->agent_path),
				&error);
	if (!result) {
		AFB_ERROR("failed to register agent to connman");
		goto err_unable_to_register_connman;
	}
	g_variant_unref(result);

	ns->agent_registered = TRUE;

	AFB_INFO("agent registered at %s", ns->agent_path);
	signal_init_done(id, 0);

	return;

err_unable_to_register_connman:
	 g_dbus_connection_unregister_object(ns->conn, ns->registration_id);
	 ns->registration_id = 0;
err_unable_to_register_bus:
	signal_init_done(id, -1);
}

static int network_register_agent(struct init_data *id)
{
	struct network_state *ns = id->ns;

	ns->agent_path = g_strdup_printf("%s/agent%d",
			CONNMAN_PATH, getpid());
	if (!ns->agent_path) {
		AFB_ERROR("can't create agent path");
		goto out_no_agent_path;
	}

	ns->introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL);
	if (!ns->introspection_data) {
		AFB_ERROR("can't create introspection data");
		goto out_no_introspection_data;
	}

	ns->agent_id = g_bus_own_name(G_BUS_TYPE_SYSTEM, AGENT_SERVICE,
			G_BUS_NAME_OWNER_FLAGS_REPLACE |
			  G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
			on_bus_acquired,
			NULL,
			NULL,
			id,
			NULL);
	if (!ns->agent_id) {
		AFB_ERROR("can't create agent bus instance");
		goto out_no_bus_name;
	}

	return 0;

out_no_bus_name:
	g_dbus_node_info_unref(ns->introspection_data);
out_no_introspection_data:
	g_free(ns->agent_path);
out_no_agent_path:
	return -1;
}

static void network_unregister_agent(struct network_state *ns)
{
	g_bus_unown_name(ns->agent_id);
	g_dbus_node_info_unref(ns->introspection_data);
	g_free(ns->agent_path);
}

static struct network_state *network_init(GMainLoop *loop)
{
	struct network_state *ns;
	GError *error = NULL;

	ns = g_try_malloc0(sizeof(*ns));
	if (!ns) {
		AFB_ERROR("out of memory allocating network state");
		goto err_no_ns;
	}

	AFB_INFO("connecting to dbus");

	ns->loop = loop;
	ns->conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
	if (!ns->conn) {
		if (error)
			g_dbus_error_strip_remote_error(error);
		AFB_ERROR("Cannot connect to D-Bus, %s",
				error ? error->message : "unspecified");
		g_error_free(error);
		goto err_no_conn;

	}

	AFB_INFO("connected to dbus");

	ns->global_state_event =
		afb_daemon_make_event("global_state");
	ns->technologies_event =
		afb_daemon_make_event("technologies");
	ns->technology_properties_event =
		afb_daemon_make_event("technology_properties");
	ns->services_event =
		afb_daemon_make_event("services");
	ns->service_properties_event =
		afb_daemon_make_event("service_properties");
	ns->agent_event =
		afb_daemon_make_event("agent");

	if (!afb_event_is_valid(ns->global_state_event) ||
	    !afb_event_is_valid(ns->technologies_event) ||
	    !afb_event_is_valid(ns->technology_properties_event) ||
	    !afb_event_is_valid(ns->services_event) ||
	    !afb_event_is_valid(ns->service_properties_event) ||
	    !afb_event_is_valid(ns->agent_event)) {
		AFB_ERROR("Cannot create events");
		goto err_no_events;
	}

	ns->manager_sub = g_dbus_connection_signal_subscribe(
			ns->conn,
			NULL,	/* sender */
			CONNMAN_MANAGER_INTERFACE,
			NULL,	/* member */
			NULL,	/* object path */
			NULL,	/* arg0 */
			G_DBUS_SIGNAL_FLAGS_NONE,
			network_manager_signal_callback,
			ns,
			NULL);
	if (!ns->manager_sub) {
		AFB_ERROR("Unable to subscribe to manager signal");
		goto err_no_manager_sub;
	}

	ns->technology_sub = g_dbus_connection_signal_subscribe(
			ns->conn,
			NULL,	/* sender */
			CONNMAN_TECHNOLOGY_INTERFACE,
			NULL,	/* member */
			NULL,	/* object path */
			NULL,	/* arg0 */
			G_DBUS_SIGNAL_FLAGS_NONE,
			network_technology_signal_callback,
			ns,
			NULL);
	if (!ns->technology_sub) {
		AFB_ERROR("Unable to subscribe to technology signal");
		goto err_no_technology_sub;
	}

	ns->service_sub = g_dbus_connection_signal_subscribe(
			ns->conn,
			NULL,	/* sender */
			CONNMAN_SERVICE_INTERFACE,
			NULL,	/* member */
			NULL,	/* object path */
			NULL,	/* arg0 */
			G_DBUS_SIGNAL_FLAGS_NONE,
			network_service_signal_callback,
			ns,
			NULL);
	if (!ns->service_sub) {
		AFB_ERROR("Unable to subscribe to service signal");
		goto err_no_service_sub;
	}

	g_mutex_init(&ns->cw_mutex);
	ns->next_cw_id = 1;

	return ns;

err_no_service_sub:
	g_dbus_connection_signal_unsubscribe(ns->conn, ns->technology_sub);
err_no_technology_sub:
	g_dbus_connection_signal_unsubscribe(ns->conn, ns->manager_sub);
err_no_manager_sub:
	/* no way to clear the events */
err_no_events:
	g_dbus_connection_close(ns->conn, NULL, NULL, NULL);
err_no_conn:
	g_free(ns);
err_no_ns:
	return NULL;
}

static void network_cleanup(struct network_state *ns)
{
	g_dbus_connection_signal_unsubscribe(ns->conn, ns->service_sub);
	g_dbus_connection_signal_unsubscribe(ns->conn, ns->technology_sub);
	g_dbus_connection_signal_unsubscribe(ns->conn, ns->manager_sub);
	g_dbus_connection_close(ns->conn, NULL, NULL, NULL);
	g_free(ns);
}

static void signal_init_done(struct init_data *id, int rc)
{
	g_mutex_lock(&id->mutex);
	id->init_done = TRUE;
	id->rc = rc;
	g_cond_signal(&id->cond);
	g_mutex_unlock(&id->mutex);
}

static gpointer network_func(gpointer ptr)
{
	struct init_data *id = ptr;
	struct network_state *ns;
	GMainLoop *loop;
	int rc;

	loop = g_main_loop_new(NULL, FALSE);
	if (!loop) {
		AFB_ERROR("Unable to create main loop");
		goto err_no_loop;
	}

	/* real network init */
	ns = network_init(loop);
	if (!ns) {
		AFB_ERROR("network_init() failed");
		goto err_no_ns;
	}

	id->ns = ns;
	rc = network_register_agent(id);
	if (rc) {
		AFB_ERROR("network_register_agent() failed");
		goto err_no_agent;
	}

	/* note that we wait for agent registration to signal done */

	afb_api_set_userdata(id->api, ns);
	g_main_loop_run(loop);

	g_main_loop_unref(ns->loop);

	network_unregister_agent(ns);

	network_cleanup(ns);
	afb_api_set_userdata(id->api, NULL);

	return NULL;

err_no_agent:
	network_cleanup(ns);

err_no_ns:
	g_main_loop_unref(loop);

err_no_loop:
	signal_init_done(id, -1);

	return NULL;
}

static int init(afb_api_t api)
{
	struct init_data init_data, *id = &init_data;
	gint64 end_time;

	memset(id, 0, sizeof(*id));
	id->init_done = FALSE;
	id->rc = 0;
	id->api = api;
	g_cond_init(&id->cond);
	g_mutex_init(&id->mutex);

	global_thread = g_thread_new("agl-service-network",
				network_func,
				id);

	AFB_INFO("network-binding waiting for init done");

	/* wait maximum 10 seconds for init done */
	end_time = g_get_monotonic_time () + 10 * G_TIME_SPAN_SECOND;
	g_mutex_lock(&id->mutex);
	while (!id->init_done) {
		if (!g_cond_wait_until(&id->cond, &id->mutex, end_time))
			break;
	}
	g_mutex_unlock(&id->mutex);

	if (!id->init_done) {
		AFB_ERROR("network-binding init timeout");
		return -1;
	}

	if (id->rc)
		AFB_ERROR("network-binding init thread returned %d",
				id->rc);
	else
		AFB_INFO("network-binding operational");

	return id->rc;
}

static void network_subscribe_unsubscribe(afb_req_t request,
		gboolean unsub)
{
	struct network_state *ns = network_get_userdata(request);
	json_object *jresp = NULL;
	const char *value;
	afb_event_t event;
	int rc;

	/* if value exists means to set offline mode */
	value = afb_req_value(request, "value");
	if (!value) {
		afb_req_fail_f(request, "failed", "Missing \"value\" event");
		return;
	}

	event = get_event_from_value(ns, value);
	if (!event) {
		afb_req_fail_f(request, "failed", "Bad \"value\" event \"%s\"",
				value);
		return;
	}

	if (!unsub)
		rc = afb_req_subscribe(request, event);
	else
		rc = afb_req_unsubscribe(request, event);
	if (rc != 0) {
		afb_req_fail_f(request, "failed",
					"%s error on \"value\" event \"%s\"",
					!unsub ? "subscribe" : "unsubscribe",
					value);
		return;
	}

	jresp = json_object_new_object();
	afb_req_success_f(request, jresp, "Network %s to event \"%s\"",
			!unsub ? "subscribed" : "unsubscribed",
			value);
}

static void network_subscribe(afb_req_t request)
{
	network_subscribe_unsubscribe(request, FALSE);
}

static void network_unsubscribe(afb_req_t request)
{
	network_subscribe_unsubscribe(request, TRUE);
}

static void network_state(afb_req_t request)
{
	struct network_state *ns = network_get_userdata(request);
	GError *error = NULL;
	json_object *jresp;

	jresp = manager_get_property(ns, FALSE, "State", &error);
	if (!jresp) {
		afb_req_fail_f(request, "failed", "property %s error %s",
				"State", error->message);
		return;
	}

	afb_req_success(request, jresp, "Network - state");
}

static void network_offline(afb_req_t request)
{
	struct network_state *ns = network_get_userdata(request);
	GError *error = NULL;
	json_object *jresp = NULL;
	const char *value;
	int set_to;
	gboolean ret;

	/* if value exists means to set offline mode */
	value = afb_req_value(request, "value");
	if (!value) {
		jresp = manager_get_property(ns, FALSE, "OfflineMode", &error);
		if (!jresp) {
			afb_req_fail_f(request, "failed", "property %s error %s",
					"OfflineMode", error->message);
			g_error_free(error);
			return;
		}
	} else {
		set_to = str2boolean(value);
		if (set_to < 0) {
			afb_req_fail_f(request, "failed", "bad value \"%s\"",
					value);
			return;
		}
		ret = manager_set_property(ns, FALSE, "OfflineMode",
				json_object_new_boolean(set_to), &error);
		if (!ret) {
			afb_req_fail_f(request, "failed",
				"Network - offline mode set to %s failed - %s",
				set_to ? "true" : "false", error->message);
			g_error_free(error);
			return;
		}
	}

	afb_req_success(request, jresp, "Network - offline mode");
}

static void network_technologies(afb_req_t request)
{
	struct network_state *ns = network_get_userdata(request);
	json_object *jresp;
	GError *error = NULL;

	/* if value exists means to set offline mode */
	jresp = technology_properties(ns, &error, NULL);
	if (!jresp) {
		afb_req_fail_f(request, "failed", "technology properties error %s",
				error->message);
		g_error_free(error);
		return;
	}

	afb_req_success(request, jresp, "Network - Network Technologies");
}

static void network_services(afb_req_t request)
{
	struct network_state *ns = network_get_userdata(request);
	json_object *jresp;
	GError *error = NULL;

	jresp = service_properties(ns, &error, NULL);
	if (!jresp) {
		afb_req_fail_f(request, "failed", "service properties error %s",
				error->message);
		g_error_free(error);
		return;
	}

	afb_req_success(request, jresp, "Network - Network Services");
}

static void network_technology_set_powered(afb_req_t request,
		gboolean powered)
{
	struct network_state *ns = network_get_userdata(request);
	GError *error = NULL;
	const char *technology;
	json_object *jpowered;
	gboolean ret, this_powered;

	/* if value exists means to set offline mode */
	technology = afb_req_value(request, "technology");
	if (!technology) {
		afb_req_fail(request, "failed",
				"technology argument missing");
		return;
	}

	jpowered = technology_get_property(ns, technology,
			FALSE, "Powered", &error);
	if (!jpowered) {
		afb_req_fail_f(request, "failed",
			"Network - failed to get current Powered state - %s",
			error->message);
		g_error_free(error);
		return;
	}
	this_powered = json_object_get_boolean(jpowered);
	json_object_put(jpowered);
	jpowered = NULL;

	if (this_powered == powered) {
		afb_req_success_f(request, NULL,
				"Network - Technology %s already %s",
				technology, powered ? "enabled" : "disabled");
		return;
	}

	ret = technology_set_property(ns, technology,
			FALSE, "Powered",
			json_object_new_boolean(powered), &error);
	if (!ret) {
		afb_req_fail_f(request, "failed",
			"Network - failed to set Powered state - %s",
			error->message);
		g_error_free(error);
		return;
	}

	afb_req_success_f(request, NULL, "Network - Technology %s %s",
			technology, powered ? "enabled" : "disabled");
}

static void network_enable_technology(afb_req_t request)
{
	return network_technology_set_powered(request, TRUE);
}

static void network_disable_technology(afb_req_t request)
{
	return network_technology_set_powered(request, FALSE);
}

static void network_scan_services(afb_req_t request)
{
	struct network_state *ns = network_get_userdata(request);
	GVariant *reply = NULL;
	GError *error = NULL;
	const char *technology;

	/* if value exists means to set offline mode */
	technology = afb_req_value(request, "technology");
	if (!technology) {
		afb_req_fail(request, "failed", "No technology given to enable");
		return;
	}

	reply = technology_call(ns, technology, "Scan", NULL, &error);
	if (!reply) {
		afb_req_fail_f(request, "failed",
				"technology %s method %s error %s",
				technology, "Scan", error->message);
		g_error_free(error);
		return;
	}
	g_variant_unref(reply);

	afb_req_success_f(request, json_object_new_object(),
			"Network - technology %s scan complete",
			technology);
}

static void network_move_service(afb_req_t request)
{
	struct network_state *ns = network_get_userdata(request);
	GVariant *reply = NULL;
	GError *error = NULL;
	const char *service;
	const char *other_service;
	const char *method_name;
	gboolean before_after;	/* false=before, true=after */

	/* first service */
	service = afb_req_value(request, "service");
	if (!service) {
		afb_req_fail(request, "failed", "No service given to move");
		return;
	}

	before_after = FALSE;
	other_service = afb_req_value(request, "before_service");
	if (!other_service) {
		before_after = TRUE;
		other_service = afb_req_value(request, "after_service");
	}

	if (!other_service) {
		afb_req_fail(request, "failed", "No other service given for move");
		return;
	}

	method_name = before_after ? "MoveAfter" : "MoveBefore";

	reply = service_call(ns, service, method_name,
			g_variant_new("o", CONNMAN_SERVICE_PATH(other_service)),
			&error);
	if (!reply) {
		afb_req_fail_f(request, "failed", "%s error %s",
				method_name,
				error ? error->message : "unspecified");
		g_error_free(error);
		return;
	}
	g_variant_unref(reply);

	afb_req_success_f(request, NULL, "Network - service %s moved %s service %s",
			service, before_after ? "before" : "after", other_service);
}

static void network_remove_service(afb_req_t request)
{
	struct network_state *ns = network_get_userdata(request);
	GVariant *reply = NULL;
	GError *error = NULL;
	const char *service;

	/* first service */
	service = afb_req_value(request, "service");
	if (!service) {
		afb_req_fail(request, "failed", "No service");
		return;
	}

	reply = service_call(ns, service, "Remove", NULL, &error);
	if (!reply) {
		afb_req_fail_f(request, "failed", "Remove error %s",
				error ? error->message : "unspecified");
		g_error_free(error);
		return;
	}
	g_variant_unref(reply);

	afb_req_success_f(request, NULL, "Network - service %s removed",
			service);
}

static void connect_service_callback(void *user_data,
		GVariant *result, GError **error)
{
	struct call_work *cw = user_data;
	struct network_state *ns = cw->ns;
	struct json_object *jerr;
	GError *sub_error = NULL;

	connman_decode_call_error(ns,
		cw->access_type, cw->type_arg, cw->connman_method,
		error);
	if (error && *error) {
		/* read the Error property (if available to be specific) */

		jerr = service_get_property(ns, cw->type_arg, FALSE,
				"Error", &sub_error);
		g_clear_error(&sub_error);
		if (jerr) {
			/* clear property error */
			service_call(ns, cw->type_arg, "ClearProperty",
					NULL, &sub_error);
			g_clear_error(&sub_error);

			afb_req_fail_f(cw->request, "failed", "Connect error: %s",
				json_object_get_string(jerr));
			json_object_put(jerr);
			jerr = NULL;
		} else
			afb_req_fail_f(cw->request, "failed", "Connect error: %s",
				(*error)->message);
		goto out_free;
	}

	if (result)
		g_variant_unref(result);

	afb_req_success_f(cw->request, NULL, "Network - service %s connected",
			cw->type_arg);
out_free:
	afb_req_unref(cw->request);
	call_work_destroy(cw);
}

static void network_connect_service(afb_req_t request)
{
	struct network_state *ns = network_get_userdata(request);
	GError *error = NULL;
	const char *service;
	struct call_work *cw;

	/* first service */
	service = afb_req_value(request, "service");
	if (!service) {
		afb_req_fail(request, "failed",
				"No service given");
		return;
	}

	cw = call_work_create(ns, "service", service,
			"connect_service", "Connect", &error);
	if (!cw) {
		afb_req_fail_f(request, "failed", "can't queue work %s",
				error->message);
		g_error_free(error);
		return;
	}

	cw->request = request;
	afb_req_addref(request);
	cw->cpw = connman_call_async(ns, "service", service,
			"Connect", NULL, &error,
			connect_service_callback, cw);
	if (!cw->cpw) {
		afb_req_fail_f(request, "failed", "connection error %s",
				error->message);
		call_work_destroy(cw);
		g_error_free(error);
		return;
	}
}

static void network_disconnect_service(afb_req_t request)
{
	struct network_state *ns = network_get_userdata(request);
	json_object *jresp;
	GVariant *reply = NULL;
	GError *error = NULL;
	const char *service;

	/* first service */
	service = afb_req_value(request, "service");
	if (!service) {
		afb_req_fail(request, "failed", "No service given to move");
		return;
	}

	reply = service_call(ns, service, "Disconnect", NULL, &error);
	if (!reply) {
		afb_req_fail_f(request, "failed", "Disconnect error %s",
				error ? error->message : "unspecified");
		g_error_free(error);
		return;
	}

	g_variant_unref(reply);

	jresp = json_object_new_object();
	afb_req_success_f(request, jresp, "Network - service %s disconnected",
			service);
}

static void network_get_property(afb_req_t request)
{
	struct network_state *ns = network_get_userdata(request);
	json_object *jobj = afb_req_json(request);
	const char *technology, *service;
	const char *access_type;
	const char *type_arg;
	GError *error = NULL;
	json_object *jprop, *jrespprop = NULL, *jreqprop = NULL;

	(void)ns;

	/* printf("%s\n", json_object_to_json_string_ext(jobj,
					JSON_C_TO_STRING_PRETTY)); */

	/* either or both may be NULL */
	technology = afb_req_value(request, "technology");
	service = afb_req_value(request, "service");

	if (technology) {
		access_type = CONNMAN_AT_TECHNOLOGY;
		type_arg = technology;
	} else if (service) {
		access_type = CONNMAN_AT_SERVICE;
		type_arg = service;
	} else {
		access_type = CONNMAN_AT_MANAGER;
		type_arg = NULL;
	}

	jprop = connman_get_properties(ns, access_type, type_arg, &error);
	if (!jprop) {
		afb_req_fail_f(request, "failed", "%s property error %s",
				access_type, error->message);
		g_error_free(error);
		return;
	}

	/* if properties exist, copy out only those */
	if (json_object_object_get_ex(jobj, "properties", &jreqprop)) {
		/* and now copy (if found) */
		jrespprop = get_property_collect(jreqprop, jprop,
				&error);
		json_object_put(jprop);

		if (!jrespprop) {
			afb_req_fail_f(request, "failed", "error - %s",
					error->message);
			g_error_free(error);
			return;
		}
	} else	/* otherwise everything */
		jrespprop = jprop;

	afb_req_success_f(request, jrespprop, "Network - %s property get",
			access_type);
}

static void network_set_property(afb_req_t request)
{
	struct network_state *ns = network_get_userdata(request);
	json_object *jobj = afb_req_json(request);
	const char *technology, *service;
	const char *access_type;
	const char *type_arg;
	json_object *jprop = NULL;
	GError *error = NULL;
	gboolean ret;
	int count;

	/* printf("%s\n", json_object_to_json_string_ext(jobj,
					JSON_C_TO_STRING_PRETTY)); */

	if (!json_object_object_get_ex(jobj, "properties", &jprop)) {
		afb_req_fail_f(request, "failed", "Network - property set no properties");
		return;
	}

	/* either or both may be NULL */
	technology = afb_req_value(request, "technology");
	service = afb_req_value(request, "service");

	if (technology) {
		access_type = CONNMAN_AT_TECHNOLOGY;
		type_arg = technology;
	} else if (service) {
		access_type = CONNMAN_AT_SERVICE;
		type_arg = service;
	} else {
		access_type = CONNMAN_AT_MANAGER;
		type_arg = NULL;
	}

	/* iterate */
	count = 0;
	json_object_object_foreach(jprop, key, jval) {
		ret = FALSE;

		/* keep jval from being consumed */
		json_object_get(jval);
		ret = connman_set_property(ns, access_type, type_arg,
					TRUE, key, jval, &error);
		if (!ret) {
			afb_req_fail_f(request, "failed",
				"Network - set property %s to %s failed - %s",
				key, json_object_to_json_string(jval),
				error->message);
			g_error_free(error);
			return;
		}
		count++;
	}

	if (!count) {
		afb_req_fail_f(request, "failed", "Network - property set empty");
		return;
	}

	afb_req_success_f(request, NULL, "Network - %s %d propert%s set",
			access_type, count, count > 1 ? "ies" : "y");
}

static void network_agent_response(afb_req_t request)
{
	struct network_state *ns = network_get_userdata(request);
	json_object *jobj = afb_req_json(request);
	json_object *jfields;
	const char *id_str;
	int id;
	const struct property_info *pi, *pi_sub;
	GError *error = NULL;
	gboolean ret;
	struct call_work *cw;
	GVariant *parameters = NULL, *item;
	GVariantBuilder builder, builder2;
	gboolean is_config;
	gchar *dbus_name;

	/* printf("%s\n", json_object_to_json_string(jobj)); */

	/* either or both may be NULL */
	id_str = afb_req_value(request, "id");
	id = id_str ? (int)strtol(id_str, NULL, 10) : -1;
	if (id <= 0) {
		afb_req_fail_f(request, "failed",
				"Network - agent response missing arguments");
		return;
	}

	call_work_lock(ns);
	cw = call_work_lookup_by_id_unlocked(ns, id);
	if (!cw || !cw->invocation) {
		call_work_unlock(ns);
		afb_req_fail_f(request, "failed",
				"Network - can't find request with id=%d", id);
		return;
	}

	ret = FALSE;
	parameters = NULL;
	if (!g_strcmp0(cw->agent_method, "RequestInput") &&
	    json_object_object_get_ex(jobj, "fields", &jfields)) {

		/* AFB_INFO("request-input response fields=%s",
				json_object_to_json_string(jfields)); */

		pi = agent_request_input_out_props;
		g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
		json_object_object_foreach(jfields, key_o, jval_o) {
			pi_sub = property_by_json_name(pi, key_o, &is_config);
			if (!pi_sub) {
				AFB_ERROR("no property %s", key_o);
				continue;
			}

			g_clear_error(&error);
			item = property_json_to_gvariant(pi_sub, NULL, NULL,
					jval_o, &error);
			if (!item) {
				AFB_ERROR("on %s", key_o);
				continue;
			}

			dbus_name = property_get_name(pi, key_o);
			g_assert(dbus_name);	/* can't fail; but check */

			g_variant_builder_add(&builder, "{sv}", dbus_name, item);

			g_free(dbus_name);
		}

		/* dbus requires response to be wrapped as a tuple */
		g_variant_builder_init(&builder2, G_VARIANT_TYPE_TUPLE);
		g_variant_builder_add_value(&builder2,
				g_variant_builder_end(&builder));

		parameters = g_variant_builder_end(&builder2);
		ret = TRUE;
	}


	if (!ret) {
		afb_req_fail_f(request, "failed",
				"Network - unhandled agent method %s",
				cw->agent_method);
		g_dbus_method_invocation_return_dbus_error(cw->invocation,
				"org.freedesktop.DBus.Error.UnknownMethod",
				"Uknown method");
		cw->invocation = NULL;
		call_work_unlock(ns);
		return;
	}

	g_dbus_method_invocation_return_value(cw->invocation, parameters);
	cw->invocation = NULL;

	call_work_unlock(ns);

	afb_req_success_f(request, NULL, "Network - agent response sent");
}

static const afb_verb_t network_verbs[] = {
	{
		.verb = "subscribe",
		.session = AFB_SESSION_NONE,
		.callback = network_subscribe,
		.info ="Subscribe to the event of 'value'",
	}, {
		.verb = "unsubscribe",
		.session = AFB_SESSION_NONE,
		.callback = network_unsubscribe,
		.info ="Unsubscribe to the event of 'value'",
	}, {
		.verb = "state",
		.session = AFB_SESSION_NONE,
		.callback = network_state,
		.info ="Return global network state"
	}, {
		.verb = "offline",
		.session = AFB_SESSION_NONE,
		.callback = network_offline,
		.info ="Return global network state"
	}, {
		.verb = "technologies",
		.session = AFB_SESSION_NONE,
		.callback = network_technologies,
		.info ="Return list of network technologies"
	}, {
		.verb = "services",
		.session = AFB_SESSION_NONE,
		.callback = network_services,
		.info ="Return list of network services"
	}, {
		.verb = "enable_technology",
		.session = AFB_SESSION_NONE,
		.callback = network_enable_technology,
		.info ="Enable a technology"
	}, {
		.verb = "disable_technology",
		.session = AFB_SESSION_NONE,
		.callback = network_disable_technology,
		.info ="Disable a technology"
	}, {
		.verb = "scan_services",
		.session = AFB_SESSION_NONE,
		.callback = network_scan_services,
		.info ="Scan services"
	}, {
		.verb = "move_service",
		.session = AFB_SESSION_NONE,
		.callback = network_move_service,
		.info ="Arrange service order"
	}, {
		.verb = "remove_service",
		.session = AFB_SESSION_NONE,
		.callback = network_remove_service,
		.info ="Remove service"
	}, {
		.verb = "connect_service",
		.session = AFB_SESSION_NONE,
		.callback = network_connect_service,
		.info ="Connect service"
	}, {
		.verb = "disconnect_service",
		.session = AFB_SESSION_NONE,
		.callback = network_disconnect_service,
		.info ="Disconnect service"
	}, {
		.verb = "get_property",
		.session = AFB_SESSION_NONE,
		.callback = network_get_property,
		.info ="Get property"
	}, {
		.verb = "set_property",
		.session = AFB_SESSION_NONE,
		.callback = network_set_property,
		.info ="Set property"
	}, {
		.verb = "agent_response",
		.session = AFB_SESSION_NONE,
		.callback = network_agent_response,
		.info ="Agent response"
	},

	{ } /* marker for end of the array */
};

/*
 * description of the binding for afb-daemon
 */
const afb_binding_t afbBindingV3 = {
	.api = "network-manager", /* the API name (or binding name or prefix) */
	.specification = "networking API", /* short description of of the binding */
	.verbs = network_verbs, /* the array describing the verbs of the API */
	.init = init,
};