/*
 * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */

#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"

#include "esp_bt.h"
#include "esp_bt_device.h"
#include "esp_gap_ble_api.h"
#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_bt_main.h"
#include "esp_system.h"
#include "esp_gatt_common_api.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"

#include "drivers/data_port/ble_spp/ble_spp_client.h"
#include "os/os.h"

#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_INF
#define SYS_LOG_DOMAIN "GATTC_SPP"
#define CONS_ABORT()
#include "sys_log.h"

typedef struct
{
    os_pipe_t pipe_obj;
} __ble_port_data_t;

static __ble_port_data_t s_data;
static ble_client_status_t s_status;
static ble_client_connect_cb s_connect_cb;
static bool s_scan_start_en;
static bool s_scan_start_flag;

#define SYS_LOG_BDA(BDA, SIZE)                            \
    do                                                    \
    {                                                     \
        char buf[0x40];                                   \
        int len = 0;                                      \
        for (int i = 0; i < SIZE - 1; i++)                \
        {                                                 \
            len += SYS_SPRINT(&buf[len], "%02X", BDA[i]); \
            if (len >= sizeof(buf) - 1)                   \
            {                                             \
                break;                                    \
            }                                             \
            if (i + 1 < SIZE)                             \
            {                                             \
                len += SYS_SPRINT(&buf[len], ":");        \
            }                                             \
        }                                                 \
        buf[len] = '\0';                                  \
        SYS_LOG_DBG("%s", buf);                           \
    } while (0)

#define SYS_LOG_STR(STR, LEN)                                    \
    do                                                           \
    {                                                            \
        char buf[0x40];                                          \
        int len = sizeof(buf) - 1 < LEN ? sizeof(buf) - 1 : LEN; \
        memcpy(buf, STR, len);                                   \
        buf[len] = '\0';                                         \
        SYS_LOG_DBG("%s", buf);                                  \
    } while (0)

#define PROFILE_NUM 1
#define PROFILE_APP_ID 0
#define BT_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x"
#define BT_BD_ADDR_HEX(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]
#define ESP_GATT_SPP_SERVICE_UUID 0xABF0
#define SCAN_ALL_THE_TIME 0

struct gattc_profile_inst
{
    esp_gattc_cb_t gattc_cb;
    uint16_t gattc_if;
    uint16_t app_id;
    uint16_t conn_id;
    uint16_t service_start_handle;
    uint16_t service_end_handle;
    uint16_t char_handle;
    esp_bd_addr_t remote_bda;
};

enum
{
    SPP_IDX_SVC,

    SPP_IDX_SPP_DATA_RECV_VAL,

    SPP_IDX_SPP_DATA_NTY_VAL,
    SPP_IDX_SPP_DATA_NTF_CFG,

    SPP_IDX_SPP_COMMAND_VAL,

    SPP_IDX_SPP_STATUS_VAL,
    SPP_IDX_SPP_STATUS_CFG,

#ifdef SUPPORT_HEARTBEAT
    SPP_IDX_SPP_HEARTBEAT_VAL,
    SPP_IDX_SPP_HEARTBEAT_CFG,
#endif

    SPP_IDX_NB,
};

/**
 * @brief 当 ble_client_start() 设置的回调函数为 NULL 时默认的回调函数
 *
 * @param status
 */
static void _cb_hdl_default(ble_client_status_t status)
{
    static char *const str[] = {
        [BLE_CLIENT_STATUS_STOP] = "stop",
        [BLE_CLIENT_STATUS_SCANNING] = "scanning",
        [BLE_CLIENT_STATUS_CONNECTED] = "connected",
        [BLE_CLIENT_STATUS_DISCONNECTED] = "disconnected",
    };
    if (status < sizeof(str) / sizeof(str[0]) && str[status] != NULL)
    {
        SYS_LOG_DBG("connect status: %s", str[status]);
    }
    else
    {
        SYS_LOG_WRN("unknow status: %d", status);
    }
}

/**
 * @brief 更新连接状态并并执行由 ble_client_start() 设置的回调函数
 *
 * @param new_status 设置新的连接状态
 */
