/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2011 GSyC/LibreSoft, Universidad Rey Juan Carlos.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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 <stdbool.h>
#include <errno.h>

#include <gdbus/gdbus.h>

#include "lib/uuid.h"
#include "plugin.h"
#include "dbus-common.h"
#include "adapter.h"
#include "device.h"
#include "profile.h"
#include "service.h"
#include "error.h"
#include "log.h"
#include "attrib/gattrib.h"
#include "attio.h"
#include "attrib/att.h"
#include "attrib/gatt.h"

#define THERMOMETER_INTERFACE		"org.bluez.Thermometer1"
#define THERMOMETER_MANAGER_INTERFACE	"org.bluez.ThermometerManager1"
#define THERMOMETER_WATCHER_INTERFACE	"org.bluez.ThermometerWatcher1"

/* Temperature measurement flag fields */
#define TEMP_UNITS		0x01
#define TEMP_TIME_STAMP		0x02
#define TEMP_TYPE		0x04

#define FLOAT_MAX_MANTISSA	16777216 /* 2^24 */

#define VALID_RANGE_DESC_SIZE	4
#define TEMPERATURE_TYPE_SIZE	1
#define MEASUREMENT_INTERVAL_SIZE	2

struct thermometer_adapter {
	struct btd_adapter	*adapter;
	GSList			*devices;
	GSList			*fwatchers;	/* Final measurements */
	GSList			*iwatchers;	/* Intermediate measurements */
};

struct thermometer {
	struct btd_device		*dev;		/* Device reference */
	struct thermometer_adapter	*tadapter;
	GAttrib				*attrib;	/* GATT connection */
	struct att_range		*svc_range;	/* Thermometer range */
	guint				attioid;	/* Att watcher id */
	/* attio id for Temperature Measurement value indications */
	guint				attio_measurement_id;
	/* attio id for Intermediate Temperature value notifications */
	guint				attio_intermediate_id;
	/* attio id for Measurement Interval value indications */
	guint				attio_interval_id;
	gboolean			intermediate;
	uint8_t				type;
	uint16_t			interval;
	uint16_t			max;
	uint16_t			min;
	gboolean			has_type;
	gboolean			has_interval;

	uint16_t			measurement_ccc_handle;
	uint16_t			intermediate_ccc_handle;
	uint16_t			interval_val_handle;
};

struct characteristic {
	struct thermometer	*t;	/* Thermometer where the char belongs */
	char			uuid[MAX_LEN_UUID_STR + 1];
};

struct watcher {
	struct thermometer_adapter	*tadapter;
	guint				id;
	char				*srv;
	char				*path;
};

struct measurement {
	struct thermometer	*t;
	int16_t			exp;
	int32_t			mant;
	uint64_t		time;
	gboolean		suptime;
	char			*unit;
	char			*type;
	char			*value;
};

struct tmp_interval_data {
	struct thermometer	*thermometer;
	uint16_t		interval;
};

static GSList *thermometer_adapters = NULL;

static const char * const temp_type[] = {
	"<reserved>",
	"armpit",
	"body",
	"ear",
	"finger",
	"intestines",
	"mouth",
	"rectum",
	"toe",
	"tympanum"
};

static const char *temptype2str(uint8_t value)
{
	 if (value > 0 && value < G_N_ELEMENTS(temp_type))
		return temp_type[value];

	error("Temperature type %d reserved for future use", value);
	return NULL;
}

static void destroy_watcher(gpointer user_data)
{
	struct watcher *watcher = user_data;

	g_free(watcher->path);
	g_free(watcher->srv);
	g_free(watcher);
}

static void remove_watcher(gpointer user_data)
{
	struct watcher *watcher = user_data;

	g_dbus_remove_watch(btd_get_dbus_connection(), watcher->id);
}

static void destroy_thermometer(gpointer user_data)
{
	struct thermometer *t = user_data;

	if (t->attioid > 0)
		btd_device_remove_attio_callback(t->dev, t->attioid);

	if (t->attrib != NULL) {
		g_attrib_unregister(t->attrib, t->attio_measurement_id);
		g_attrib_unregister(t->attrib, t->attio_intermediate_id);
		g_attrib_unregister(t->attrib, t->attio_interval_id);
		g_attrib_unref(t->attrib);
	}

	btd_device_unref(t->dev);
	g_free(t->svc_range);
	g_free(t);
}

static void destroy_thermometer_adapter(gpointer user_data)
{
	struct thermometer_adapter *tadapter = user_data;

	if (tadapter->devices != NULL)
		g_slist_free_full(tadapter->devices, destroy_thermometer);

	if (tadapter->fwatchers != NULL)
		g_slist_free_full(tadapter->fwatchers, remove_watcher);

	g_free(tadapter);
}

