#include "common.h"

#define EIR_FLAGS 0x01         /* flags */
#define EIR_UUID16_SOME 0x02   /* 16-bit UUID, more available */
#define EIR_UUID16_ALL 0x03    /* 16-bit UUID, all listed */
#define EIR_UUID32_SOME 0x04   /* 32-bit UUID, more available */
#define EIR_UUID32_ALL 0x05    /* 32-bit UUID, all listed */
#define EIR_UUID128_SOME 0x06  /* 128-bit UUID, more available */
#define EIR_UUID128_ALL 0x07   /* 128-bit UUID, all listed */
#define EIR_NAME_SHORT 0x08    /* shortened local name */
#define EIR_NAME_COMPLETE 0x09 /* complete local name */
#define EIR_TX_POWER 0x0A      /* transmit power level */
#define EIR_DEVICE_ID 0x10     /* device ID */
#define EIR_MANUFACTURE_SPECIFIC 0xFF

static void process_data(const uint8_t *data, size_t data_len)
{
    if (data[0] == EIR_NAME_SHORT || data[0] == EIR_NAME_COMPLETE)
    {
        size_t name_len = data_len - 1;
        char *name = malloc(name_len + 1);

        memset(name, 0, name_len + 1);
        memcpy(name, &data[1], name_len);

        printf("name=%s\n", name);
        free(name);
    }
    else if (data[0] == EIR_FLAGS)
    {
        return;
        printf("Flag type: len=%d\n", data_len);
        int i;
        for (i = 1; i < data_len;)
        {
            printf("0x%0X ", data[i]);
            i++;
            if (i % 32 == 0)
                printf("\n");
            else if (i % 8 == 0)
                printf("\t");
        }
        printf("\n");
    }
    else if (data[0] == EIR_MANUFACTURE_SPECIFIC)
    {
        return;
        printf("Manufacture specific type: len=%d\n", data_len);
        int i;
        for (i = 1; i < data_len; i++)
        {
            printf("0x%0X ", data[i]);
            i++;
            if (i % 32 == 0)
                printf("\n");
            else if (i % 8 == 0)
                printf("\t");
        }
        printf("\n");
    }
    else
    {
        return;
        printf("Unknown type: type=%X\n", data[0]);
    }
}

static void process_adv_data(ble_dev_t *dev, unsigned char *buf, int len)
{
    evt_le_meta_event *meta = NULL;
    le_advertising_info *info = NULL;
    int current_index = 0;
    int data_error = 0;
    int rssi;

    meta = (void *)(buf + (1 + HCI_EVENT_HDR_SIZE));
    len -= (1 + HCI_EVENT_HDR_SIZE);
    if (meta->subevent != EVT_LE_ADVERTISING_REPORT)
    {
        return;
    }
    info = (le_advertising_info *)(meta->data + 1);
    if (info->length == 0)
    {
        return;
    }
    // printf("buf:%p info->data:%p info->len:%d \n", buf, info->data, info->length);
    rssi = info->data[info->length];
    rssi = (256 - rssi) * -1;
    char addr[18];

    switch (info->bdaddr_type)
    {
    case LE_PUBLIC_ADDRESS:
        printf("public ");
        break;
    case LE_RANDOM_ADDRESS:
        printf("random ");
        break;
    default:
        printf("unkonwn ");
    }
    ba2str(&info->bdaddr, addr);
    printf("addr=%s RSSI:%d\n", addr, rssi);

    while (!data_error && current_index < info->length)
    {
        size_t data_len = info->data[current_index];

        if (data_len + 1 > info->length)
        {
            printf("EIR data length is longer than EIR packet length. %d + 1 > %d", data_len, info->length);
            data_error = 1;
        }
        else
        {
            process_data(info->data + current_index + 1, data_len);
            current_index += data_len + 1;
        }
    }
    printf("\n=============================================\n");
}

static gboolean bt_sock_scan_cb(GIOChannel *io, GIOCondition cond,
                                gpointer data)
{
    ble_dev_t *dev = data;
    unsigned char buf[512];
    int len = 0, sent;
    int fd;

    if (cond & G_IO_HUP)
    {
        printf("Socket %d hang up", g_io_channel_unix_get_fd(io));
        g_io_channel_unref(io);
        goto fail;
    }

    if (cond & (G_IO_ERR | G_IO_NVAL))
    {
        printf("Socket %d error", g_io_channel_unix_get_fd(io));
        g_io_channel_unref(io);
        goto fail;
    }

    fd = g_io_channel_unix_get_fd(io);
    len = read(fd, buf, sizeof(buf));
    if (len < 0)
    {
        printf("read len < 0\n", len);
        goto fail;
    }
    process_adv_data(dev, buf, len);
    return TRUE;
fail:
    return FALSE;
}

bool start_hci_scan(ble_var_t *var, ble_dev_t *dev)
{
    ble_scanner_cfg_t *scan_cfg = &dev->scan_cfg;
    uint8_t filter_dup = scan_cfg->filter_dup;
    int fd;
    int err;
    struct hci_filter new_filter;

    if (!scan_cfg->enable)
    {
        printf("scan not enabled\n");
        return false;
    }
    fd = hci_open_dev(dev->dev_id);
    if (fd < 0)
    {
        printf("open %s failed\n", dev->dev_name);
        return false;
    }
    scan_cfg->scan_fd = fd;

    err = hci_le_set_scan_enable(fd, 0x00, 0x00, 10000);
    if (err < 0)
    {
        printf("set scan disable failed, err:%d; %s errno:%d\n", err, strerror(errno), errno);
        return false;
    }

    err = hci_le_set_scan_parameters(fd, scan_cfg->scan_type,
                                     htobs(scan_cfg->interval), htobs(scan_cfg->window),
                                     scan_cfg->own_type, scan_cfg->filter_policy, 10000);
    if (err < 0)
    {
        printf("set scan parameter failed, err:%d; %s errno:%d\n", err, strerror(errno), errno);
        return false;
    }
    err = hci_le_set_scan_enable(fd, 0x01, filter_dup, 10000);
    if (err < 0)
    {
        printf("set scan enable failed, err:%d; %s errno:%d\n", err, strerror(errno), errno);
        return false;
    }

    hci_filter_clear(&new_filter);
    hci_filter_set_ptype(HCI_EVENT_PKT, &new_filter);
    hci_filter_set_event(EVT_LE_META_EVENT, &new_filter);

    if (setsockopt(fd, SOL_HCI, HCI_FILTER, &new_filter, sizeof(new_filter)) < 0)
    {
        printf("set scoket filter failed, err:%d; %s errno:%d\n", err, strerror(errno), errno);
        return false;
    }

    printf("start scan");
    if (1)
    {
        GIOChannel *io;

        io = g_io_channel_unix_new(fd);
        g_io_channel_set_close_on_unref(io, TRUE);
        g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
                       bt_sock_scan_cb, (void *)dev);
        g_io_channel_unref(io);
        scan_cfg->io = io;
    }
    return true;
}