static void _port_cb(ble_client_status_t new_status)
{
    if (s_status != new_status)
    {
        s_status = new_status;
        ble_client_connect_cb cb = s_connect_cb;
        if (cb)
        {
            cb(s_status);
        }
        else
        {
            _cb_hdl_default(s_status);
        }
    }
}

/**
 * @brief 启动扫描
 *
 * @param duration 单位为秒
 */
static void _gap_start(uint32_t duration)
{
    if (s_scan_start_en && s_scan_start_flag)
    {
        if (s_status != BLE_CLIENT_STATUS_SCANNING)
        {
            esp_ble_gap_start_scanning(duration);
            _port_cb(BLE_CLIENT_STATUS_SCANNING);
        }
    }
    else
    {
        _port_cb(BLE_CLIENT_STATUS_STOP);
    }
}

/**
 * @brief 关闭扫描
 */
static void _gap_stop(void)
{
    if (s_status == BLE_CLIENT_STATUS_SCANNING)
    {
        s_status = BLE_CLIENT_STATUS_STOP;
        esp_ble_gap_stop_scanning();
    }
}

/// Declare static functions
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);

/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */
static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
    [PROFILE_APP_ID] = {
        .gattc_cb = gattc_profile_event_handler,
        .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
    },
};

static esp_ble_scan_params_t ble_scan_params = {
    .scan_type = BLE_SCAN_TYPE_ACTIVE,
    .own_addr_type = BLE_ADDR_TYPE_PUBLIC,
    .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
    .scan_interval = 0x50,
    .scan_window = 0x30,
    .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE};

static char *s_device_name;
static bool is_connect = false;
static uint16_t spp_conn_id = 0;
static uint16_t spp_mtu_size = 23;
static uint16_t cmd = 0;
static uint16_t spp_srv_start_handle = 0;
static uint16_t spp_srv_end_handle = 0;
static uint16_t spp_gattc_if = 0xff;
static char *notify_value_p = NULL;
static int notify_value_offset = 0;
static int notify_value_count = 0;
static uint16_t count = SPP_IDX_NB;
static esp_gattc_db_elem_t *db = NULL;
static esp_ble_gap_cb_param_t scan_rst;
static QueueHandle_t cmd_reg_queue = NULL;
QueueHandle_t spp_uart_queue = NULL;

#ifdef SUPPORT_HEARTBEAT
static uint8_t heartbeat_s[9] = {'E', 's', 'p', 'r', 'e', 's', 's', 'i', 'f'};
static QueueHandle_t cmd_heartbeat_queue = NULL;
#endif

static esp_bt_uuid_t spp_service_uuid = {
    .len = ESP_UUID_LEN_16,
    .uuid = {
        .uuid16 = ESP_GATT_SPP_SERVICE_UUID,
    },
};

static void notify_event_handler(esp_ble_gattc_cb_param_t *p_data)
{
    uint8_t handle = 0;

    if (p_data->notify.is_notify == true)
    {
        SYS_LOG_DBG("+NOTIFY:handle = %d,length = %d ", p_data->notify.handle, p_data->notify.value_len);
    }
    else
    {
        SYS_LOG_DBG("+INDICATE:handle = %d,length = %d ", p_data->notify.handle, p_data->notify.value_len);
    }
    handle = p_data->notify.handle;
    if (db == NULL)
    {
        SYS_LOG_ERR(" db is NULL");
        return;
    }
    if (handle == db[SPP_IDX_SPP_DATA_NTY_VAL].attribute_handle)
    {
        SYS_LOG_DUMP(p_data->notify.value, p_data->notify.value_len, 1, 0);
        os_pipe_fifo_fill(&s_data.pipe_obj, p_data->notify.value, p_data->notify.value_len);
    }
    else if (handle == ((db + SPP_IDX_SPP_STATUS_VAL)->attribute_handle))
    {
        SYS_LOG_STR((char *)p_data->notify.value, p_data->notify.value_len);
        // TODO:server notify status characteristic
    }
    else
    {
        SYS_LOG_STR((char *)p_data->notify.value, p_data->notify.value_len);
    }
}

