/**
 ****************************************************************************************
 * @addtogroup HTPT
 * @{
 ****************************************************************************************
 */

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

#if (BLE_HT_THERMOM)
#include "attm.h"
#include "co_utils.h"
#include "htpt.h"
#include "htpt_task.h"
#include "prf_utils.h"

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

/*
 * HTPT PROFILE ATTRIBUTES
 ****************************************************************************************
 */
/// Full HTS Database Description - Used to add attributes into the database
const struct attm_desc htpt_att_db[HTS_IDX_NB] = {
    // Health Thermometer Service Declaration
    [HTS_IDX_SVC] = {ATT_DECL_PRIMARY_SERVICE, PERM(RD, ENABLE), 0, 0},

    // Temperature Measurement Characteristic Declaration
    [HTS_IDX_TEMP_MEAS_CHAR] = {ATT_DECL_CHARACTERISTIC, PERM(RD, ENABLE), 0,
                                0},
    // Temperature Measurement Characteristic Value
    [HTS_IDX_TEMP_MEAS_VAL] = {ATT_CHAR_TEMPERATURE_MEAS, PERM(IND, ENABLE),
                               PERM(RI, ENABLE), 0},
    // Temperature Measurement Characteristic - Client Characteristic
    // Configuration Descriptor
    [HTS_IDX_TEMP_MEAS_IND_CFG] = {ATT_DESC_CLIENT_CHAR_CFG,
                                   PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE),
                                   0, 0},

    // Temperature Type Characteristic Declaration
    [HTS_IDX_TEMP_TYPE_CHAR] = {ATT_DECL_CHARACTERISTIC, PERM(RD, ENABLE), 0,
                                0},
    // Temperature Type Characteristic Value
    [HTS_IDX_TEMP_TYPE_VAL] = {ATT_CHAR_TEMPERATURE_TYPE, PERM(RD, ENABLE),
                               PERM(RI, ENABLE), 0},

    // Intermediate Measurement Characteristic Declaration
    [HTS_IDX_INTERM_TEMP_CHAR] = {ATT_DECL_CHARACTERISTIC, PERM(RD, ENABLE), 0,
                                  0},
    // Intermediate Measurement Characteristic Value
    [HTS_IDX_INTERM_TEMP_VAL] = {ATT_CHAR_INTERMED_TEMPERATURE,
                                 PERM(NTF, ENABLE), PERM(RI, ENABLE), 0},
    // Intermediate Measurement Characteristic - Client Characteristic
    // Configuration Descriptor
    [HTS_IDX_INTERM_TEMP_CFG] = {ATT_DESC_CLIENT_CHAR_CFG,
                                 PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE), 0,
                                 0},

    // Measurement Interval Characteristic Declaration
    [HTS_IDX_MEAS_INTV_CHAR] = {ATT_DECL_CHARACTERISTIC, PERM(RD, ENABLE), 0,
                                0},
    // Measurement Interval Characteristic Value
    [HTS_IDX_MEAS_INTV_VAL] = {ATT_CHAR_MEAS_INTERVAL, PERM(RD, ENABLE),
                               PERM(RI, ENABLE), HTPT_MEAS_INTV_MAX_LEN},
    // Measurement Interval Characteristic - Client Characteristic Configuration
    // Descriptor
    [HTS_IDX_MEAS_INTV_CFG] = {ATT_DESC_CLIENT_CHAR_CFG,
                               PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE), 0,
                               0},
    // Measurement Interval Characteristic - Valid Range Descriptor
    [HTS_IDX_MEAS_INTV_VAL_RANGE] = {ATT_DESC_VALID_RANGE, PERM(RD, ENABLE),
                                     PERM(RI, ENABLE), 0},
};

static uint16_t htpt_compute_att_table(uint16_t features);

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

