/*
 * Copyright (C) 2018 "IoT.bzh"
 * Author : Thierry Bultel <thierry.bultel@iot.bzh>
 *
 * 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 <unistd.h>

#include <wrap-json.h>

#include <errno.h>
#include <ctl-config.h>
#include <systemd/sd-event.h>
#include <bluealsa/bluealsa.h>
#include <systemd/sd-bus.h>
#include <urcu/list.h>

#include "hal-bluealsa.h"
#include "4a-hal-utilities-data.h"

#define HAL_BLUEALSA_PLUGIN_NAME "hal-bluealsa"

#define A2DP_LISTEN_FORMAT	"S16_LE"

#define SCO_TALK_RATE 44100
#define SCO_TALK_FORMAT "S16_LE"

#define SCO_TALK_ZONE "sco_talk_zone"
#define SCO_CHANNEL_MONO "sco-mono"

#define ORG_FREEDESKTOP_DBUS_NAME "org.freedesktop.DBus"
#define ORG_FREEDESKTOP_DBUS_PATH "/org/freedesktop/DBus"
#define ORG_FREEDESKTOP_DBUS_INTERFACE ORG_FREEDESKTOP_DBUS_NAME

#define BLUEZALSA_DBUS_NAME_PREFIX "org.bluez-alsa"

/* forward static declarations */
static int halBlueAlsaTransportEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData);
static int halBlueAlsaFetchTransports(bluealsa_watch * plugin);
static int halBlueAlsaRegisterAll(CtlPluginT* plugin);
static int halBlueAlsaRegister(CtlPluginT* plugin, const char * interface);

CTLP_CAPI_REGISTER(HAL_BLUEALSA_PLUGIN_NAME)


// Call at initialization time ('requires' are forbidden at this stage)
CTLP_ONLOAD(plugin, callbacks)
{
	AFB_API_NOTICE(plugin->api, "%s Plugin Registered correctly: uid='%s' 'info='%s'", HAL_BLUEALSA_PLUGIN_NAME, plugin->uid, plugin->info);
	return 0;
}


CTLP_INIT(plugin, callbacks)
{
	json_object *actionsToAdd = NULL;
	CtlConfigT *ctrlConfig;
	struct HalData *currentHalData;

	ctrlConfig = (CtlConfigT *) afb_api_get_userdata(plugin->api);
	if (!ctrlConfig) {
		AFB_API_ERROR(plugin->api, "Can't get current internal hal controller config");
		goto fail;
	}

	currentHalData = (struct HalData *) getExternalData(ctrlConfig);
	if (!currentHalData) {
		AFB_API_ERROR(plugin->api, "Can't get current internal hal controller data");
		goto fail;
	}

	if (currentHalData->status != HAL_STATUS_AVAILABLE) {
		AFB_API_WARNING(plugin->api,
				"Controller initialization of %s plugin cannot be done because hal is not ready to be used", HAL_BLUEALSA_PLUGIN_NAME);
		goto done;
	}

	wrap_json_pack(&actionsToAdd, "{s:s s:s s:s}",
				      "uid", "init-bluealsa-plugin",
				      "info", "Init Bluez-Alsa hal plugin",
				      "action", "plugin://hal-bluealsa#init");

	int idx = 0;
	while (ctrlConfig->sections[idx].key && strcasecmp(ctrlConfig->sections[idx].key, "onload"))
		idx++;

	if (!ctrlConfig->sections[idx].key) {
		AFB_API_ERROR(plugin->api, "Wasn't able to add '%s' as a new onload, 'onload' section not found", json_object_get_string(actionsToAdd));
		goto fail;
	}

	if(AddActionsToSection(plugin->api, &ctrlConfig->sections[idx], actionsToAdd, 0)) {
		AFB_API_ERROR(plugin->api, "Wasn't able to add '%s' as a new onload to %s", json_object_get_string(actionsToAdd), ctrlConfig->sections[idx].uid);
		goto fail;
	}

	AFB_API_NOTICE(plugin->api, "Plugin initialization of %s plugin correctly done", HAL_BLUEALSA_PLUGIN_NAME);
done:
	return 0;
fail:
	if (actionsToAdd)
		json_object_put(actionsToAdd);
	return -1;
}


