#include "common.h"

/* static time_t send_ts = 0;
static time_t reply_ts = 0; */
extern ble_var_t *var;

static void disconn_reply(DBusMessage *message, void *user_data)
{
    GDBusProxy *proxy = user_data;
    DBusError error;

    dbus_error_init(&error);

    if (dbus_set_error_from_message(&error, message) == TRUE)
    {
        dy_syslog(LOG_ERR, "Failed to disconnect: %s %s\n", error.name, error.message);
        dbus_error_free(&error);
        return;
    }

    dy_syslog(LOG_INFO, "Successful disconnected\n");

    return;
}

void disconnect_dev(ble_var_t *var, ble_slave_dev_t *slave_dev)
{
    if (g_dbus_proxy_method_call(slave_dev->proxy, "Disconnect", NULL, disconn_reply,
                                 slave_dev->proxy, NULL) == FALSE)
    {
        dy_syslog(LOG_ERR, "Failed to disconnect\n");
        return;
    }

    dy_syslog(LOG_INFO, "Attempting to disconnect from %s\n", slave_dev->addr);
}

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

    dbus_error_init(&error);

    if (dbus_set_error_from_message(&error, message) == TRUE)
    {
        dy_syslog(LOG_ERR, "Failed to remove device: %s\n", error.name);
        dbus_error_free(&error);
        return bt_shell_noninteractive_quit(EXIT_FAILURE);
    }

    bt_shell_printf("Device has been removed\n");
    return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

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

    dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
}

static void remove_device(GDBusProxy *proxy)
{
    char *path;

    if (!var->adapter)
        return;

    path = g_strdup(g_dbus_proxy_get_path(proxy));

    if (g_dbus_proxy_method_call(var->adapter->proxy, "RemoveDevice",
                                 remove_device_setup,
                                 remove_device_reply,
                                 path, g_free) == FALSE)
    {
        dy_syslog(LOG_ERR, "Failed to remove device");
        g_free(path);
        return bt_shell_noninteractive_quit(EXIT_FAILURE);
    }
}

static void connect_device_setup(DBusMessageIter *iter, void *user_data)
{
    ble_slave_dev_t *slave_dev = user_data;
    DBusMessageIter dict;

    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);

    if (slave_dev->addr != NULL)
        g_dbus_dict_append_entry(&dict, "Address", DBUS_TYPE_STRING,
                                 &slave_dev->addr);

    if (slave_dev->AddressType)
        g_dbus_dict_append_entry(&dict, "AddressType",
                                 DBUS_TYPE_STRING,
                                 &slave_dev->AddressType);

    dbus_message_iter_close_container(iter, &dict);
}

static void connect_device_reply(DBusMessage *message, void *user_data)
{
    DBusError error;
    ble_slave_dev_t *slave_dev = user_data;

    dbus_error_init(&error);
    if (dbus_set_error_from_message(&error, message) == TRUE)
    {
        dy_syslog(LOG_ERR, "connect device %s failed: %s\n", slave_dev->addr, error.name);
        if (strcmp(error.name, "org.bluez.Error.AlreadyExists") == 0)
        {
        }
        dbus_error_free(&error);
        return bt_shell_noninteractive_quit(EXIT_FAILURE);
    }

    dy_syslog(LOG_INFO, "connect device success\n");

    return;
}

static void connect_reply(DBusMessage *message, void *user_data)
{
    DBusError error;
    char *buff = NULL;
    static int count = 0;
    ble_slave_dev_t * slave = (ble_slave_dev_t *) user_data;

    dbus_error_init(&error);
    /* reply_ts = time(NULL); */

    if (dbus_set_error_from_message(&error, message) == TRUE)
    {
        DBusMessageIter iter;
        const char *str = (slave != NULL) ? slave->addr : NULL;
        dy_syslog(LOG_DEBUG, "Failed to connect addr %s: %s %s", str == NULL ? "" : str, error.name, error.message);
#if 1
        if (error.message != NULL && strcmp(error.message, "Connection aborted") == 0)
        {
            char exec[256];

            count++;
            if (count < 10)
            {
                goto out;
            }

            sprintf(exec, "hcitool -i %s con | grep \"state 1\"", var->dev_name);
            system_reply(exec, &buff);
            if (buff != NULL)
            {
                if (strlen(buff) > 0)
                {
                    goto out;
                }
            }
            sprintf(exec, "hciconfig %s down; hciconfig %s up", var->dev_name, var->dev_name);
            system(exec);
        }
        else
        {
            count = 0;
        }
    out:
        free(buff);
#endif
        dbus_error_free(&error);

        return bt_shell_noninteractive_quit(EXIT_FAILURE);
    }

    dy_syslog(LOG_DEBUG, "Connection successful");

    return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

void start_discovery_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) == TRUE)
    {
        const char * ename = error.name;
        if (ename == NULL)
            ename = "what the hell";
        if (strstr(ename, "InProgress") != NULL) {
            dbus_error_free(&error);
            return;
        }

        dy_syslog(LOG_DEBUG, "Failed to start discovery: %s\n", ename);
        if (error.message != NULL && strcmp(error.message, "Resource Not Ready") == 0)
        {
            char exec[256];
            sprintf(exec, "hciconfig %s up", var->dev_name);
            system(exec);
        }
        dbus_error_free(&error);
        bt_shell_noninteractive_quit(EXIT_FAILURE);
        return;
    }

    dy_syslog(LOG_DEBUG, "Discovery started\n");
    /* Leave the discovery running even on noninteractive mode */
}