static uint8_t htpt_init(struct prf_task_env *env, uint16_t *start_hdl,
                         uint16_t app_task, uint8_t sec_lvl,
                         struct htpt_db_cfg *params) {
  // Service content flag
  uint16_t cfg_flag;
  // DB Creation Status
  uint8_t status = ATT_ERR_NO_ERROR;

  BLE_PRF_HP_FUNC_ENTER();

  cfg_flag = htpt_compute_att_table(params->features);

  status = attm_svc_create_db(
      start_hdl, ATT_SVC_HEALTH_THERMOM, (uint8_t *)&cfg_flag, HTS_IDX_NB, NULL,
      env->task, &htpt_att_db[0],
      (sec_lvl & (PERM_MASK_SVC_DIS | PERM_MASK_SVC_AUTH | PERM_MASK_SVC_EKS)) |
          PERM(SVC_MI, DISABLE));

  if (status == ATT_ERR_NO_ERROR) {
    //-------------------- allocate memory required for the profile
    //---------------------
    struct htpt_env_tag *htpt_env = (struct htpt_env_tag *)ke_malloc(
        sizeof(struct htpt_env_tag), KE_MEM_ATT_DB);

    // allocate PROXR required environment variable
    env->env = (prf_env_t *)htpt_env;

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

    // initialize environment variable
    env->id = TASK_ID_HTPT;
    htpt_task_init(&(env->desc));

    // Save features on the environment
    htpt_env->features = params->features;
    htpt_env->meas_intv = params->meas_intv;
    htpt_env->meas_intv_min = params->valid_range_min;
    htpt_env->meas_intv_max = params->valid_range_max;
    htpt_env->temp_type = params->temp_type;
    htpt_env->operation = NULL;
    memset(htpt_env->ntf_ind_cfg, 0, sizeof(htpt_env->ntf_ind_cfg));

    // Update measurement interval permissions
    if (HTPT_IS_FEATURE_SUPPORTED(params->features, HTPT_MEAS_INTV_CHAR_SUP)) {
      uint16_t perm = PERM(RD, ENABLE);

      // Check if Measurement Interval Char. supports indications
      if (HTPT_IS_FEATURE_SUPPORTED(params->features, HTPT_MEAS_INTV_IND_SUP)) {
        perm |= PERM(IND, ENABLE);
      }

      // Check if Measurement Interval Char. is writable
      if (HTPT_IS_FEATURE_SUPPORTED(params->features, HTPT_MEAS_INTV_WR_SUP)) {
        perm |= PERM(WP, UNAUTH) | PERM(WRITE_REQ, ENABLE);
      }

      attm_att_set_permission(HTPT_HANDLE(HTS_IDX_MEAS_INTV_VAL), perm, 0);
    }

    // service is ready, go into an Idle state
    ke_state_set(env->task, HTPT_IDLE);
  }

  BLE_PRF_HP_FUNC_LEAVE();

  return (status);
}

static void htpt_destroy(struct prf_task_env *env) {
  struct htpt_env_tag *htpt_env = (struct htpt_env_tag *)env->env;

  BLE_PRF_HP_FUNC_ENTER();

  // free profile environment variables
  if (htpt_env->operation != NULL) {
    ke_free(htpt_env->operation);
  }

  env->env = NULL;
  ke_free(htpt_env);

  BLE_PRF_HP_FUNC_LEAVE();
}

static void htpt_create(struct prf_task_env *env, uint8_t conidx) {
  BLE_PRF_HP_FUNC_ENTER();

  /* Clear configuration for this connection */
  struct htpt_env_tag *htpt_env = (struct htpt_env_tag *)env->env;
  htpt_env->ntf_ind_cfg[conidx] = 0;

  BLE_PRF_HP_FUNC_LEAVE();
}

static void htpt_cleanup(struct prf_task_env *env, uint8_t conidx,
                         uint8_t reason) {
  BLE_PRF_HP_FUNC_ENTER();

  /* Clear configuration for this connection */
  struct htpt_env_tag *htpt_env = (struct htpt_env_tag *)env->env;
  htpt_env->ntf_ind_cfg[conidx] = 0;

  BLE_PRF_HP_FUNC_LEAVE();
}

/**
 ****************************************************************************************
 * @brief Compute a flag allowing to know attributes to add into the database
 *
 * @return a 16-bit flag whose each bit matches an attribute. If the bit is set
 *to 1, the attribute will be added into the database.
 ****************************************************************************************
 */