// Call at controller onload time
CTLP_CAPI(init, source, argsJ, queryJ)
{
	AFB_API_NOTICE(source->api, "Controller onload event");

	CtlPluginT * plugin = source->plugin;

	hal_bluealsa_plugin_data_t * pluginData  = (hal_bluealsa_plugin_data_t*) calloc(1, sizeof(hal_bluealsa_plugin_data_t));
	if(!pluginData)
		goto fail;

	int error;

	/*
	"params": {
		"sco": {
			"mic": "2CH-GENERIC-USB",
			"zone": "full-stereo",
			"delayms": 300
		},
		"a2dp": {
			"zone": "full-stereo",
			"delayms": 1000
		}
	}
	 */

	json_object * scoParamsJ = NULL;
	json_object * a2dpParamsJ = NULL;

	error = wrap_json_unpack(plugin->paramsJ, "{s?o,s?o !}",
				"sco", &scoParamsJ,
				"a2dp", &a2dpParamsJ);

	if (error) {
		AFB_API_ERROR(plugin->api, "%s: wrong parameters", __func__);
		goto fail;
	}

	if (scoParamsJ) {
		AFB_API_INFO(plugin->api, "%s: sco parameters: %s", __func__, json_object_get_string(scoParamsJ));
		error = wrap_json_unpack(scoParamsJ, "{s:s,s:s,s?i !}",
				"mic", &pluginData->sco.mic,
				"zone", &pluginData->sco.speaker,
				"delayms", &pluginData->sco.delayms);
		if (error) {
			AFB_API_ERROR(plugin->api, "%s: wrong sco parameters: err %s", __func__, wrap_json_get_error_string(error));
			goto fail;
		}
	}

	if (a2dpParamsJ) {
		AFB_API_INFO(plugin->api, "%s: a2dp parameters: %s", __func__, json_object_get_string(a2dpParamsJ));
		error = wrap_json_unpack(a2dpParamsJ, "{s:s,s?i !}",
				"zone", &pluginData->a2dp.zone,
				"delayms", &pluginData->a2dp.delayms);
		if (error) {
			AFB_API_ERROR(plugin->api, "%s: wrong a2dp parameters: err=%s", __func__, wrap_json_get_error_string(error));
			goto fail;
		}
	}

	halBlueAlsaTransportsInit(&pluginData->transport_list);

	setPluginContext(plugin, pluginData);

	if (halBlueAlsaRegisterAll(plugin) != 0)
		goto fail;

	return 0;
fail:
	return -1;
}


static int halBlueAlsaTransportEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData) {

	bluealsa_watch * watch = (bluealsa_watch *)userData;
	CtlPluginT * plugin = watch->plugin;

	struct ba_msg_event event;
	ssize_t ret;

	AFB_API_DEBUG(plugin->api, "------ %s ------!", __func__);

	if ((revents & EPOLLIN) == 0)
		goto done;

	if (revents & EPOLLHUP) {
		AFB_API_INFO(plugin->api, "Lost connection with bluealsa on interface %s", watch->interface);
		sd_event_source_unref(src);
		goto done;
	}

	while ((ret = recv(watch->fd, &event, sizeof(event), MSG_DONTWAIT)) == -1 && errno == EINTR)
		continue;

	if (ret != sizeof(event)) {
		AFB_API_ERROR(plugin->api, "Couldn't read event: %s", strerror(ret == -1 ? errno : EBADMSG));
		goto done;
	}

	halBlueAlsaFetchTransports(watch);

done:
	return 0;
}


