/**
 ****************************************************************************************
 * @addtogroup HOGPD
 * @{
 ****************************************************************************************
 */

/*
 * INCLUDE FILES
 ****************************************************************************************
 */
#include "rwip_config.h"

#if (BLE_HID_DEVICE)

#include "attm.h"
#include "gap.h"
#include "gattc_task.h"
#include "hogpd.h"
#include "hogpd_task.h"
#include "prf_utils.h"

#include "ke_mem.h"

/*
 * DEFINES
 ****************************************************************************************
 */
#define HIDS_CFG_FLAG_MANDATORY_MASK ((uint32_t)0x000FD)
#define HIDS_MANDATORY_ATT_NB (7)
#define HIDS_CFG_FLAG_MAP_EXT_MASK ((uint32_t)0x00102)
#define HIDS_MAP_EXT_ATT_NB (2)
#define HIDS_CFG_FLAG_PROTO_MODE_MASK ((uint32_t)0x00600)
#define HIDS_PROTO_MODE_ATT_NB (2)
#define HIDS_CFG_FLAG_KEYBOARD_MASK ((uint32_t)0x0F800)
#define HIDS_KEYBOARD_ATT_NB (5)
#define HIDS_CFG_FLAG_MOUSE_MASK ((uint32_t)0x70000)
#define HIDS_MOUSE_ATT_NB (3)

#define HIDS_CFG_REPORT_MANDATORY_MASK ((uint32_t)0x7)
#define HIDS_REPORT_MANDATORY_ATT_NB (3)
#define HIDS_CFG_REPORT_IN_MASK ((uint32_t)0x8)
#define HIDS_REPORT_IN_ATT_NB (1)
// number of attribute index for a report
#define HIDS_REPORT_NB_IDX (4)

/*
 * HIDS ATTRIBUTES DEFINITION
 ****************************************************************************************
 */

