/**
 * Copyright (c) 2012 - 2018, Nordic Semiconductor ASA
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 *
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
/* Attention!
 * To maintain compliance with Nordic Semiconductor ASA's Bluetooth profile
 * qualification listings, this section of source code must not be modified.
 */
#include "sdk_common.h"
#if NRF_MODULE_ENABLED(BLE_HIDS)
#include "ble_hids.h"
#include <string.h>
#include "app_error.h"
#include "ble_srv_common.h"


// Protocol Mode values
#define PROTOCOL_MODE_BOOT               0x00                        /**< Boot Protocol Mode. */
#define PROTOCOL_MODE_REPORT             0x01                        /**< Report Protocol Mode. */

// HID Control Point values
#define HIDS_CONTROL_POINT_SUSPEND       0                           /**< Suspend command. */
#define HIDS_CONTROL_POINT_EXIT_SUSPEND  1                           /**< Exit Suspend command. */

#define DEFAULT_PROTOCOL_MODE            PROTOCOL_MODE_REPORT        /**< Default value for the Protocol Mode characteristic. */
#define INITIAL_VALUE_HID_CONTROL_POINT  HIDS_CONTROL_POINT_SUSPEND  /**< Initial value for the HID Control Point characteristic. */

#define ENCODED_HID_INFORMATION_LEN      4                           /**< Maximum size of an encoded HID Information characteristic. */

/**@brief Handles errors for HID service module.
 *
 * @param[in]   _p_hids   HID Service structure.
 * @param[in]   _err_code Maximum number of HID hosts connected at a time.
 *
 */
#define BLE_HIDS_ERROR_HANDLE(_p_hids, _err_code)                               \
    do                                                                          \
    {                                                                           \
        if (((_err_code) != NRF_SUCCESS) && ((_p_hids)->error_handler != NULL)) \
        {                                                                       \
            (_p_hids)->error_handler((_err_code));                              \
        }                                                                       \
    } while (0)


/**@brief Function for calculating the link context size for the HID service.
 *
 * @param[in]   p_hids      HID Service structure.
 *
 * @return      Link context size.
 */
static size_t ble_hids_client_context_size_calc(ble_hids_t * p_hids)
{
    size_t client_size = sizeof(ble_hids_client_context_t) + (BOOT_KB_INPUT_REPORT_MAX_SIZE) +
                         (BOOT_KB_OUTPUT_REPORT_MAX_SIZE) + (BOOT_MOUSE_INPUT_REPORT_MAX_SIZE);

    if (p_hids->p_inp_rep_init_array != NULL)
    {
        for (uint32_t i = 0; i < p_hids->inp_rep_count; i++)
        {
            client_size += p_hids->p_inp_rep_init_array[i].max_len;
        }
    }
    if (p_hids->p_outp_rep_init_array != NULL)
    {
        for (uint32_t i = 0; i < p_hids->outp_rep_count; i++)
        {
            client_size += p_hids->p_outp_rep_init_array[i].max_len;
        }
    }
    if (p_hids->p_feature_rep_init_array != NULL)
    {
        for (uint32_t i = 0; i < p_hids->feature_rep_count; i++)
        {
            client_size += p_hids->p_feature_rep_init_array[i].max_len;
        }
    }

    return client_size;
}


/**@brief Function for making a HID Service characteristic id.
 *
 * @param[in]   uuid        UUID of characteristic.
 * @param[in]   rep_type    Type of report.
 * @param[in]   rep_index   Index of the characteristic.
 *
 * @return      HID Service characteristic id structure.
 */
static ble_hids_char_id_t make_char_id(uint16_t uuid, uint8_t rep_type, uint8_t rep_index)
{
    ble_hids_char_id_t char_id = {0};

    char_id.uuid      = uuid;
    char_id.rep_type  = rep_type;
    char_id.rep_index = rep_index;

    return char_id;
}


/**@brief Function for handling the Connect event.
 *
 * @param[in]   p_hids      HID Service structure.
 * @param[in]   p_ble_evt   Event received from the BLE stack.
 */
static void on_connect(ble_hids_t * p_hids, ble_evt_t const * p_ble_evt)
{
    uint32_t                    err_code;
    ble_gatts_value_t           gatts_value;
    ble_hids_client_context_t * p_client = NULL;

    err_code = blcm_link_ctx_get(p_hids->p_link_ctx_storage,
                                 p_ble_evt->evt.gap_evt.conn_handle,
                                 (void *) &p_client);
    BLE_HIDS_ERROR_HANDLE(p_hids, err_code);

    if (p_client == NULL)
    {
        return;
    }

    memset(p_client, 0, ble_hids_client_context_size_calc(p_hids));

    if (p_hids->protocol_mode_handles.value_handle)
    {
        // Set Protocol Mode characteristic value to default value
        p_client->protocol_mode = DEFAULT_PROTOCOL_MODE;

        // Initialize value struct.
        memset(&gatts_value, 0, sizeof(gatts_value));

        gatts_value.len     = sizeof(uint8_t);
        gatts_value.offset  = 0;
        gatts_value.p_value = &p_client->protocol_mode;

        err_code = sd_ble_gatts_value_set(p_ble_evt->evt.gap_evt.conn_handle,
                                          p_hids->protocol_mode_handles.value_handle,
                                          &gatts_value);
        BLE_HIDS_ERROR_HANDLE(p_hids, err_code);
    }
}


/**@brief Function for handling write events to the HID Control Point value.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_evt_write   Write event received from the BLE stack.
 */
static void on_control_point_write(ble_hids_t * p_hids, ble_evt_t const * p_ble_evt)
{
    ret_code_t                        err_code;
    ble_hids_client_context_t       * p_host;
    ble_gatts_evt_write_t     const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;

    err_code = blcm_link_ctx_get(p_hids->p_link_ctx_storage,
                                 p_ble_evt->evt.gatts_evt.conn_handle,
                                 (void *) &p_host);
    BLE_HIDS_ERROR_HANDLE(p_hids, err_code);

    if (p_host == NULL)
    {
        return;
    }

    if (p_evt_write->len == 1)
    {
        ble_hids_evt_t evt;

        switch (p_evt_write->data[0])
        {
            case HIDS_CONTROL_POINT_SUSPEND:
                evt.evt_type = BLE_HIDS_EVT_HOST_SUSP;
                break;

            case HIDS_CONTROL_POINT_EXIT_SUSPEND:
                evt.evt_type = BLE_HIDS_EVT_HOST_EXIT_SUSP;
                break;

            default:
                // Illegal Control Point value, ignore
                return;
        }

        // Store the new Control Point value for the host
        p_host->ctrl_pt = p_evt_write->data[0];

        // HID Control Point written, propagate event to application
        if (p_hids->evt_handler != NULL)
        {
            evt.p_ble_evt = p_ble_evt;
            p_hids->evt_handler(p_hids, &evt);
        }
    }
}