/*
 *
 * The following function builds that kind of json data and send it to smixer
 *
 * A2DP Case:
 *
{
	"uid":"a2dp_F6:32:15:2A:80:70",
	"captures": {
		"uid":"a2dp_listen_capture",
		"pcmplug_params" : "bluealsa:HCI=hci0,DEV=F6:32:15:2A:80:70,PROFILE=a2dp",
		"source" : {
			"channels": [
				{
					"uid": "a2dp-right",
					"port": 0
				},
				{
					"uid": "a2dp-left",
					"port": 1
				}
			]
		}
	},
	"streams": {
		"uid" : "a2dp_listen_stream",
		"source":"a2dp_listen_capture",
		"zone": "full-stereo"
	}
}
 *
 *-----------------------------------------------------------------------------------
 *
 * SCO Case:
 *
{
	"uid":"sco_F6:32:15:2A:80:70",",
	"captures": {
		"uid":"sco_listen_capture",
		"pcmplug_params" : "bluealsa:HCI=hci0,DEV=F6:32:15:2A:80:70,PROFILE=sco",
		"source" : {
			"channels": [
				{
					"uid": "sco-right",
					"port": 0
				},
				{
					"uid": "sco-left",
					"port": 1
				}
			]
		}
	},
	"playbacks" : {
		"uid"="sco_talk_playback",
		"pcmplug_params" : "bluealsa:HCI=hci0,DEV=F6:32:15:2A:80:70,PROFILE=sco",
		"params": {
			"rate": 44100,
			"format": "S16_LE",
		},
		"sink": {
			"channels": [
				{
					"uid": "sco-mono"
				}
			]
		}
	},
	"zones" : [
		{
			"uid": "sco_talk_zone",
			"sink": [
				{
					"target": "sco-mono",
					"channel": 0,
					"volume": 0.5
				},
				{
					"target": "sco-mono",
					"channel": 1,
					"volume": 0.5
				}
			]
		}
	],
	"streams": [
		{
			"uid" : "sco_listen_stream",
			"source":"sco_listen_capture",
			"zone": "full-stereo",
			"delayms": 300
		},
		{
			"uid" : "sco_talk_stream",
			"source": "2CH-GENERIC-USB",
			"zone": "sco_talk_zone",
			"delayms": 300
		}
	]
}
 *
 * */

static json_object* halBlueAlsaA2DPTransportChannels(const char * transportTypeS) {
	json_object* channelRightJ;
	json_object* channelLeftJ;
	json_object* channelsJ = json_object_new_array();
	char * channelRightS;
	char * channelLeftS;

	channelRightJ = json_object_new_object();
	if (asprintf(&channelRightS, "%s-right", transportTypeS) == -1)
		goto fail;

	json_object_object_add(channelRightJ, "uid", json_object_new_string(channelRightS));
	json_object_object_add(channelRightJ, "port", json_object_new_int(0));

	channelLeftJ = json_object_new_object();
	if (asprintf(&channelLeftS, "%s-left", transportTypeS) == -1)
		goto fail;

	json_object_object_add(channelLeftJ, "uid", json_object_new_string(channelLeftS));
	json_object_object_add(channelLeftJ, "port", json_object_new_int(1));

	json_object_array_add(channelsJ, channelRightJ);
	json_object_array_add(channelsJ, channelLeftJ);
fail:
	return channelsJ;
}


static json_object* halBlueAlsaSCOTransportChannels(const char * transportTypeS) {
	json_object* channelMonoJ;

	json_object* channelsJ = json_object_new_array();
	char * channelMonoS;

	channelMonoJ = json_object_new_object();
	if (asprintf(&channelMonoS, "%s-mono", transportTypeS) == -1)
		goto fail;

	json_object_object_add(channelMonoJ, "uid", json_object_new_string(channelMonoS));
	json_object_object_add(channelMonoJ, "port", json_object_new_int(0));

	json_object_array_add(channelsJ, channelMonoJ);

fail:
	return channelsJ;
}


static json_object* halBlueAlsaPcmPlugParams(const char * interface, const char * addr, const char * transportTypeS) {
	char * pcmplug_paramsS = NULL;
	if (asprintf(&pcmplug_paramsS, "bluealsa:HCI=%s,DEV=%s,PROFILE=%s", interface, addr, transportTypeS) == -1)
		goto fail;
	return json_object_new_string(pcmplug_paramsS);
fail:
	return NULL;
}


