/*
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under
 * the OAI Public License, Version 1.1  (the "License"); you may not use this file
 * except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.openairinterface.org/?page_id=698
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *-------------------------------------------------------------------------------
 * For more information about the OpenAirInterface (OAI) Software Alliance:
 *      contact@openairinterface.org
 */

#include "ran_func_kpm.h"
#include "ran_func_kpm_subs.h"
#include "ran_e2sm_ue_id.h"

#include "openair2/E1AP/e1ap_common.h"
#include "openair2/E2AP/flexric/src/util/time_now_us.h"
#include "openair2/F1AP/f1ap_ids.h"

static pthread_once_t once_kpm_mutex = PTHREAD_ONCE_INIT;

static void init_once_kpm(void)
{
  init_kpm_subs_data();
}

static ngran_node_t get_e2_node_type(void)
{
  ngran_node_t node_type = 0;

#if defined(NGRAN_GNB_DU) && defined(NGRAN_GNB_CUUP) && defined(NGRAN_GNB_CUCP)
  node_type = RC.nrrrc[0]->node_type;
#elif defined (NGRAN_GNB_CUUP)
  node_type =  ngran_gNB_CUUP;
#endif

  return node_type;
}

typedef struct {
  size_t sz;
  ue_id_e2sm_t* ue_id;

  // Optional
  // only used to retreive MAC/RLC stats
  NR_UE_info_t** ue_info_list;
}arr_ue_id_t;

static meas_data_lst_t fill_kpm_meas_data_item(const meas_info_format_1_lst_t* meas_info_lst, const size_t len, const uint32_t gran_period_ms, cudu_ue_info_pair_t ue_info, const size_t ue_idx)
{
  meas_data_lst_t data_item = {0};

  // Measurement Record
  data_item.meas_record_len = len;  // record data list length corresponds to info list length from action definition

  data_item.meas_record_lst = calloc(len, sizeof(meas_record_lst_t));
  assert(data_item.meas_record_lst != NULL && "Memory exhausted");

  for (size_t i = 0; i < len; i++) {
    // Measurement Type as requested in Action Definition
    assert(meas_info_lst[i].meas_type.type == NAME_MEAS_TYPE && "Only NAME supported");

    char* meas_info_name_str = cp_ba_to_str(meas_info_lst[i].meas_type.name);

    data_item.meas_record_lst[i] = get_kpm_meas_value(meas_info_name_str, gran_period_ms, ue_info, ue_idx);

    free(meas_info_name_str);
  }

  return data_item;
}

static kpm_ind_msg_format_1_t fill_kpm_ind_msg_frm_1(cudu_ue_info_pair_t ue_info, const size_t ue_idx, const kpm_act_def_format_1_t* act_def_fr_1)
{
  kpm_ind_msg_format_1_t msg_frm_1 = {0};

  // Measurement Data contains a set of Meas Records, each collected at each granularity period
  msg_frm_1.meas_data_lst_len = 1;  /*  this value is equal to (kpm_ric_event_trigger_format_1.report_period_ms/act_def_fr_1->gran_period_ms)
                                        please, check their values in xApp */
  
  msg_frm_1.meas_data_lst = calloc(msg_frm_1.meas_data_lst_len, sizeof(*msg_frm_1.meas_data_lst));
  assert(msg_frm_1.meas_data_lst != NULL && "Memory exhausted" );

  msg_frm_1.meas_data_lst[0] = fill_kpm_meas_data_item(act_def_fr_1->meas_info_lst,
                                                       act_def_fr_1->meas_info_lst_len,
                                                       act_def_fr_1->gran_period_ms,
                                                       ue_info,
                                                       ue_idx);

  // Measurement Information - OPTIONAL
  msg_frm_1.meas_info_lst_len = act_def_fr_1->meas_info_lst_len;
  msg_frm_1.meas_info_lst = calloc(msg_frm_1.meas_info_lst_len, sizeof(meas_info_format_1_lst_t));
  assert(msg_frm_1.meas_info_lst != NULL && "Memory exhausted");
  for (size_t i = 0; i < msg_frm_1.meas_info_lst_len; i++) {
    msg_frm_1.meas_info_lst[i] = cp_meas_info_format_1_lst(&act_def_fr_1->meas_info_lst[i]);
  }

  return msg_frm_1;
}

