/*
*	\file 			ble.c
*
*	\author 		Aritz Martikorena
*
*	\date 			2 Nov 2021
*
*	\brief 			BLE application functions
*
*	\par
*
*	COPYRIGHT NOTICE: (c) 2021, Reactec Ltd.
*	All rights reserved.
*/

#include <stdio.h>
#include <logging/log.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/addr.h>
#include <bluetooth/gatt.h>
#include <bluetooth/conn.h>
#include <bluetooth/scan.h>
#include <bluetooth/services/bas.h>
#include <al/data/rap.h>
#include "ble.h"

LOG_MODULE_REGISTER(BLE, LOG_LEVEL_INF);

/////////////////////////////////////////////////////////////////////////////
// Defines

// Device name length
#define DEV_NAME_LEN        15
// Cohort ID size
#define OP_COHORT_SIZE      3
// TX power (dBm)
#if IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_PLUS_8)
#define TX_POWER_LEVEL_DBM 8
#elif IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_PLUS_7)
#define TX_POWER_LEVEL_DBM 7
#elif IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_PLUS_6)
#define TX_POWER_LEVEL_DBM 6
#elif IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_PLUS_5)
#define TX_POWER_LEVEL_DBM 5
#elif IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_PLUS_4)
#define TX_POWER_LEVEL_DBM 4
#elif IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_PLUS_3)
#define TX_POWER_LEVEL_DBM 3
#elif IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_PLUS_2)
#define TX_POWER_LEVEL_DBM 2
#elif IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_0)
#define TX_POWER_LEVEL_DBM 1
#endif

// BLE device queue to process scan response
#define DEV_QUEUE_SIZE      16

/////////////////////////////////////////////////////////////////////////////
// Types

#pragma pack(push, 1)

typedef struct
{
    const uint16_t reactec_id;
    const uint8_t dev_type;
    uint32_t cust_id;
    uint8_t op_guid[OP_GUID_SIZE];
    uint8_t cohort_id[OP_COHORT_SIZE];
}
adv_data_t;

typedef struct
{
    const uint16_t reactec_id;
    const uint8_t dev_type;
    uint32_t cust_id;
    uint8_t op_guid[OP_GUID_SIZE];
    uint32_t cohort_id;
    uint16_t uwb_addr;
}
ext_adv_data_t;

#pragma pack(pop)

typedef struct {
    bt_addr_le_t ble_addr;
    ble_dev_type_t dev_type;
    int8_t rssi;
    uint32_t cust_id;
    uint8_t op_guid[OP_GUID_SIZE];
    bool guid_avail;
    uint32_t cohort_id;
    uint16_t uwb_addr;
}
ble_dev_t;

/////////////////////////////////////////////////////////////////////////////
// Static Data

static char dev_name[DEV_NAME_LEN + 1];
static struct bt_le_ext_adv *std_adv_ctx = NULL;
static struct bt_le_ext_adv *ext_adv_ctx = NULL;

static struct bt_le_adv_param std_adv_param = {
    .id = BT_ID_DEFAULT,
    .sid = 0,
    .secondary_max_skip = 0,
    .options = BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_USE_IDENTITY | BT_LE_ADV_OPT_SCANNABLE,   
    .interval_min = 160,    // 100 ms (160 * 0.625)
    .interval_max = 240,    // 150 ms (240 * 0.625)
    .peer = NULL
};

static struct bt_le_adv_param ext_adv_param = {
    .id = BT_ID_DEFAULT,
    .sid = 1,
    .secondary_max_skip = 0,
    .options = BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_EXT_ADV | BT_LE_ADV_OPT_CODED |
               BT_LE_ADV_OPT_USE_IDENTITY | BT_LE_ADV_OPT_USE_NAME,
    .interval_min = 160,    // 100 ms (160 * 0.625)
    .interval_max = 240,    // 150 ms (240 * 0.625)
    .peer = NULL
};

// BLE advertising data
static adv_data_t std_adv_data =
    {.reactec_id = CONFIG_BT_COMPANY_ID, .dev_type = BLE_DEV_STD_WEARABLE};
