/**
 ****************************************************************************************
 * @addtogroup BASS
 * @{
 ****************************************************************************************
 */

/*
 * INCLUDE FILES
 ****************************************************************************************
 */

#include "rwip_config.h"

#if (BLE_BATT_SERVER)
#include "bass.h"
#include "bass_task.h"
#include "prf.h"
#include "prf_utils.h"

#include "ke_mem.h"

/*
 * BAS ATTRIBUTES DEFINITION
 ****************************************************************************************
 */

/// Full BAS Database Description - Used to add attributes into the database
const struct attm_desc bas_att_db[BAS_IDX_NB] = {
    // Battery Service Declaration
    [BAS_IDX_SVC] = {ATT_DECL_PRIMARY_SERVICE, PERM(RD, ENABLE), 0, 0},

    // Battery Level Characteristic Declaration
    [BAS_IDX_BATT_LVL_CHAR] = {ATT_DECL_CHARACTERISTIC, PERM(RD, ENABLE), 0, 0},
    // Battery Level Characteristic Value
    [BAS_IDX_BATT_LVL_VAL] = {ATT_CHAR_BATTERY_LEVEL, PERM(RD, ENABLE),
                              PERM(RI, ENABLE), 0},
    // Battery Level Characteristic - Client Characteristic Configuration
    // Descriptor
    [BAS_IDX_BATT_LVL_NTF_CFG] = {ATT_DESC_CLIENT_CHAR_CFG,
                                  PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE), 0,
                                  0},
    // Battery Level Characteristic - Characteristic Presentation Format
    // Descriptor
    [BAS_IDX_BATT_LVL_PRES_FMT] = {ATT_DESC_CHAR_PRES_FORMAT, PERM(RD, ENABLE),
                                   PERM(RI, ENABLE), 0},
};

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

/**
 ****************************************************************************************
 * @brief Initialization of the BASS 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 bass_init(struct prf_task_env *env, uint16_t *start_hdl,
                         uint16_t app_task, uint8_t sec_lvl,
                         struct bass_db_cfg *params) {
  uint16_t shdl[BASS_NB_BAS_INSTANCES_MAX];
  struct bass_env_tag *bass_env = NULL;
  // Status
  uint8_t status = GAP_ERR_NO_ERROR;
  // Counter
  uint8_t i;

  // Check number of BAS instances
  if ((params->bas_nb > 0) && (params->bas_nb <= BASS_NB_BAS_INSTANCES_MAX)) {
    //-------------------- allocate memory required for the profile
    //---------------------
    bass_env = (struct bass_env_tag *)ke_malloc(sizeof(struct bass_env_tag),
                                                KE_MEM_ATT_DB);
    memset(bass_env, 0, sizeof(struct bass_env_tag));

    // Save number of BAS
    bass_env->svc_nb = params->bas_nb;

    for (i = 0; ((i < params->bas_nb) && (status == GAP_ERR_NO_ERROR)); i++) {
      // Service content flag
      uint8_t cfg_flag = BAS_CFG_FLAG_MANDATORY_MASK;

      // Save database configuration
      bass_env->features |= (params->features[i]) << i;
      bass_env->batt_level_pres_format[i] = params->batt_level_pres_format[i];

      // Check if notifications are supported
      if (params->features[i] == BAS_BATT_LVL_NTF_SUP) {
        cfg_flag |= BAS_CFG_FLAG_NTF_SUP_MASK;
      }

      // Check if multiple instances
      if (bass_env->svc_nb > 1) {
        cfg_flag |= BAS_CFG_FLAG_MTP_BAS_MASK;
      }

      shdl[i] = *start_hdl;

      // Create BAS in the DB
      //------------------ create the attribute database for the profile
      //-------------------
      status = attm_svc_create_db(
          &(shdl[i]), ATT_SVC_BATTERY_SERVICE, (uint8_t *)&cfg_flag, BAS_IDX_NB,
          NULL, env->task, bas_att_db,
          (sec_lvl &
           (PERM_MASK_SVC_DIS | PERM_MASK_SVC_AUTH | PERM_MASK_SVC_EKS)));

      // Set optional permissions
      if (status == GAP_ERR_NO_ERROR) {
        // update start handle for next service - only useful if multiple
        // service, else not used. 4 characteristics + optional notification
        // characteristic.
        *start_hdl = shdl[i] + BAS_IDX_NB -
                     ((params->features[i] == BAS_BATT_LVL_NTF_SUP) ? 0 : 1);

        // Set optional permissions
        if (params->features[i] == BAS_BATT_LVL_NTF_SUP) {
          // Battery Level characteristic value permissions
          uint16_t perm = PERM(RD, ENABLE) | PERM(NTF, ENABLE);

          attm_att_set_permission(shdl[i] + BAS_IDX_BATT_LVL_VAL, perm, 0);
        }
      }

      // Reset configuration flag
      cfg_flag = BAS_CFG_FLAG_MANDATORY_MASK;
    }
  } else {
    status = PRF_ERR_INVALID_PARAM;
  }

  //-------------------- Update profile task information  ---------------------
  if (status == ATT_ERR_NO_ERROR) {

    // allocate BASS required environment variable
    env->env = (prf_env_t *)bass_env;
    *start_hdl = shdl[0];
    bass_env->start_hdl = *start_hdl;
    bass_env->prf_env.app_task =
        app_task | (PERM_GET(sec_lvl, SVC_MI) ? PERM(PRF_MI, ENABLE)
                                              : PERM(PRF_MI, DISABLE));
    bass_env->prf_env.prf_task = env->task | PERM(PRF_MI, DISABLE);

    // initialize environment variable
    env->id = TASK_ID_BASS;
    bass_task_init(&(env->desc));

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

  return (status);
}
/**
 ****************************************************************************************
 * @brief Destruction of the BASS 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 bass_destroy(struct prf_task_env *env) {
  struct bass_env_tag *bass_env = (struct bass_env_tag *)env->env;

  // clear on-going operation
  if (bass_env->operation != NULL) {
    ke_free(bass_env->operation);
  }

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

/**
 ****************************************************************************************
 * @brief Handles Connection creation
 *
 * @param[in|out]    env        Collector or Service allocated environment data.
 * @param[in]        conidx     Connection index
 ****************************************************************************************
 */
