/**
 ****************************************************************************************
 * @addtogroup CPPC
 * @{
 ****************************************************************************************
 */

/*
 * INCLUDE FILES
 ****************************************************************************************
 */
#include "rwip_config.h"
#if (BLE_CP_COLLECTOR)

#include "cpp_common.h"
#include "cppc.h"
#include "cppc_task.h"
#include "ke_timer.h"

#include "co_utils.h"
#include "ke_mem.h"

/*
 * GLOBAL VARIABLES DECLARATION
 ****************************************************************************************
 */

/*
 * LOCAL FUNCTION DEFINITIONS
 ****************************************************************************************
 */

/**
 ****************************************************************************************
 * @brief Initialization of the CPPC module.
 * This function performs all the initializations of the Profile module.
 *  - Creation of database (if it's a service)
 *  - Allocation of profile required memory
 *  - Initialization of task descriptor to register application
 *      - Task State array
 *      - Number of tasks
 *      - Default task handler
 *
 * @param[out]    env        Collector or Service allocated environment data.
 * @param[in|out] start_hdl  Service start handle (0 - dynamically allocated),
 *only applies for services.
 * @param[in]     app_task   Application task number.
 * @param[in]     sec_lvl    Security level (AUTH, EKS and MI field of @see enum
 *attm_value_perm_mask)
 * @param[in]     param      Configuration parameters of profile collector or
 *service (32 bits aligned)
 *
 * @return status code to know if profile initialization succeed or not.
 ****************************************************************************************
 */
static uint8_t cppc_init(struct prf_task_env *env, uint16_t *start_hdl,
                         uint16_t app_task, uint8_t sec_lvl, void *params) {
  uint8_t idx;
  //-------------------- allocate memory required for the profile
  //---------------------

  struct cppc_env_tag *cppc_env = (struct cppc_env_tag *)ke_malloc(
      sizeof(struct cppc_env_tag), KE_MEM_ATT_DB);

  // allocate CPPC required environment variable
  env->env = (prf_env_t *)cppc_env;

  cppc_env->prf_env.app_task =
      app_task | (PERM_GET(sec_lvl, SVC_MI) ? PERM(PRF_MI, ENABLE)
                                            : PERM(PRF_MI, DISABLE));
  cppc_env->prf_env.prf_task = env->task | PERM(PRF_MI, ENABLE);

  // initialize environment variable
  env->id = TASK_ID_CPPC;
  cppc_task_init(&(env->desc));

  for (idx = 0; idx < CPPC_IDX_MAX; idx++) {
    cppc_env->env[idx] = NULL;
    // service is ready, go into an Idle state
    ke_state_set(KE_BUILD_ID(env->task, idx), CPPC_FREE);
  }

  return GAP_ERR_NO_ERROR;
}

/**
 ****************************************************************************************
 * @brief Clean-up connection dedicated environment parameters
 * This function performs cleanup of ongoing operations
 * @param[in|out]    env        Collector or Service allocated environment data.
 * @param[in]        conidx     Connection index
 * @param[in]        reason     Detach reason
 ****************************************************************************************
 */
static void cppc_cleanup(struct prf_task_env *env, uint8_t conidx,
                         uint8_t reason) {
  struct cppc_env_tag *cppc_env = (struct cppc_env_tag *)env->env;

  // clean-up environment variable allocated for task instance
  if (cppc_env->env[conidx] != NULL) {
    if (cppc_env->env[conidx]->operation != NULL) {
      ke_free(ke_param2msg(cppc_env->env[conidx]->operation));
    }
    ke_timer_clear(CPPC_TIMEOUT_TIMER_IND,
                   prf_src_task_get(&cppc_env->prf_env, conidx));
    ke_free(cppc_env->env[conidx]);
    cppc_env->env[conidx] = NULL;
  }

  /* Put CPP Client in Free state */
  ke_state_set(KE_BUILD_ID(env->task, conidx), CPPC_FREE);
}

