#include "common.h"

#define AD_PATH "/org/bluez/advertising"
#define AD_IFACE "org.bluez.LEAdvertisement1"

struct ad
{
    bool registered;
    char *type;
    char *local_name;
    uint16_t local_appearance;
    uint16_t duration;
    uint16_t timeout;
    uint16_t discoverable_to;
    char **uuids;
    size_t uuids_len;
    struct service_data service;
    struct manufacturer_data manufacturer;
    struct data data;
    bool discoverable;
    bool tx_power;
    bool name;
    bool appearance;
};

static void print_uuid(const char *uuid)
{
    const char *text;

    text = bt_uuidstr_to_str(uuid);
    if (text)
    {
        char str[26];
        unsigned int n;

        str[sizeof(str) - 1] = '\0';

        n = snprintf(str, sizeof(str), "%s", text);
        if (n > sizeof(str) - 1)
        {
            str[sizeof(str) - 2] = '.';
            str[sizeof(str) - 3] = '.';
            if (str[sizeof(str) - 4] == ' ')
                str[sizeof(str) - 4] = '.';

            n = sizeof(str) - 1;
        }

        bt_shell_printf("UUID: %s(%s)\n", str, uuid);
    }
    else
        bt_shell_printf("UUID: (%s)\n", uuid ? uuid : "");
}

static void print_ad_uuids(struct ad *ad)
{
    char **uuid;

    for (uuid = ad->uuids; uuid && *uuid; uuid++)
        print_uuid(*uuid);
}

static void print_ad(struct ad *ad)
{
    print_ad_uuids(ad);

    if (ad->service.uuid)
    {
        print_uuid(ad->service.uuid);
        bt_shell_hexdump(ad->service.data.data, ad->service.data.len);
    }

    if (ad->manufacturer.data.len)
    {
        bt_shell_printf("Manufacturer: %u\n", ad->manufacturer.id);
        bt_shell_hexdump(ad->manufacturer.data.data,
                         ad->manufacturer.data.len);
    }

    if (ad->data.data.len)
    {
        bt_shell_printf("Data Type: 0x%02x\n", ad->data.type);
        bt_shell_hexdump(ad->data.data.data, ad->data.data.len);
    }

    bt_shell_printf("Tx Power: %s\n", ad->tx_power ? "on" : "off");

    if (ad->local_name)
        bt_shell_printf("LocalName: %s\n", ad->local_name);
    else
        bt_shell_printf("Name: %s\n", ad->name ? "on" : "off");

    if (ad->local_appearance != UINT16_MAX)
        bt_shell_printf("Appearance: %s (0x%04x)\n",
                        bt_appear_to_str(ad->local_appearance),
                        ad->local_appearance);
    else
        bt_shell_printf("Apperance: %s\n",
                        ad->appearance ? "on" : "off");

    bt_shell_printf("Discoverable: %s\n", ad->discoverable ? "on" : "off");

    if (ad->duration)
        bt_shell_printf("Duration: %u sec\n", ad->duration);

    if (ad->timeout)
        bt_shell_printf("Timeout: %u sec\n", ad->timeout);
}

static void ad_release(DBusConnection *conn)
{
    g_dbus_unregister_interface(conn, AD_PATH, AD_IFACE);
}

static DBusMessage *release_advertising(DBusConnection *conn,
                                        DBusMessage *msg, void *user_data)
{
    struct ad *ad = user_data;
    bt_shell_printf("Advertising released\n");
    ad->registered = false;
    ad_release(conn);

    return dbus_message_new_method_return(msg);
}

static const GDBusMethodTable ad_methods[] = {
    {GDBUS_METHOD("Release", NULL, NULL, release_advertising)},
    {}};

static gboolean get_type(const GDBusPropertyTable *property,
                         DBusMessageIter *iter, void *user_data)
{
    const char *type = "peripheral";
    struct ad *ad = user_data;

    if (ad->type && strlen(ad->type) > 0)
        type = ad->type;

    dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &type);

    return TRUE;
}

static gboolean uuids_exists(const GDBusPropertyTable *property, void *user_data)
{
    struct ad *ad = user_data;
    return ad->uuids_len != 0;
}

static gboolean get_uuids(const GDBusPropertyTable *property,
                          DBusMessageIter *iter, void *user_data)
{
    DBusMessageIter array;
    size_t i;
    struct ad *ad = user_data;

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

    for (i = 0; i < ad->uuids_len; i++)
        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING,
                                       &ad->uuids[i]);

    dbus_message_iter_close_container(iter, &array);

    return TRUE;
}

static gboolean service_data_exists(const GDBusPropertyTable *property,
                                    void *user_data)
{
    struct ad *ad = user_data;
    return ad->service.uuid != NULL;
}