static void bass_create(struct prf_task_env *env, uint8_t conidx) {
  struct bass_env_tag *bass_env = (struct bass_env_tag *)env->env;
  ASSERT_ERR(conidx < BLE_CONNECTION_MAX);

  // force notification config to zero when peer device is connected
  bass_env->ntf_cfg[conidx] = 0;
}

/**
 ****************************************************************************************
 * @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 bass_cleanup(struct prf_task_env *env, uint8_t conidx,
                         uint8_t reason) {
  struct bass_env_tag *bass_env = (struct bass_env_tag *)env->env;

  ASSERT_ERR(conidx < BLE_CONNECTION_MAX);
  // force notification config to zero when peer device is disconnected
  bass_env->ntf_cfg[conidx] = 0;
}

/**
 ****************************************************************************************
 * @brief  Trigger battery level notification
 *
 * @param bass_env profile environment
 * @param conidx   peer destination connection index
 * @param svc_idx  Service index
 ****************************************************************************************
 */
static void bass_notify_batt_lvl(struct bass_env_tag *bass_env, uint8_t conidx,
                                 uint8_t svc_idx) {
  // Allocate the GATT notification message
  struct gattc_send_evt_cmd *batt_lvl =
      KE_MSG_ALLOC_DYN(GATTC_SEND_EVT_CMD, KE_BUILD_ID(TASK_GATTC, conidx),
                       prf_src_task_get(&(bass_env->prf_env), 0),
                       gattc_send_evt_cmd, sizeof(uint8_t));

  // Fill in the parameter structure
  batt_lvl->operation = GATTC_NOTIFY;
  batt_lvl->handle = bass_get_att_handle(svc_idx, BAS_IDX_BATT_LVL_VAL);
  // pack measured value in database
  batt_lvl->length = sizeof(uint8_t);
  batt_lvl->value[0] = bass_env->batt_lvl[svc_idx];

  // send notification to peer device
  ke_msg_send(batt_lvl);
}

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

/// BASS Task interface required by profile manager
const struct prf_task_cbs bass_itf = {
    (prf_init_fnct)bass_init,
    bass_destroy,
    bass_create,
    bass_cleanup,
};

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

const struct prf_task_cbs *bass_prf_itf_get(void) { return &bass_itf; }

uint16_t bass_get_att_handle(uint8_t svc_idx, uint8_t att_idx) {
  struct bass_env_tag *bass_env = PRF_ENV_GET(BASS, bass);
  uint16_t handle = ATT_INVALID_HDL;
  uint8_t i = 0;

  if (svc_idx < bass_env->svc_nb) {
    handle = bass_env->start_hdl;

    for (i = 0; i < svc_idx; i++) {
      // update start handle for next service - only useful if multiple service,
      // else not used. 4 characteristics + optional notification
      // characteristic.
      handle +=
          BAS_IDX_NB -
          ((((bass_env->features >> i) & 0x01) == BAS_BATT_LVL_NTF_SUP) ? 0
                                                                        : 1);
    }

    // increment index according to expected index
    if (att_idx < BAS_IDX_BATT_LVL_NTF_CFG) {
      handle += att_idx;
    }
    // Battery notification
    else if ((att_idx == BAS_IDX_BATT_LVL_NTF_CFG) &&
             (((bass_env->features >> i) & 0x01) == BAS_BATT_LVL_NTF_SUP)) {
      handle += BAS_IDX_BATT_LVL_NTF_CFG;
    }
    // Battery Level format
    else if ((att_idx == BAS_IDX_BATT_LVL_PRES_FMT) && (bass_env->svc_nb > 1)) {
      handle +=
          BAS_IDX_BATT_LVL_PRES_FMT -
          ((((bass_env->features >> i) & 0x01) == BAS_BATT_LVL_NTF_SUP) ? 0
                                                                        : 1);
    } else {
      handle = ATT_INVALID_HDL;
    }
  }

  return handle;
}