static struct bt_data std_ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    BT_DATA(BT_DATA_MANUFACTURER_DATA, &std_adv_data, sizeof(std_adv_data)),
};

// Extended advertising data
static ext_adv_data_t ext_adv_data =
    {.reactec_id = CONFIG_BT_COMPANY_ID, .dev_type = BLE_DEV_EXT_WEARABLE};
static struct bt_data ext_ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    BT_DATA(BT_DATA_MANUFACTURER_DATA, &ext_adv_data, sizeof(ext_adv_data)),
    BT_DATA_BYTES(BT_DATA_TX_POWER, TX_POWER_LEVEL_DBM),
};

// Scan response data
static struct bt_data sd[] = {
    BT_DATA(BT_DATA_NAME_COMPLETE, dev_name, DEV_NAME_LEN),
    BT_DATA_BYTES(BT_DATA_TX_POWER, TX_POWER_LEVEL_DBM),
};

// Scan parameters
static struct bt_le_scan_param scan_param = {
    .type               = BT_LE_SCAN_TYPE_PASSIVE,
    .options            = BT_LE_SCAN_OPT_CODED,
    .interval           = CONFIG_BT_BACKGROUND_SCAN_INTERVAL,
    .window             = CONFIG_BT_BACKGROUND_SCAN_WINDOW,
};
static struct bt_scan_init_param init_scan_param = {
    .scan_param         = &scan_param,
    .conn_param         = BT_LE_CONN_PARAM_DEFAULT,
    .connect_if_match   = false,
};

// Flag indicating where scanning is running
static bool ble_scan_started;
// Flag indicating where advertising is running
static bool ble_adv_started;

// Current connection count
static uint8_t ble_conn_count;

static ble_dev_t dev_queue[DEV_QUEUE_SIZE];

/////////////////////////////////////////////////////////////////////////////
// Private Function Prototypes

static void scan_filter_match(struct bt_scan_device_info *device_info,
                              struct bt_scan_filter_match *filter_match,
			                  bool connectable);
static void scan_connecting_error(struct bt_scan_device_info *device_info);
static void scan_connecting(struct bt_scan_device_info *device_info,
			                struct bt_conn *conn);
static void connected_cb(struct bt_conn *conn, uint8_t err);
static void disconnected_cb(struct bt_conn *conn, uint8_t reason);
static bool le_param_req_cb(struct bt_conn *conn, struct bt_le_conn_param *param);
static void le_param_updated_cb(struct bt_conn *conn, uint16_t interval,
				                uint16_t latency, uint16_t timeout);
static void ext_adv_sent(struct bt_le_ext_adv *adv,
                         struct bt_le_ext_adv_sent_info *info);
static void ext_adv_connected(struct bt_le_ext_adv *adv,
                         struct bt_le_ext_adv_connected_info *info);
static void ext_adv_scanned(struct bt_le_ext_adv *adv,
                         struct bt_le_ext_adv_scanned_info *info);
static const char* phy_id_to_str(const uint8_t id);

// Connection callback structure
static const struct bt_conn_cb conn_callbacks = {
    .connected = connected_cb,
    .disconnected = disconnected_cb,
    .le_param_req = le_param_req_cb,
    .le_param_updated = le_param_updated_cb,
};

// Advertising callback structure
static const struct bt_le_ext_adv_cb adv_callbacks = {
    .sent = ext_adv_sent,
    .connected = ext_adv_connected,
    .scanned = ext_adv_scanned,
};

// Scanning definition
BT_SCAN_CB_INIT(scan_callbacks, scan_filter_match, NULL,
		scan_connecting_error, scan_connecting);

/////////////////////////////////////////////////////////////////////////////
// Public Functions