static void free_gattc_srv_db(void)
{
    is_connect = false;
    spp_gattc_if = 0xff;
    spp_conn_id = 0;
    spp_mtu_size = 23;
    cmd = 0;
    spp_srv_start_handle = 0;
    spp_srv_end_handle = 0;
    notify_value_p = NULL;
    notify_value_offset = 0;
    notify_value_count = 0;
    if (db)
    {
        free(db);
        db = NULL;
    }
}

static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
    uint8_t *adv_name = NULL;
    uint8_t adv_name_len = 0;
    esp_err_t err;

    switch (event)
    {
    case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
    {
        SYS_LOG_DBG("ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT");
        if ((err = param->scan_param_cmpl.status) != ESP_BT_STATUS_SUCCESS)
        {
            SYS_LOG_ERR("Scan param set failed: %s", esp_err_to_name(err));
            break;
        }
        s_scan_start_en = true;
        _gap_start(0xFFFF);
        break;
    }
    case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
        // scan start complete event to indicate scan start successfully or failed
        if ((err = param->scan_start_cmpl.status) != ESP_BT_STATUS_SUCCESS)
        {
            SYS_LOG_ERR("ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: Scan start failed: %s", esp_err_to_name(err));
            break;
        }
        SYS_LOG_DBG("ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: Scan start successed. Target name: '%s'", s_device_name);
        break;
    case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
        if ((err = param->scan_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS)
        {
            SYS_LOG_ERR("ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: Scan stop failed: %s", esp_err_to_name(err));
            break;
        }
        if (is_connect == false)
        {
            esp_ble_gattc_open(gl_profile_tab[PROFILE_APP_ID].gattc_if, scan_rst.scan_rst.bda, scan_rst.scan_rst.ble_addr_type, true);
            SYS_LOG_DBG("ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: Scan stop successed");
            SYS_LOG_DBG("Connect to the remote device.");
        }
        else
        {
            _port_cb(BLE_CLIENT_STATUS_STOP);
        }
        break;
    case ESP_GAP_BLE_SCAN_RESULT_EVT:
    {
        esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param;

        static uint8_t con_scan_count;

        switch (scan_result->scan_rst.search_evt)
        {
        case ESP_GAP_SEARCH_INQ_RES_EVT:
            adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
            if (s_device_name != NULL)
            {
                if (adv_name != NULL && strlen(s_device_name) == adv_name_len)
                {
                    if (strncmp((char *)adv_name, s_device_name, adv_name_len) == 0)
                    {
                        memcpy(&(scan_rst), scan_result, sizeof(esp_ble_gap_cb_param_t));
                        _gap_stop();
                        SYS_LOG_DBG("");
                        SYS_LOG_BDA(scan_result->scan_rst.bda, 6);
                        SYS_LOG_DBG("Searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len);
                        SYS_LOG_DBG("Searched Device Name Len %d", adv_name_len);
                        SYS_LOG_STR(adv_name, adv_name_len);
                    }
                }
            }
            else
            {
                _gap_stop();
            }
            if (con_scan_count++ == 100)
            {
                os_thread_sleep(100);
            }
            break;
        case ESP_GAP_SEARCH_INQ_CMPL_EVT:
            SYS_LOG_DBG("ESP_GAP_SEARCH_INQ_CMPL_EVT");
            break;
        default:
            SYS_LOG_DBG("scan_result->scan_rst.search_evt = %d", scan_result->scan_rst.search_evt);
            break;
        }
        break;
    }
    case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
        if ((err = param->adv_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS)
        {
            SYS_LOG_ERR("ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: Adv stop failed: %s", esp_err_to_name(err));
        }
        else
        {
            SYS_LOG_DBG("ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: Stop adv successfully");
        }
        break;
    default:
        SYS_LOG_DBG("event = %d", event);
        break;
    }
}

static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
    SYS_LOG_DBG("EVT %d, gattc if %d", event, gattc_if);

    /* If event is register event, store the gattc_if for each profile */
    if (event == ESP_GATTC_REG_EVT)
    {
        if (param->reg.status == ESP_GATT_OK)
        {
            gl_profile_tab[param->reg.app_id].gattc_if = gattc_if;
        }
        else
        {
            SYS_LOG_DBG("Reg app failed, app_id %04x, status %d", param->reg.app_id, param->reg.status);
            return;
        }
    }
    /* If the gattc_if equal to profile A, call profile A cb handler,
     * so here call each profile's callback */
    do
    {
        int idx;
        for (idx = 0; idx < PROFILE_NUM; idx++)
        {
            if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
                gattc_if == gl_profile_tab[idx].gattc_if)
            {
                if (gl_profile_tab[idx].gattc_cb)
                {
                    gl_profile_tab[idx].gattc_cb(event, gattc_if, param);
                }
            }
        }
    } while (0);

    if (event == ESP_GATTC_CLOSE_EVT)
    {
        SYS_LOG_DBG("ESP_GATTC_CLOSE_EVT");
        if (s_scan_start_flag)
        {
            _port_cb(BLE_CLIENT_STATUS_DISCONNECTED);
        }
        _gap_start(SCAN_ALL_THE_TIME);
    }
}