static cudu_ue_info_pair_t fill_ue_related_info(arr_ue_id_t* arr_ue_id, const size_t ue_idx)
{
  cudu_ue_info_pair_t ue_info = {0};

  if (arr_ue_id->ue_id[ue_idx].type == GNB_UE_ID_E2SM) {
    ue_info.rrc_ue_id = *arr_ue_id->ue_id[ue_idx].gnb.ran_ue_id;  // rrc_ue_id
    ue_info.ue = arr_ue_id->ue_info_list[ue_idx];
  } else if (arr_ue_id->ue_id[ue_idx].type == GNB_CU_UP_UE_ID_E2SM) {
    /* in OAI implementation, CU-UP ue id = CU-CP ue id
                           => CU-UP ue id = rrc_ue_id, but it should not be the case by the spec */
    ue_info.rrc_ue_id = *arr_ue_id->ue_id[ue_idx].gnb_cu_up.ran_ue_id;  // cucp_ue_id = rrc_ue_id
  } else if (arr_ue_id->ue_id[ue_idx].type == GNB_DU_UE_ID_E2SM) {
    ue_info.rrc_ue_id = *arr_ue_id->ue_id[ue_idx].gnb_du.ran_ue_id;  // rrc_ue_id
    ue_info.ue = arr_ue_id->ue_info_list[ue_idx];
  }
  
  return ue_info;
}

static kpm_ind_msg_format_3_t fill_kpm_ind_msg_frm_3(arr_ue_id_t* arr_ue_id, const kpm_act_def_format_1_t* act_def_fr_1)
{
  assert(act_def_fr_1 != NULL);

  kpm_ind_msg_format_3_t msg_frm_3 = {0};

  // Fill UE Measurement Reports
  msg_frm_3.ue_meas_report_lst_len = arr_ue_id->sz;
  msg_frm_3.meas_report_per_ue = calloc(msg_frm_3.ue_meas_report_lst_len, sizeof(meas_report_per_ue_t));
  assert(msg_frm_3.meas_report_per_ue != NULL && "Memory exhausted");

  for (size_t i = 0; i<msg_frm_3.ue_meas_report_lst_len; i++) {
    // Fill UE ID data
    msg_frm_3.meas_report_per_ue[i].ue_meas_report_lst = cp_ue_id_e2sm(&arr_ue_id->ue_id[i]);

    // Fill UE related info
    cudu_ue_info_pair_t ue_info = fill_ue_related_info(arr_ue_id, i);

    // Fill UE measurements
    msg_frm_3.meas_report_per_ue[i].ind_msg_format_1 = fill_kpm_ind_msg_frm_1(ue_info, i, act_def_fr_1);
  }

  return msg_frm_3;
}

static void capture_sst_sd(test_cond_value_t* test_cond_value, uint8_t *sst, uint32_t **sd)
{
  DevAssert(sst != NULL);
  DevAssert(sd != NULL);

  // S-NSSAI is an OCTET_STRING, as defined by spec
  switch (test_cond_value->type) {
    case OCTET_STRING_TEST_COND_VALUE: {
      if (test_cond_value->octet_string_value->len == 1) {
        *sst = test_cond_value->octet_string_value->buf[0];
        *sd = NULL;
      } else {
        DevAssert(test_cond_value->octet_string_value->len == 4);
        uint8_t *buf = test_cond_value->octet_string_value->buf;
        *sst = buf[0];
        *sd = malloc(**sd);
        **sd = buf[1] << 16 | buf[2] << 8 | buf[3];
      }
      break;
    }
    default:
      AssertFatal(false, "test condition value %d impossible\n", test_cond_value->type);
  }
}