/**
 ****************************************************************************************
 * @brief Destruction of the CPPC module - due to a reset for instance.
 * This function clean-up allocated memory (attribute database is destroyed by
 *another procedure)
 *
 * @param[in|out]    env        Collector or Service allocated environment data.
 ****************************************************************************************
 */
static void cppc_destroy(struct prf_task_env *env) {
  uint8_t idx;
  struct cppc_env_tag *cppc_env = (struct cppc_env_tag *)env->env;

  // cleanup environment variable for each task instances
  for (idx = 0; idx < CPPC_IDX_MAX; idx++) {
    cppc_cleanup(env, idx, 0);
  }

  // free profile environment variables
  env->env = NULL;
  ke_free(cppc_env);
}

/**
 ****************************************************************************************
 * @brief Handles Connection creation
 *
 * @param[in|out]    env        Collector or Service allocated environment data.
 * @param[in]        conidx     Connection index
 ****************************************************************************************
 */
static void cppc_create(struct prf_task_env *env, uint8_t conidx) {
  /* Put CPP Client in Idle state */
  ke_state_set(KE_BUILD_ID(env->task, conidx), CPPC_IDLE);
}

/// CPPC Task interface required by profile manager
const struct prf_task_cbs cppc_itf = {
    cppc_init,
    cppc_destroy,
    cppc_create,
    cppc_cleanup,
};

/*
 * GLOBAL FUNCTIONS DEFINITIONS
 ****************************************************************************************
 */

const struct prf_task_cbs *cppc_prf_itf_get(void) { return &cppc_itf; }

void cppc_enable_rsp_send(struct cppc_env_tag *cppc_env, uint8_t conidx,
                          uint8_t status) {
  // Send to APP the details of the discovered attributes on CPPS
  struct cppc_enable_rsp *rsp = KE_MSG_ALLOC(
      CPPC_ENABLE_RSP, prf_dst_task_get(&(cppc_env->prf_env), conidx),
      prf_src_task_get(&(cppc_env->prf_env), conidx), cppc_enable_rsp);
  rsp->status = status;

  if (status == GAP_ERR_NO_ERROR) {
    rsp->cps = cppc_env->env[conidx]->cps;
    // Register CPPC task in gatt for indication/notifications
    prf_register_atthdl2gatt(&(cppc_env->prf_env), conidx,
                             &(cppc_env->env[conidx]->cps.svc));
    // Go to connected state
    ke_state_set(prf_src_task_get(&(cppc_env->prf_env), conidx), CPPC_IDLE);
  }

  ke_msg_send(rsp);
}

void cppc_send_no_conn_cmp_evt(uint8_t src_id, uint8_t dest_id,
                               uint8_t operation) {
  // Send the message
  struct cppc_cmp_evt *evt =
      KE_MSG_ALLOC(CPPC_CMP_EVT, dest_id, src_id, cppc_cmp_evt);

  evt->operation = operation;
  evt->status = PRF_ERR_REQ_DISALLOWED;

  ke_msg_send(evt);
}

void cppc_send_cmp_evt(struct cppc_env_tag *cppc_env, uint8_t conidx,
                       uint8_t operation, uint8_t status) {
  // Free the stored operation if needed
  if (cppc_env->env[conidx]->operation != NULL) {
    ke_msg_free(ke_param2msg(cppc_env->env[conidx]->operation));
    cppc_env->env[conidx]->operation = NULL;
  }

  // Go back to the IDLE state if the state is busy
  if (ke_state_get(prf_src_task_get(&(cppc_env->prf_env), conidx)) ==
      CPPC_BUSY) {
    ke_state_set(prf_src_task_get(&cppc_env->prf_env, conidx), CPPC_IDLE);
  }

  // Send the message
  struct cppc_cmp_evt *evt = KE_MSG_ALLOC(
      CPPC_CMP_EVT, prf_dst_task_get(&(cppc_env->prf_env), conidx),
      prf_src_task_get(&(cppc_env->prf_env), conidx), cppc_cmp_evt);

  evt->operation = operation;
  evt->status = status;

  ke_msg_send(evt);
}