static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
    esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;

    switch (event)
    {
    case ESP_GATTC_REG_EVT:
        SYS_LOG_DBG("ESP_GATTC_REG_EVT: set scan params");
        esp_ble_gap_set_scan_params(&ble_scan_params);
        break;
    case ESP_GATTC_CONNECT_EVT:
        SYS_LOG_DBG("ESP_GATTC_CONNECT_EVT: conn_id=%d, gatt_if = %d", spp_conn_id, gattc_if);
        SYS_LOG_DBG("REMOTE BDA:");
        SYS_LOG_BDA(gl_profile_tab[PROFILE_APP_ID].remote_bda, sizeof(esp_bd_addr_t));
        spp_gattc_if = gattc_if;
        is_connect = true;
        spp_conn_id = p_data->connect.conn_id;
        memcpy(gl_profile_tab[PROFILE_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t));
        esp_ble_gattc_search_service(spp_gattc_if, spp_conn_id, &spp_service_uuid);
        _port_cb(BLE_CLIENT_STATUS_CONNECTED);
        break;
    case ESP_GATTC_DISCONNECT_EVT:
        SYS_LOG_DBG("ESP_GATTC_DISCONNECT_EVT: disconnect");
        free_gattc_srv_db();
        break;
    case ESP_GATTC_SEARCH_RES_EVT:
        SYS_LOG_DBG("ESP_GATTC_SEARCH_RES_EVT: start_handle = %d, end_handle = %d, UUID:0x%04x", p_data->search_res.start_handle, p_data->search_res.end_handle, p_data->search_res.srvc_id.uuid.uuid.uuid16);
        spp_srv_start_handle = p_data->search_res.start_handle;
        spp_srv_end_handle = p_data->search_res.end_handle;
        break;
    case ESP_GATTC_SEARCH_CMPL_EVT:
        SYS_LOG_DBG("ESP_GATTC_SEARCH_CMPL_EVT: conn_id = %x, status %d", spp_conn_id, p_data->search_cmpl.status);
        esp_ble_gattc_send_mtu_req(gattc_if, spp_conn_id);
        break;
    case ESP_GATTC_REG_FOR_NOTIFY_EVT:
    {
        if (p_data->reg_for_notify.status == ESP_GATT_OK)
        {
            SYS_LOG_DBG("ESP_GATTC_REG_FOR_NOTIFY_EVT: Index = %d,status = %d,handle = %d", cmd, p_data->reg_for_notify.status, p_data->reg_for_notify.handle);
        }
        else
        {
            SYS_LOG_ERR("ESP_GATTC_REG_FOR_NOTIFY_EVT, status = %d", p_data->reg_for_notify.status);
            break;
        }
        uint16_t notify_en = 1;
        esp_ble_gattc_write_char_descr(
            spp_gattc_if,
            spp_conn_id,
            (db + cmd + 1)->attribute_handle,
            sizeof(notify_en),
            (uint8_t *)&notify_en,
            ESP_GATT_WRITE_TYPE_RSP,
            ESP_GATT_AUTH_REQ_NONE);

        break;
    }
    case ESP_GATTC_NOTIFY_EVT:
        SYS_LOG_DBG("ESP_GATTC_NOTIFY_EVT");
        notify_event_handler(p_data);
        break;
    case ESP_GATTC_READ_CHAR_EVT:
        SYS_LOG_DBG("ESP_GATTC_READ_CHAR_EVT");
        break;
    case ESP_GATTC_WRITE_CHAR_EVT:
        if (param->write.status == ESP_GATT_OK)
        {
            SYS_LOG_DBG("ESP_GATTC_WRITE_CHAR_EVT: status = %d, handle = %d", param->write.status, param->write.handle);
        }
        else
        {
            SYS_LOG_ERR("ESP_GATTC_WRITE_CHAR_EVT, error status = %d", p_data->write.status);
            _gap_start(SCAN_ALL_THE_TIME);
        }
        break;
    case ESP_GATTC_PREP_WRITE_EVT:
        SYS_LOG_DBG("ESP_GATTC_PREP_WRITE_EVT");
        break;
    case ESP_GATTC_EXEC_EVT:
        SYS_LOG_DBG("ESP_GATTC_EXEC_EVT");
        break;
    case ESP_GATTC_WRITE_DESCR_EVT:
        if (p_data->write.status == ESP_GATT_OK)
        {
            SYS_LOG_DBG("ESP_GATTC_WRITE_DESCR_EVT: status =%d, handle = %d ", p_data->write.status, p_data->write.handle);
        }
        else
        {
            SYS_LOG_ERR("ESP_GATTC_WRITE_DESCR_EVT, error status = %d", p_data->write.status);
            break;
        }
        switch (cmd)
        {
        case SPP_IDX_SPP_DATA_NTY_VAL:
            cmd = SPP_IDX_SPP_STATUS_VAL;
            xQueueSend(cmd_reg_queue, &cmd, 10 / portTICK_PERIOD_MS);
            break;
        case SPP_IDX_SPP_STATUS_VAL:
#ifdef SUPPORT_HEARTBEAT
            cmd = SPP_IDX_SPP_HEARTBEAT_VAL;
            xQueueSend(cmd_reg_queue, &cmd, 10 / portTICK_PERIOD_MS);
#endif
            break;
#ifdef SUPPORT_HEARTBEAT
        case SPP_IDX_SPP_HEARTBEAT_VAL:
            xQueueSend(cmd_heartbeat_queue, &cmd, 10 / portTICK_PERIOD_MS);
            break;
#endif
        default:
            break;
        };
        break;
    case ESP_GATTC_CFG_MTU_EVT:
        if (p_data->cfg_mtu.status != ESP_OK)
        {
            break;
        }
        SYS_LOG_DBG("ESP_GATTC_CFG_MTU_EVT: +MTU:%d", p_data->cfg_mtu.mtu);
        spp_mtu_size = p_data->cfg_mtu.mtu;

        db = (esp_gattc_db_elem_t *)malloc(count * sizeof(esp_gattc_db_elem_t));
        if (db == NULL)
        {
            SYS_LOG_ERR("malloc db falied");
            break;
        }
        if (esp_ble_gattc_get_db(spp_gattc_if, spp_conn_id, spp_srv_start_handle, spp_srv_end_handle, db, &count) != ESP_GATT_OK)
        {
            SYS_LOG_ERR("get db falied");
            break;
        }
        if (count != SPP_IDX_NB)
        {
            SYS_LOG_ERR("get db count != SPP_IDX_NB, count = %d, SPP_IDX_NB = %d", count, SPP_IDX_NB);
            break;
        }
        for (int i = 0; i < SPP_IDX_NB; i++)
        {
            switch ((db + i)->type)
            {
            case ESP_GATT_DB_PRIMARY_SERVICE:
                SYS_LOG_DBG("attr_type = PRIMARY_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x",
                            (db + i)->attribute_handle, (db + i)->start_handle, (db + i)->end_handle, (db + i)->properties, (db + i)->uuid.uuid.uuid16);
                break;
            case ESP_GATT_DB_SECONDARY_SERVICE:
                SYS_LOG_DBG("attr_type = SECONDARY_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x",
                            (db + i)->attribute_handle, (db + i)->start_handle, (db + i)->end_handle, (db + i)->properties, (db + i)->uuid.uuid.uuid16);
                break;
            case ESP_GATT_DB_CHARACTERISTIC:
                SYS_LOG_DBG("attr_type = CHARACTERISTIC,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x",
                            (db + i)->attribute_handle, (db + i)->start_handle, (db + i)->end_handle, (db + i)->properties, (db + i)->uuid.uuid.uuid16);
                break;
            case ESP_GATT_DB_DESCRIPTOR:
                SYS_LOG_DBG("attr_type = DESCRIPTOR,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x",
                            (db + i)->attribute_handle, (db + i)->start_handle, (db + i)->end_handle, (db + i)->properties, (db + i)->uuid.uuid.uuid16);
                break;
            case ESP_GATT_DB_INCLUDED_SERVICE:
                SYS_LOG_DBG("attr_type = INCLUDED_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x",
                            (db + i)->attribute_handle, (db + i)->start_handle, (db + i)->end_handle, (db + i)->properties, (db + i)->uuid.uuid.uuid16);
                break;
            case ESP_GATT_DB_ALL:
                SYS_LOG_DBG("attr_type = ESP_GATT_DB_ALL,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x",
                            (db + i)->attribute_handle, (db + i)->start_handle, (db + i)->end_handle, (db + i)->properties, (db + i)->uuid.uuid.uuid16);
                break;
            default:
                break;
            }
        }
        cmd = SPP_IDX_SPP_DATA_NTY_VAL;
        xQueueSend(cmd_reg_queue, &cmd, 10 / portTICK_PERIOD_MS);
        break;
    case ESP_GATTC_SRVC_CHG_EVT:
        SYS_LOG_DBG("ESP_GATTC_SRVC_CHG_EVT");
        break;
    default:
        break;
    }
}

