
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "TxBuffer.h"
#include "sdk_config.h"
#include "nordic_common.h"
#include "nrf_sdh.h"
#include "nrf_sdh_ble.h"
#include "app_timer.h"
#include "bsp_btn_ble.h"
#include "ble.h"
#include "ble_hci.h"
#include "ble_advertising.h"
#include "ble_conn_params.h"
#include "ble_db_discovery.h"
#include "nrf_ble_gatt.h"
#include "ble_conn_state.h"
#include "nrf_ble_gatt.h"
#include "nrf_pwr_mgmt.h"
#include "nrf_ble_scan.h"
#include "peer_manager_types.h"
#include "peer_manager.h"
#include "peer_manager_handler.h"
#include "nrf_soc.h"
#include "nrf_sdh_soc.h"

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"


#define APP_BLE_CONN_CFG_TAG        1                                   /**< Tag that refers to the BLE stack configuration that is set with @ref sd_ble_cfg_set. The default tag is @ref APP_BLE_CONN_CFG_TAG. */
#define APP_BLE_OBSERVER_PRIO       3                                   /**< BLE observer priority of the application. There is no need to modify this value. */

#define APP_SOC_OBSERVER_PRIO       1

#define CENTRAL_SCANNING_LED        BSP_BOARD_LED_0
#define CENTRAL_CONNECTED_LED       BSP_BOARD_LED_1
#define LEDHA0_LED                  BSP_BOARD_LED_2                     /**< LED to indicate if HA is in program 0. */
#define LEDHA1_LED                  BSP_BOARD_LED_3                     /**< LED to indicate if HA is in program 0. */

#define HA0VOLUP_BUTTON             BSP_BUTTON_0                        /**< Button performs volume up on first connected HA. */
#define HA0VOLDOWN_BUTTON           BSP_BUTTON_1                        /**< Button performs volume down on first connected HA. */
#define HA1VOLUP_BUTTON             BSP_BUTTON_2                        /**< Button performs volume up on second connected HA. */
#define HA1VOLDOWN_BUTTON           BSP_BUTTON_3                        /**< Button performs volume down on second connected HA. */
#define BUTTON_DETECTION_DELAY      APP_TIMER_TICKS(50)                 /**< Delay from a GPIOTE event until a button is reported as pushed (in number of timer ticks). */

NRF_BLE_GATT_DEF(m_gatt);                                               /**< GATT module instance. */
//NRF_BLE_GATTS_C_ARRAY_DEF(m_gatts_c, NRF_SDH_BLE_CENTRAL_LINK_COUNT);       /**< GATT service client instances. */
BLE_DB_DISCOVERY_ARRAY_DEF(m_db_disc, NRF_SDH_BLE_CENTRAL_LINK_COUNT);  /**< Database discovery module instances. */
NRF_BLE_SCAN_DEF(m_scan);                                               /**< Scanning Module instance. */

static char const m_target_periph_name[] = "Nordic_Blinky";             /**< Name of the device to try to connect to. This name is searched for in the scanning report data. */

#define LBS_UUID_BASE        {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, \
                              0xDE, 0xEF, 0x12, 0x12, 0x00, 0x00, 0x00, 0x00}
#define LBS_UUID_SERVICE     0x1523
#define LBS_UUID_BUTTON_CHAR 0x1524
#define LBS_UUID_LED_CHAR    0x1525



/**@brief Structure containing the handles related to the LED Button Service found on the peer. */
typedef struct
{
    uint16_t button_cccd_handle;  /**< Handle of the CCCD of the Button characteristic. */
    uint16_t button_handle;       /**< Handle of the Button characteristic as provided by the SoftDevice. */
    uint16_t led_handle;          /**< Handle of the LED characteristic as provided by the SoftDevice. */
} lbs_db_t;

typedef struct
{
    uint16_t ConHandle;
    pm_peer_id_t PeerId;
    bool Secure;
    bool Discovered;
    lbs_db_t PeerDb;
//    ServiceCache_t PeerCache;
} LbsDevice_t;



static tx_message_t m_aTxBufferStorage[8];
static STxBuffer m_TxBuffer;

static uint8_t m_ble_uuid_type;
static LbsDevice_t m_aLbsDevices[NRF_SDH_BLE_CENTRAL_LINK_COUNT];


static void OnLbsDeviceDisconnect(LbsDevice_t* pDevice);
static void SendPacket(uint8_t * pPacket);


/**@brief Function for handling asserts in the SoftDevice.
 *
 * @details This function is called in case of an assert in the SoftDevice.
 *
 * @warning This handler is only an example and is not meant for the final product. You need to analyze
 *          how your product is supposed to react in case of an assert.
 * @warning On assert from the SoftDevice, the system can only recover on reset.
 *
 * @param[in] line_num     Line number of the failing assert call.
 * @param[in] p_file_name  File name of the failing assert call.
 */
void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name)
{
    app_error_handler(0xDEADBEEF, line_num, p_file_name);
}


/**@brief Function for initializing the LEDs.
 *
 * @details Initializes all LEDs used by the application.
 */
static void leds_init(void)
{
    bsp_board_init(BSP_INIT_LEDS);
}



static uint32_t GetConHandleIndex(uint16_t ConHandle)
{
    for(uint32_t i = 0; i < ARRAY_SIZE(m_aLbsDevices); i++)
    {
        if( ConHandle == m_aLbsDevices[i].ConHandle)
        {
            return i;
        }
    }

    return 0xFFFFFFFF;  // Not found value
}

static LbsDevice_t* GetLbsDevice(uint16_t ConHandle)
{
    for(uint32_t i = 0; i < ARRAY_SIZE(m_aLbsDevices); i++)
    {
        if( ConHandle == m_aLbsDevices[i].ConHandle)
        {
            return &m_aLbsDevices[i];
        }
    }

    return 0;
}