/**@brief Function for handling write events to the Protocol Mode value.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_ble_evt     BLE event received from the BLE stack.
 */
static void on_protocol_mode_write(ble_hids_t * p_hids, ble_evt_t const * p_ble_evt)
{
    ret_code_t                        err_code;
    ble_hids_client_context_t       * p_host;
    ble_gatts_evt_write_t     const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;

    err_code = blcm_link_ctx_get(p_hids->p_link_ctx_storage,
                                 p_ble_evt->evt.gatts_evt.conn_handle,
                                 (void *) &p_host);
    BLE_HIDS_ERROR_HANDLE(p_hids, err_code);


    if (p_host == NULL)
    {
        return;
    }

    if (p_evt_write->len == 1)
    {
        ble_hids_evt_t evt;

        switch (p_evt_write->data[0])
        {
            case PROTOCOL_MODE_BOOT:
                evt.evt_type = BLE_HIDS_EVT_BOOT_MODE_ENTERED;
                break;

            case PROTOCOL_MODE_REPORT:
                evt.evt_type = BLE_HIDS_EVT_REPORT_MODE_ENTERED;
                break;

            default:
                // Illegal Protocol Mode value, ignore
                return;
        }

        // Store Protocol Mode of the host
        p_host->protocol_mode = p_evt_write->data[0];

        // HID Protocol Mode written, propagate event to application
        if (p_hids->evt_handler != NULL)
        {
            evt.p_ble_evt = p_ble_evt;
            p_hids->evt_handler(p_hids, &evt);
        }
    }
}


/**@brief Function for handling authorize read events to the Protocol Mode value.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_ble_evt     BLE event received from the BLE stack.
 */
void on_protocol_mode_read_auth(ble_hids_t * p_hids, ble_evt_t const * p_ble_evt)
{
    ret_code_t                                   err_code;
    ble_gatts_rw_authorize_reply_params_t        auth_read_params;
    ble_hids_client_context_t                  * p_host;
    ble_gatts_evt_rw_authorize_request_t const * p_read_auth =
        &p_ble_evt->evt.gatts_evt.params.authorize_request;

    err_code = blcm_link_ctx_get(p_hids->p_link_ctx_storage,
                                 p_ble_evt->evt.gatts_evt.conn_handle,
                                 (void *) &p_host);
    BLE_HIDS_ERROR_HANDLE(p_hids, err_code);

    // Update GATTS table with this host's Protocol Mode value and authorize Read
    if (p_host != NULL)
    {
        memset(&auth_read_params, 0, sizeof(auth_read_params));

        auth_read_params.type                    = BLE_GATTS_AUTHORIZE_TYPE_READ;
        auth_read_params.params.read.gatt_status = BLE_GATT_STATUS_SUCCESS;
        auth_read_params.params.read.offset      = p_read_auth->request.read.offset;
        auth_read_params.params.read.len         = sizeof(uint8_t);
        auth_read_params.params.read.p_data      = &p_host->protocol_mode;
        auth_read_params.params.read.update      = 1;

        err_code = sd_ble_gatts_rw_authorize_reply(p_ble_evt->evt.gap_evt.conn_handle,
                                                   &auth_read_params);
        BLE_HIDS_ERROR_HANDLE(p_hids, err_code);
    }
}


/**@brief Function for handling write events to a report CCCD.
 *
 * @param[in]   p_hids          HID Service structure.
 * @param[in]   p_char_id       Id of report characteristic.
 * @param[in]   p_ble_evt       BLE event received from the BLE stack.
 */
static void on_report_cccd_write(ble_hids_t         * p_hids,
                                 ble_hids_char_id_t * p_char_id,
                                 ble_evt_t    const * p_ble_evt)
{
    ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;

    if (p_evt_write->len == 2)
    {
        // CCCD written, update notification state
        if (p_hids->evt_handler != NULL)
        {
            ble_hids_evt_t evt;

            if (ble_srv_is_notification_enabled(p_evt_write->data))
            {
                evt.evt_type = BLE_HIDS_EVT_NOTIF_ENABLED;
            }
            else
            {
                evt.evt_type = BLE_HIDS_EVT_NOTIF_DISABLED;
            }
            evt.params.notification.char_id = *p_char_id;
            evt.p_ble_evt                   = p_ble_evt;

            p_hids->evt_handler(p_hids, &evt);
        }
    }
}


/**@brief Function for handling write events to a report value.
 *
 * @param[in]   p_hids      HID Service structure.
 * @param[in]   p_ble_evt   Pointer to BLE event structure.
 * @param[in]   p_char_id   Id of report characteristic.
 * @param[in]   rep_offset  Offset to the affected HID report data.
 * @param[in]   rep_max_len Maximum HID report length.
 */
static void on_report_value_write(ble_hids_t         * p_hids,
                                  ble_evt_t    const * p_ble_evt,
                                  ble_hids_char_id_t * p_char_id,
                                  uint16_t             rep_offset,
                                  uint16_t             rep_max_len)
{
    // Update host's Output Report data
    ret_code_t                        err_code;
    uint8_t                         * p_report;
    ble_hids_client_context_t       * p_host;
    ble_gatts_evt_write_t     const * p_write = &p_ble_evt->evt.gatts_evt.params.write;

    err_code = blcm_link_ctx_get(p_hids->p_link_ctx_storage,
                                 p_ble_evt->evt.gatts_evt.conn_handle,
                                 (void *) &p_host);
    BLE_HIDS_ERROR_HANDLE(p_hids, err_code);

    // Store the written values in host's report data
    if ((p_host != NULL) && (p_write->len + p_write->offset <= rep_max_len))
    {
        p_report = (uint8_t *) p_host + rep_offset;
        memcpy(p_report, p_write->data, p_write->len);
    }
    else
    {
        return;
    }

    // Notify the applicartion
    if (p_hids->evt_handler != NULL)
    {
        ble_hids_evt_t evt;

        evt.evt_type                  = BLE_HIDS_EVT_REP_CHAR_WRITE;
        evt.params.char_write.char_id = *p_char_id;
        evt.params.char_write.offset  = p_ble_evt->evt.gatts_evt.params.write.offset;
        evt.params.char_write.len     = p_ble_evt->evt.gatts_evt.params.write.len;
        evt.params.char_write.data    = p_ble_evt->evt.gatts_evt.params.write.data;
        evt.p_ble_evt                 = p_ble_evt;

        p_hids->evt_handler(p_hids, &evt);
    }
}


/**@brief Handle authorize read events to a report value.
 *
 * @param[in]   p_hids              HID Service structure.
 * @param[in]   p_char_id           Id of report characteristic.
 * @param[in]   p_ble_evt           Pointer to BLE event structure.
 * @param[in]   rep_offset          Report data offset.
 * @param[in]   rep_max_len         Report maximum length.
 */
