/*
 * 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 amf_app.cpp
 \brief
 \author  Keliang DU, BUPT
 \date 2020
 \email: contact@openairinterface.org
 */

#include "amf_app.hpp"

#include <cstdlib>
#include <iostream>
#include <stdexcept>
#include "itti.hpp"

#include "DLNASTransport.hpp"
#include "amf_config.hpp"
#include "amf_n1.hpp"
#include "amf_n11.hpp"
#include "amf_n2.hpp"
#include "amf_statistics.hpp"
#include "ngap_app.hpp"

#include <nlohmann/json.hpp>
#include <curl/curl.h>

using namespace ngap;
using namespace nas;
using namespace amf_application;
using namespace config;

extern void print_buffer(
    const std::string app, const std::string commit, uint8_t* buf, int len);
extern amf_app* amf_app_inst;
extern itti_mw* itti_inst;
amf_n2* amf_n2_inst   = nullptr;
amf_n1* amf_n1_inst   = nullptr;
amf_n11* amf_n11_inst = nullptr;
extern amf_config amf_cfg;
extern statistics stacs;

void amf_app_task(void*);
uint32_t golbal_tmsi = 1;

//------------------------------------------------------------------------------
amf_app::amf_app(const amf_config& amf_cfg) {
  Logger::amf_app().startup("Creating AMF application functionality layer");
  if (itti_inst->create_task(TASK_AMF_APP, amf_app_task, nullptr)) {
    Logger::amf_app().error("Cannot create task TASK_AMF_APP");
    throw std::runtime_error("Cannot create task TASK_AMF_APP");
  }

  try {
    amf_n1_inst = new amf_n1();
    amf_n2_inst =
        new amf_n2(std::string(inet_ntoa(amf_cfg.n2.addr4)), amf_cfg.n2.port);
    amf_n11_inst = new amf_n11();
  } catch (std::exception& e) {
    Logger::amf_app().error("Cannot create AMF APP: %s", e.what());
    throw;
  }

  timer_id_t tid = itti_inst->timer_setup(
      amf_cfg.statistics_interval, 0, TASK_AMF_APP,
      TASK_AMF_APP_PERIODIC_STATISTICS, 0);
  Logger::amf_app().startup("Started timer(%d)", tid);
}

//------------------------------------------------------------------------------
void amf_app::allRegistredModulesInit(const amf_modules& modules) {
  Logger::amf_app().info("Initiating all registered modules");
}

//------------------------------------------------------------------------------
void amf_app_task(void*) {
  const task_id_t task_id = TASK_AMF_APP;
  itti_inst->notify_task_ready(task_id);
  do {
    std::shared_ptr<itti_msg> shared_msg = itti_inst->receive_msg(task_id);
    auto* msg                            = shared_msg.get();
    timer_id_t tid;
    switch (msg->msg_type) {
      case NAS_SIG_ESTAB_REQ: {
        Logger::amf_app().debug("Received NAS_SIG_ESTAB_REQ");
        itti_nas_signalling_establishment_request* m =
            dynamic_cast<itti_nas_signalling_establishment_request*>(msg);
        amf_app_inst->handle_itti_message(ref(*m));
      } break;

      case N1N2_MESSAGE_TRANSFER_REQ: {
        Logger::amf_app().debug("Received N1N2_MESSAGE_TRANSFER_REQ");
        itti_n1n2_message_transfer_request* m =
            dynamic_cast<itti_n1n2_message_transfer_request*>(msg);
        amf_app_inst->handle_itti_message(ref(*m));
      } break;

      case TEST_SIGNALLING_PAGING: {
        Logger::amf_app().debug("Received TEST_SIGNALLING_PAGING");
        itti_test_signalling_paging* m =
            dynamic_cast<itti_test_signalling_paging*>(msg);
        amf_app_inst->handle_itti_message(ref(*m));
      } break;

      case TEST_SIGNALLING_NETWORK_INITIATED_DEREGISTRAATION: {
        Logger::amf_app().debug("Received TEST_SIGNALLING_NETWORK_INITIATED_DEREGISTRAATION");
        itti_test_signalling_network_initiated_deregistration* m =
            dynamic_cast<itti_test_signalling_network_initiated_deregistration*>(msg);
        amf_app_inst->handle_itti_message(ref(*m));
      } break;

      case PUSH_TO_AMF_AGENT: {
        Logger::amf_app().debug("Received itti_push_to_amf_agent");
        itti_push_to_amf_agent* m =
            dynamic_cast<itti_push_to_amf_agent*>(msg);
        amf_app_inst->handle_itti_message(ref(*m));
      } break;

      case TIME_OUT:
        if (itti_msg_timeout* to = dynamic_cast<itti_msg_timeout*>(msg)) {
          switch (to->arg1_user) {
            case TASK_AMF_APP_PERIODIC_STATISTICS:
              tid = itti_inst->timer_setup(
                  amf_cfg.statistics_interval, 0, TASK_AMF_APP,
                  TASK_AMF_APP_PERIODIC_STATISTICS, 0);
              stacs.display();
              break;
            default:
              Logger::amf_app().info(
                  "No handler for timer(%d) with arg1_user(%d) ", to->timer_id,
                  to->arg1_user);
          }
        }
        break;
      default:
        Logger::amf_app().info("no handler for msg type %d", msg->msg_type);
    }
  } while (true);
}

