#include "lib/ble/ble_client.h"

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/services/nus.h>

#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/hci_vs.h>

static uint8_t device_id[8];

#if defined(CONFIG_CUSTOM_MANUFACTURER_DATA)

static const uint8_t manu_data[] = {
    CONFIG_MANUFACTURER_DATA_0,
    CONFIG_MANUFACTURER_DATA_1,
    CONFIG_MANUFACTURER_DATA_2,
    CONFIG_MANUFACTURER_DATA_3,
    CONFIG_MANUFACTURER_DATA_4,
    CONFIG_MANUFACTURER_DATA_5,
    CONFIG_MANUFACTURER_DATA_6,
};

#else

static const uint8_t manu_data[] = {
    CONFIG_CLIENT_MANUFACTURER_DATA_0,
    CONFIG_CLIENT_MANUFACTURER_DATA_1,
    CONFIG_CLIENT_MANUFACTURER_DATA_2,
    CONFIG_CLIENT_MANUFACTURER_DATA_3,
    CONFIG_CLIENT_MANUFACTURER_DATA_4,
    CONFIG_CLIENT_MANUFACTURER_DATA_5,
    CONFIG_CLIENT_MANUFACTURER_DATA_6,
};

#endif

static const struct bt_data ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    BT_DATA(BT_DATA_MANUFACTURER_DATA, &manu_data, sizeof(manu_data)),
    BT_DATA(BT_DATA_DEVICE_ID, &device_id, sizeof(device_id)),

    BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_NUS_SRV_VAL),

    BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
};

enum _LIB_NAME_(state)
{
    _LIB_NAME_CAP_(STATE_BLE_NUS_INIT),
};

static struct __maybe_unused _LIB_NAME_(instance)
{
    atomic_t state;

    struct bt_le_ext_adv *adv;
    struct bt_conn *conn;

    struct _LIB_NAME_(config) * config;
} _LIB_NAME_(inst);

static void connected(struct bt_conn *conn, uint8_t conn_err)
{
    int err;

    if (conn_err)
    {
        printk("Connection failed, err 0x%02x \n", conn_err);
        _LIB_NAME_(inst).conn = NULL;
        return;
    }

#if defined(CONFIG_BT_CTLR_PHY_CODED)

    const struct bt_conn_le_phy_param preferred_phy = {
        .options = BT_CONN_LE_PHY_OPT_CODED_S8,
        .pref_rx_phy = BT_GAP_LE_PHY_CODED,
        .pref_tx_phy = BT_GAP_LE_PHY_CODED,
    };
    err = bt_conn_le_phy_update(conn, &preferred_phy);
    if (err)
    {
        printk("bt_conn_le_phy_update() returned %d", err);
    }

#endif

    _LIB_NAME_(inst).conn = conn;
}

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
    if(_LIB_NAME_(inst).conn != conn)
    {
        return;
    }

    _LIB_NAME_(inst).conn = NULL;
    
    if (_LIB_NAME_(inst).config != NULL && _LIB_NAME_(inst).config->callback.connected != NULL)
    {
        _LIB_NAME_(inst).config->callback.connected(false);
    }
}

static void le_phy_updated(struct bt_conn *conn, struct bt_conn_le_phy_info *param)
{
}

BT_CONN_CB_DEFINE(conn_callbacks) = {
    .connected = connected,
    .disconnected = disconnected,
    .le_phy_updated = le_phy_updated,
};

static void notif_enabled(bool enabled, void *ctx)
{
    ARG_UNUSED(ctx);

    if (_LIB_NAME_(inst).config != NULL && _LIB_NAME_(inst).config->callback.connected != NULL)
    {
        _LIB_NAME_(inst).config->callback.connected(enabled);
    }
}

static void received(struct bt_conn *conn, const void *data, uint16_t len, void *ctx)
{
    ARG_UNUSED(conn);
    ARG_UNUSED(ctx);

    if (_LIB_NAME_(inst).config != NULL && _LIB_NAME_(inst).config->callback.receive != NULL)
    {
        _LIB_NAME_(inst).config->callback.receive(data, len);
    }
}

struct bt_nus_cb nus_listener = {
    .notif_enabled = notif_enabled,
    .received = received,
};

