#include "common.h"

#define MAX_ATTR_VAL_LEN 512

struct desc
{
	struct chrc *chrc;
	char *path;
	char *uuid;
	char **flags;
	int value_len;
	unsigned int max_val_len;
	uint8_t *value;
};

struct chrc
{
	struct service *service;
	char *path;
	char *uuid;
	char **flags;
	bool notifying;
	GList *descs;
	int value_len;
	unsigned int max_val_len;
	uint8_t *value;
	uint16_t mtu;
	struct io *write_io;
	struct io *notify_io;
	bool authorization_req;
};

struct service
{
	DBusConnection *conn;
	char *path;
	char *uuid;
	bool primary;
	GList *chrcs;
	GList *inc;
};

struct authorize_attribute_data
{
	DBusConnection *conn;
	void *attribute;
	uint16_t offset;
};
static GList *managers;
extern ble_var_t *var;

void my_gatt_add_manager(GDBusProxy *proxy)
{
	managers = g_list_append(managers, proxy);
}

void my_gatt_remove_manager(GDBusProxy *proxy)
{
	managers = g_list_remove(managers, proxy);
}

/* print start */
static void print_service(struct service *service, const char *description)
{
	const char *text;

	text = bt_uuidstr_to_str(service->uuid);
	if (!text)
		dy_syslog(LOG_DEBUG, "%s%s%s%s Service\n\t%s\n\t%s\n",
				  description ? "[" : "",
				  description ?: "",
				  description ? "] " : "",
				  service->primary ? "Primary" : "Secondary",
				  service->path, service->uuid);
	else
		dy_syslog(LOG_DEBUG, "%s%s%s%s Service\n\t%s\n\t%s\n\t%s\n",
				  description ? "[" : "",
				  description ?: "",
				  description ? "] " : "",
				  service->primary ? "Primary" : "Secondary",
				  service->path, service->uuid, text);
}

static void print_inc_service(struct service *service, const char *description)
{
	const char *text;

	text = bt_uuidstr_to_str(service->uuid);
	if (!text)
		dy_syslog(LOG_DEBUG, "%s%s%s%s Included Service\n\t%s\n\t%s\n",
				  description ? "[" : "",
				  description ?: "",
				  description ? "] " : "",
				  service->primary ? "Primary" : "Secondary",
				  service->path, service->uuid);
	else
		dy_syslog(LOG_DEBUG, "%s%s%s%s Included Service\n\t%s\n\t%s\n\t%s\n",
				  description ? "[" : "",
				  description ?: "",
				  description ? "] " : "",
				  service->primary ? "Primary" : "Secondary",
				  service->path, service->uuid, text);
}

static void print_service_proxy(GDBusProxy *proxy, const char *description)
{
	struct service service;
	DBusMessageIter iter;
	const char *uuid;
	dbus_bool_t primary;

	if (g_dbus_proxy_get_property(proxy, "UUID", &iter) == FALSE)
		return;

	dbus_message_iter_get_basic(&iter, &uuid);

	if (g_dbus_proxy_get_property(proxy, "Primary", &iter) == FALSE)
		return;

	dbus_message_iter_get_basic(&iter, &primary);

	service.path = (char *)g_dbus_proxy_get_path(proxy);
	service.uuid = (char *)uuid;
	service.primary = primary;

	print_service(&service, description);
}

static void print_chrc(struct chrc *chrc, const char *description)
{
	const char *text;

	text = bt_uuidstr_to_str(chrc->uuid);
	if (!text)
		dy_syslog(LOG_DEBUG, "%s%s%sCharacteristic\n\t%s\n\t%s\n",
				  description ? "[" : "",
				  description ?: "",
				  description ? "] " : "",
				  chrc->path, chrc->uuid);
	else
		dy_syslog(LOG_DEBUG, "%s%s%sCharacteristic\n\t%s\n\t%s\n\t%s\n",
				  description ? "[" : "",
				  description ?: "",
				  description ? "] " : "",
				  chrc->path, chrc->uuid, text);
}