/*
 * LOCAL FUNCTIONS DEFINITIONS
 ****************************************************************************************
 */

uint16_t cppc_get_read_handle_req(struct cppc_env_tag *cppc_env, uint8_t conidx,
                                  struct cpps_read_cmd *param) {
  // Attribute Handle
  uint16_t handle = ATT_INVALID_SEARCH_HANDLE;

  switch (param->read_code) {
  // Read CP Feature
  case (CPPC_RD_CP_FEAT): {
    handle = cppc_env->env[conidx]->cps.chars[CPP_CPS_FEAT_CHAR].val_hdl;
  } break;

  // Read Sensor Location
  case (CPPC_RD_SENSOR_LOC): {
    handle = cppc_env->env[conidx]->cps.chars[CPP_CPS_SENSOR_LOC_CHAR].val_hdl;
  } break;

  // Read CP Measurement Characteristic Client Char. Cfg. Descriptor Value
  case (CPPC_RD_WR_CP_MEAS_CL_CFG): {
    handle =
        cppc_env->env[conidx]->cps.descs[CPPC_DESC_CP_MEAS_CL_CFG].desc_hdl;
  } break;

  // Read CP Measurement Characteristic Server Char. Cfg. Descriptor Value
  case (CPPC_RD_WR_CP_MEAS_SV_CFG): {
    handle =
        cppc_env->env[conidx]->cps.descs[CPPC_DESC_CP_MEAS_SV_CFG].desc_hdl;
  } break;

  // Read CP Vector Characteristic Server Char. Cfg. Descriptor Value
  case (CPPC_RD_WR_VECTOR_CFG): {
    handle = cppc_env->env[conidx]->cps.descs[CPPC_DESC_VECTOR_CL_CFG].desc_hdl;
  } break;

  // Read Unread Alert Characteristic Client Char. Cfg. Descriptor Value
  case (CPPC_RD_WR_CTNL_PT_CFG): {
    handle =
        cppc_env->env[conidx]->cps.descs[CPPC_DESC_CTNL_PT_CL_CFG].desc_hdl;
  } break;

  default: {
    handle = ATT_INVALID_SEARCH_HANDLE;
  } break;
  }

  return handle;
}

