/**
 ****************************************************************************************
 * @addtogroup PRF_UTILS
 * @{
 ****************************************************************************************
 */

/*
 * INCLUDE FILES
 ****************************************************************************************
 */
#include "rwip_config.h"
#if (BLE_PROFILES)
#if (BLE_SERVER_PRF || BLE_CLIENT_PRF)

#include "attm.h"
#include "gap.h"
#include "gapc.h"
#include "gattc_task.h"
#include "ke_task.h"
#include "prf_utils.h"
#include <stdbool.h>
#include <stdint.h>

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

#endif /* (BLE_SERVER_PRF || BLE_CLIENT_PRF) */

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

/*
 * EXPORTED FUNCTIONS DEFINITIONS
 ****************************************************************************************
 */

#if (BLE_BATT_SERVER)
void prf_pack_char_pres_fmt(uint8_t *packed_val,
                            const struct prf_char_pres_fmt *char_pres_fmt) {
  *packed_val = char_pres_fmt->format;
  *(packed_val + 1) = char_pres_fmt->exponent;
  co_write16p(packed_val + 2, char_pres_fmt->unit);
  *(packed_val + 4) = char_pres_fmt->name_space;
  co_write16p(packed_val + 5, char_pres_fmt->description);
}
#endif // (BLE_BATT_SERVER)

#if (BLE_BATT_CLIENT)
void prf_unpack_char_pres_fmt(const uint8_t *packed_val,
                              struct prf_char_pres_fmt *char_pres_fmt) {

  char_pres_fmt->format = *packed_val;
  char_pres_fmt->exponent = *(packed_val + 1);
  char_pres_fmt->unit = co_read16p(packed_val + 2);
  char_pres_fmt->name_space = *(packed_val + 4);
  char_pres_fmt->description = co_read16p(packed_val + 5);
}
#endif // (BLE_BATT_CLIENT)

#if (BLE_CLIENT_PRF)
void prf_read_char_send(prf_env_t *prf_env, uint8_t conidx, uint16_t shdl,
                        uint16_t ehdl, uint16_t valhdl) {
  struct gattc_read_cmd *req =
      KE_MSG_ALLOC(GATTC_READ_CMD, KE_BUILD_ID(TASK_GATTC, conidx),
                   prf_src_task_get(prf_env, conidx), gattc_read_cmd);
  // request type
  req->operation = GATTC_READ;
  req->nb = 1;
  req->req.simple.offset = 0;
  req->req.simple.length = 0;
  req->req.simple.handle = valhdl;

  // send request to GATT
  ke_msg_send(req);
}

void prf_register_atthdl2gatt(prf_env_t *prf_env, uint8_t conidx,
                              struct prf_svc *svc) {
  if (svc->shdl != ATT_INVALID_HANDLE) {
    // register profile task in gatt for indication/notifications
    struct gattc_reg_to_peer_evt_cmd *reg = KE_MSG_ALLOC(
        GATTC_REG_TO_PEER_EVT_CMD, KE_BUILD_ID(TASK_GATTC, conidx),
        prf_src_task_get(prf_env, conidx), gattc_reg_to_peer_evt_cmd);

    reg->operation = GATTC_REGISTER;
    reg->start_hdl = svc->shdl;
    reg->end_hdl = svc->ehdl;

    ke_msg_send(reg);
  }
}

void prf_unregister_atthdl2gatt(prf_env_t *prf_env, uint8_t conidx,
                                struct prf_svc *svc) {
  if (svc->shdl != ATT_INVALID_HANDLE) {
    // un register profile task in gatt for indication/notifications
    struct gattc_reg_to_peer_evt_cmd *reg = KE_MSG_ALLOC(
        GATTC_REG_TO_PEER_EVT_CMD, KE_BUILD_ID(TASK_GATTC, conidx),
        prf_src_task_get(prf_env, conidx), gattc_reg_to_peer_evt_cmd);

    reg->operation = GATTC_UNREGISTER;
    reg->start_hdl = svc->shdl;
    reg->end_hdl = svc->ehdl;

    ke_msg_send(reg);
  }
}