/*!
*   \brief      BLE initialization
*   \param      None
*   \return     bool, true if successful, false in case of error
*/
bool ble_init(void)
{
    int err;
    bt_addr_le_t bt_addr;
    const struct bt_scan_manufacturer_data scan_manu_data =
        {.data = &ext_adv_data.reactec_id, .data_len = sizeof(ext_adv_data.reactec_id)};

    // Initialize the Bluetooth stack
    err = bt_enable(NULL);
    if (err) {
        LOG_ERR("Bluetooth init failed (err %d)", err);
        return false;
    }

    LOG_INF("Bluetooth stack init success");

    // Get bt address
    bt_id_get(&bt_addr, CONFIG_BT_ID_MAX);
    LOG_INF("addr %02X:%02X:%02X:%02X:%02X:%02X",
            bt_addr.a.val[5], bt_addr.a.val[4], bt_addr.a.val[3],
            bt_addr.a.val[2], bt_addr.a.val[1], bt_addr.a.val[0]);

    // Register for connection callbacks
    bt_conn_cb_register(&conn_callbacks);

    // Set the device name
    snprintf((char *)dev_name, sizeof(dev_name), "Wearable-%02x%02x%02x",
        bt_addr.a.val[2], bt_addr.a.val[1], bt_addr.a.val[0]);
    bt_set_name(dev_name);
    LOG_INF("name %s", bt_get_name());
#if 0
    // Init Scanning
    bt_scan_init(&init_scan_param);
    bt_scan_cb_register(&scan_callbacks);
    err = bt_scan_filter_add(BT_SCAN_FILTER_TYPE_MANUFACTURER_DATA, &scan_manu_data);
    if (err) {
        LOG_ERR("scanning filters cannot be set (err %d)", err);
        return false;
    }
    err = bt_scan_filter_enable(BT_SCAN_MANUFACTURER_DATA_FILTER, false);
    if (err) {
        LOG_ERR("scanning filters cannot be turned on (err %d)", err);
        return false;
    }
#endif
    // TODO: update DIS service characteristics
    // Serial Number, Firmware, Hardware and Software revision

    return true;
}

/*!
*   \brief      Starts BLE scanning
*   \param      void
*   \return     bool, true if successful, false otherwise
*/
bool ble_start_scanning(void)
{
    int err;

    if (!ble_scan_started) {
        err = bt_scan_start(BT_SCAN_TYPE_SCAN_PASSIVE);
        if (err) {
            LOG_ERR("scanning failed to start (err %d)", err);
            return false;
        }
        ble_scan_started = true;
        LOG_INF("scanning started");
    }
    else {
        LOG_INF("scanner already started");
    }

    return true;
}

/*!
*   \brief      Stops BLE scanning
*   \param      void
*   \return     bool, true if successful, false otherwise
*/
bool ble_stop_scanning(void)
{
    int err;

    if (ble_scan_started) {
        err = bt_scan_stop();
        if (err) {
		    LOG_ERR("scanning failed to stop (err %d)", err);
            return false;
	    }
        ble_scan_started = false;
        LOG_INF("scanning stopped");
    }
    else {
        LOG_INF("scanner already stopped");
    }

    return true;
}

/*!
*   \brief      Inits BLE extended advertising
*   \param      void
*   \return     bool, true if successful, false otherwise
*/
bool ble_ext_init_advertising(void)
{
    int err;
    const uint32_t op_cohort_id = rap_get_op_cohort_id();

    // Set extended advertising data
    ext_adv_data.cust_id = rap_get_op_cust_id();
    memcpy(ext_adv_data.op_guid, rap_get_op_guid(), OP_GUID_SIZE);
    ext_adv_data.cohort_id = op_cohort_id;
    // TODO: get UWB address
    ext_adv_data.uwb_addr = 0xBEEF;
    // Set standard advertising data
    std_adv_data.cust_id = rap_get_op_cust_id();
    memcpy(std_adv_data.op_guid, rap_get_op_guid(), OP_GUID_SIZE);
    memcpy(std_adv_data.cohort_id, &op_cohort_id, OP_COHORT_SIZE);

    // Create extended advertising set
    err = bt_le_ext_adv_create(&ext_adv_param, &adv_callbacks, &ext_adv_ctx);
    if (err) {
        LOG_ERR("failed to create extended advertising set (err %d)", err);
        return false;
    }
    // Create standard advertising set
    err = bt_le_ext_adv_create(&std_adv_param, &adv_callbacks, &std_adv_ctx);
    if (err) {
        LOG_ERR("failed to create standard advertising set (err %d)", err);
        return false;
    }

    // Set extended advertising data
    err = bt_le_ext_adv_set_data(ext_adv_ctx, ext_ad, ARRAY_SIZE(ext_ad), NULL, 0);
    if (err) {
        LOG_ERR("failed to set extended advertising data (err %d)", err);
        return false;
    }
    // Set BLE advertising and scan response data
    err = bt_le_ext_adv_set_data(std_adv_ctx, std_ad, ARRAY_SIZE(std_ad), NULL, 0);
    if (err) {
        LOG_ERR("failed to set standard advertising data (err %d)", err);
        return false;
    }

    LOG_INF("extended advertising initialized");
    return true;
}