static uint16_t htpt_compute_att_table(uint16_t features) {
  BLE_PRF_HP_FUNC_ENTER();

  // Temperature Measurement Characteristic is mandatory
  uint16_t att_table = HTPT_TEMP_MEAS_MASK;

  // Check if Temperature Type Char. is supported
  if (HTPT_IS_FEATURE_SUPPORTED(features, HTPT_TEMP_TYPE_CHAR_SUP)) {
    att_table |= HTPT_TEMP_TYPE_MASK;
  }

  // Check if Intermediate Temperature Char. is supported
  if (HTPT_IS_FEATURE_SUPPORTED(features, HTPT_INTERM_TEMP_CHAR_SUP)) {
    att_table |= HTPT_INTM_TEMP_MASK;
  }

  // Check if Measurement Interval Char. is supported
  if (HTPT_IS_FEATURE_SUPPORTED(features, HTPT_MEAS_INTV_CHAR_SUP)) {
    att_table |= HTPT_MEAS_INTV_MASK;

    // Check if Measurement Interval Char. supports indications
    if (HTPT_IS_FEATURE_SUPPORTED(features, HTPT_MEAS_INTV_IND_SUP)) {
      att_table |= HTPT_MEAS_INTV_CCC_MASK;
    }

    // Check if Measurement Interval Char. is writable
    if (HTPT_IS_FEATURE_SUPPORTED(features, HTPT_MEAS_INTV_WR_SUP)) {
      att_table |= HTPT_MEAS_INTV_VALID_RGE_MASK;
    }
  }

  BLE_PRF_HP_FUNC_LEAVE();

  return att_table;
}

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

/// HTPT Task interface required by profile manager
const struct prf_task_cbs htpt_itf = {
    (prf_init_fnct)htpt_init,
    htpt_destroy,
    htpt_create,
    htpt_cleanup,
};

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

uint8_t htpt_get_valid_rge_offset(uint16_t features) {
  uint8_t offset = 0;

  if (HTPT_IS_FEATURE_SUPPORTED(features, HTPT_MEAS_INTV_WR_SUP)) {
    offset += 1;

    if (HTPT_IS_FEATURE_SUPPORTED(features, HTPT_MEAS_INTV_IND_SUP)) {
      offset += 1;
    }
  }

  return offset;
}

uint8_t htpt_pack_temp_value(uint8_t *packed_temp,
                             struct htp_temp_meas temp_meas) {
  uint8_t cursor = 0;

  *(packed_temp + cursor) = temp_meas.flags;
  cursor += 1;

  co_write32p(packed_temp + cursor, temp_meas.temp);
  cursor += 4;

  // Time Flag Set
  if ((temp_meas.flags & HTP_FLAG_TIME) == HTP_FLAG_TIME) {
    cursor += prf_pack_date_time(packed_temp + cursor, &(temp_meas.time_stamp));
  }

  // Type flag set
  if ((temp_meas.flags & HTP_FLAG_TYPE) == HTP_FLAG_TYPE) {
    *(packed_temp + cursor) = temp_meas.type;
    cursor += 1;
  }

  // Clear unused packet data
  if (cursor < HTPT_TEMP_MEAS_MAX_LEN) {
    memset(packed_temp + cursor, 0, (HTPT_TEMP_MEAS_MAX_LEN - cursor));
  }

  return cursor;
}