static json_object* halBlueAlsaListenCapture(
	const char * listenCaptureS,
	json_object * pcmplugParamsJ,
	json_object * captureParamsJ,
	json_object * sourceJ) {

	json_object * captureJ = json_object_new_object();

	json_object_object_add(captureJ, "uid", json_object_new_string(listenCaptureS));
	json_object_object_add(captureJ, "pcmplug_params", pcmplugParamsJ);
	json_object_object_add(captureJ, "params", captureParamsJ);
	json_object_object_add(captureJ, "source", sourceJ);

	return captureJ;
}

static json_object * halBlueAlsaTalkPlayback(
	const char * talkPlaybackS,
	json_object* pcmplugParamsJ,
	json_object* paramsJ,
	json_object* sinkJ ) {

	json_object * playbackJ = json_object_new_object();

	json_object_object_add(playbackJ, "uid", json_object_new_string(talkPlaybackS));
	json_object_object_add(playbackJ, "pcmplug_params", pcmplugParamsJ);
	json_object_object_add(playbackJ, "params", paramsJ);
	json_object_object_add(playbackJ, "sink", sinkJ);

	return playbackJ;
}

static json_object * halBlueAlsaScoTalkParamsJ(uint32_t sampling) {
	json_object * paramsJ = json_object_new_object();

	json_object_object_add(paramsJ, "rate",   json_object_new_int(sampling));
	json_object_object_add(paramsJ, "format", json_object_new_string(SCO_TALK_FORMAT));
	json_object_object_add(paramsJ, "channels", json_object_new_int(1));
	return paramsJ;
}

static json_object * halBlueAlsaListenParamsJ(uint32_t sampling) {
	json_object * paramsJ = json_object_new_object();

	json_object_object_add(paramsJ, "rate",   json_object_new_int(sampling));
	json_object_object_add(paramsJ, "format", json_object_new_string(A2DP_LISTEN_FORMAT));
	return paramsJ;
}

static json_object * halBlueAlsaScoZone() {
	json_object * zoneJ = json_object_new_object();
	json_object * sinkJ = json_object_new_array();
	json_object_object_add(zoneJ, "uid", json_object_new_string(SCO_TALK_ZONE));

	json_object * channel1J = json_object_new_object();
	json_object_object_add(channel1J, "target" , json_object_new_string(SCO_CHANNEL_MONO));
	json_object_object_add(channel1J, "channel", json_object_new_int(0));
	json_object_object_add(channel1J, "volume", json_object_new_double(0.5));

	json_object * channel2J = json_object_new_object();
	json_object_object_add(channel2J, "target" , json_object_new_string(SCO_CHANNEL_MONO));
	json_object_object_add(channel2J, "channel", json_object_new_int(1));
	json_object_object_add(channel2J, "volume", json_object_new_double(0.5));

	json_object_array_add(sinkJ, channel1J);
	json_object_array_add(sinkJ, channel2J);

	json_object_object_add(zoneJ, "sink", sinkJ);

	return zoneJ;
}

