/**
 ****************************************************************************************
 * @addtogroup PROXMTASK
 * @{
 ****************************************************************************************
 */

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

#if (BLE_PROX_MONITOR)
#include "co_utils.h"

#include "gap.h"
#include "gapc.h"
#include "gattc_task.h"
#include "prf_types.h"
#include "prf_utils.h"
#include "proxm.h"
#include "proxm_task.h"

#include "ke_mem.h"

///// State machine used to retrieve services characteristics information

const struct prf_char_def proxm_svc_char[PROXM_SVC_NB][PROXM_SVCS_CHAR_NB] = {
    [PROXM_LK_LOSS_SVC] = {{ATT_CHAR_ALERT_LEVEL, ATT_MANDATORY,
                            ATT_CHAR_PROP_RD | ATT_CHAR_PROP_WR}},

    [PROXM_IAS_SVC] = {{ATT_CHAR_ALERT_LEVEL, ATT_MANDATORY,
                        ATT_CHAR_PROP_WR_NO_RESP}},

    [PROXM_TX_POWER_SVC] = {
        {ATT_CHAR_TX_POWER_LEVEL, ATT_MANDATORY, ATT_CHAR_PROP_RD}}};

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

/**
 ****************************************************************************************
 * @brief Enable the Proximity Monitor role, used after connection.
 * @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 proxm_enable_req_handler(ke_msg_id_t const msgid,
                                      struct proxm_enable_req const *param,
                                      ke_task_id_t const dest_id,
                                      ke_task_id_t const src_id) {
  // Status
  uint8_t status = GAP_ERR_NO_ERROR;

  uint8_t state = ke_state_get(dest_id);
  uint8_t conidx = KE_IDX_GET(dest_id);
  // Proximity Monitor Environment
  struct proxm_env_tag *proxm_env = PRF_ENV_GET(PROXM, proxm);

  ASSERT_INFO(proxm_env != NULL, dest_id, src_id);
  if ((state == PROXM_IDLE) && (proxm_env->env[conidx] == NULL)) {
    // allocate environment variable for task instance
    proxm_env->env[conidx] = (struct proxm_cnx_env *)ke_malloc(
        sizeof(struct proxm_cnx_env), KE_MEM_ATT_DB);
    memset(proxm_env->env[conidx], 0, sizeof(struct proxm_cnx_env));

    if (param->con_type == PRF_CON_DISCOVERY) {
      proxm_env->env[conidx]->last_svc_req = PROXM_LK_LOSS_SVC;
      prf_disc_svc_send(&(proxm_env->prf_env), conidx, ATT_SVC_LINK_LOSS);

      // Go to DISCOVERING state
      ke_state_set(dest_id, PROXM_DISCOVERING);
    } else {
      proxm_env->env[conidx]->prox[PROXM_LK_LOSS_SVC] = param->lls;
      proxm_env->env[conidx]->prox[PROXM_IAS_SVC] = param->ias;
      proxm_env->env[conidx]->prox[PROXM_TX_POWER_SVC] = param->txps;

      // send APP confirmation that can start normal connection to TH
      proxm_enable_rsp_send(proxm_env, conidx, GAP_ERR_NO_ERROR);
    }

  } else if (state != PROXM_FREE) {
    status = PRF_ERR_REQ_DISALLOWED;
  }

  // send an error if request fails
  if (status != GAP_ERR_NO_ERROR) {
    proxm_enable_rsp_send(proxm_env, conidx, status);
  }

  return (KE_MSG_CONSUMED);
}

/**
 ****************************************************************************************
 * @brief Handles reception of the @ref PROXM_RD_REQ message.
 * Request to read the LLS alert level.
 * @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 proxm_rd_req_handler(ke_msg_id_t const msgid,
                                  struct proxm_rd_req const *param,
                                  ke_task_id_t const dest_id,
                                  ke_task_id_t const src_id) {
  uint8_t state = ke_state_get(dest_id);
  uint8_t status = GAP_ERR_NO_ERROR;
  uint8_t conidx = KE_IDX_GET(dest_id);
  uint8_t current_svc = PROXM_SVC_NB;

  if (state == PROXM_IDLE) {
    struct proxm_env_tag *proxm_env = PRF_ENV_GET(PROXM, proxm);

    ASSERT_INFO(proxm_env != NULL, dest_id, src_id);

    // environment variable not ready
    if (proxm_env->env[conidx] == NULL) {
      status = PRF_APP_ERROR;
    } else {
      // Get the request type and handle value for this type of write
      switch (param->svc_code) {
      case PROXM_RD_LL_ALERT_LVL:
        current_svc = PROXM_LK_LOSS_SVC;
        status = proxm_validate_request(proxm_env, conidx, PROXM_LK_LOSS_SVC);
        break;
      case PROXM_RD_TX_POWER_LVL:
        current_svc = PROXM_TX_POWER_SVC;
        status = proxm_validate_request(proxm_env, conidx, PROXM_TX_POWER_SVC);
        break;
      default:
        current_svc = PROXM_SVC_NB;
        status = PRF_ERR_INVALID_PARAM;
        break;
      }

      if (status == GAP_ERR_NO_ERROR) {
        prf_read_char_send(
            &(proxm_env->prf_env), conidx,
            proxm_env->env[conidx]->prox[current_svc].characts[0].char_hdl,
            proxm_env->env[conidx]->prox[current_svc].characts[0].char_ehdl_off,
            proxm_env->env[conidx]->prox[current_svc].characts[0].val_hdl);
        // wait for end of read request
        proxm_env->env[conidx]->last_svc_req = param->svc_code;

        ke_state_set(dest_id, PROXM_BUSY);
      }
    }

    // request cannot be performed
    if (status != GAP_ERR_NO_ERROR) {
      struct proxm_rd_rsp *rsp =
          KE_MSG_ALLOC(PROXM_RD_RSP, src_id, dest_id, proxm_rd_rsp);
      rsp->svc_code = param->svc_code;
      // set error status
      rsp->status = status;
      ke_msg_send(rsp);
    }
  }
  return (KE_MSG_CONSUMED);
}

/**
 ****************************************************************************************
 * @brief Handles reception of the @ref PROXM_WR_ALERT_LVL_REQ message.
 * Request to write either the LLS or IAS alert levels.
 * @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 proxm_wr_alert_lvl_req_handler(
    ke_msg_id_t const msgid, struct proxm_wr_alert_lvl_req const *param,
    ke_task_id_t const dest_id, ke_task_id_t const src_id) {
  uint8_t state = ke_state_get(dest_id);
  uint8_t status = GAP_ERR_NO_ERROR;
  uint8_t operation = 0x00;
  uint16_t val_hdl = 0x0000;
  uint8_t conidx = KE_IDX_GET(dest_id);
  struct proxm_env_tag *proxm_env = PRF_ENV_GET(PROXM, proxm);

  if (state == PROXM_IDLE) {
    ASSERT_INFO(proxm_env != NULL, dest_id, src_id);

    // environment variable not ready
    if (proxm_env->env[conidx] == NULL) {
      status = PRF_APP_ERROR;
    } else {
      // Alert level only has 3 values, other not useful
      if (param->lvl <= PROXM_ALERT_HIGH) {
        // Get the request type and handle value for this type of write
        switch (param->svc_code) {
        case PROXM_SET_LK_LOSS_ALERT:
          operation = GATTC_WRITE;
          val_hdl = proxm_env->env[conidx]
                        ->prox[PROXM_LK_LOSS_SVC]
                        .characts[0]
                        .val_hdl;
          break;
        case PROXM_SET_IMMDT_ALERT:
          operation = GATTC_WRITE_NO_RESPONSE;
          val_hdl =
              proxm_env->env[conidx]->prox[PROXM_IAS_SVC].characts[0].val_hdl;
          break;
        default:
          status = PRF_ERR_INVALID_PARAM;
          break;
        }

        if (status == GAP_ERR_NO_ERROR) {
          // Send GATT Write Request
          prf_gatt_write(&(proxm_env->prf_env), conidx, val_hdl,
                         (uint8_t *)&param->lvl, sizeof(uint8_t), operation);
          // wait for end of write request
          ke_state_set(dest_id, PROXM_BUSY);
        }
      } else {
        // wrong level - not one of the possible 3
        status = PRF_ERR_INVALID_PARAM;
      }
    }
    // An error occurs, send the response with error status
    if (status != GAP_ERR_NO_ERROR) {
      struct proxm_wr_alert_lvl_rsp *rsp = KE_MSG_ALLOC(
          PROXM_WR_ALERT_LVL_RSP, src_id, dest_id, proxm_wr_alert_lvl_rsp);
      // set error status
      rsp->status = status;

      ke_msg_send(rsp);
    }
  }
  return (KE_MSG_CONSUMED);
}

/**
 ****************************************************************************************
 * @brief Handles reception of the @ref GATTC_SDP_SVC_IND_HANDLER message.
 * The handler stores the found service details for service discovery.
 * @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_sdp_svc_ind_handler(ke_msg_id_t const msgid,
                                       struct gattc_sdp_svc_ind const *ind,
                                       ke_task_id_t const dest_id,
                                       ke_task_id_t const src_id) {
  uint8_t state = ke_state_get(dest_id);

  if (state == PROXM_DISCOVERING) {
    uint8_t conidx = KE_IDX_GET(dest_id);

    uint16_t current_svc = PROXM_SVC_NB;
    const struct prf_char_def *current_char_def;

    struct proxm_env_tag *proxm_env = PRF_ENV_GET(PROXM, proxm);

    ASSERT_INFO(proxm_env != NULL, dest_id, src_id);
    ASSERT_INFO(proxm_env->env[conidx] != NULL, dest_id, src_id);

    // check if a valid service is found
    if (attm_uuid16_comp((unsigned char *)ind->uuid, ind->uuid_len,
                         ATT_SVC_LINK_LOSS)) {
      current_svc = PROXM_LK_LOSS_SVC;
      current_char_def = &proxm_svc_char[PROXM_LK_LOSS_SVC][PROXM_LK_LOSS_CHAR];
    } else if (attm_uuid16_comp((unsigned char *)ind->uuid, ind->uuid_len,
                                ATT_SVC_IMMEDIATE_ALERT)) {
      current_svc = PROXM_IAS_SVC;
      current_char_def = &proxm_svc_char[PROXM_IAS_SVC][PROXM_IAS_CHAR];
    } else if (attm_uuid16_comp((unsigned char *)ind->uuid, ind->uuid_len,
                                ATT_SVC_TX_POWER)) {
      current_svc = PROXM_TX_POWER_SVC;
      current_char_def =
          &proxm_svc_char[PROXM_TX_POWER_SVC][PROXM_TX_POWER_CHAR];
    }

    // if a valid service found, put it into environment variable
    if (current_svc != PROXM_SVC_NB) {
      prf_extract_svc_info(ind, PROXM_CHAR_NB_MAX, current_char_def,
                           proxm_env->env[conidx]->prox[current_svc].characts,
                           0, NULL, NULL);

      proxm_env->env[conidx]->prox[current_svc].svc.shdl = ind->start_hdl;
      proxm_env->env[conidx]->prox[current_svc].svc.ehdl = ind->end_hdl;

      proxm_env->env[conidx]->nb_svc++;
    }
  }
  return (KE_MSG_CONSUMED);
}

/**
 ****************************************************************************************
 * @brief Handles reception of the @ref GATTC_CMP_EVT message.
 * This generic event is received for different requests, so need to keep track.
 * @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_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) {
  uint8_t state = ke_state_get(dest_id);
  // Get the address of the environment
  struct proxm_env_tag *proxm_env = PRF_ENV_GET(PROXM, proxm);
  uint8_t conidx = KE_IDX_GET(dest_id);
  uint16_t current_att_svc = ATT_INVALID_UUID;
  uint8_t status = GAP_ERR_NO_ERROR;

  if (state == PROXM_DISCOVERING) {
    status = prf_check_svc_char_validity(
        PROXM_SVCS_CHAR_NB,
        proxm_env->env[conidx]
            ->prox[proxm_env->env[conidx]->last_svc_req]
            .characts,
        proxm_svc_char[proxm_env->env[conidx]->last_svc_req]);

    // Too many services found only one such service should exist
    if (proxm_env->env[conidx]->nb_svc > 1) {
      status = PRF_ERR_MULTIPLE_SVC;
    }
    // if an error append, send confirmation with error status and stop
    // discovering
    if (status != GAP_ERR_NO_ERROR) {
      proxm_enable_rsp_send(proxm_env, conidx, status);
    }
    // if a valid service found, discover and then send confirmation message
    else {
      proxm_env->env[conidx]->nb_svc = 0;
      proxm_env->env[conidx]->last_svc_req += 1;

      if ((proxm_env->env[conidx]->last_svc_req) < PROXM_SVC_NB) {
        switch (proxm_env->env[conidx]->last_svc_req) {
        case PROXM_LK_LOSS_SVC:
          current_att_svc = ATT_SVC_LINK_LOSS;
          break;
        case PROXM_IAS_SVC:
          current_att_svc = ATT_SVC_IMMEDIATE_ALERT;
          break;
        case PROXM_TX_POWER_SVC:
          current_att_svc = ATT_SVC_TX_POWER;
          break;
        default:
          break;
        }

        prf_disc_svc_send(&(proxm_env->prf_env), conidx, current_att_svc);
      } else {
        proxm_enable_rsp_send(proxm_env, conidx, GAP_ERR_NO_ERROR);
      }
    }
  } else if (state == PROXM_BUSY) {
    switch (param->operation) {
    case GATTC_WRITE:
    case GATTC_WRITE_NO_RESPONSE: {
      struct proxm_wr_alert_lvl_rsp *rsp =
          KE_MSG_ALLOC(PROXM_WR_ALERT_LVL_RSP,
                       prf_dst_task_get(&(proxm_env->prf_env), conidx),
                       prf_src_task_get(&(proxm_env->prf_env), conidx),
                       proxm_wr_alert_lvl_rsp);
      // set error status
      rsp->status = param->status;
      ke_msg_send(rsp);

      ke_state_set(dest_id, PROXM_IDLE);
    } break;

    case GATTC_READ: {
      if (param->status != GAP_ERR_NO_ERROR) {
        struct proxm_rd_rsp *rsp = KE_MSG_ALLOC(
            PROXM_RD_RSP, prf_dst_task_get(&(proxm_env->prf_env), conidx),
            prf_src_task_get(&(proxm_env->prf_env), conidx), proxm_rd_rsp);
        // set error status
        rsp->status = param->status;
        rsp->svc_code = proxm_env->env[conidx]->last_svc_req;
        ke_msg_send(rsp);
      }
      ke_state_set(dest_id, PROXM_IDLE);
    } break;
    default:
      break;
    }
  }

  return (KE_MSG_CONSUMED);
}

/**
 ****************************************************************************************
 * @brief Handles reception of the @ref GATTC_READ_IND message.
 * Generic event received after every simple read command sent to peer server.
 * @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_ind_handler(ke_msg_id_t const msgid,
                                    struct gattc_read_ind const *param,
                                    ke_task_id_t const dest_id,
                                    ke_task_id_t const src_id) {
  if (ke_state_get(dest_id) == PROXM_BUSY) {
    struct proxm_env_tag *proxm_env = PRF_ENV_GET(PROXM, proxm);
    uint8_t conidx = KE_IDX_GET(dest_id);

    ASSERT_INFO(proxm_env != NULL, dest_id, src_id);
    ASSERT_INFO(proxm_env->env[conidx] != NULL, dest_id, src_id);

    struct proxm_rd_rsp *rsp = KE_MSG_ALLOC(
        PROXM_RD_RSP, prf_dst_task_get(&(proxm_env->prf_env), conidx), dest_id,
        proxm_rd_rsp);
    rsp->status = GAP_ERR_NO_ERROR;
    rsp->svc_code = proxm_env->env[conidx]->last_svc_req;
    rsp->value = param->value[0];
    ke_msg_send(rsp);
  }
  return (KE_MSG_CONSUMED);
}

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

/// Default State handlers definition
KE_MSG_HANDLER_TAB(proxm){
    {GATTC_SDP_SVC_IND, (ke_msg_func_t)gattc_sdp_svc_ind_handler},
    {PROXM_ENABLE_REQ, (ke_msg_func_t)proxm_enable_req_handler},
    {PROXM_WR_ALERT_LVL_REQ, (ke_msg_func_t)proxm_wr_alert_lvl_req_handler},
    {PROXM_RD_REQ, (ke_msg_func_t)proxm_rd_req_handler},
    {GATTC_READ_IND, (ke_msg_func_t)gattc_read_ind_handler},
    {GATTC_CMP_EVT, (ke_msg_func_t)gattc_cmp_evt_handler},
};

void proxm_task_init(struct ke_task_desc *task_desc) {
  // Get the address of the environment
  struct proxm_env_tag *proxm_env = PRF_ENV_GET(PROXM, proxm);

  task_desc->msg_handler_tab = proxm_msg_handler_tab;
  task_desc->msg_cnt = ARRAY_LEN(proxm_msg_handler_tab);
  task_desc->state = proxm_env->state;
  task_desc->idx_max = PROXM_IDX_MAX;
}

#endif // BLE_PROX_MONITOR

/// @} PROXMTASK