//------------------------------------------------------------------------------
long amf_app::generate_amf_ue_ngap_id() {
  long tmp = 0;
  tmp      = __sync_fetch_and_add(&amf_app_ue_ngap_id_generator, 1);
  return tmp & 0xffffffffff;
}

//------------------------------------------------------------------------------
bool amf_app::is_amf_ue_id_2_ue_context(const long& amf_ue_ngap_id) const {
  std::shared_lock lock(m_amf_ue_ngap_id2ue_ctx);
  return bool{amf_ue_ngap_id2ue_ctx.count(amf_ue_ngap_id) > 0};
}

//------------------------------------------------------------------------------
std::shared_ptr<ue_context> amf_app::amf_ue_id_2_ue_context(
    const long& amf_ue_ngap_id) const {
  std::shared_lock lock(m_amf_ue_ngap_id2ue_ctx);
  return amf_ue_ngap_id2ue_ctx.at(amf_ue_ngap_id);
}

//------------------------------------------------------------------------------
void amf_app::set_amf_ue_ngap_id_2_ue_context(
    const long& amf_ue_ngap_id, std::shared_ptr<ue_context> uc) {
  std::shared_lock lock(m_amf_ue_ngap_id2ue_ctx);
  amf_ue_ngap_id2ue_ctx[amf_ue_ngap_id] = uc;
}

//------------------------------------------------------------------------------
bool amf_app::is_ran_amf_id_2_ue_context(const string& ue_context_key) const {
  std::shared_lock lock(m_ue_ctx_key);
  return bool{ue_ctx_key.count(ue_context_key) > 0};
}

//------------------------------------------------------------------------------
std::shared_ptr<ue_context> amf_app::ran_amf_id_2_ue_context(
    const string& ue_context_key) const {
  std::shared_lock lock(m_ue_ctx_key);
  return ue_ctx_key.at(ue_context_key);
}

//------------------------------------------------------------------------------
void amf_app::set_ran_amf_id_2_ue_context(
    const string& ue_context_key, std::shared_ptr<ue_context> uc) {
  std::shared_lock lock(m_ue_ctx_key);
  ue_ctx_key[ue_context_key] = uc;
}

// ITTI handlers
//------------------------------------------------------------------------------
void amf_app::handle_itti_message(
    itti_test_signalling_paging& itti_msg) {

  itti_paging* paging_msg = new itti_paging(TASK_AMF_APP, TASK_AMF_N2);

  paging_msg->ran_ue_ngap_id = itti_msg.ran_ue_ngap_id;
  paging_msg->amf_ue_ngap_id = itti_msg.amf_ue_ngap_id;

  std::shared_ptr<itti_paging> i =
      std::shared_ptr<itti_paging>(paging_msg);
  int ret = itti_inst->send_msg(i);
  if (0 != ret) {
    Logger::amf_app().error(
        "Could not send ITTI message %s to task TASK_AMF_N2",
        i->get_msg_name());
  }
}