/// Full HIDS Database Description - Used to add attributes into the database
const struct attm_desc hids_att_db[HOGPD_IDX_NB] = {
    // HID Service Declaration
    [HOGPD_IDX_SVC] = {ATT_DECL_PRIMARY_SERVICE, PERM(RD, ENABLE), 0, 0},

    // HID Service Declaration
    [HOGPD_IDX_INCL_SVC] = {ATT_DECL_INCLUDE, PERM(RD, ENABLE), 0, 0},

    // HID Information Characteristic Declaration
    [HOGPD_IDX_HID_INFO_CHAR] = {ATT_DECL_CHARACTERISTIC, PERM(RD, ENABLE), 0,
                                 0},
    // HID Information Characteristic Value
    [HOGPD_IDX_HID_INFO_VAL] = {ATT_CHAR_HID_INFO, PERM(RD, ENABLE), 0,
                                sizeof(struct hids_hid_info)},

    // HID Control Point Characteristic Declaration
    [HOGPD_IDX_HID_CTNL_PT_CHAR] = {ATT_DECL_CHARACTERISTIC, PERM(RD, ENABLE),
                                    0, 0},
    // HID Control Point Characteristic Value
    [HOGPD_IDX_HID_CTNL_PT_VAL] = {ATT_CHAR_HID_CTNL_PT,
                                   PERM(WRITE_COMMAND, ENABLE),
                                   PERM(RI, ENABLE), sizeof(uint8_t)},

    // Report Map Characteristic Declaration
    [HOGPD_IDX_REPORT_MAP_CHAR] = {ATT_DECL_CHARACTERISTIC, PERM(RD, ENABLE), 0,
                                   0},
    // Report Map Characteristic Value
    [HOGPD_IDX_REPORT_MAP_VAL] = {ATT_CHAR_REPORT_MAP, PERM(RD, ENABLE),
                                  PERM(RI, ENABLE), HOGPD_REPORT_MAP_MAX_LEN},
    // Report Map Characteristic - External Report Reference Descriptor
    [HOGPD_IDX_REPORT_MAP_EXT_REP_REF] = {ATT_DESC_EXT_REPORT_REF,
                                          PERM(RD, ENABLE), 0,
                                          sizeof(uint16_t)},

    // Protocol Mode Characteristic Declaration
    [HOGPD_IDX_PROTO_MODE_CHAR] = {ATT_DECL_CHARACTERISTIC, PERM(RD, ENABLE), 0,
                                   0},
    // Protocol Mode Characteristic Value
    [HOGPD_IDX_PROTO_MODE_VAL] = {ATT_CHAR_PROTOCOL_MODE,
                                  (PERM(RD, ENABLE) |
                                   PERM(WRITE_COMMAND, ENABLE)),
                                  PERM(RI, ENABLE), sizeof(uint8_t)},

    // Boot Keyboard Input Report Characteristic Declaration
    [HOGPD_IDX_BOOT_KB_IN_REPORT_CHAR] = {ATT_DECL_CHARACTERISTIC,
                                          PERM(RD, ENABLE), 0, 0},
    // Boot Keyboard Input Report Characteristic Value
    [HOGPD_IDX_BOOT_KB_IN_REPORT_VAL] = {ATT_CHAR_BOOT_KB_IN_REPORT,
                                         (PERM(RD, ENABLE) | PERM(NTF, ENABLE)),
                                         PERM(RI, ENABLE),
                                         HOGPD_BOOT_REPORT_MAX_LEN},
    // Boot Keyboard Input Report Characteristic - Client Characteristic
    // Configuration Descriptor
    [HOGPD_IDX_BOOT_KB_IN_REPORT_NTF_CFG] = {ATT_DESC_CLIENT_CHAR_CFG,
                                             PERM(RD, ENABLE) |
                                                 PERM(WRITE_REQ, ENABLE),
                                             0, 0},

    // Boot Keyboard Output Report Characteristic Declaration
    [HOGPD_IDX_BOOT_KB_OUT_REPORT_CHAR] = {ATT_DECL_CHARACTERISTIC,
                                           PERM(RD, ENABLE), 0, 0},
    // Boot Keyboard Output Report Characteristic Value
    [HOGPD_IDX_BOOT_KB_OUT_REPORT_VAL] = {ATT_CHAR_BOOT_KB_OUT_REPORT,
                                          (PERM(RD, ENABLE) |
                                           PERM(WRITE_REQ, ENABLE) |
                                           PERM(WRITE_COMMAND, ENABLE)),
                                          PERM(RI, ENABLE),
                                          HOGPD_BOOT_REPORT_MAX_LEN},

    // Boot Mouse Input Report Characteristic Declaration
    [HOGPD_IDX_BOOT_MOUSE_IN_REPORT_CHAR] = {ATT_DECL_CHARACTERISTIC,
                                             PERM(RD, ENABLE), 0, 0},
    // Boot Mouse Input Report Characteristic Value
    [HOGPD_IDX_BOOT_MOUSE_IN_REPORT_VAL] =
        {
            ATT_CHAR_BOOT_MOUSE_IN_REPORT,
            (PERM(RD, ENABLE) | PERM(NTF, ENABLE)),
            PERM(RI, ENABLE),
            HOGPD_BOOT_REPORT_MAX_LEN,
        },
    // Boot Mouse Input Report Characteristic - Client Characteristic
    // Configuration Descriptor
    [HOGPD_IDX_BOOT_MOUSE_IN_REPORT_NTF_CFG] = {ATT_DESC_CLIENT_CHAR_CFG,
                                                PERM(RD, ENABLE) |
                                                    PERM(WRITE_REQ, ENABLE) |
                                                    PERM(WRITE_COMMAND, ENABLE),
                                                0, 0},

    // Report Characteristic Declaration
    [HOGPD_IDX_REPORT_CHAR] = {ATT_DECL_CHARACTERISTIC, PERM(RD, ENABLE), 0, 0},
    // Report Characteristic Value
    [HOGPD_IDX_REPORT_VAL] = {ATT_CHAR_REPORT, PERM(RD, ENABLE),
                              PERM(RI, ENABLE), HOGPD_REPORT_MAX_LEN},
    // Report Characteristic - Report Reference Descriptor
    [HOGPD_IDX_REPORT_REP_REF] = {ATT_DESC_REPORT_REF, PERM(RD, ENABLE), 0,
                                  sizeof(struct hids_report_ref)},
    // Report Characteristic - Client Characteristic Configuration Descriptor
    [HOGPD_IDX_REPORT_NTF_CFG] = {ATT_DESC_CLIENT_CHAR_CFG,
                                  PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE) |
                                      PERM(WRITE_COMMAND, ENABLE),
                                  0, 0},
};

