#include "common.h"
#include "ipc_session.h"
#include "dy_utils/dy_common.h"
#include "libubox/utils.h"

#define SUB_TOPIC "ipc/+/BLE/device/+/data/Set_Rglt_Raw"

static ble_slave_dev_t *find_slave_dev_by_sn(ble_var_t *var, char *sn)
{
    ble_slave_cfg_t *slave_cfg = NULL;
    int i;

    struct adapter *adapter;

    adapter = var->adapter;
    if (adapter == NULL)
    {
        return NULL;
    }

    slave_cfg = &adapter->slave_cfg;
    for (i = 0; i < slave_cfg->dev_cnt; i++)
    {
        ble_slave_dev_t *slave = &slave_cfg->slave[i];
        if (strcmp(slave->SN, sn) == 0)
        {
            return slave;
        }
    }

    return NULL;
}

int ble_send_data_to_sn(ble_var_t *var, char *sn, char *uuid, char *data_str, char *opcode, bool update_last)
{
    unsigned char buf[1024];
    int len;
    ble_slave_dev_t *dev = NULL;
    GDBusProxy *proxy;

    dev = find_slave_dev_by_sn(var, sn);
    if (dev == NULL)
    {
        dy_syslog(LOG_DEBUG, "cannot find the device by sn");
        return -1;
    }

    if (update_last)
    {
        if (dev->last_data.uuid != NULL)
        {
            free(dev->last_data.uuid);
            dev->last_data.uuid = NULL;
        }
        dev->last_data.uuid = strdup(uuid);

        if (dev->last_data.data_str != NULL)
        {
            free(dev->last_data.data_str);
            dev->last_data.data_str = NULL;
        }
        dev->last_data.data_str = strdup(data_str);

        if (dev->last_data.opcode != NULL)
        {
            free(dev->last_data.opcode);
            dev->last_data.opcode = NULL;
        }
        dev->last_data.opcode = strdup(opcode);
        dy_syslog(LOG_DEBUG, "sn:%s dev->last_data.uuid:%s", dev->SN, dev->last_data.uuid);
    }

    proxy = gatt_select_attribute(dev->proxy, uuid);

    if (!proxy)
    {
        dy_syslog(LOG_DEBUG, "cannot find the attribute for uuid:%s", uuid);
        return -1;
    }

    if (strcmp(opcode, "read") == 0)
    {
        dy_syslog(LOG_DEBUG, "read:");
        gatt_read_attribute(proxy, 1, NULL);
    }
    else if (strcmp(opcode, "write") == 0)
    {
        dy_syslog(LOG_DEBUG, "write:");
        len = b64_decode(data_str, buf, 1024);
        write_attribute_bin(proxy, buf, len);
    }

    dy_syslog(LOG_DEBUG, "sending data\n");
    return 0;
}

static int ble_handle_mqtt_recv_msg(void *obj, ipc_msg_t *mqtt_msg)
{
    ble_var_t *var = (ble_var_t *)obj;
    char *data_str = NULL;
    char *sn = NULL;
    char *uuid = NULL;
    char *raw_data = NULL;
    char *opcode = NULL;
    cJSON *root = cJSON_Parse(mqtt_msg->payload);
    if (!root)
    {
        return -1;
    }
    GET_JSON_VALUE_DY_STRING(root, "data_b64", data_str);
    GET_JSON_VALUE_DY_STRING(root, "sn", sn);
    cJSON_Delete(root);

    cJSON *data = cJSON_Parse(data_str);
    GET_JSON_VALUE_DY_STRING(data, "uuid", uuid);
    GET_JSON_VALUE_DY_STRING(data, "data", raw_data);
    GET_JSON_VALUE_DY_STRING(data, "opcode", opcode);

    if (sn != NULL && uuid != NULL && raw_data != NULL)
    {
        ble_send_data_to_sn(var, sn, uuid, raw_data, opcode ? opcode : "read", true);
    }

    free(sn);
    free(uuid);
    free(raw_data);
    free(data_str);
    free(opcode);
    cJSON_Delete(data);
    return 0;
}

