/*******************************************************************************
 * @file    ble_core.c
 * @brief   BLE Core Functions - Stack, GAP, GATT, Advertising
 * @author  Charles KWON <charleskwon@medithings.co.kr>
 * @date    2025-01-30
 * @copyright (c) 2025 Medithings Inc. All rights reserved.
 *
 * @details BLE stack initialization, GAP/GATT configuration, advertising.
 ******************************************************************************/

#include "ble_core.h"
#include "ble_data_tx.h"
#include "power_ctrl.h"
#include "device_config.h"

#include "nordic_common.h"
#include "nrf.h"
#include "ble_hci.h"
#include "ble_advdata.h"
#include "ble_advertising.h"
#include "ble_srv_common.h"
#include "ble_conn_params.h"
#include "ble_nus.h"
#include "nrf_sdh.h"
#include "nrf_sdh_soc.h"
#include "nrf_sdh_ble.h"
#include "nrf_ble_gatt.h"
#include "nrf_ble_qwr.h"
#include "app_timer.h"
#include "app_error.h"
#include "bsp.h"
#include "debug_print.h"
#include "main_timer.h"
#include "battery_saadc.h"
#include "measurements.h"
#include "power_control.h"
#include "main.h"
#include <cmd_parse.h>

#if FEATURE_SECURE_CONNECTION
#include "peer_manager.h"
#include "peer_manager_handler.h"
#include "nrf_ble_lesc.h"
#endif

#if BLE_DFU_ENABLED
#include "ble_dfu.h"
#include "nrf_pwr_mgmt.h"
#include "nrf_power.h"
#include "nrf_bootloader_info.h"
#include "nrf_dfu_ble_svci_bond_sharing.h"
#include "nrf_svci_async_function.h"
#include "nrf_svci_async_handler.h"
#include "ble_conn_state.h"
#endif

#include <string.h>

/*==============================================================================
 * BLE SERVICE INSTANCES
 *============================================================================*/

BLE_NUS_DEF(m_nus, NRF_SDH_BLE_TOTAL_LINK_COUNT);
NRF_BLE_GATT_DEF(m_gatt);
NRF_BLE_QWR_DEF(m_qwr);
BLE_ADVERTISING_DEF(m_advertising);

/*==============================================================================
 * GLOBAL VARIABLES
 *============================================================================*/

uint16_t m_conn_handle = BLE_CONN_HANDLE_INVALID;
uint16_t m_ble_nus_max_data_len = BLE_GATT_ATT_MTU_DEFAULT - 3;

static ble_uuid_t m_adv_uuids[] =
{
    {BLE_UUID_NUS_SERVICE, NUS_SERVICE_UUID_TYPE}
};

/*==============================================================================
 * FORWARD DECLARATIONS
 *============================================================================*/

static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context);
static void on_adv_evt(ble_adv_evt_t ble_adv_evt);
static void on_conn_params_evt(ble_conn_params_evt_t * p_evt);
static void conn_params_error_handler(uint32_t nrf_error);
static void nus_data_handler(ble_nus_evt_t * p_evt);
static void nrf_qwr_error_handler(uint32_t nrf_error);
void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt);

#if BLE_DFU_ENABLED
static bool app_shutdown_handler(nrf_pwr_mgmt_evt_t event);
static void buttonless_dfu_sdh_state_observer(nrf_sdh_state_evt_t state, void * p_context);
static void advertising_config_get(ble_adv_modes_config_t * p_config);
static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event);
#endif

/*==============================================================================
 * BLE STACK INITIALIZATION
 *============================================================================*/

void ble_stack_init(void)
{
    ret_code_t err_code;

    err_code = nrf_sdh_enable_request();
    APP_ERROR_CHECK(err_code);

    /* Configure BLE stack using default settings */
    uint32_t ram_start = 0;
    err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
    APP_ERROR_CHECK(err_code);

    /* Enable BLE stack */
    err_code = nrf_sdh_ble_enable(&ram_start);
    APP_ERROR_CHECK(err_code);

    /* Register BLE event handler */
    NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
}

