/**
 * Device Firmware Update Service Client
 */

#include "sdk_common.h"
#if NRF_MODULE_ENABLED(BLE_DFU_C)
#include "ble_dfu_c.h"
#include <string.h>

#define NRF_LOG_MODULE_NAME ble_dfu_c
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();


#define MAX_CTRL_POINT_RESP_PARAM_LEN 3 /**< Max length of the responses. */

#define BLE_DFU_SERVICE_UUID 0xFE59 /**< The 16-bit UUID of the Secure DFU Service. */

/**@brief Function that is called if no event handler is provided.
 */
static void dummy_evt_handler(ble_dfu_c_t * p_ble_dfu_c, ble_dfu_c_evt_t const * p_evt) { NRF_LOG_DEBUG("Dummy event handler received event 0x%x", p_evt->evt_type); }


#define TX_BUFFER_MASK 0x07                 /**< TX Buffer mask, must be a mask of continuous zeroes, followed by continuous sequence of ones: 000...111. */
#define TX_BUFFER_SIZE (TX_BUFFER_MASK + 1) /**< Size of send buffer, which is 1 higher than the mask. */

#define WRITE_MESSAGE_LENGTH 8 /**< Length of the write message for CCCD. */

typedef enum
{
    READ_REQ, /**< Type identifying that this tx_message is a read request. */
    WRITE_REQ /**< Type identifying that this tx_message is a write request. */
} tx_request_t;

/**@brief Structure for writing a message to the peer, i.e. CCCD.
 */
typedef struct
{
    uint8_t gattc_value[WRITE_MESSAGE_LENGTH]; /**< The message to write. */
    ble_gattc_write_params_t gattc_params;     /**< GATTC parameters for this message. */
} write_params_t;

/**@brief Structure for holding data to be transmitted to the connected central.
 */
typedef struct
{
    uint16_t conn_handle; /**< Connection handle to be used when transmitting this message. */
    tx_request_t type;    /**< Type of this message, i.e. read or write message. */
    union {
        uint16_t read_handle;     /**< Read request message. */
        write_params_t write_req; /**< Write request message. */
    } req;
} tx_message_t;


static tx_message_t m_tx_buffer[TX_BUFFER_SIZE]; /**< Transmit buffer for messages to be transmitted to the central. */
static uint32_t m_tx_insert_index = 0;           /**< Current index in the transmit buffer where the next message should be inserted. */
static uint32_t m_tx_index = 0;                  /**< Current index in the transmit buffer from where the next message to be transmitted resides. */


/**@brief Function for passing any pending request from the buffer to the stack.
 */
static uint32_t tx_buffer_process(void)
{
    if (m_tx_index != m_tx_insert_index)
    {
        uint32_t err_code;

        if (m_tx_buffer[m_tx_index].type == READ_REQ)
        {
            err_code = sd_ble_gattc_read(m_tx_buffer[m_tx_index].conn_handle, m_tx_buffer[m_tx_index].req.read_handle, 0);
        }
        else
        {
            err_code = sd_ble_gattc_write(m_tx_buffer[m_tx_index].conn_handle, &m_tx_buffer[m_tx_index].req.write_req.gattc_params);
        }

        if (err_code != NRF_SUCCESS)
        {
            NRF_LOG_DEBUG("Error: Msg[%d] will be repeated..", m_tx_index);
        }
        return err_code;
    }
    return NRF_SUCCESS;
}


/**@brief     Function for handling write response events.
 *
 * @param[in] p_ble_hrs_c Pointer to the Heart Rate Client structure.
 * @param[in] p_ble_evt   Pointer to the BLE event received.
 */
static void on_write_rsp(ble_dfu_c_t * p_ble_dfu_c, const ble_evt_t * p_ble_evt)
{
    // Check if the event if on the link for this instance
    if (p_ble_dfu_c->conn_handle != p_ble_evt->evt.gattc_evt.conn_handle)
    {
        return;
    }

    if ((p_ble_evt->evt.gattc_evt.gatt_status == BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION) || (p_ble_evt->evt.gattc_evt.gatt_status == BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION))
    {
        // Do nothing to reattempt write.
    }
    else
    {
        if (p_ble_evt->evt.gattc_evt.params.write_rsp.handle == p_ble_dfu_c->handles.chr || p_ble_evt->evt.gattc_evt.params.write_rsp.handle == p_ble_dfu_c->handles.cccd)
        {
            m_tx_index++;
            m_tx_index &= TX_BUFFER_MASK;
        }
    }

    // Check if there is any message to be sent across to the peer and send it.
    tx_buffer_process();
}


/**@brief Disconnect event handler.
 *
 * @param[in]   p_ble_evt   Event received from the BLE stack.
 */