void spp_client_reg_task(void *arg)
{
    uint16_t cmd_id;
    for (;;)
    {
        vTaskDelay(100 / portTICK_PERIOD_MS);
        if (xQueueReceive(cmd_reg_queue, &cmd_id, portMAX_DELAY))
        {
            if (db != NULL)
            {
                if (cmd_id == SPP_IDX_SPP_DATA_NTY_VAL)
                {
                    SYS_LOG_DBG("SPP_IDX_SPP_DATA_NTY_VAL: Index = %d,UUID = 0x%04x, handle = %d ", cmd_id, (db + SPP_IDX_SPP_DATA_NTY_VAL)->uuid.uuid.uuid16, (db + SPP_IDX_SPP_DATA_NTY_VAL)->attribute_handle);
                    esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db + SPP_IDX_SPP_DATA_NTY_VAL)->attribute_handle);
                }
                else if (cmd_id == SPP_IDX_SPP_STATUS_VAL)
                {
                    SYS_LOG_DBG("SPP_IDX_SPP_STATUS_VAL: Index = %d,UUID = 0x%04x, handle = %d ", cmd_id, (db + SPP_IDX_SPP_STATUS_VAL)->uuid.uuid.uuid16, (db + SPP_IDX_SPP_STATUS_VAL)->attribute_handle);
                    esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db + SPP_IDX_SPP_STATUS_VAL)->attribute_handle);
                }