static void ble_send_noslave(ble_var_t * var,
    const char * uuid, const char * opcode, const char * value,
    int valen, const char * type, const char * scansn)
{
    time_t ts;
    cJSON *rt_data;
    char * data_tmp;
    char topic[TOPIC_MAX_LEN];
    unsigned char * pbuf, * b64buf;
    int ulen, olen, tlen, slen, totlen;

    struct ble_sess_sn * psn;
    struct ble_sess_data * pdat;

    pbuf = NULL;
    b64buf = NULL;
    rt_data = NULL;
    ts = time(NULL);
    data_tmp = NULL;
    ulen = olen = tlen = slen = 0;
    rt_data = cJSON_CreateObject();

    cJSON_AddStringToObject(rt_data, "port", "BLE");
    cJSON_AddStringToObject(rt_data, "sn", scansn);
    cJSON_AddNumberToObject(rt_data, "ts", ts);

    if (uuid != NULL)
        ulen = strlen(uuid);
    if (opcode != NULL)
        olen = strlen(opcode);
    if (type != NULL)
        tlen = strlen(type);
    slen = strlen(scansn);

    totlen = ulen + olen + tlen + valen + slen + 0x20;
    pbuf = (unsigned char *) calloc(0x1, (size_t) totlen);
    b64buf = (unsigned char *) calloc(0x2, (size_t) totlen);
    if (pbuf == NULL) {
        dy_syslog(LOG_ERR, "Error, system out of memory: %d\n", totlen);
        goto err0;
    }
    psn = (struct ble_sess_sn *) pbuf;
    psn->sn_magic = BLE_SESS_SNMAGIC;
    psn->sn_len = (unsigned short) slen;
    psn->sn_time = (unsigned int) ts;
    if (slen > 0)
        memcpy(pbuf + sizeof(*psn), scansn, (size_t) slen);
    slen += sizeof(*psn);
    if (slen & 0x1)
        slen++;

    pdat = (struct ble_sess_data *) (pbuf + slen);
    pdat->sd_magic = BLE_SESS_DATAMAGIC;
    pdat->sl_uuid = (unsigned short) ulen;
    pdat->sl_opcode = (unsigned short) olen;
    pdat->sl_data = (unsigned short) valen;
    pdat->sl_type = (unsigned short) tlen;
    slen += sizeof(*pdat);
    if (ulen > 0) {
        memcpy(pbuf + slen, uuid, (size_t) ulen);
        slen += ulen;
    }
    if (olen > 0) {
        memcpy(pbuf + slen, opcode, (size_t) olen);
        slen += olen;
    }
    if (valen > 0) {
        memcpy(pbuf + slen, value, (size_t) valen);
        slen += valen;
    }
    if (tlen > 0) {
        memcpy(pbuf + slen, type, (size_t) tlen);
        slen += tlen;
    }
    if (slen & 0x1)
        slen++;

    b64_encode(pbuf, slen, b64buf, totlen * 2);
    cJSON_AddNumberToObject(rt_data, "len", slen);
    cJSON_AddStringToObject(rt_data, "data_b64", b64buf);
    data_tmp = cJSON_Print(rt_data);

    snprintf(topic, TOPIC_MAX_LEN, "ipc/%s/%s/device/%s/data/%s", scansn, "BLE",
        scansn, "raw_data");
    ipc_session_publish(var->session, topic, data_tmp, strlen(data_tmp));

err0:
    free(data_tmp);
    cJSON_Delete(rt_data);
    if (pbuf != NULL)
        free(pbuf);
    if (b64buf != NULL)
        free(b64buf);
}

void ble_send_adv_data_to_pp(ble_var_t *var, int dev_id, ble_scan_dev_t *scan_dev,
	const char *type, uint8_t *value, int value_len)
{
    if (scan_dev != NULL)
    {
#if 1
        ble_send_noslave(var, scan_dev->addr, NULL, value, value_len, type, scan_dev->SN);
#else
        long long ms = get_time_stamp_ms();

        cJSON *rt_data = cJSON_CreateObject();
        char buf[512] = {0};
        char topic[TOPIC_MAX_LEN] = {0};
        time_t ts = time(NULL);
        cJSON_AddStringToObject(rt_data, "port", "BLE");
        cJSON_AddStringToObject(rt_data, "sn", scan_dev->SN);
        cJSON_AddNumberToObject(rt_data, "ts", ts);

        cJSON *data = cJSON_CreateObject();
        cJSON_AddStringToObject(data, "sn", scan_dev->SN);
        cJSON_AddStringToObject(data, "type", type);
        if (value != NULL && value_len > 0)
        {
            b64_encode(value, value_len, buf, 512);
            cJSON_AddStringToObject(data, "data", buf);
        }
        char *tmp = cJSON_Print(data);
        b64_encode(tmp, strlen(tmp), buf, 512);
        cJSON_AddNumberToObject(rt_data, "len", strlen(tmp));
        cJSON_AddStringToObject(rt_data, "data_b64", buf);
        char *data_tmp = cJSON_Print(rt_data);

        snprintf(topic, TOPIC_MAX_LEN, "ipc/%s/%s/device/%s/data/%s", var->sn_str, "BLE",
                 scan_dev->SN, "raw_data");

        ipc_session_publish(var->session, topic, data_tmp, strlen(data_tmp));
        free(data_tmp);
        free(tmp);
        cJSON_Delete(data);
        cJSON_Delete(rt_data);
#endif
    }
}

