/**
 ****************************************************************************************
 * @addtogroup LANC
 * @{
 ****************************************************************************************
 */

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

#if (BLE_LN_COLLECTOR)
#include "lan_common.h"

#include "lanc.h"
#include "lanc_task.h"

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

/*
 * GLOBAL VARIABLES DECLARATION
 ****************************************************************************************
 */

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

/**
 ****************************************************************************************
 * @brief Initialization of the LANC 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 lanc_init(struct prf_task_env *env, uint16_t *start_hdl,
                         uint16_t app_task, uint8_t sec_lvl, void *params) {
  uint8_t idx;
  //-------------------- allocate memory required for the profile
  //---------------------

  struct lanc_env_tag *lanc_env = (struct lanc_env_tag *)ke_malloc(
      sizeof(struct lanc_env_tag), KE_MEM_ATT_DB);

  // allocate LANC required environment variable
  env->env = (prf_env_t *)lanc_env;

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

  // initialize environment variable
  env->id = TASK_ID_LANC;
  lanc_task_init(&(env->desc));

  for (idx = 0; idx < LANC_IDX_MAX; idx++) {
    lanc_env->env[idx] = NULL;
    // service is ready, go into an Idle state
    ke_state_set(KE_BUILD_ID(env->task, idx), LANC_FREE);
  }

  return GAP_ERR_NO_ERROR;
}

/**
 ****************************************************************************************
 * @brief Clean-up connection dedicated environment parameters
 * This function performs cleanup of ongoing operations
 * @param[in|out]    env        Collector or Service allocated environment data.
 * @param[in]        conidx     Connection index
 * @param[in]        reason     Detach reason
 ****************************************************************************************
 */
static void lanc_cleanup(struct prf_task_env *env, uint8_t conidx,
                         uint8_t reason) {
  struct lanc_env_tag *lanc_env = (struct lanc_env_tag *)env->env;

  // clean-up environment variable allocated for task instance
  if (lanc_env->env[conidx] != NULL) {
    if (lanc_env->env[conidx]->operation != NULL) {
      ke_free(ke_param2msg(lanc_env->env[conidx]->operation));
    }
    ke_timer_clear(LANC_TIMEOUT_TIMER_IND,
                   prf_src_task_get(&lanc_env->prf_env, conidx));

    ke_free(lanc_env->env[conidx]);
    lanc_env->env[conidx] = NULL;
  }

  /* Put LAN Client in Free state */
  ke_state_set(KE_BUILD_ID(env->task, conidx), LANC_FREE);
}

/**
 ****************************************************************************************
 * @brief Destruction of the LANC 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 lanc_destroy(struct prf_task_env *env) {
  uint8_t idx;
  struct lanc_env_tag *lanc_env = (struct lanc_env_tag *)env->env;

  // cleanup environment variable for each task instances
  for (idx = 0; idx < LANC_IDX_MAX; idx++) {
    lanc_cleanup(env, idx, 0);
  }

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

/**
 ****************************************************************************************
 * @brief Handles Connection creation
 *
 * @param[in|out]    env        Collector or Service allocated environment data.
 * @param[in]        conidx     Connection index
 ****************************************************************************************
 */
static void lanc_create(struct prf_task_env *env, uint8_t conidx) {
  /* Put LAN Client in Idle state */
  ke_state_set(KE_BUILD_ID(env->task, conidx), LANC_IDLE);
}

/// LANC Task interface required by profile manager
const struct prf_task_cbs lanc_itf = {
    lanc_init,
    lanc_destroy,
    lanc_create,
    lanc_cleanup,
};

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

const struct prf_task_cbs *lanc_prf_itf_get(void) { return &lanc_itf; }

void lanc_enable_rsp_send(struct lanc_env_tag *lanc_env, uint8_t conidx,
                          uint8_t status) {
  // Send to APP the details of the discovered attributes on LANS
  struct lanc_enable_rsp *rsp = KE_MSG_ALLOC(
      LANC_ENABLE_RSP, prf_dst_task_get(&(lanc_env->prf_env), conidx),
      prf_src_task_get(&(lanc_env->prf_env), conidx), lanc_enable_rsp);
  rsp->status = status;

  if (status == GAP_ERR_NO_ERROR) {
    rsp->lns = lanc_env->env[conidx]->lans;
    // Register LANC task in gatt for indication/notifications
    prf_register_atthdl2gatt(&(lanc_env->prf_env), conidx,
                             &(lanc_env->env[conidx]->lans.svc));
    // Go to connected state
    ke_state_set(prf_src_task_get(&(lanc_env->prf_env), conidx), LANC_IDLE);
  }

  ke_msg_send(rsp);
}