/*==============================================================================
 * GAP INITIALIZATION
 *============================================================================*/

void gap_params_init(void)
{
    uint32_t                err_code;
    ble_gap_conn_params_t   gap_conn_params;
    ble_gap_conn_sec_mode_t sec_mode;
#if FEATURE_STATIC_PASSKEY
    ble_opt_t               ble_opt;
#endif

    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);

    /* Set device name from EEPROM serial number */
    err_code = sd_ble_gap_device_name_set(&sec_mode,
                                          (const uint8_t *) SERIAL_NO,
                                          strlen(SERIAL_NO));
    APP_ERROR_CHECK(err_code);

    /* Configure preferred connection parameters */
    memset(&gap_conn_params, 0, sizeof(gap_conn_params));
    gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;
    gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;
    gap_conn_params.slave_latency     = SLAVE_LATENCY;
    gap_conn_params.conn_sup_timeout  = CONN_SUP_TIMEOUT;

    err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
    APP_ERROR_CHECK(err_code);

#if FEATURE_STATIC_PASSKEY
    /* Set static passkey for pairing */
    ble_opt.gap_opt.passkey.p_passkey = (const uint8_t *)m_static_passkey;
    err_code = sd_ble_opt_set(BLE_GAP_OPT_PASSKEY, &ble_opt);
    APP_ERROR_CHECK(err_code);
#endif
}

/*==============================================================================
 * GATT INITIALIZATION
 *============================================================================*/

void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)
{
    if ((m_conn_handle == p_evt->conn_handle) && (p_evt->evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED))
    {
        m_ble_nus_max_data_len = p_evt->params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH;
    }
}

void gatt_init(void)
{
    ret_code_t err_code;

    err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_ble_gatt_att_mtu_periph_set(&m_gatt, NRF_SDH_BLE_GATT_MAX_MTU_SIZE);
    APP_ERROR_CHECK(err_code);
}

/*==============================================================================
 * CONNECTION PARAMETERS
 *============================================================================*/

static void on_conn_params_evt(ble_conn_params_evt_t * p_evt)
{
    uint32_t err_code;

    if (p_evt->evt_type == BLE_CONN_PARAMS_EVT_FAILED)
    {
        err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_CONN_INTERVAL_UNACCEPTABLE);
        APP_ERROR_CHECK(err_code);
    }
}

static void conn_params_error_handler(uint32_t nrf_error)
{
    APP_ERROR_HANDLER(nrf_error);
}

void conn_params_init(void)
{
    uint32_t               err_code;
    ble_conn_params_init_t cp_init;

    memset(&cp_init, 0, sizeof(cp_init));

    cp_init.p_conn_params                  = NULL;
    cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY;
    cp_init.next_conn_params_update_delay  = NEXT_CONN_PARAMS_UPDATE_DELAY;
    cp_init.max_conn_params_update_count   = MAX_CONN_PARAMS_UPDATE_COUNT;
    cp_init.start_on_notify_cccd_handle    = BLE_GATT_HANDLE_INVALID;
    cp_init.disconnect_on_fail             = false;
    cp_init.evt_handler                    = on_conn_params_evt;
    cp_init.error_handler                  = conn_params_error_handler;

    err_code = ble_conn_params_init(&cp_init);
    APP_ERROR_CHECK(err_code);
}

/*==============================================================================
 * NUS DATA HANDLER
 *============================================================================*/

extern which_cmd_t cmd_type_t;

static void nus_data_handler(ble_nus_evt_t * p_evt)
{
    if (p_evt->type == BLE_NUS_EVT_RX_DATA)
    {
        cmd_type_t = CMD_BLE;
        DBG_PRINTF("recv :%s \n", p_evt->params.rx_data.p_data);
        DBG_PRINTF("length %d \r\n", p_evt->params.rx_data.length);
        received_command_process(p_evt->params.rx_data.p_data, CMD_BLE, p_evt->params.rx_data.length);
    }
}