/**
 ****************************************************************************************
 * @brief Initialization of the HOGPD 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 hogpd_init(struct prf_task_env *env, uint16_t *start_hdl,
                          uint16_t app_task, uint8_t sec_lvl,
                          struct hogpd_db_cfg *params) {
  struct hogpd_env_tag *hogpd_env = NULL;
  // Service content flag - Without Report Characteristics
  uint32_t cfg_flag[HOGPD_NB_HIDS_INST_MAX][(HOGPD_ATT_MAX / 32) + 1];
  // Start handle used to allocate service.
  uint16_t shdl;

  // Total number of attributes
  uint8_t tot_nb_att = 0;

  // array of service description used to allocate the service
  struct attm_desc *hids_db[HOGPD_NB_HIDS_INST_MAX];
  // Status
  uint8_t status = GAP_ERR_NO_ERROR;
  // Service Instance Counter, Counter
  uint8_t svc_idx, report_idx;
  // Report Char. Report Ref value
  struct hids_report_ref report_ref;

  //-------------------- allocate memory required for the profile
  //---------------------
  hogpd_env = (struct hogpd_env_tag *)ke_malloc(sizeof(struct hogpd_env_tag),
                                                KE_MEM_ATT_DB);

  // ensure that everything is initialized
  memset(hids_db, 0, sizeof(hids_db));
  memset(cfg_flag, 0, sizeof(cfg_flag));
  memset(hogpd_env, 0, sizeof(struct hogpd_env_tag));

  // Check number of HID instances
  if ((params->hids_nb == 0) || (params->hids_nb > HOGPD_NB_HIDS_INST_MAX)) {
    // Invalid number of service instances
    status = PRF_ERR_INVALID_PARAM;
  }

  // For each required HIDS instance
  for (svc_idx = 0;
       ((svc_idx < params->hids_nb) && (status == GAP_ERR_NO_ERROR));
       svc_idx++) {
    // Check number of Report Char. instances
    if (params->cfg[svc_idx].report_nb > HOGPD_NB_REPORT_INST_MAX) {
      // Too many Report Char. Instances
      status = PRF_ERR_INVALID_PARAM;
      break;
    }

    // retrieve features
    hogpd_env->svcs[svc_idx].features =
        params->cfg[svc_idx].svc_features & HOGPD_CFG_MASK;
    hogpd_env->svcs[svc_idx].nb_report = params->cfg[svc_idx].report_nb;

    hids_db[svc_idx] = (struct attm_desc *)ke_malloc(
        HOGPD_ATT_MAX * sizeof(struct attm_desc), KE_MEM_NON_RETENTION);

    cfg_flag[svc_idx][0] = HIDS_CFG_FLAG_MANDATORY_MASK;
    hogpd_env->svcs[svc_idx].nb_att += HIDS_MANDATORY_ATT_NB;

    // copy default definition of the HID attribute database
    memcpy(hids_db[svc_idx], hids_att_db,
           sizeof(struct attm_desc) * HOGPD_ATT_UNIQ_NB);

    //--------------------------------------------------------------------
    // Compute cfg_flag[i] without Report Characteristics
    //--------------------------------------------------------------------
    if ((params->cfg[svc_idx].svc_features & HOGPD_CFG_MAP_EXT_REF) ==
        HOGPD_CFG_MAP_EXT_REF) {
      cfg_flag[svc_idx][0] |= HIDS_CFG_FLAG_MAP_EXT_MASK;
      hogpd_env->svcs[svc_idx].nb_att += HIDS_MAP_EXT_ATT_NB;
      // store reference handle in database
      hids_db[svc_idx][HOGPD_IDX_INCL_SVC].max_size =
          params->cfg[svc_idx].ext_ref.inc_svc_hdl;
    }

    if ((params->cfg[svc_idx].svc_features & HOGPD_CFG_PROTO_MODE) ==
        HOGPD_CFG_PROTO_MODE) {
      cfg_flag[svc_idx][0] |= HIDS_CFG_FLAG_PROTO_MODE_MASK;
      hogpd_env->svcs[svc_idx].nb_att += HIDS_PROTO_MODE_ATT_NB;
    }

    if ((params->cfg[svc_idx].svc_features & HOGPD_CFG_KEYBOARD) ==
        HOGPD_CFG_KEYBOARD) {
      cfg_flag[svc_idx][0] |= HIDS_CFG_FLAG_KEYBOARD_MASK;
      hogpd_env->svcs[svc_idx].nb_att += HIDS_KEYBOARD_ATT_NB;

      if ((params->cfg[svc_idx].svc_features & HOGPD_CFG_BOOT_KB_WR) ==
          HOGPD_CFG_BOOT_KB_WR) {
        // Adds write permissions on report
        hids_db[svc_idx][HOGPD_IDX_BOOT_KB_IN_REPORT_VAL].perm |=
            (PERM(WRITE_REQ, ENABLE));
      }
    }

    if ((params->cfg[svc_idx].svc_features & HOGPD_CFG_MOUSE) ==
        HOGPD_CFG_MOUSE) {
      cfg_flag[svc_idx][0] |= HIDS_CFG_FLAG_MOUSE_MASK;
      hogpd_env->svcs[svc_idx].nb_att += HIDS_MOUSE_ATT_NB;

      if ((params->cfg[svc_idx].svc_features & HOGPD_CFG_BOOT_MOUSE_WR) ==
          HOGPD_CFG_BOOT_MOUSE_WR) {
        // Adds write permissions on report
        hids_db[svc_idx][HOGPD_IDX_BOOT_MOUSE_IN_REPORT_VAL].perm |=
            (PERM(WRITE_REQ, ENABLE));
      }
    }

    // set report handle offset
    hogpd_env->svcs[svc_idx].report_hdl_offset =
        hogpd_env->svcs[svc_idx].nb_att;

    //--------------------------------------------------------------------
    // Update cfg_flag_rep[i] with Report Characteristics
    //--------------------------------------------------------------------
    for (report_idx = 0; report_idx < params->cfg[svc_idx].report_nb;
         report_idx++) {
      uint16_t perm = 0;
      uint16_t report_offset = HIDS_REPORT_NB_IDX * report_idx;

      // update config for current report
      cfg_flag[svc_idx][(HOGPD_IDX_REPORT_CHAR + report_offset) / 32] |=
          (1 << ((HOGPD_IDX_REPORT_CHAR + report_offset) % 32));
      cfg_flag[svc_idx][(HOGPD_IDX_REPORT_VAL + report_offset) / 32] |=
          (1 << ((HOGPD_IDX_REPORT_VAL + report_offset) % 32));
      cfg_flag[svc_idx][(HOGPD_IDX_REPORT_REP_REF + report_offset) / 32] |=
          (1 << ((HOGPD_IDX_REPORT_REP_REF + report_offset) % 32));
      hogpd_env->svcs[svc_idx].nb_att += HIDS_REPORT_MANDATORY_ATT_NB;

      // copy default definition of the HID report database
      memcpy(&(hids_db[svc_idx][HOGPD_IDX_REPORT_CHAR + report_offset]),
             &(hids_att_db[HOGPD_IDX_REPORT_CHAR]),
             sizeof(struct attm_desc) * HIDS_REPORT_NB_IDX);

      // according to the report type, update value property
      switch (params->cfg[svc_idx].report_char_cfg[report_idx] &
              HOGPD_CFG_REPORT_FEAT) {
      // Input Report
      case HOGPD_CFG_REPORT_IN: {
        // add notification permission on report
        perm = PERM(RD, ENABLE) | PERM(NTF, ENABLE);
        // Report Char. supports NTF => Client Characteristic Configuration
        // Descriptor
        cfg_flag[svc_idx][(HOGPD_IDX_REPORT_NTF_CFG + report_offset) / 32] |=
            (1 << ((HOGPD_IDX_REPORT_NTF_CFG + report_offset) % 32));
        hogpd_env->svcs[svc_idx].nb_att += HIDS_REPORT_IN_ATT_NB;

        // update feature flag
        hogpd_env->svcs[svc_idx].features |=
            (HOGPD_CFG_REPORT_NTF_EN << report_idx);

        // check if attribute value could be written
        if ((params->cfg[svc_idx].report_char_cfg[report_idx] &
             HOGPD_CFG_REPORT_WR) == HOGPD_CFG_REPORT_WR) {
          perm |= PERM(WRITE_REQ, ENABLE);
        }
      } break;

      // Output Report
      case HOGPD_CFG_REPORT_OUT: {
        perm = PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE) |
               PERM(WRITE_COMMAND, ENABLE);
      } break;

      // Feature Report
      case HOGPD_CFG_REPORT_FEAT: {
        perm = PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE);
      } break;

      default: {
        status = PRF_ERR_INVALID_PARAM;
      } break;
      }

      hids_db[svc_idx][HOGPD_IDX_REPORT_VAL + (HIDS_REPORT_NB_IDX * report_idx)]
          .perm = perm;
    }

    // increment total number of attributes to allocate.
    tot_nb_att += hogpd_env->svcs[svc_idx].nb_att;
  }

  // Check that attribute list can be allocated.
  if (status == ATT_ERR_NO_ERROR) {
    status = attm_reserve_handle_range(start_hdl, tot_nb_att);
  }

  // used start handle calculated when handle range reservation has been
  // performed
  shdl = *start_hdl;
  hogpd_env->start_hdl = *start_hdl;
  hogpd_env->hids_nb = params->hids_nb;
  // Initialize the Report ID
  report_ref.report_id = 0;

  // allocate services
  for (svc_idx = 0;
       ((svc_idx < params->hids_nb) && (status == GAP_ERR_NO_ERROR));
       svc_idx++) {
    uint16_t handle;
    status = attm_svc_create_db(
        &shdl, ATT_SVC_HID, (uint8_t *)&(cfg_flag[svc_idx][0]), HOGPD_ATT_MAX,
        NULL, env->task, hids_db[svc_idx],
        (sec_lvl &
         (PERM_MASK_SVC_DIS | PERM_MASK_SVC_AUTH | PERM_MASK_SVC_EKS)));
    // update start handle for next service
    shdl += hogpd_env->svcs[svc_idx].nb_att;

    // Set HID Information Char. Value
    if (status == GAP_ERR_NO_ERROR) {
      handle =
          hogpd_get_att_handle(hogpd_env, svc_idx, HOGPD_IDX_HID_INFO_VAL, 0);
      ASSERT_ERR(handle != ATT_INVALID_HDL);
      status = attm_att_set_value(handle, sizeof(struct hids_hid_info), 0,
                                  (uint8_t *)&params->cfg[svc_idx].hid_info);
    }

    // Set Report Map Char. External Report Ref value
    if ((status == GAP_ERR_NO_ERROR) &&
        ((params->cfg[svc_idx].svc_features & HOGPD_CFG_MAP_EXT_REF) ==
         HOGPD_CFG_MAP_EXT_REF)) {
      handle = hogpd_get_att_handle(hogpd_env, svc_idx,
                                    HOGPD_IDX_REPORT_MAP_EXT_REP_REF, 0);
      ASSERT_ERR(handle != ATT_INVALID_HDL);
      status = attm_att_set_value(
          handle, sizeof(uint16_t), 0,
          (uint8_t *)&params->cfg[svc_idx].ext_ref.rep_ref_uuid);
    }

    // Set Report External Ref value
    for (report_idx = 0; (status == GAP_ERR_NO_ERROR) &&
                         (report_idx < params->cfg[svc_idx].report_nb);
         report_idx++) {
      // Save the Report ID
      report_ref.report_id = params->cfg[svc_idx].report_id[report_idx];
      // Set Report Type
      report_ref.report_type =
          (params->cfg[svc_idx].report_char_cfg[report_idx] &
           HOGPD_CFG_REPORT_FEAT);

      handle = hogpd_get_att_handle(hogpd_env, svc_idx,
                                    HOGPD_IDX_REPORT_REP_REF, report_idx);
      ASSERT_ERR(handle != ATT_INVALID_HDL);
      // Set value in the database
      status = attm_att_set_value(handle, sizeof(struct hids_report_ref), 0,
                                  (uint8_t *)&report_ref);
    }

    // by default in Report protocol mode.
    hogpd_env->svcs[svc_idx].proto_mode = HOGP_REPORT_PROTOCOL_MODE;
  }

  //-------------------- Update profile task information  ---------------------
  if (status == ATT_ERR_NO_ERROR) {
    // allocate HOGPD required environment variable
    env->env = (prf_env_t *)hogpd_env;
    hogpd_env->prf_env.app_task =
        app_task | (PERM_GET(sec_lvl, SVC_MI) ? PERM(PRF_MI, ENABLE)
                                              : PERM(PRF_MI, DISABLE));
    hogpd_env->prf_env.prf_task = env->task | PERM(PRF_MI, DISABLE);
    // initialize environment variable
    env->id = TASK_ID_HOGPD;
    hogpd_task_init(&(env->desc));

    // service is ready, go into an Idle state
    ke_state_set(env->task, HOGPD_IDLE);
  } else if (hogpd_env != NULL) {
    ke_free(hogpd_env);
  }

  // ensure that temporary allocated databases description is correctly free
  for (svc_idx = 0; svc_idx < HOGPD_NB_HIDS_INST_MAX; svc_idx++) {
    if (hids_db[svc_idx] != NULL) {
      ke_free(hids_db[svc_idx]);
    }
  }

  return (status);
}

/**
 ****************************************************************************************
 * @brief Destruction of the HOGPD 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 hogpd_destroy(struct prf_task_env *env) {
  struct hogpd_env_tag *hogpd_env = (struct hogpd_env_tag *)env->env;

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

/**
 ****************************************************************************************
 * @brief Handles Connection creation
 *
 * @param[in|out]    env        Collector or Service allocated environment data.
 * @param[in]        conidx     Connection index
 ****************************************************************************************
 */