void prf_disc_svc_send(prf_env_t *prf_env, uint8_t conidx, uint16_t uuid) {
  // send GATT discover primary services by UUID request
  struct gattc_sdp_svc_disc_cmd *svc_req =
      KE_MSG_ALLOC_DYN(GATTC_SDP_SVC_DISC_CMD, KE_BUILD_ID(TASK_GATTC, conidx),
                       prf_src_task_get(prf_env, conidx),
                       gattc_sdp_svc_disc_cmd, ATT_UUID_16_LEN);

  // gatt request type: by UUID
  svc_req->operation = GATTC_SDP_DISC_SVC;
  // start handle;
  svc_req->start_hdl = ATT_1ST_REQ_START_HDL;
  // end handle
  svc_req->end_hdl = ATT_1ST_REQ_END_HDL;

  // UUID search
  svc_req->uuid_len = ATT_UUID_16_LEN;

  // set the first two bytes to the value array, LSB to MSB:Health Thermometer
  // Service UUID first
  co_write16p(&(svc_req->uuid[0]), uuid);

  // send the message to GATT, which will send back the response when it gets it
  ke_msg_send(svc_req);
}

void prf_gatt_write(prf_env_t *prf_env, uint8_t conidx, uint16_t handle,
                    uint8_t *value, uint16_t length, uint8_t operation) {
  if (handle != ATT_INVALID_HANDLE) {
    struct gattc_write_cmd *wr_char = KE_MSG_ALLOC_DYN(
        GATTC_WRITE_CMD, KE_BUILD_ID(TASK_GATTC, conidx),
        prf_src_task_get(prf_env, conidx), gattc_write_cmd, length);

    // Offset
    wr_char->offset = 0x0000;
    // cursor always 0
    wr_char->cursor = 0x0000;
    // Write Type
    wr_char->operation = operation;
    // Characteristic Value attribute handle
    wr_char->handle = handle;
    // Value Length
    wr_char->length = length;
    // Auto Execute
    wr_char->auto_execute = true;
    // Value
    memcpy(&wr_char->value[0], value, length);

    // Send the message
    ke_msg_send(wr_char);
  }
}

void prf_gatt_write_ntf_ind(prf_env_t *prf_env, uint8_t conidx, uint16_t handle,
                            uint16_t ntf_ind_cfg) {
  uint8_t value[2];

  // put value in air format
  co_write16p((&value[0]), ntf_ind_cfg);
  // write value over GATT
  prf_gatt_write(prf_env, conidx, handle, value, 2, GATTC_WRITE);
}

uint8_t prf_check_svc_char_validity(uint8_t nb_chars,
                                    const struct prf_char_inf *chars,
                                    const struct prf_char_def *chars_req) {
  uint8_t status = GAP_ERR_NO_ERROR;
  uint8_t i;

  for (i = 0; ((i < nb_chars) && (status == GAP_ERR_NO_ERROR)); i++) {
    if (chars[i].char_hdl == ATT_INVALID_HANDLE) {
      // If Characteristic is not present, check requirements
      if (chars_req[i].req_flag == ATT_MANDATORY) {
        status = PRF_ERR_STOP_DISC_CHAR_MISSING;
      }
    } else {
      // If Characteristic is present, check properties
      if ((chars[i].prop & chars_req[i].prop_mand) != chars_req[i].prop_mand) {
        status = PRF_ERR_STOP_DISC_WRONG_CHAR_PROP;
      }
    }
  }

  return (status);
}

uint8_t
prf_check_svc_char_desc_validity(uint8_t descs_size,
                                 const struct prf_char_desc_inf *descs,
                                 const struct prf_char_desc_def *descs_req,
                                 const struct prf_char_inf *chars) {
  uint8_t status = GAP_ERR_NO_ERROR;
  uint8_t i;

  for (i = 0; ((i < descs_size) && (status == GAP_ERR_NO_ERROR)); i++) {
    if (descs[i].desc_hdl == ATT_INVALID_HANDLE) {
      // If Descriptor is missing, check if it is mandatory
      if (descs_req[i].req_flag == ATT_MANDATORY) {
        // Check if Char is present
        if (chars[descs_req[i].char_code].char_hdl != ATT_INVALID_HANDLE) {
          // Char. is present and descriptor not, error
          status = PRF_ERR_STOP_DISC_CHAR_MISSING;
        }
      }
    }
  }

  return (status);
}