static void print_characteristic(GDBusProxy *proxy, const char *description)
{
	struct chrc chrc;
	DBusMessageIter iter;
	const char *uuid;

	if (g_dbus_proxy_get_property(proxy, "UUID", &iter) == FALSE)
		return;

	dbus_message_iter_get_basic(&iter, &uuid);

	chrc.path = (char *)g_dbus_proxy_get_path(proxy);
	chrc.uuid = (char *)uuid;

	print_chrc(&chrc, description);
}

static void print_desc(struct desc *desc, const char *description)
{
	const char *text;

	text = bt_uuidstr_to_str(desc->uuid);
	if (!text)
		dy_syslog(LOG_DEBUG, "%s%s%sDescriptor\n\t%s\n\t%s\n",
				  description ? "[" : "",
				  description ?: "",
				  description ? "] " : "",
				  desc->path, desc->uuid);
	else
		dy_syslog(LOG_DEBUG, "%s%s%sDescriptor\n\t%s\n\t%s\n\t%s\n",
				  description ? "[" : "",
				  description ?: "",
				  description ? "] " : "",
				  desc->path, desc->uuid, text);
}

static void print_descriptor(GDBusProxy *proxy, const char *description)
{
	struct desc desc;
	DBusMessageIter iter;
	const char *uuid;

	if (g_dbus_proxy_get_property(proxy, "UUID", &iter) == FALSE)
		return;

	dbus_message_iter_get_basic(&iter, &uuid);

	desc.path = (char *)g_dbus_proxy_get_path(proxy);
	desc.uuid = (char *)uuid;

	print_desc(&desc, description);
}
/* print end */

static void desc_unregister(void *data)
{
	struct desc *desc = data;

	print_desc(desc, COLORED_DEL);

	g_dbus_unregister_interface(desc->chrc->service->conn, desc->path,
								DESC_INTERFACE);
}

static void chrc_free(void *data)
{
	struct chrc *chrc = data;

	g_list_free_full(chrc->descs, desc_unregister);
}

static gboolean attr_authorization_flag_exists(char **flags)
{
	int i;

	for (i = 0; flags[i]; i++)
	{
		if (!strcmp("authorize", flags[i]))
			return TRUE;
	}

	return FALSE;
}

static bool is_device_trusted(const char *path)
{
	GDBusProxy *proxy;
	DBusMessageIter iter;
	bool trusted;

	proxy = bt_shell_get_env(path);

	if (g_dbus_proxy_get_property(proxy, "Trusted", &iter))
		dbus_message_iter_get_basic(&iter, &trusted);

	return trusted;
}

static DBusMessage *read_value(DBusMessage *msg, uint8_t *value,
							   uint16_t value_len)
{
	DBusMessage *reply;
	DBusMessageIter iter, array;

	reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID);

	dbus_message_iter_init_append(reply, &iter);
	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "y", &array);
	dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
										 &value, value_len);
	dbus_message_iter_close_container(&iter, &array);

	return reply;
}

static uint8_t *str2bytearray(char *arg, int *val_len)
{
	uint8_t value[MAX_ATTR_VAL_LEN];
	char *entry;
	unsigned int i;

	for (i = 0; (entry = strsep(&arg, " \t")) != NULL; i++)
	{
		long int val;
		char *endptr = NULL;

		if (*entry == '\0')
			continue;

		if (i >= G_N_ELEMENTS(value))
		{
			bt_shell_printf("Too much data\n");
			return NULL;
		}

		val = strtol(entry, &endptr, 0);
		if (!endptr || *endptr != '\0' || val > UINT8_MAX)
		{
			bt_shell_printf("Invalid value at index %d\n", i);
			return NULL;
		}

		value[i] = val;
	}

	*val_len = i;

	return g_memdup(value, i);
}

static const char *path_to_address(const char *path)
{
	GDBusProxy *proxy;
	DBusMessageIter iter;
	const char *address = path;

	proxy = bt_shell_get_env(path);

	if (g_dbus_proxy_get_property(proxy, "Address", &iter))
		dbus_message_iter_get_basic(&iter, &address);

	return address;
}