static bool nssai_matches(nssai_t a_nssai, uint8_t b_sst, const uint32_t *b_sd)
{
  if (b_sd == NULL) {
    return a_nssai.sst == b_sst && a_nssai.sd == 0xffffff;
  } else {
    AssertFatal(*b_sd <= 0xffffff, "illegal SD %d\n", *b_sd);
    return a_nssai.sst == b_sst && a_nssai.sd == *b_sd;
  }
}

static arr_ue_id_t filter_ues_by_s_nssai_in_cu(const test_info_lst_t test_info)
{
  assert(test_info.S_NSSAI == TRUE_TEST_COND_TYPE && "Must be true");
  assert(test_info.test_cond != NULL && "Must be filled, even though it's optional by the specification");
  assert(*test_info.test_cond == EQUAL_TEST_COND && "Currently support EQUAL test condition");
  assert(test_info.test_cond_value != NULL && "Must be filled, even though it's optional by the specification");
  
  uint8_t sst = 0;
  uint32_t *sd = NULL;
  capture_sst_sd(test_info.test_cond_value, &sst, &sd);

  arr_ue_id_t arr_ue_id = {.sz = 0};
  arr_ue_id.ue_id = calloc(MAX_MOBILES_PER_GNB, sizeof(ue_id_e2sm_t));
  assert(arr_ue_id.ue_id != NULL);

  struct rrc_gNB_ue_context_s* rrc_ue_context = NULL;
  RB_FOREACH(rrc_ue_context, rrc_nr_ue_tree_s, &RC.nrrrc[0]->rrc_ue_head) {
    gNB_RRC_UE_t *ue = &rrc_ue_context->ue_context;
    for (int p = 0; p < ue->nb_of_pdusessions; ++p) {
      pdusession_t *pdu = &ue->pduSession[p].param;
      if (nssai_matches(pdu->nssai, sst, sd)) {
        arr_ue_id.ue_id[arr_ue_id.sz] = fill_ue_id_data[ngran_gNB_CU](ue, 0, 0);

        arr_ue_id.sz++;
        break;
      }
    }
    AssertFatal(arr_ue_id.sz < MAX_MOBILES_PER_GNB, "cannot have more UEs than global UE number maximum\n");
  }

  free(sd); // if NULL, nothing happens

  return arr_ue_id;
}

static arr_ue_id_t filter_ues_by_s_nssai_in_cuup(const test_info_lst_t test_info)
{
  assert(test_info.S_NSSAI == TRUE_TEST_COND_TYPE && "Must be true");
  assert(test_info.test_cond != NULL && "Must be filled, even though it's optional by the specification");
  assert(*test_info.test_cond == EQUAL_TEST_COND && "Currently support EQUAL test condition");
  assert(test_info.test_cond_value != NULL && "Must be filled, even though it's optional by the specification");
  
  uint8_t sst = 0;
  uint32_t *sd = NULL;
  capture_sst_sd(test_info.test_cond_value, &sst, &sd);

  arr_ue_id_t arr_ue_id = {.sz = 0};
  arr_ue_id.ue_id = calloc(MAX_MOBILES_PER_GNB, sizeof(ue_id_e2sm_t));
  assert(arr_ue_id.ue_id != NULL);

  // Get NSSAI info from E1 context
  const instance_t e1_inst = 0;
  const e1ap_upcp_inst_t *e1inst = getCxtE1(e1_inst);

  // currently the CU-UP does not store the slices, so we can only do this "coarse filtering"
  for(int s = 0; s < e1inst->cuup.setupReq.plmn[0].supported_slices; s++) {
    if (nssai_matches(e1inst->cuup.setupReq.plmn[0].slice[s], sst, sd)) {
      ue_id_t *pdcp_ue_id_list = calloc(MAX_MOBILES_PER_GNB, sizeof(ue_id_t));
      assert(pdcp_ue_id_list != NULL);

      arr_ue_id.sz = nr_pdcp_get_num_ues(pdcp_ue_id_list, MAX_MOBILES_PER_GNB);
      for (size_t i = 0; i < arr_ue_id.sz; i++) {
        arr_ue_id.ue_id[i] = fill_ue_id_data[ngran_gNB_CUUP](NULL, 0, pdcp_ue_id_list[i]);
      }
      free(pdcp_ue_id_list);
    }
  }

  free(sd); // if NULL, nothing happens

  return arr_ue_id;
}