//------------------------------------------------------------------------------
void amf_app::handle_itti_message(
    itti_test_signalling_network_initiated_deregistration& itti_msg) {

  amf_n1_inst->network_initiate_de_registration_handle(itti_msg.ran_ue_ngap_id, itti_msg.amf_ue_ngap_id);

}

//------------------------------------------------------------------------------
#define CURL_TIMEOUT_MS 300L

std::size_t callback_amf_agent(const char *in, std::size_t size, std::size_t num,
                          std::string *out) {
  const std::size_t totalBytes(size * num);
  out->append(in, totalBytes);
  return totalBytes;
}

void amf_app::curl_http_client(std::string remoteUri, std::string Method,
                              std::string msgBody, std::string &Response) {
  Logger::amf_n1().info("Send HTTP message with body %s", msgBody.c_str());

  uint32_t str_len = msgBody.length();
  char *body_data = (char *)malloc(str_len + 1);
  memset(body_data, 0, str_len + 1);
  memcpy((void *)body_data, (void *)msgBody.c_str(), str_len);

  curl_global_init(CURL_GLOBAL_ALL);
  CURL *curl = curl_easy_init();

  if (curl) {
    CURLcode res = {};
    struct curl_slist *headers = nullptr;
    if (!Method.compare("POST") || !Method.compare("PATCH") ||
        !Method.compare("PUT")) {
      std::string content_type = "Content-Type: application/json";
      headers = curl_slist_append(headers, content_type.c_str());
      curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    }
    curl_easy_setopt(curl, CURLOPT_URL, remoteUri.c_str());
    if (!Method.compare("POST"))
      curl_easy_setopt(curl, CURLOPT_HTTPPOST, 1);
    else if (!Method.compare("PATCH"))
      curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
    else if (!Method.compare("PUT")) {
      curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
    } else
      curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT_MS);
    curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1);
//    curl_easy_setopt(curl, CURLOPT_INTERFACE, amf_cfg.nausf.if_name.c_str());
    curl_easy_setopt(curl, CURLOPT_INTERFACE, amf_cfg.n11.if_name.c_str());

    // Response information.
    long httpCode = {0};
    std::unique_ptr<std::string> httpData(new std::string());
    std::unique_ptr<std::string> httpHeaderData(new std::string());

    // Hook up data handling function.
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &callback_amf_agent);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, httpData.get());
    curl_easy_setopt(curl, CURLOPT_HEADERDATA, httpHeaderData.get());
    if (!Method.compare("POST") || !Method.compare("PATCH") ||
        !Method.compare("PUT")) {
      curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, msgBody.length());
      curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body_data);
    }

    res = curl_easy_perform(curl);
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);

    // get the response
    std::string response = *httpData.get();
    std::string json_data_response = "";
    std::string resMsg = "";
    bool is_response_ok = true;
    Logger::amf_n1().info("Get response with httpcode (%d)", httpCode);

    if (httpCode == 0) {
      Logger::amf_n1().info("Cannot get response when calling %s",
                            remoteUri.c_str());
      // free curl before returning
      curl_slist_free_all(headers);
      curl_easy_cleanup(curl);
      return;
    }

    nlohmann::json response_data = {};

    if (httpCode != 200 && httpCode != 201 && httpCode != 204) {
      is_response_ok = false;
      if (response.size() < 1) {
        Logger::amf_n1().info("There's no content in the response");
        // TODO: send context response error
        return;
      }
      Logger::amf_n1().info("Wrong response code");

      return;
    }

    else {
      Response = *httpData.get();
    }

    if (!is_response_ok) {
      try {
        response_data = nlohmann::json::parse(json_data_response);
      } catch (nlohmann::json::exception &e) {
        Logger::amf_n1().info("Could not get Json content from the response");
        // Set the default Cause
        response_data["error"]["cause"] = "504 Gateway Timeout";
      }

      Logger::amf_n1().info("Get response with jsonData: %s",
                            json_data_response.c_str());

      std::string cause = response_data["error"]["cause"];
      Logger::amf_n1().info("Call Network Function services failure");
      Logger::amf_n1().info("Cause value: %s", cause.c_str());
    }
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
  }

  curl_global_cleanup();

  if (body_data) {
    free(body_data);
    body_data = NULL;
  }
  fflush(stdout);
}