void htpt_exe_operation(void) {
  BLE_PRF_HP_FUNC_ENTER();

  struct htpt_env_tag *htpt_env = PRF_ENV_GET(HTPT, htpt);

  ASSERT_ERR(htpt_env->operation != NULL);

  bool finished = true;

  while (htpt_env->operation->cursor < BLE_CONNECTION_MAX) {
    // check if this type of event is enabled
    if (((htpt_env->ntf_ind_cfg[htpt_env->operation->cursor] &
          htpt_env->operation->op) != 0)
        // and event not filtered on current connection
        && (htpt_env->operation->conidx != htpt_env->operation->cursor)) {
      // trigger the event
      struct gattc_send_evt_cmd *evt =
          KE_MSG_ALLOC_DYN(GATTC_SEND_EVT_CMD,
                           KE_BUILD_ID(TASK_GATTC, htpt_env->operation->cursor),
                           prf_src_task_get(&htpt_env->prf_env, 0),
                           gattc_send_evt_cmd, htpt_env->operation->length);

      evt->operation = (htpt_env->operation->op != HTPT_CFG_INTERM_MEAS_NTF)
                           ? GATTC_INDICATE
                           : GATTC_NOTIFY;
      evt->length = htpt_env->operation->length;
      evt->handle = htpt_env->operation->handle;
      memcpy(evt->value, htpt_env->operation->data, evt->length);

      ke_msg_send(evt);

      finished = false;
      htpt_env->operation->cursor++;
      break;
    }
    htpt_env->operation->cursor++;
  }

  // check if operation is finished
  if (finished) {
    // do not send response if operation has been locally requested
    if (htpt_env->operation->dest_id !=
        prf_src_task_get(&htpt_env->prf_env, 0)) {
      // send response to requester
      struct htpt_meas_intv_upd_rsp *rsp = KE_MSG_ALLOC(
          ((htpt_env->operation->op == HTPT_CFG_MEAS_INTV_IND)
               ? HTPT_MEAS_INTV_UPD_RSP
               : HTPT_TEMP_SEND_RSP),
          htpt_env->operation->dest_id, prf_src_task_get(&htpt_env->prf_env, 0),
          htpt_meas_intv_upd_rsp);
      rsp->status = GAP_ERR_NO_ERROR;
      ke_msg_send(rsp);
    }

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

  BLE_PRF_HP_FUNC_LEAVE();
}

uint8_t htpt_update_ntf_ind_cfg(uint8_t conidx, uint8_t cfg, uint16_t valid_val,
                                uint16_t value) {
  struct htpt_env_tag *htpt_env = PRF_ENV_GET(HTPT, htpt);
  uint8_t status = GAP_ERR_NO_ERROR;

  BLE_PRF_HP_FUNC_ENTER();

  if ((value != valid_val) && (value != PRF_CLI_STOP_NTFIND)) {
    status = PRF_APP_ERROR;

  } else if (value == valid_val) {
    htpt_env->ntf_ind_cfg[conidx] |= cfg;
  } else {
    htpt_env->ntf_ind_cfg[conidx] &= ~cfg;
  }

  if (status == GAP_ERR_NO_ERROR) {
    // inform application that notification/indication configuration has changed
    struct htpt_cfg_indntf_ind *ind = KE_MSG_ALLOC(
        HTPT_CFG_INDNTF_IND, prf_dst_task_get(&htpt_env->prf_env, conidx),
        prf_src_task_get(&htpt_env->prf_env, conidx), htpt_cfg_indntf_ind);
    ind->conidx = conidx;
    ind->ntf_ind_cfg = htpt_env->ntf_ind_cfg[conidx];
    ke_msg_send(ind);
  }

  BLE_PRF_HP_FUNC_LEAVE();

  return (status);
}

const struct prf_task_cbs *htpt_prf_itf_get(void) { return &htpt_itf; }

uint16_t htpt_att_hdl_get(struct htpt_env_tag *htpt_env, uint8_t att_idx) {
  uint16_t handle = htpt_env->shdl;

  do {
    // Mandatory attribute handle
    if (att_idx > HTS_IDX_TEMP_MEAS_IND_CFG) {
      handle += HTPT_TEMP_MEAS_ATT_NB;
    } else {
      handle += att_idx;
      break;
    }

    // Temperature Type
    if ((HTPT_IS_FEATURE_SUPPORTED(htpt_env->features,
                                   HTPT_TEMP_TYPE_CHAR_SUP)) &&
        (att_idx > HTS_IDX_TEMP_TYPE_VAL)) {
      handle += HTPT_TEMP_TYPE_ATT_NB;
    } else if (!HTPT_IS_FEATURE_SUPPORTED(htpt_env->features,
                                          HTPT_TEMP_TYPE_CHAR_SUP)) {
      handle = ATT_INVALID_HANDLE;
      break;
    } else {
      handle += att_idx - HTS_IDX_TEMP_TYPE_CHAR;
      break;
    }

    // Intermediate Temperature Measurement
    if ((HTPT_IS_FEATURE_SUPPORTED(htpt_env->features,
                                   HTPT_INTERM_TEMP_CHAR_SUP)) &&
        (att_idx > HTS_IDX_INTERM_TEMP_CFG)) {
      handle += HTPT_INTERM_MEAS_ATT_NB;
    } else if (!HTPT_IS_FEATURE_SUPPORTED(htpt_env->features,
                                          HTPT_INTERM_TEMP_CHAR_SUP)) {
      handle = ATT_INVALID_HANDLE;
      break;
    } else {
      handle += att_idx - HTS_IDX_INTERM_TEMP_CHAR;
      break;
    }

    // Measurement Interval
    if (!HTPT_IS_FEATURE_SUPPORTED(htpt_env->features,
                                   HTPT_MEAS_INTV_CHAR_SUP) ||
        (att_idx >= HTS_IDX_NB)) {
      handle = ATT_INVALID_HANDLE;
      break;
    }

    if (att_idx <= HTS_IDX_MEAS_INTV_VAL) {
      handle += att_idx - HTS_IDX_MEAS_INTV_CHAR;
      break;
    } else {
      handle += HTPT_MEAS_INTV_ATT_NB;
    }

    // Measurement Interval Indication
    if (att_idx == HTS_IDX_MEAS_INTV_CFG) {
      if (!HTPT_IS_FEATURE_SUPPORTED(htpt_env->features,
                                     HTPT_MEAS_INTV_IND_SUP)) {
        handle = ATT_INVALID_HANDLE;
        break;
      }
    }
    // Measurement Interval Write permission
    else if (HTPT_IS_FEATURE_SUPPORTED(htpt_env->features,
                                       HTPT_MEAS_INTV_WR_SUP)) {
      handle += HTPT_MEAS_INTV_CCC_ATT_NB;

      if (!HTPT_IS_FEATURE_SUPPORTED(htpt_env->features,
                                     HTPT_MEAS_INTV_WR_SUP)) {
        handle = ATT_INVALID_HANDLE;
        break;
      }
    }
  } while (0);

  return handle;
}

uint8_t htpt_att_idx_get(struct htpt_env_tag *htpt_env, uint16_t handle) {
  uint16_t handle_ref = htpt_env->shdl;
  uint8_t att_idx = ATT_INVALID_IDX;

  do {
    // not valid hande
    if (handle < handle_ref) {
      break;
    }

    // Mandatory attribute handle
    handle_ref += HTPT_TEMP_MEAS_ATT_NB;

    if (handle < handle_ref) {
      att_idx = HTS_IDX_TEMP_TYPE_CHAR - (handle_ref - handle);
      break;
    }

    // Temperature Type
    if (HTPT_IS_FEATURE_SUPPORTED(htpt_env->features,
                                  HTPT_TEMP_TYPE_CHAR_SUP)) {
      handle_ref += HTPT_TEMP_TYPE_ATT_NB;

      if (handle < handle_ref) {
        att_idx = HTS_IDX_INTERM_TEMP_CHAR - (handle_ref - handle);
        break;
      }
    }

    // Intermediate Temperature Measurement
    if (HTPT_IS_FEATURE_SUPPORTED(htpt_env->features,
                                  HTPT_INTERM_TEMP_CHAR_SUP)) {
      handle_ref += HTPT_INTERM_MEAS_ATT_NB;

      if (handle < handle_ref) {
        att_idx = HTS_IDX_MEAS_INTV_CHAR - (handle_ref - handle);
        break;
      }
    }

    // Measurement Interval
    if (HTPT_IS_FEATURE_SUPPORTED(htpt_env->features,
                                  HTPT_MEAS_INTV_CHAR_SUP)) {
      handle_ref += HTPT_MEAS_INTV_ATT_NB;

      if (handle < handle_ref) {
        att_idx = HTS_IDX_MEAS_INTV_CFG - (handle_ref - handle);
        break;
      }

      if (HTPT_IS_FEATURE_SUPPORTED(htpt_env->features,
                                    HTPT_MEAS_INTV_IND_SUP)) {
        if (handle == handle_ref) {
          att_idx = HTS_IDX_MEAS_INTV_CFG;
          break;
        }

        handle_ref += HTPT_MEAS_INTV_CCC_ATT_NB;
      }

      if (HTPT_IS_FEATURE_SUPPORTED(htpt_env->features,
                                    HTPT_MEAS_INTV_WR_SUP)) {
        if (handle == handle_ref) {
          att_idx = HTS_IDX_MEAS_INTV_VAL_RANGE;
          break;
        }
      }
    }
  } while (0);

  return att_idx;
}

#endif // BLE_HT_THERMOM

/// @} HTPT