static void hogpd_create(struct prf_task_env *env, uint8_t conidx) {
  /* Nothing to do */
}

/**
 ****************************************************************************************
 * @brief Handles Disconnection
 *
 * @param[in|out]    env        Collector or Service allocated environment data.
 * @param[in]        conidx     Connection index
 * @param[in]        reason     Detach reason
 ****************************************************************************************
 */
static void hogpd_cleanup(struct prf_task_env *env, uint8_t conidx,
                          uint8_t reason) {
  struct hogpd_env_tag *hogpd_env = (struct hogpd_env_tag *)env->env;
  uint8_t svc_idx;
  ASSERT_ERR(conidx < BLE_CONNECTION_MAX);

  // Reset the notification configuration to ensure that no notification will be
  // sent on a disconnected link
  for (svc_idx = 0; svc_idx < hogpd_env->hids_nb; svc_idx++) {
    hogpd_env->svcs[svc_idx].ntf_cfg[conidx] = 0;
  }
}

/*
 * GLOBAL VARIABLE DEFINITIONS
 ****************************************************************************************
 */

/// HOGPD Task interface required by profile manager
const struct prf_task_cbs hogpd_itf = {
    (prf_init_fnct)hogpd_init,
    hogpd_destroy,
    hogpd_create,
    hogpd_cleanup,
};

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