_API_(int) _LIB_NAME_(set_device_config)(struct _LIB_NAME_(config) * config)
{
    int err;

    if (config != NULL)
    {
        _LIB_NAME_(inst).config = config;
    }

    device_id[0] = (uint8_t)((NRF_FICR->DEVICEID[1] >> 24) & 0xFF);
    device_id[1] = (uint8_t)((NRF_FICR->DEVICEID[1] >> 16) & 0xFF);
    device_id[2] = (uint8_t)((NRF_FICR->DEVICEID[1] >> 8) & 0xFF);
    device_id[3] = (uint8_t)((NRF_FICR->DEVICEID[1] >> 00) & 0xFF);
    device_id[4] = (uint8_t)((NRF_FICR->DEVICEID[0] >> 24) & 0xFF);
    device_id[5] = (uint8_t)((NRF_FICR->DEVICEID[0] >> 16) & 0xFF);
    device_id[6] = (uint8_t)((NRF_FICR->DEVICEID[0] >> 8) & 0xFF);
    device_id[7] = (uint8_t)((NRF_FICR->DEVICEID[0] >> 00) & 0xFF);

    if (atomic_test_and_set_bit(&_LIB_NAME_(inst).state, _LIB_NAME_CAP_(STATE_BLE_NUS_INIT)))
    {
        return -EALREADY;
    }

    err = bt_nus_cb_register(&nus_listener, NULL);
    if (err)
    {
        printk("Failed to register NUS callback: %d\n", err);
        atomic_clear_bit(&_LIB_NAME_(inst).state, _LIB_NAME_CAP_(STATE_BLE_NUS_INIT));
        return err;
    }

    return 0;
}

_API_(int) _LIB_NAME_(send_data)(const uint8_t *data, uint16_t len)
{
    return bt_nus_send(_LIB_NAME_(inst).conn, data, len);
}

_API_(int) _LIB_NAME_(init)()
{
    int err;

    err = bt_enable(NULL);
    if (err)
    {
        return err;
    }

#if defined(CONFIG_BT_CTLR_PHY_CODED)

    struct bt_le_adv_param param =
        BT_LE_ADV_PARAM_INIT(BT_LE_ADV_OPT_CONNECTABLE |
                                 BT_LE_ADV_OPT_EXT_ADV |
                                 BT_LE_ADV_OPT_CODED,
                             BT_GAP_ADV_FAST_INT_MIN_2,
                             BT_GAP_ADV_FAST_INT_MAX_2,
                             NULL);

#elif defined(CONFIG_BT_EXT_ADV)

    struct bt_le_adv_param param =
        BT_LE_ADV_PARAM_INIT(BT_LE_ADV_OPT_CONNECTABLE |
                                 BT_LE_ADV_OPT_EXT_ADV,
                             BT_GAP_ADV_FAST_INT_MIN_1,
                             BT_GAP_ADV_FAST_INT_MAX_1,
                             NULL);

#endif

    err = bt_le_ext_adv_create(&param, NULL, &_LIB_NAME_(inst).adv);

    if (err)
    {
        printk("Failed to create Coded PHY extended advertising set (err %d)\n", err);
        return err;
    }

    err = bt_le_ext_adv_set_data(_LIB_NAME_(inst).adv, ad, ARRAY_SIZE(ad), NULL, 0);
    if (err)
    {
        printk("Failed to set extended advertising data (err %d)\n", err);
        return err;
    }

    return 0;
}

_API_(int) _LIB_NAME_(uninit)()
{
    _LIB_NAME_(disconnect)();
    return bt_disable();
}

_API_(int) _LIB_NAME_(adv_start)()
{
    int err = bt_le_ext_adv_start(_LIB_NAME_(inst).adv, BT_LE_EXT_ADV_START_DEFAULT);
    if (err)
    {
        printk("Failed to start advertising set (err %d)\n", err);
        return err;
    }
    return 0;
}

_API_(int) _LIB_NAME_(adv_stop)()
{
    int err = bt_le_ext_adv_stop(_LIB_NAME_(inst).adv);
    if (err)
    {
        printk("Failed to stop advertising set (err %d)\n", err);
        return err;
    }
    return 0;
}

_API_(void) _LIB_NAME_(disconnect)(void)
{
    if (_LIB_NAME_(inst).conn != NULL)
        bt_conn_disconnect(_LIB_NAME_(inst).conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
    bt_le_ext_adv_stop(_LIB_NAME_(inst).adv);
}