uint8_t bass_get_att_idx(uint16_t handle, uint8_t *svc_idx, uint8_t *att_idx) {
  struct bass_env_tag *bass_env = PRF_ENV_GET(BASS, bass);
  uint16_t hdl_cursor = bass_env->start_hdl;
  uint8_t status = PRF_APP_ERROR;

  // Browse list of services
  // handle must be greater than current index
  for (*svc_idx = 0; (*svc_idx < bass_env->svc_nb) && (handle >= hdl_cursor);
       (*svc_idx)++) {
    // check if it's a mandatory index
    if (handle <= (hdl_cursor + BAS_IDX_BATT_LVL_VAL)) {
      *att_idx = handle - hdl_cursor;
      status = GAP_ERR_NO_ERROR;
      break;
    }

    hdl_cursor += BAS_IDX_BATT_LVL_VAL;

    // check if it's a notify index
    if (((bass_env->features >> *svc_idx) & 0x01) == BAS_BATT_LVL_NTF_SUP) {
      hdl_cursor++;
      if (handle == hdl_cursor) {
        *att_idx = BAS_IDX_BATT_LVL_NTF_CFG;
        status = GAP_ERR_NO_ERROR;
        break;
      }
    }

    // check if it's battery level format
    if (bass_env->svc_nb > 1) {
      hdl_cursor++;
      if (handle == hdl_cursor) {
        *att_idx = BAS_IDX_BATT_LVL_PRES_FMT;
        status = GAP_ERR_NO_ERROR;
        break;
      }
    }

    hdl_cursor++;
  }

  return (status);
}

void bass_exe_operation(void) {
  struct bass_env_tag *bass_env = PRF_ENV_GET(BASS, bass);
  ASSERT_ERR(bass_env->operation != NULL);
  bool finished = true;
  uint8_t conidx = GAP_INVALID_CONIDX;

  // Restoring connection information requested
  if (bass_env->operation->id == BASS_ENABLE_REQ) {
    struct bass_enable_req *enable =
        (struct bass_enable_req *)ke_msg2param(bass_env->operation);
    conidx = enable->conidx;
    // loop on all services to check if notification should be triggered
    while (bass_env->cursor < BASS_NB_BAS_INSTANCES_MAX) {
      if (((bass_env->ntf_cfg[enable->conidx] & (1 << bass_env->cursor)) !=
           0) &&
          (enable->old_batt_lvl[bass_env->cursor] !=
           bass_env->batt_lvl[bass_env->cursor])) {
        // trigger notification
        bass_notify_batt_lvl(bass_env, enable->conidx, bass_env->cursor);

        finished = false;
        bass_env->cursor++;
        break;
      }
      bass_env->cursor++;
    }
  }
  // Battery level updated
  else if (bass_env->operation->id == BASS_BATT_LEVEL_UPD_REQ) {
    struct bass_batt_level_upd_req *update =
        (struct bass_batt_level_upd_req *)ke_msg2param(bass_env->operation);

    // loop on all connection
    while (bass_env->cursor < BLE_CONNECTION_MAX) {
      if ((bass_env->ntf_cfg[bass_env->cursor] & (1 << update->bas_instance)) !=
          0) {
        // trigger notification
        bass_notify_batt_lvl(bass_env, bass_env->cursor, update->bas_instance);
        finished = false;
        bass_env->cursor++;
        break;
      }
      bass_env->cursor++;
    }

  }
  // default, should not happen
  else {
    ASSERT_ERR(0);
  }

  // check if operation is finished
  if (finished) {
    // trigger response message
    if (bass_env->operation->id == BASS_ENABLE_REQ) {
      struct bass_enable_rsp *rsp =
          KE_MSG_ALLOC(BASS_ENABLE_RSP, bass_env->operation->src_id,
                       bass_env->operation->dest_id, bass_enable_rsp);

      rsp->conidx = conidx;
      rsp->status = GAP_ERR_NO_ERROR;
      ke_msg_send(rsp);
    } else if (bass_env->operation->id == BASS_BATT_LEVEL_UPD_REQ) {
      struct bass_batt_level_upd_rsp *rsp =
          KE_MSG_ALLOC(BASS_BATT_LEVEL_UPD_RSP, bass_env->operation->src_id,
                       bass_env->operation->dest_id, bass_batt_level_upd_rsp);

      rsp->status = GAP_ERR_NO_ERROR;
      ke_msg_send(rsp);
    }

    // free operation
    ke_free(bass_env->operation);
    bass_env->operation = NULL;
    // go back to idle state
    ke_state_set(prf_src_task_get(&(bass_env->prf_env), 0), BASS_IDLE);
  }
}

#endif // (BLE_BATT_SERVER)

/// @} BASS