unsigned int ble_gettime(time_t * psec, unsigned int * pmsec)
{
    int ret;
    struct timespec tim;

    ret = clock_gettime(CLOCK_REALTIME, &tim);
    if (ret != 0) {
        tim.tv_sec = time(NULL);
        tim.tv_nsec = 0;
    }

    if (psec != NULL)
        *psec  = tim.tv_sec;
    if (pmsec != NULL)
        *pmsec = (unsigned int) (tim.tv_nsec / 1000000);
    return (unsigned int) tim.tv_sec;
}

#define BLE_USE_DIFF_TIME 0
#if BLE_USE_DIFF_TIME
int ble_difftime(time_t now_sec, unsigned int now_nsec,
    time_t then_sec, unsigned int then_msec)
{
    int ret;
    if (now_sec < then_sec)
        return -1;

    ret = (int) (now_sec - then_sec);
    ret *= 1000;
    ret += (int) (now_nsec - then_msec);
    return ret;
}
#endif

#define BLE_STORE_OK     0  /* BLE data pushed into buffer store */
#define BLE_STORE_SEND   1  /* BLE data pushed into buffer store and needs to be delivered */
#define BLE_STORE_NOTOK  2  /* BLE data not pushed, and store needs to be delivered */
static int ble_store_slave_data(ble_slave_dev_t * slave,
    char * uuid, char * ops, uint8_t * value, int valen)
{
    time_t nowt;
    int ulen, olen;
    unsigned int nowtm;
    unsigned char * pbuf;
    int curlen, inval, tlen;
    struct ble_sess_data * pdat;
    struct ble_sess_store * bstore;

    pdat = NULL;
    bstore = &slave->buf_store;
    if (bstore->ble_buffer == NULL)
        return BLE_STORE_NOTOK;

    nowtm = 0;
    nowt = ble_gettime(NULL, &nowtm);
#if BLE_USE_DIFF_TIME
    inval = ble_difftime(nowt, nowtm,
        bstore->lsend_tsec, bstore->lsend_tmsec);
    if (inval < 0 || inval >= 1000)
        return BLE_STORE_NOTOK;
#else
    if (bstore->lsend_tsec && nowt != bstore->lsend_tsec)
        return BLE_STORE_NOTOK;
#endif

    ulen = (int) strlen(uuid);
    olen = (int) strlen(ops);
    tlen = (int) sizeof(*pdat);
    tlen += ulen + olen + valen;
    if (tlen & 0x1)
        tlen++;

    curlen = bstore->cur_offset;
    if (curlen == 0) {
        size_t snlen;
        struct ble_sess_sn * psn;

        /* Add SN binary data */
        psn = (struct ble_sess_sn *) bstore->ble_buffer;
        snlen = slave->SN ? strlen(slave->SN) : 0;
        psn->sn_magic = BLE_SESS_SNMAGIC;
        psn->sn_len = (unsigned short) snlen;
        psn->sn_time = (unsigned int) nowt;
        if (snlen > 0)
            memcpy(bstore->ble_buffer + sizeof(*psn), slave->SN, snlen);

        if (snlen & 0x1)
            snlen++;

        snlen += sizeof(*psn);
        curlen = (int) snlen;
        /* update timestamp and*/
        bstore->lsend_tsec = nowt;
        bstore->lsend_tmsec = nowtm;
        bstore->cur_offset = curlen;
    }
    if ((curlen + tlen + sizeof(struct ble_sess_data)) >= BLE_SESS_BUFSIZ)
        return BLE_STORE_NOTOK;

    pbuf = (unsigned char *) &(bstore->ble_buffer[curlen]);
    pdat = (struct ble_sess_data *) pbuf;
    pdat->sd_magic   = BLE_SESS_DATAMAGIC;
    pdat->sl_uuid    = (unsigned short) ulen;
    pdat->sl_opcode  = (unsigned short) olen;
    pdat->sl_data    = (unsigned short) valen;
    pdat->sl_type    = 0;
    pbuf += sizeof(*pdat);
    if (ulen > 0) {
        memcpy(pbuf, uuid, (size_t) ulen);
        pbuf += ulen;
    }
    if (olen > 0) {
        memcpy(pbuf, ops, (size_t) olen);
        pbuf += olen;
    }
    if (valen > 0) {
        memcpy(pbuf, value, (size_t) valen);
        pbuf += valen;
    }

    curlen += tlen;
    pbuf = (unsigned char *) &(bstore->ble_buffer[curlen]);
    pdat = (struct ble_sess_data *) pbuf;
    pdat->sd_magic = 0x0;
    bstore->cur_offset = curlen;
    bstore->num_data++;
    if (bstore->num_data >= BLE_SESS_DATA_MAX)
        return BLE_STORE_SEND;
    return BLE_STORE_OK;
}