static int cmp_adapter(gconstpointer a, gconstpointer b)
{
	const struct thermometer_adapter *tadapter = a;
	const struct btd_adapter *adapter = b;

	if (adapter == tadapter->adapter)
		return 0;

	return -1;
}

static int cmp_device(gconstpointer a, gconstpointer b)
{
	const struct thermometer *t = a;
	const struct btd_device *dev = b;

	if (dev == t->dev)
		return 0;

	return -1;
}

static int cmp_watcher(gconstpointer a, gconstpointer b)
{
	const struct watcher *watcher = a;
	const struct watcher *match = b;
	int ret;

	ret = g_strcmp0(watcher->srv, match->srv);
	if (ret != 0)
		return ret;

	return g_strcmp0(watcher->path, match->path);
}

static struct thermometer_adapter *
find_thermometer_adapter(struct btd_adapter *adapter)
{
	GSList *l = g_slist_find_custom(thermometer_adapters, adapter,
								cmp_adapter);
	if (!l)
		return NULL;

	return l->data;
}

static void change_property(struct thermometer *t, const char *name,
							gpointer value) {
	if (g_strcmp0(name, "Intermediate") == 0) {
		gboolean *intermediate = value;
		if (t->intermediate == *intermediate)
			return;

		t->intermediate = *intermediate;
	} else if (g_strcmp0(name, "Interval") == 0) {
		uint16_t *interval = value;
		if (t->has_interval && t->interval == *interval)
			return;

		t->has_interval = TRUE;
		t->interval = *interval;
	} else if (g_strcmp0(name, "Maximum") == 0) {
		uint16_t *max = value;
		if (t->max == *max)
			return;

		t->max = *max;
	} else if (g_strcmp0(name, "Minimum") == 0) {
		uint16_t *min = value;
		if (t->min == *min)
			return;

		t->min = *min;
	} else {
		DBG("%s is not a thermometer property", name);
		return;
	}

	g_dbus_emit_property_changed(btd_get_dbus_connection(),
						device_get_path(t->dev),
						THERMOMETER_INTERFACE, name);
}

static void update_watcher(gpointer data, gpointer user_data)
{
	struct watcher *w = data;
	struct measurement *m = user_data;
	const char *path = device_get_path(m->t->dev);
	DBusMessageIter iter;
	DBusMessageIter dict;
	DBusMessage *msg;

	msg = dbus_message_new_method_call(w->srv, w->path,
				THERMOMETER_WATCHER_INTERFACE,
				"MeasurementReceived");
	if (msg == NULL)
		return;

	dbus_message_iter_init_append(msg, &iter);

	dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH , &path);

	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);

	dict_append_entry(&dict, "Exponent", DBUS_TYPE_INT16, &m->exp);
	dict_append_entry(&dict, "Mantissa", DBUS_TYPE_INT32, &m->mant);
	dict_append_entry(&dict, "Unit", DBUS_TYPE_STRING, &m->unit);

	if (m->suptime)
		dict_append_entry(&dict, "Time", DBUS_TYPE_UINT64, &m->time);

	dict_append_entry(&dict, "Type", DBUS_TYPE_STRING, &m->type);
	dict_append_entry(&dict, "Measurement", DBUS_TYPE_STRING, &m->value);

	dbus_message_iter_close_container(&iter, &dict);

	dbus_message_set_no_reply(msg, TRUE);
	g_dbus_send_message(btd_get_dbus_connection(), msg);
}

static void recv_measurement(struct thermometer *t, struct measurement *m)
{
	GSList *wlist;

	m->t = t;

	if (g_strcmp0(m->value, "intermediate") == 0)
		wlist = t->tadapter->iwatchers;
	else
		wlist = t->tadapter->fwatchers;

	g_slist_foreach(wlist, update_watcher, m);
}

