/**
 * Copyright (c) 2015 - 2020, 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.
 *
 */
#include "ble_ln_cp.h"
#include "ble_ln_db.h"
#include "ble_ln_common.h"
#include "sdk_common.h"

#define NRF_LOG_MODULE_NAME ble_ln_cp
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();

// Feature Mask bits
#define FEATURE_MASK_INSTANTANEOUS_SPEED                 (0x01 << 0)         /**< Instantaneous Speed mask bit. */
#define FEATURE_MASK_TOTAL_DISTANCE                      (0x01 << 1)         /**< Total Distance mask bit. */
#define FEATURE_MASK_LOCATION                            (0x01 << 2)         /**< Location mask bit. */
#define FEATURE_MASK_ELEVATION                           (0x01 << 3)         /**< Elevation mask bit. */
#define FEATURE_MASK_HEADING                             (0x01 << 4)         /**< Heading mask bit. */
#define FEATURE_MASK_ROLLING_TIME                        (0x01 << 5)         /**< Rolling Time mask bit. */
#define FEATURE_MASK_UTC_TIME                            (0x01 << 6)         /**< UTC Time mask bit. */

// Data Control point parameter type lengths.
#define INT8_LEN   1
#define INT16_LEN  2
#define INT24_LEN  3
#define INT32_LEN  4

#define OPCODE_LENGTH                                   1  /**< Length of opcode inside Location and Navigation Measurement packet. */
#define HANDLE_LENGTH                                   2  /**< Length of handle inside Location and Navigation Measurement packet. */


/**@brief Function for interception of GATT errors and @ref nrf_ble_gq errors.
 *
 * @param[in] nrf_error   Error code.
 * @param[in] p_ctx       Parameter from the event handler.
 * @param[in] conn_handle Connection handle.
 */
static void gatt_error_handler(uint32_t   nrf_error,
                               void     * p_ctx,
                               uint16_t   conn_handle)
{
    ble_lncp_t * p_lncp = (ble_lncp_t *)p_ctx;

    if (p_lncp->error_handler != NULL)
    {
        p_lncp->error_handler(nrf_error);
    }
}


static ble_lncp_rsp_code_t notify_app(ble_lncp_t const * p_lncp, ble_lncp_evt_t const * p_evt)
{
    ble_lncp_rsp_code_t rsp = LNCP_RSP_SUCCESS;

    if (p_lncp->evt_handler != NULL)
    {
        rsp = p_lncp->evt_handler(p_lncp, p_evt);
    }

    return rsp;
}


static void resp_send(ble_lncp_t * p_lncp)
{
    // Send indication
    uint16_t               hvx_len;
    uint8_t                hvx_data[MAX_CTRL_POINT_RESP_PARAM_LEN];
    uint32_t               err_code;
    nrf_ble_gq_req_t       lncp_req;

    memset(&lncp_req, 0, sizeof(nrf_ble_gq_req_t));

    hvx_len = 3 + p_lncp->pending_rsp.rsp_param_len;
    hvx_data[0] = LNCP_OP_RESPONSE_CODE;
    hvx_data[1] = p_lncp->pending_rsp.op_code;
    hvx_data[2] = p_lncp->pending_rsp.rsp_code;

    memcpy(&hvx_data[3], &p_lncp->pending_rsp.rsp_param[0], p_lncp->pending_rsp.rsp_param_len);

    lncp_req.type                               = NRF_BLE_GQ_REQ_GATTS_HVX;
    lncp_req.error_handler.cb                   = gatt_error_handler;
    lncp_req.error_handler.p_ctx                = p_lncp;
    lncp_req.params.gatts_hvx.handle  = p_lncp->ctrlpt_handles.value_handle;
    lncp_req.params.gatts_hvx.offset  = 0;
    lncp_req.params.gatts_hvx.p_data  = hvx_data;
    lncp_req.params.gatts_hvx.p_len   = &hvx_len;
    lncp_req.params.gatts_hvx.type    = BLE_GATT_HVX_INDICATION;

    p_lncp->is_indication_pending = true;

    err_code = nrf_ble_gq_item_add(p_lncp->p_gatt_queue, &lncp_req, p_lncp->conn_handle);

    if ((p_lncp->error_handler != NULL) &&
        (err_code != NRF_SUCCESS))
    {
        p_lncp->error_handler(err_code);
    }
}