static arr_ue_id_t filter_ues_by_s_nssai_in_du_or_monolithic(const test_info_lst_t test_info)
{
  assert(test_info.S_NSSAI == TRUE_TEST_COND_TYPE && "Must be true");
  assert(test_info.test_cond != NULL && "Must be filled, even though it's optional by the specification");
  assert(*test_info.test_cond == EQUAL_TEST_COND && "Currently support EQUAL test condition");
  assert(test_info.test_cond_value != NULL && "Must be filled, even though it's optional by the specification");
  
  uint8_t sst = 0;
  uint32_t *sd = NULL;
  capture_sst_sd(test_info.test_cond_value, &sst, &sd);

  arr_ue_id_t arr_ue_id = {.sz = 0};
  arr_ue_id.ue_id = calloc(MAX_MOBILES_PER_GNB, sizeof(ue_id_e2sm_t));
  assert(arr_ue_id.ue_id != NULL);

  arr_ue_id.ue_info_list = calloc(MAX_MOBILES_PER_GNB, sizeof(*arr_ue_id.ue_info_list));
  assert(arr_ue_id.ue_info_list != NULL);

  const ngran_node_t node_type = get_e2_node_type();

  // Take MAC info
  UE_iterator (RC.nrmac[0]->UE_info.list, ue) {
    NR_UE_sched_ctrl_t *sched_ctrl = &ue->UE_sched_ctrl;
    // UE matches if any of its DRBs matches
    for (size_t l = 0; l < seq_arr_size(&sched_ctrl->lc_config); ++l) {
      const nr_lc_config_t *c = seq_arr_at(&sched_ctrl->lc_config, l);
      if (nssai_matches(c->nssai, sst, sd)) {
        if (node_type == ngran_gNB_DU) {
          f1_ue_data_t rrc_ue_id = du_get_f1_ue_data(ue->rnti); // gNB CU UE ID = rrc_ue_id
          arr_ue_id.ue_id[arr_ue_id.sz] = fill_ue_id_data[ngran_gNB_DU](NULL, rrc_ue_id.secondary_ue, 0);
        } else {
          rrc_gNB_ue_context_t* rrc_ue_context = rrc_gNB_get_ue_context_by_rnti(RC.nrrrc[0], -1, ue->rnti);
          arr_ue_id.ue_id[arr_ue_id.sz] = fill_ue_id_data[ngran_gNB](&rrc_ue_context->ue_context, 0, 0);
        }

        // store NR_UE_info_t
        arr_ue_id.ue_info_list[arr_ue_id.sz] = ue;

        arr_ue_id.sz++;
        break;
      }
    }
    AssertFatal(arr_ue_id.sz < MAX_MOBILES_PER_GNB, "cannot have more UEs than global UE number maximum\n");
  }

  free(sd); // if NULL, nothing happens

  return arr_ue_id;
}

typedef arr_ue_id_t (*ue_type_matcher)(const test_info_lst_t test_info);

static ue_type_matcher match_s_nssai_test_cond_type[END_NGRAN_NODE_TYPE] = 
{
  NULL,
  NULL,
  filter_ues_by_s_nssai_in_du_or_monolithic,
  NULL,
  NULL,
  filter_ues_by_s_nssai_in_cu,
  NULL,
  filter_ues_by_s_nssai_in_du_or_monolithic,
  NULL,
  NULL, // for CU-CP we don't use this
  filter_ues_by_s_nssai_in_cuup,
};

typedef ue_type_matcher (*filter_ues)[END_NGRAN_NODE_TYPE];

static filter_ues match_cond_arr[END_TEST_COND_TYPE_KPM_V2_01] = { 
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  &match_s_nssai_test_cond_type,
};