static void on_report_value_read_auth(ble_hids_t         * p_hids,
                                      ble_hids_char_id_t * p_char_id,
                                      ble_evt_t    const * p_ble_evt,
                                      uint16_t             rep_offset,
                                      uint16_t             rep_max_len)
{
    ble_gatts_rw_authorize_reply_params_t auth_read_params;
    ble_hids_client_context_t           * p_host;
    uint8_t                             * p_report;
    uint16_t                              read_offset;
    uint32_t                              err_code = NRF_SUCCESS;

    err_code = blcm_link_ctx_get(p_hids->p_link_ctx_storage,
                                 p_ble_evt->evt.gatts_evt.conn_handle,
                                 (void *) &p_host);
    BLE_HIDS_ERROR_HANDLE(p_hids, err_code);

    // Update Report GATTS table with host's current report data
    if (p_host != NULL)
    {
        read_offset = p_ble_evt->evt.gatts_evt.params.authorize_request.request.read.offset;
        p_report    = (uint8_t *) p_host + rep_offset;
        memset(&auth_read_params, 0, sizeof(auth_read_params));

        auth_read_params.type                    = BLE_GATTS_AUTHORIZE_TYPE_READ;
        auth_read_params.params.read.gatt_status = BLE_GATT_STATUS_SUCCESS;
        auth_read_params.params.read.offset      = read_offset;
        auth_read_params.params.read.len         = rep_max_len - read_offset;
        auth_read_params.params.read.p_data      = p_report + read_offset;
        auth_read_params.params.read.update      = 1;

        err_code = sd_ble_gatts_rw_authorize_reply(p_ble_evt->evt.gap_evt.conn_handle,
                                                   &auth_read_params);

        BLE_HIDS_ERROR_HANDLE(p_hids, err_code);
    }
    else
    {
        return;
    }

    if (p_hids->evt_handler != NULL)
    {
        ble_hids_evt_t evt;

        evt.evt_type                      = BLE_HIDS_EVT_REPORT_READ;
        evt.params.char_auth_read.char_id = *p_char_id;
        evt.p_ble_evt                     = p_ble_evt;

        p_hids->evt_handler(p_hids, &evt);
    }
}


/**@brief Function for finding the Characteristic Id of a characteristic corresponding to a CCCD handle.
 *
 * @param[in]   p_hids      HID Service structure.
 * @param[in]   handle      Handle to search for.
 * @param[out]  p_char_id   Id of report characteristic.
 *
 * @return      TRUE if CCCD handle was found, FALSE otherwise.
 */
static bool inp_rep_cccd_identify(ble_hids_t         * p_hids,
                                  uint16_t             handle,
                                  ble_hids_char_id_t * p_char_id)
{
    uint8_t i;

    for (i = 0; i < p_hids->inp_rep_count; i++)
    {
        if (handle == p_hids->inp_rep_array[i].char_handles.cccd_handle)
        {
            *p_char_id = make_char_id(BLE_UUID_REPORT_CHAR, BLE_HIDS_REP_TYPE_INPUT, i);
            return true;
        }
    }

    return false;
}


/**@brief Function for finding the Characteristic Id of a characteristic corresponding to a value handle.
 *
 * @param[in]   p_hids           HID Service structure.
 * @param[in]   handle           Handle to search for.
 * @param[out]  p_char_id        Id of report characteristic.
 * @param[out]  p_context_offset Pointer to offset to the report data within the host data.
 * @param[out]  p_rep_max_len    Pointer to maximum report length.
 *
 * @return      TRUE if value handle was found, FALSE otherwise.
 */
static bool rep_value_identify(ble_hids_t         * p_hids,
                               uint16_t             handle,
                               ble_hids_char_id_t * p_char_id,
                               uint16_t           * p_context_offset,
                               uint16_t           * p_rep_max_len)
{
    uint8_t i;

    /* Initialize the offset with size of non-report-related data */
    *p_context_offset = sizeof(ble_hids_client_context_t) + BOOT_KB_INPUT_REPORT_MAX_SIZE +
                        BOOT_KB_OUTPUT_REPORT_MAX_SIZE + BOOT_MOUSE_INPUT_REPORT_MAX_SIZE;

    if (p_hids->p_inp_rep_init_array != NULL)
    {
        for (i = 0; i < p_hids->inp_rep_count; i++)
        {
            if (handle == p_hids->inp_rep_array[i].char_handles.value_handle)
            {
                *p_char_id     = make_char_id(BLE_UUID_REPORT_CHAR, BLE_HIDS_REP_TYPE_INPUT, i);
                *p_rep_max_len = p_hids->p_inp_rep_init_array[i].max_len;
                return true;
            }
            *p_context_offset += p_hids->p_inp_rep_init_array[i].max_len;
        }
    }
    if (p_hids->p_outp_rep_init_array != NULL)
    {
        for (i = 0; i < p_hids->outp_rep_count; i++)
        {
            if (handle == p_hids->outp_rep_array[i].char_handles.value_handle)
            {
                *p_char_id     = make_char_id(BLE_UUID_REPORT_CHAR, BLE_HIDS_REP_TYPE_OUTPUT, i);
                *p_rep_max_len = p_hids->p_outp_rep_init_array[i].max_len;
                return true;
            }
            *p_context_offset += p_hids->p_outp_rep_init_array[i].max_len;
        }
    }
    if (p_hids->p_feature_rep_init_array != NULL)
    {
        for (i = 0; i < p_hids->feature_rep_count; i++)
        {
            if (handle == p_hids->feature_rep_array[i].char_handles.value_handle)
            {
                *p_char_id     = make_char_id(BLE_UUID_REPORT_CHAR, BLE_HIDS_REP_TYPE_FEATURE, i);
                *p_rep_max_len = p_hids->p_feature_rep_init_array[i].max_len;
                return true;
            }
            *p_context_offset += p_hids->p_feature_rep_init_array[i].max_len;
        }
    }

    return false;
}


/**@brief Function for handling the Write event.
 *
 * @param[in]   p_hids      HID Service structure.
 * @param[in]   p_ble_evt   Event received from the BLE stack.
 */