static void on_connect(ble_lncp_t * p_lncp, ble_evt_t const * p_ble_evt)
{
    memset(&p_lncp->mask, 0, sizeof(ble_lncp_mask_t));
    p_lncp->conn_handle        = p_ble_evt->evt.gap_evt.conn_handle;
}


static void on_disconnect(ble_lncp_t * p_lncp, ble_evt_t const * p_ble_evt)
{
    UNUSED_PARAMETER(p_ble_evt);
    p_lncp->conn_handle        = BLE_CONN_HANDLE_INVALID;
}


static void on_hvc_confirm(ble_lncp_t * p_lncp, ble_evt_t const * p_ble_evt)
{
    if (p_ble_evt->evt.gatts_evt.params.hvc.handle == p_lncp->ctrlpt_handles.value_handle)
    {
        if (p_lncp->is_indication_pending)
        {
            p_lncp->is_indication_pending = false;
        }
        else
        {
            if (p_lncp->error_handler != NULL)
            {
                p_lncp->error_handler(NRF_ERROR_INVALID_STATE);
            }
        }
    }
}


/**@brief Handle write events to the control point cccd.
 *
 * @param[in]   p_lncp       Location and Navigation Service structure.
 * @param[in]   p_evt_write Write event received from the BLE stack.
 */
static void on_lncp_cccd_write(ble_lncp_t * p_lncp, ble_gatts_evt_write_t const * p_evt_write)
{
    if (p_evt_write->len == BLE_CCCD_VALUE_LEN)
    {
        // CCCD written, update indications state
        p_lncp->is_ctrlpt_indication_enabled = ble_srv_is_indication_enabled(p_evt_write->data);
    }
}


/**@brief Handle write events to the navigation cccd.
 *
 * @param[in]   p_lncp      Location and Navigation Service structure.
 * @param[in]   p_evt_write Write event received from the BLE stack.
 */
static void on_nav_cccd_write(ble_lncp_t * p_lncp, ble_gatts_evt_write_t const * p_evt_write)
{
    if (p_evt_write->len == BLE_CCCD_VALUE_LEN)
    {
        // CCCD written, update notification state
        p_lncp->is_nav_notification_enabled = ble_srv_is_notification_enabled(p_evt_write->data);
    }
}


/**@brief Event handler for control point write.
 *
 * @param[in]   p_lncp     Location and Navigation Service structure.
 * @param[in]   p_ble_evt Event received from the BLE stack.
 */
static void on_set_cumulative_value(ble_lncp_t * p_lncp, ble_gatts_evt_write_t const * p_evt_write)
{
    if ( !(p_lncp->available_features & BLE_LNS_FEATURE_TOTAL_DISTANCE_SUPPORTED) )
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_OP_CODE_NOT_SUPPORTED;
        return;
    }

    if (p_evt_write->len != OPCODE_LENGTH + INT24_LEN)
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_INVALID_PARAMETER;
        return;
    }

    const uint32_t total_distance = uint24_decode(&p_evt_write->data[1]);

    const ble_lncp_evt_t evt = {
        .evt_type = LNCP_EVT_TOTAL_DISTANCE_SET,
        .params.total_distance = total_distance
    };
    p_lncp->pending_rsp.rsp_code = notify_app(p_lncp, &evt);

    if (p_lncp->pending_rsp.rsp_code == LNCP_RSP_SUCCESS)
    {
        p_lncp->total_distance = total_distance;
    }

}