void lanc_send_no_conn_cmp_evt(uint8_t src_id, uint8_t dest_id,
                               uint8_t operation) {
  // Send the message
  struct lanc_cmp_evt *evt =
      KE_MSG_ALLOC(LANC_CMP_EVT, dest_id, src_id, lanc_cmp_evt);

  evt->operation = operation;
  evt->status = PRF_ERR_REQ_DISALLOWED;

  ke_msg_send(evt);
}

void lanc_send_cmp_evt(struct lanc_env_tag *lanc_env, uint8_t conidx,
                       uint8_t operation, uint8_t status) {
  // Free the stored operation if needed
  if (lanc_env->env[conidx]->operation != NULL) {
    ke_msg_free(ke_param2msg(lanc_env->env[conidx]->operation));
    lanc_env->env[conidx]->operation = NULL;
  }

  // Go back to the CONNECTED state if the state is busy
  if (ke_state_get(prf_src_task_get(&(lanc_env->prf_env), conidx)) ==
      LANC_BUSY) {
    ke_state_set(prf_src_task_get(&(lanc_env->prf_env), conidx), LANC_IDLE);
  }

  // Send the message
  struct lanc_cmp_evt *evt = KE_MSG_ALLOC(
      LANC_CMP_EVT, prf_dst_task_get(&(lanc_env->prf_env), conidx),
      prf_src_task_get(&(lanc_env->prf_env), conidx), lanc_cmp_evt);

  evt->operation = operation;
  evt->status = status;

  ke_msg_send(evt);
}

uint16_t lanc_get_read_handle_req(struct lanc_env_tag *lanc_env, uint8_t conidx,
                                  struct lanc_read_cmd *param) {
  // Attribute Handle
  uint16_t handle = ATT_INVALID_SEARCH_HANDLE;

  switch (param->read_code) {
  // Read LN Feature
  case (LANC_RD_LN_FEAT): {
    handle = lanc_env->env[conidx]->lans.chars[LANP_LANS_LN_FEAT_CHAR].val_hdl;
  } break;

  // Read Position Quality
  case (LANC_RD_POS_Q): {
    handle = lanc_env->env[conidx]->lans.chars[LANP_LANS_POS_Q_CHAR].val_hdl;
  } break;

  // Read Location and Speed Characteristic Client Char. Cfg. Descriptor Value
  case (LANC_RD_WR_LOC_SPEED_CL_CFG): {
    handle =
        lanc_env->env[conidx]->lans.descs[LANC_DESC_LOC_SPEED_CL_CFG].desc_hdl;
  } break;

  // Read Unread Alert Characteristic Client Char. Cfg. Descriptor Value
  case (LANC_RD_WR_LN_CTNL_PT_CFG): {
    handle =
        lanc_env->env[conidx]->lans.descs[LANC_DESC_LN_CTNL_PT_CL_CFG].desc_hdl;
  } break;

  // Read Navigation Characteristic Server Char. Cfg. Descriptor Value
  case (LANC_RD_WR_NAVIGATION_CFG): {
    handle =
        lanc_env->env[conidx]->lans.descs[LANC_DESC_NAVIGATION_CL_CFG].desc_hdl;
  } break;

  default: {
    handle = ATT_INVALID_SEARCH_HANDLE;
  } break;
  }

  return handle;
}