/*==============================================================================
 * QWR ERROR HANDLER
 *============================================================================*/

static void nrf_qwr_error_handler(uint32_t nrf_error)
{
    APP_ERROR_HANDLER(nrf_error);
}

/*==============================================================================
 * DFU HANDLERS
 *============================================================================*/

#if BLE_DFU_ENABLED
NRF_PWR_MGMT_HANDLER_REGISTER(app_shutdown_handler, 0);

NRF_SDH_STATE_OBSERVER(m_buttonless_dfu_state_obs, 0) =
{
    .handler = buttonless_dfu_sdh_state_observer,
};

static bool app_shutdown_handler(nrf_pwr_mgmt_evt_t event)
{
    switch (event)
    {
        case NRF_PWR_MGMT_EVT_PREPARE_DFU:
            DBG_PRINTF("Power management wants to reset to DFU mode.\r\n");
            break;

        default:
            return true;
    }

    DBG_PRINTF("Power management allowed to reset to DFU mode.\r\n");
    return true;
}

static void buttonless_dfu_sdh_state_observer(nrf_sdh_state_evt_t state, void * p_context)
{
    if (state == NRF_SDH_EVT_STATE_DISABLED)
    {
        nrf_power_gpregret2_set(BOOTLOADER_DFU_SKIP_CRC);
        nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_SYSOFF);
    }
}

static void advertising_config_get(ble_adv_modes_config_t * p_config)
{
    memset(p_config, 0, sizeof(ble_adv_modes_config_t));

    p_config->ble_adv_fast_enabled  = true;
    p_config->ble_adv_fast_interval = APP_ADV_INTERVAL;
    p_config->ble_adv_fast_timeout  = APP_ADV_DURATION;
}

static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event)
{
    switch (event)
    {
        case BLE_DFU_EVT_BOOTLOADER_ENTER_PREPARE:
        {
            DBG_PRINTF("Device is preparing to enter bootloader mode.\r\n");

            ble_adv_modes_config_t config;
            advertising_config_get(&config);
            config.ble_adv_on_disconnect_disabled = true;
            ble_advertising_modes_config_set(&m_advertising, &config);

            uint32_t conn_count = ble_conn_state_for_each_connected(disconnect, NULL);
            DBG_PRINTF("Disconnected %d links.\r\n", conn_count);
            break;
        }

        case BLE_DFU_EVT_BOOTLOADER_ENTER:
            DBG_PRINTF("Device will enter bootloader mode.\r\n");
            break;

        case BLE_DFU_EVT_BOOTLOADER_ENTER_FAILED:
            DBG_PRINTF("Request to enter bootloader mode failed asynchroneously.\r\n");
            break;

        case BLE_DFU_EVT_RESPONSE_SEND_ERROR:
            DBG_PRINTF("Request to send a response to client failed.\r\n");
            APP_ERROR_CHECK(false);
            break;

        default:
            DBG_PRINTF("Unknown event from ble_dfu_buttonless.\r\n");
            break;
    }
}

void dfu_init(void)
{
    ret_code_t err_code = ble_dfu_buttonless_async_svci_init();
    APP_ERROR_CHECK(err_code);
}
#endif

/*==============================================================================
 * SERVICES INITIALIZATION
 *============================================================================*/

