#include "common.h"

static void bluez_property_value(const gchar *key, GVariant *value)
{
    const gchar *type = g_variant_get_type_string(value);

    g_print("\t%s : ", key);
    switch (*type)
    {
    case 'o':
    case 's':
        g_print("%s\n", g_variant_get_string(value, NULL));
        break;
    case 'b':
        g_print("%d\n", g_variant_get_boolean(value));
        break;
    case 'u':
        g_print("%d\n", g_variant_get_uint32(value));
        break;
    case 'a':
        /* TODO Handling only 'as', but not array of dicts */
        if (g_strcmp0(type, "as"))
            break;
        g_print("\n");
        const gchar *uuid;
        GVariantIter i;
        g_variant_iter_init(&i, value);
        while (g_variant_iter_next(&i, "s", &uuid))
            g_print("\t\t%s\n", uuid);
        break;
    default:
        g_print("Other\n");
        break;
    }
}

static void bluez_device_appeared(GDBusConnection *sig,
                                  const gchar *sender_name,
                                  const gchar *object_path,
                                  const gchar *interface,
                                  const gchar *signal_name,
                                  GVariant *parameters,
                                  gpointer user_data)
{
    (void)sig;
    (void)sender_name;
    (void)object_path;
    (void)interface;
    (void)signal_name;
    (void)user_data;

    ble_var_t *var = (ble_var_t *)user_data;

    GVariantIter *interfaces;
    const char *object;
    const gchar *interface_name;
    GVariant *properties;

    g_variant_get(parameters, "(&oa{sa{sv}})", &object, &interfaces);
    while (g_variant_iter_next(interfaces, "{&s@a{sv}}", &interface_name, &properties))
    {
        if (g_strstr_len(g_ascii_strdown(interface_name, -1), -1, "device"))
        {
            g_print("[ %s ]\n", object);
            const gchar *property_name;
            GVariantIter i;
            GVariant *prop_val;
            g_variant_iter_init(&i, properties);
            while (g_variant_iter_next(&i, "{&sv}", &property_name, &prop_val))
            {
                bluez_property_value(property_name, prop_val);
                if (!g_strcmp0(property_name, "Address"))
                {
                    if (g_variant_is_of_type(prop_val, G_VARIANT_TYPE_STRING))
                    {
                        const char *addr = g_variant_get_string(prop_val, NULL);
                        int i = 0;

                        for (i = 0; i < var->dev_cnt; i++)
                        {
                            if (!g_strcmp0(g_ascii_strdown(var->dev[i].dst, -1), g_ascii_strdown(addr, -1)))
                            {
                                printf("find our device %s\n", var->dev[i].dst);
                                bluez_adapter_connect_dev(&var->dev[i], var->dev[i].dst);
                            }
                        }
                    }
                }
            }
            g_variant_unref(prop_val);
        }
        g_variant_unref(properties);
    }

    return;
}

#define BT_ADDRESS_STRING_SIZE 18
static void bluez_device_disappeared(GDBusConnection *sig,
                                     const gchar *sender_name,
                                     const gchar *object_path,
                                     const gchar *interface,
                                     const gchar *signal_name,
                                     GVariant *parameters,
                                     gpointer user_data)
{
    (void)sig;
    (void)sender_name;
    (void)object_path;
    (void)interface;
    (void)signal_name;

    GVariantIter *interfaces;
    const char *object;
    const gchar *interface_name;
    char address[BT_ADDRESS_STRING_SIZE] = {'\0'};

    g_variant_get(parameters, "(&oas)", &object, &interfaces);
    while (g_variant_iter_next(interfaces, "s", &interface_name))
    {
        if (g_strstr_len(g_ascii_strdown(interface_name, -1), -1, "device"))
        {
            int i;
            char *tmp = g_strstr_len(object, -1, "dev_") + 4;

            for (i = 0; *tmp != '\0'; i++, tmp++)
            {
                if (*tmp == '_')
                {
                    address[i] = ':';
                    continue;
                }
                address[i] = *tmp;
            }
            g_print("\nDevice %s removed\n", address);
        }
    }
    return;
}

