#include "common.h"
#include "src/shared/att-types.h"

static void ctrl_up(int dev_id)
{
    char cmd[32];

    sprintf(cmd, "hciconfig hci%d up", dev_id);
    system(cmd);
    printf(cmd);
}

static void ctrl_down(int dev_id)
{
    char cmd[32];

    sprintf(cmd, "hciconfig hci%d down", dev_id);
    system(cmd);
    printf(cmd);
}

static bool probe_controllers(ble_var_t *var)
{
    struct hci_dev_list_req *dl;
    struct hci_dev_req *dr;
    bool result;
    int fd, i;

    fd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
    if (fd < 0)
    {
        perror("Failed to open raw HCI socket");
        return false;
    }

    dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t));
    if (!dl)
    {
        perror("Failed allocate HCI device request memory");
        close(fd);
        return false;
    }

    dl->dev_num = HCI_MAX_DEV;
    dr = dl->dev_req;

    if (ioctl(fd, HCIGETDEVLIST, (void *)dl) < 0)
    {
        perror("Failed to get HCI device list");
        result = false;
        goto done;
    }

    result = true;

    var->ble_cnt = dl->dev_num;
    if (var->ble_cnt > 0)
    {
        ble_dev_t *dev = NULL;
        var->dev = calloc(var->ble_cnt, sizeof(ble_dev_t));
        for (i = 0; i < dl->dev_num; i++)
        {
            struct hci_dev_info *di = NULL;
            int dev_id = (dr + i)->dev_id;
            dev = &var->dev[i];

            di = calloc(1, sizeof(struct hci_dev_info));

            di->dev_id = dev_id;
            dev->dev_id = dev_id;
            dev->var = var;

            if (ioctl(fd, HCIGETDEVINFO, (void *)di) < 0)
            {
                printf("get dev info failed");
                free(di);
                continue;
            }
            dev->di = di;
            strcpy(dev->dev_name, di->name);
            printf("find dev name:%s\n", dev->dev_name);
        }
    }

done:
    close(fd);
    free(dl);
    return result;
}

static bool ble_load_cfg(ble_var_t *var)
{
    int i = 0;
    ble_dev_t *dev = NULL;

    for (i = 0; i < var->ble_cnt; i++)
    {
        ble_scanner_cfg_t *scan_cfg = NULL;
        ble_advertising_cfg_t *adv_cfg = NULL;
        ble_slave_cfg_t *slave_cfg = NULL;
        ble_gatt_server_cfg_t *server_cfg = NULL;

        dev = &var->dev[i];
        dev->mode = MASTER_SLAVE;
        dev->mode = SLAVE;

        dev->enable = true;
        scan_cfg = &dev->scan_cfg;
        scan_cfg->dev = dev;
        if (1)
        {
            scan_cfg->enable = true;
            scan_cfg->scan_type = 0x00; // passive
            scan_cfg->filter_policy = 0x00;
            scan_cfg->interval = 0x0100;
            scan_cfg->window = 0x0010;
            scan_cfg->filter_dup = 0x00; // do not filter duplicates
            scan_cfg->own_type = LE_PUBLIC_ADDRESS;
        }

        adv_cfg = &dev->adv_cfg;
        adv_cfg->dev = dev;
        if (1)
        {
            adv_cfg->enable = true;

            adv_cfg->min_interval = 0x0800;
            adv_cfg->max_interval = 0x0900;
            adv_cfg->type = 0x00;          /* Non-connectable advertising */
            adv_cfg->own_addr_type = 0x00; /* Local IRK, random address fallback */
            adv_cfg->channel_map = 0x07;
            adv_cfg->filter_policy = 0x00;

            static const uint8_t set_adv_data_test2[] = {
                0x06,                         /* AD len */
                0x08,                         /* AD type: shortened local name */
                0x74, 0x65, 0x73, 0x74, 0x32, /* "test2" */
                                              /* padding */
            };
            int size = sizeof(set_adv_data_test2);
            printf("adv size:%d\n", size);
            adv_cfg->data = malloc(size);
            memcpy(adv_cfg->data, set_adv_data_test2, size);
            adv_cfg->size = size;
        }

        slave_cfg = &dev->slave_cfg;
        if (1)
        {
            int j = 0;
            char *addr[2];
            uint8_t type[2];

            slave_cfg->dev_cnt = 1;
            slave_cfg->slave = calloc(slave_cfg->dev_cnt, sizeof(ble_slave_dev_t));

            addr[0] = "E0:51:24:CC:53:A3";
            type[0] = BDADDR_LE_PUBLIC;

            //addr[0] = "47:27:80:24:70:31";
            //type[0] = BDADDR_LE_RANDOM;
            for (j = 0; j < slave_cfg->dev_cnt; j++)
            {
                ble_slave_dev_t *slave = &slave_cfg->slave[j];
                str2ba(addr[j], &slave->addr);
                slave->addr_type = type[j];
                slave->dev = dev;
            }
        }

        server_cfg = &dev->server_cfg;
        if (1)
        {
            int j = 0;

            server_cfg->enable = true;
            server_cfg->addr_type = BDADDR_LE_PUBLIC;
            server_cfg->service_cnt = 1;
            server_cfg->services = calloc(server_cfg->service_cnt, sizeof(ble_gatt_service_cfg_t));

            for (j = 0; j < server_cfg->service_cnt; j++)
            {
                ble_gatt_service_cfg_t *serv_cfg = &server_cfg->services[j];
                bt_string_to_uuid(&serv_cfg->uuid, "01ff0100-ba5e-f4ee-5ca1-eb1e5e4b1ce0");
                serv_cfg->primary = true;
                serv_cfg->num_handles = 3;
                serv_cfg->num_char = 1;
                serv_cfg->chars = calloc(serv_cfg->num_char, sizeof(ble_gatt_service_char_t));

                int k = 0;
                for (k = 0; k < serv_cfg->num_char; k++)
                {
                    ble_gatt_service_char_t *chr = &serv_cfg->chars[k];
                    bt_string_to_uuid(&chr->uuid, "01ff0101-ba5e-f4ee-5ca1-eb1e5e4b1ce0");
                    chr->permissions = BT_ATT_PERM_READ | BT_ATT_PERM_WRITE;
                    chr->properties = BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP;
                }
            }
        }
    }
}