static void proc_measurement(struct thermometer *t, const uint8_t *pdu,
						uint16_t len, gboolean final)
{
	struct measurement m;
	const char *type = NULL;
	uint8_t flags;
	uint32_t raw;

	/* skip opcode and handle */
	pdu += 3;
	len -= 3;

	if (len < 1) {
		DBG("Mandatory flags are not provided");
		return;
	}

	memset(&m, 0, sizeof(m));

	flags = *pdu;

	if (flags & TEMP_UNITS)
		m.unit = "fahrenheit";
	else
		m.unit = "celsius";

	pdu++;
	len--;

	if (len < 4) {
		DBG("Mandatory temperature measurement value is not provided");
		return;
	}

	raw = att_get_u32(pdu);
	m.mant = raw & 0x00FFFFFF;
	m.exp = ((int32_t) raw) >> 24;

	if (m.mant & 0x00800000) {
		/* convert to C2 negative value */
		m.mant = m.mant - FLOAT_MAX_MANTISSA;
	}

	pdu += 4;
	len -= 4;

	if (flags & TEMP_TIME_STAMP) {
		struct tm ts;
		time_t time;

		if (len < 7) {
			DBG("Time stamp is not provided");
			return;
		}

		ts.tm_year = att_get_u16(pdu) - 1900;
		ts.tm_mon = *(pdu + 2) - 1;
		ts.tm_mday = *(pdu + 3);
		ts.tm_hour = *(pdu + 4);
		ts.tm_min = *(pdu + 5);
		ts.tm_sec = *(pdu + 6);
		ts.tm_isdst = -1;

		time = mktime(&ts);
		m.time = (uint64_t) time;
		m.suptime = TRUE;

		pdu += 7;
		len -= 7;
	}

	if (flags & TEMP_TYPE) {
		if (len < 1) {
			DBG("Temperature type is not provided");
			return;
		}

		type = temptype2str(*pdu);
	} else if (t->has_type) {
		type = temptype2str(t->type);
	}

	m.type = g_strdup(type);
	m.value = final ? "final" : "intermediate";

	recv_measurement(t, &m);
	g_free(m.type);
}


static void measurement_ind_handler(const uint8_t *pdu, uint16_t len,
							gpointer user_data)
{
	struct thermometer *t = user_data;
	uint8_t *opdu;
	uint16_t olen;
	size_t plen;

	if (len < 3) {
		DBG("Bad pdu received");
		return;
	}

	proc_measurement(t, pdu, len, TRUE);

	opdu = g_attrib_get_buffer(t->attrib, &plen);
	olen = enc_confirmation(opdu, plen);

	if (olen > 0)
		g_attrib_send(t->attrib, 0, opdu, olen, NULL, NULL, NULL);
}

static void intermediate_notify_handler(const uint8_t *pdu, uint16_t len,
							gpointer user_data)
{
	struct thermometer *t = user_data;

	if (len < 3) {
		DBG("Bad pdu received");
		return;
	}

	proc_measurement(t, pdu, len, FALSE);
}

static void interval_ind_handler(const uint8_t *pdu, uint16_t len,
							gpointer user_data)
{
	struct thermometer *t = user_data;
	uint16_t interval;
	uint8_t *opdu;
	uint16_t olen;
	size_t plen;

	if (len < 5) {
		DBG("Bad pdu received");
		return;
	}

	interval = att_get_u16(pdu + 3);
	change_property(t, "Interval", &interval);

	opdu = g_attrib_get_buffer(t->attrib, &plen);
	olen = enc_confirmation(opdu, plen);

	if (olen > 0)
		g_attrib_send(t->attrib, 0, opdu, olen, NULL, NULL, NULL);
}

static void valid_range_desc_cb(guint8 status, const guint8 *pdu, guint16 len,
							gpointer user_data)
{
	struct thermometer *t = user_data;
	uint8_t value[VALID_RANGE_DESC_SIZE];
	uint16_t max, min;
	ssize_t vlen;

	if (status != 0) {
		DBG("Valid Range descriptor read failed: %s",
							att_ecode2str(status));
		return;
	}

	vlen = dec_read_resp(pdu, len, value, sizeof(value));
	if (vlen < 0) {
		DBG("Protocol error\n");
		return;
	}

	if (vlen < 4) {
		DBG("Invalid range received");
		return;
	}

	min = att_get_u16(&value[0]);
	max = att_get_u16(&value[2]);

	if (min == 0 || min > max) {
		DBG("Invalid range");
		return;
	}

	change_property(t, "Maximum", &max);
	change_property(t, "Minimum", &min);
}

static void write_ccc_cb(guint8 status, const guint8 *pdu,
						guint16 len, gpointer user_data)
{
	char *msg = user_data;

	if (status != 0)
		error("%s failed", msg);

	g_free(msg);
}

