/**
 ****************************************************************************************
 * @addtogroup ANPSTASK
 * @{
 ****************************************************************************************
 */

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

#include "rwip_config.h"

#if (BLE_AN_SERVER)

#include "anps.h"
#include "anps_task.h"
#include "gapm.h"
#include "gattc_task.h"

#include "prf_types.h"
#include "prf_utils.h"

#include "co_utils.h"

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

/**
 ****************************************************************************************
 * @brief Handles reception of the @ref ANPS_ENABLE_REQ message.
 * @param[in] msgid Id of the message received.
 * @param[in] param Pointer to the parameters of the message.
 * @param[in] dest_id ID of the receiving task instance.
 * @param[in] src_id ID of the sending task instance.
 * @return If the message was consumed or not.
 ****************************************************************************************
 */
__STATIC int anps_enable_req_handler(ke_msg_id_t const msgid,
                                     struct anps_enable_req *param,
                                     ke_task_id_t const dest_id,
                                     ke_task_id_t const src_id) {
  // Alert Notification Profile Server Role Task Index Environment
  struct anps_env_tag *anps_env = PRF_ENV_GET(ANPS, anps);
  uint8_t conidx = KE_IDX_GET(src_id);
  // Status
  uint8_t status = PRF_ERR_REQ_DISALLOWED;

  if (ke_state_get(dest_id) == ANPS_IDLE) {
    if (anps_env->env[conidx] != NULL) {
      // Bonded data was not used before
      if (!ANPS_IS_ALERT_ENABLED(conidx, anps_env,
                                 ANPS_FLAG_CFG_PERFORMED_OK)) {
        status = GAP_ERR_NO_ERROR;
        // Update the state in the environment
        if (param->new_alert_ntf_cfg != PRF_CLI_STOP_NTFIND) {
          // Force to PRF_CLI_START_NTF
          param->new_alert_ntf_cfg = PRF_CLI_START_NTF;

          ANPS_ENABLE_ALERT(conidx, anps_env, ANP_NEW_ALERT);
        }

        if (param->unread_alert_status_ntf_cfg != PRF_CLI_STOP_NTFIND) {
          // Force to PRF_CLI_START_NTF
          param->unread_alert_status_ntf_cfg = PRF_CLI_START_NTF;

          ANPS_ENABLE_ALERT(conidx, anps_env, ANP_UNREAD_ALERT);
        }
      }
      // Enable Bonded Data
      ANPS_ENABLE_ALERT(conidx, anps_env, ANPS_FLAG_CFG_PERFORMED_OK);
    }
  }

  // send completed information to APP task that contains error status
  struct anps_enable_rsp *cmp_evt =
      KE_MSG_ALLOC(ANPS_ENABLE_RSP, src_id, dest_id, anps_enable_rsp);
  cmp_evt->status = status;
  ke_msg_send(cmp_evt);

  return (KE_MSG_CONSUMED);
}

/**
 ****************************************************************************************
 * @brief Handles reception of the @ref ANPS_NTF_ALERT_CMD message.
 * @param[in] msgid Id of the message received.
 * @param[in] param Pointer to the parameters of the message.
 * @param[in] dest_id ID of the receiving task instance.
 * @param[in] src_id ID of the sending task instance.
 * @return If the message was consumed or not.
 ****************************************************************************************
 */