/**@brief Event handler for control point write.
 *
 * @param[in]   p_lncp     Location and Navigation Service structure.
 * @param[in]   p_ble_evt Event received from the BLE stack.
 */
static void on_mask_loc_speed_content(ble_lncp_t * p_lncp, ble_gatts_evt_write_t const * p_evt_write)
{
    if ( !(p_lncp->available_features & BLE_LNS_FEATURE_LOC_AND_SPEED_CONTENT_MASKING_SUPPORTED) )
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_OP_CODE_NOT_SUPPORTED;
        return;
    }

    if (p_evt_write->len != OPCODE_LENGTH + INT16_LEN)
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_INVALID_PARAMETER;
        return;
    }

    uint16_t rcvd_mask = uint16_decode(&p_evt_write->data[1]);

    if (rcvd_mask > 0x7F)
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_INVALID_PARAMETER;
        return;
    }

    const ble_lncp_evt_t evt = {
        .evt_type = LNCP_EVT_MASK_SET,
        .params.mask.flags = rcvd_mask
    };
    p_lncp->pending_rsp.rsp_code = notify_app(p_lncp, &evt);

    if (p_lncp->pending_rsp.rsp_code == LNCP_RSP_SUCCESS)
    {
        p_lncp->mask.flags = rcvd_mask;
    }
}


/**@brief Event handler for control point write.
 *
 * @param[in]   p_lncp     Location and Navigation Service structure.
 * @param[in]   p_ble_evt Event received from the BLE stack.
 */
static void on_nav_control(ble_lncp_t * p_lncp, ble_gatts_evt_write_t const * p_evt_write)
{
    if ( !(p_lncp->is_navigation_present) )
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_OP_CODE_NOT_SUPPORTED;
        return;
    }

    if (p_evt_write->len != LNCP_NAV_CMD_LEN)
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_INVALID_PARAMETER;
        return;
    }

    /*lint --e{415} --e{416} -save suppress Warning 415: possible access out of bond */
    const uint8_t data_buf = p_evt_write->data[1];
    /*lint -restore*/

    if (data_buf > LNCP_NAV_CMD_MAX)
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_INVALID_PARAMETER;
        return;
    }

    const ble_lncp_nav_cmd_t cmd = (ble_lncp_nav_cmd_t) data_buf;

    if (cmd == LNCP_CMD_NAV_START || cmd == LNCP_CMD_NAV_CONTINUE || cmd == LNCP_CMD_NAV_NEAREST)
    {
        p_lncp->is_navigation_running = true;
    }
    else if (cmd == LNCP_CMD_NAV_STOP || cmd == LNCP_CMD_NAV_PAUSE)
    {
        p_lncp->is_navigation_running = false;
    }

    const ble_lncp_evt_t evt = {
        .evt_type = LNCP_EVT_NAV_COMMAND,
        .params.nav_cmd = cmd
    };
    p_lncp->pending_rsp.rsp_code = notify_app(p_lncp, &evt);
}


/**@brief Event handler for control point write.
 *
 * @param[in]   p_lncp     Location and Navigation Service structure.
 * @param[in]   p_ble_evt Event received from the BLE stack.
 */
static void on_req_num_routes(ble_lncp_t * p_lncp, ble_gatts_evt_write_t const * p_evt_write)
{
    p_lncp->pending_rsp.rsp_code = LNCP_RSP_SUCCESS;

    if ( !(p_lncp->is_navigation_present) )
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_OP_CODE_NOT_SUPPORTED;
        return;
    }

    if (p_evt_write->len != OPCODE_LENGTH)
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_INVALID_PARAMETER;
        return;
    }

    const uint8_t num_records = ble_ln_db_num_records_get();
    p_lncp->pending_rsp.rsp_param_len = uint16_encode(num_records, &p_lncp->pending_rsp.rsp_param[0]);
}


/**@brief Event handler for control point write.
 *
 * @param[in]   p_lncp     Location and Navigation Service structure.
 * @param[in]   p_ble_evt Event received from the BLE stack.
 */