/*!
*   \brief      Starts BLE extended advertising
*   \param      void
*   \return     bool, true if successful, false otherwise
*/
bool ble_ext_start_advertising(void)
{
    int err;

    if (!ble_adv_started) {
#if 1
        err = bt_le_ext_adv_start(ext_adv_ctx, NULL);
        if (err) {
            LOG_ERR("failed to start extended advertising (err %d)", err);
            return false;
        }
#endif
#if 1
        err = bt_le_ext_adv_start(std_adv_ctx, NULL);
        if (err) {
            LOG_ERR("failed to start BLE advertising (err %d)", err);
            bt_le_ext_adv_stop(ext_adv_ctx);
            return false;
        }
#endif
        ble_adv_started = true;
        LOG_INF("advertising started");
    }
    else {
        LOG_INF("advertising already started");
    }

    return true;
}

/*!
*   \brief      Stops BLE extended advertising
*   \param      void
*   \return     bool, true if successful, false otherwise
*/
bool ble_ext_stop_advertising(void)
{
    int err;

    if (ble_adv_started) {
        err = bt_le_ext_adv_stop(ext_adv_ctx);
        if (err) {
            LOG_ERR("extended advertising stop error %d", err);
            return false;
        }
        err = bt_le_ext_adv_stop(std_adv_ctx);
        if (err) {
            LOG_ERR("BLE advertising stop error %d", err);
            return false;
        }
        ble_adv_started = false;
        LOG_INF("advertising stopped");
    }
    else {
        LOG_INF("advertising already stopped");
    }

    return true;
}

/////////////////////////////////////////////////////////////////////////////
// Private Functions

/*!
*   \brief      Scan filter matched callback function
*   \param      device_info, connection and advertising information
*   \param      filter_match, filter match status
*   \param      filter_match, inform that device is connectable
*   \return     None
*/
static void scan_filter_match(struct bt_scan_device_info *device_info,
                              struct bt_scan_filter_match *filter_match,
			                  bool connectable)
{
	char addr[BT_ADDR_LE_STR_LEN];
    ble_dev_t dev;

    const struct bt_le_scan_recv_info *recv_info = device_info->recv_info;

	bt_addr_le_to_str(recv_info->addr, addr, sizeof(addr));
	LOG_INF("device found %s %sconnectable", addr, connectable ? "" : "not ");
	LOG_INF("RSSI %d dBm [prim %s sec %s]", recv_info->rssi,
            phy_id_to_str(recv_info->primary_phy), phy_id_to_str(recv_info->secondary_phy));

    if (parse_scan_rsp(device_info, &dev) == true){
        // TODO: queue device
        LOG_INF("queue device");
    }
}

/*!
*   \brief      Connecting error callback function
*   \param      device_info, connection and advertising information
*   \return     None
*/
static void scan_connecting_error(struct bt_scan_device_info *device_info)
{
	LOG_INF("scan connecting failed");
}

/*!
*   \brief      Connecting callback function
*   \param      device_info, connection and advertising information
*   \return     None
*/
static void scan_connecting(struct bt_scan_device_info *device_info,
			                struct bt_conn *conn)
{
    LOG_INF("scan connecting success");
}