uint8_t cppc_get_write_desc_handle_req(uint8_t conidx,
                                       struct cppc_cfg_ntfind_cmd *param,
                                       struct cppc_env_tag *cppc_env,
                                       uint16_t *handle) {
  // Status
  uint8_t status = GAP_ERR_NO_ERROR;

  switch (param->desc_code) {
  // Write CP Measurement Characteristic Client Char. Cfg. Descriptor Value
  case (CPPC_RD_WR_CP_MEAS_CL_CFG): {
    if (param->ntfind_cfg <= PRF_CLI_START_NTF) {
      *handle =
          cppc_env->env[conidx]->cps.descs[CPPC_DESC_CP_MEAS_CL_CFG].desc_hdl;

      // The descriptor is mandatory
      ASSERT_ERR(*handle != ATT_INVALID_SEARCH_HANDLE);
    } else {
      status = PRF_ERR_INVALID_PARAM;
    }
  } break;

  // Write CP Measurement Characteristic Server Char. Cfg. Descriptor Value
  case (CPPC_RD_WR_CP_MEAS_SV_CFG): {
    if (param->ntfind_cfg <= PRF_SRV_START_BCST) {
      *handle =
          cppc_env->env[conidx]->cps.descs[CPPC_DESC_CP_MEAS_SV_CFG].desc_hdl;

      if (*handle == ATT_INVALID_SEARCH_HANDLE) {
        // The descriptor has not been found.
        status = PRF_ERR_INEXISTENT_HDL;
      }
    } else {
      status = PRF_ERR_INVALID_PARAM;
    }
  } break;

  // Write CP Vector Characteristic Client Char. Cfg. Descriptor Value
  case (CPPC_RD_WR_VECTOR_CFG): {
    if (param->ntfind_cfg <= PRF_CLI_START_NTF) {
      *handle =
          cppc_env->env[conidx]->cps.descs[CPPC_DESC_VECTOR_CL_CFG].desc_hdl;

      if (*handle == ATT_INVALID_SEARCH_HANDLE) {
        // The descriptor has not been found.
        status = PRF_ERR_INEXISTENT_HDL;
      }
    } else {
      status = PRF_ERR_INVALID_PARAM;
    }
  } break;

  // Write SC Control Point Characteristic Client Char. Cfg. Descriptor Value
  case (CPPC_RD_WR_CTNL_PT_CFG): {
    if ((param->ntfind_cfg == PRF_CLI_STOP_NTFIND) ||
        (param->ntfind_cfg == PRF_CLI_START_IND)) {
      *handle =
          cppc_env->env[conidx]->cps.descs[CPPC_DESC_CTNL_PT_CL_CFG].desc_hdl;

      if (*handle == ATT_INVALID_SEARCH_HANDLE) {
        // The descriptor has not been found.
        status = PRF_ERR_INEXISTENT_HDL;
      }
    } else {
      status = PRF_ERR_INVALID_PARAM;
    }
  } break;

  default: {
    status = PRF_ERR_INVALID_PARAM;
  } break;
  }

  return (status);
}