static void on_req_name_of_route(ble_lncp_t * p_lncp, ble_gatts_evt_write_t const * p_evt_write)
{
    uint8_t * p_name;
    uint32_t err_code;

    p_lncp->pending_rsp.rsp_code = LNCP_RSP_SUCCESS;

    if ( !(p_lncp->is_navigation_present) )
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_OP_CODE_NOT_SUPPORTED;
        return;
    }

    if (p_evt_write->len != OPCODE_LENGTH + INT16_LEN)
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_INVALID_PARAMETER;
        return;
    }

    /*lint --e{415} --e{416} -save suppress Warning 415: possible access out of bond */
    const uint16_t route_num = uint16_decode(&p_evt_write->data[1]);
    /*lint -restore*/

    err_code = ble_ln_db_record_name_get(route_num, &p_name);
    if (err_code != NRF_SUCCESS)
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_OPERATION_FAILED;
        return;
    }
    memcpy(&p_lncp->pending_rsp.rsp_param[0], p_name, BLE_LNS_MAX_ROUTE_NAME_LEN);

    p_lncp->pending_rsp.rsp_param_len = BLE_LNS_MAX_ROUTE_NAME_LEN;
}


/**@brief Event handler for control point write.
 *
 * @param[in]   p_lncp     Location and Navigation Service structure.
 * @param[in]   p_ble_evt Event received from the BLE stack.
 */
static void on_select_route(ble_lncp_t * p_lncp, ble_gatts_evt_write_t const * p_evt_write)
{
    if ( !(p_lncp->is_navigation_present))
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_OP_CODE_NOT_SUPPORTED;
        return;
    }

    if (p_evt_write->len != OPCODE_LENGTH + INT16_LEN)
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_INVALID_PARAMETER;
        return;
    }

    const uint16_t route_num  = uint16_decode(&p_evt_write->data[1]);
    const uint16_t stored_num = ble_ln_db_num_records_get();

    if (route_num >= stored_num)
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_INVALID_PARAMETER;
        return;
    }

    const ble_lncp_evt_t evt = {
        .evt_type = LNCP_EVT_ROUTE_SELECTED,
        .params.selected_route = route_num
    };
    p_lncp->pending_rsp.rsp_code = notify_app(p_lncp, &evt);

    if (p_lncp->pending_rsp.rsp_code == LNCP_RSP_SUCCESS)
    {
        p_lncp->selected_route = route_num;
    }
}


/**@brief Event handler for control point write.
 *
 * @param[in]   p_lncp     Location and Navigation Service structure.
 * @param[in]   p_ble_evt Event received from the BLE stack.
 */
static void on_set_fix_rate(ble_lncp_t * p_lncp, ble_gatts_evt_write_t const * p_evt_write)
{
    p_lncp->pending_rsp.rsp_code = LNCP_RSP_SUCCESS;

    if ( !(p_lncp->available_features & BLE_LNS_FEATURE_FIX_RATE_SETTING_SUPPORTED) )
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_OP_CODE_NOT_SUPPORTED;
        return;
    }

    if (p_evt_write->len != OPCODE_LENGTH + INT8_LEN)
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_INVALID_PARAMETER;
        return;
    }

    /*lint --e{415} --e{416} -save suppress Warning 415: possible access out of bond */
    const uint8_t fix_rate = p_evt_write->data[1];
    /*lint -restore*/

    const ble_lncp_evt_t evt = {
        .evt_type           = LNCP_EVT_FIX_RATE_SET,
        .params.fix_rate    = fix_rate
    };
    p_lncp->pending_rsp.rsp_code = notify_app(p_lncp, &evt);

    if (p_lncp->pending_rsp.rsp_code == LNCP_RSP_SUCCESS)
    {
        p_lncp->fix_rate = fix_rate;
    }
}