static void bluez_signal_adapter_changed(GDBusConnection *conn,
                                         const gchar *sender,
                                         const gchar *path,
                                         const gchar *interface,
                                         const gchar *signal,
                                         GVariant *params,
                                         void *userdata)
{
    (void)conn;
    (void)sender;
    (void)path;
    (void)interface;
    (void)userdata;

    GVariantIter *properties = NULL;
    GVariantIter *unknown = NULL;
    const char *iface;
    const char *key;
    GVariant *value = NULL;
    const gchar *signature = g_variant_get_type_string(params);

    printf("============adapter changed===");

    if (g_strcmp0(signature, "(sa{sv}as)") != 0)
    {
        g_print("Invalid signature for %s: %s != %s", signal, signature, "(sa{sv}as)");
        goto done;
    }

    g_variant_get(params, "(&sa{sv}as)", &iface, &properties, &unknown);
    while (g_variant_iter_next(properties, "{&sv}", &key, &value))
    {
        if (!g_strcmp0(key, "Powered"))
        {
            if (!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN))
            {
                g_print("Invalid argument type for %s: %s != %s", key,
                        g_variant_get_type_string(value), "b");
                goto done;
            }
            g_print("Adapter is Powered \"%s\"\n", g_variant_get_boolean(value) ? "on" : "off");
        }
        if (!g_strcmp0(key, "Discovering"))
        {
            if (!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN))
            {
                g_print("Invalid argument type for %s: %s != %s", key,
                        g_variant_get_type_string(value), "b");
                goto done;
            }
            g_print("Adapter scan \"%s\"\n", g_variant_get_boolean(value) ? "on" : "off");
        }
    }
done:
    if (properties != NULL)
        g_variant_iter_free(properties);
    if (value != NULL)
        g_variant_unref(value);
}

static int bluez_adapter_call_method(GDBusConnection *con, const char *method)
{
    GVariant *result;
    GError *error = NULL;

    result = g_dbus_connection_call_sync(con,
                                         "org.bluez",
                                         /* TODO Find the adapter path runtime */
                                         "/org/bluez/hci0",
                                         "org.bluez.Adapter1",
                                         method,
                                         NULL,
                                         NULL,
                                         G_DBUS_CALL_FLAGS_NONE,
                                         -1,
                                         NULL,
                                         &error);
    if (error != NULL)
        return 1;

    g_variant_unref(result);
    return 0;
}

static int bluez_adapter_set_property(GDBusConnection *con, const char *prop, GVariant *value)
{
    GVariant *result;
    GError *error = NULL;

    result = g_dbus_connection_call_sync(con,
                                         "org.bluez",
                                         "/org/bluez/hci0",
                                         "org.freedesktop.DBus.Properties",
                                         "Set",
                                         g_variant_new("(ssv)", "org.bluez.Adapter1", prop, value),
                                         NULL,
                                         G_DBUS_CALL_FLAGS_NONE,
                                         -1,
                                         NULL,
                                         &error);
    if (error != NULL)
        return 1;

    g_variant_unref(result);
    return 0;
}

int adapter_init(ble_var_t *var)
{
    GDBusConnection *con = var->con;
    guint prop_changed;
    guint iface_added;
    guint iface_removed;
    int rc;

    prop_changed = g_dbus_connection_signal_subscribe(con,
                                                      "org.bluez",
                                                      "org.freedesktop.DBus.Properties",
                                                      "PropertiesChanged",
                                                      "org.bluez.Adapter1",
                                                      NULL,
                                                      G_DBUS_SIGNAL_FLAGS_NONE,
                                                      bluez_signal_adapter_changed,
                                                      (void *)var,
                                                      NULL);

    iface_added = g_dbus_connection_signal_subscribe(con,
                                                     "org.bluez",
                                                     "org.freedesktop.DBus.ObjectManager",
                                                     "InterfacesAdded",
                                                     NULL,
                                                     NULL,
                                                     G_DBUS_SIGNAL_FLAGS_NONE,
                                                     bluez_device_appeared,
                                                     (void *)var,
                                                     NULL);

    iface_removed = g_dbus_connection_signal_subscribe(con,
                                                       "org.bluez",
                                                       "org.freedesktop.DBus.ObjectManager",
                                                       "InterfacesRemoved",
                                                       NULL,
                                                       NULL,
                                                       G_DBUS_SIGNAL_FLAGS_NONE,
                                                       bluez_device_disappeared,
                                                       (void *)var,
                                                       NULL);
    rc = bluez_adapter_set_property(con, "Powered", g_variant_new("b", FALSE));
    if (rc)
    {
        g_print("Not able to enable the adapter\n");
        goto fail;
    }
    rc = bluez_adapter_set_property(con, "Powered", g_variant_new("b", TRUE));
    if (rc)
    {
        g_print("Not able to enable the adapter\n");
        goto fail;
    }

    rc = bluez_adapter_call_method(con, "StartDiscovery");
    if (rc)
    {
        g_print("Not able to scan for new devices\n");
        goto fail;
    }

fail:
    return 0;
}