uint8_t cppc_unpack_meas_ind(uint8_t conidx,
                             struct gattc_event_ind const *param,
                             struct cppc_env_tag *cppc_env) {
  // CP Measurement value has been received
  struct cppc_value_meas_ind *ind = KE_MSG_ALLOC(
      CPPC_VALUE_IND, prf_dst_task_get(&(cppc_env->prf_env), conidx),
      prf_src_task_get(&(cppc_env->prf_env), conidx), cppc_value_meas_ind);

  // Offset
  uint8_t offset = CPP_CP_MEAS_NTF_MIN_LEN;

  // Attribute code
  ind->att_code = CPPC_NTF_CP_MEAS;

  // Flags
  ind->value.cp_meas.flags = co_read16p(&param->value[0]);
  // Instant power
  ind->value.cp_meas.inst_power = co_read16p(&param->value[2]);

  if (ind->value.cp_meas.flags & CPP_MEAS_PEDAL_POWER_BALANCE_PRESENT) {
    // Unpack Pedal Power Balance info
    ind->value.cp_meas.pedal_power_balance = param->value[offset];
    offset++;
  }

  if (ind->value.cp_meas.flags & CPP_MEAS_ACCUM_TORQUE_PRESENT) {
    // Unpack Accumulated Torque info
    ind->value.cp_meas.accum_torque = co_read16p(&param->value[offset]);
    offset += 2;
  }

  if (ind->value.cp_meas.flags & CPP_MEAS_WHEEL_REV_DATA_PRESENT) {
    // Unpack Wheel Revolution Data (Cumulative Wheel & Last Wheel Event Time)
    ind->value.cp_meas.cumul_wheel_rev = co_read32p(&param->value[offset]);
    offset += 4;
    ind->value.cp_meas.last_wheel_evt_time = co_read16p(&param->value[offset]);
    offset += 2;
  }

  if (ind->value.cp_meas.flags & CPP_MEAS_CRANK_REV_DATA_PRESENT) {
    // Unpack Crank Revolution Data (Cumulative Crank & Last Crank Event Time)
    ind->value.cp_meas.cumul_crank_rev = co_read16p(&param->value[offset]);
    offset += 2;
    ind->value.cp_meas.last_crank_evt_time = co_read16p(&param->value[offset]);
    offset += 2;
  }

  if (ind->value.cp_meas.flags & CPP_MEAS_EXTREME_FORCE_MAGNITUDES_PRESENT) {
    // Unpack Extreme Force Magnitudes (Maximum Force Magnitude & Minimum Force
    // Magnitude)
    ind->value.cp_meas.max_force_magnitude = co_read16p(&param->value[offset]);
    offset += 2;
    ind->value.cp_meas.min_force_magnitude = co_read16p(&param->value[offset]);
    offset += 2;
  }

  else if (ind->value.cp_meas.flags &
           CPP_MEAS_EXTREME_TORQUE_MAGNITUDES_PRESENT) {
    // Unpack Extreme Force Magnitudes (Maximum Force Magnitude & Minimum Force
    // Magnitude)
    ind->value.cp_meas.max_torque_magnitude = co_read16p(&param->value[offset]);
    offset += 2;
    ind->value.cp_meas.min_torque_magnitude = co_read16p(&param->value[offset]);
    offset += 2;
  }

  if (ind->value.cp_meas.flags & CPP_MEAS_EXTREME_ANGLES_PRESENT) {
    // Unpack Extreme Angles (Maximum Angle & Minimum Angle)
    uint32_t angle = co_read24p(&param->value[offset]);
    offset += 3;

    // Force to 12 bits
    ind->value.cp_meas.max_angle = (angle & (0x0FFF));
    ind->value.cp_meas.min_angle = ((angle >> 12) & 0x0FFF);
  }

  if (ind->value.cp_meas.flags & CPP_MEAS_TOP_DEAD_SPOT_ANGLE_PRESENT) {
    // Unpack Top Dead Spot Angle
    ind->value.cp_meas.top_dead_spot_angle = co_read16p(&param->value[offset]);
    offset += 2;
  }

  if (ind->value.cp_meas.flags & CPP_MEAS_BOTTOM_DEAD_SPOT_ANGLE_PRESENT) {
    // Unpack Bottom Dead Spot Angle
    ind->value.cp_meas.bot_dead_spot_angle = co_read16p(&param->value[offset]);
    offset += 2;
  }

  if (ind->value.cp_meas.flags & CPP_MEAS_ACCUM_ENERGY_PRESENT) {
    // Unpack Accumulated Energy
    ind->value.cp_meas.accum_energy = co_read16p(&param->value[offset]);
    offset += 2;
  }

  // Send the message
  ke_msg_send(ind);

  return offset;
}

uint8_t cppc_unpack_vector_ind(uint8_t conidx,
                               struct gattc_event_ind const *param,
                               struct cppc_env_tag *cppc_env) {
  // CP Measurement value has been received
  struct cppc_value_ind *ind = KE_MSG_ALLOC_DYN(
      CPPC_VALUE_IND, prf_dst_task_get(&cppc_env->prf_env, conidx),
      prf_src_task_get(&cppc_env->prf_env, conidx), cppc_value_ind,
      param->length);

  // Offset
  uint8_t offset = CPP_CP_VECTOR_MIN_LEN;

  // Attribute code
  ind->att_code = CPPC_NTF_CP_VECTOR;

  // Flags
  ind->value.cp_vector.flags = param->value[0];

  if (ind->value.cp_vector.flags & CPP_VECTOR_CRANK_REV_DATA_PRESENT) {
    // Unpack Crank Revolution Data
    ind->value.cp_vector.cumul_crank_rev = co_read16p(&param->value[offset]);
    offset += 2;
    // Unpack Last Crank Evt time
    ind->value.cp_vector.last_crank_evt_time =
        co_read16p(&param->value[offset]);
    offset += 2;
  }

  if (ind->value.cp_vector.flags & CPP_VECTOR_FIRST_CRANK_MEAS_ANGLE_PRESENT) {
    // Unpack First Crank Measurement Angle
    ind->value.cp_vector.first_crank_meas_angle =
        co_read16p(&param->value[offset]);
    offset += 2;
  }

  if (!(ind->value.cp_vector.flags &
        CPP_VECTOR_INST_FORCE_MAGNITUDE_ARRAY_PRESENT) !=
      !(ind->value.cp_vector.flags &
        CPP_VECTOR_INST_TORQUE_MAGNITUDE_ARRAY_PRESENT)) {
    // Unpack Force or Torque magnitude (mutually excluded)
    ind->value.cp_vector.nb = (param->length - offset) / 2;
    if (ind->value.cp_vector.nb) {
      for (int i = 0; i < ind->value.cp_vector.nb; i++) {
        // Handle the array buffer to extract parameters
        ind->value.cp_vector.force_torque_magnitude[i] =
            co_read16p(&param->value[offset]);
        offset += 2;
      }
    }
  }

  //    if ((ind->value.cp_vector.flags & CPP_VECTOR_INST_MEAS_DIRECTION_LSB) ||
  //            (ind->value.cp_vector.flags &
  //            CPP_VECTOR_INST_MEAS_DIRECTION_MSB))
  //    {
  //
  //    }

  // Send the message
  ke_msg_send(ind);

  return offset;
}