static void process_thermometer_desc(struct characteristic *ch, uint16_t uuid,
								uint16_t handle)
{
	uint8_t atval[2];
	uint16_t val;
	char *msg;

	if (uuid == GATT_CHARAC_VALID_RANGE_UUID) {
		if (g_strcmp0(ch->uuid, MEASUREMENT_INTERVAL_UUID) == 0)
			gatt_read_char(ch->t->attrib, handle,
						valid_range_desc_cb, ch->t);
		return;
	}

	if (uuid != GATT_CLIENT_CHARAC_CFG_UUID)
		return;

	if (g_strcmp0(ch->uuid, TEMPERATURE_MEASUREMENT_UUID) == 0) {
		ch->t->measurement_ccc_handle = handle;

		if (g_slist_length(ch->t->tadapter->fwatchers) == 0) {
			val = 0x0000;
			msg = g_strdup("Disable Temperature Measurement ind");
		} else {
			val = GATT_CLIENT_CHARAC_CFG_IND_BIT;
			msg = g_strdup("Enable Temperature Measurement ind");
		}
	} else if (g_strcmp0(ch->uuid, INTERMEDIATE_TEMPERATURE_UUID) == 0) {
		ch->t->intermediate_ccc_handle = handle;

		if (g_slist_length(ch->t->tadapter->iwatchers) == 0) {
			val = 0x0000;
			msg = g_strdup("Disable Intermediate Temperature noti");
		} else {
			val = GATT_CLIENT_CHARAC_CFG_NOTIF_BIT;
			msg = g_strdup("Enable Intermediate Temperature noti");
		}
	} else if (g_strcmp0(ch->uuid, MEASUREMENT_INTERVAL_UUID) == 0) {
		val = GATT_CLIENT_CHARAC_CFG_IND_BIT;
		msg = g_strdup("Enable Measurement Interval indication");
	} else {
		return;
	}

	att_put_u16(val, atval);
	gatt_write_char(ch->t->attrib, handle, atval, sizeof(atval),
							write_ccc_cb, msg);
}

static void discover_desc_cb(guint8 status, const guint8 *pdu, guint16 len,
							gpointer user_data)
{
	struct characteristic *ch = user_data;
	struct att_data_list *list = NULL;
	uint8_t format;
	int i;

	if (status != 0) {
		error("Discover all characteristic descriptors failed [%s]: %s",
					ch->uuid, att_ecode2str(status));
		goto done;
	}

	list = dec_find_info_resp(pdu, len, &format);
	if (list == NULL)
		goto done;

	if (format != ATT_FIND_INFO_RESP_FMT_16BIT)
		goto done;

	for (i = 0; i < list->num; i++) {
		uint8_t *value;
		uint16_t handle, uuid;

		value = list->data[i];
		handle = att_get_u16(value);
		uuid = att_get_u16(value + 2);

		process_thermometer_desc(ch, uuid, handle);
	}

done:
	if (list != NULL)
		att_data_list_free(list);
	g_free(ch);
}

static void discover_desc(struct thermometer *t, struct gatt_char *c,
						struct gatt_char *c_next)
{
	struct characteristic *ch;
	uint16_t start, end;

	start = c->value_handle + 1;

	if (c_next != NULL) {
		if (start == c_next->handle)
			return;
		end = c_next->handle - 1;
	} else if (c->value_handle != t->svc_range->end) {
		end = t->svc_range->end;
	} else {
		return;
	}

	ch = g_new0(struct characteristic, 1);
	ch->t = t;
	memcpy(ch->uuid, c->uuid, sizeof(c->uuid));

	gatt_find_info(t->attrib, start, end, discover_desc_cb, ch);
}

static void read_temp_type_cb(guint8 status, const guint8 *pdu, guint16 len,
							gpointer user_data)
{
	struct thermometer *t = user_data;
	uint8_t value[TEMPERATURE_TYPE_SIZE];
	ssize_t vlen;

	if (status != 0) {
		DBG("Temperature Type value read failed: %s",
							att_ecode2str(status));
		return;
	}

	vlen = dec_read_resp(pdu, len, value, sizeof(value));
	if (vlen < 0) {
		DBG("Protocol error.");
		return;
	}

	if (vlen != 1) {
		DBG("Invalid length for Temperature type");
		return;
	}

	t->has_type = TRUE;
	t->type = value[0];
}

static void read_interval_cb(guint8 status, const guint8 *pdu, guint16 len,
							gpointer user_data)
{
	struct thermometer *t = user_data;
	uint8_t value[MEASUREMENT_INTERVAL_SIZE];
	uint16_t interval;
	ssize_t vlen;

	if (status != 0) {
		DBG("Measurement Interval value read failed: %s",
							att_ecode2str(status));
		return;
	}

	vlen = dec_read_resp(pdu, len, value, sizeof(value));
	if (vlen < 0) {
		DBG("Protocol error\n");
		return;
	}

	if (vlen < 2) {
		DBG("Invalid Interval received");
		return;
	}

	interval = att_get_u16(&value[0]);
	change_property(t, "Interval", &interval);
}