const struct prf_task_cbs *hogpd_prf_itf_get(void) { return &hogpd_itf; }

uint16_t hogpd_get_att_handle(struct hogpd_env_tag *hogpd_env, uint8_t svc_idx,
                              uint8_t att_idx, uint8_t report_idx) {
  uint16_t handle = ATT_INVALID_HDL;
  uint8_t i = 0;

  // Sanity check
  if ((svc_idx < hogpd_env->hids_nb) && (att_idx < HOGPD_IDX_NB) &&
      ((att_idx < HOGPD_ATT_UNIQ_NB) ||
       (report_idx < hogpd_env->svcs[svc_idx].nb_report))) {
    handle = hogpd_env->start_hdl;

    for (i = 0; i < svc_idx; i++) {
      // update start handle for next service - only useful if multiple service,
      // else not used.
      handle += hogpd_env->svcs[i].nb_att;
    }

    // increment index according to expected index
    if (att_idx < HOGPD_ATT_UNIQ_NB) {
      handle += att_idx;

      // check if Keyboard feature active
      if ((hogpd_env->svcs[svc_idx].features & HOGPD_CFG_KEYBOARD) == 0) {
        if (att_idx > HOGPD_IDX_BOOT_KB_OUT_REPORT_VAL) {
          handle -= HIDS_KEYBOARD_ATT_NB;
        }
        // Error Case
        else if ((att_idx >= HOGPD_IDX_BOOT_KB_IN_REPORT_CHAR) &&
                 (att_idx <= HOGPD_IDX_BOOT_KB_OUT_REPORT_VAL)) {
          handle = ATT_INVALID_HANDLE;
        }
      }

      // check if Mouse feature active
      if ((hogpd_env->svcs[svc_idx].features & HOGPD_CFG_MOUSE) == 0) {
        if (att_idx > HOGPD_IDX_BOOT_MOUSE_IN_REPORT_NTF_CFG) {
          handle -= HIDS_MOUSE_ATT_NB;
        }
        // Error Case
        else if ((att_idx >= HOGPD_IDX_BOOT_MOUSE_IN_REPORT_CHAR) &&
                 (att_idx <= HOGPD_IDX_BOOT_MOUSE_IN_REPORT_NTF_CFG)) {
          handle = ATT_INVALID_HANDLE;
        }
      }

      // check if Protocol Mode feature active
      if ((hogpd_env->svcs[svc_idx].features & HOGPD_CFG_PROTO_MODE) == 0) {
        if (att_idx > HOGPD_IDX_PROTO_MODE_VAL) {
          handle -= HIDS_PROTO_MODE_ATT_NB;
        }
        // Error Case
        else if ((att_idx >= HOGPD_IDX_PROTO_MODE_CHAR) &&
                 (att_idx <= HOGPD_IDX_PROTO_MODE_VAL)) {
          handle = ATT_INVALID_HANDLE;
        }
      }

      // check if Ext Ref feature active
      if ((hogpd_env->svcs[svc_idx].features & HOGPD_CFG_MAP_EXT_REF) == 0) {
        if (att_idx > HOGPD_IDX_REPORT_MAP_EXT_REP_REF) {
          handle -= HIDS_MAP_EXT_ATT_NB;
        } else if (att_idx > HOGPD_IDX_INCL_SVC) {
          handle -= 1;
        }
        // Error Case
        else if ((att_idx == HOGPD_IDX_INCL_SVC) ||
                 (att_idx == HOGPD_IDX_REPORT_MAP_EXT_REP_REF)) {
          handle = ATT_INVALID_HANDLE;
        }
      }
    } else {
      handle += hogpd_env->svcs[svc_idx].report_hdl_offset;

      // increment attribute handle with other reports
      for (i = 0; i < report_idx; i++) {
        handle += HIDS_REPORT_MANDATORY_ATT_NB;
        // check if it's a Report input
        if ((hogpd_env->svcs[svc_idx].features &
             (HOGPD_CFG_REPORT_NTF_EN << i)) != 0) {
          handle += HIDS_REPORT_IN_ATT_NB;
        }
      }

      // Error check
      if ((att_idx == HOGPD_IDX_REPORT_NTF_CFG) &&
          ((hogpd_env->svcs[svc_idx].features &
            (HOGPD_CFG_REPORT_NTF_EN << report_idx)) != 0)) {
        handle = ATT_INVALID_HANDLE;
      } else {
        // update handle cursor
        handle += att_idx - HOGPD_ATT_UNIQ_NB;
      }
    }
  }

  return handle;
}