/*!
*   \brief      New connection has been stablished callback function
*   \param      conn, new connection object
*   \param      err, HCI error. Zero for success, non-zero otherwise
*   \return     None
*/
static void connected_cb(struct bt_conn *conn, uint8_t err)
{
    struct bt_conn_info conn_info;
    bt_addr_le_t *addr;
	char addr_str[BT_ADDR_LE_STR_LEN];

    if (err) {
        LOG_ERR("Connection failed with err %d", err);
    }
    else {
        ble_conn_count++;

        bt_conn_get_info(conn, &conn_info);
        addr = bt_conn_get_dst(conn);
        bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));

        LOG_INF("connection opened (%d, %s)",
            ble_conn_count, conn_info.role ==  BT_CONN_ROLE_CENTRAL ? "master" : "slave");
        LOG_INF("%s", addr_str);
    }
}

/*!
*   \brief      Connection has been disconnected callback function
*   \param      conn, new connection object
*   \param      reason, HCI reason for the disconnection
*   \return     None
*/
static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
{
    bt_addr_le_t *addr;
	char addr_str[BT_ADDR_LE_STR_LEN];

    addr = bt_conn_get_dst(conn);
    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));

    if (ble_conn_count > 0) {
        ble_conn_count--;
    }

    LOG_INF("connection closed (0x%x, %d)", reason, ble_conn_count);
    LOG_INF("%s", addr_str);
}

/*!
*   \brief      Request LE connection paramaters callback function
*   \param      conn, connection object
*   \param      param, proposed connection paramaters
*   \return     None
*/
static bool le_param_req_cb(struct bt_conn *conn, struct bt_le_conn_param *param)
{
    bt_addr_le_t *addr;
	char addr_str[BT_ADDR_LE_STR_LEN];

    addr = bt_conn_get_dst(conn);
    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));

    LOG_INF("connection requested params: interval max=%.2f, min=%.2f, latency=%d, timeout=%d",
                    (float)param->interval_max * 1.25, (float)param->interval_min * 1.25,
                    param->latency, param->timeout * 10);
    LOG_INF("%s", addr_str);

    return true;
}

/*!
*   \brief      LE connection paramaters have been updated callback function
*   \param      conn, connection object
*   \param      interval, connection interval
*   \param      latency, connection latency
*   \param      timeout, connection timeout
*   \return     None
*/
static void le_param_updated_cb(struct bt_conn *conn, uint16_t interval,
				                uint16_t latency, uint16_t timeout)
{
    bt_addr_le_t *addr;
	char addr_str[BT_ADDR_LE_STR_LEN];

    addr = bt_conn_get_dst(conn);
    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));

    LOG_INF("connection params updated: interval=%.2f, latency=%d, timeout=%d",
                    (float)interval * 1.25, latency, timeout * 10);
    LOG_INF("%s", addr_str);
}

/*!
*   \brief      Advertising set has finished sending adv data callback function
*   \param      adv, advertising set object
*   \param      info, sent event information
*   \return     None
*/
static void ext_adv_sent(struct bt_le_ext_adv *adv,
                         struct bt_le_ext_adv_sent_info *info)
{
	LOG_INF("advertising data sent");
}

/*!
*   \brief      Advertising set has accepted a new connection callback function
*   \param      adv, advertising set object
*   \param      info, connected event information
*   \return     None
*/
static void ext_adv_connected(struct bt_le_ext_adv *adv,
                         struct bt_le_ext_adv_connected_info *info)
{
	LOG_INF("advertising connected");
}

/*!
*   \brief      Advertising set has sent scan response callback function
*   \param      adv, advertising set object
*   \param      info, scanned event information
*   \return     None
*/
static void ext_adv_scanned(struct bt_le_ext_adv *adv,
                         struct bt_le_ext_adv_scanned_info *info)
{
	LOG_INF("advertising connected");
}

/*!
*   \brief      Convert BLE PHY ID to string
*   \param      id, PHY ID
*   \return     const char*, PHY name string
*/
static const char* phy_id_to_str(const uint8_t id)
{
    switch (id)
    {
    case 0:     return "null";
    case 1:     return "1M PHY";
    case 2:     return "2M PHY";
    case 4:     return "Coded PHY";
    default:    return "unknown PHY";
    }
}