static int halBlueAlsaAttachTransportStreams(bluealsa_transport_t * transport) {
	int ret = -1;
	const char* transportTypeS;

	struct ba_msg_transport * ba_transport = &transport->transport;
	const bluealsa_watch * watch = transport->watch;

	CtlPluginT* plugin = watch->plugin;
	hal_bluealsa_plugin_data_t * pluginData=getPluginContext(plugin);

	json_object* requestJ = NULL;

	json_object* sourceJ = NULL;
	json_object* playbackJ = NULL;

	json_object* pcmplugParamsJ, *pcmplugParamsJ2 = NULL;
	json_object* captureParamsJ;

	json_object* streamsJ = NULL;
	json_object* streamJ = NULL;

	json_object* zonesJ = NULL;

	char * playbackZoneS;
	uint32_t delayms;

	if (ba_transport->type & BA_PCM_TYPE_SCO) {
		transportTypeS = "sco";
		delayms = pluginData->sco.delayms;
		playbackZoneS = pluginData->sco.speaker;
	}
	else if (ba_transport->type & BA_PCM_TYPE_A2DP) {
		transportTypeS = "a2dp";
		delayms = pluginData->a2dp.delayms;
		playbackZoneS = pluginData->a2dp.zone;
	} else {
		AFB_API_ERROR(plugin->api, "%s: unsupported transport type", __func__ );
		goto fail;
	}

	char * captureS = NULL;
	if (asprintf(&captureS, "%s_listen_capture", transportTypeS) == -1)
		goto fail;

	// for SCO only
	char * playbackS = NULL;
	if (asprintf(&playbackS, "%s_talk_playback", transportTypeS) == -1)
		goto fail;

	char * transactionUidS = NULL;
	char * streamS = NULL;

	char addr[18];
	ba2str(&ba_transport->addr, addr);

	requestJ = json_object_new_object();
	if (!requestJ)
		goto fail;

	if (asprintf(&transactionUidS, "%s_%s", transportTypeS, addr) == -1)
		goto fail;

	json_object_object_add(requestJ, "uid", json_object_new_string(transactionUidS));

	pcmplugParamsJ = halBlueAlsaPcmPlugParams(watch->interface, addr, transportTypeS);

	sourceJ = json_object_new_object();
	if (ba_transport->type & BA_PCM_TYPE_A2DP)
		json_object_object_add(sourceJ, "channels", halBlueAlsaA2DPTransportChannels(transportTypeS));
	else
		json_object_object_add(sourceJ, "channels", halBlueAlsaSCOTransportChannels(transportTypeS));

	captureParamsJ = halBlueAlsaListenParamsJ(ba_transport->sampling);

	json_object_object_add(requestJ, "captures", halBlueAlsaListenCapture(captureS, pcmplugParamsJ, captureParamsJ, sourceJ));

	if (ba_transport->type & BA_PCM_TYPE_SCO) {
		playbackJ = json_object_new_object();
		// that is a shame that deep copy in not available in the current json-c version
		pcmplugParamsJ2 = halBlueAlsaPcmPlugParams(watch->interface, addr, transportTypeS);
		json_object * paramsJ = halBlueAlsaScoTalkParamsJ(ba_transport->sampling);

		json_object_object_add(playbackJ, "channels", halBlueAlsaSCOTransportChannels(transportTypeS));
		json_object_object_add(requestJ, "playbacks", halBlueAlsaTalkPlayback(playbackS, pcmplugParamsJ2, paramsJ, playbackJ));

	}

	/* ZONES */

	if (ba_transport->type & BA_PCM_TYPE_SCO) {
		zonesJ = json_object_new_array();
		json_object_array_add(zonesJ, halBlueAlsaScoZone());
		json_object_object_add(requestJ, "zones", zonesJ);
	}

	/* STREAMS */

	/* Build the array of streams */
	streamsJ = json_object_new_array();

	streamJ = json_object_new_object();
	if (asprintf(&streamS, "%s_listen_stream", transportTypeS) == -1)
		goto fail;

	json_object_object_add(streamJ, "uid", json_object_new_string(streamS));
	json_object_object_add(streamJ, "source", json_object_new_string(captureS));
	json_object_object_add(streamJ, "zone", json_object_new_string(playbackZoneS));
	if (delayms) {
		json_object_object_add(streamJ, "delayms", json_object_new_int(delayms));
	}

	json_object_array_add(streamsJ, streamJ);

	/* In case of SCO, to have full-duplex, we instantiate a stream for 'talk' */
	if (ba_transport->type & BA_PCM_TYPE_SCO ) {

		streamJ = json_object_new_object();
		if (asprintf(&streamS, "%s_talk_stream", transportTypeS) == -1)
			goto fail;

		json_object_object_add(streamJ, "uid", json_object_new_string(streamS));
		json_object_object_add(streamJ, "source", json_object_new_string(pluginData->sco.mic));
		json_object_object_add(streamJ, "zone", json_object_new_string(SCO_TALK_ZONE));
		if (delayms)
			json_object_object_add(streamJ, "delayms", json_object_new_int(delayms));
		json_object_array_add(streamsJ, streamJ);
	}

	json_object_object_add(requestJ, "streams", streamsJ);

	/* In softmixer, this will create a transaction verb (whose name is transactionUidS),
	 * will be used later to destroy all the created objects upon transport removal */

	/* a call sync is forbidden, because we in the context of an event io */
	afb_api_call(plugin->api, SMIXER_API_NAME, "attach", requestJ, NULL, NULL);

	transport->transactionUidS = transactionUidS;

	ret = 0;
	goto done;

fail:
	return -1;
done:
	AFB_API_DEBUG(plugin->api, "DONE.");
	return ret;
}