static gboolean get_service_data(const GDBusPropertyTable *property,
                                 DBusMessageIter *iter, void *user_data)
{
    DBusMessageIter dict;
    struct ad *ad = user_data;
    struct ad_data *data = &ad->service.data;
    uint8_t *val = data->data;

    dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict);

    g_dbus_dict_append_array(&dict, ad->service.uuid, DBUS_TYPE_BYTE, &val,
                             data->len);

    dbus_message_iter_close_container(iter, &dict);

    return TRUE;
}

static gboolean manufacturer_data_exists(const GDBusPropertyTable *property,
                                         void *user_data)
{
    struct ad *ad = user_data;
    return ad->manufacturer.id != 0;
}

static gboolean get_manufacturer_data(const GDBusPropertyTable *property,
                                      DBusMessageIter *iter, void *user_data)
{
    DBusMessageIter dict;
    struct ad *ad = user_data;
    struct ad_data *data = &ad->manufacturer.data;
    uint8_t *val = data->data;

    dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{qv}", &dict);

    g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_UINT16,
                                   &ad->manufacturer.id,
                                   DBUS_TYPE_BYTE, &val, data->len);

    dbus_message_iter_close_container(iter, &dict);

    return TRUE;
}

static gboolean includes_exists(const GDBusPropertyTable *property, void *user_data)
{
    struct ad *ad = user_data;
    return ad->tx_power || ad->name || ad->appearance;
}

static gboolean get_includes(const GDBusPropertyTable *property,
                             DBusMessageIter *iter, void *user_data)
{
    struct ad *ad = user_data;
    DBusMessageIter array;

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

    if (ad->tx_power)
    {
        const char *str = "tx-power";

        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &str);
    }

    if (ad->name)
    {
        const char *str = "local-name";

        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &str);
    }

    if (ad->appearance)
    {
        const char *str = "appearance";

        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &str);
    }

    dbus_message_iter_close_container(iter, &array);

    return TRUE;
}

static gboolean local_name_exits(const GDBusPropertyTable *property, void *user_data)
{
    struct ad *ad = user_data;
    return ad->local_name ? TRUE : FALSE;
}

static gboolean get_local_name(const GDBusPropertyTable *property,
                               DBusMessageIter *iter, void *user_data)
{
    struct ad *ad = user_data;
    dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ad->local_name);

    return TRUE;
}

static gboolean appearance_exits(const GDBusPropertyTable *property, void *user_data)
{
    struct ad *ad = user_data;
    return ad->local_appearance != UINT16_MAX ? TRUE : FALSE;
}

static gboolean get_appearance(const GDBusPropertyTable *property,
                               DBusMessageIter *iter, void *user_data)
{
    struct ad *ad = user_data;
    dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16,
                                   &ad->local_appearance);

    return TRUE;
}

static gboolean duration_exits(const GDBusPropertyTable *property, void *user_data)
{
    struct ad *ad = user_data;
    return ad->duration;
}

static gboolean get_duration(const GDBusPropertyTable *property,
                             DBusMessageIter *iter, void *user_data)
{
    struct ad *ad = user_data;
    dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &ad->duration);

    return TRUE;
}

static gboolean timeout_exits(const GDBusPropertyTable *property, void *user_data)
{
    struct ad *ad = user_data;
    return ad->timeout;
}

static gboolean get_timeout(const GDBusPropertyTable *property,
                            DBusMessageIter *iter, void *user_data)
{
    struct ad *ad = user_data;
    dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &ad->timeout);

    return TRUE;
}

static gboolean data_exists(const GDBusPropertyTable *property, void *user_data)
{
    struct ad *ad = user_data;
    return ad->data.type != 0;
}

static gboolean get_data(const GDBusPropertyTable *property,
                         DBusMessageIter *iter, void *user_data)
{
    DBusMessageIter dict;
    struct ad *ad = user_data;
    struct ad_data *data = &ad->data.data;
    uint8_t *val = data->data;

    dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{yv}", &dict);

    g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_BYTE, &ad->data.type,
                                   DBUS_TYPE_BYTE, &val, data->len);

    dbus_message_iter_close_container(iter, &dict);

    return TRUE;
}

static gboolean discoverable_exists(const GDBusPropertyTable *property,
                                    void *user_data)
{
    struct ad *ad = user_data;
    return ad->discoverable;
}

static gboolean get_discoverable(const GDBusPropertyTable *property,
                                 DBusMessageIter *iter, void *user_data)
{
    struct ad *ad = user_data;
    dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN,
                                   &ad->discoverable);

    return TRUE;
}

static gboolean discoverable_timeout_exits(const GDBusPropertyTable *property,
                                           void *user_data)
{
    struct ad *ad = user_data;
    return ad->discoverable_to;
}

static gboolean get_discoverable_timeout(const GDBusPropertyTable *property,
                                         DBusMessageIter *iter, void *user_data)
{
    struct ad *ad = user_data;
    dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16,
                                   &ad->discoverable_to);

    return TRUE;
}