void services_init(void)
{
    uint32_t           err_code;
    ble_nus_init_t     nus_init;
    nrf_ble_qwr_init_t qwr_init = {0};

    /* Initialize Queued Write Module */
    qwr_init.error_handler = nrf_qwr_error_handler;
    err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
    APP_ERROR_CHECK(err_code);

    /* Initialize Nordic UART Service */
    memset(&nus_init, 0, sizeof(nus_init));
    nus_init.data_handler = nus_data_handler;
    err_code = ble_nus_init(&m_nus, &nus_init);
    APP_ERROR_CHECK(err_code);

#if BLE_DFU_ENABLED
    /* Initialize DFU service */
    ble_dfu_buttonless_init_t dfus_init = {0};
    dfus_init.evt_handler = ble_dfu_evt_handler;
    err_code = ble_dfu_buttonless_init(&dfus_init);
    APP_ERROR_CHECK(err_code);
#endif
}

/*==============================================================================
 * WRAPPER FUNCTIONS FOR STATIC VARIABLES
 *============================================================================*/

uint32_t ble_nus_data_send_wrapper(uint8_t *p_data, uint16_t *p_length, uint16_t conn_handle)
{
    return ble_nus_data_send(&m_nus, p_data, p_length, conn_handle);
}

uint32_t ble_advertising_restart_without_whitelist_wrapper(void)
{
    return ble_advertising_restart_without_whitelist(&m_advertising);
}

/*==============================================================================
 * ADVERTISING
 *============================================================================*/

static void on_adv_evt(ble_adv_evt_t ble_adv_evt)
{
    uint32_t err_code;

    switch (ble_adv_evt)
    {
        case BLE_ADV_EVT_FAST:
            err_code = bsp_indication_set(BSP_INDICATE_ADVERTISING);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_ADV_EVT_IDLE:
            /* Advertising timeout - enter sleep mode */
            go_sleep_mode_enter = true;
            main_timer_start();
            break;

        default:
            break;
    }
}

void advertising_init(void)
{
    uint32_t               err_code;
    ble_advertising_init_t init;

    memset(&init, 0, sizeof(init));

    init.advdata.name_type          = BLE_ADVDATA_FULL_NAME;
    init.advdata.include_appearance = true;
#if FEATURE_NO_SLEEP
    init.advdata.flags              = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
#else
    init.advdata.flags              = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE;
#endif
    init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
    init.srdata.uuids_complete.p_uuids  = m_adv_uuids;

    init.config.ble_adv_fast_enabled  = true;
    init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;
    init.config.ble_adv_fast_timeout  = APP_ADV_DURATION;
    init.evt_handler = on_adv_evt;

    err_code = ble_advertising_init(&m_advertising, &init);
    APP_ERROR_CHECK(err_code);

    ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
}

#if FEATURE_SECURE_CONNECTION
void advertising_start(bool erase_bonds)
{
    extern void delete_bonds(void);

    DBG_PRINTF("adv_start(erase=%d)\r\n", erase_bonds);
    if (erase_bonds == true) {
        bond_data_delete = false;
        DBG_PRINTF("delete_bonds\r\n");
        delete_bonds();
        /* Advertising started by PM_EVT_PEERS_DELETE_SUCCEEDED event */
    } else {
        DBG_PRINTF("ble_adv_start...\r\n");
        ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
        DBG_PRINTF("err=%d\r\n", err_code);
        APP_ERROR_CHECK(err_code);
    }
}
#else
void advertising_start(void)
{
    uint32_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
    /* Ignore INVALID_STATE - already advertising or not ready */
    if (err_code != NRF_SUCCESS && err_code != NRF_ERROR_INVALID_STATE) {
        APP_ERROR_CHECK(err_code);
    }
}
#endif

/*==============================================================================
 * DISCONNECT
 *============================================================================*/