static int halBluezAlsaRemoveTransportStream(bluealsa_transport_t * transport) {

	CtlPluginT * plugin = transport->watch->plugin;
	json_object* requestJ = NULL;

	AFB_API_INFO(plugin->api, "Call transaction detach verb %s", transport->transactionUidS);
	if (!transport->transactionUidS)
		goto fail;

	requestJ = json_object_new_object();
	if (!requestJ)
		goto fail;

	json_object_object_add(requestJ, "action", json_object_new_string("remove"));

	/* a call sync is forbidden, because we in the context of an event io */
	afb_api_call(plugin->api, SMIXER_API_NAME, transport->transactionUidS, requestJ, NULL, NULL);
	return 0;

fail:
	return -1;
}

static int halBlueAlsaFetchTransports(bluealsa_watch * watch) {
	ssize_t nbTransports;
	struct ba_msg_transport *transports;
	CtlPluginT * plugin = watch->plugin;

	hal_bluealsa_plugin_data_t * pluginData = (hal_bluealsa_plugin_data_t*)getPluginContext(plugin);
	bluealsa_transport_t * transport_list = &pluginData->transport_list;
	bluealsa_transport_t * transport = NULL;

	AFB_API_DEBUG(plugin->api, "Fetching available transports of interface %s", watch->interface);

	nbTransports = bluealsa_get_transports(watch->fd, &transports);
	if (nbTransports == -1) {
		AFB_API_ERROR(plugin->api, "Couldn't get transports: %s", strerror(errno));
		goto done;
	}

	AFB_API_DEBUG(plugin->api, "Got %zu transport(s)", nbTransports);

	for (int ix=0; ix<nbTransports; ix++) {
		char addr[18];
		struct ba_msg_transport * ba_transport = &transports[ix];
		ba2str(&ba_transport->addr, addr);
		const char * typeS;
		if (ba_transport->type & BA_PCM_TYPE_SCO)
			typeS = "sco";
		else if (ba_transport->type & BA_PCM_TYPE_A2DP)
			typeS = "a2dp";
		else
			typeS = "unknown";

		AFB_API_DEBUG(plugin->api, "Transport %d: type %s (%x), dev %s, codec %d", ix, typeS, ba_transport->type, addr, ba_transport->codec);

		if (BA_PCM_TYPE(ba_transport->type) == BA_PCM_TYPE_SCO &&
			ba_transport->codec == 0) {
			AFB_API_DEBUG(plugin->api, "Transport has no codec, skip it");
			continue;
		}

		if (halBlueAlsaTransportFind(watch, transport_list, ba_transport)) {
			AFB_API_DEBUG(plugin->api, "This transport is already streamed");
			continue;
		}

		AFB_API_INFO(plugin->api, "Registering transport type %s, dev %s", typeS, addr);
		transport = halBlueAlsaTransportsAdd(watch, transport_list, ba_transport);
		if (!transport) {
			AFB_API_ERROR(plugin->api, "Failed to register this transport");
			goto done;
		}

		// Do the softmixer stuff
		if (halBlueAlsaAttachTransportStreams(transport) != 0) {
			AFB_API_ERROR(plugin->api, "Failed create transport streams");
			goto done;
		}

		AFB_API_DEBUG(plugin->api, "%s: transaction id %s", __func__, transport->transactionUidS);

	}

	halBlueAlsaTransportUpdate(watch, transport_list, transports, nbTransports , halBluezAlsaRemoveTransportStream);

done:
	free(transports);
	return 0;
}