static void process_thermometer_char(struct thermometer *t,
				struct gatt_char *c, struct gatt_char *c_next)
{
	if (g_strcmp0(c->uuid, INTERMEDIATE_TEMPERATURE_UUID) == 0) {
		gboolean intermediate = TRUE;
		change_property(t, "Intermediate", &intermediate);

		t->attio_intermediate_id = g_attrib_register(t->attrib,
					ATT_OP_HANDLE_NOTIFY, c->value_handle,
					intermediate_notify_handler, t, NULL);

		discover_desc(t, c, c_next);
	} else if (g_strcmp0(c->uuid, TEMPERATURE_MEASUREMENT_UUID) == 0) {

		t->attio_measurement_id = g_attrib_register(t->attrib,
					ATT_OP_HANDLE_IND, c->value_handle,
					measurement_ind_handler, t, NULL);

		discover_desc(t, c, c_next);
	} else if (g_strcmp0(c->uuid, TEMPERATURE_TYPE_UUID) == 0) {
		gatt_read_char(t->attrib, c->value_handle,
							read_temp_type_cb, t);
	} else if (g_strcmp0(c->uuid, MEASUREMENT_INTERVAL_UUID) == 0) {
		bool need_desc = false;

		gatt_read_char(t->attrib, c->value_handle, read_interval_cb, t);

		if (c->properties & ATT_CHAR_PROPER_WRITE) {
			t->interval_val_handle = c->value_handle;
			need_desc = true;
		}

		if (c->properties & ATT_CHAR_PROPER_INDICATE) {
			t->attio_interval_id = g_attrib_register(t->attrib,
					ATT_OP_HANDLE_IND, c->value_handle,
					interval_ind_handler, t, NULL);
			need_desc = true;
		}

		if (need_desc)
			discover_desc(t, c, c_next);
	}
}

static void configure_thermometer_cb(GSList *characteristics, guint8 status,
							gpointer user_data)
{
	struct thermometer *t = user_data;
	GSList *l;

	if (status != 0) {
		error("Discover thermometer characteristics: %s",
							att_ecode2str(status));
		return;
	}

	for (l = characteristics; l; l = l->next) {
		struct gatt_char *c = l->data;
		struct gatt_char *c_next = (l->next ? l->next->data : NULL);

		process_thermometer_char(t, c, c_next);
	}
}

static void write_interval_cb(guint8 status, const guint8 *pdu, guint16 len,
							gpointer user_data)
{
	struct tmp_interval_data *data = user_data;

	if (status != 0) {
		error("Interval Write Request failed %s",
							att_ecode2str(status));
		goto done;
	}

	if (!dec_write_resp(pdu, len)) {
		error("Interval Write Request: protocol error");
		goto done;
	}

	change_property(data->thermometer, "Interval", &data->interval);

done:
	g_free(user_data);
}

static void enable_final_measurement(gpointer data, gpointer user_data)
{
	struct thermometer *t = data;
	uint16_t handle = t->measurement_ccc_handle;
	uint8_t value[2];
	char *msg;

	if (t->attrib == NULL || !handle)
		return;

	att_put_u16(GATT_CLIENT_CHARAC_CFG_IND_BIT, value);
	msg = g_strdup("Enable Temperature Measurement indications");

	gatt_write_char(t->attrib, handle, value, sizeof(value),
							write_ccc_cb, msg);
}

static void enable_intermediate_measurement(gpointer data, gpointer user_data)
{
	struct thermometer *t = data;
	uint16_t handle = t->intermediate_ccc_handle;
	uint8_t value[2];
	char *msg;

	if (t->attrib == NULL || !handle)
		return;

	att_put_u16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value);
	msg = g_strdup("Enable Intermediate Temperature notifications");

	gatt_write_char(t->attrib, handle, value, sizeof(value),
							write_ccc_cb, msg);
}

static void disable_final_measurement(gpointer data, gpointer user_data)
{
	struct thermometer *t = data;
	uint16_t handle = t->measurement_ccc_handle;
	uint8_t value[2];
	char *msg;

	if (t->attrib == NULL || !handle)
		return;

	att_put_u16(0x0000, value);
	msg = g_strdup("Disable Temperature Measurement indications");

	gatt_write_char(t->attrib, handle, value, sizeof(value),
							write_ccc_cb, msg);
}

static void disable_intermediate_measurement(gpointer data, gpointer user_data)
{
	struct thermometer *t = data;
	uint16_t handle = t->intermediate_ccc_handle;
	uint8_t value[2];
	char *msg;

	if (t->attrib == NULL || !handle)
		return;

	att_put_u16(0x0000, value);
	msg = g_strdup("Disable Intermediate Temperature notifications");

	gatt_write_char(t->attrib, handle, value, sizeof(value),
							write_ccc_cb, msg);
}

