#include "sdk_config.h"
#include "advertising.h"
#include "ble_conn_state.h"
#include "error.h"
#include "fds.h"
#include "nrf_log.h"
#include "peer_manager.h"
#include "peer_manager_handler.h"
#include "peer_manager_types.h"
#include "security.h"

#if 0 // nrf51
#define SEC_PARAM_TIMEOUT               30                                          /**< Timeout for Pairing Request or Security Request (in seconds). */
#define SEC_PARAM_BOND                  1                                           /**< Perform bonding. */
#define SEC_PARAM_MITM                  1                                           /**< Man In The Middle protection not required. */
#define SEC_PARAM_IO_CAPABILITIES       BLE_GAP_IO_CAPS_DISPLAY_ONLY                        /**< No I/O capabilities. */
#define SEC_PARAM_OOB                   0                                           /**< Out Of Band data not available. */
#define SEC_PARAM_MIN_KEY_SIZE          7                                           /**< Minimum encryption key size. */
#define SEC_PARAM_MAX_KEY_SIZE          16 /**< Maximum encryption key size. */
#define SEC_PARAM_LESC                  0
#define SEC_PARAM_KEYPRESS              0 

#else
#define SEC_PARAM_BOND                  1                                           /**< Perform bonding. */
#define SEC_PARAM_MITM                  0                                           /**< Man In The Middle protection required (applicable when display module is detected). */
#define SEC_PARAM_LESC                  0                                           /**< LE Secure Connections not enabled. */
#define SEC_PARAM_KEYPRESS              0                                           /**< Keypress notifications not enabled. */
#define SEC_PARAM_IO_CAPABILITIES       BLE_GAP_IO_CAPS_NONE                        /**< No I/O capabilities. */
#define SEC_PARAM_OOB                   0                                           /**< Out Of Band data not available. */
#define SEC_PARAM_MIN_KEY_SIZE          7                                           /**< Minimum encryption key size. */
#define SEC_PARAM_MAX_KEY_SIZE          16                                          /**< Maximum encryption key size. */
#endif