static int parse_options(DBusMessageIter *iter, uint16_t *offset, uint16_t *mtu,
						 char **device, char **link,
						 bool *prep_authorize)
{
	DBusMessageIter dict;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
		return -EINVAL;

	dbus_message_iter_recurse(iter, &dict);

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

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

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

		var = dbus_message_iter_get_arg_type(&value);
		if (strcasecmp(key, "offset") == 0)
		{
			if (var != DBUS_TYPE_UINT16)
				return -EINVAL;
			if (offset)
				dbus_message_iter_get_basic(&value, offset);
		}
		else if (strcasecmp(key, "MTU") == 0)
		{
			if (var != DBUS_TYPE_UINT16)
				return -EINVAL;
			if (mtu)
				dbus_message_iter_get_basic(&value, mtu);
		}
		else if (strcasecmp(key, "device") == 0)
		{
			if (var != DBUS_TYPE_OBJECT_PATH)
				return -EINVAL;
			if (device)
				dbus_message_iter_get_basic(&value, device);
		}
		else if (strcasecmp(key, "link") == 0)
		{
			if (var != DBUS_TYPE_STRING)
				return -EINVAL;
			if (link)
				dbus_message_iter_get_basic(&value, link);
		}
		else if (strcasecmp(key, "prepare-authorize") == 0)
		{
			if (var != DBUS_TYPE_BOOLEAN)
				return -EINVAL;
			if (prep_authorize)
				dbus_message_iter_get_basic(&value,
											prep_authorize);
		}

		dbus_message_iter_next(&dict);
	}

	return 0;
}

static DBusMessage *chrc_read_value(DBusConnection *conn, DBusMessage *msg,
									void *user_data)
{
	struct chrc *chrc = user_data;
	DBusMessageIter iter;
	uint16_t offset = 0;
	char *device, *link;
	char *str;

	dbus_message_iter_init(msg, &iter);

	if (parse_options(&iter, &offset, NULL, &device, &link, NULL))
		return g_dbus_create_error(msg,
								   "org.bluez.Error.InvalidArguments",
								   NULL);

	dy_syslog(LOG_DEBUG, "ReadValue: %s offset %u link %s uuid %s\n",
			  path_to_address(device), offset, link, chrc->uuid);

	GDBusProxy *proxy;
	proxy = bt_shell_get_env(device);

	struct adapter *adapter = find_parent(proxy);
	ble_slave_dev_t *slave = find_slave_dev_by_address(adapter, path_to_address(device));
	ble_send_raw_data_to_pp(var, adapter->dev_id, slave, chrc->uuid, "read", NULL, 0);

#if 0
    // TODO skip authorization firstly
	if (!is_device_trusted(device) && chrc->authorization_req) {
		struct authorize_attribute_data *aad;

		aad = g_new0(struct authorize_attribute_data, 1);
		aad->conn = conn;
		aad->attribute = chrc;
		aad->offset = offset;

		str = g_strdup_printf("Authorize attribute(%s) read (yes/no):",
								chrc->path);

		bt_shell_prompt_input("gatt", str, authorize_read_response,
									aad);
		g_free(str);

		pending_message = dbus_message_ref(msg);

		return NULL;
	}
#endif

	if (offset > chrc->value_len)
		return g_dbus_create_error(msg, "org.bluez.Error.InvalidOffset",
								   NULL);

	return read_value(msg, &chrc->value[offset], chrc->value_len - offset);
}

static int parse_value_arg(DBusMessageIter *iter, uint8_t **value, int *len)
{
	DBusMessageIter array;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
		return -EINVAL;

	dbus_message_iter_recurse(iter, &array);
	dbus_message_iter_get_fixed_array(&array, value, len);

	return 0;
}

static int write_value(int *dst_len, uint8_t **dst_value, uint8_t *src_val,
					   int src_len, uint16_t offset, uint16_t max_len)
{
	if ((offset + src_len) > max_len)
		return -EOVERFLOW;

	if ((offset + src_len) != *dst_len)
	{
		*dst_len = offset + src_len;
		*dst_value = g_realloc(*dst_value, *dst_len);
	}

	memcpy(*dst_value + offset, src_val, src_len);

	return 0;
}