#ifdef SUPPORT_HEARTBEAT
                else if (cmd_id == SPP_IDX_SPP_HEARTBEAT_VAL)
                {
                    SYS_LOG_DBG("SPP_IDX_SPP_HEARTBEAT_VAL: Index = %d,UUID = 0x%04x, handle = %d ", cmd_id, (db + SPP_IDX_SPP_HEARTBEAT_VAL)->uuid.uuid.uuid16, (db + SPP_IDX_SPP_HEARTBEAT_VAL)->attribute_handle);
                    esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db + SPP_IDX_SPP_HEARTBEAT_VAL)->attribute_handle);
                }
#endif
            }
        }
    }
}

#ifdef SUPPORT_HEARTBEAT
void spp_heart_beat_task(void *arg)
{
    uint16_t cmd_id;

    for (;;)
    {
        vTaskDelay(50 / portTICK_PERIOD_MS);
        if (xQueueReceive(cmd_heartbeat_queue, &cmd_id, portMAX_DELAY))
        {
            while (1)
            {
                if ((is_connect == true) && (db != NULL) && ((db + SPP_IDX_SPP_HEARTBEAT_VAL)->properties & (ESP_GATT_CHAR_PROP_BIT_WRITE_NR | ESP_GATT_CHAR_PROP_BIT_WRITE)))
                {
                    esp_ble_gattc_write_char(spp_gattc_if,
                                             spp_conn_id,
                                             (db + SPP_IDX_SPP_HEARTBEAT_VAL)->attribute_handle,
                                             sizeof(heartbeat_s),
                                             (uint8_t *)heartbeat_s,
                                             ESP_GATT_WRITE_TYPE_RSP,
                                             ESP_GATT_AUTH_REQ_NONE);
                    vTaskDelay(5000 / portTICK_PERIOD_MS);
                }
                else
                {
                    SYS_LOG_DBG("disconnect");
                    break;
                }
            }
        }
    }
}
#endif