#define BLE_STORE_B64_LEN ((BLE_SESS_BUFSIZ * 16 / 9) + 1024)
static void ble_send_store_to_pp(ble_var_t * var, ble_slave_dev_t * slave)
{
    time_t ts;
    char * pbuf;
    int curlen, numdat;
    char * tmp, * data_tmp;
    struct ble_sess_store * bstore;
    cJSON * rt_data, * jdata;
    char topic[TOPIC_MAX_LEN];

    pbuf = NULL;
    data_tmp = NULL;
    rt_data = jdata = NULL;
    bstore = &slave->buf_store;
    if (bstore->ble_buffer == NULL)
        return;
    numdat = bstore->num_data;
    curlen = bstore->cur_offset;
    if (numdat < 0 || curlen < 0) {
        bstore->num_data = 0;
        bstore->cur_offset = 0;
        return;
    }

    ts = (time_t) bstore->lsend_tsec;
    pbuf = (unsigned char *) calloc(0x1, BLE_STORE_B64_LEN);
    if (pbuf == NULL) {
        dy_syslog(LOG_ERR, "Error, system out of memory!\n");
        goto err0;
    }

    rt_data = cJSON_CreateObject();
    cJSON_AddStringToObject(rt_data, "port", "BLE");
    cJSON_AddStringToObject(rt_data, "sn", slave->SN);
    cJSON_AddNumberToObject(rt_data, "ts", ts);

    b64_encode(bstore->ble_buffer, curlen, pbuf, BLE_STORE_B64_LEN);

    cJSON_AddNumberToObject(rt_data, "len", curlen);
    cJSON_AddStringToObject(rt_data, "data_b64", pbuf);
    data_tmp = cJSON_Print(rt_data);

    snprintf(topic, TOPIC_MAX_LEN, "ipc/%s/%s/device/%s/data/%s",
        var->sn_str, "BLE", slave->SN, "raw_data");

    ipc_session_publish(var->session, topic, data_tmp, strlen(data_tmp));

err0:
    bstore->lsend_tsec = 0;
    bstore->lsend_tmsec = 0;
    bstore->num_data = 0;
    bstore->cur_offset = 0;
    if (data_tmp != NULL)
        free(data_tmp);
    if (pbuf != NULL)
        free(pbuf);
    if (jdata != NULL)
        cJSON_Delete(jdata);
    if (rt_data != NULL)
        cJSON_Delete(rt_data);
}