uint8_t hogpd_get_att_idx(struct hogpd_env_tag *hogpd_env, uint16_t handle,
                          uint8_t *svc_idx, uint8_t *att_idx,
                          uint8_t *report_idx) {
  uint16_t hdl_cursor = hogpd_env->start_hdl;
  uint8_t status = PRF_APP_ERROR;

  // invalid index
  *att_idx = HOGPD_IDX_NB;

  // Browse list of services
  // handle must be greater than current index
  for (*svc_idx = 0; (*svc_idx < hogpd_env->hids_nb) && (handle >= hdl_cursor);
       (*svc_idx)++) {
    // check if handle is on current service
    if (handle >= (hdl_cursor + hogpd_env->svcs[*svc_idx].nb_att)) {
      hdl_cursor += hogpd_env->svcs[*svc_idx].nb_att;
      continue;
    }

    // if we are here, we are sure that handle is valid
    status = GAP_ERR_NO_ERROR;
    *report_idx = 0;

    // check if handle is in reports or not
    if (handle < (hdl_cursor + hogpd_env->svcs[*svc_idx].report_hdl_offset)) {
      if (handle == hdl_cursor) {
        *att_idx = HOGPD_IDX_SVC;
        break;
      }

      // check if Ext Ref feature active
      if ((hogpd_env->svcs[*svc_idx].features & HOGPD_CFG_MAP_EXT_REF) != 0) {
        hdl_cursor += 1;
        if (handle == hdl_cursor) {
          *att_idx = HOGPD_IDX_INCL_SVC;
          break;
        }
      }

      // check if handle is in mandatory range
      hdl_cursor += HIDS_MANDATORY_ATT_NB;
      if (handle <= hdl_cursor) {
        *att_idx = HOGPD_IDX_REPORT_MAP_VAL - (hdl_cursor - handle - 1);
        break;
      }

      // check if Ext Ref feature active
      if ((hogpd_env->svcs[*svc_idx].features & HOGPD_CFG_MAP_EXT_REF) != 0) {
        hdl_cursor += 1;
        if (handle == hdl_cursor) {
          *att_idx = HOGPD_IDX_REPORT_MAP_EXT_REP_REF;
          break;
        }
      }

      // check if Protocol Mode feature active
      if ((hogpd_env->svcs[*svc_idx].features & HOGPD_CFG_PROTO_MODE) != 0) {
        hdl_cursor += HIDS_PROTO_MODE_ATT_NB;
        if (handle <= hdl_cursor) {
          *att_idx = HOGPD_IDX_PROTO_MODE_VAL - (hdl_cursor - handle - 1);
          break;
        }
      }

      // check if Keyboard feature active
      if ((hogpd_env->svcs[*svc_idx].features & HOGPD_CFG_KEYBOARD) != 0) {
        hdl_cursor += HIDS_KEYBOARD_ATT_NB;
        if (handle <= hdl_cursor) {
          *att_idx =
              HOGPD_IDX_BOOT_KB_OUT_REPORT_VAL - (hdl_cursor - handle - 1);
          break;
        }
      }

      // check if Mouse feature active
      if ((hogpd_env->svcs[*svc_idx].features & HOGPD_CFG_MOUSE) != 0) {
        hdl_cursor += HIDS_MOUSE_ATT_NB;
        if (handle <= hdl_cursor) {
          *att_idx = HOGPD_IDX_BOOT_MOUSE_IN_REPORT_NTF_CFG -
                     (hdl_cursor - handle - 1);
          break;
        }
      }

      // not expected
      ASSERT_ERR(0);
    } else {
      // add handle offset
      hdl_cursor += hogpd_env->svcs[*svc_idx].report_hdl_offset;

      for (*report_idx = 0; (*report_idx < hogpd_env->svcs[*svc_idx].nb_report);
           (*report_idx)++) {
        hdl_cursor += HIDS_REPORT_MANDATORY_ATT_NB;
        if (handle <= hdl_cursor) {
          *att_idx = HOGPD_IDX_REPORT_REP_REF - (hdl_cursor - handle - 1);
          break;
        }

        if ((hogpd_env->svcs[*svc_idx].features &
             (HOGPD_CFG_REPORT_NTF_EN << *report_idx)) != 0) {
          hdl_cursor += HIDS_REPORT_IN_ATT_NB;
          if (handle == hdl_cursor) {
            *att_idx = HOGPD_IDX_REPORT_NTF_CFG;
            break;
          }
        }
      }

      // not expected
      ASSERT_ERR(*att_idx != HOGPD_IDX_NB);
    }
    // loop not expected here
    break;
  }

  return (status);
}