static void on_write(ble_hids_t * p_hids, ble_evt_t const * p_ble_evt)
{
    ble_hids_char_id_t            char_id;
    ble_gatts_evt_write_t const * p_evt_write     = &p_ble_evt->evt.gatts_evt.params.write;
    uint16_t                      rep_data_offset = sizeof(ble_hids_client_context_t);
    uint16_t                      max_rep_len     = 0;

    if (p_evt_write->handle == p_hids->hid_control_point_handles.value_handle)
    {
        on_control_point_write(p_hids, p_ble_evt);
    }
    else if (p_evt_write->handle == p_hids->protocol_mode_handles.value_handle)
    {
        on_protocol_mode_write(p_hids, p_ble_evt);
    }
    else if (p_evt_write->handle == p_hids->boot_kb_inp_rep_handles.cccd_handle)
    {
        char_id = make_char_id(BLE_UUID_BOOT_KEYBOARD_INPUT_REPORT_CHAR, 0, 0);
        on_report_cccd_write(p_hids, &char_id, p_ble_evt);
    }
    else if (p_evt_write->handle == p_hids->boot_kb_inp_rep_handles.value_handle)
    {
        char_id     = make_char_id(BLE_UUID_BOOT_KEYBOARD_INPUT_REPORT_CHAR, 0, 0);
        max_rep_len = BOOT_KB_INPUT_REPORT_MAX_SIZE;
        on_report_value_write(p_hids, p_ble_evt, &char_id, rep_data_offset, max_rep_len);
    }
    else if (p_evt_write->handle == p_hids->boot_kb_outp_rep_handles.value_handle)
    {
        char_id          = make_char_id(BLE_UUID_BOOT_KEYBOARD_OUTPUT_REPORT_CHAR, 0, 0);
        rep_data_offset += BOOT_KB_INPUT_REPORT_MAX_SIZE;
        max_rep_len      = BOOT_KB_OUTPUT_REPORT_MAX_SIZE;
        on_report_value_write(p_hids, p_ble_evt, &char_id, rep_data_offset, max_rep_len);
    }
    else if (p_evt_write->handle == p_hids->boot_mouse_inp_rep_handles.cccd_handle)
    {
        char_id = make_char_id(BLE_UUID_BOOT_MOUSE_INPUT_REPORT_CHAR, 0, 0);
        on_report_cccd_write(p_hids, &char_id, p_ble_evt);
    }
    else if (p_evt_write->handle == p_hids->boot_mouse_inp_rep_handles.value_handle)
    {
        char_id          = make_char_id(BLE_UUID_BOOT_MOUSE_INPUT_REPORT_CHAR, 0, 0);
        rep_data_offset += BOOT_KB_INPUT_REPORT_MAX_SIZE + BOOT_KB_OUTPUT_REPORT_MAX_SIZE;
        max_rep_len      = BOOT_MOUSE_INPUT_REPORT_MAX_SIZE;
        on_report_value_write(p_hids, p_ble_evt, &char_id, rep_data_offset, max_rep_len);
    }
    else if (inp_rep_cccd_identify(p_hids, p_evt_write->handle, &char_id))
    {
        on_report_cccd_write(p_hids, &char_id, p_ble_evt);
    }
    else if (rep_value_identify(p_hids,
                                p_evt_write->handle,
                                &char_id,
                                &rep_data_offset,
                                &max_rep_len))
    {
        on_report_value_write(p_hids, p_ble_evt, &char_id, rep_data_offset, max_rep_len);
    }
    else
    {
        // No implementation needed.
    }
}


/**@brief Read/write authorize request event handler.
 *
 * @param[in]   p_hids      HID Service structure.
 * @param[in]   p_ble_evt   Event received from the BLE stack.
 */
static void on_rw_authorize_request(ble_hids_t * p_hids, ble_evt_t const * p_ble_evt)
{
    ble_gatts_evt_rw_authorize_request_t const * evt_rw_auth =
        &p_ble_evt->evt.gatts_evt.params.authorize_request;

    ble_hids_char_id_t char_id;
    uint16_t           rep_data_offset = sizeof(ble_hids_client_context_t);
    uint16_t           max_rep_len     = 0;

    if (evt_rw_auth->type != BLE_GATTS_AUTHORIZE_TYPE_READ)
    {
        // Unexpected operation
        return;
    }

    /* Update SD GATTS values of appropriate host before SD sends the Read Response */
    if (evt_rw_auth->request.read.handle == p_hids->protocol_mode_handles.value_handle)
    {
        on_protocol_mode_read_auth(p_hids, p_ble_evt);
    }
    else if (evt_rw_auth->request.read.handle == p_hids->boot_kb_inp_rep_handles.value_handle)
    {
        char_id     = make_char_id(BLE_UUID_BOOT_KEYBOARD_INPUT_REPORT_CHAR, 0, 0);
        max_rep_len = BOOT_KB_INPUT_REPORT_MAX_SIZE;
        on_report_value_read_auth(p_hids, &char_id, p_ble_evt, rep_data_offset, max_rep_len);
    }
    else if (evt_rw_auth->request.read.handle == p_hids->boot_kb_outp_rep_handles.value_handle)
    {
        char_id          = make_char_id(BLE_UUID_BOOT_KEYBOARD_OUTPUT_REPORT_CHAR, 0, 0);
        rep_data_offset += BOOT_KB_INPUT_REPORT_MAX_SIZE;
        max_rep_len      = BOOT_KB_OUTPUT_REPORT_MAX_SIZE;
        on_report_value_read_auth(p_hids, &char_id, p_ble_evt, rep_data_offset, max_rep_len);
    }
    else if (evt_rw_auth->request.read.handle == p_hids->boot_mouse_inp_rep_handles.value_handle)
    {
        char_id          = make_char_id(BLE_UUID_BOOT_MOUSE_INPUT_REPORT_CHAR, 0, 0);
        rep_data_offset += BOOT_KB_INPUT_REPORT_MAX_SIZE + BOOT_KB_OUTPUT_REPORT_MAX_SIZE;
        max_rep_len      = BOOT_MOUSE_INPUT_REPORT_MAX_SIZE;
        on_report_value_read_auth(p_hids, &char_id, p_ble_evt, rep_data_offset, max_rep_len);
    }
    else if (rep_value_identify(p_hids,
                                evt_rw_auth->request.read.handle,
                                &char_id,
                                &rep_data_offset,
                                &max_rep_len))
    {
        on_report_value_read_auth(p_hids, &char_id, p_ble_evt, rep_data_offset, max_rep_len);
    }
    else
    {
        // Do nothing.
    }
}


void ble_hids_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
{
    ble_hids_t * p_hids = (ble_hids_t *) p_context;

    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GAP_EVT_CONNECTED:
            on_connect(p_hids, p_ble_evt);
            break;

        case BLE_GATTS_EVT_WRITE:
            on_write(p_hids, p_ble_evt);
            break;

        case BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST:
            on_rw_authorize_request(p_hids, p_ble_evt);
            break;

        default:
            // No implementation needed.
            break;
    }
}