uint8_t lanc_get_write_desc_handle_req(uint8_t conidx,
                                       struct lanc_cfg_ntfind_cmd *param,
                                       struct lanc_env_tag *lanc_env,
                                       uint16_t *handle) {
  // Status
  uint8_t status = GAP_ERR_NO_ERROR;

  switch (param->desc_code) {
  // Write Location and speed Characteristic Client Char. Cfg. Descriptor Value
  case (LANC_RD_WR_LOC_SPEED_CL_CFG): {
    if (param->ntfind_cfg <= PRF_CLI_START_NTF) {
      *handle = lanc_env->env[conidx]
                    ->lans.descs[LANC_DESC_LOC_SPEED_CL_CFG]
                    .desc_hdl;

      // The descriptor is mandatory
      ASSERT_ERR(*handle != ATT_INVALID_SEARCH_HANDLE);
    } else {
      status = PRF_ERR_INVALID_PARAM;
    }
  } break;

  // Write LN Control Point Characteristic Client Char. Cfg. Descriptor Value
  case (LANC_RD_WR_LN_CTNL_PT_CFG): {
    if ((param->ntfind_cfg == PRF_CLI_STOP_NTFIND) ||
        (param->ntfind_cfg == PRF_CLI_START_IND)) {
      *handle = lanc_env->env[conidx]
                    ->lans.descs[LANC_DESC_LN_CTNL_PT_CL_CFG]
                    .desc_hdl;

      if (*handle == ATT_INVALID_SEARCH_HANDLE) {
        // The descriptor has not been found.
        status = PRF_ERR_INEXISTENT_HDL;
      }
    } else {
      status = PRF_ERR_INVALID_PARAM;
    }
  } break;

  // Write Navigation Characteristic Client Char. Cfg. Descriptor Value
  case (LANC_RD_WR_NAVIGATION_CFG): {
    if (param->ntfind_cfg <= PRF_CLI_START_NTF) {
      *handle = lanc_env->env[conidx]
                    ->lans.descs[LANC_DESC_NAVIGATION_CL_CFG]
                    .desc_hdl;

      if (*handle == ATT_INVALID_SEARCH_HANDLE) {
        // The descriptor has not been found.
        status = PRF_ERR_INEXISTENT_HDL;
      }
    } else {
      status = PRF_ERR_INVALID_PARAM;
    }
  } break;

  default: {
    status = PRF_ERR_INVALID_PARAM;
  } break;
  }

  return (status);
}

uint8_t lanc_unpack_loc_speed_ind(uint8_t conidx,
                                  struct gattc_event_ind const *param,
                                  struct lanc_env_tag *lanc_env) {
  // Offset
  uint8_t offset = LANP_LAN_LOC_SPEED_MIN_LEN;

  // LN Measurement value has been received
  struct lanc_value_ind *ind = KE_MSG_ALLOC(
      LANC_VALUE_IND, prf_dst_task_get(&(lanc_env->prf_env), conidx),
      prf_src_task_get(&(lanc_env->prf_env), conidx), lanc_value_ind);

  // Attribute code
  ind->att_code = LANC_NTF_LOC_SPEED;

  if ((param->length >= LANP_LAN_LOC_SPEED_MIN_LEN) &&
      (param->length <= LANP_LAN_LOC_SPEED_MAX_LEN)) {
    // Flags
    ind->value.loc_speed.flags = co_read16p(&param->value[0]);

    if (ind->value.loc_speed.flags & LANP_LSPEED_INST_SPEED_PRESENT) {
      // Unpack instantaneous speed
      ind->value.loc_speed.inst_speed = co_read16p(&param->value[offset]);
      offset += 2;
    }

    if (ind->value.loc_speed.flags & LANP_LSPEED_TOTAL_DISTANCE_PRESENT) {
      // Unpack Total distance (24 bits)
      ind->value.loc_speed.total_dist = co_read24p(&param->value[offset]);
      offset += 3;
    }

    if (ind->value.loc_speed.flags & LANP_LSPEED_LOCATION_PRESENT) {
      // Unpack Location
      ind->value.loc_speed.latitude = co_read32p(&param->value[offset]);
      offset += 4;
      ind->value.loc_speed.longitude = co_read32p(&param->value[offset]);
      offset += 4;
    }

    if (ind->value.loc_speed.flags & LANP_LSPEED_ELEVATION_PRESENT) {
      // Unpack Elevation (24 bits)
      ind->value.loc_speed.elevation = co_read24p(&param->value[offset]);
      offset += 3;
    }

    if (ind->value.loc_speed.flags & LANP_LSPEED_HEADING_PRESENT) {
      // Unpack heading
      ind->value.loc_speed.heading = co_read16p(&param->value[offset]);
      offset += 2;
    }

    if (ind->value.loc_speed.flags & LANP_LSPEED_ROLLING_TIME_PRESENT) {
      // Unpack rolling time
      ind->value.loc_speed.rolling_time = param->value[offset];
      offset++;
    }

    if (ind->value.loc_speed.flags & LANP_LSPEED_UTC_TIME_PRESENT) {
      // Unpack UTC time
      offset += prf_unpack_date_time((uint8_t *)&(param->value[offset]),
                                     &(ind->value.loc_speed.date_time));
    }
  }

  // Send the message
  ke_msg_send(ind);

  return offset;
}