uint8_t hogpd_ntf_send(uint8_t conidx, const struct hogpd_report_info *report) {
  struct hogpd_env_tag *hogpd_env = PRF_ENV_GET(HOGPD, hogpd);
  uint8_t status = GAP_ERR_NO_ERROR;
  uint16_t handle = ATT_INVALID_HANDLE;
  uint8_t att_idx = HOGPD_IDX_NB;
  uint16_t max_report_len = 0;
  uint16_t feature_mask = 0;
  uint8_t exp_prot_mode = 0;

  // According to the report type retrieve:
  // - Attribute index
  // - Attribute max length
  // - Feature to use
  // - Expected protocol mode
  switch (report->type) {
  // An Input Report
  case HOGPD_REPORT: {
    att_idx = HOGPD_IDX_REPORT_VAL;
    max_report_len = HOGPD_REPORT_MAX_LEN;
    feature_mask = HOGPD_CFG_REPORT_NTF_EN << report->idx;
    exp_prot_mode = HOGP_REPORT_PROTOCOL_MODE;
  } break;
  // Boot Keyboard input report
  case HOGPD_BOOT_KEYBOARD_INPUT_REPORT: {
    att_idx = HOGPD_IDX_BOOT_KB_IN_REPORT_VAL;
    max_report_len = HOGPD_BOOT_REPORT_MAX_LEN;
    feature_mask = HOGPD_CFG_KEYBOARD;
    exp_prot_mode = HOGP_BOOT_PROTOCOL_MODE;
  } break;
  // Boot Mouse input report
  case HOGPD_BOOT_MOUSE_INPUT_REPORT: {
    att_idx = HOGPD_IDX_BOOT_MOUSE_IN_REPORT_VAL;
    max_report_len = HOGPD_BOOT_REPORT_MAX_LEN;
    feature_mask = HOGPD_CFG_MOUSE;
    exp_prot_mode = HOGP_BOOT_PROTOCOL_MODE;
  } break;

  default: /* Nothing to do */
    break;
  }

  handle =
      hogpd_get_att_handle(hogpd_env, report->hid_idx, att_idx, report->idx);

  // check if attribute is found
  if (handle == ATT_INVALID_HANDLE) {
    // check if it's an unsupported feature
    if ((feature_mask != 0) && (report->hid_idx < hogpd_env->hids_nb) &&
        (report->idx < hogpd_env->svcs[report->hid_idx].nb_report) &&
        ((hogpd_env->svcs[report->hid_idx].features & feature_mask) == 0)) {
      status = PRF_ERR_FEATURE_NOT_SUPPORTED;
    }
    // or an invalid param
    else {
      status = PRF_ERR_INVALID_PARAM;
    }
  }
  // check if length is valid
  else if (report->length > max_report_len) {
    status = PRF_ERR_UNEXPECTED_LEN;
  }
  // check if notification is enabled
  else if ((hogpd_env->svcs[report->hid_idx].ntf_cfg[conidx] & feature_mask) ==
           0) {
    status = PRF_ERR_NTF_DISABLED;
  }
  // check if protocol mode is valid
  else if ((hogpd_env->svcs[report->hid_idx].proto_mode != exp_prot_mode) &&
           ((hogpd_env->svcs[report->hid_idx].features &
             HOGPD_CFG_PROTO_MODE) != 0)) {
    status = PRF_ERR_REQ_DISALLOWED;
  }

  if (status == GAP_ERR_NO_ERROR) {
    // Allocate the GATT notification message
    struct gattc_send_evt_cmd *report_ntf =
        KE_MSG_ALLOC_DYN(GATTC_SEND_EVT_CMD, KE_BUILD_ID(TASK_GATTC, conidx),
                         prf_src_task_get(&(hogpd_env->prf_env), conidx),
                         gattc_send_evt_cmd, report->length);

    // Fill in the parameter structure
    report_ntf->operation = GATTC_NOTIFY;
    report_ntf->handle = handle;
    // pack measured value in database
    report_ntf->length = report->length;
    memcpy(report_ntf->value, report->value, report->length);
    // send notification to peer device
    ke_msg_send(report_ntf);
  }

  return (status);
}