void prf_extract_svc_info(const struct gattc_sdp_svc_ind *param,
                          uint8_t nb_chars,
                          const struct prf_char_def *chars_req,
                          struct prf_char_inf *chars, uint8_t nb_descs,
                          const struct prf_char_desc_def *descs_req,
                          struct prf_char_desc_inf *descs) {
  // Counters
  uint8_t svc_char;
  uint8_t svc_desc;
  uint8_t fnd_att;

  for (fnd_att = 0; fnd_att < (param->end_hdl - param->start_hdl); fnd_att++) {
    if (param->info[fnd_att].att_type == GATTC_SDP_ATT_CHAR) {
      uint16_t char_hdl = param->start_hdl + 1 + fnd_att;
      uint16_t val_hdl = param->info[fnd_att].att_char.handle;
      uint8_t val_prop = param->info[fnd_att].att_char.prop;
      uint8_t char_idx = fnd_att;

      // check that value handle is in a valid range
      if ((val_hdl <= param->end_hdl) &&
          (val_hdl > (param->start_hdl + fnd_att))) {
        // retrieve value index
        uint8_t val_idx = (val_hdl - param->start_hdl - 1);

        // Look over requested characteristics
        for (svc_char = 0; svc_char < nb_chars; svc_char++) {
          // check if attribute is valid
          if ((chars[svc_char].char_hdl == ATT_INVALID_HDL) &&
              attm_uuid16_comp((uint8_t *)param->info[val_idx].att.uuid,
                               param->info[val_idx].att.uuid_len,
                               chars_req[svc_char].uuid)) {
            // Save properties and handles
            chars[svc_char].char_hdl = char_hdl;
            chars[svc_char].val_hdl = val_hdl;
            chars[svc_char].prop = val_prop;

            // find end of characteristic handle and discover descriptors
            do {
              fnd_att++;

              // found a descriptor
              if (param->info[fnd_att].att_type == GATTC_SDP_ATT_DESC) {
                // Retrieve characteristic descriptor handle using UUID
                for (svc_desc = 0; svc_desc < nb_descs; svc_desc++) {
                  // check if it's expected descriptor
                  if ((descs[svc_desc].desc_hdl == ATT_INVALID_HANDLE) &&
                      (descs_req[svc_desc].char_code == svc_char) &&
                      (attm_uuid16_comp(
                          (uint8_t *)param->info[fnd_att].att.uuid,
                          param->info[fnd_att].att.uuid_len,
                          descs_req[svc_desc].uuid))) {
                    descs[svc_desc].desc_hdl = param->start_hdl + 1 + fnd_att;
                    // search for next descriptor
                    break;
                  }
                }
              }
            } while (((param->start_hdl + 1 + fnd_att) <= param->end_hdl) &&
                     (param->info[fnd_att].att_type != GATTC_SDP_ATT_CHAR) &&
                     (param->info[fnd_att].att_type != GATTC_SDP_INC_SVC));

            // return to previous valid value
            fnd_att--;
            // previous handle was end of the characteristic
            chars[svc_char].char_ehdl_off = fnd_att - char_idx;

            // search next characteristic
            break;
          }
        }
      }
    }
  }
}

#endif //(BLE_CLIENT_PRF)

#if (BLE_CLIENT_PRF || BLE_TIP_SERVER || BLE_AN_SERVER || BLE_PAS_SERVER)

void prf_client_att_info_rsp(prf_env_t *prf_env, uint8_t conidx,
                             uint16_t msg_id, uint8_t status,
                             struct gattc_read_ind const *read_ind) {
  // retrieve value length
  uint16_t length = 0;
  if (status == GAP_ERR_NO_ERROR) {
    length = read_ind->length;
  }

  // prepare response
  struct prf_att_info *rsp =
      KE_MSG_ALLOC_DYN(msg_id, prf_dst_task_get(prf_env, conidx),
                       prf_src_task_get(prf_env, conidx), prf_att_info, length);

  rsp->status = status;
  rsp->handle = ATT_INVALID_HDL;
  rsp->length = length;

  // set value array
  if (read_ind != NULL) {
    rsp->handle = read_ind->handle;
    memcpy(&(rsp->value[0]), &(read_ind->value[0]), length);
  }

  ke_msg_send(rsp);
}
#endif //(BLE_CLIENT_PRF || BLE_TIP_SERVER || BLE_AN_SERVER || BLE_PAS_SERVER)

#if ((BLE_SERVER_PRF || BLE_CLIENT_PRF))

uint8_t prf_pack_date_time(uint8_t *packed_date,
                           const struct prf_date_time *date_time) {
  co_write16p(packed_date, date_time->year);
  *(packed_date + 2) = date_time->month;
  *(packed_date + 3) = date_time->day;
  *(packed_date + 4) = date_time->hour;
  *(packed_date + 5) = date_time->min;
  *(packed_date + 6) = date_time->sec;

  return 7;
}
uint8_t prf_unpack_date_time(uint8_t *packed_date,
                             struct prf_date_time *date_time) {
  date_time->year = co_read16p(&(packed_date[0]));
  date_time->month = packed_date[2];
  date_time->day = packed_date[3];
  date_time->hour = packed_date[4];
  date_time->min = packed_date[5];
  date_time->sec = packed_date[6];

  return 7;
}

#endif /* ((BLE_SERVER_PRF || BLE_CLIENT_PRF)) */
#endif // (BLE_PROFILES)
/// @} PRF_UTILS