//------------------------------------------------------------------------------
void amf_app::handle_itti_message(itti_push_to_amf_agent& itti_msg)
{
    nlohmann::json amf_agent_data;
    amf_agent_data["supi"] = itti_msg.supi;
    amf_agent_data["accessType"] = itti_msg.accesstype;
    amf_agent_data["rmState"] = itti_msg.rmState;
    amf_agent_data["cmState"] = itti_msg.cmstate;
    amf_agent_data["ranUeNgapId"] = itti_msg.ran_ue_ngap_id;
    amf_agent_data["amfUeNgapId"] = itti_msg.amf_ue_ngap_id;

    std::string remoteUri = amf_cfg.amf_agent_addr4 + ":" + std::to_string(amf_cfg.amf_agent_port) + "/namf-event-exposure/bridge";
    std::string Method = "POST";
    std::string msgBody = amf_agent_data.dump();
    std::string Response;

    curl_http_client(remoteUri, Method, msgBody, Response);
}



//------------------------------------------------------------------------------
void amf_app::handle_itti_message(
    itti_n1n2_message_transfer_request& itti_msg) {
  // 1. encode DL NAS TRANSPORT message(NAS message)
  DLNASTransport* dl = new DLNASTransport();
  dl->setHeader(PLAIN_5GS_MSG);
  dl->setPayload_Container_Type(N1_SM_INFORMATION);
  dl->setPayload_Container(
      (uint8_t*) bdata(itti_msg.n1sm), blength(itti_msg.n1sm));
  dl->setPDUSessionId(itti_msg.pdu_session_id);
  uint8_t nas[1024];
  int encoded_size = dl->encode2buffer(nas, 1024);
  print_buffer("amf_app", "n1n2 transfer", nas, encoded_size);
  bstring dl_nas = blk2bstr(nas, encoded_size);

  itti_downlink_nas_transfer* dl_msg =
      new itti_downlink_nas_transfer(TASK_AMF_APP, TASK_AMF_N1);
  dl_msg->dl_nas = dl_nas;
  if (!itti_msg.is_n2sm_set) {
    dl_msg->is_n2sm_set = false;
  } else {
    dl_msg->n2sm           = itti_msg.n2sm;
    dl_msg->pdu_session_id = itti_msg.pdu_session_id;
    dl_msg->is_n2sm_set    = true;
    dl_msg->n2sm_info_type = itti_msg.n2sm_info_type;
  }
  dl_msg->amf_ue_ngap_id = amf_n1_inst->supi2amfId.at(itti_msg.supi);
  dl_msg->ran_ue_ngap_id = amf_n1_inst->supi2ranId.at(itti_msg.supi);
  std::shared_ptr<itti_downlink_nas_transfer> i =
      std::shared_ptr<itti_downlink_nas_transfer>(dl_msg);
  int ret = itti_inst->send_msg(i);
  if (0 != ret) {
    Logger::amf_app().error(
        "Could not send ITTI message %s to task TASK_AMF_N1",
        i->get_msg_name());
  }
}