static int halBlueAlsaUnregister(CtlPluginT * plugin, const char * dbus_name) {
	hal_bluealsa_plugin_data_t * pluginData = getPluginContext(plugin);

	const char * interface = dbus_name + strlen(BLUEZALSA_DBUS_NAME_PREFIX) + 1;

	bluealsa_transport_t * transport_list = &pluginData->transport_list;

	const bluealsa_watch * watcher = NULL;

	bluealsa_transport_t * tmp, *sav;
	cds_list_for_each_entry_safe(tmp, sav, &transport_list->list, list) {

		const bluealsa_watch * watch = tmp->watch;
		if (!watch)
			continue;

		if (strcmp(watch->interface, interface) != 0)
			continue;

		watcher = watch;

		cds_list_del(&tmp->list);
		halBluezAlsaRemoveTransportStream(tmp);

		free(tmp->transactionUidS);
		free(tmp);
	}

	if (watcher) {
		close(watcher->fd);
		free((char*)watcher->interface);
		free((bluealsa_watch*)watcher);
	}

	return 0;
}

static int halBlueAlsaRegister(CtlPluginT* plugin, const char * dbus_name) {
	int ret;
	sd_event * sdLoop;
	sd_event_source * evtsrc;

	const char * interface = dbus_name + strlen(BLUEZALSA_DBUS_NAME_PREFIX) +1 ;

	sdLoop = afb_api_get_event_loop(plugin->api);

	enum ba_event transport_mask = BA_EVENT_TRANSPORT_ADDED |
				       BA_EVENT_TRANSPORT_CHANGED |
				       BA_EVENT_TRANSPORT_REMOVED;

	bluealsa_watch * watch = (bluealsa_watch *)malloc(sizeof(bluealsa_watch));
	if (!watch)
		goto fail;

	watch->interface = strdup(interface);
	if (!watch->interface) {
		AFB_API_ERROR(plugin->api, "%s: Insufficient memory", __func__);
		goto fail;
	}
	watch->plugin = plugin;

	watch->fd = bluealsa_open(interface);
	if (watch->fd == -1) {
		AFB_API_ERROR(plugin->api, "BlueALSA connection failed: %s", strerror(errno));
		goto fail;
	}

	halBlueAlsaFetchTransports(watch);

	if (bluealsa_event_subscribe(watch->fd, transport_mask) == -1) {
		AFB_API_ERROR(plugin->api, "BlueALSA subscription failed: %s", strerror(errno));
		goto fail;
	}

	// Register transport change event to the main loop of the binder
	ret = sd_event_add_io(sdLoop, &evtsrc, watch->fd, EPOLLIN, halBlueAlsaTransportEventCB, watch);
	if (ret < 0) {
		AFB_API_ERROR(plugin->api,
			      "%s: Failed to register event fd to io loop",
			      __func__);
		goto fail;
	}

	return 0;

fail:
	if (watch->interface)
		free((char*)watch->interface);
	if (watch->fd)
		close(watch->fd);
	if (watch)
		free(watch);

	return -1;
}

/* Notification callback for DBus "NameOwnerChanged event.
 * Notice that since the dbus name of bluez-alsa daemon has a prefix
 * for the hci interface,
 * (eg, org.bluez-alsa.hci0, org.bluez-alsa.hci1 .., it is not possible
 * to set a match rule when registering the callback, so we have to perform
 * the filtering for the service name within the callback instead */