static void remove_int_watcher(struct thermometer_adapter *tadapter,
							struct watcher *w)
{
	if (!g_slist_find(tadapter->iwatchers, w))
		return;

	tadapter->iwatchers = g_slist_remove(tadapter->iwatchers, w);

	if (g_slist_length(tadapter->iwatchers) == 0)
		g_slist_foreach(tadapter->devices,
					disable_intermediate_measurement, 0);
}

static void watcher_exit(DBusConnection *conn, void *user_data)
{
	struct watcher *watcher = user_data;
	struct thermometer_adapter *tadapter = watcher->tadapter;

	DBG("Thermometer watcher %s disconnected", watcher->path);

	remove_int_watcher(tadapter, watcher);

	tadapter->fwatchers = g_slist_remove(tadapter->fwatchers, watcher);
	g_dbus_remove_watch(btd_get_dbus_connection(), watcher->id);

	if (g_slist_length(tadapter->fwatchers) == 0)
		g_slist_foreach(tadapter->devices,
					disable_final_measurement, 0);
}

static struct watcher *find_watcher(GSList *list, const char *sender,
							const char *path)
{
	struct watcher *match;
	GSList *l;

	match = g_new0(struct watcher, 1);
	match->srv = g_strdup(sender);
	match->path = g_strdup(path);

	l = g_slist_find_custom(list, match, cmp_watcher);
	destroy_watcher(match);

	if (l != NULL)
		return l->data;

	return NULL;
}

static DBusMessage *register_watcher(DBusConnection *conn, DBusMessage *msg,
								void *data)
{
	const char *sender = dbus_message_get_sender(msg);
	struct thermometer_adapter *tadapter = data;
	struct watcher *watcher;
	char *path;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
							DBUS_TYPE_INVALID))
		return btd_error_invalid_args(msg);

	watcher = find_watcher(tadapter->fwatchers, sender, path);
	if (watcher != NULL)
		return btd_error_already_exists(msg);

	DBG("Thermometer watcher %s registered", path);

	watcher = g_new0(struct watcher, 1);
	watcher->srv = g_strdup(sender);
	watcher->path = g_strdup(path);
	watcher->tadapter = tadapter;
	watcher->id = g_dbus_add_disconnect_watch(conn, sender, watcher_exit,
						watcher, destroy_watcher);

	if (g_slist_length(tadapter->fwatchers) == 0)
		g_slist_foreach(tadapter->devices, enable_final_measurement, 0);

	tadapter->fwatchers = g_slist_prepend(tadapter->fwatchers, watcher);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *unregister_watcher(DBusConnection *conn, DBusMessage *msg,
								void *data)
{
	const char *sender = dbus_message_get_sender(msg);
	struct thermometer_adapter *tadapter = data;
	struct watcher *watcher;
	char *path;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
							DBUS_TYPE_INVALID))
		return btd_error_invalid_args(msg);

	watcher = find_watcher(tadapter->fwatchers, sender, path);
	if (watcher == NULL)
		return btd_error_does_not_exist(msg);

	DBG("Thermometer watcher %s unregistered", path);

	remove_int_watcher(tadapter, watcher);

	tadapter->fwatchers = g_slist_remove(tadapter->fwatchers, watcher);
	g_dbus_remove_watch(btd_get_dbus_connection(), watcher->id);

	if (g_slist_length(tadapter->fwatchers) == 0)
		g_slist_foreach(tadapter->devices,
					disable_final_measurement, 0);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *enable_intermediate(DBusConnection *conn, DBusMessage *msg,
								void *data)
{
	const char *sender = dbus_message_get_sender(msg);
	struct thermometer_adapter *ta = data;
	struct watcher *watcher;
	char *path;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
							DBUS_TYPE_INVALID))
		return btd_error_invalid_args(msg);

	watcher = find_watcher(ta->fwatchers, sender, path);
	if (watcher == NULL)
		return btd_error_does_not_exist(msg);

	if (find_watcher(ta->iwatchers, sender, path))
		return btd_error_already_exists(msg);

	DBG("Intermediate measurement watcher %s registered", path);

	if (g_slist_length(ta->iwatchers) == 0)
		g_slist_foreach(ta->devices,
					enable_intermediate_measurement, 0);

	ta->iwatchers = g_slist_prepend(ta->iwatchers, watcher);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *disable_intermediate(DBusConnection *conn, DBusMessage *msg,
								void *data)
{
	const char *sender = dbus_message_get_sender(msg);
	struct thermometer_adapter *ta = data;
	struct watcher *watcher;
	char *path;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
							DBUS_TYPE_INVALID))
		return btd_error_invalid_args(msg);

	watcher = find_watcher(ta->iwatchers, sender, path);
	if (watcher == NULL)
		return btd_error_does_not_exist(msg);

	DBG("Intermediate measurement %s unregistered", path);

	remove_int_watcher(ta, watcher);

	return dbus_message_new_method_return(msg);
}