//------------------------------------------------------------------------------
void amf_app::handle_itti_message(
    itti_nas_signalling_establishment_request& itti_msg) {
  // 1. generate amf_ue_ngap_id
  // 2. establish ue_context associated with amf_ue_ngap_id
  // 3. store ue-reated core information
  // 4. send nas-pdu to task_amf_n1
  long amf_ue_ngap_id = 0;
  std::shared_ptr<ue_context> uc;
  // check ue context with 5g-s-tmsi

  if ((amf_ue_ngap_id = itti_msg.amf_ue_ngap_id) == -1) {
    amf_ue_ngap_id = generate_amf_ue_ngap_id();
  }
  string ue_context_key = "app_ue_ranid_" + to_string(itti_msg.ran_ue_ngap_id) +
                          ":amfid_" + to_string(amf_ue_ngap_id);
  // if(!is_amf_ue_id_2_ue_context(amf_ue_ngap_id)){
  if (!is_ran_amf_id_2_ue_context(ue_context_key)) {
    Logger::amf_app().debug(
        "No existing UE Context, Create a new one with ran_amf_id %s",
        ue_context_key.c_str());
    uc = std::shared_ptr<ue_context>(new ue_context());
    // set_amf_ue_ngap_id_2_ue_context(amf_ue_ngap_id, uc);
    set_ran_amf_id_2_ue_context(ue_context_key, uc);
  }
  if (uc.get() == nullptr) {
    Logger::amf_app().error(
        "Failed to create ue_context with ran_amf_id %s",
        ue_context_key.c_str());
  } else {
    uc.get()->cgi = itti_msg.cgi;
    uc.get()->tai = itti_msg.tai;
    if (itti_msg.rrc_cause != -1)
      uc.get()->rrc_estb_cause =
          (e_Ngap_RRCEstablishmentCause) itti_msg.rrc_cause;
    if (itti_msg.ueCtxReq == -1)
      uc.get()->isUeContextRequest = false;
    else
      uc.get()->isUeContextRequest = true;
    uc.get()->ran_ue_ngap_id = itti_msg.ran_ue_ngap_id;
    uc.get()->amf_ue_ngap_id = amf_ue_ngap_id;

    std::string guti;
    bool is_guti_valid = false;
    if (itti_msg.is_5g_s_tmsi_present) {
      guti = itti_msg.tai.mcc + itti_msg.tai.mnc + amf_cfg.guami.regionID +
             itti_msg._5g_s_tmsi;
      is_guti_valid = true;
      Logger::amf_app().debug("Receiving GUTI %s", guti.c_str());
    }

    itti_uplink_nas_data_ind* itti_n1_msg =
        new itti_uplink_nas_data_ind(TASK_AMF_APP, TASK_AMF_N1);
    itti_n1_msg->amf_ue_ngap_id              = amf_ue_ngap_id;
    itti_n1_msg->ran_ue_ngap_id              = itti_msg.ran_ue_ngap_id;
    itti_n1_msg->is_nas_signalling_estab_req = true;
    itti_n1_msg->nas_msg                     = itti_msg.nas_buf;
    itti_n1_msg->mcc                         = itti_msg.tai.mcc;
    itti_n1_msg->mnc                         = itti_msg.tai.mnc;
    itti_n1_msg->is_guti_valid               = is_guti_valid;
    if (is_guti_valid) {
      itti_n1_msg->guti = guti;
    }
    std::shared_ptr<itti_uplink_nas_data_ind> i =
        std::shared_ptr<itti_uplink_nas_data_ind>(itti_n1_msg);
    int ret = itti_inst->send_msg(i);
    if (0 != ret) {
      Logger::amf_app().error(
          "Could not send ITTI message %s to task TASK_AMF_N1",
          i->get_msg_name());
    }
  }
}

// SMF Client response handlers
//------------------------------------------------------------------------------
void amf_app::handle_post_sm_context_response_error_400() {
  Logger::amf_app().error("Post SM context response error 400");
}

bool amf_app::generate_5g_guti(
    uint32_t ranid, long amfid, string& mcc, string& mnc, uint32_t& tmsi) {
  string ue_context_key =
      "app_ue_ranid_" + to_string(ranid) + ":amfid_" + to_string(amfid);
  if (!is_ran_amf_id_2_ue_context(ue_context_key)) {
    Logger::amf_app().error(
        "No UE context for ran_amf_id %s, exit", ue_context_key.c_str());
    return false;
  }
  std::shared_ptr<ue_context> uc;
  uc   = ran_amf_id_2_ue_context(ue_context_key);
  mcc  = uc.get()->tai.mcc;
  mnc  = uc.get()->tai.mnc;
  tmsi = golbal_tmsi;
  golbal_tmsi++;
  return true;
}