static const GDBusPropertyTable ad_props[] = {
    {"Type", "s", get_type},
    {"ServiceUUIDs", "as", get_uuids, NULL, uuids_exists},
    {"ServiceData", "a{sv}", get_service_data, NULL, service_data_exists},
    {"ManufacturerData", "a{qv}", get_manufacturer_data, NULL,
     manufacturer_data_exists},
    {"Data", "a{yv}", get_data, NULL, data_exists},
    {"Discoverable", "b", get_discoverable, NULL, discoverable_exists},
    {"DiscoverableTimeout", "q", get_discoverable_timeout, NULL,
     discoverable_timeout_exits},
    {"Includes", "as", get_includes, NULL, includes_exists},
    {"LocalName", "s", get_local_name, NULL, local_name_exits},
    {"Appearance", "q", get_appearance, NULL, appearance_exits},
    {"Duration", "q", get_duration, NULL, duration_exits},
    {"Timeout", "q", get_timeout, NULL, timeout_exits},
    {}};

static void register_setup(DBusMessageIter *iter, void *user_data)
{
    DBusMessageIter dict;
    const char *path = AD_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,
                                     &dict);
    dbus_message_iter_close_container(iter, &dict);
}

static void register_reply(DBusMessage *message, void *user_data)
{
    struct adapter *adapter = user_data;
    DBusError error;

    dbus_error_init(&error);

    if (dbus_set_error_from_message(&error, message) == FALSE)
    {
        adapter->ad->registered = true;
        bt_shell_printf("Advertising object registered\n");
        print_ad(adapter->ad);
        /* Leave advertise running even on noninteractive mode */
    }
    else
    {
        bt_shell_printf("Failed to register advertisement: %s\n", error.name);
        dbus_error_free(&error);

        if (g_dbus_unregister_interface(adapter->var->dbus_conn, AD_PATH,
                                        AD_IFACE) == FALSE)
            bt_shell_printf("Failed to unregister advertising object\n");
        return bt_shell_noninteractive_quit(EXIT_FAILURE);
    }
}

bool start_advertise(struct adapter *adapter, ble_advertising_cfg_t *adv_cfg)
{
    struct ad *ad;

    if (!adv_cfg->enable)
    {
        return true;
    }

    dy_syslog(LOG_DEBUG, "start adv\n");

    ble_var_t *var = adapter->var;
    GDBusProxy *manager = adapter->ad_proxy;
    ad = g_new0(struct ad, 1);
    ad->type = adv_cfg->type;
    ad->uuids = adv_cfg->uuids;
    ad->uuids_len = adv_cfg->uuids_len;
    memcpy(&ad->service, &adv_cfg->service, sizeof(ad->service));
    memcpy(&ad->manufacturer, &adv_cfg->manufacturer, sizeof(ad->manufacturer));
    memcpy(&ad->data, &adv_cfg->data, sizeof(ad->data));
    ad->discoverable = adv_cfg->discoverable;
    ad->discoverable_to = adv_cfg->discoverable_to;
    ad->tx_power = adv_cfg->tx_power;
    ad->local_name = adv_cfg->local_name;
    ad->appearance = adv_cfg->appearance;
    ad->local_appearance = adv_cfg->local_appearance;
    ad->duration = adv_cfg->duration;
    ad->timeout = adv_cfg->timeout;
    adapter->ad = ad;

    if (g_dbus_register_interface(var->dbus_conn, AD_PATH, AD_IFACE, ad_methods,
                                  NULL, ad_props, ad, free) == FALSE)
    {
        bt_shell_printf("Failed to register advertising object\n");
        return false;
    }

    if (g_dbus_proxy_method_call(manager, "RegisterAdvertisement",
                                 register_setup, register_reply,
                                 adapter, NULL) == FALSE)
    {
        bt_shell_printf("Failed to register advertising\n");
        return false;
    }

    return true;
}


static void unregister_setup(DBusMessageIter *iter, void *user_data)
{
	const char *path = AD_PATH;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
}

static void unregister_reply(DBusMessage *message, void *user_data)
{
    struct adapter *adapter = user_data;
	DBusError error;

	dbus_error_init(&error);

	if (dbus_set_error_from_message(&error, message) == FALSE) {
		adapter->ad->registered = false;
		bt_shell_printf("Advertising object unregistered\n");
		if (g_dbus_unregister_interface(adapter->var->dbus_conn, AD_PATH,
							AD_IFACE) == FALSE)
			bt_shell_printf("Failed to unregister advertising"
					" object\n");
		return bt_shell_noninteractive_quit(EXIT_SUCCESS);
	} else {
		bt_shell_printf("Failed to unregister advertisement: %s\n",
								error.name);
		dbus_error_free(&error);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}
}

void ad_unregister(struct adapter *adapter, GDBusProxy *manager)
{
	if (!manager)
		ad_release(adapter->var->dbus_conn);


	if (g_dbus_proxy_method_call(manager, "UnregisterAdvertisement",
					unregister_setup, unregister_reply,
					adapter, NULL) == FALSE) {
		bt_shell_printf("Failed to unregister advertisement method\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}
}