static DBusMessage *chrc_write_value(DBusConnection *conn, DBusMessage *msg,
									 void *user_data)
{
	struct chrc *chrc = user_data;
	uint16_t offset = 0;
	bool prep_authorize = false;
	char *device = NULL;
	DBusMessageIter iter;
	int value_len;
	uint8_t *value;
	char *str;

	dbus_message_iter_init(msg, &iter);

	if (parse_value_arg(&iter, &value, &value_len))
	{
		dy_syslog(LOG_DEBUG, "%s:%d\n", __FUNCTION__, __LINE__);
		return g_dbus_create_error(msg,
								   "org.bluez.Error.InvalidArguments", NULL);
	}

	dbus_message_iter_next(&iter);
	if (parse_options(&iter, &offset, NULL, &device, NULL, &prep_authorize))
	{
		dy_syslog(LOG_DEBUG, "%s:%d\n", __FUNCTION__, __LINE__);
		return g_dbus_create_error(msg,
								   "org.bluez.Error.InvalidArguments", NULL);
	}

	if (write_value(&chrc->value_len, &chrc->value, value, value_len,
					offset, chrc->max_val_len))
	{
		dy_syslog(LOG_DEBUG, "%s:%d\n", __FUNCTION__, __LINE__);
		return g_dbus_create_error(msg,
								   "org.bluez.Error.InvalidValueLength", NULL);
	}
	char *addr;
	GDBusProxy *proxy;
	DBusMessageIter iter_n;

	proxy = bt_shell_get_env(device);

	if (g_dbus_proxy_get_property(proxy, "Address", &iter_n))
		dbus_message_iter_get_basic(&iter_n, &addr);

	struct adapter *adapter = find_parent(proxy);
	ble_slave_dev_t *slave = find_slave_dev_by_address(adapter, addr);

	ble_send_raw_data_to_pp(var, adapter->dev_id, slave, chrc->uuid, "write", value, value_len);
	if (slave != NULL)
	{
		// dy_syslog(LOG_DEBUG, "[" COLORED_CHG "] Attribute %s written by dev:%s\n", chrc->path, slave->SN);
	}

	g_dbus_emit_property_changed(conn, chrc->path, CHRC_INTERFACE, "Value");

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *chrc_start_notify(DBusConnection *conn, DBusMessage *msg,
									  void *user_data)
{
	struct chrc *chrc = user_data;

	if (!chrc->notifying)
		return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);

	chrc->notifying = true;
	dy_syslog(LOG_DEBUG, "[" COLORED_CHG "] Attribute %s notifications enabled",
			  chrc->path);
	g_dbus_emit_property_changed(conn, chrc->path, CHRC_INTERFACE,
								 "Notifying");

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *chrc_stop_notify(DBusConnection *conn, DBusMessage *msg,
									 void *user_data)
{
	struct chrc *chrc = user_data;

	if (chrc->notifying)
		return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);

	chrc->notifying = false;
	dy_syslog(LOG_DEBUG, "[" COLORED_CHG "] Attribute %s notifications disabled",
			  chrc->path);
	g_dbus_emit_property_changed(conn, chrc->path, CHRC_INTERFACE,
								 "Notifying");

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *chrc_confirm(DBusConnection *conn, DBusMessage *msg,
								 void *user_data)
{
	struct chrc *chrc = user_data;

	dy_syslog(LOG_DEBUG, "Attribute %s indication confirm received", chrc->path);

	return dbus_message_new_method_return(msg);
}

static const GDBusMethodTable chrc_methods[] = {
	{GDBUS_ASYNC_METHOD("ReadValue", GDBUS_ARGS({"options", "a{sv}"}),
						GDBUS_ARGS({"value", "ay"}),
						chrc_read_value)},
	{GDBUS_ASYNC_METHOD("WriteValue", GDBUS_ARGS({"value", "ay"}, {"options", "a{sv}"}),
						NULL, chrc_write_value)},
	{GDBUS_ASYNC_METHOD("StartNotify", NULL, NULL, chrc_start_notify)},
	{GDBUS_METHOD("StopNotify", NULL, NULL, chrc_stop_notify)},
	{GDBUS_METHOD("Confirm", NULL, NULL, chrc_confirm)},
	{}};

static gboolean chrc_get_uuid(const GDBusPropertyTable *property,
							  DBusMessageIter *iter, void *data)
{
	struct chrc *chrc = data;
	dy_syslog(LOG_DEBUG, "%s:%d\n", __FUNCTION__, __LINE__);
	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &chrc->uuid);

	return TRUE;
}