void disconnect(uint16_t conn_handle, void * p_context)
{
    UNUSED_PARAMETER(p_context);

    ret_code_t err_code = sd_ble_gap_disconnect(conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
    if (err_code != NRF_SUCCESS)
    {
        DBG_PRINTF("Failed to disconnect connection. Connection handle: %d Error: %d\r\n", conn_handle, err_code);
    }
    else
    {
        DBG_PRINTF("Disconnected connection handle %d\r\n", conn_handle);
    }
}

/*==============================================================================
 * BLE EVENT HANDLER
 *============================================================================*/

static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
    uint32_t err_code;
    extern void ble_data_tx_complete(void);

#if FEATURE_SECURE_CONNECTION
    pm_handler_secure_on_connection(p_ble_evt);
#endif

    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GAP_EVT_DISCONNECTED:
            DBG_PRINTF("Disconnected\r\n");
            ble_connection_st = 0;
            m_conn_handle = BLE_CONN_HANDLE_INVALID;

            /* Put device to sleep on disconnect if active */
            if (device_status == true) {
                LED_ALLOFF();
                PD_ALLOFF();
                if (device_sleep_mode() == 0) {
                    device_status = false;
                }
            }

            /* Restart advertising to allow reconnection */
            /* Delay to let PC complete disconnect processing */
            nrf_delay_ms(100);
#if FEATURE_SECURE_CONNECTION
            advertising_start(false);
#else
            advertising_start();
#endif
            DBG_PRINTF("Advertising restarted\r\n");
            break;

        case BLE_GAP_EVT_CONNECTED:
        {
            DBG_PRINTF("Connected\r\n");
            ble_connection_st = 1;
            battery_timer_start();
            m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
            err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr, m_conn_handle);
            APP_ERROR_CHECK(err_code);

            /* Set TX power to +8 dBm for connected state */
            sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_CONN, m_conn_handle, 8);

            err_code = bsp_indication_set(BSP_INDICATE_CONNECTED);
            APP_ERROR_CHECK(err_code);
            break;
        }

        case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
        {
            DBG_PRINTF("PHY update request.\r\n");
            /* Use 1M PHY only for better PC compatibility */
            ble_gap_phys_t const phys =
            {
                .rx_phys = BLE_GAP_PHY_1MBPS,
                .tx_phys = BLE_GAP_PHY_1MBPS,
            };
            err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
            APP_ERROR_CHECK(err_code);
        } break;

        case BLE_GATTC_EVT_TIMEOUT:
            DBG_PRINTF("Client Timeout.\r\n");
            err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
                                             BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_GATTS_EVT_TIMEOUT:
            DBG_PRINTF("Server Timeout.\r\n");
            err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
                                             BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
#if !FEATURE_SECURE_CONNECTION
            /* Reject pairing when security is disabled */
            err_code = sd_ble_gap_sec_params_reply(m_conn_handle, BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP, NULL, NULL);
            APP_ERROR_CHECK(err_code);
#endif
            break;

#if FEATURE_SECURE_CONNECTION
        case BLE_GAP_EVT_PASSKEY_DISPLAY:
        {
            /* Display passkey for user verification */
            char passkey[7];
            memcpy(passkey, p_ble_evt->evt.gap_evt.params.passkey_display.passkey, 6);
            passkey[6] = 0;
            DBG_PRINTF("Passkey: %s\r\n", passkey);
        } break;

        case BLE_GAP_EVT_AUTH_KEY_REQUEST:
#if FEATURE_STATIC_PASSKEY
            /* Provide static passkey for authentication */
            if (p_ble_evt->evt.gap_evt.params.auth_key_request.key_type == BLE_GAP_AUTH_KEY_TYPE_PASSKEY)
            {
                err_code = sd_ble_gap_auth_key_reply(p_ble_evt->evt.gap_evt.conn_handle,
                                                     BLE_GAP_AUTH_KEY_TYPE_PASSKEY,
                                                     (const uint8_t *)m_static_passkey);
                APP_ERROR_CHECK(err_code);
            }
#endif
            break;

        case BLE_GAP_EVT_LESC_DHKEY_REQUEST:
            /* Handled by LESC module */
            break;
#endif

        case BLE_GATTS_EVT_HVN_TX_COMPLETE:
            /* TX complete - ready for next transmission */
            ble_data_tx_complete();
            break;

        default:
            break;
    }
}