static void on_disconnect(ble_dfu_c_t * p_ble_dfu_c, ble_evt_t const * p_ble_evt)
{
    if (p_ble_dfu_c->conn_handle != p_ble_evt->evt.gap_evt.conn_handle)
    {
        return;
    }

    if (p_ble_dfu_c->evt_handler != NULL)
    {
        ble_dfu_c_evt_t dfu_c_evt;

        dfu_c_evt.conn_handle = p_ble_dfu_c->conn_handle;
        dfu_c_evt.evt_type = BLE_DFU_C_EVT_DISCONNECTED;

        p_ble_dfu_c->evt_handler(p_ble_dfu_c, &dfu_c_evt);
    }

    p_ble_dfu_c->conn_handle = BLE_CONN_HANDLE_INVALID;
    p_ble_dfu_c->indication_enabled = false;
}

/**@brief     Function for handling Handle Value Notification received from the SoftDevice.
 *
 * @details   This function will uses the Handle Value Notification received from the SoftDevice
 *            and checks if it is a notification of the NUS TX characteristic from the peer. If
 *            it is, this function will decode the data and send it to the
 *            application.
 *
 * @param[in] p_ble_nus_c Pointer to the NUS Client structure.
 * @param[in] p_ble_evt   Pointer to the BLE event received.
 */
static void on_hvx(ble_dfu_c_t * p_ble_dfu_c, ble_evt_t const * p_ble_evt)
{
    // HVX can only occur from client sending.
    if ((p_ble_dfu_c->handles.chr != BLE_GATT_HANDLE_INVALID) && (p_ble_evt->evt.gattc_evt.params.hvx.handle == p_ble_dfu_c->handles.chr) && (p_ble_dfu_c->evt_handler != NULL))
    {
        NRF_LOG_ERROR("on HVX: %d", p_ble_evt->evt.gattc_evt.params.hvx.handle);
        NRF_LOG_HEXDUMP_DEBUG(p_ble_evt->evt.gattc_evt.params.hvx.data, p_ble_evt->evt.gattc_evt.params.hvx.len);

        ble_dfu_c_evt_t dfu_c_evt;

        dfu_c_evt.conn_handle = p_ble_dfu_c->conn_handle;
        dfu_c_evt.evt_type = BLE_DFU_C_EVT_BOOTLOADER_STARTING;

        p_ble_dfu_c->evt_handler(p_ble_dfu_c, &dfu_c_evt);
        NRF_LOG_DEBUG("DFU C bootloader starting?");
    }
}


void ble_dfu_c_on_db_disc_evt(ble_dfu_c_t * p_ble_dfu_c, ble_db_discovery_evt_t * p_evt)
{
    ble_dfu_c_evt_t dfu_c_evt;
    memset(&dfu_c_evt, 0, sizeof(ble_dfu_c_evt_t));

    ble_gatt_db_char_t * p_chars = p_evt->params.discovered_db.charateristics;

    // Check if the DFU was discovered.
    if ((p_evt->evt_type == BLE_DB_DISCOVERY_COMPLETE) && (p_evt->params.discovered_db.srv_uuid.uuid == BLE_DFU_SERVICE_UUID) && (p_evt->params.discovered_db.srv_uuid.type == p_ble_dfu_c->uuid_type))
    {
        for (uint32_t i = 0; i < p_evt->params.discovered_db.char_count; i++)
        {
            switch (p_chars[i].characteristic.uuid.uuid)
            {
                case BLE_DFU_C_CHAR_UUID:
                    dfu_c_evt.handles.chr = p_chars[i].characteristic.handle_value;
                    dfu_c_evt.handles.cccd = p_chars[i].cccd_handle;
                    break;

                default:
                    break;
            }
        }
        if (p_ble_dfu_c->evt_handler != NULL)
        {
            dfu_c_evt.conn_handle = p_evt->conn_handle;
            dfu_c_evt.evt_type = BLE_DFU_C_EVT_DISCOVERY_COMPLETE;
            p_ble_dfu_c->evt_handler(p_ble_dfu_c, &dfu_c_evt);
        }
    }
}

void ble_dfu_c_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
{

    ble_dfu_c_t * p_ble_dfu_c = (ble_dfu_c_t *)p_context;

    if ((p_ble_dfu_c == NULL) || (p_ble_evt == NULL))
    {
        return;
    }

    if ((p_ble_dfu_c->conn_handle != BLE_CONN_HANDLE_INVALID) && (p_ble_dfu_c->conn_handle != p_ble_evt->evt.gap_evt.conn_handle))
    {
        return;
    }

    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GATTC_EVT_HVX:
            on_hvx(p_ble_dfu_c, p_ble_evt);
            break;

        case BLE_GATTC_EVT_WRITE_RSP:
            on_write_rsp(p_ble_dfu_c, p_ble_evt);
            break;

        case BLE_GAP_EVT_DISCONNECTED:
            on_disconnect(p_ble_dfu_c, p_ble_evt);
            break;

        default:
            // no implementation
            break;
    }
}


