/*
 * 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
 */

/*! \file
 \brief
 \author  Keliang DU, BUPT
 \date 2020
 \email: contact@openairinterface.org
 */

#include "NGSetupResponse.hpp"
#include "logger.hpp"

extern "C" {
#include "asn_codecs.h"
#include "constr_TYPE.h"
#include "constraints.h"
#include "per_decoder.h"
#include "per_encoder.h"
}

#include <iostream>
using namespace std;

namespace ngap {

//------------------------------------------------------------------------------
NGSetupResponseMsg::NGSetupResponseMsg() {
  ngSetupResponsePdu  = nullptr;
  ngSetupResponsIEs   = nullptr;
  amfName             = nullptr;
  servedGUAMIList     = nullptr;
  relativeAmfCapacity = nullptr;
  plmnSupportList     = nullptr;
  // criticalityDiagnostics = nullptr;
}

//------------------------------------------------------------------------------
NGSetupResponseMsg::~NGSetupResponseMsg() {}

//------------------------------------------------------------------------------
void NGSetupResponseMsg::setMessageType() {
  if (!ngSetupResponsePdu)
    ngSetupResponsePdu = (Ngap_NGAP_PDU_t*) calloc(1, sizeof(Ngap_NGAP_PDU_t));

  MessageType NgSetupResponseMessageTypeIE;
  NgSetupResponseMessageTypeIE.setProcedureCode(Ngap_ProcedureCode_id_NGSetup);
  NgSetupResponseMessageTypeIE.setTypeOfMessage(
      Ngap_NGAP_PDU_PR_successfulOutcome);
  NgSetupResponseMessageTypeIE.setValuePresent(
      Ngap_SuccessfulOutcome__value_PR_NGSetupResponse);

  if (NgSetupResponseMessageTypeIE.getProcedureCode() ==
          Ngap_ProcedureCode_id_NGSetup &&
      NgSetupResponseMessageTypeIE.getTypeOfMessage() ==
          Ngap_NGAP_PDU_PR_successfulOutcome) {
    NgSetupResponseMessageTypeIE.encode2pdu(ngSetupResponsePdu);
    ngSetupResponsIEs = &(ngSetupResponsePdu->choice.successfulOutcome->value
                              .choice.NGSetupResponse);
  } else {
    Logger::ngap().warn(
        "This information doesn't refer to NGSetupRespons message!");
  }
}

//------------------------------------------------------------------------------
void NGSetupResponseMsg::setAMFName(const std::string name) {
  amfName = new AmfName();
  amfName->setValue(name);

  Ngap_NGSetupResponseIEs_t* ie =
      (Ngap_NGSetupResponseIEs_t*) calloc(1, sizeof(Ngap_NGSetupResponseIEs_t));
  ie->id            = Ngap_ProtocolIE_ID_id_AMFName;
  ie->criticality   = Ngap_Criticality_reject;
  ie->value.present = Ngap_NGSetupResponseIEs__value_PR_AMFName;

  int ret = amfName->encode2AmfName(&ie->value.choice.AMFName);
  if (!ret) {
    Logger::ngap().error("Encode NGAP AMFName IE error");
    return;
  }

  ret = ASN_SEQUENCE_ADD(&ngSetupResponsIEs->protocolIEs.list, ie);
  if (ret != 0) Logger::ngap().error("Encode NGAP AMFName IE error");
}

//------------------------------------------------------------------------------
void NGSetupResponseMsg::setGUAMIList(std::vector<struct GuamiItem_s> list) {
  servedGUAMIList = new ServedGUAMIList();
  ServedGUAMIItem* servedGUAMIItem;
  servedGUAMIItem = new ServedGUAMIItem[list.size()]();
  for (int i = 0; i < list.size(); i++) {
    GUAMI* guami          = new GUAMI();
    PlmnId* plmnid        = new PlmnId();
    AMFRegionID* regionID = new AMFRegionID();
    AMFSetID* setID       = new AMFSetID();
    AMFPointer* pointer   = new AMFPointer();

    plmnid->setMccMnc(list[i].mcc, list[i].mnc);
    regionID->setAMFRegionID(list[i].regionID);
    setID->setAMFSetID(list[i].AmfSetID);
    pointer->setAMFPointer(list[i].AmfPointer);
    guami->setGUAMI(plmnid, regionID, setID, pointer);
    servedGUAMIItem[i].setGUAMI(guami);

    if (list[i].backupAMFName.size() > 0) {
      AmfName* backupamfname = new AmfName();
      backupamfname->setValue(list[i].backupAMFName);

      servedGUAMIItem[i].setBackupAMFName(backupamfname);
    }
  }
  servedGUAMIList->addServedGUAMIItems(servedGUAMIItem, list.size());

  Ngap_NGSetupResponseIEs_t* ie =
      (Ngap_NGSetupResponseIEs_t*) calloc(1, sizeof(Ngap_NGSetupResponseIEs_t));
  ie->id            = Ngap_ProtocolIE_ID_id_ServedGUAMIList;
  ie->criticality   = Ngap_Criticality_reject;
  ie->value.present = Ngap_NGSetupResponseIEs__value_PR_ServedGUAMIList;

  servedGUAMIList->encode2ServedGUAMIList(&ie->value.choice.ServedGUAMIList);

  int ret = ASN_SEQUENCE_ADD(&ngSetupResponsIEs->protocolIEs.list, ie);
  if (ret != 0) Logger::ngap().error("Encode NGAP ServedGUAMIList IE error");
}

//------------------------------------------------------------------------------
void NGSetupResponseMsg::setRelativeAmfCapacity(long capacity) {
  relativeAmfCapacity = new RelativeAMFCapacity();
  relativeAmfCapacity->setValue(capacity);

  Ngap_NGSetupResponseIEs_t* ie =
      (Ngap_NGSetupResponseIEs_t*) calloc(1, sizeof(Ngap_NGSetupResponseIEs_t));
  ie->id            = Ngap_ProtocolIE_ID_id_RelativeAMFCapacity;
  ie->criticality   = Ngap_Criticality_ignore;
  ie->value.present = Ngap_NGSetupResponseIEs__value_PR_RelativeAMFCapacity;

  relativeAmfCapacity->encode2RelativeAMFCapacity(
      &ie->value.choice.RelativeAMFCapacity);

  int ret = ASN_SEQUENCE_ADD(&ngSetupResponsIEs->protocolIEs.list, ie);
  if (ret != 0)
    Logger::ngap().error("Encode NGAP RelativeAMFCapacity IE error");
}

//------------------------------------------------------------------------------
void NGSetupResponseMsg::setPlmnSupportList(
    std::vector<PlmnSliceSupport_t> list) {
  plmnSupportList                  = new PLMNSupportList();
  PLMNSupportItem* plmnSupportItem = new PLMNSupportItem[list.size()]();
  for (int i = 0; i < list.size(); i++) {
    PlmnId* plmn = new PlmnId();
    plmn->setMccMnc(list[i].mcc, list[i].mnc);
    cout << "mcc = " << list[i].mcc << "  mnc = " << list[i].mnc << endl;
    S_NSSAI* snssai = new S_NSSAI[list[i].slice_list.size()]();
    for (int j = 0; j < list[i].slice_list.size(); j++) {
      snssai[j].setSst(list[i].slice_list[j].sst);
      if (list[i].slice_list[j].sd.size() &&
          (list[i].slice_list[j].sd.compare("None") &&
           list[i].slice_list[j].sd.compare("none"))) {
        snssai[j].setSd(list[i].slice_list[j].sd);
      }
    }
    plmnSupportItem[i].setPlmnSliceSupportList(
        plmn, snssai, list[i].slice_list.size());
  }
  plmnSupportList->addPLMNSupportItems(plmnSupportItem, list.size());

  Ngap_NGSetupResponseIEs_t* ie =
      (Ngap_NGSetupResponseIEs_t*) calloc(1, sizeof(Ngap_NGSetupResponseIEs_t));
  ie->id            = Ngap_ProtocolIE_ID_id_PLMNSupportList;
  ie->criticality   = Ngap_Criticality_reject;
  ie->value.present = Ngap_NGSetupResponseIEs__value_PR_PLMNSupportList;

  plmnSupportList->encode2PLMNSupportList(&ie->value.choice.PLMNSupportList);

  int ret = ASN_SEQUENCE_ADD(&ngSetupResponsIEs->protocolIEs.list, ie);
  if (ret != 0) Logger::ngap().error("Encode NGAP PLMNSupportList IE error");
}

//------------------------------------------------------------------------------
int NGSetupResponseMsg::encode2buffer(uint8_t* buf, int buf_size) {
  asn_fprint(stderr, &asn_DEF_Ngap_NGAP_PDU, ngSetupResponsePdu);
  asn_enc_rval_t er = aper_encode_to_buffer(
      &asn_DEF_Ngap_NGAP_PDU, NULL, ngSetupResponsePdu, buf, buf_size);
  Logger::ngap().debug("er.encoded (%d)", er.encoded);
  return er.encoded;
}

//------------------------------------------------------------------------------
// Decapsulation
bool NGSetupResponseMsg::decodefrompdu(Ngap_NGAP_PDU_t* ngap_msg_pdu) {
  ngSetupResponsePdu = ngap_msg_pdu;

  if (ngSetupResponsePdu->present == Ngap_NGAP_PDU_PR_successfulOutcome) {
    if (ngSetupResponsePdu->choice.successfulOutcome &&
        ngSetupResponsePdu->choice.successfulOutcome->procedureCode ==
            Ngap_ProcedureCode_id_NGSetup &&
        ngSetupResponsePdu->choice.successfulOutcome->criticality ==
            Ngap_Criticality_reject &&
        ngSetupResponsePdu->choice.successfulOutcome->value.present ==
            Ngap_SuccessfulOutcome__value_PR_NGSetupResponse) {
      ngSetupResponsIEs = &ngSetupResponsePdu->choice.successfulOutcome->value
                               .choice.NGSetupResponse;
    } else {
      Logger::ngap().error("Check NGSetupResponse message error");
      return false;
    }
  } else {
    Logger::ngap().error(
        "MessageType error, ngSetupResponsePdu->present %d",
        ngSetupResponsePdu->present);
    return false;
  }
  for (int i = 0; i < ngSetupResponsIEs->protocolIEs.list.count; i++) {
    switch (ngSetupResponsIEs->protocolIEs.list.array[i]->id) {
      case Ngap_ProtocolIE_ID_id_AMFName: {
        if (ngSetupResponsIEs->protocolIEs.list.array[i]->criticality ==
                Ngap_Criticality_reject &&
            ngSetupResponsIEs->protocolIEs.list.array[i]->value.present ==
                Ngap_NGSetupResponseIEs__value_PR_AMFName) {
          amfName = new AmfName();
          if (!amfName->decodefromAmfName(
                  &ngSetupResponsIEs->protocolIEs.list.array[i]
                       ->value.choice.AMFName)) {
            Logger::ngap().error("Decoded NGAP AMFName error");
            return false;
          }
        } else {
          Logger::ngap().error("Decoded NGAP AMFName error");
          return false;
        }
      } break;
      case Ngap_ProtocolIE_ID_id_ServedGUAMIList: {
        if (ngSetupResponsIEs->protocolIEs.list.array[i]->criticality ==
                Ngap_Criticality_reject &&
            ngSetupResponsIEs->protocolIEs.list.array[i]->value.present ==
                Ngap_NGSetupResponseIEs__value_PR_ServedGUAMIList) {
          servedGUAMIList = new ServedGUAMIList();
          if (!servedGUAMIList->decodefromServedGUAMIList(
                  &ngSetupResponsIEs->protocolIEs.list.array[i]
                       ->value.choice.ServedGUAMIList)) {
            Logger::ngap().error("Decoded NGAP ServedGUAMIList error");
            return false;
          }
        } else {
          Logger::ngap().error("Decoded NGAP ServedGUAMIList error");
          return false;
        }
      } break;
      case Ngap_ProtocolIE_ID_id_RelativeAMFCapacity: {
        if (ngSetupResponsIEs->protocolIEs.list.array[i]->criticality ==
                Ngap_Criticality_ignore &&
            ngSetupResponsIEs->protocolIEs.list.array[i]->value.present ==
                Ngap_NGSetupResponseIEs__value_PR_RelativeAMFCapacity) {
          relativeAmfCapacity = new RelativeAMFCapacity();
          if (!relativeAmfCapacity->decodefromRelativeAMFCapacity(
                  &ngSetupResponsIEs->protocolIEs.list.array[i]
                       ->value.choice.RelativeAMFCapacity)) {
            Logger::ngap().error("Decoded NGAP RelativeAMFCapacity error");
            return false;
          }
        } else {
          Logger::ngap().error("Decoded NGAP RelativeAMFCapacity error");
          return false;
        }
      } break;
      case Ngap_ProtocolIE_ID_id_PLMNSupportList: {
        if (ngSetupResponsIEs->protocolIEs.list.array[i]->criticality ==
                Ngap_Criticality_reject &&
            ngSetupResponsIEs->protocolIEs.list.array[i]->value.present ==
                Ngap_NGSetupResponseIEs__value_PR_PLMNSupportList) {
          plmnSupportList = new PLMNSupportList();
          if (!plmnSupportList->decodefromPLMNSupportList(
                  &ngSetupResponsIEs->protocolIEs.list.array[i]
                       ->value.choice.PLMNSupportList)) {
            Logger::ngap().error("Decoded NGAP PLMNSupportList error");
            return false;
          }
        } else {
          Logger::ngap().error("Decoded NGAP PLMNSupportList error");
          return false;
        }
      } break;
      case Ngap_ProtocolIE_ID_id_CriticalityDiagnostics: {
        cout << "decoded ngap: This is CriticalityDiagnostics IE" << endl;
        Logger::ngap().debug("Decoded NGAP CriticalityDiagnostics");
      } break;
      default: {
        Logger::ngap().error("Decoded NGAP Message PDU error");
        return false;
      }
    }
  }

  return true;
}

//------------------------------------------------------------------------------
bool NGSetupResponseMsg::getAMFName(std::string& name) {
  if (!amfName) return false;
  amfName->getValue(name);
  return true;
}

//------------------------------------------------------------------------------
bool NGSetupResponseMsg::getGUAMIList(std::vector<struct GuamiItem_s>& list) {
  if (!servedGUAMIList) return false;
  ServedGUAMIItem* servedGUAMIItem;
  int numOfItem = 0;
  servedGUAMIList->getServedGUAMIItems(servedGUAMIItem, numOfItem);
  for (int i = 0; i < numOfItem; i++) {
    GuamiItem_t guamiitem_data;
    GUAMI* guami;
    servedGUAMIItem[i].getGUAMI(guami);
    PlmnId* plmnid;
    AMFRegionID* regionID;
    AMFSetID* setID;
    AMFPointer* pointer;
    guami->getGUAMI(plmnid, regionID, setID, pointer);
    plmnid->getMcc(guamiitem_data.mcc);
    plmnid->getMnc(guamiitem_data.mnc);
    regionID->getAMFRegionID(guamiitem_data.regionID);
    setID->getAMFSetID(guamiitem_data.AmfSetID);
    pointer->getAMFPointer(guamiitem_data.AmfPointer);

    AmfName* backupAMFName;
    if (servedGUAMIItem[i].getBackupAMFName(backupAMFName)) {
      backupAMFName->getValue(guamiitem_data.backupAMFName);
    } else {
      guamiitem_data.backupAMFName = "None";
    }

    list.push_back(guamiitem_data);
  }

  return true;
}

//------------------------------------------------------------------------------
long NGSetupResponseMsg::getRelativeAmfCapacity() {
  if (!relativeAmfCapacity) return -1;

  return relativeAmfCapacity->getValue();
}

//------------------------------------------------------------------------------
bool NGSetupResponseMsg::getPlmnSupportList(
    std::vector<PlmnSliceSupport_t>& list) {
  if (!plmnSupportList) return false;

  PLMNSupportItem* plmnsupportItemItem;
  int numOfItem = 0;
  plmnSupportList->getPLMNSupportItems(plmnsupportItemItem, numOfItem);
  for (int i = 0; i < numOfItem; i++) {
    PlmnSliceSupport_t plmnSupportItem_data;
    PlmnId* plmn;
    S_NSSAI* snssai;
    int numofsnssai = 0;
    plmnsupportItemItem[i].getPlmnSliceSupportList(plmn, snssai, numofsnssai);
    plmn->getMcc(plmnSupportItem_data.mcc);
    plmn->getMnc(plmnSupportItem_data.mnc);
    for (int j = 0; j < numofsnssai; j++) {
      S_Nssai sliceSupportItem_data;
      snssai[j].getSst(sliceSupportItem_data.sst);
      snssai[j].getSd(sliceSupportItem_data.sd);
      plmnSupportItem_data.slice_list.push_back(sliceSupportItem_data);
    }

    list.push_back(plmnSupportItem_data);
  }

  return true;
}

}  // namespace ngap