/**@brief Function for adding Protocol Mode characteristics.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   read_access   Security requirement for reading characteristic value.
 * @param[in]   write_access  Security requirement for writing characteristic value.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t protocol_mode_char_add(ble_hids_t   * p_hids,
                                       security_req_t read_access,
                                       security_req_t write_access)
{
    ble_add_char_params_t add_char_params;
    uint8_t               initial_protocol_mode;

    initial_protocol_mode = DEFAULT_PROTOCOL_MODE;

    memset(&add_char_params, 0, sizeof(add_char_params));
    add_char_params.uuid                     = BLE_UUID_PROTOCOL_MODE_CHAR;
    add_char_params.max_len                  = sizeof(uint8_t);
    add_char_params.init_len                 = sizeof(uint8_t);
    add_char_params.p_init_value             = &initial_protocol_mode;
    add_char_params.char_props.read          = 1;
    add_char_params.char_props.write_wo_resp = 1;
    add_char_params.read_access              = read_access;
    add_char_params.write_access             = write_access;
    add_char_params.is_defered_read          = true;

    return characteristic_add(p_hids->service_handle,
                              &add_char_params,
                              &p_hids->protocol_mode_handles);
}


/**@brief Function for adding report characteristics.
 *
 * @param[in]   p_hids              HID Service structure.
 * @param[in]   p_properties        Report characteristic properties.
 * @param[in]   max_len             Maximum length of report value.
 * @param[in]   p_rep_ref           Report Reference descriptor.
 * @param[in]   p_char_sec          Characteristic security settings.
 * @param[out]  p_rep_char          Handles of new characteristic.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t rep_char_add(ble_hids_t                      * p_hids,
                             ble_gatt_char_props_t           * p_properties,
                             uint16_t                          max_len,
                             ble_srv_report_ref_t const      * p_rep_ref,
                             ble_hids_char_sec_t const * const p_char_sec,
                             ble_hids_rep_char_t             * p_rep_char)
{
    uint32_t               err_code;
    ble_add_char_params_t  add_char_params;
    ble_add_descr_params_t add_descr_params;
    uint8_t                encoded_rep_ref[BLE_SRV_ENCODED_REPORT_REF_LEN];

    memset(&add_char_params, 0, sizeof(add_char_params));
    add_char_params.uuid              = BLE_UUID_REPORT_CHAR;
    add_char_params.max_len           = max_len;
    add_char_params.char_props        = *p_properties;
    add_char_params.read_access       = p_char_sec->rd;
    add_char_params.write_access      = p_char_sec->wr;
    add_char_params.cccd_write_access = p_char_sec->cccd_wr;
    add_char_params.is_var_len        = true;
    add_char_params.is_defered_read   = true;

    err_code = characteristic_add(p_hids->service_handle,
                                  &add_char_params,
                                  &p_rep_char->char_handles);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    // Add Report Reference descriptor
    memset(&add_descr_params, 0, sizeof(add_descr_params));
    add_descr_params.uuid         = BLE_UUID_REPORT_REF_DESCR;
    add_descr_params.read_access  = p_char_sec->rd;
    add_descr_params.write_access = p_char_sec->wr;
    add_descr_params.init_len     = ble_srv_report_ref_encode(encoded_rep_ref, p_rep_ref);
    add_descr_params.max_len      = add_descr_params.init_len;
    add_descr_params.p_value      = encoded_rep_ref;

    return descriptor_add(p_rep_char->char_handles.value_handle,
                          &add_descr_params,
                          &p_rep_char->ref_handle);
}


/**@brief Function for adding Report Map characteristics.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_hids_init   Service initialization structure.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t rep_map_char_add(ble_hids_t * p_hids, const ble_hids_init_t * p_hids_init)
{
    uint32_t               err_code;
    ble_add_char_params_t  add_char_params;
    ble_add_descr_params_t add_descr_params;

    // Add Report Map characteristic
    memset(&add_char_params, 0, sizeof(add_char_params));
    add_char_params.uuid            = BLE_UUID_REPORT_MAP_CHAR;
    add_char_params.max_len         = p_hids_init->rep_map.data_len;
    add_char_params.init_len        = p_hids_init->rep_map.data_len;
    add_char_params.p_init_value    = p_hids_init->rep_map.p_data;
    add_char_params.char_props.read = 1;
    add_char_params.read_access     = p_hids_init->rep_map.rd_sec;
    add_char_params.is_var_len      = true;

    err_code = characteristic_add(p_hids->service_handle,
                                  &add_char_params,
                                  &p_hids->rep_map_handles);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    if (p_hids_init->rep_map.ext_rep_ref_num != 0 && p_hids_init->rep_map.p_ext_rep_ref == NULL)
    {
        return NRF_ERROR_INVALID_PARAM;
    }

    for (int i = 0; i < p_hids_init->rep_map.ext_rep_ref_num; ++i)
    {
        uint8_t encoded_rep_ref[sizeof(ble_uuid128_t)];
        uint8_t encoded_rep_ref_len;

        // Add External Report Reference descriptor
        err_code = sd_ble_uuid_encode(&p_hids_init->rep_map.p_ext_rep_ref[i],
                                      &encoded_rep_ref_len,
                                      encoded_rep_ref);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }

        memset(&add_descr_params, 0, sizeof(add_descr_params));
        add_descr_params.uuid        = BLE_UUID_EXTERNAL_REPORT_REF_DESCR;
        add_descr_params.read_access = p_hids_init->rep_map.rd_sec;
        add_descr_params.init_len    = encoded_rep_ref_len;
        add_descr_params.max_len     = add_descr_params.init_len;
        add_descr_params.p_value     = encoded_rep_ref;

        err_code = descriptor_add(p_hids->rep_map_handles.value_handle,
                                  &add_descr_params,
                                  &p_hids->rep_map_ext_rep_ref_handle);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    }

    return NRF_SUCCESS;
}


/**@brief Function for adding Input Report characteristics.
 *
 * @param[in]   p_hids           HID Service structure.
 * @param[in]   uuid             UUID of report characteristic to be added.
 * @param[in]   max_data_len     Maximum length of report value.
 * @param[in]   p_char_sec       Characteristic security settings.
 * @param[out]  p_char_handles   Handles of new characteristic.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t boot_inp_rep_char_add(ble_hids_t                      * p_hids,
                                      uint16_t                          uuid,
                                      uint16_t                          max_data_len,
                                      ble_hids_char_sec_t const * const p_char_sec,
                                      ble_gatts_char_handles_t        * p_char_handles)
{
    ble_add_char_params_t add_char_params;

    memset(&add_char_params, 0, sizeof(add_char_params));
    add_char_params.uuid              = uuid;
    add_char_params.max_len           = max_data_len;
    add_char_params.char_props.read   = 1;
    add_char_params.char_props.write  = (p_char_sec->wr != SEC_NO_ACCESS) ? 1 : 0;
    add_char_params.char_props.notify = 1;
    add_char_params.read_access       = p_char_sec->rd;
    add_char_params.write_access      = p_char_sec->wr;
    add_char_params.cccd_write_access = p_char_sec->cccd_wr;
    add_char_params.is_defered_read   = true;

    return characteristic_add(p_hids->service_handle,
                              &add_char_params,
                              p_char_handles);
}


/**@brief Function for adding Boot Keyboard Output Report characteristics.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_hids_init   Service initialization structure.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t boot_kb_outp_rep_char_add(ble_hids_t * p_hids, const ble_hids_init_t * p_hids_init)
{
    ble_add_char_params_t add_char_params;

    memset(&add_char_params, 0, sizeof(add_char_params));
    add_char_params.uuid                     = BLE_UUID_BOOT_KEYBOARD_OUTPUT_REPORT_CHAR;
    add_char_params.max_len                  = BOOT_KB_OUTPUT_REPORT_MAX_SIZE;
    add_char_params.char_props.read          = 1;
    add_char_params.char_props.write         = 1;
    add_char_params.char_props.write_wo_resp = 1;
    add_char_params.read_access              = p_hids_init->boot_kb_outp_rep_sec.rd;
    add_char_params.write_access             = p_hids_init->boot_kb_outp_rep_sec.wr;
    add_char_params.is_defered_read          = true;

    return characteristic_add(p_hids->service_handle,
                              &add_char_params,
                              &p_hids->boot_kb_outp_rep_handles);
}


/**@brief Function for encoding a HID Information characteristic value.
 *
 * @param[out]  p_encoded_buffer    Buffer where the encoded data will be written.
 * @param[in]   p_hid_information   Measurement to be encoded.
 *
 * @return      Size of encoded data.
 */