static int name_changed_cb(sd_bus_message *m, void *userdata, sd_bus_error * ret_error) {

	CtlPluginT * plugin = (CtlPluginT*) userdata;

	const char * signature = sd_bus_message_get_signature(m, 1);

	/* simply ignore silently that spurious message */
	if (strcmp(signature,"s") == 0) {
		goto done;
	}

	if (strcmp(signature,"sss") != 0) {
		AFB_API_ERROR(plugin->api, "%s: wrong message signature '%s'", __func__, signature);
		goto done;
	}

	const char *name;
	const char *owner_old;
	const char *owner_new;

	if (sd_bus_message_read(m, "sss", &name, &owner_old, &owner_new) < 0) {
		AFB_API_ERROR(plugin->api, "%s: failed to read message parameters", __func__);
		goto done;
	}

	if (!strstr(name, BLUEZALSA_DBUS_NAME_PREFIX))
		goto done;

	if (owner_old != NULL && owner_old[0] != '\0') {
		AFB_API_INFO(plugin->api, "bluez-alsa: %s disappeared", name);
		halBlueAlsaUnregister(plugin, name);
	}

	if (owner_new != NULL && owner_new[0] != '\0') {
		AFB_API_INFO(plugin->api, "bluez-alsa %s appeared", name);
		halBlueAlsaRegister(plugin, name);
	}

done:
	return 0;
}

/*
 * Callback for the DBus "ListNames" call.
 * This will list all the available services on DBus (or more precisely,
 * the ones that have registered a name, because it is not mandatory)
 * The "ListNames" is done at startup to get the initial list of available
 * bluez-alsa daemons to connect to.
 * After, we use the NameOwnerChanged signals for the notification of new
 * instances of bluez-alsa, or when it disappears */

static int sd_bus_list_message_handler (sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {

	const char * signature = sd_bus_message_get_signature(m, 1);
	int ret;

	CtlPluginT * plugin = (CtlPluginT*) userdata;

	if (strcmp(signature,"as") != 0) {
		AFB_API_ERROR(plugin->api, "%s: wrong message signature: '%s'", __func__, signature);
		goto done;
	}

	const char * string = NULL;

	ret = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "s");
	if (ret < 0) {
		AFB_API_ERROR(plugin->api, "failed to enter container, err=%s", strerror(-ret));
		goto failed;
	}

	for (;;) {
		ret = sd_bus_message_read(m, "s", &string);
		if (ret < 0) {
			AFB_API_ERROR(plugin->api, "%s: failed to read string", __func__);
			goto done;
		}

		if (!ret)
			break;

		if (strstr(string, BLUEZALSA_DBUS_NAME_PREFIX) != 0) {
			halBlueAlsaRegister(plugin, string);
		}
	}

done:
	sd_bus_message_exit_container(m);

failed:
	return 0;
}


static int halBlueAlsaRegisterAll(CtlPluginT* plugin) {
	sd_bus_slot * slot = NULL;
	struct sd_bus *bus = afb_api_get_system_bus(plugin->api);
	int ret;

	if (!bus) {
		AFB_API_ERROR(plugin->api, "%s: Unable to get a DBus connection", __func__);
		goto failed;
	}

	AFB_API_INFO(plugin->api, "Ask DBus for the list of services");

	ret = sd_bus_call_method_async(bus,
				       &slot,
				       ORG_FREEDESKTOP_DBUS_NAME,
				       ORG_FREEDESKTOP_DBUS_PATH,
				       ORG_FREEDESKTOP_DBUS_INTERFACE,
				       "ListNames",
				       sd_bus_list_message_handler,
				       plugin,
				       NULL);
	if (ret < 0) {
		AFB_API_ERROR(plugin->api, "Unable to request service list from DBus: %s", strerror(-ret));
		goto failed;
	}

	char * match = "type='signal',sender='" ORG_FREEDESKTOP_DBUS_NAME "', interface='" ORG_FREEDESKTOP_DBUS_INTERFACE "'";

	if (sd_bus_add_match(bus, &slot, match, name_changed_cb, plugin) < 0) {
		AFB_API_ERROR(plugin->api, "Unable to add the dbus signal match rule");
		goto failed;
	}

	return 0;
failed:
	return -1;
}