__STATIC int anps_ntf_alert_cmd_handler(ke_msg_id_t const msgid,
                                        struct anps_ntf_alert_cmd const *param,
                                        ke_task_id_t const dest_id,
                                        ke_task_id_t const src_id) {
  // Status
  uint8_t status = PRF_ERR_INVALID_PARAM;
  // Message Status
  uint8_t msg_status = KE_MSG_CONSUMED;
  // Task environment
  struct anps_env_tag *anps_env = PRF_ENV_GET(ANPS, anps);
  uint8_t conidx = KE_IDX_GET(dest_id);
  uint8_t state = ke_state_get(dest_id);

  if (state == ANPS_IDLE) {
    // Category ID
    uint8_t cat_id;
    // Is the category supported ?
    bool cat_supported;
    // Handle
    uint16_t handle = ATT_ERR_INVALID_HANDLE;

    ASSERT_ERR(anps_env != NULL);
    ASSERT_ERR(anps_env->env[conidx] != NULL);

    do {
      // Check the operation code, get the category ID.
      if (param->operation == ANPS_UPD_NEW_ALERT_OP_CODE) {
        // Check the length of the string info value
        if (param->value.new_alert.info_str_len >
            ANS_NEW_ALERT_STRING_INFO_MAX_LEN) {
          status = PRF_ERR_INVALID_PARAM;
          break;
        }

        // Get the category ID
        cat_id = param->value.new_alert.cat_id;
        // Check if the category is supported
        cat_supported = ANPS_IS_NEW_ALERT_CATEGORY_SUPPORTED(cat_id);
      } else if (param->operation == ANPS_UPD_UNREAD_ALERT_STATUS_OP_CODE) {
        // Get the category ID
        cat_id = param->value.unread_alert_status.cat_id;
        // Check if the category is supported
        cat_supported = ANPS_IS_UNREAD_ALERT_CATEGORY_SUPPORTED(cat_id);
      } else {
        status = PRF_ERR_INVALID_PARAM;
        break;
      }

      // Check the category ID
      if ((cat_id >= CAT_ID_NB) || (!cat_supported)) {
        status = PRF_ERR_INVALID_PARAM;
        break;
      }

      if (param->operation == ANPS_UPD_NEW_ALERT_OP_CODE) {
        // Check if sending of notification is enabled for the provided category
        if (ANPS_IS_NEW_ALERT_CATEGORY_ENABLED(conidx, cat_id, anps_env) &&
            ANPS_IS_ALERT_ENABLED(conidx, anps_env, ANP_NEW_ALERT)) {
          handle = anps_env->shdl + ANS_IDX_NEW_ALERT_VAL;

          // Allocate the GATT notification message
          struct gattc_send_evt_cmd *ntf = KE_MSG_ALLOC_DYN(
              GATTC_SEND_EVT_CMD, KE_BUILD_ID(TASK_GATTC, conidx), dest_id,
              gattc_send_evt_cmd, 2 + param->value.new_alert.info_str_len);

          // Fill in the parameter structure
          ntf->operation = GATTC_NOTIFY;
          ntf->handle = handle;
          // pack measured value in database
          ntf->length = 2 + param->value.new_alert.info_str_len;
          // Fill data
          memcpy(&ntf->value, &param->value.new_alert.cat_id, ntf->length);

          // The notification can be sent, send the notification
          status = GAP_ERR_NO_ERROR;
          ke_msg_send(ntf);
        } else {
          status = PRF_ERR_NTF_DISABLED;
          break;
        }
      } else {
        if (ANPS_IS_UNREAD_ALERT_CATEGORY_ENABLED(conidx, cat_id, anps_env) &&
            ANPS_IS_ALERT_ENABLED(conidx, anps_env, ANP_UNREAD_ALERT)) {
          handle = anps_env->shdl + ANS_IDX_UNREAD_ALERT_STATUS_VAL;

          // Allocate the GATT notification message
          struct gattc_send_evt_cmd *ntf = KE_MSG_ALLOC_DYN(
              GATTC_SEND_EVT_CMD, KE_BUILD_ID(TASK_GATTC, conidx), dest_id,
              gattc_send_evt_cmd, sizeof(struct anp_unread_alert));

          // Fill in the parameter structure
          ntf->operation = GATTC_NOTIFY;
          ntf->handle = handle;
          // pack measured value in database
          ntf->length = sizeof(struct anp_unread_alert);
          // Fill data
          memcpy(&ntf->value, &param->value.unread_alert_status, ntf->length);

          // The notification can be sent, send the notification
          status = GAP_ERR_NO_ERROR;
          ke_msg_send(ntf);
        } else {
          status = PRF_ERR_NTF_DISABLED;
          break;
        }
      }

      // Configure the environment
      anps_env->operation = param->operation;
      // Go to Busy state
      ke_state_set(dest_id, ANPS_BUSY);
    } while (0);
  } else if (state == ANPS_BUSY) {
    // Save it for later
    msg_status = KE_MSG_SAVED;
    status = GAP_ERR_NO_ERROR;
  }
  // no connection
  else {
    status = GAP_ERR_DISCONNECTED;
  }

  if (status != GAP_ERR_NO_ERROR) {
    // Send the message to the application
    anps_send_cmp_evt(dest_id, src_id, param->operation, status);
  }

  return (int)msg_status;
}