void ble_client_appRegister(void)
{
    esp_err_t status;
    char err_msg[20];

    SYS_LOG_DBG("register callback");

    // register the scan callback function to the gap module
    if ((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK)
    {
        SYS_LOG_ERR("gap register error: %s", esp_err_to_name_r(status, err_msg, sizeof(err_msg)));
        return;
    }
    // register the callback function to the gattc module
    if ((status = esp_ble_gattc_register_callback(esp_gattc_cb)) != ESP_OK)
    {
        SYS_LOG_ERR("gattc register error: %s", esp_err_to_name_r(status, err_msg, sizeof(err_msg)));
        return;
    }
    esp_ble_gattc_app_register(PROFILE_APP_ID);

    esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(200);
    if (local_mtu_ret)
    {
        SYS_LOG_ERR("set local  MTU failed: %s", esp_err_to_name_r(local_mtu_ret, err_msg, sizeof(err_msg)));
    }

    cmd_reg_queue = xQueueCreate(10, sizeof(uint32_t));
    xTaskCreate(spp_client_reg_task, "spp_client_reg_task", 2048, NULL, 10, NULL);

#ifdef SUPPORT_HEARTBEAT
    cmd_heartbeat_queue = xQueueCreate(10, sizeof(uint32_t));
    xTaskCreate(spp_heart_beat_task, "spp_heart_beat_task", 2048, NULL, 10, NULL);
#endif
}

static void _ble_spp_client_init(void)
{
    esp_err_t ret;

    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));

    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();

    ret = esp_bt_controller_init(&bt_cfg);
    if (ret)
    {
        SYS_LOG_ERR("enable controller failed: %s", esp_err_to_name(ret));
        return;
    }

    ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
    if (ret)
    {
        SYS_LOG_ERR("enable controller failed: %s", esp_err_to_name(ret));
        return;
    }

    SYS_LOG_DBG("init bluetooth");
    ret = esp_bluedroid_init();
    if (ret)
    {
        SYS_LOG_ERR("init bluetooth failed: %s", esp_err_to_name(ret));
        return;
    }
    ret = esp_bluedroid_enable();
    if (ret)
    {
        SYS_LOG_ERR("enable bluetooth failed: %s", esp_err_to_name(ret));
        return;
    }

    ble_client_appRegister();
}

/* port ------------------------------------------------------------------------------------------------------------*/

typedef struct
{
    const char *tar_name;
    ble_client_connect_cb cb;
} __ble_start_opt_t;

static sb_data_port_t s_port;

static void _port_init(sb_data_port_t *port);
static void _port_uninit(sb_data_port_t *port);
static void _port_start(sb_data_port_t *port, SB_DATA_PORT_RXMODE mode, const void *opt);
static void _port_stop(sb_data_port_t *port);
static int _port_write(sb_data_port_t *port, const void *data, uint32_t size, uint32_t wait_ms);
static int _port_read(sb_data_port_t *port, void *data, uint32_t size, uint32_t wait_ms);
static void *_port_get_recv_pipe(sb_data_port_t *port);

static sb_data_port_vtable_t const s_port_vtable = {
    .init = _port_init,
    .uninit = _port_uninit,
    .start = _port_start,
    .stop = _port_stop,
    .write = _port_write,
    .read = _port_read,
};

static void _port_init(sb_data_port_t *port)
{
    _ble_spp_client_init();
}

static void _port_uninit(sb_data_port_t *port)
{
}