static kpm_ric_ind_hdr_format_1_t kpm_ind_hdr_frm_1(void)
{
  kpm_ric_ind_hdr_format_1_t hdr_frm_1 = {0};

  int64_t const t = time_now_us();
#if defined KPM_V2_03
  hdr_frm_1.collectStartTime = t/1000000; // seconds
#elif defined KPM_V3_00 
  hdr_frm_1.collectStartTime = t; // microseconds
#else
  static_assert(0!=0, "Undefined KPM SM Version");
#endif

  hdr_frm_1.fileformat_version = NULL;

  // Check E2 Node NG-RAN Type
  const ngran_node_t node_type = get_e2_node_type();
  const char* sender_name = get_ngran_name(node_type);
  hdr_frm_1.sender_name = calloc(1, sizeof(byte_array_t));
  *hdr_frm_1.sender_name = cp_str_to_ba(sender_name);

  return hdr_frm_1;
}

kpm_ind_hdr_t kpm_ind_hdr(void)
{
  kpm_ind_hdr_t hdr = {0};

  hdr.type = FORMAT_1_INDICATION_HEADER;
  hdr.kpm_ric_ind_hdr_format_1 = kpm_ind_hdr_frm_1();

  return hdr;
}

bool read_kpm_sm(void* data)
{
  assert(data != NULL);
  // assert(data->type == KPM_STATS_V3_0);

  kpm_rd_ind_data_t* const kpm = (kpm_rd_ind_data_t*)data;

  assert(kpm->act_def != NULL && "Cannot be NULL");

  // 7.8 Supported RIC Styles and E2SM IE Formats
  // Action Definition Format 4 corresponds to Indication Message Format 3
  switch (kpm->act_def->type) {
    case FORMAT_4_ACTION_DEFINITION: {
      kpm->ind.hdr = kpm_ind_hdr();

      kpm->ind.msg.type = FORMAT_3_INDICATION_MESSAGE;
      // Filter the UE by the test condition criteria
      kpm_act_def_format_4_t const* frm_4 = &kpm->act_def->frm_4; // 8.2.1.2.4
      for (size_t i = 0; i < frm_4->matching_cond_lst_len; i++) {
        const test_info_lst_t test_info = frm_4->matching_cond_lst[i].test_info_lst;
        const ngran_node_t node_type = get_e2_node_type();

        /* Based on the node type (gNB-mono, CU, DU,...) and matching condition (S-NSSAI, DL RSRP, GBR,...)
           get array of E2SM UE IDs with optionally their MAC info */
        ue_type_matcher fp_match_cond_type = (*match_cond_arr[test_info.test_cond_type])[node_type];
        arr_ue_id_t arr_ue_id = fp_match_cond_type(test_info);

        if (arr_ue_id.sz == 0) return false;
        kpm->ind.msg.frm_3 = fill_kpm_ind_msg_frm_3(&arr_ue_id, &frm_4->action_def_format_1);
      }
      break;
    }

    default: {
      AssertFatal(kpm->act_def->type == FORMAT_4_ACTION_DEFINITION, "Action Definition Format %d not yet implemented", kpm->act_def->type);
    }
  }
  
  return true;
}

static const char* kpm_meas_du[] = {
  "DRB.RlcSduDelayDl",
  "DRB.UEThpDl",
  "DRB.UEThpUl",
  "RRU.PrbTotDl",
  "RRU.PrbTotUl",
  NULL,
};

static const char* kpm_meas_gnb[] = {
  "DRB.PdcpSduVolumeDL",
  "DRB.PdcpSduVolumeUL",
  "DRB.RlcSduDelayDl",
  "DRB.UEThpDl",
  "DRB.UEThpUl",
  "RRU.PrbTotDl",
  "RRU.PrbTotUl",
  NULL,
};

static const char* kpm_meas_cuup[] = {
  "DRB.PdcpSduVolumeDL",
  "DRB.PdcpSduVolumeUL",
  NULL,
};

typedef const char** meas_list;

static const meas_list ran_def_kpm[END_NGRAN_NODE_TYPE] = {
    NULL,
    NULL,
    kpm_meas_gnb,
    NULL,
    NULL,
    kpm_meas_cuup, // at the moment, for CU, we use the same function as for CU-UP
    NULL,
    kpm_meas_du,
    NULL,
    NULL, // at the moment, no measurement is implemented in CU-CP
    kpm_meas_cuup,
};