/**@brief Event handler for control point write.
 *
 * @param[in]   p_lncp     Location and Navigation Service structure.
 * @param[in]   p_ble_evt Event received from the BLE stack.
 */
static void on_set_elevation(ble_lncp_t * p_lncp, ble_gatts_evt_write_t const * p_evt_write)
{
    p_lncp->pending_rsp.rsp_code = LNCP_RSP_SUCCESS;

    if ( !(p_lncp->available_features & BLE_LNS_FEATURE_ELEVATION_SETTING_SUPPORTED) )
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_OP_CODE_NOT_SUPPORTED;
        return;
    }

    if (p_evt_write->len != OPCODE_LENGTH + INT24_LEN)
    {
        p_lncp->pending_rsp.rsp_code = LNCP_RSP_INVALID_PARAMETER;
        return;
    }

    const uint32_t elevation = uint24_decode(&p_evt_write->data[1]);
    ble_lncp_evt_t evt = {
        .evt_type = LNCP_EVT_ELEVATION_SET,
        .params.elevation = elevation
    };
    p_lncp->pending_rsp.rsp_code = notify_app(p_lncp, &evt);

    if (p_lncp->pending_rsp.rsp_code == LNCP_RSP_SUCCESS)
    {
        p_lncp->elevation = elevation;
    }
}


/**@brief Handle write events to the Location and Navigation Service Control Point characteristic.
 *
 * @param[in]   p_lncp         Location and Navigation Service structure.
 * @param[in]   p_evt_write   Write event received from the BLE stack.
 */
static void on_ctrlpt_write(ble_lncp_t * p_lncp, ble_gatts_evt_write_t const * p_evt_write)
{
    uint32_t      err_code;

    p_lncp->pending_rsp.rsp_param_len = 0;

    ble_gatts_rw_authorize_reply_params_t write_authorize_reply;
    memset(&write_authorize_reply, 0, sizeof(write_authorize_reply));

    write_authorize_reply.type   = BLE_GATTS_AUTHORIZE_TYPE_WRITE;

    if (p_lncp->is_ctrlpt_indication_enabled)
    {
        if (!p_lncp->is_indication_pending)
        {
            write_authorize_reply.params.write.update      = 1;
            write_authorize_reply.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS;

            // if the op code is navigation control, its cccd must be checked
            if (p_evt_write->len > 0 && p_lncp->is_navigation_present)
            {
                if (   p_evt_write->data[0] == LNCP_OP_NAV_CONTROL
                    || p_evt_write->data[0] == LNCP_OP_REQ_NAME_OF_ROUTE
                    || p_evt_write->data[0] == LNCP_OP_REQ_NUM_ROUTES)
                {
                    if (!p_lncp->is_nav_notification_enabled)
                    {
                        write_authorize_reply.params.write.gatt_status = BLE_GATT_STATUS_ATTERR_CPS_CCCD_CONFIG_ERROR;
                    }
                }
            }
        }
        else
        {
            write_authorize_reply.params.write.gatt_status = BLE_GATT_STATUS_ATTERR_CPS_PROC_ALR_IN_PROG;
        }
    }
    else
    {
        write_authorize_reply.params.write.gatt_status = BLE_GATT_STATUS_ATTERR_CPS_CCCD_CONFIG_ERROR;
    }

    // reply to the write authorization
    do {
        err_code = sd_ble_gatts_rw_authorize_reply(p_lncp->conn_handle, &write_authorize_reply);
        if (err_code != NRF_SUCCESS && err_code != NRF_ERROR_BUSY)
        {
            if (p_lncp->error_handler != NULL)
            {
                p_lncp->error_handler(err_code);
            }
        }
    } while (err_code == NRF_ERROR_BUSY);


    if (write_authorize_reply.params.write.gatt_status != BLE_GATT_STATUS_SUCCESS)
    {
        return;
    }

    // Start executing the control point write action
    if (p_evt_write->len > 0)
    {
        p_lncp->pending_rsp.op_code = (ble_lncp_op_code_t) p_evt_write->data[0];
        switch (p_lncp->pending_rsp.op_code)
        {
            case LNCP_OP_SET_CUMULATIVE_VALUE:
                on_set_cumulative_value(p_lncp, p_evt_write);
                break;

            case LNCP_OP_MASK_LOC_SPEED_CONTENT:
                on_mask_loc_speed_content(p_lncp, p_evt_write);
                break;

            case LNCP_OP_NAV_CONTROL:
                on_nav_control(p_lncp, p_evt_write);
                break;

            case LNCP_OP_REQ_NUM_ROUTES:
                on_req_num_routes(p_lncp, p_evt_write);
                break;

            case LNCP_OP_REQ_NAME_OF_ROUTE:
                on_req_name_of_route(p_lncp, p_evt_write);
                break;

            case LNCP_OP_SELECT_ROUTE:
                on_select_route(p_lncp, p_evt_write);
                break;

            case LNCP_OP_SET_FIX_RATE:
                on_set_fix_rate(p_lncp, p_evt_write);
                break;

            case LNCP_OP_SET_ELEVATION:
                on_set_elevation(p_lncp, p_evt_write);
                break;

            // Unrecognized Op Code
            default:
                p_lncp->pending_rsp.rsp_code = LNCP_RSP_OP_CODE_NOT_SUPPORTED;
                break;
        }

        resp_send(p_lncp);
    }
}