void stop_discovery_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) == TRUE)
    {
        dy_syslog(LOG_DEBUG, "Failed to %s discovery: %s\n",
                  "stop", error.name);
        dbus_error_free(&error);
        return bt_shell_noninteractive_quit(EXIT_FAILURE);
    }

    dy_syslog(LOG_DEBUG, "Discovery stopped\n");
    /* Leave the discovery running even on noninteractive mode */
}

int get_device_connect_property(GList *devices, char *addr, dbus_bool_t *connected)
{
    DBusMessageIter conn_iter;
    GDBusProxy *proxy = find_proxy_by_address(devices, addr);
    if (proxy)
    {
        if (g_dbus_proxy_get_property(proxy, "Connected", &conn_iter) == TRUE)
        {
            dbus_message_iter_get_basic(&conn_iter, connected);
            return RET_SUCCEED;
        }
        else
        {
            dy_syslog(LOG_DEBUG, "get device %s property failed\n", addr);
        }
    }
    return RET_ERROR;
}

int get_adapter_discovering_property(GDBusProxy *ad_proxy, dbus_bool_t *is_discovering)
{
    if (NULL == ad_proxy || NULL == is_discovering)
    {
        dy_syslog(LOG_DEBUG, "Invalid input\n");
        return RET_ERROR;
    }
    DBusMessageIter discover_iter;
    if (g_dbus_proxy_get_property(ad_proxy, "Discovering", &discover_iter) == TRUE)
    {
        dbus_message_iter_get_basic(&discover_iter, is_discovering);
        return RET_SUCCEED;
    }
    else
    {
        dy_syslog(LOG_DEBUG, "get adapter discovering property failed\n");
    }
    return RET_ERROR;
}
void *connect_devs_loop(void *arg)
{
    ble_slave_cfg_t *slave_cfg = arg;
    ble_scanner_cfg_t *scan_cfg = &slave_cfg->adapter->scan_cfg;
    GDBusProxy *proxy;
    struct adapter *adapter;
    int i;
    int ret = 0;
    dbus_bool_t is_discovering = FALSE;

    adapter = slave_cfg->adapter;

    while (true) {
        struct timespec tspec;
        /*
        if (send_ts != 0)
        {
            if (send_ts - reply_ts > 60)
            {
                char cmd[64];
                dy_syslog(LOG_ERR, "timeout to send connect");
                sprintf(cmd, "/etc/init.d/ble_%d restart", var->dev_name, var->slot);
                system(cmd);
            }
        }
        */
        for (i = 0; i < slave_cfg->dev_cnt; i++)
        {
            long long ms = 0;
            dbus_bool_t connected = FALSE;
            ble_slave_dev_t *slave_dev = &slave_cfg->slave[i];
            ret = get_device_connect_property(adapter->devices, slave_dev->addr, &connected);
            if (ret == RET_SUCCEED)
            {
                if (connected) {
                    slave_dev->state = SLAVE_DEV_CONNECTED;
                } else {
                    slave_dev->state = SLAVE_DEV_NONE;
                }
                dy_syslog(LOG_DEBUG, "device %s connect state is %d\n", slave_dev->addr, slave_dev->state);
            }
            if (slave_dev->state != SLAVE_DEV_CONNECTED)
            {
                ms = get_time_stamp_ms();
                slave_dev->last_ms = ms;
                __sync_synchronize();
                slave_dev->state = SLAVE_DEV_CONNECTING;
                proxy = find_proxy_by_address(adapter->devices, slave_dev->addr);
                if (proxy)
                {
                    dy_syslog(LOG_DEBUG, "known device, use proxy to connect\n");

                    /* send_ts = time(NULL); */
                    if (g_dbus_proxy_method_call(proxy, "Connect", NULL, connect_reply,
                                                 slave_dev, NULL) == FALSE)
                    {
                        bt_shell_printf("Failed to connect\n");
                    }
                }
                else
                {
                    g_dbus_proxy_method_call(adapter->proxy, "ConnectDevice",
                                             connect_device_setup,
                                             connect_device_reply,
                                             slave_dev,
                                             NULL);
                }
                dy_syslog(LOG_DEBUG, "Attempting to connect to %s\n", slave_dev->addr);
            }
        }
        is_discovering = FALSE;
        if (RET_SUCCEED == get_adapter_discovering_property(adapter->proxy, &is_discovering)) {
            dy_syslog(LOG_DEBUG, "discovering state %s\n", is_discovering == TRUE ? "YES" : "NO");
        }
        // unconditionally start discovery
        if (g_dbus_proxy_method_call(adapter->proxy, "StartDiscovery", NULL, start_discovery_reply, adapter, NULL) == FALSE) {
            dy_syslog(LOG_ERR, "start scan failed\n");
        }
        tspec.tv_sec = 10;
        tspec.tv_nsec = 0;
        nanosleep(&tspec, NULL);
    }

    return NULL;
}

bool start_connect(ble_slave_cfg_t *slave_cfg)
{
    pthread_t tid;

    int ret = pthread_create(&tid, NULL, connect_devs_loop, (void *)slave_cfg);
    if (ret != 0)
    {
        dy_syslog(LOG_DEBUG, "pthread_create failed: %d\n", ret);
    }

    return true;
}