static int ble_start_service(ble_var_t *var)
{
    int i = 0;
    ble_dev_t *dev = NULL;

    for (i = 0; i < var->ble_cnt; i++)
    {
        dev = &var->dev[i];
        if (dev->enable)
        {
            bool ret;
            ctrl_up(dev->dev_id);
            start_hci_scan(var, dev);
            start_gatt_services(var, dev);
            // if (dev->mode == SCANNER || dev->mode == ADVERTISER)
            // {
            //     printf("bt hci new user channer\n");
            //     struct bt_hci *hci_dev = bt_hci_new_user_channel(dev->dev_id);
            //     if (!hci_dev)
            //     {
            //         fprintf(stderr, "Can't down device hci%d: %s (%d)\n",
            //                 dev->dev_id, strerror(errno), errno);
            //         continue;
            //     }
            //     dev->hci_dev = hci_dev;

            //     bt_hci_send(hci_dev, BT_HCI_CMD_RESET, NULL, 0, reset_done_callback, dev, NULL);
            // }
            // else if (dev->mode == MASTER)
            // {
            //     start_connect_slaves(dev->var, dev);
            // }
            // else if (dev->mode == SLAVE)
            // {
            //     // start_slave_services(dev->var, dev);
            //     //start_adv_service(var, dev);
            //     struct bt_hci *hci_dev = bt_hci_new_user_channel(dev->dev_id);
            //     if (!hci_dev)
            //     {
            //         fprintf(stderr, "Can't down device hci%d: %s (%d)\n",
            //                 dev->dev_id, strerror(errno), errno);
            //         // continue;
            //     }
            //     dev->hci_dev = hci_dev;

            //     bt_hci_send(hci_dev, BT_HCI_CMD_RESET, NULL, 0, reset_done_callback, dev, NULL);
            //     //start_slave_services(dev->var, dev);
            // }
            // else if (dev->mode == MASTER_SLAVE)
            // {
            //     start_connect_slaves(dev->var, dev);
            //     start_slave_services(dev->var, dev);
            // }
        }
        else
        {
            ctrl_down(dev->dev_id);
        }
    }
}

static int ble_init(ble_var_t *var)
{
    probe_controllers(var);
    ble_load_cfg(var);
    ble_start_service(var);
}

static void register_app_reply(DBusMessage *reply, void *user_data)
{
    DBusError derr;

    dbus_error_init(&derr);
    dbus_set_error_from_message(&derr, reply);

    if (dbus_error_is_set(&derr))
        printf("RegisterApplication: %s\n", derr.message);
    else
        printf("RegisterApplication: OK\n");

    dbus_error_free(&derr);
}

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

    dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);

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

    /* TODO: Add options dictionary */

    dbus_message_iter_close_container(iter, &dict);
}

static void register_app(GDBusProxy *proxy)
{
    if (!g_dbus_proxy_method_call(proxy, "RegisterApplication",
                                  register_app_setup, register_app_reply,
                                  NULL, NULL))
    {
        printf("Unable to call RegisterApplication\n");
        return;
    }
}