void read_kpm_setup_sm(void* e2ap)
{
  assert(e2ap != NULL);
//  assert(e2ap->type == KPM_V3_0_AGENT_IF_E2_SETUP_ANS_V0);

  kpm_e2_setup_t* kpm = (kpm_e2_setup_t*)(e2ap);

  /* Fill the RAN Function Definition with currently supported measurements */
  
  // RAN Function Name is already filled in fill_ran_function_name() in kpm_sm_agent.c

  // Fill supported measurements depending on the E2 node
  // [1, 65535]
  // 3GPP TS 28.552
  const ngran_node_t node_type = get_e2_node_type();
  const char** kpm_meas = ran_def_kpm[node_type];
  if (kpm_meas == NULL) return; // e.g. CU-CP node, doesn't support any measurements
  size_t sz = 0;
  while (kpm_meas[sz] != NULL) {
    sz++;
  }

  // Sequence of Report styles
  const size_t sz_report = 1;
  kpm->ran_func_def.sz_ric_report_style_list = sz_report;
  kpm->ran_func_def.ric_report_style_list = calloc(sz_report, sizeof(ric_report_style_item_t));
  assert(kpm->ran_func_def.ric_report_style_list != NULL && "Memory exhausted");

  ric_report_style_item_t* report_item = &kpm->ran_func_def.ric_report_style_list[0];

  report_item->report_style_type = STYLE_4_RIC_SERVICE_REPORT;
  const char report_style_name[] = "Common Condition-based, UE-level Measurement";
  report_item->report_style_name = cp_str_to_ba(report_style_name);
  report_item->act_def_format_type = FORMAT_4_ACTION_DEFINITION;

  report_item->meas_info_for_action_lst_len = sz;
  report_item->meas_info_for_action_lst = calloc(sz, sizeof(meas_info_for_action_lst_t));
  assert(report_item->meas_info_for_action_lst != NULL && "Memory exhausted");

  for(size_t i = 0; i < sz; i++) {
    meas_info_for_action_lst_t* meas_item = &report_item->meas_info_for_action_lst[i];
    // 8.3.9
    meas_item->name = cp_str_to_ba(kpm_meas[i]);

    // 8.3.10  -  OPTIONAL
    meas_item->id = NULL;

    // 8.3.26  -  OPTIONAL
    meas_item->bin_range_def = NULL;
  } 

  // Sequence of Event Trigger styles
  const size_t sz_ev_tr = 1;
  kpm->ran_func_def.sz_ric_event_trigger_style_list = sz_ev_tr;
  kpm->ran_func_def.ric_event_trigger_style_list = calloc(sz_ev_tr, sizeof(ric_event_trigger_style_item_t));
  assert(kpm->ran_func_def.ric_event_trigger_style_list != NULL && "Memory exhausted");

  ric_event_trigger_style_item_t* ev_tr_item = &kpm->ran_func_def.ric_event_trigger_style_list[0];

  ev_tr_item->style_type = STYLE_1_RIC_EVENT_TRIGGER;
  const char ev_style_name[] = "Periodic Report";
  ev_tr_item->style_name = cp_str_to_ba(ev_style_name);
  ev_tr_item->format_type = FORMAT_1_RIC_EVENT_TRIGGER;

  // Supported RIC Indication Formats
  report_item->ind_hdr_format_type = FORMAT_1_INDICATION_HEADER;  // 8.3.5
  report_item->ind_msg_format_type = FORMAT_3_INDICATION_MESSAGE;  // 8.3.5

  // E2 Setup Request is sent periodically until the connection is established
  // RC subscritpion data should be initialized only once
  const int ret = pthread_once(&once_kpm_mutex, init_once_kpm);
  DevAssert(ret == 0);
}

sm_ag_if_ans_t write_ctrl_kpm_sm(void const* src)
{
  printf("write_ctrl callback for KPM SM: operation not supported\n");
  (void)src;
  sm_ag_if_ans_t ans = {0};
  return ans;
}