static gboolean chrc_get_service(const GDBusPropertyTable *property,
								 DBusMessageIter *iter, void *data)
{
	struct chrc *chrc = data;
	dy_syslog(LOG_DEBUG, "%s:%d\n", __FUNCTION__, __LINE__);
	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
								   &chrc->service->path);

	return TRUE;
}

static gboolean chrc_get_value(const GDBusPropertyTable *property,
							   DBusMessageIter *iter, void *data)
{
	struct chrc *chrc = data;
	DBusMessageIter array;

	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "y", &array);

	dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
										 &chrc->value, chrc->value_len);

	dbus_message_iter_close_container(iter, &array);

	return TRUE;
}

static gboolean chrc_get_notifying(const GDBusPropertyTable *property,
								   DBusMessageIter *iter, void *data)
{
	struct chrc *chrc = data;
	dbus_bool_t value;

	value = chrc->notifying ? TRUE : FALSE;
	dy_syslog(LOG_DEBUG, "%s:%d\n", __FUNCTION__, __LINE__);
	dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);

	return TRUE;
}

static gboolean chrc_get_flags(const GDBusPropertyTable *property,
							   DBusMessageIter *iter, void *data)
{
	struct chrc *chrc = data;
	int i;
	DBusMessageIter array;

	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "s", &array);

	for (i = 0; chrc->flags[i]; i++)
	{
		dy_syslog(LOG_DEBUG, "%s:%d flags:%s\n", __FUNCTION__, __LINE__, chrc->flags[i]);
		dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING,
									   &chrc->flags[i]);
	}

	dbus_message_iter_close_container(iter, &array);

	return TRUE;
}

static void chrc_unregister(void *data)
{
	struct chrc *chrc = data;

	print_chrc(chrc, COLORED_DEL);

	g_dbus_unregister_interface(chrc->service->conn, chrc->path,
								CHRC_INTERFACE);
}

static const GDBusPropertyTable chrc_properties[] = {
	{"UUID", "s", chrc_get_uuid, NULL, NULL},
	{"Service", "o", chrc_get_service, NULL, NULL},
	{"Value", "ay", chrc_get_value, NULL, NULL},
	{"Notifying", "b", chrc_get_notifying, NULL, NULL},
	{"Flags", "as", chrc_get_flags, NULL, NULL},
	{}};

static void chrc_set_value(const char *input, void *user_data)
{
	struct chrc *chrc = user_data;

	g_free(chrc->value);

	chrc->value = str2bytearray((char *)input, &chrc->value_len);

	if (!chrc->value)
	{
		print_chrc(chrc, COLORED_DEL);
		chrc_unregister(chrc);
	}

	chrc->max_val_len = chrc->value_len;
}