uint8_t cppc_pack_ctnl_pt_req(struct cppc_ctnl_pt_cfg_req *param, uint8_t *req,
                              uint8_t *status) {
  // Request Length
  uint8_t req_len = CPP_CP_CNTL_PT_REQ_MIN_LEN;

  // Set the operation code
  req[0] = param->ctnl_pt.op_code;

  // Fulfill the message according to the operation code
  switch (param->ctnl_pt.op_code) {
  case (CPP_CTNL_PT_SET_CUMUL_VAL): {
    // Set the cumulative value
    co_write32p(&req[req_len], param->ctnl_pt.value.cumul_val);
    // Update length
    req_len += 4;
  } break;

  case (CPP_CTNL_PT_UPD_SENSOR_LOC): {
    // Set the sensor location
    req[req_len] = param->ctnl_pt.value.sensor_loc;
    // Update length
    req_len++;
  } break;

  case (CPP_CTNL_PT_SET_CRANK_LENGTH): {
    // Set the crank length
    co_write16p(&req[req_len], param->ctnl_pt.value.crank_length);
    // Update length
    req_len += 2;
  } break;

  case (CPP_CTNL_PT_SET_CHAIN_LENGTH): {
    // Set the chain length
    co_write16p(&req[req_len], param->ctnl_pt.value.chain_length);
    // Update length
    req_len += 2;
  } break;

  case (CPP_CTNL_PT_SET_CHAIN_WEIGHT): {
    // Set the chain weight
    co_write16p(&req[req_len], param->ctnl_pt.value.chain_weight);
    // Update length
    req_len += 2;
  } break;

  case (CPP_CTNL_PT_SET_SPAN_LENGTH): {
    // Set the span length
    co_write16p(&req[req_len], param->ctnl_pt.value.span_length);
    // Update length
    req_len += 2;
  } break;

  case (CPP_CTNL_MASK_CP_MEAS_CH_CONTENT): {
    // Set the Content Mask
    co_write16p(&req[req_len], param->ctnl_pt.value.mask_content);
    // Update length
    req_len += 2;
  } break;

  case (CPP_CTNL_PT_REQ_SUPP_SENSOR_LOC):
  case (CPP_CTNL_PT_REQ_CRANK_LENGTH):
  case (CPP_CTNL_PT_REQ_CHAIN_LENGTH):
  case (CPP_CTNL_PT_REQ_CHAIN_WEIGHT):
  case (CPP_CTNL_PT_REQ_SPAN_LENGTH):
  case (CPP_CTNL_PT_START_OFFSET_COMP):
  case (CPP_CTNL_REQ_SAMPLING_RATE):
  case (CPP_CTNL_REQ_FACTORY_CALIBRATION_DATE): {
    // Nothing more to do
  } break;

  default: {
    *status = PRF_ERR_INVALID_PARAM;
  } break;
  }

  return req_len;
}