static void _pmEvtHandler(pm_evt_t const * pEvt) {
    ret_code_t errCode;

    switch (pEvt->evt_id) {
        case PM_EVT_BONDED_PEER_CONNECTED:
        {
            NRF_LOG_INFO("Connected to a previously bonded device.");
        } break;

        case PM_EVT_CONN_SEC_SUCCEEDED:
        {
            NRF_LOG_INFO("Connection secured: role: %d, conn_handle: 0x%x, procedure: %d.",
                         ble_conn_state_role(pEvt->conn_handle),
                         pEvt->conn_handle,
                         pEvt->params.conn_sec_succeeded.procedure);
        } break;


        case PM_EVT_CONN_SEC_FAILED:
        {
            NRF_LOG_INFO("Faield to secure connection with handle 0%x.", pEvt->conn_handle);
            errCode = sd_ble_gap_disconnect(pEvt->conn_handle,
                                      BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            if (errCode != NRF_ERROR_INVALID_STATE)
            {
                APP_ERROR_CHECK(errCode);
            }
            //pEvt->conn_handle = BLE_CONN_HANDLE_INVALID;
            /* Often, when securing fails, it shouldn't be restarted, for security reasons.
             * Other times, it can be restarted directly.
             * Sometimes it can be restarted, but only after changing some Security Parameters.
             * Sometimes, it cannot be restarted until the link is disconnected and reconnected.
             * Sometimes it is impossible, to secure the link, or the peer device does not support it.
             * How to handle this error is highly application dependent. */
        } break;

        case PM_EVT_CONN_SEC_CONFIG_REQ:
        {
            // Reject pairing request from an already bonded peer.
            pm_conn_sec_config_t conn_sec_config = {.allow_repairing = false};
            pm_conn_sec_config_reply(pEvt->conn_handle, &conn_sec_config);
        } break;

        case PM_EVT_STORAGE_FULL:
        {
            // Run garbage collection on the flash.
            errCode = fds_gc();
            if (errCode == FDS_ERR_NO_SPACE_IN_QUEUES)
            {
                // Retry.
            }
            else
            {
                APP_ERROR_CHECK(errCode);
            }
        } break;

        case PM_EVT_PEERS_DELETE_SUCCEEDED:
        {
            NRF_LOG_DEBUG("PM_EVT_PEERS_DELETE_SUCCEEDED");
            advertisingStart();
        } break;

        case PM_EVT_PEER_DATA_UPDATE_FAILED:
        {
            // Assert.
            APP_ERROR_CHECK(pEvt->params.peer_data_update_failed.error);
        } break;

        case PM_EVT_PEER_DELETE_FAILED:
        {
            // Assert.
            APP_ERROR_CHECK(pEvt->params.peer_delete_failed.error);
        } break;

        case PM_EVT_PEERS_DELETE_FAILED:
        {
            // Assert.
            APP_ERROR_CHECK(pEvt->params.peers_delete_failed_evt.error);
        } break;

        case PM_EVT_ERROR_UNEXPECTED:
        {
            // Assert.
            APP_ERROR_CHECK(pEvt->params.error_unexpected.error);
        } break;

        case PM_EVT_CONN_SEC_START:
        case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED:
        case PM_EVT_PEER_DELETE_SUCCEEDED:
        case PM_EVT_LOCAL_DB_CACHE_APPLIED:
        case PM_EVT_LOCAL_DB_CACHE_APPLY_FAILED:
            // This can happen when the local DB has changed.
        case PM_EVT_SERVICE_CHANGED_IND_SENT:
        case PM_EVT_SERVICE_CHANGED_IND_CONFIRMED:
        default:
            break;
    }
}


/**@brief Function for the Peer Manager initialization.
 */
int peerManagerInit(const bool eraseBonds) {
    ble_gap_sec_params_t secParam;
    ret_code_t errCode;
    
    errCode = pm_init();
    if (errCode != NRF_SUCCESS) {
        NRF_LOG_WARNING("pm_init() failed, errCode=0x%x\n", errCode);
        return ERROR_PEER_MANAGER_INITIALIZE_FAIL;
    }    
     
    if (eraseBonds) {
        errCode = pm_peers_delete();
        if (errCode != NRF_SUCCESS) {
            NRF_LOG_WARNING("pm_peers_delete() failed, errCode=0x%x\n", errCode);
            return ERROR_PEER_MANAGER_INITIALIZE_FAIL;
        } 
    }
    
        memset(&secParam, 0, sizeof(ble_gap_sec_params_t));
    
        // Security parameters to be used for all security procedures.
        secParam.bond           = SEC_PARAM_BOND;
        secParam.mitm           = SEC_PARAM_MITM;
        secParam.lesc           = SEC_PARAM_LESC;
        secParam.keypress       = SEC_PARAM_KEYPRESS;
        secParam.io_caps        = SEC_PARAM_IO_CAPABILITIES;
        secParam.oob            = SEC_PARAM_OOB;
        secParam.min_key_size   = SEC_PARAM_MIN_KEY_SIZE;
        secParam.max_key_size   = SEC_PARAM_MAX_KEY_SIZE;
        secParam.kdist_own.enc  = 1;
        secParam.kdist_own.id   = 1;
        secParam.kdist_peer.enc = 1;
        secParam.kdist_peer.id  = 1;
    
        errCode = pm_sec_params_set(&secParam);
        if (errCode != NRF_SUCCESS) {
            NRF_LOG_WARNING("pm_sec_params_set() failed, errCode=0x%x\n", errCode);
            return ERROR_PEER_MANAGER_INITIALIZE_FAIL;
        } 
    
        errCode = pm_register(_pmEvtHandler);
        if (errCode != NRF_SUCCESS) {
            NRF_LOG_WARNING("pm_register() failed, errCode=0x%x\n", errCode);
            return ERROR_PEER_MANAGER_INITIALIZE_FAIL;
        } 

    return 0;
}