static gboolean property_get_intermediate(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct thermometer *t = data;
	dbus_bool_t val;

	val = !!t->intermediate;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val);

	return TRUE;
}

static gboolean property_get_interval(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct thermometer *t = data;

	if (!t->has_interval)
		return FALSE;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &t->interval);

	return TRUE;
}

static void property_set_interval(const GDBusPropertyTable *property,
					DBusMessageIter *iter,
					GDBusPendingPropertySet id, void *data)
{
	struct thermometer *t = data;
	struct tmp_interval_data *interval_data;
	uint16_t val;
	uint8_t atval[2];

	if (t->interval_val_handle == 0) {
		g_dbus_pending_property_error(id,
					ERROR_INTERFACE ".NotSupported",
					"Operation is not supported");
		return;
	}

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) {
		g_dbus_pending_property_error(id,
					ERROR_INTERFACE ".InvalidArguments",
					"Invalid arguments in method call");
		return;
	}

	dbus_message_iter_get_basic(iter, &val);

	if (val < t->min || val > t->max) {
		g_dbus_pending_property_error(id,
					ERROR_INTERFACE ".InvalidArguments",
					"Invalid arguments in method call");
		return;
	}

	att_put_u16(val, &atval[0]);

	interval_data = g_new0(struct tmp_interval_data, 1);
	interval_data->thermometer = t;
	interval_data->interval = val;
	gatt_write_char(t->attrib, t->interval_val_handle, atval, sizeof(atval),
					write_interval_cb, interval_data);

	g_dbus_pending_property_success(id);
}

static gboolean property_exists_interval(const GDBusPropertyTable *property,
								void *data)
{
	struct thermometer *t = data;

	return t->has_interval;
}

static gboolean property_get_maximum(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct thermometer *t = data;

	if (!t->has_interval)
		return FALSE;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &t->max);

	return TRUE;
}

static gboolean property_get_minimum(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct thermometer *t = data;

	if (!t->has_interval)
		return FALSE;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &t->min);

	return TRUE;
}

static const GDBusPropertyTable thermometer_properties[] = {
	{ "Intermediate", "b", property_get_intermediate },
	{ "Interval", "q", property_get_interval, property_set_interval,
						property_exists_interval },
	{ "Maximum", "q", property_get_maximum, NULL,
						property_exists_interval },
	{ "Minimum", "q", property_get_minimum, NULL,
						property_exists_interval },
	{ }
};

static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
{
	struct thermometer *t = user_data;

	t->attrib = g_attrib_ref(attrib);

	gatt_discover_char(t->attrib, t->svc_range->start, t->svc_range->end,
					NULL, configure_thermometer_cb, t);
}

static void attio_disconnected_cb(gpointer user_data)
{
	struct thermometer *t = user_data;

	DBG("GATT Disconnected");

	if (t->attio_measurement_id > 0) {
		g_attrib_unregister(t->attrib, t->attio_measurement_id);
		t->attio_measurement_id = 0;
	}

	if (t->attio_intermediate_id > 0) {
		g_attrib_unregister(t->attrib, t->attio_intermediate_id);
		t->attio_intermediate_id = 0;
	}

	if (t->attio_interval_id > 0) {
		g_attrib_unregister(t->attrib, t->attio_interval_id);
		t->attio_interval_id = 0;
	}

	g_attrib_unref(t->attrib);
	t->attrib = NULL;
}

static int thermometer_register(struct btd_device *device,
						struct gatt_primary *tattr)
{
	const char *path = device_get_path(device);
	struct thermometer *t;
	struct btd_adapter *adapter;
	struct thermometer_adapter *tadapter;

	adapter = device_get_adapter(device);

	tadapter = find_thermometer_adapter(adapter);

	if (tadapter == NULL)
		return -1;

	t = g_new0(struct thermometer, 1);
	t->dev = btd_device_ref(device);
	t->tadapter = tadapter;
	t->svc_range = g_new0(struct att_range, 1);
	t->svc_range->start = tattr->range.start;
	t->svc_range->end = tattr->range.end;

	tadapter->devices = g_slist_prepend(tadapter->devices, t);