static uint8_t encode_hid_information(uint8_t                          * p_encoded_buffer,
                                      const ble_hids_hid_information_t * p_hid_information)
{
    uint8_t len = uint16_encode(p_hid_information->bcd_hid, p_encoded_buffer);

    p_encoded_buffer[len++] = p_hid_information->b_country_code;
    p_encoded_buffer[len++] = p_hid_information->flags;

    APP_ERROR_CHECK_BOOL(len == ENCODED_HID_INFORMATION_LEN);

    return len;
}


/**@brief Function for adding HID Information characteristics.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_hids_init   Service initialization structure.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t hid_information_char_add(ble_hids_t * p_hids, const ble_hids_init_t * p_hids_init)
{
    uint8_t               encoded_hid_information[ENCODED_HID_INFORMATION_LEN];
    uint8_t               hid_info_len;
    ble_add_char_params_t add_char_params;

    hid_info_len = encode_hid_information(encoded_hid_information, &p_hids_init->hid_information);

    memset(&add_char_params, 0, sizeof(add_char_params));
    add_char_params.uuid            = BLE_UUID_HID_INFORMATION_CHAR;
    add_char_params.max_len         = hid_info_len;
    add_char_params.char_props.read = 1;
    add_char_params.read_access     = p_hids_init->hid_information.rd_sec;
    add_char_params.init_len        = hid_info_len;
    add_char_params.p_init_value    = encoded_hid_information;
    
    return characteristic_add(p_hids->service_handle,
                              &add_char_params,
                              &p_hids->hid_information_handles);
}


/**@brief Function for adding HID Control Point characteristics.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   write_access  Security requirement for writing characteristic value.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t hid_control_point_char_add(ble_hids_t * p_hids, security_req_t write_access)
{
    uint8_t               initial_hid_control_point;
    ble_add_char_params_t add_char_params;

    initial_hid_control_point = INITIAL_VALUE_HID_CONTROL_POINT;

    memset(&add_char_params, 0, sizeof(add_char_params));
    add_char_params.uuid                     = BLE_UUID_HID_CONTROL_POINT_CHAR;
    add_char_params.max_len                  = sizeof(uint8_t);
    add_char_params.char_props.write_wo_resp = 1;
    add_char_params.write_access             = write_access;
    add_char_params.init_len                 = sizeof(uint8_t);
    add_char_params.p_init_value             = &initial_hid_control_point;

    return characteristic_add(p_hids->service_handle, 
                              &add_char_params, 
                              &p_hids->hid_control_point_handles);
}


/**@brief Function for adding input report characteristics.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_hids_init   Service initialization structure.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t inp_rep_characteristics_add(ble_hids_t            * p_hids,
                                            const ble_hids_init_t * p_hids_init)
{
    if ((p_hids_init->inp_rep_count != 0) && (p_hids_init->p_inp_rep_array != NULL))
    {
        uint8_t i;

        for (i = 0; i < p_hids_init->inp_rep_count; i++)
        {
            uint32_t                        err_code;
            ble_hids_inp_rep_init_t const * p_rep_init = &p_hids_init->p_inp_rep_array[i];
            ble_gatt_char_props_t           properties;

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

            properties.read   = true;
            properties.write  = (p_rep_init->sec.wr != SEC_NO_ACCESS) ? 1 : 0;
            properties.notify = true;

            err_code = rep_char_add(p_hids,
                                    &properties,
                                    p_rep_init->max_len,
                                    &p_rep_init->rep_ref,
                                    &p_rep_init->sec,
                                    &p_hids->inp_rep_array[i]);
            if (err_code != NRF_SUCCESS)
            {
                return err_code;
            }
        }
    }

    return NRF_SUCCESS;
}


/**@brief Function for adding output report characteristics.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_hids_init   Service initialization structure.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t outp_rep_characteristics_add(ble_hids_t            * p_hids,
                                             const ble_hids_init_t * p_hids_init)
{
    if ((p_hids_init->outp_rep_count != 0) && (p_hids_init->p_outp_rep_array != NULL))
    {
        uint8_t i;

        for (i = 0; i < p_hids_init->outp_rep_count; i++)
        {
            uint32_t                         err_code;
            ble_hids_outp_rep_init_t const * p_rep_init = &p_hids_init->p_outp_rep_array[i];
            ble_gatt_char_props_t            properties;

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

            properties.read          = true;
            properties.write         = true;
            properties.write_wo_resp = true;

            err_code = rep_char_add(p_hids,
                                    &properties,
                                    p_rep_init->max_len,
                                    &p_rep_init->rep_ref,
                                    &p_rep_init->sec,
                                    &p_hids->outp_rep_array[i]);
            if (err_code != NRF_SUCCESS)
            {
                return err_code;
            }
        }
    }

    return NRF_SUCCESS;
}


/**@brief Function for adding feature report characteristics.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_hids_init   Service initialization structure.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t feature_rep_characteristics_add(ble_hids_t            * p_hids,
                                                const ble_hids_init_t * p_hids_init)
{
    if ((p_hids_init->feature_rep_count != 0) && (p_hids_init->p_feature_rep_array != NULL))
    {
        uint8_t i;

        for (i = 0; i < p_hids_init->feature_rep_count; i++)
        {
            uint32_t                            err_code;
            ble_hids_feature_rep_init_t const * p_rep_init = &p_hids_init->p_feature_rep_array[i];
            ble_gatt_char_props_t               properties;

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

            properties.read  = true;
            properties.write = true;

            err_code = rep_char_add(p_hids,
                                    &properties,
                                    p_rep_init->max_len,
                                    &p_rep_init->rep_ref,
                                    &p_rep_init->sec,
                                    &p_hids->feature_rep_array[i]);
            if (err_code != NRF_SUCCESS)
            {
                return err_code;
            }
        }
    }

    return NRF_SUCCESS;
}


/**@brief Function for adding included services.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_hids_init   Service initialization structure.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t includes_add(ble_hids_t * p_hids, const ble_hids_init_t * p_hids_init)
{
    uint32_t err_code;
    uint8_t  i;
    uint16_t unused_include_handle;

    for (i = 0; i < p_hids_init->included_services_count; i++)
    {
        err_code = sd_ble_gatts_include_add(p_hids->service_handle,
                                            p_hids_init->p_included_services_array[i],
                                            &unused_include_handle);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    }

    return NRF_SUCCESS;
}


uint32_t ble_hids_init(ble_hids_t * p_hids, const ble_hids_init_t * p_hids_init)
{
    VERIFY_PARAM_NOT_NULL(p_hids);
    VERIFY_PARAM_NOT_NULL(p_hids_init);

    uint32_t   err_code;
    ble_uuid_t ble_uuid;

    if ((p_hids_init->inp_rep_count > BLE_HIDS_MAX_INPUT_REP) ||
        (p_hids_init->outp_rep_count > BLE_HIDS_MAX_OUTPUT_REP) ||
        (p_hids_init->feature_rep_count > BLE_HIDS_MAX_FEATURE_REP)
       )
    {
        return NRF_ERROR_INVALID_PARAM;
    }

    // Initialize service structure.
    p_hids->evt_handler       = p_hids_init->evt_handler;
    p_hids->error_handler     = p_hids_init->error_handler;
    p_hids->inp_rep_count     = p_hids_init->inp_rep_count;
    p_hids->outp_rep_count    = p_hids_init->outp_rep_count;
    p_hids->feature_rep_count = p_hids_init->feature_rep_count;

    // Add service.
    BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_HUMAN_INTERFACE_DEVICE_SERVICE);

    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
                                        &ble_uuid,
                                        &p_hids->service_handle);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    // Add includes.
    err_code = includes_add(p_hids, p_hids_init);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    if (p_hids_init->is_kb || p_hids_init->is_mouse)
    {
        // Add Protocol Mode characteristic.
        err_code = protocol_mode_char_add(p_hids,
                                          p_hids_init->protocol_mode_rd_sec,
                                          p_hids_init->protocol_mode_wr_sec);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    }

    // Add Input Report characteristics (if any).
    err_code = inp_rep_characteristics_add(p_hids, p_hids_init);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    // Add Output Report characteristics (if any).
    err_code = outp_rep_characteristics_add(p_hids, p_hids_init);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    // Add Feature Report characteristic (if any).
    err_code = feature_rep_characteristics_add(p_hids, p_hids_init);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    // Add Report Map characteristic.
    err_code = rep_map_char_add(p_hids, p_hids_init);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    if (p_hids_init->is_kb)
    {
        // Add Boot Keyboard Input Report characteristic.
        err_code = boot_inp_rep_char_add(p_hids,
                                         BLE_UUID_BOOT_KEYBOARD_INPUT_REPORT_CHAR,
                                         BOOT_KB_INPUT_REPORT_MAX_SIZE,
                                         &p_hids_init->boot_kb_inp_rep_sec,
                                         &p_hids->boot_kb_inp_rep_handles);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }

        // Add Boot Keyboard Output Report characteristic.
        err_code = boot_kb_outp_rep_char_add(p_hids, p_hids_init);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    }

    if (p_hids_init->is_mouse)
    {
        // Add Boot Mouse Input Report characteristic.
        err_code = boot_inp_rep_char_add(p_hids,
                                         BLE_UUID_BOOT_MOUSE_INPUT_REPORT_CHAR,
                                         BOOT_MOUSE_INPUT_REPORT_MAX_SIZE,
                                         &p_hids_init->boot_mouse_inp_rep_sec,
                                         &p_hids->boot_mouse_inp_rep_handles);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    }

    // Add HID Information characteristic.
    err_code = hid_information_char_add(p_hids, p_hids_init);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    // Add HID Control Point characteristic.
    err_code = hid_control_point_char_add(p_hids, p_hids_init->ctrl_point_wr_sec);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    // Set pointers to user-defined report arrays
    p_hids->p_inp_rep_init_array     = p_hids_init->p_inp_rep_array;
    p_hids->p_outp_rep_init_array    = p_hids_init->p_outp_rep_array;
    p_hids->p_feature_rep_init_array = p_hids_init->p_feature_rep_array;

    return err_code;
}


uint32_t ble_hids_inp_rep_send(ble_hids_t * p_hids,
                               uint8_t      rep_index,
                               uint16_t     len,
                               uint8_t    * p_data,
                               uint16_t     conn_handle)
{
    VERIFY_PARAM_NOT_NULL(p_hids);
    VERIFY_PARAM_NOT_NULL(p_data);

    uint32_t err_code;

    if (rep_index < p_hids->inp_rep_count)
    {
        ble_hids_rep_char_t * p_rep_char = &p_hids->inp_rep_array[rep_index];

        if (conn_handle != BLE_CONN_HANDLE_INVALID)
        {
            ble_gatts_hvx_params_t hvx_params;
            uint8_t                index   = 0;
            uint16_t               hvx_len = len;
            uint8_t              * p_host_rep_data;

            err_code = blcm_link_ctx_get(p_hids->p_link_ctx_storage,
                                         conn_handle,
                                         (void *) &p_host_rep_data);
            VERIFY_SUCCESS(err_code);

            p_host_rep_data += sizeof(ble_hids_client_context_t) + BOOT_KB_INPUT_REPORT_MAX_SIZE +
                               BOOT_KB_OUTPUT_REPORT_MAX_SIZE + BOOT_MOUSE_INPUT_REPORT_MAX_SIZE;

            // Store the new report data in host's context
            while (index < rep_index)
            {
                p_host_rep_data += p_hids->p_inp_rep_init_array[index].max_len;
                ++index;
            }

            if (len <= p_hids->p_inp_rep_init_array[rep_index].max_len)
            {
                memcpy(p_host_rep_data, p_data, len);
            }
            else
            {
                return NRF_ERROR_DATA_SIZE;
            }

            // Notify host
            memset(&hvx_params, 0, sizeof(hvx_params));

            hvx_params.handle = p_rep_char->char_handles.value_handle;
            hvx_params.type   = BLE_GATT_HVX_NOTIFICATION;
            hvx_params.offset = 0;
            hvx_params.p_len  = &hvx_len;
            hvx_params.p_data = p_data;

            err_code = sd_ble_gatts_hvx(conn_handle, &hvx_params);
            if ((err_code == NRF_SUCCESS) && (*hvx_params.p_len != len))
            {
                err_code = NRF_ERROR_DATA_SIZE;
            }
        }
        else
        {
            err_code = NRF_ERROR_INVALID_STATE;
        }
    }
    else
    {
        err_code = NRF_ERROR_INVALID_PARAM;
    }

    return err_code;
}


uint32_t ble_hids_boot_kb_inp_rep_send(ble_hids_t * p_hids,
                                       uint16_t     len,
                                       uint8_t    * p_data,
                                       uint16_t     conn_handle)
{
    VERIFY_PARAM_NOT_NULL(p_hids);
    VERIFY_PARAM_NOT_NULL(p_data);

    uint32_t err_code;

    if (conn_handle != BLE_CONN_HANDLE_INVALID)
    {
        ble_gatts_hvx_params_t hvx_params;
        uint16_t               hvx_len = len;
        uint8_t              * p_host_rep_data;

        err_code = blcm_link_ctx_get(p_hids->p_link_ctx_storage,
                                     conn_handle,
                                     (void *) &p_host_rep_data);
        VERIFY_SUCCESS(err_code);

        p_host_rep_data += sizeof(ble_hids_client_context_t);

        // Store the new value in the host's context
        if (len <= BOOT_KB_INPUT_REPORT_MAX_SIZE)
        {
            memcpy(p_host_rep_data, p_data, len);
        }

        // Notify host
        memset(&hvx_params, 0, sizeof(hvx_params));

        hvx_params.handle = p_hids->boot_kb_inp_rep_handles.value_handle;
        hvx_params.type   = BLE_GATT_HVX_NOTIFICATION;
        hvx_params.offset = 0;
        hvx_params.p_len  = &hvx_len;
        hvx_params.p_data = p_data;

        err_code = sd_ble_gatts_hvx(conn_handle, &hvx_params);
        if ((err_code == NRF_SUCCESS) && (*hvx_params.p_len != len))
        {
            err_code = NRF_ERROR_DATA_SIZE;
        }
    }
    else
    {
        err_code = NRF_ERROR_INVALID_STATE;
    }

    return err_code;
}


uint32_t ble_hids_boot_mouse_inp_rep_send(ble_hids_t * p_hids,
                                          uint8_t      buttons,
                                          int8_t       x_delta,
                                          int8_t       y_delta,
                                          uint16_t     optional_data_len,
                                          uint8_t    * p_optional_data,
                                          uint16_t     conn_handle)
{
    VERIFY_PARAM_NOT_NULL(p_hids);

    uint32_t err_code;

    if (conn_handle != BLE_CONN_HANDLE_INVALID)
    {
        uint16_t hvx_len = BOOT_MOUSE_INPUT_REPORT_MIN_SIZE + optional_data_len;

        if (hvx_len <= BOOT_MOUSE_INPUT_REPORT_MAX_SIZE)
        {
            uint8_t                buffer[BOOT_MOUSE_INPUT_REPORT_MAX_SIZE];
            ble_gatts_hvx_params_t hvx_params;
            uint8_t              * p_host_rep_data;

            err_code = blcm_link_ctx_get(p_hids->p_link_ctx_storage,
                                         conn_handle,
                                         (void *) &p_host_rep_data);
            VERIFY_SUCCESS(err_code);

            p_host_rep_data += sizeof(ble_hids_client_context_t) + BOOT_KB_INPUT_REPORT_MAX_SIZE +
                               BOOT_KB_OUTPUT_REPORT_MAX_SIZE;

            APP_ERROR_CHECK_BOOL(BOOT_MOUSE_INPUT_REPORT_MIN_SIZE == 3);

            // Build buffer
            buffer[0] = buttons;
            buffer[1] = (uint8_t)x_delta;
            buffer[2] = (uint8_t)y_delta;

            if (optional_data_len > 0)
            {
                memcpy(&buffer[3], p_optional_data, optional_data_len);
            }

            // Store the new value in the host's context
            memcpy(p_host_rep_data, buffer, hvx_len);

            // Pass buffer to stack
            memset(&hvx_params, 0, sizeof(hvx_params));

            hvx_params.handle = p_hids->boot_mouse_inp_rep_handles.value_handle;
            hvx_params.type   = BLE_GATT_HVX_NOTIFICATION;
            hvx_params.offset = 0;
            hvx_params.p_len  = &hvx_len;
            hvx_params.p_data = buffer;

            err_code = sd_ble_gatts_hvx(conn_handle, &hvx_params);
            if ((err_code == NRF_SUCCESS) &&
                (*hvx_params.p_len != BOOT_MOUSE_INPUT_REPORT_MIN_SIZE + optional_data_len)
               )
            {
                err_code = NRF_ERROR_DATA_SIZE;
            }
        }
        else
        {
            err_code = NRF_ERROR_DATA_SIZE;
        }
    }
    else
    {
        err_code = NRF_ERROR_INVALID_STATE;
    }

    return err_code;
}


uint32_t ble_hids_outp_rep_get(ble_hids_t * p_hids,
                               uint8_t      rep_index,
                               uint16_t     len,
                               uint8_t      offset,
                               uint16_t     conn_handle,
                               uint8_t    * p_outp_rep)
{
    VERIFY_PARAM_NOT_NULL(p_hids);
    VERIFY_PARAM_NOT_NULL(p_outp_rep);

    ret_code_t err_code;
    uint8_t  * p_rep_data;
    uint8_t    index;

    if (rep_index >= p_hids->outp_rep_count)
    {
        return NRF_ERROR_INVALID_PARAM;
    }

    err_code = blcm_link_ctx_get(p_hids->p_link_ctx_storage,
                                 conn_handle,
                                 (void *) &p_rep_data);
    VERIFY_SUCCESS(err_code);

    p_rep_data += sizeof(ble_hids_client_context_t) + BOOT_KB_INPUT_REPORT_MAX_SIZE +
                  BOOT_KB_OUTPUT_REPORT_MAX_SIZE + BOOT_MOUSE_INPUT_REPORT_MAX_SIZE;

    for (index = 0; index < p_hids->inp_rep_count; index++)
    {
        p_rep_data += p_hids->p_inp_rep_init_array[index].max_len;
    }

    for (index = 0; index < rep_index; index++)
    {
        p_rep_data += p_hids->p_outp_rep_init_array[index].max_len;
    }

    // Copy the requested output report data
    if (len + offset <= p_hids->p_outp_rep_init_array[rep_index].max_len)
    {
        memcpy(p_outp_rep, p_rep_data + offset, len);
    }
    else
    {
        return NRF_ERROR_INVALID_LENGTH;
    }

    return NRF_SUCCESS;
}


/**
   @}
 */
#endif // NRF_MODULE_ENABLED(BLE_HIDS)