/**@brief Write authorization request event handler.
 *
 * @details The write authorization request event handler is only called when writing to the control point.
 *
 * @param[in]   p_lncp     Location and Navigation Service structure.
 * @param[in]   p_ble_evt Event received from the BLE stack.
 */
static void on_rw_authorize_req(ble_lncp_t * p_lncp, ble_evt_t const * p_ble_evt)
{
    const ble_gatts_evt_rw_authorize_request_t * p_auth_req =
        &p_ble_evt->evt.gatts_evt.params.authorize_request;

    if (
        (p_auth_req->type == BLE_GATTS_AUTHORIZE_TYPE_WRITE)
        &&
        (p_auth_req->request.write.handle == p_lncp->ctrlpt_handles.value_handle)
        &&
        (p_auth_req->request.write.op != BLE_GATTS_OP_PREP_WRITE_REQ)
        &&
        (p_auth_req->request.write.op != BLE_GATTS_OP_EXEC_WRITE_REQ_NOW)
        &&
        (p_auth_req->request.write.op != BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL)
       )
    {
        on_ctrlpt_write(p_lncp, &p_auth_req->request.write);
    }

}


/**@brief Write event handler.
 *
 * @param[in]   p_lncp     Location and Navigation Service structure.
 * @param[in]   p_ble_evt Event received from the BLE stack.
 */
static void on_write(ble_lncp_t * p_lncp, ble_evt_t const * p_ble_evt)
{
    const ble_gatts_evt_write_t * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;

    if (p_evt_write->handle == p_lncp->ctrlpt_handles.cccd_handle)
    {
        on_lncp_cccd_write(p_lncp, p_evt_write);
    }
    else if (p_evt_write->handle == p_lncp->navigation_handles.cccd_handle)
    {
        on_nav_cccd_write(p_lncp, p_evt_write);
    }
}


void ble_lncp_on_ble_evt(ble_lncp_t * p_lncp, ble_evt_t const * p_ble_evt)
{
    VERIFY_PARAM_NOT_NULL_VOID(p_lncp);
    VERIFY_PARAM_NOT_NULL_VOID(p_ble_evt);

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

        case BLE_GAP_EVT_DISCONNECTED:
            if (p_ble_evt->evt.gap_evt.conn_handle == p_lncp->conn_handle)
            {
                on_disconnect(p_lncp, p_ble_evt);
            }
            break;

        case BLE_GATTS_EVT_WRITE:
            if (p_ble_evt->evt.gatts_evt.conn_handle == p_lncp->conn_handle)
            {
                on_write(p_lncp, p_ble_evt);
            }
            break;

        case BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST:
            if (p_ble_evt->evt.gatts_evt.conn_handle == p_lncp->conn_handle)
            {
                on_rw_authorize_req(p_lncp, p_ble_evt);
            }
            break;

        case BLE_GATTS_EVT_HVC:
            if (p_ble_evt->evt.gatts_evt.conn_handle == p_lncp->conn_handle)
            {
                on_hvc_confirm(p_lncp, p_ble_evt);
            }
            break;


        default:
            // no implementation
            break;
    }
}