uint8_t lanc_unpack_navigation_ind(uint8_t conidx,
                                   struct gattc_event_ind const *param,
                                   struct lanc_env_tag *lanc_env) {
  // Offset
  uint8_t offset = LANP_LAN_NAVIGATION_MIN_LEN;

  // LN Measurement value has been received
  struct lanc_value_ind *ind = KE_MSG_ALLOC(
      LANC_VALUE_IND, prf_dst_task_get(&(lanc_env->prf_env), conidx),
      prf_src_task_get(&(lanc_env->prf_env), conidx), lanc_value_ind);

  // Attribute code
  ind->att_code = LANC_NTF_NAVIGATION;

  if ((param->length >= LANP_LAN_NAVIGATION_MIN_LEN) &&
      (param->length <= LANP_LAN_NAVIGATION_MAX_LEN)) {
    // Flags
    ind->value.navigation.flags = co_read16p(&param->value[0]);
    // Bearing
    ind->value.navigation.bearing = co_read16p(&param->value[2]);
    // Heading
    ind->value.navigation.heading = co_read16p(&param->value[4]);

    if (ind->value.navigation.flags & LANP_NAVI_REMAINING_DIS_PRESENT) {
      // Unpack remaining distance (24 bits)
      ind->value.navigation.remaining_distance =
          co_read24p(&param->value[offset]);
      offset += 3;
    }

    if (ind->value.navigation.flags & LANP_NAVI_REMAINING_VER_DIS_PRESENT) {
      // Unpack remaining vertical distance (24 bits)
      ind->value.navigation.remaining_ver_distance =
          co_read24p(&param->value[offset]);
      offset += 3;
    }

    if (ind->value.navigation.flags &
        LANP_NAVI_ESTIMATED_TIME_OF_ARRIVAL_PRESENT) {
      // Unpack time
      offset +=
          prf_unpack_date_time((uint8_t *)&(param->value[offset]),
                               &(ind->value.navigation.estimated_arrival_time));
    }
  }

  // Send the message
  ke_msg_send(ind);

  return offset;
}

uint8_t lanc_unpack_pos_q_ind(struct gattc_read_ind const *param,
                              struct lanc_value_ind *ind) {
  /*----------------------------------------------------
   * Unpack Position Quality ---------------------------
   *----------------------------------------------------*/
  // Offset
  uint8_t offset = LANP_LAN_POSQ_MIN_LEN;

  // Flags
  ind->value.pos_q.flags = co_read16p(&param->value[0]);

  if (ind->value.pos_q.flags &
      LANP_POSQ_NUMBER_OF_BEACONS_IN_SOLUTION_PRESENT) {
    // Unpack beacons in solution
    ind->value.pos_q.n_beacons_solution = param->value[offset];
    offset++;
  }

  if (ind->value.pos_q.flags & LANP_POSQ_NUMBER_OF_BEACONS_IN_VIEW_PRESENT) {
    // Unpack beacons in view
    ind->value.pos_q.n_beacons_view = param->value[offset];
    offset++;
  }

  if (ind->value.pos_q.flags & LANP_POSQ_TIME_TO_FIRST_FIX_PRESENT) {
    // Unpack time first fix
    ind->value.pos_q.time_first_fix = co_read16p(&param->value[offset]);
    offset += 2;
  }

  if (ind->value.pos_q.flags & LANP_POSQ_EHPE_PRESENT) {
    // Unpack ehpe
    ind->value.pos_q.ehpe = co_read32p(&param->value[offset]);
    offset += 4;
  }

  if (ind->value.pos_q.flags & LANP_POSQ_EVPE_PRESENT) {
    // Unpack evpe
    ind->value.pos_q.evpe = co_read32p(&param->value[offset]);
    offset += 4;
  }

  if (ind->value.pos_q.flags & LANP_POSQ_HDOP_PRESENT) {
    // Unpack hdop
    ind->value.pos_q.hdop = param->value[offset];
    offset++;
  }

  if (ind->value.pos_q.flags & LANP_POSQ_VDOP_PRESENT) {
    // Unpack vdop
    ind->value.pos_q.vdop = param->value[offset];
    offset++;
  }

  return offset;
}