struct chrc *register_one_chrc(struct adapter *adapter, struct service *service,
							   ble_gatt_service_char_t *chrc_cfg)
{
	struct chrc *chrc;
	ble_var_t *var = adapter->var;

	if (!service)
	{
		dy_syslog(LOG_DEBUG, "No service\n");
		return NULL;
	}

	chrc = g_new0(struct chrc, 1);
	chrc->service = service;
	chrc->uuid = chrc_cfg->uuid;
	chrc->path = g_strdup_printf("%s/chrc%p", service->path, chrc);
	chrc->flags = chrc_cfg->Flags;
	chrc->authorization_req = attr_authorization_flag_exists(chrc->flags);
	chrc->value = calloc(1, 512);
	chrc->max_val_len = 512;

	if (g_dbus_register_interface(var->dbus_conn, chrc->path, CHRC_INTERFACE,
								  chrc_methods, NULL, chrc_properties,
								  chrc, chrc_free) == FALSE)
	{
		dy_syslog(LOG_DEBUG, "Failed to register characteristic object\n");
		chrc_free(chrc);
		return NULL;
	}

	service->chrcs = g_list_append(service->chrcs, chrc);

	print_chrc(chrc, COLORED_NEW);

	return chrc;
}

static void service_free(void *data)
{
	struct service *service = data;
	g_list_free_full(service->chrcs, chrc_unregister);
}

static gboolean service_get_uuid(const GDBusPropertyTable *property,
								 DBusMessageIter *iter, void *data)
{
	struct service *service = data;

	dy_syslog(LOG_DEBUG, "%s:%d uuid:%s \n", __FUNCTION__, __LINE__, service->uuid);
	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &service->uuid);

	return TRUE;
}

static gboolean service_get_primary(const GDBusPropertyTable *property,
									DBusMessageIter *iter, void *data)
{
	struct service *service = data;
	dbus_bool_t primary;
	primary = service->primary ? TRUE : FALSE;

	dy_syslog(LOG_DEBUG, "%s:%d\n", __FUNCTION__, __LINE__);
	dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &primary);
	return TRUE;
}

static gboolean service_get_includes(const GDBusPropertyTable *property,
									 DBusMessageIter *iter, void *data)
{
	DBusMessageIter array;
	struct service *service = data;
	char *inc = NULL;
	GList *l;

	if (service->inc)
	{
		for (l = service->inc; l; l = g_list_next(l))
		{
			inc = l->data;
			dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
											 DBUS_TYPE_OBJECT_PATH_AS_STRING, &array);
			dy_syslog(LOG_DEBUG, "%s:%d\n", __FUNCTION__, __LINE__);
			dbus_message_iter_append_basic(&array,
										   DBUS_TYPE_OBJECT_PATH, &inc);
		}
		dbus_message_iter_close_container(iter, &array);
		return TRUE;
	}
	return FALSE;
}

static gboolean service_exist_includes(const GDBusPropertyTable *property,
									   void *data)
{
	struct service *service = data;

	if (service->inc)
		return TRUE;
	else
		return FALSE;
}

static const GDBusPropertyTable service_properties[] = {
	{"UUID", "s", service_get_uuid},
	{"Primary", "b", service_get_primary},
	{"Includes", "ao", service_get_includes,
	 NULL, service_exist_includes},
	{}};

static struct service *register_one_service(struct adapter *adapter, ble_gatt_service_cfg_t *service_cfg)
{
	struct service *service;
	ble_var_t *var = adapter->var;

	service = g_new0(struct service, 1);
	service->conn = var->dbus_conn;
	service->uuid = service_cfg->uuid;
	service->path = g_strdup_printf("%s/service%p", APP_PATH, service);
	service->primary = service_cfg->primary;

	if (g_dbus_register_interface(var->dbus_conn, service->path,
								  SERVICE_INTERFACE, NULL, NULL,
								  service_properties, service,
								  service_free) == FALSE)
	{
		dy_syslog(LOG_DEBUG, "Failed to register service object\n");
		service_free(service);
		return NULL;
	}

	print_service(service, COLORED_NEW);
	adapter->local_services = g_list_append(adapter->local_services, service);

	return service;
}

/* DESC begin */
static DBusMessage *desc_read_value(DBusConnection *conn, DBusMessage *msg,
									void *user_data)
{
	struct desc *desc = user_data;
	DBusMessageIter iter;
	uint16_t offset = 0;
	char *device = NULL, *link = NULL;

	dbus_message_iter_init(msg, &iter);

	if (parse_options(&iter, &offset, NULL, &device, &link, NULL))
		return g_dbus_create_error(msg,
								   "org.bluez.Error.InvalidArguments",
								   NULL);

	dy_syslog(LOG_DEBUG, "ReadValue: %s offset %u link %s\n",
			  path_to_address(device), offset, link);

	if (offset > desc->value_len)
		return g_dbus_create_error(msg, "org.bluez.Error.InvalidOffset",
								   NULL);

	return read_value(msg, &desc->value[offset], desc->value_len - offset);
}