/**
 ****************************************************************************************
 * @brief Handles @ref GATTC_CMP_EVT message meaning that a notification or an
 *indication has been correctly sent to peer device (but not confirmed by peer
 *device).
 *
 * @param[in] msgid     Id of the message received.
 * @param[in] param     Pointer to the parameters of the message.
 * @param[in] dest_id   ID of the receiving task instance
 * @param[in] src_id    ID of the sending task instance.
 * @return If the message was consumed or not.
 ****************************************************************************************
 */
__STATIC int gattc_cmp_evt_handler(ke_msg_id_t const msgid,
                                   struct gattc_cmp_evt const *param,
                                   ke_task_id_t const dest_id,
                                   ke_task_id_t const src_id) {
  // Get the address of the environment
  struct anps_env_tag *anps_env = PRF_ENV_GET(ANPS, anps);
  uint8_t conidx = KE_IDX_GET(src_id);

  if (anps_env != NULL) {
    // Send a complete event status to the application
    anps_send_cmp_evt(prf_src_task_get(&anps_env->prf_env, conidx),
                      prf_dst_task_get(&anps_env->prf_env, conidx),
                      anps_env->operation, param->status);
  }
  // else ignore the message

  return (KE_MSG_CONSUMED);
}

/**
 ****************************************************************************************
 * @brief Handles reception of the read request from peer device
 *
 * @param[in] msgid Id of the message received (probably unused).
 * @param[in] param Pointer to the parameters of the message.
 * @param[in] dest_id ID of the receiving task instance (probably unused).
 * @param[in] src_id ID of the sending task instance.
 * @return If the message was consumed or not.
 ****************************************************************************************
 */
__STATIC int gattc_read_req_ind_handler(ke_msg_id_t const msgid,
                                        struct gattc_read_req_ind const *param,
                                        ke_task_id_t const dest_id,
                                        ke_task_id_t const src_id) {
  if (ke_state_get(dest_id) == ANPS_IDLE) {
    // Get the address of the environment
    struct anps_env_tag *anps_env = PRF_ENV_GET(ANPS, anps);
    uint8_t conidx = KE_IDX_GET(src_id);
    uint8_t att_idx = param->handle - anps_env->shdl;

    uint8_t value[2];
    uint8_t value_size = 0;
    uint8_t status = ATT_ERR_NO_ERROR;

    switch (att_idx) {
    case ANS_IDX_SUPP_NEW_ALERT_CAT_VAL: {
      // Fill data
      value_size = sizeof(uint16_t);
      co_write16p(value, anps_env->supp_new_alert_cat);
    } break;

    case ANS_IDX_NEW_ALERT_CFG: {
      // Fill data
      value_size = sizeof(uint16_t);
      co_write16p(value,
                  ANPS_IS_ALERT_ENABLED(conidx, anps_env, ANP_NEW_ALERT));
    } break;

    case ANS_IDX_SUPP_UNREAD_ALERT_CAT_VAL: {
      // Fill data
      value_size = sizeof(uint16_t);
      co_write16p(value, anps_env->supp_unread_alert_cat);
    } break;

    case ANS_IDX_UNREAD_ALERT_STATUS_CFG: {
      // Fill data
      value_size = sizeof(uint16_t);
      co_write16p(value,
                  ANPS_IS_ALERT_ENABLED(conidx, anps_env, ANP_UNREAD_ALERT));
    } break;

    default: {
      status = ATT_ERR_REQUEST_NOT_SUPPORTED;
    } break;
    }

    // Send data to peer device
    struct gattc_read_cfm *cfm = KE_MSG_ALLOC_DYN(
        GATTC_READ_CFM, src_id, dest_id, gattc_read_cfm, value_size);
    cfm->length = value_size;
    memcpy(cfm->value, value, value_size);
    cfm->handle = param->handle;
    cfm->status = status;

    // Send value to peer device.
    ke_msg_send(cfm);
  }

  return (KE_MSG_CONSUMED);
}