	if (!g_dbus_register_interface(btd_get_dbus_connection(),
				path, THERMOMETER_INTERFACE,
				NULL, NULL, thermometer_properties,
				t, destroy_thermometer)) {
		error("D-Bus failed to register %s interface",
							THERMOMETER_INTERFACE);
		destroy_thermometer(t);
		return -EIO;
	}

	t->attioid = btd_device_add_attio_callback(device, attio_connected_cb,
						attio_disconnected_cb, t);
	return 0;
}

static void thermometer_unregister(struct btd_device *device)
{
	struct thermometer *t;
	struct btd_adapter *adapter;
	struct thermometer_adapter *tadapter;
	GSList *l;

	adapter = device_get_adapter(device);

	tadapter = find_thermometer_adapter(adapter);

	if (tadapter == NULL)
		return;

	l = g_slist_find_custom(tadapter->devices, device, cmp_device);
	if (l == NULL)
		return;

	t = l->data;

	tadapter->devices = g_slist_remove(tadapter->devices, t);

	g_dbus_unregister_interface(btd_get_dbus_connection(),
				device_get_path(t->dev), THERMOMETER_INTERFACE);
}

static const GDBusMethodTable thermometer_manager_methods[] = {
	{ GDBUS_METHOD("RegisterWatcher",
			GDBUS_ARGS({ "agent", "o" }), NULL,
			register_watcher) },
	{ GDBUS_METHOD("UnregisterWatcher",
			GDBUS_ARGS({ "agent", "o" }), NULL,
			unregister_watcher) },
	{ GDBUS_METHOD("EnableIntermediateMeasurement",
			GDBUS_ARGS({ "agent", "o" }), NULL,
			enable_intermediate) },
	{ GDBUS_METHOD("DisableIntermediateMeasurement",
			GDBUS_ARGS({ "agent", "o" }), NULL,
			disable_intermediate) },
	{ }
};

static int thermometer_adapter_register(struct btd_adapter *adapter)
{
	struct thermometer_adapter *tadapter;

	tadapter = g_new0(struct thermometer_adapter, 1);
	tadapter->adapter = adapter;

	if (!g_dbus_register_interface(btd_get_dbus_connection(),
						adapter_get_path(adapter),
						THERMOMETER_MANAGER_INTERFACE,
						thermometer_manager_methods,
						NULL, NULL, tadapter,
						destroy_thermometer_adapter)) {
		error("D-Bus failed to register %s interface",
						THERMOMETER_MANAGER_INTERFACE);
		destroy_thermometer_adapter(tadapter);
		return -EIO;
	}

	thermometer_adapters = g_slist_prepend(thermometer_adapters, tadapter);

	return 0;
}

static void thermometer_adapter_unregister(struct btd_adapter *adapter)
{
	struct thermometer_adapter *tadapter;

	tadapter = find_thermometer_adapter(adapter);
	if (tadapter == NULL)
		return;

	thermometer_adapters = g_slist_remove(thermometer_adapters, tadapter);

	g_dbus_unregister_interface(btd_get_dbus_connection(),
					adapter_get_path(tadapter->adapter),
					THERMOMETER_MANAGER_INTERFACE);
}

static int thermometer_device_probe(struct btd_service *service)
{
	struct btd_device *device = btd_service_get_device(service);
	struct gatt_primary *tattr;

	tattr = btd_device_get_primary(device, HEALTH_THERMOMETER_UUID);
	if (tattr == NULL)
		return -EINVAL;

	return thermometer_register(device, tattr);
}

static void thermometer_device_remove(struct btd_service *service)
{
	struct btd_device *device = btd_service_get_device(service);

	thermometer_unregister(device);
}

static int thermometer_adapter_probe(struct btd_profile *p,
						struct btd_adapter *adapter)
{
	return thermometer_adapter_register(adapter);
}

static void thermometer_adapter_remove(struct btd_profile *p,
						struct btd_adapter *adapter)
{
	thermometer_adapter_unregister(adapter);
}

static struct btd_profile thermometer_profile = {
	.name		= "Health Thermometer GATT driver",
	.remote_uuid	= HEALTH_THERMOMETER_UUID,
	.device_probe	= thermometer_device_probe,
	.device_remove	= thermometer_device_remove,
	.adapter_probe	= thermometer_adapter_probe,
	.adapter_remove	= thermometer_adapter_remove
};

static int thermometer_init(void)
{
	return btd_profile_register(&thermometer_profile);
}

static void thermometer_exit(void)
{
	btd_profile_unregister(&thermometer_profile);
}

BLUETOOTH_PLUGIN_DEFINE(thermometer, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
					thermometer_init, thermometer_exit)