static DBusMessage *desc_write_value(DBusConnection *conn, DBusMessage *msg,
									 void *user_data)
{
	struct desc *desc = user_data;
	DBusMessageIter iter;
	uint16_t offset = 0;
	char *device = NULL, *link = NULL;
	int value_len;
	uint8_t *value;

	dbus_message_iter_init(msg, &iter);

	if (parse_value_arg(&iter, &value, &value_len))
		return g_dbus_create_error(msg,
								   "org.bluez.Error.InvalidArguments", NULL);

	dbus_message_iter_next(&iter);
	if (parse_options(&iter, &offset, NULL, &device, &link, NULL))
		return g_dbus_create_error(msg,
								   "org.bluez.Error.InvalidArguments", NULL);

	if (write_value(&desc->value_len, &desc->value, value,
					value_len, offset, desc->max_val_len))
		return g_dbus_create_error(msg,
								   "org.bluez.Error.InvalidValueLength", NULL);

	dy_syslog(LOG_DEBUG, "WriteValue: %s offset %u link %s\n",
			  path_to_address(device), offset, link);

	dy_syslog(LOG_DEBUG, "[" COLORED_CHG "] Attribute %s written", desc->path);

	g_dbus_emit_property_changed(conn, desc->path, CHRC_INTERFACE, "Value");

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static const GDBusMethodTable desc_methods[] = {
	{GDBUS_ASYNC_METHOD("ReadValue", GDBUS_ARGS({"options", "a{sv}"}),
						GDBUS_ARGS({"value", "ay"}),
						desc_read_value)},
	{GDBUS_ASYNC_METHOD("WriteValue", GDBUS_ARGS({"value", "ay"}, {"options", "a{sv}"}),
						NULL, desc_write_value)},
	{}};

static gboolean desc_get_uuid(const GDBusPropertyTable *property,
							  DBusMessageIter *iter, void *data)
{
	struct desc *desc = data;
	dy_syslog(LOG_DEBUG, "%s:%d\n", __FUNCTION__, __LINE__);
	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &desc->uuid);

	return TRUE;
}

static gboolean desc_get_chrc(const GDBusPropertyTable *property,
							  DBusMessageIter *iter, void *data)
{
	struct desc *desc = data;
	dy_syslog(LOG_DEBUG, "%s:%d\n", __FUNCTION__, __LINE__);
	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
								   &desc->chrc->path);

	return TRUE;
}

static gboolean desc_get_value(const GDBusPropertyTable *property,
							   DBusMessageIter *iter, void *data)
{
	struct desc *desc = data;
	DBusMessageIter array;

	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "y", &array);

	if (desc->value)
		dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
											 &desc->value,
											 desc->value_len);

	dbus_message_iter_close_container(iter, &array);

	return TRUE;
}

static gboolean desc_get_flags(const GDBusPropertyTable *property,
							   DBusMessageIter *iter, void *data)
{
	struct desc *desc = data;
	int i;
	DBusMessageIter array;

	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "s", &array);

	for (i = 0; desc->flags[i]; i++)
	{
		dy_syslog(LOG_DEBUG, "%s:%d flags:%s\n", __FUNCTION__, __LINE__, desc->flags[i]);
		dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING,
									   &desc->flags[i]);
	}

	dbus_message_iter_close_container(iter, &array);

	return TRUE;
}

static void desc_free(void *data)
{
	struct desc *desc = data;

	g_free(desc);
}

static const GDBusPropertyTable desc_properties[] = {
	{"UUID", "s", desc_get_uuid, NULL, NULL},
	{"Characteristic", "o", desc_get_chrc, NULL, NULL},
	{"Value", "ay", desc_get_value, NULL, NULL},
	{"Flags", "as", desc_get_flags, NULL, NULL},
	{}};