void ble_send_raw_data_to_pp(ble_var_t *var, int dev_id, ble_slave_dev_t *slave, char *uuid, char *ops, uint8_t *value, int value_len)
{

    if (slave != NULL)
    {
#if 1
        int ret;
        struct ble_sess_store * bstore;
        long long ms = get_time_stamp_ms();

        slave->last_ms = ms;
        bstore = &slave->buf_store;
        ret = pthread_mutex_lock(&bstore->store_lock);
        if (ret != 0) {
            dy_syslog(LOG_ERR, "Failed to acquire buffer store lock: %d\n", ret);
            return;
        }
        ret = ble_store_slave_data(slave, uuid, ops, value, value_len);
        if (ret == BLE_STORE_OK) {
            pthread_mutex_unlock(&bstore->store_lock);
            return;
        }
        ble_send_store_to_pp(var, slave);
        if (ret == BLE_STORE_NOTOK) {
            ret = ble_store_slave_data(slave, uuid, ops, value, value_len);
            if (ret == BLE_STORE_NOTOK) {
                /* TODO: send the BLE data immediately */
                dy_syslog(LOG_ERR, "Error, dropping BLE raw data!\n");
            }
        }
        /* important: unlock the mutex */
        pthread_mutex_unlock(&bstore->store_lock);
#else
        long long ms = get_time_stamp_ms();

        if (ms - slave->last_ms < 100)
        {
            //return;
        }
        slave->last_ms = ms;

        cJSON *rt_data = cJSON_CreateObject();
        char buf[512] = {0};
        char topic[TOPIC_MAX_LEN] = {0};
        time_t ts = time(NULL);
        cJSON_AddStringToObject(rt_data, "port", "BLE");
        cJSON_AddStringToObject(rt_data, "sn", slave->SN);
        cJSON_AddNumberToObject(rt_data, "ts", ts);

        cJSON *data = cJSON_CreateObject();
        cJSON_AddStringToObject(data, "sn", slave->SN);
        cJSON_AddStringToObject(data, "uuid", uuid);
        if (value != NULL && value_len > 0)
        {
            b64_encode(value, value_len, buf, 512);
            cJSON_AddStringToObject(data, "data", buf);
        }
        cJSON_AddStringToObject(data, "opcode", ops);
        char *tmp = cJSON_Print(data);
        b64_encode(tmp, strlen(tmp), buf, 512);
        cJSON_AddNumberToObject(rt_data, "len", strlen(tmp));
        cJSON_AddStringToObject(rt_data, "data_b64", buf);
        char *data_tmp = cJSON_Print(rt_data);

        snprintf(topic, TOPIC_MAX_LEN, "ipc/%s/%s/device/%s/data/%s", var->sn_str, "BLE",
                 slave->SN, "raw_data");

        ipc_session_publish(var->session, topic, data_tmp, strlen(data_tmp));
        free(data_tmp);
        free(tmp);
        cJSON_Delete(data);
        cJSON_Delete(rt_data);
#endif
    }
    else
    {
#if 1
        ble_send_noslave(var, uuid, ops, value, value_len, NULL, var->sn_str);
#else
        static long long last_ms = 0;

        long long ms = get_time_stamp_ms();

        dy_syslog(LOG_DEBUG, "send out the raw data to pp, no slave\n");
        if (ms - last_ms < 100)
        {
            //return;
        }
        last_ms = ms;

        cJSON *rt_data = cJSON_CreateObject();
        char buf[512] = {0};
        char topic[TOPIC_MAX_LEN] = {0};
        time_t ts = time(NULL);
        cJSON_AddStringToObject(rt_data, "port", "BLE");
        cJSON_AddStringToObject(rt_data, "sn", var->sn_str);
        cJSON_AddNumberToObject(rt_data, "ts", ts);
        cJSON *data = cJSON_CreateObject();
        cJSON_AddNumberToObject(data, "dev_id", dev_id);
        cJSON_AddStringToObject(data, "uuid", uuid);
        if (value != NULL && value_len > 0)
        {
            b64_encode(value, value_len, buf, 512);
            cJSON_AddStringToObject(data, "data", buf);
        }
        cJSON_AddStringToObject(data, "opcode", ops);
        char *tmp = cJSON_Print(data);
        b64_encode(tmp, strlen(tmp), buf, 512);
        cJSON_AddNumberToObject(rt_data, "len", strlen(tmp));
        cJSON_AddStringToObject(rt_data, "data_b64", buf);
        char *data_tmp = cJSON_Print(rt_data);

        snprintf(topic, TOPIC_MAX_LEN, "ipc/%s/%s/device/%s/data/%s", var->sn_str, "BLE",
                 var->sn_str, "raw_data");

        ipc_session_publish(var->session, topic, data_tmp, strlen(data_tmp));
        free(data_tmp);
        free(tmp);
        cJSON_Delete(data);
        cJSON_Delete(rt_data);
#endif
    }
}

bool ble_mqtt_init(ble_var_t *var)
{
    char clientId[MAX_CLIENT_ID_LEN] = {0};

    get_board_sn(var->sn_str);
    snprintf(clientId, MAX_CLIENT_ID_LEN, "ble_%s_%s", var->sn_str, var->dev_name);
    var->session = ipc_session_new(clientId, (void*)var, IPC_DEFAULT);
    if (var->session == NULL)
        return false;

    ipc_session_set_callbacks(var->session, ble_handle_mqtt_recv_msg, NULL);
    ipc_session_subscribe(var->session, SUB_TOPIC);
    ipc_session_start(var->session);


    return true;
}