/**
 ****************************************************************************************
 * @brief Handles reception of the @ref GATTC_WRITE_REQ_IND message.
 * @param[in] msgid Id of the message received (probably unused).
 * @param[in] param Pointer to the parameters of the message.
 * @param[in] dest_id ID of the receiving task instance (probably unused).
 * @param[in] src_id ID of the sending task instance.
 * @return If the message was consumed or not.
 ****************************************************************************************
 */
__STATIC int gattc_write_req_ind_handler(
    ke_msg_id_t const msgid, struct gattc_write_req_ind const *param,
    ke_task_id_t const dest_id, ke_task_id_t const src_id) {
  // Status
  uint8_t status = GAP_ERR_NO_ERROR;
  // Get the conidx
  uint8_t conidx = KE_IDX_GET(src_id);
  // Get the address of the environment
  struct anps_env_tag *anps_env = PRF_ENV_GET(ANPS, anps);

  // Check if the connection exists
  if (anps_env != NULL) {
    /*
     * ---------------------------------------------------------------------------------------------
     * New Alert Client Characteristic Configuration Descriptor Value - Write
     * ---------------------------------------------------------------------------------------------
     * Unread Status Alert Client Characteristic Configuration Descriptor Value
     * - Write
     * ---------------------------------------------------------------------------------------------
     */
    if ((param->handle == (anps_env->shdl + ANS_IDX_NEW_ALERT_CFG)) ||
        (param->handle == (anps_env->shdl + ANS_IDX_UNREAD_ALERT_STATUS_CFG))) {
      // Received configuration value
      uint16_t ntf_cfg = co_read16p(&param->value[0]);

      if (ntf_cfg <= PRF_CLI_START_NTF) {
        // Alert type
        uint8_t alert_type =
            (param->handle == (anps_env->shdl + ANS_IDX_NEW_ALERT_CFG))
                ? ANP_NEW_ALERT
                : ANP_UNREAD_ALERT;

        // Update the status in the environment
        if (ntf_cfg == PRF_CLI_START_NTF) {
          ANPS_ENABLE_ALERT(conidx, anps_env, alert_type);
        } else {
          ANPS_DISABLE_ALERT(conidx, anps_env, alert_type);
        }

        // Inform the HL that the notification configuration status has been
        // written
        anps_send_ntf_status_update_ind(conidx, anps_env, alert_type);
        // Enable bond Data
        ANPS_ENABLE_ALERT(conidx, anps_env, ANPS_FLAG_CFG_PERFORMED_OK);
      } else {
        status = PRF_APP_ERROR;
      }
    }
    /*
     * ---------------------------------------------------------------------------------------------
     * Alert Notification Control Point Characteristic Value - Write
     * ---------------------------------------------------------------------------------------------
     */
    else if (param->handle ==
             (anps_env->shdl + ANS_IDX_ALERT_NTF_CTNL_PT_VAL)) {
      do {
        // Check the command ID value
        if (param->value[0] >= CMD_ID_NB) {
          status = ANP_CMD_NOT_SUPPORTED;
          break;
        }

        // Check the category ID value
        if ((param->value[1] >= CAT_ID_NB) &&
            (param->value[1] != CAT_ID_ALL_SUPPORTED_CAT)) {
          status = ANP_CAT_NOT_SUPPORTED;
          break;
        }

        if (param->value[1] < CAT_ID_NB) {
          // New Alert
          if ((param->value[0] % 2) == 0) {
            // Check if the category is supported
            if (!ANPS_IS_NEW_ALERT_CATEGORY_SUPPORTED(param->value[1])) {
              status = ANP_CAT_NOT_SUPPORTED;
              break;
            }
          }
          // Unread Alert Status
          else {
            // Check if the category is supported
            if (!ANPS_IS_UNREAD_ALERT_CATEGORY_SUPPORTED(param->value[1])) {
              status = ANP_CAT_NOT_SUPPORTED;
              break;
            }
          }
        }

        // React according to the received command id value
        switch (param->value[0]) {
        // Enable New Alert Notification
        case (CMD_ID_EN_NEW_IN_ALERT_NTF): {
          if (param->value[1] != CAT_ID_ALL_SUPPORTED_CAT) {
            // Enable sending of new alert notification for the specified
            // category
            ANPS_ENABLE_NEW_ALERT_CATEGORY(conidx, param->value[1], anps_env);
          } else {
            // Enable sending of new alert notification for all supported
            // category
            anps_env->env[conidx]->ntf_new_alert_cfg |=
                anps_env->supp_new_alert_cat;
          }

          anps_send_ntf_status_update_ind(conidx, anps_env, ANP_NEW_ALERT);
        } break;

        // Enable Unread Alert Status Notification
        case (CMD_ID_EN_UNREAD_CAT_STATUS_NTF): {
          if (param->value[1] != CAT_ID_ALL_SUPPORTED_CAT) {
            // Enable sending of unread alert notification for the specified
            // category
            ANPS_ENABLE_UNREAD_ALERT_CATEGORY(conidx, param->value[1],
                                              anps_env);
          } else {
            // Enable sending of unread alert notification for all supported
            // category
            anps_env->env[conidx]->ntf_unread_alert_cfg |=
                anps_env->supp_unread_alert_cat;
          }

          anps_send_ntf_status_update_ind(conidx, anps_env, ANP_UNREAD_ALERT);
        } break;

        // Disable New Alert Notification
        case (CMD_ID_DIS_NEW_IN_ALERT_NTF): {
          if (param->value[1] != CAT_ID_ALL_SUPPORTED_CAT) {
            // Disable sending of new alert notification for the specified
            // category
            ANPS_DISABLE_NEW_ALERT_CATEGORY(conidx, param->value[1], anps_env);
          } else {
            // Disable sending of new alert notification for all supported
            // category
            anps_env->env[conidx]->ntf_new_alert_cfg &=
                ~anps_env->supp_new_alert_cat;
          }

          anps_send_ntf_status_update_ind(conidx, anps_env, ANP_NEW_ALERT);
        } break;

        // Disable Unread Alert Status Notification
        case (CMD_ID_DIS_UNREAD_CAT_STATUS_NTF): {
          if (param->value[1] != CAT_ID_ALL_SUPPORTED_CAT) {
            // Disable sending of unread alert notification for the specified
            // category
            ANPS_DISABLE_UNREAD_ALERT_CATEGORY(conidx, param->value[1],
                                               anps_env);
          } else {
            // Enable sending of unread alert notification for all supported
            // category
            anps_env->env[conidx]->ntf_unread_alert_cfg &=
                ~anps_env->supp_unread_alert_cat;
          }

          anps_send_ntf_status_update_ind(conidx, anps_env, ANP_UNREAD_ALERT);
        } break;

        // Notify New Alert immediately
        case (CMD_ID_NTF_NEW_IN_ALERT_IMM): {
          // Check if sending of notification is enabled
          if (ANPS_IS_ALERT_ENABLED(conidx, anps_env, ANP_NEW_ALERT)) {
            if (param->value[1] == CAT_ID_ALL_SUPPORTED_CAT) {
              // Check if at least one category can be notified
              if (anps_env->env[conidx]->ntf_new_alert_cfg != 0) {
                anps_send_ntf_immediate_req_ind(conidx, anps_env, ANP_NEW_ALERT,
                                                CAT_ID_ALL_SUPPORTED_CAT);
              }
            } else {
              // Check if sending of notifications has been enabled for the
              // specified category.
              if (ANPS_IS_NEW_ALERT_CATEGORY_ENABLED(conidx, param->value[1],
                                                     anps_env)) {
                anps_send_ntf_immediate_req_ind(conidx, anps_env, ANP_NEW_ALERT,
                                                param->value[1]);
              }
            }
          }
        } break;

        // Notify Unread Alert Status immediately
        case (CMD_ID_NTF_UNREAD_CAT_STATUS_IMM): {
          if (ANPS_IS_ALERT_ENABLED(conidx, anps_env, ANP_UNREAD_ALERT)) {
            // Check if sending of notification is enabled
            if (ANPS_IS_ALERT_ENABLED(conidx, anps_env, ANP_UNREAD_ALERT)) {
              if (param->value[1] == CAT_ID_ALL_SUPPORTED_CAT) {
                // Check if at least one category can be notified
                if (anps_env->env[conidx]->ntf_unread_alert_cfg != 0) {
                  anps_send_ntf_immediate_req_ind(conidx, anps_env,
                                                  ANP_UNREAD_ALERT,
                                                  CAT_ID_ALL_SUPPORTED_CAT);
                }
              } else {
                // Check if sending of notifications has been enabled for the
                // specified category.
                if (ANPS_IS_UNREAD_ALERT_CATEGORY_ENABLED(
                        conidx, param->value[1], anps_env)) {
                  anps_send_ntf_immediate_req_ind(
                      conidx, anps_env, ANP_UNREAD_ALERT, param->value[1]);
                }
              }
            }
          }
        } break;

        default: {
          ASSERT_ERR(0);
        } break;
        }
      } while (0);
    } else {
      ASSERT_ERR(0);
    }
    // Send the write response to the peer device
    struct gattc_write_cfm *cfm =
        KE_MSG_ALLOC(GATTC_WRITE_CFM, src_id, dest_id, gattc_write_cfm);
    cfm->handle = param->handle;
    cfm->status = status;
    ke_msg_send(cfm);
  }
  // else ignore the message

  return (KE_MSG_CONSUMED);
}

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

/// Specifies the default message handlers
KE_MSG_HANDLER_TAB(anps){
    {ANPS_ENABLE_REQ, (ke_msg_func_t)anps_enable_req_handler},
    {ANPS_NTF_ALERT_CMD, (ke_msg_func_t)anps_ntf_alert_cmd_handler},
    {GATTC_READ_REQ_IND, (ke_msg_func_t)gattc_read_req_ind_handler},
    {GATTC_WRITE_REQ_IND, (ke_msg_func_t)gattc_write_req_ind_handler},
    {GATTC_CMP_EVT, (ke_msg_func_t)gattc_cmp_evt_handler},
};

void anps_task_init(struct ke_task_desc *task_desc) {
  // Get the address of the environment
  struct anps_env_tag *anps_env = PRF_ENV_GET(ANPS, anps);

  task_desc->msg_handler_tab = anps_msg_handler_tab;
  task_desc->msg_cnt = ARRAY_LEN(anps_msg_handler_tab);
  task_desc->state = anps_env->state;
  task_desc->idx_max = ANPS_IDX_MAX;
}

#endif //(BLE_AN_SERVER)

/// @} ANPSTASK