static void LbsDeviceClear(LbsDevice_t* pDevice)
{
    pDevice->ConHandle = BLE_CONN_HANDLE_INVALID;
    pDevice->PeerId = PM_PEER_ID_INVALID;
    pDevice->Secure = false;
    pDevice->Discovered = false;
    memset(&pDevice->PeerDb, 0x00, sizeof(lbs_db_t));
}

static LbsDevice_t* LbsDeviceGetFreeSlot(void)
{
    uint32_t iIndex = GetConHandleIndex(BLE_CONN_HANDLE_INVALID);
    if(0xFFFFFFFF == iIndex)
    {
        return 0;
    }

    return &m_aLbsDevices[iIndex];
}


static void scan_evt_handler(scan_evt_t const * p_scan_evt)
{
    ret_code_t err_code;

    switch(p_scan_evt->scan_evt_id)
    {
        case NRF_BLE_SCAN_EVT_CONNECTING_ERROR:
        {
            err_code = p_scan_evt->params.connecting_err.err_code;
            APP_ERROR_CHECK(err_code);
        } break;

        default:
            break;
    }
}


/**@brief Function for initializing the scanning and setting the filters.
 */
static void scan_init(void)
{
    ret_code_t          err_code;
    nrf_ble_scan_init_t init_scan;

    memset(&init_scan, 0, sizeof(init_scan));

    init_scan.connect_if_match = true;
    init_scan.conn_cfg_tag     = APP_BLE_CONN_CFG_TAG;

    err_code = nrf_ble_scan_init(&m_scan, &init_scan, scan_evt_handler);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_ble_scan_filter_set(&m_scan, SCAN_NAME_FILTER, m_target_periph_name);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_ble_scan_filters_enable(&m_scan, NRF_BLE_SCAN_NAME_FILTER, false);
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for starting scanning. */
static void scan_start(void)
{
    ret_code_t ret;

    NRF_LOG_INFO("Start scanning for device name %s.", (uint32_t)m_target_periph_name);
    ret = nrf_ble_scan_start(&m_scan);
    APP_ERROR_CHECK(ret);
    // Turn on the LED to signal scanning.
    bsp_board_led_on(CENTRAL_SCANNING_LED);
}

/*
static void ButtonChange(LbsDevice_t* pDevice, int8_t iButton)
{
    tx_message_t * p_msg;

    p_msg = TxBufferPush(&m_TxBuffer);
    if(!p_msg)
    {
        NRF_LOG_INFO("TxBufferFull");
        return;
    }

    NRF_LOG_DEBUG("Sending Button %d", NewButton);

    p_msg->req.write_req.gattc_params.handle   = pDevice->PeerDb.button_handle;
    p_msg->req.write_req.gattc_params.len      = 1;
    p_msg->req.write_req.gattc_params.p_value  = p_msg->req.write_req.gattc_value;
    p_msg->req.write_req.gattc_params.offset   = 0;
    p_msg->req.write_req.gattc_params.write_op = BLE_GATT_OP_WRITE_REQ;
    p_msg->req.write_req.gattc_value[0]        = 0x01;
    p_msg->conn_handle                         = pDevice->ConHandle;
    p_msg->type                                = WRITE_REQ;

    TxBufferProcess(&m_TxBuffer);
}
*/

static void ButtonUpdate(LbsDevice_t* pDevice, const uint8_t* pButtonData)
{
    // Debug info - report button
    NRF_LOG_INFO("Conn %d, button = %d", GetConHandleIndex(pDevice->ConHandle), pButtonData[0]);

    // Update LEDs
    uint32_t iIndex = GetConHandleIndex(pDevice->ConHandle);
    uint32_t LedId = (0 == iIndex) ? LEDHA0_LED : LEDHA1_LED;
    uint32_t Button = pButtonData[0];

    if(!Button)
    {
        bsp_board_led_on(LedId);
    }
    else
    {
        bsp_board_led_off(LedId);
    }
}


/**@brief Function for configuring the CCCD.
 *
 * @param[in] conn_handle The connection handle on which to configure the CCCD.
 * @param[in] handle_cccd The handle of the CCCD to be configured.
 * @param[in] enable      Whether to enable or disable the CCCD.
 *
 * @return NRF_SUCCESS if the CCCD configure was successfully sent to the peer.
 */
static uint32_t cccd_configure(uint16_t conn_handle, uint16_t handle_cccd, bool enable)
{
    NRF_LOG_DEBUG("Configuring CCCD. CCCD Handle = %d, Connection Handle = %d",
        handle_cccd,conn_handle);

    tx_message_t * p_msg;
    uint16_t       cccd_val = enable ? BLE_GATT_HVX_NOTIFICATION : 0;

    p_msg = TxBufferPush(&m_TxBuffer);
    if(!p_msg)
    {
        NRF_LOG_INFO("TxBufferFull");
        return NRF_ERROR_BUSY;
    }

    p_msg->req.write_req.gattc_params.handle   = handle_cccd;
    p_msg->req.write_req.gattc_params.len      = BLE_CCCD_VALUE_LEN;
    p_msg->req.write_req.gattc_params.p_value  = p_msg->req.write_req.gattc_value;
    p_msg->req.write_req.gattc_params.offset   = 0;
    p_msg->req.write_req.gattc_params.write_op = BLE_GATT_OP_WRITE_REQ;
    p_msg->req.write_req.gattc_value[0]        = LSB_16(cccd_val);
    p_msg->req.write_req.gattc_value[1]        = MSB_16(cccd_val);
    p_msg->conn_handle                         = conn_handle;
    p_msg->type                                = WRITE_REQ;

    TxBufferProcess(&m_TxBuffer);
    return NRF_SUCCESS;
}


/**@brief Function for handling BLE events.
 *
 * @param[in]   p_ble_evt   Bluetooth stack event.
 * @param[in]   p_context   Unused.
 */
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
    ret_code_t err_code;

    // For readability.
    //    ble_common_evt_t const *    p_common_evt    = &p_ble_evt->evt.common_evt; /**< Common Event, evt_id in BLE_EVT_* series. */
    ble_gap_evt_t const *       p_gap_evt       = &p_ble_evt->evt.gap_evt;    /**< GAP originated event, evt_id in BLE_GAP_EVT_* series. */
    ble_gattc_evt_t const *     p_gattc_evt     = &p_ble_evt->evt.gattc_evt;  /**< GATT client originated event, evt_id in BLE_GATTC_EVT* series. */

    if(p_ble_evt->header.evt_id == 29)    // I cant figure out what type of event this is, but its spamming like crazy
    {
        return;
    }

    NRF_LOG_DEBUG("BLE Event ID %d", p_ble_evt->header.evt_id);

    switch (p_ble_evt->header.evt_id)
    {
        // Upon connection, check which peripheral is connected, initiate DB
        // discovery, update LEDs status, and resume scanning, if necessary.
        case BLE_GAP_EVT_CONNECTED:
        {
            NRF_LOG_INFO("Connection 0x%x established", p_gap_evt->conn_handle);

            APP_ERROR_CHECK_BOOL(p_gap_evt->conn_handle < NRF_SDH_BLE_CENTRAL_LINK_COUNT);

            // Store the conHandle
            LbsDevice_t* pDevice = LbsDeviceGetFreeSlot();
            if(!pDevice)
            {
                NRF_LOG_INFO("Out of Device storage");
                break;
            }
            else
            {
                pDevice->ConHandle = p_gap_evt->conn_handle;
            }


            uint32_t iIndex = GetConHandleIndex(pDevice->ConHandle);
            err_code = ble_db_discovery_start(&m_db_disc[iIndex],
                                              pDevice->ConHandle);

            if (err_code != NRF_ERROR_BUSY)
            {
                APP_ERROR_CHECK(err_code);
            }

            // Update LEDs status and check whether it is needed to look for more
            // peripherals to connect to.
            bsp_board_led_on(CENTRAL_CONNECTED_LED);
            if (ble_conn_state_central_conn_count() == NRF_SDH_BLE_CENTRAL_LINK_COUNT)
            {
                bsp_board_led_off(CENTRAL_SCANNING_LED);
            }
            else
            {
                // Resume scanning.
                bsp_board_led_on(CENTRAL_SCANNING_LED);
                scan_start();
            }
        } break; // BLE_GAP_EVT_CONNECTED

        // Upon disconnection, reset the connection handle of the peer that disconnected, update
        // the LEDs status and start scanning again.
        case BLE_GAP_EVT_DISCONNECTED:
        {
            if(BLE_HCI_CONNECTION_TIMEOUT == p_gap_evt->params.disconnected.reason)
            {
                NRF_LOG_INFO("central link 0x%x disconnected (reason: Connection timeout)",
                    p_gap_evt->conn_handle);
            }
            else
            {
                NRF_LOG_INFO("central link 0x%x disconnected (reason: 0x%x)",
                    p_gap_evt->conn_handle,
                    p_gap_evt->params.disconnected.reason);
            }

            // Clean up
            LbsDevice_t* pDevice = GetLbsDevice(p_gap_evt->conn_handle);
            if(!pDevice)
            {
                NRF_LOG_INFO("Disconnected Con handle not recognized");
            }
            else
            {
                OnLbsDeviceDisconnect(pDevice);
            }


            if (ble_conn_state_central_conn_count() == 0)
            {
                // Turn off the LED that indicates the connection.
                bsp_board_led_off(CENTRAL_CONNECTED_LED);
            }

            // Start scanning.
            scan_start();

            // Turn on the LED for indicating scanning.
            bsp_board_led_on(CENTRAL_SCANNING_LED);

        } break;

        case BLE_GAP_EVT_TIMEOUT:
        {
            // Timeout for scanning is not specified, so only the connection requests can time out.
            if (p_gap_evt->params.timeout.src == BLE_GAP_TIMEOUT_SRC_CONN)
            {
                NRF_LOG_DEBUG("Connection request timed out.");
            }
        } break;

        case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
        {
            NRF_LOG_DEBUG("BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST.");
            // Accept parameters requested by peer.
            err_code = sd_ble_gap_conn_param_update(p_gap_evt->conn_handle,
                                        &p_gap_evt->params.conn_param_update_request.conn_params);
            APP_ERROR_CHECK(err_code);
        } break;

        case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
        {
            NRF_LOG_DEBUG("PHY update request.");
            ble_gap_phys_t const phys =
            {
                .rx_phys = BLE_GAP_PHY_AUTO,
                .tx_phys = BLE_GAP_PHY_AUTO,
            };
            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:
        {
            // Disconnect on GATT client timeout event.
            NRF_LOG_DEBUG("GATT client timeout.");
            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:
        {
            // Disconnect on GATT server timeout event.
            NRF_LOG_DEBUG("GATT server timeout.");
            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;

        // The next two cases are Gatt stuff
        // GATT Value Notification received from the SoftDevice
        case BLE_GATTC_EVT_HVX:
        {
            // Check if the event is on the link for this instance
            LbsDevice_t* pDevice = GetLbsDevice(p_gattc_evt->conn_handle);
            if(!pDevice)    {   return; }

            NRF_LOG_DEBUG("Notification received on Con %d", pDevice->ConHandle);

            // Check if this is a Program volume fine tuning notification.
            if (p_gattc_evt->params.hvx.handle == pDevice->PeerDb.button_handle)
            {
                // Ensure we got at least expected length
                if (p_gattc_evt->params.hvx.len >= 17)
                {
                    ButtonUpdate(pDevice, p_gattc_evt->params.hvx.data);
                }
            }
        }   break;

        // GATT Write response received from the SoftDevice
        case BLE_GATTC_EVT_WRITE_RSP:
        {
            // Check if this conn handle is interesting
            LbsDevice_t* pDevice = GetLbsDevice(p_ble_evt->evt.gattc_evt.conn_handle);
            if(!pDevice)    {   return; }

            //If it is, Process pending Tx in the Tx Buffer
            TxBufferProcess(&m_TxBuffer);
        }   break;

        // GATT read response received from the SoftDevice
        case BLE_GATTC_EVT_READ_RSP:
        {
            const ble_gattc_evt_read_rsp_t* pReadResp = &p_gattc_evt->params.read_rsp;

            if(pReadResp->len < 1)
            {
                TxBufferProcess(&m_TxBuffer);
                break;
            }

            LbsDevice_t* pDevice = GetLbsDevice(p_ble_evt->evt.gattc_evt.conn_handle);

            if(pReadResp->handle == pDevice->PeerDb.button_handle)
            {
                if(pReadResp->len >= 1)
                {
                    ButtonUpdate(pDevice, pReadResp->data);
                }
            }

        }   break;

        default:
            // Update GATT
            nrf_ble_gatt_on_ble_evt(p_ble_evt, p_context);  // FIXME is this needed?

            // No further implementation needed.
            break;
    }
}

/**@brief Function for initializing the BLE stack.
 *
 * @details Initializes the SoftDevice and the BLE event interrupts.
 */
static void ble_stack_init(void)
{
    ret_code_t err_code;

    err_code = nrf_sdh_enable_request();
    APP_ERROR_CHECK(err_code);

    // Configure the BLE stack using the default settings.
    // Fetch the start address of the application RAM.
    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 a handler for BLE events.
    NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
}


static void ClearBondings(void)
{
    // Abort if there is no peers to clear
    if(0 == pm_peer_count())                            {   return;     }
    
    // Abort if we have any active BLE connections
    if(0 != ble_conn_state_central_conn_count())        {   return;     }

    nrf_ble_scan_stop();

    ret_code_t err_code;
    //err_code = softdevice_handler_sd_disable();
    err_code = pm_peers_delete();

    if(NRF_SUCCESS == err_code)
    {
        NRF_LOG_INFO("Starting bondings clearing");
    }
    else
    {
        NRF_LOG_INFO("Bondings clear failed, code %d", err_code);
    }
}



/**@brief Function for handling events from the button handler module.
 *
 * @param[in] pin_no        The pin that the event applies to.
 * @param[in] button_action The button action (press or release).
 */
static void button_event_handler(uint8_t pin_no, uint8_t button_action)
{
    static uint8_t ButtonStatus;    // Keeps an overview of the state of all buttons

    uint8_t iIndex = 0;
//    int8_t  iDeltaVolume = 0;
    uint8_t Mask;

    switch (pin_no)
    {
        case HA0VOLUP_BUTTON:
            iIndex = 0;
//            iDeltaVolume = 1;
            Mask = 1;
            break;

        case HA0VOLDOWN_BUTTON:
            iIndex = 0;
//            iDeltaVolume = -1;
            Mask = 2;
            break;

        case HA1VOLUP_BUTTON:
            iIndex = 1;
//            iDeltaVolume = 1;
            Mask = 4;
            break;

        case HA1VOLDOWN_BUTTON:
            iIndex = 1;
//            iDeltaVolume = -1;
            Mask = 8;
            break;

        default:
            APP_ERROR_HANDLER(pin_no);
            return;
            break;
    }

    // Only adjust volume on button pressed, ignore releases
    if(button_action)
    {
        if(m_aLbsDevices[iIndex].Secure && m_aLbsDevices[iIndex].Discovered)
        {
            //ButtonChange(&m_aLbsDevices[iIndex], iButton);
        }
    }


    // Update button status overview
    if(button_action)
    {
        ButtonStatus |= Mask;
    }
    else
    {
        ButtonStatus &= ~Mask;
    }


    // Check for clear bondings
    if( 
        (button_action) &&              // if event is button pressed
        (ButtonStatus == (1 | 4))       // And only the Clear bonding buttons are currently pressed
    )                                   // Its because the second of the two Clear bonding buttons was just pressed
    {
        ClearBondings();
    }
}


/**@brief Function for initializing the button handler module.
 */
static void buttons_init(void)
{
    ret_code_t err_code;

   // The array must be static because a pointer to it is saved in the button handler module.
    static app_button_cfg_t buttons[] =
    {
        {HA0VOLUP_BUTTON,   false, BUTTON_PULL, button_event_handler},
        {HA0VOLDOWN_BUTTON, false, BUTTON_PULL, button_event_handler},
        {HA1VOLUP_BUTTON,   false, BUTTON_PULL, button_event_handler},
        {HA1VOLDOWN_BUTTON, false, BUTTON_PULL, button_event_handler}
    };

    err_code = app_button_init(buttons, ARRAY_SIZE(buttons), BUTTON_DETECTION_DELAY);
    APP_ERROR_CHECK(err_code);

    err_code = app_button_enable();
    APP_ERROR_CHECK(err_code);
}


static void EnableLbsNotifications(const LbsDevice_t* pDevice)
{
    // Enable button notifications
    uint32_t Status = cccd_configure(pDevice->ConHandle, pDevice->PeerDb.button_cccd_handle, true);
    if(Status != NRF_SUCCESS)
    {
        NRF_LOG_INFO("cccd_configure error %d", Status);
        NRF_LOG_INFO("Handle Button %d, CCCd %d", pDevice->PeerDb.button_handle, pDevice->PeerDb.button_cccd_handle);
    }
}

static void ReadCharacteristic(uint16_t ConHandle, uint16_t CharHandle)
{
    tx_message_t * p_msg = TxBufferPush(&m_TxBuffer);

    p_msg->req.read_handle                     = CharHandle;
    p_msg->conn_handle                         = ConHandle;
    p_msg->type                                = READ_REQ;
    TxBufferProcess(&m_TxBuffer);

    NRF_LOG_DEBUG("Reading characteristic Char %d on con %d", CharHandle, ConHandle);
}

static void OnLbsDeviceReady(const LbsDevice_t* pDevice)
{
    EnableLbsNotifications(pDevice);
    ReadCharacteristic(pDevice->ConHandle, pDevice->PeerDb.button_handle);
}


static void OnLbsDeviceDisconnect(LbsDevice_t* pDevice)
{
    uint32_t iIndex = GetConHandleIndex(pDevice->ConHandle);
    uint32_t LedId = (0 == iIndex) ? LEDHA0_LED : LEDHA1_LED;
    bsp_board_led_off(LedId);
    LbsDeviceClear(pDevice);
}


/**@brief Function for handling database discovery events.
 *
 * @details This function is a callback function to handle events from the database discovery module.
 *          Depending on the UUIDs that are discovered, this function forwards the events
 *          to their respective services.
 *
 * @param[in] p_event  Pointer to the database discovery event.
 */
static void db_disc_handler(ble_db_discovery_evt_t * p_evt)
{
    NRF_LOG_INFO("Call to db_disc_handler for link 0x%x!", p_evt->conn_handle);

    // Check if the Led Button Service was discovered.
    if (
        p_evt->evt_type == BLE_DB_DISCOVERY_COMPLETE &&
        p_evt->params.discovered_db.srv_uuid.uuid == LBS_UUID_SERVICE &&
        p_evt->params.discovered_db.srv_uuid.type == m_ble_uuid_type
       )
    {
        NRF_LOG_INFO("LSB service discovered");

        uint16_t ConHandle = p_evt->conn_handle;
        lbs_db_t DiscoveredDb;

        for (uint32_t i = 0; i < p_evt->params.discovered_db.char_count; i++)
        {
            const ble_gatt_db_char_t * p_char = &(p_evt->params.discovered_db.charateristics[i]);
            NRF_LOG_DEBUG("UUID %x", p_char->characteristic.uuid.uuid);
            NRF_LOG_DEBUG("notify %d, read %d, write %d, write_command %d",
                        p_char->characteristic.char_props.notify,
                        p_char->characteristic.char_props.read,
                        p_char->characteristic.char_props.write,
                        p_char->characteristic.char_props.write_wo_resp
            );


            switch (p_char->characteristic.uuid.uuid)
            {
                case LBS_UUID_LED_CHAR:
                    DiscoveredDb.led_handle                     = p_char->characteristic.handle_value;
                    break;
                case LBS_UUID_BUTTON_CHAR:
                    DiscoveredDb.button_handle      = p_char->characteristic.handle_value;
                    DiscoveredDb.button_cccd_handle = p_char->cccd_handle;
                    break;

                default:
                    NRF_LOG_INFO("Unused characteristic");
                    break;
            }
        }

        LbsDevice_t* pDevice = GetLbsDevice(ConHandle);
        if(!pDevice)
        {
            NRF_LOG_INFO("Discovery before saved device");
            return;
        }

        // Store the discovered service parameters if entry is clear
        if (
            (pDevice->PeerDb.led_handle         == BLE_GATT_HANDLE_INVALID)&&
            (pDevice->PeerDb.button_handle      == BLE_GATT_HANDLE_INVALID)&&
            (pDevice->PeerDb.button_cccd_handle == BLE_GATT_HANDLE_INVALID)
            )
        {
            pDevice->PeerDb = DiscoveredDb;
            pDevice->Discovered = true;

            // See if we are already bonded
            if(!pDevice->Secure)
            {
                pm_peer_id_get(pDevice->ConHandle, &pDevice->PeerId);

                if(PM_PEER_ID_INVALID == pDevice->PeerId)
                {
                    // If not start bonding
                    NRF_LOG_INFO("New LBS Device - Securing connection and bonding");
                    ret_code_t Status = pm_conn_secure(pDevice->ConHandle, false);
                    if( NRF_SUCCESS != Status)
                    {
                        NRF_LOG_INFO("Securing connection failed with code %d", Status);
                    }
                }
            }
            else
            {
                OnLbsDeviceReady(pDevice);
            }
        }
        else
        {
            NRF_LOG_INFO("Discovery info rejected due to peer info already set");
        }
    }
    else
    {
        if(p_evt->evt_type == BLE_DB_DISCOVERY_COMPLETE)
        {
            NRF_LOG_INFO("Discovery complete");
            NRF_LOG_INFO("Failed to discover Button LED Service");
            NRF_LOG_INFO("Found    UUID %d, type %d", p_evt->params.discovered_db.srv_uuid.uuid, p_evt->params.discovered_db.srv_uuid.type);
            NRF_LOG_INFO("Expected UUID %d, type %d", LBS_UUID_SERVICE, m_ble_uuid_type);
        }
        else
        {
            if(p_evt->evt_type == BLE_DB_DISCOVERY_SRV_NOT_FOUND)
            {
                NRF_LOG_INFO("DISC: Service not found");
            }

            NRF_LOG_INFO("Disc event type %d", p_evt->evt_type);
        }
    }
}


/** @brief Database discovery initialization.
 */
static void db_discovery_init(void)
{
    ret_code_t err_code = ble_db_discovery_init(db_disc_handler);
    APP_ERROR_CHECK(err_code);
}


static void PeerManagerEventHandler(pm_evt_t const * p_event)
{
    NRF_LOG_DEBUG("PeerManagerEvent %d", p_event->evt_id);
    pm_handler_on_pm_evt(p_event);
    pm_handler_flash_clean(p_event);

    switch(p_event->evt_id)
    {
        case PM_EVT_CONN_SEC_SUCCEEDED:
        {
            LbsDevice_t* pDevice = GetLbsDevice(p_event->conn_handle);
            if(!pDevice)
            {
                NRF_LOG_INFO("Secured connection to non LBS device!!")
                return;
            }

            pDevice->PeerId = p_event->peer_id;
            pDevice->Secure = true;

            if(pDevice->Discovered)
            {
                OnLbsDeviceReady(pDevice);
            }

        }   break;

        case PM_EVT_PEERS_DELETE_SUCCEEDED:
        {
            NRF_LOG_INFO("Bondings cleared");
            scan_start();
        }   break;

        case PM_EVT_PEERS_DELETE_FAILED:
        {
            NRF_LOG_INFO("Failed to clear Bondings");
        }   break;

        default:
            break;
    }

}


/**@brief Function for initializing power management.
 */
static void power_management_init(void)
{
    ret_code_t err_code;
    err_code = nrf_pwr_mgmt_init();
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for handling the idle state (main loop).
 *
 * @details This function handles any pending log operations, then sleeps until the next event occurs.
 */
static void idle_state_handle(void)
{
    if (NRF_LOG_PROCESS() == false)
    {
        //nrf_pwr_mgmt_run();   // Disabled, as the Time slot events does not cause main process to exit this sleep
    }
}


/** @brief Function for initializing the log module.
 */
static void log_init(void)
{
    ret_code_t err_code = NRF_LOG_INIT(NULL);
    APP_ERROR_CHECK(err_code);

    NRF_LOG_DEFAULT_BACKENDS_INIT();
}


/** @brief Function for initializing the timer.
 */
static void timer_init(void)
{
    ret_code_t err_code = app_timer_init();
    APP_ERROR_CHECK(err_code);
}


static void gatt_event_handler(nrf_ble_gatt_t *p_gatt, nrf_ble_gatt_evt_t const *p_evt)
{
    // FIXME this is called when a parameter value is changed
    NRF_LOG_INFO("Gatt event handler triggered.");
}

/**@brief Function for initializing the GATT module.
 */
static void gatt_init(void)
{
    ret_code_t err_code = nrf_ble_gatt_init(&m_gatt, gatt_event_handler);
    APP_ERROR_CHECK(err_code);
}

static void PeerManagerInit(void)
{
    ret_code_t err_code;

    err_code = pm_init();
    APP_ERROR_CHECK(err_code);

    // Configure peer manager
    static ble_gap_sec_params_t sec_params =
    {
        .bond           = true,
        .mitm           = false,
        .lesc           = 1,
        .keypress       = 0,
        .io_caps        = BLE_GAP_IO_CAPS_NONE,
        .oob            = false,
        .min_key_size   = 7,
        .max_key_size   = 16,
        .kdist_own      =
        {
            .enc            = 1,
            .id             = 1,
            .sign           = 0,
            .link           = 0
        },
        .kdist_peer     =
        {
            .enc            = 1,
            .id             = 1,
            .sign           = 0,
            .link           = 0
        }
    };

    err_code = pm_sec_params_set(&sec_params);
    APP_ERROR_CHECK(err_code);
}

ret_code_t ble_MyStuff_client_init(void)
{
    ret_code_t    err_code;
    ble_uuid_t    lbs_uuid;
    ble_uuid128_t lbs_base_uuid = {LBS_UUID_BASE};

    TxBufferInit(&m_TxBuffer, m_aTxBufferStorage, ARRAY_SIZE(m_aTxBufferStorage));

    // Initialize con handle and peer db storage
    for(uint32_t i = 0; i < NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
    {
        LbsDeviceClear(&m_aLbsDevices[i]);
    }

    // This function sets p_ble_lbs_c->uuid_type to a suitable value
    err_code = sd_ble_uuid_vs_add(&lbs_base_uuid, &m_ble_uuid_type);
    VERIFY_SUCCESS(err_code);

    lbs_uuid.type = m_ble_uuid_type;
    lbs_uuid.uuid = LBS_UUID_SERVICE;

    // Register discovery event handler
    err_code = ble_db_discovery_evt_register(&lbs_uuid);
    VERIFY_SUCCESS(err_code);

    // Register Peer Manager event handler
    err_code = pm_register(PeerManagerEventHandler);
    VERIFY_SUCCESS(err_code);

    return NRF_SUCCESS;
}



// Struct for requesting first time slot
static const nrf_radio_request_t RadioRequestFirst =
{
    .request_type = NRF_RADIO_REQ_TYPE_EARLIEST,
    .params = 
    {
        .earliest =
        {
            .hfclk = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED,
            .priority = NRF_RADIO_PRIORITY_NORMAL,
            .length_us = 600,               // Time slot duration of 600 us
            .timeout_us = 1000000           // Error if time slot not aquired within 1 second
        }
    }
};

//Struct for periodic time slot requesting
static const nrf_radio_request_t RadioRequestPeriodic =
{
    .request_type = NRF_RADIO_REQ_TYPE_NORMAL,
    .params =
    {
        .normal =
        {
            .hfclk = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED,
            .priority = NRF_RADIO_PRIORITY_HIGH,
            .length_us = 600,               // Time slot duration of 600 us
            .distance_us = 10000            // Request periodic time slot every 10 ms
//            .distance_us = 1000000            // Request periodic time slot every 1 s
        }
    }
};


static uint8_t aTestPacket[80];

/**@brief Function for configuring radio for raw transmission of BLE like packets.
 *
 * @details Never use function outside of a timeslot given by the BLE stack, or when BLE stack is disabled
 *
 * @param[in] iFrequency    Frequency in MHz.
 * @param[in] bUse2Mbit     true: 2 Mbit mode, false: 1 Mbit mode.
 * @param[in] iPower        Power in db(note that not all values are legal, consult NRF radio peripheral manual).
 * @param[in] Address       Address is not implemented!!
 * @param[in] CrcSeed       24 bit CRC seed.
 */
static void RadioConfigure(uint32_t iFrequency, bool bUse2Mbit, int8_t iPower, uint32_t Address, uint32_t CrcSeed)
{
    NRF_RADIO->POWER = 0x01;    // Power on the radio, no side effect if already powered on
    NRF_RADIO->INTENSET = 0x200001; // DEBUG
    NRF_RADIO->INTENSET = 0xFFFFFFFF; // DEBUG

    NRF_RADIO->SHORTS = 0x40002;  // END->Disable and TxReady->Start 
    NRF_RADIO->FREQUENCY = iFrequency - 2400;
    NRF_RADIO->TXPOWER = iPower;

    if(bUse2Mbit)
    {
        NRF_RADIO->MODE = 0x04; // 2 mbit BLE
        NRF_RADIO->PCNF0 = 0x1000008;   // 2 bytes pre-amble (2 mbit BLE), Length field on air is 8 bits
    }
    else
    {
        NRF_RADIO->MODE = 0x03; // 1 mbit BLE
        NRF_RADIO->PCNF0 = 0x08;   // 1 byte1 pre-amble (non 2 mbit BLE), Length field on air is 8 bits
    }

    NRF_RADIO->PCNF1 = 0x2030000;   // FIXME
    //NRF_RADIO->BASE0           - Radio base addr, controls something about the address, cant quite figure it out
    //NRF_RADIO->BASE1
    //NRF_RADIO->PREFIX0         - Radio prefix addr, controls something about addr. 
    //NRF_RADIO->PREFIX1         -
    NRF_RADIO->TXADDRESS = 0;

    NRF_RADIO->CRCCNF = 0x0103;
    NRF_RADIO->CRCPOLY = 0x80032C;  // Polynomium config for BLE
    NRF_RADIO->CRCINIT = CrcSeed;  // 0x555555 used for advertising, something agreed on connection for non advertising. Defined in WICL advertising for WICL

    uint32_t WhiteningSeed = (iFrequency + 1);              // BLE whitening seed is ((channel no * 2) + 1)
    NRF_RADIO->DATAWHITEIV = (__RBIT(WhiteningSeed)) >> 25; // Reverse and move to bits 0-6, as thats how NRF radio HW wants it
}


static nrf_radio_signal_callback_return_param_t TimeslotCbReturn;
static uint32_t iLostSlots = 0;
static uint32_t aStates[10];

/**@brief Function for requesting a timeslot with the periodic configuration, when a timeslot has been denied.
 *
 * @param[in] iFrequency    Number of consecutively denied time slots, so it requests a time slot at the correct time.
 */
static void RequestPeriodic(uint32_t iIntervalMultiplier)
{
     static nrf_radio_request_t RadioRequest;
    memcpy(&RadioRequest, &RadioRequestPeriodic, sizeof(RadioRequest));
    //RadioRequest.params.normal.distance_us *= iIntervalMultiplier;
    // Debug - try and adjust timing, by adding an extra 0,1 ms. to get out of sync with connection events
    RadioRequest.params.normal.distance_us *= iIntervalMultiplier;
    RadioRequest.params.normal.distance_us += 100;

    // Turn on LED, Time slot denied (function is only called when being denied a time slot)
    bsp_board_led_on(LEDHA1_LED);
    // debug end

    sd_radio_request(&RadioRequest);
}


/**@brief Function called by stack on time slot start and for events during time slot.
 * 
 * @details Note this function is called in high priority IRQ, blocking even the stack.
 *
 * @param[in] Signal    Reason for function being called
 */
nrf_radio_signal_callback_return_param_t* RadioTimeslotCb(uint8_t Signal)
{
    static nrf_radio_request_t RadioRequest;
    // NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE
    // NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND
    // NRF_RADIO_SIGNAL_CALLBACK_ACTION_END
    // NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END

    // Time stamp radio timeslot start
    NRF_TIMER0->TASKS_CAPTURE[0] = 1U;
    aStates[0] = NRF_TIMER0->CC[0];

    switch(Signal)
    {
        case NRF_RADIO_CALLBACK_SIGNAL_TYPE_START:
        {
            SendPacket(aTestPacket);

            // Turn off LED, as we got the time slot
            bsp_board_led_off(LEDHA1_LED);

            iLostSlots = 0;

            // Request next timeslot
            memcpy(&RadioRequest, &RadioRequestPeriodic, sizeof(RadioRequest)); // Use copy as return arg is not const
            TimeslotCbReturn.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END;
            TimeslotCbReturn.params.request.p_next = &RadioRequest;
        }   break;

        case NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0:
        {
            NRF_LOG_INFO("A");
            APP_ERROR_HANDLER(Signal);
            return 0;
        }   break;

        case NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO:
        {
            NRF_LOG_INFO("B");
            APP_ERROR_HANDLER(Signal);
            return 0;
        }   break;

        case NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED:
        {
            NRF_LOG_INFO("C");
            APP_ERROR_HANDLER(Signal);
            return 0;
        }   break;

        case NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED:
        {
            NRF_LOG_INFO("D");
            APP_ERROR_HANDLER(Signal);
            return 0;
        }   break;

        default:
        NRF_LOG_INFO("E");
            APP_ERROR_HANDLER(Signal);
            return 0;
            break;
    }

    // Time stamp radio timeslot end
    NRF_TIMER0->TASKS_CAPTURE[0] = 1U;
    aStates[6] = NRF_TIMER0->CC[0];
    return &TimeslotCbReturn;
}

/**@brief Function called by stack on SOC events.
 * 
 * @details Among other things, called by stack with new of time slots being cancelled.
 *
 * @param[in] sys_evt   Reason for function being called
 * @param[in] pContext  Unused.
 */
static void soc_evt_handler(uint32_t sys_evt, void * pContext)
{
    switch(sys_evt)
    {
        case NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN:
        {
            NRF_LOG_INFO("Timeslot Invalid");
        }   break;

        case NRF_EVT_RADIO_SESSION_IDLE:
        {
            NRF_LOG_INFO("Timeslot idle");
        }   break;

        case NRF_EVT_RADIO_SESSION_CLOSED:
        {
            NRF_LOG_INFO("Timeslot closed");
        }   break;

        case NRF_EVT_RADIO_BLOCKED:
        {
            NRF_LOG_INFO("Timeslot blocked");

            iLostSlots++;

            // Request next timeslot
            RequestPeriodic(iLostSlots + 1);
        }   break;

        case NRF_EVT_RADIO_CANCELED:
        {
            NRF_LOG_INFO("Timeslot canceled");

            iLostSlots++;

            // Request next timeslot
            RequestPeriodic(iLostSlots + 1);
        }   break;

        case NRF_EVT_FLASH_OPERATION_SUCCESS:
        {
            NRF_LOG_INFO("Flash Wr OK");
            break;
        }

        default:
            NRF_LOG_INFO("Unknown soc event %d", sys_evt);
            APP_ERROR_HANDLER(sys_evt);
            break;
    }
}

static bool bTxing = false;

/**@brief Sends pPacket as 2Mbit BLE on 2404 MHz with advertising CRC and some undefined address
 * 
 * @details Sends BLE like 2 Mbit package and saves a time stamp for each radio state during the activity.
 *          NOTE: Only call when BEL stack does not control the radio, e.g. in time slots or when stack is disabled.
 *
 * @param[in] pPacket   First byte of Packet is length of the packet. The following bytes is the payload.
 */
static void SendPacket(uint8_t * pPacket)
{
    //Power : int8_t(8), int8_t(4), int8_t(0), int8_t(-40)
    uint32_t iFrequency = 2404;
    bool bUse2Mbit = true;
    int8_t iPower = (int8_t)4;
    uint32_t Address = 0;   // FIXME
    uint32_t CrcSeed = 0x555555;    // 0x555555 used for advertising, something agreed on connection for non advertising. Defined in WICL advertising for WICL
    RadioConfigure(iFrequency, bUse2Mbit, iPower, Address, CrcSeed);
    NRF_RADIO->PACKETPTR = (uint32_t)pPacket;

    // send the packet:
    NRF_RADIO->TASKS_TXEN   = 1;

    // Time stamp the radio states, for debug information
    // Time stamp TX start
    NRF_TIMER0->TASKS_CAPTURE[0] = 1U;
    aStates[1] = NRF_TIMER0->CC[0];

    // Wait for radio to exit disabled
    while(0x00 == NRF_RADIO->STATE);

    // Time stamp disabled exited
    NRF_TIMER0->TASKS_CAPTURE[0] = 1U;
    aStates[2] = NRF_TIMER0->CC[0];

    // Wait for radio to exit Tx ramp up
    while(0x09 == NRF_RADIO->STATE);

    // Time stamp TX ramp up exited
    NRF_TIMER0->TASKS_CAPTURE[0] = 1U;
    aStates[3] = NRF_TIMER0->CC[0];


    // Wait for radio to exit TX
    while(0x0B == NRF_RADIO->STATE);

    // Time stamp radio TX exited
    NRF_TIMER0->TASKS_CAPTURE[0] = 1U;
    aStates[4] = NRF_TIMER0->CC[0];


    // Wait for radio to exit Tx disable state
    while(0x0C == NRF_RADIO->STATE);

    // Time stamp radio Tx disable exit
    NRF_TIMER0->TASKS_CAPTURE[0] = 1U;
    aStates[5] = NRF_TIMER0->CC[0];

    bTxing = true;
}

int main(void)
{
    NRF_NVMC->ICACHECNF |= 0x0100;
    // Initialize.
    log_init();
    timer_init();
    leds_init();
    buttons_init();
    power_management_init();
    ble_stack_init();
    gatt_init();
    db_discovery_init();
    PeerManagerInit();

    uint32_t InitStatus = ble_MyStuff_client_init();
    if(NRF_SUCCESS != InitStatus)
    {
        NRF_LOG_INFO("INIT FAILED!!");
        APP_ERROR_CHECK(InitStatus);
    }

    NRF_SDH_SOC_OBSERVER(m_soc_observer, APP_SOC_OBSERVER_PRIO, soc_evt_handler, NULL);

    ble_conn_state_init();
    scan_init();


    // Start execution.
    NRF_LOG_INFO("Time slot Tx example.");

    scan_start();

    memset(aTestPacket, 0xDEADBEEF, 80);
    aTestPacket[0] = 79;

    ret_code_t Status = sd_radio_session_open(RadioTimeslotCb);
    APP_ERROR_CHECK(Status);

    Status = sd_radio_request(&RadioRequestFirst);
    APP_ERROR_CHECK(Status);

    for (;;)
    {
        if(bTxing)
        {
            bTxing = false;
            // Debug
            // Print debug info
            // NRF_LOG_INFO("States0 %X, %X, %X, %X", aStates[0], aStates[1], aStates[2], aStates[3]);
            // NRF_LOG_INFO("States1 %X, %X, %X, %X", aStates[4], aStates[5], aStates[6], aStates[7]);
            // Debug end
        }
        else
        {
            idle_state_handle();
        }
    }
}