static void proxy_added(GDBusProxy *proxy, void *user_data)
{       
    const char *interface;
    
    interface = g_dbus_proxy_get_interface(proxy);

    if (!strcmp(interface, "org.bluez.Device1")) {
        device_added(proxy);
    } else if (!strcmp(interface, "org.bluez.Adapter1")) {
        adapter_added(proxy);
    } else if (!strcmp(interface, "org.bluez.AgentManager1")) {
        if (!agent_manager) {
            agent_manager = proxy;
                            
            if (auto_register_agent &&
                    !bt_shell_get_env("NON_INTERACTIVE"))
                agent_register(dbus_conn, agent_manager,
                            auto_register_agent);
        }   
    } else if (!strcmp(interface, "org.bluez.GattService1")) {
        if (service_is_child(proxy))
            gatt_add_service(proxy);
    } else if (!strcmp(interface, "org.bluez.GattCharacteristic1")) {
        gatt_add_characteristic(proxy);
    } else if (!strcmp(interface, "org.bluez.GattDescriptor1")) {
        gatt_add_descriptor(proxy);
    } else if (!strcmp(interface, "org.bluez.GattManager1")) {
        gatt_add_manager(proxy);
    } else if (!strcmp(interface, "org.bluez.LEAdvertisingManager1")) {
        ad_manager_added(proxy);
    }
}

static void proxy_removed(GDBusProxy *proxy, void *user_data)
{
    const char *interface;

    interface = g_dbus_proxy_get_interface(proxy);

    if (!strcmp(interface, "org.bluez.Device1")) {
        device_removed(proxy);
    } else if (!strcmp(interface, "org.bluez.Adapter1")) {
        adapter_removed(proxy);
    } else if (!strcmp(interface, "org.bluez.AgentManager1")) {
        if (agent_manager == proxy) {
            agent_manager = NULL;
            if (auto_register_agent)
                agent_unregister(dbus_conn, NULL);
        }
    } else if (!strcmp(interface, "org.bluez.GattService1")) {
        gatt_remove_service(proxy);

        if (default_attr == proxy)
            set_default_attribute(NULL);
    } else if (!strcmp(interface, "org.bluez.GattCharacteristic1")) {
        gatt_remove_characteristic(proxy);

        if (default_attr == proxy)
            set_default_attribute(NULL);
    } else if (!strcmp(interface, "org.bluez.GattDescriptor1")) {
        gatt_remove_descriptor(proxy);

        if (default_attr == proxy)
            set_default_attribute(NULL);
    } else if (!strcmp(interface, "org.bluez.GattManager1")) {
        gatt_remove_manager(proxy);
    } else if (!strcmp(interface, "org.bluez.LEAdvertisingManager1")) {
        ad_unregister(dbus_conn, NULL);
    }
}


static void connect_handler(DBusConnection *connection, void *user_data)
{   
    printf("connect handler\n");
}   
    
static void disconnect_handler(DBusConnection *connection, void *user_data)
{
    printf("disconnect handler\n");
    
    g_list_free_full(ctrl_list, proxy_leak);
    ctrl_list = NULL;

    default_ctrl = NULL;
}
static void message_handler(DBusConnection *connection,
                    DBusMessage *message, void *user_data)
{
    bt_shell_printf("[SIGNAL] %s.%s\n", dbus_message_get_interface(message),
                    dbus_message_get_member(message));
}


int main(int argc, char *argv[])
{
    ble_var_t var = {0};
    GMainLoop *main_loop;
    DBusConnection *con;
    GDBusClient *client;
    GSList *services;

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

    con = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL);
    if (con == NULL)
    {
        g_print("Not able to get connection to system bus\n");
        return 1;
    }

    g_dbus_attach_object_manager(con);

    printf("gatt-service unique name: %s\n",
           dbus_bus_get_unique_name(con));

    var.con = con;
    main_loop = g_main_loop_new(NULL, FALSE);

    client = g_dbus_client_new(con, "org.bluez", "/org/bluez");
    var.client = client;

    ble_init(&var);

    g_dbus_client_set_connect_watch(client, connect_handler, NULL);
    g_dbus_client_set_disconnect_watch(client, disconnect_handler, NULL);
    g_dbus_client_set_signal_watch(client, message_handler, NULL);

    g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed,
                            property_changed, NULL);
    
    g_dbus_client_set_ready_watch(client, client_ready, NULL);


    g_main_loop_run(main_loop);

    g_dbus_client_unref(client);
    g_slist_free_full(services, g_free);
    dbus_connection_unref(con);

    return 0;
}