uint8_t cppc_unpack_ctln_pt_ind(uint8_t conidx,
                                struct gattc_event_ind const *param,
                                struct cppc_env_tag *cppc_env) {
  // Offset
  uint8_t offset = CPP_CP_CNTL_PT_RSP_MIN_LEN;

  // Control Point value has been received
  struct cppc_ctnl_pt_rsp *ind = KE_MSG_ALLOC(
      CPPC_CTNL_PT_RSP, prf_dst_task_get(&cppc_env->prf_env, conidx),
      prf_src_task_get(&cppc_env->prf_env, conidx), cppc_ctnl_pt_rsp);

  // Requested operation code
  ind->rsp.req_op_code = param->value[1];
  // Response value
  ind->rsp.resp_value = param->value[2];

  if ((ind->rsp.resp_value == CPP_CTNL_PT_RESP_SUCCESS) &&
      (param->length >= 3)) {
    switch (ind->rsp.req_op_code) {
    case (CPP_CTNL_PT_REQ_SUPP_SENSOR_LOC): {
      // Get the number of supported locations that have been received
      uint8_t nb_supp_loc = (param->length - 3);
      // Location
      uint8_t loc;

      for (uint8_t counter = 0; counter < nb_supp_loc; counter++) {
        loc = param->value[counter + CPP_CP_CNTL_PT_RSP_MIN_LEN];
        // Check if valid
        if (loc < CPP_LOC_MAX) {
          ind->rsp.value.supp_loc |= (1 << loc);
        }
        offset++;
      }
    } break;

    case (CPP_CTNL_PT_REQ_CRANK_LENGTH): {
      ind->rsp.value.crank_length = co_read16p(&param->value[offset]);
      offset += 2;
    } break;

    case (CPP_CTNL_PT_REQ_CHAIN_LENGTH): {
      ind->rsp.value.chain_length = co_read16p(&param->value[offset]);
      offset += 2;
    } break;

    case (CPP_CTNL_PT_REQ_CHAIN_WEIGHT): {
      ind->rsp.value.chain_weight = co_read16p(&param->value[offset]);
      offset += 2;
    } break;

    case (CPP_CTNL_PT_REQ_SPAN_LENGTH): {
      ind->rsp.value.span_length = co_read16p(&param->value[offset]);
      offset += 2;
    } break;

    case (CPP_CTNL_PT_START_OFFSET_COMP): {
      ind->rsp.value.offset_comp = co_read16p(&param->value[offset]);
      offset += 2;
    } break;

    case (CPP_CTNL_REQ_SAMPLING_RATE): {
      ind->rsp.value.sampling_rate = param->value[offset];
      offset++;
    } break;

    case (CPP_CTNL_REQ_FACTORY_CALIBRATION_DATE): {
      offset += prf_unpack_date_time((uint8_t *)&(param->value[offset]),
                                     &(ind->rsp.value.factory_calibration));
    } break;

    case (CPP_CTNL_PT_SET_CUMUL_VAL):
    case (CPP_CTNL_PT_UPD_SENSOR_LOC):
    case (CPP_CTNL_PT_SET_CRANK_LENGTH):
    case (CPP_CTNL_PT_SET_CHAIN_LENGTH):
    case (CPP_CTNL_PT_SET_CHAIN_WEIGHT):
    case (CPP_CTNL_PT_SET_SPAN_LENGTH):
    case (CPP_CTNL_MASK_CP_MEAS_CH_CONTENT): {
      // No parameters
    } break;

    default: {

    } break;
    }
  }

  // Send the message
  ke_msg_send(ind);

  return offset;
}
#endif //(BLE_CP_COLLECTOR)

/// @} CPP