uint8_t hogpd_ntf_cfg_ind_send(uint8_t conidx, uint8_t svc_idx, uint8_t att_idx,
                               uint8_t report_idx, uint16_t ntf_cfg) {
  // Status
  uint8_t status = GAP_ERR_NO_ERROR;
  uint16_t mask = 0;

  struct hogpd_env_tag *hogpd_env = PRF_ENV_GET(HOGPD, hogpd);

  // set notification config update mask
  switch (att_idx) {
  // An Input Report
  case HOGPD_IDX_REPORT_NTF_CFG: {
    mask = HOGPD_CFG_REPORT_NTF_EN << report_idx;
  } break;
  // Boot Keyboard input report
  case HOGPD_IDX_BOOT_KB_IN_REPORT_NTF_CFG: {
    mask = HOGPD_CFG_KEYBOARD;
  } break;
  // Boot Mouse input report
  case HOGPD_IDX_BOOT_MOUSE_IN_REPORT_NTF_CFG: {
    mask = HOGPD_CFG_MOUSE;
  } break;

  default: {
    ASSERT_ERR(0);
  } break;
  }

  // Stop notification
  if (ntf_cfg == PRF_CLI_STOP_NTFIND) {
    hogpd_env->svcs[svc_idx].ntf_cfg[conidx] &= ~mask;
  }
  // Start notification
  else if (ntf_cfg == PRF_CLI_START_NTF) {
    hogpd_env->svcs[svc_idx].ntf_cfg[conidx] |= mask;
  }
  // Provided value is incorrect
  else {
    status = PRF_APP_ERROR;
  }

  // inform application about updated notification configuration
  if (status == GAP_ERR_NO_ERROR) {
    // Allocate the GATT notification message
    struct hogpd_ntf_cfg_ind *ntf_cfg_ind = KE_MSG_ALLOC(
        HOGPD_NTF_CFG_IND, prf_dst_task_get(&(hogpd_env->prf_env), conidx),
        prf_src_task_get(&(hogpd_env->prf_env), conidx), hogpd_ntf_cfg_ind);

    // Fill in the parameter structure
    ntf_cfg_ind->conidx = conidx;

    for (svc_idx = 0; svc_idx < HOGPD_NB_HIDS_INST_MAX; svc_idx++) {
      ntf_cfg_ind->ntf_cfg[svc_idx] = hogpd_env->svcs[svc_idx].ntf_cfg[conidx];
    }

    // send indication to application
    ke_msg_send(ntf_cfg_ind);
  }

  return (status);
}

#endif /* BLE_HID_DEVICE */

/// @} HOGPD