static void _port_start(sb_data_port_t *port, SB_DATA_PORT_RXMODE mode, const void *opt)
{
    const __ble_start_opt_t *p = opt;

    do
    {
        if (s_device_name)
        {
            if (is_connect != false)
            {
                ble_client_stop();
            }
            for (int i = 0; i < 100; i++)
            {
                if (is_connect == false)
                {
                    os_thread_sleep(100);
                    break;
                }
                os_thread_sleep(10);
            }

            if (is_connect != false)
            {
                SYS_LOG_WRN("can not stop gap scanning!!");
                break;
            }

            os_free(s_device_name);
        }

        int len = strlen(p->tar_name);
        s_device_name = os_malloc(len + 1);
        if (s_device_name == NULL)
        {
            SYS_LOG_WRN("insufficient memory");
            break;
        }

        strcpy(s_device_name, p->tar_name);
        s_connect_cb = p->cb;

        s_scan_start_flag = true;
        _gap_start(0xFFFF);

    } while (0);
}

static void _port_stop(sb_data_port_t *port)
{
    if (s_scan_start_flag)
    {
        s_scan_start_flag = false;
        _gap_stop();
        if (is_connect == true)
        {
            esp_ble_gap_disconnect(gl_profile_tab[PROFILE_APP_ID].remote_bda);
        }
    }
}

static int _port_write(sb_data_port_t *port, const void *data, uint32_t size, uint32_t wait_ms)
{
    int wtotal = size;
    while (size)
    {
        int wsize = size;
        if (wsize > (1 << sizeof(uint16_t) * 2) - 1)
        {
            wsize = (1 << sizeof(uint16_t) * 2) - 1;
        }

        if ((is_connect == true) && (db != NULL) && ((db + SPP_IDX_SPP_DATA_RECV_VAL)->properties & (ESP_GATT_CHAR_PROP_BIT_WRITE_NR | ESP_GATT_CHAR_PROP_BIT_WRITE)))
        {
            if (esp_ble_gattc_write_char(spp_gattc_if,
                                         spp_conn_id,
                                         (db + SPP_IDX_SPP_DATA_RECV_VAL)->attribute_handle,
                                         wsize,
                                         (void *)data,
                                         ESP_GATT_WRITE_TYPE_NO_RSP,
                                         ESP_GATT_AUTH_REQ_NONE) != ESP_OK)
            {
                return -1;
            }
        }
        else
        {
            return -1;
        }

        size -= wsize;
        data = &((uint8_t *)data)[wsize];
    }
    return wtotal;
}

static int _port_read(sb_data_port_t *port, void *data, uint32_t size, uint32_t wait_ms)
{
    SYS_LOG_ERR("TODO");
    return -1;
}

static void *_port_get_recv_pipe(sb_data_port_t *port)
{
    __ble_port_data_t *data = port->data;
    return &data->pipe_obj;
}

sb_data_port_t *sb_ble_client_port_init(uint32_t buffer_size, int rx_task_priority)
{
    /* 设置 s_port */
    s_port.vtable = &s_port_vtable;
    s_port.data = &s_data;

    /* s_data::pipe_obj */
    if (os_pipe_is_valid(&s_data.pipe_obj))
    {
        SYS_LOG_WRN("ble initialize again!");
        return &s_port;
    }
    os_pipe_create(&s_data.pipe_obj, buffer_size);

    s_port.vtable->init(&s_port);

    return &s_port;
}

sb_data_port_t *ble_client_port_bind(void)
{
    return &s_port;
}

/**
 * @brief 开始扫描指定的设备名并连接
 *
 * @param tar_name  目标设备名
 * @param cb        回调函数， NULL 值可用
 */
void ble_client_start(const char *tar_name, ble_client_connect_cb cb)
{
    __ble_start_opt_t opt = {
        .tar_name = tar_name,
        .cb = cb,
    };
    s_port_vtable.start(&s_port, SB_DATA_PORT_RXMODE_AUTO_PULL_DATA, &opt);
}

/**
 * @brief 停止扫描，断开连接
 */
void ble_client_stop(void)
{
    s_port_vtable.stop(&s_port);
}

/**
 * @brief 获取当前的链接状态
 *
 * @return ble_client_status_t
 */
ble_client_status_t ble_client_get_gap_status(void)
{
    return s_status;
}