uint8_t lanc_pack_ln_ctnl_pt_req(struct lanc_ln_ctnl_pt_cfg_req *param,
                                 uint8_t *req, uint8_t *status) {
  // Request Length
  uint8_t req_len = LANP_LAN_LN_CNTL_PT_REQ_MIN_LEN;

  // Set the operation code
  req[0] = param->ln_ctnl_pt.op_code;

  // Fulfill the message according to the operation code
  switch (param->ln_ctnl_pt.op_code) {
  case (LANP_LN_CTNL_PT_SET_CUMUL_VALUE): {
    // Set the cumulative value (24 bits)
    co_write24p(&req[req_len], param->ln_ctnl_pt.value.cumul_val);
    // Update length
    req_len += 3;
  } break;

  case (LANP_LN_CTNL_PT_MASK_LSPEED_CHAR_CT): {
    // Set mask content
    co_write16p(&req[req_len], param->ln_ctnl_pt.value.mask_content);
    // Update length
    req_len += 2;
  } break;

  case (LANP_LN_CTNL_PT_NAVIGATION_CONTROL): {
    // Set control value
    req[req_len] = param->ln_ctnl_pt.value.control_value;
    // Update length
    req_len++;
  } break;

  case (LANP_LN_CTNL_PT_REQ_NAME_OF_ROUTE):
  case (LANP_LN_CTNL_PT_SELECT_ROUTE): {
    // Set route number
    co_write16p(&req[req_len], param->ln_ctnl_pt.value.route_number);
    // Update length
    req_len += 2;
  } break;

  case (LANP_LN_CTNL_PT_SET_FIX_RATE): {
    // Set the fix rate
    req[req_len] = param->ln_ctnl_pt.value.fix_rate;
    // Update length
    req_len++;
  } break;

  case (LANP_LN_CTNL_PT_SET_ELEVATION): {
    // Set elevation (24 bits)
    co_write24p(&req[req_len], param->ln_ctnl_pt.value.elevation);
    // Update length
    req_len += 3;
  } break;

  case (LANP_LN_CTNL_PT_REQ_NUMBER_OF_ROUTES): {
    // Nothing more to do
  } break;

  default: {
    *status = PRF_ERR_INVALID_PARAM;
  } break;
  }

  return req_len;
}

uint8_t lanc_unpack_ln_ctln_pt_ind(struct gattc_event_ind const *param,
                                   ke_task_id_t src, ke_task_id_t dest) {
  // Offset
  uint8_t offset = LANP_LAN_LN_CNTL_PT_RSP_MIN_LEN;

  // Control Point value has been received
  struct lanc_ln_ctnl_pt_rsp *ind = KE_MSG_ALLOC_DYN(
      LANC_LN_CTNL_PT_RSP, dest, src, lanc_ln_ctnl_pt_rsp, param->length);
  // Requested operation code
  ind->rsp.req_op_code = param->value[1];
  // Response value
  ind->rsp.resp_value = param->value[2];

  if ((ind->rsp.resp_value == LANP_LN_CTNL_PT_RESP_SUCCESS) &&
      (param->length >= 3)) {
    switch (ind->rsp.req_op_code) {
    case (LANP_LN_CTNL_PT_REQ_NUMBER_OF_ROUTES): {
      ind->rsp.value.number_of_routes = co_read16p(&param->value[offset]);
      offset += 2;

    } break;

    case (LANP_LN_CTNL_PT_REQ_NAME_OF_ROUTE): {
      // Get the length of the route
      ind->rsp.value.route.length = (param->length - 3);
      for (int i = 0; i < ind->rsp.value.route.length; i++) {
        ind->rsp.value.route.name[i] = param->value[i + 3];
        offset++;
      }
    } break;

    case (LANP_LN_CTNL_PT_SET_CUMUL_VALUE):
    case (LANP_LN_CTNL_PT_MASK_LSPEED_CHAR_CT):
    case (LANP_LN_CTNL_PT_NAVIGATION_CONTROL):
    case (LANP_LN_CTNL_PT_SELECT_ROUTE):
    case (LANP_LN_CTNL_PT_SET_FIX_RATE):
    case (LANP_LN_CTNL_PT_SET_ELEVATION): {
      // No parameters
    } break;

    default: {

    } break;
    }
  }

  // Send the message
  ke_msg_send(ind);

  return offset;
}
#endif //(BLE_LN_COLLECTOR)

/// @} LAN