uint32_t ble_dfu_c_init(ble_dfu_c_t * p_ble_dfu_c, const ble_dfu_c_init_t * p_ble_dfu_c_init)
{
    uint32_t err_code;
    ble_uuid_t service_uuid;
    ble_uuid128_t base_uuid = BLE_NORDIC_VENDOR_BASE_UUID;

    VERIFY_PARAM_NOT_NULL(p_ble_dfu_c);
    VERIFY_PARAM_NOT_NULL(p_ble_dfu_c_init);

    // Initialize the service structure.
    p_ble_dfu_c->conn_handle = BLE_CONN_HANDLE_INVALID;
    p_ble_dfu_c->evt_handler = p_ble_dfu_c_init->evt_handler;
    p_ble_dfu_c->handles.chr = BLE_GATT_HANDLE_INVALID;
    p_ble_dfu_c->handles.cccd = BLE_GATT_HANDLE_INVALID;

    BLE_UUID_BLE_ASSIGN(service_uuid, BLE_DFU_SERVICE_UUID);

    if (p_ble_dfu_c->evt_handler == NULL)
    {
        p_ble_dfu_c->evt_handler = dummy_evt_handler;
    }

    p_ble_dfu_c->uuid_type = BLE_UUID_TYPE_BLE;

    return ble_db_discovery_evt_register(&service_uuid);
}


uint32_t ble_dfu_c_indication_enable(ble_dfu_c_t * p_ble_dfu_c, bool enable)
{
    VERIFY_PARAM_NOT_NULL(p_ble_dfu_c);

    if ((p_ble_dfu_c->conn_handle == BLE_CONN_HANDLE_INVALID) || (p_ble_dfu_c->handles.cccd == BLE_GATT_HANDLE_INVALID))
    {
        return NRF_ERROR_INVALID_STATE;
    }

    ret_code_t err_code = NRF_SUCCESS;

    if (p_ble_dfu_c->indication_enabled == false)
    {

        tx_message_t * p_msg;
        uint16_t cccd_val = enable ? BLE_GATT_HVX_INDICATION : 0;

        p_msg = &m_tx_buffer[m_tx_insert_index++];
        m_tx_insert_index &= TX_BUFFER_MASK;

        p_msg->req.write_req.gattc_params.handle = p_ble_dfu_c->handles.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 = p_ble_dfu_c->conn_handle;
        p_msg->type = WRITE_REQ;
        err_code = tx_buffer_process();
        p_ble_dfu_c->indication_enabled = true;
    }
    return err_code;
}

uint32_t ble_dfu_c_advname_set(ble_dfu_c_t * p_ble_dfu_c, uint8_t * p_adv_name, uint16_t length)
{
    VERIFY_PARAM_NOT_NULL(p_ble_dfu_c);

    if (length > BLE_GAP_DEVNAME_DEFAULT_LEN - 2)
    {
        NRF_LOG_WARNING("Content too long.");
        return NRF_ERROR_INVALID_PARAM;
    }
    if (p_ble_dfu_c->conn_handle == BLE_CONN_HANDLE_INVALID)
    {
        NRF_LOG_WARNING("Connection handle invalid.");
        return NRF_ERROR_INVALID_STATE;
    }

    tx_message_t * p_msg;
    p_msg = &m_tx_buffer[m_tx_insert_index++];
    m_tx_insert_index &= TX_BUFFER_MASK;

    p_msg->req.write_req.gattc_params.handle = p_ble_dfu_c->handles.chr;
    p_msg->req.write_req.gattc_params.len = (length + 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->conn_handle = p_ble_dfu_c->conn_handle;
    p_msg->type = WRITE_REQ;

    p_msg->req.write_req.gattc_value[0] = DFU_OP_SET_ADV_NAME;
    p_msg->req.write_req.gattc_value[1] = length;
    memcpy(&p_msg->req.write_req.gattc_value[2], p_adv_name, length);

    return tx_buffer_process();
}

uint32_t ble_dfu_c_enter_bootloader(ble_dfu_c_t * p_ble_dfu_c)
{
    VERIFY_PARAM_NOT_NULL(p_ble_dfu_c);

    if (p_ble_dfu_c->conn_handle == BLE_CONN_HANDLE_INVALID)
    {
        NRF_LOG_WARNING("Connection handle invalid.");
        return NRF_ERROR_INVALID_STATE;
    }

    tx_message_t * p_msg;
    p_msg = &m_tx_buffer[m_tx_insert_index++];
    m_tx_insert_index &= TX_BUFFER_MASK;

    p_msg->req.write_req.gattc_params.handle = p_ble_dfu_c->handles.chr;
    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->conn_handle = p_ble_dfu_c->conn_handle;
    p_msg->type = WRITE_REQ;

    p_msg->req.write_req.gattc_value[0] = DFU_OP_ENTER_BOOTLOADER;

    return tx_buffer_process();
}

uint32_t ble_dfu_c_handles_assign(ble_dfu_c_t * p_ble_dfu_c, uint16_t conn_handle, ble_dfu_c_handles_t const * p_peer_handles)
{
    VERIFY_PARAM_NOT_NULL(p_ble_dfu_c);

    p_ble_dfu_c->conn_handle = conn_handle;
    if (p_peer_handles != NULL)
    {
        p_ble_dfu_c->handles.chr = p_peer_handles->chr;
        p_ble_dfu_c->handles.cccd = p_peer_handles->cccd;
    }
    return NRF_SUCCESS;
}

#endif