uint32_t ble_lncp_total_distance_get(ble_lncp_t const * p_lncp)
{
    if (p_lncp == NULL)
    {
        return 0;
    }

    return p_lncp->total_distance;
}


uint32_t ble_lncp_elevation_get(ble_lncp_t const * p_lncp)
{
    if (p_lncp == NULL)
    {
        return 0;
    }

    return p_lncp->elevation;
}


ble_lncp_mask_t ble_lncp_mask_get(ble_lncp_t const * p_lncp)
{
    if (p_lncp == NULL)
    {
        const ble_lncp_mask_t empty_mask = {0};
        return empty_mask;
    }

    return p_lncp->mask;
}


bool ble_lncp_is_navigation_running(ble_lncp_t const * p_lncp)
{
    if (p_lncp == NULL)
    {
        return false;
    }

    return p_lncp->is_navigation_running;
}


ret_code_t ble_lncp_init(ble_lncp_t * p_lncp, ble_lncp_init_t const * p_lncp_init)
{
    VERIFY_PARAM_NOT_NULL(p_lncp);
    VERIFY_PARAM_NOT_NULL(p_lncp_init);
    VERIFY_PARAM_NOT_NULL(p_lncp_init->p_gatt_queue);

    ble_add_char_params_t add_char_params;

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

    p_lncp->service_handle               = p_lncp_init->service_handle;
    p_lncp->evt_handler                  = p_lncp_init->evt_handler;
    p_lncp->error_handler                = p_lncp_init->error_handler;
    p_lncp->p_gatt_queue                 = p_lncp_init->p_gatt_queue;
    p_lncp->available_features           = p_lncp_init->available_features;
    p_lncp->is_position_quality_present  = p_lncp_init->is_position_quality_present;
    p_lncp->is_navigation_present        = p_lncp_init->is_navigation_present;
    p_lncp->total_distance               = p_lncp_init->total_distance;
    p_lncp->elevation                    = p_lncp_init->elevation;
    p_lncp->navigation_handles           = p_lncp_init->navigation_handles;

    p_lncp->fix_rate                     = BLE_LNS_NO_FIX;
    p_lncp->selected_route               = BLE_LNS_INVALID_ROUTE;

    p_lncp->conn_handle                  = BLE_CONN_HANDLE_INVALID;
    p_lncp->is_navigation_running        = false;
    p_lncp->is_nav_notification_enabled  = false;
    p_lncp->is_ctrlpt_indication_enabled = false;

    memset(&p_lncp->mask, 0, sizeof(ble_lncp_mask_t));

    add_char_params.uuid                 = BLE_UUID_LN_CONTROL_POINT_CHAR;
    add_char_params.max_len              = 0;
    add_char_params.char_props.indicate  = true;
    add_char_params.char_props.write     = true;
    add_char_params.is_defered_write     = true;
    add_char_params.is_var_len           = true;
    add_char_params.max_len              = BLE_GATT_ATT_MTU_DEFAULT;
    add_char_params.write_access         = p_lncp_init->write_perm;
    add_char_params.cccd_write_access    = p_lncp_init->cccd_write_perm;

    NRF_LOG_DEBUG("Initialized");

    return characteristic_add(p_lncp->service_handle,
                              &add_char_params,
                              &p_lncp->ctrlpt_handles);
}