struct desc *register_one_desc(struct adapter *adapter, struct chrc *chrc,
							   ble_gatt_service_desc_t *desc_cfg)
{
	struct service *service;
	struct desc *desc;
	ble_var_t *var = adapter->var;

	if (!chrc)
	{
		dy_syslog(LOG_DEBUG, "No characteristic registered\n");
		return NULL;
	}

	desc = g_new0(struct desc, 1);
	desc->chrc = chrc;
	desc->uuid = desc_cfg->uuid;
	desc->path = g_strdup_printf("%s/desc%p", chrc->path, desc);
	desc->flags = desc_cfg->Flags;

	desc->value = calloc(1, 512);
	desc->max_val_len = 512;

	if (g_dbus_register_interface(var->dbus_conn, desc->path, DESC_INTERFACE,
								  desc_methods, NULL, desc_properties,
								  desc, desc_free) == FALSE)
	{
		dy_syslog(LOG_DEBUG, "Failed to register descriptor object\n");
		desc_free(desc);
		return NULL;
	}

	desc->chrc->descs = g_list_append(desc->chrc->descs, desc);

	print_desc(desc, COLORED_NEW);

	return desc;
}
/* DESC end */

static void register_app_setup(DBusMessageIter *iter, void *user_data)
{
	DBusMessageIter opt;
	const char *path = "/";

	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,
									 &opt);
	dbus_message_iter_close_container(iter, &opt);
}

static void register_app_reply(DBusMessage *message, void *user_data)
{
	DBusError error;

	dbus_error_init(&error);

	if (dbus_set_error_from_message(&error, message) == TRUE)
	{
		bt_shell_printf("Failed to register application: %s\n",
						error.name);
		dbus_error_free(&error);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Application registered\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static int match_proxy(const void *a, const void *b)
{
	GDBusProxy *proxy1 = (void *)a;
	GDBusProxy *proxy2 = (void *)b;

	return strcmp(g_dbus_proxy_get_path(proxy1),
				  g_dbus_proxy_get_path(proxy2));
}

void gatt_register_application(struct adapter *adapter)
{
	GList *l;
	int i;

	l = g_list_find_custom(managers, adapter->proxy, match_proxy);
	if (!l)
	{
		bt_shell_printf("Unable to find GattManager proxy\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	if (g_dbus_proxy_method_call(l->data, "RegisterApplication",
								 register_app_setup,
								 register_app_reply, NULL,
								 NULL) == FALSE)
	{
		bt_shell_printf("Failed register application\n");
		g_dbus_unregister_interface(adapter->var->dbus_conn, APP_PATH, PROFILE_INTERFACE);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}
}

bool start_services(struct adapter *adapter, ble_gatt_server_cfg_t *server_cfg)
{
	int i;

	if (server_cfg->enable)
	{
		for (i = 0; i < server_cfg->service_cnt; i++)
		{
			ble_gatt_service_cfg_t *service_cfg = &server_cfg->services[i];
			struct service *service = NULL;
			dy_syslog(LOG_DEBUG, "service uuid:%s", service_cfg->uuid);
			service = register_one_service(adapter, service_cfg);
			if (service != NULL)
			{
				int j = 0;

				for (j = 0; j < service_cfg->num_char; j++)
				{
					ble_gatt_service_char_t *chrc_cfg = &service_cfg->chars[j];
					struct chrc *chrc;

					chrc = register_one_chrc(adapter, service, chrc_cfg);
					if (chrc != NULL)
					{
						int k = 0;

						for (k = 0; k < chrc_cfg->num_desc; k++)
						{
							ble_gatt_service_desc_t *desc_cfg = &chrc_cfg->desc[k];
							struct desc *desc;

							desc = register_one_desc(adapter, chrc, desc_cfg);
						}
					}
				}
			}
		}
		sleep(3);
		gatt_register_application(adapter);
	}
	return true;
}
