/*
 * 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 pfcp.hpp
  \brief
  \author Lionel Gauthier
  \company Eurecom
  \email: lionel.gauthier@eurecom.fr
*/
#ifndef FILE_PFCP_HPP_SEEN
#define FILE_PFCP_HPP_SEEN

#include "3gpp_29.274.h"
#include "3gpp_29.244.hpp"
#include "itti.hpp"
#include "udp.hpp"
#include "uint_generator.hpp"

#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "msg_pfcp.hpp"

namespace pfcp {

static const uint16_t default_port = 8805;

class pfcp_procedure {
public:
  std::shared_ptr<pfcp_msg> retry_msg;
  endpoint remote_endpoint;
  timer_id_t   retry_timer_id;
  timer_id_t   proc_cleanup_timer_id;
  uint64_t                    trxn_id;
  uint8_t                     initial_msg_type; // sent or received
  uint8_t                     triggered_msg_type; // sent or received
  uint8_t                     retry_count;

  pfcp_procedure() : retry_msg(),
    remote_endpoint(),
    retry_timer_id(0),
    proc_cleanup_timer_id(0),
    trxn_id(0),
    initial_msg_type(0),
    triggered_msg_type(0),
    retry_count(0) {}

  pfcp_procedure(const pfcp_procedure& p) : retry_msg(p.retry_msg),
      remote_endpoint(p.remote_endpoint),
      retry_timer_id(p.retry_timer_id),
      proc_cleanup_timer_id(p.proc_cleanup_timer_id),
      trxn_id(p.trxn_id),
      initial_msg_type(p.initial_msg_type),
      triggered_msg_type(p.triggered_msg_type),
      retry_count(p.retry_count) {}
};


enum pfcp_transaction_action {
  DELETE_TX = 0,
  CONTINUE_TX
};

class pfcp_l4_stack : public udp_application {
#define PFCP_T1_RESPONSE_MS           1000
#define PFCP_N1_REQUESTS              3
#define PFCP_PROC_TIME_OUT_MS         ((PFCP_T1_RESPONSE_MS) * (PFCP_N1_REQUESTS + 1 + 1))

protected:
  uint32_t                            id;
  udp_server                          udp_s_8805;
  udp_server                          udp_s_allocated;

  // seems no need for std::atomic_uint32_t
  uint32_t                            seq_num;
  uint32_t                            restart_counter;

  std::map<uint64_t, uint32_t>        trxn_id2seq_num;
  std::map<timer_id_t, uint32_t>      proc_cleanup_timers;
  std::map<timer_id_t, uint32_t>      msg_out_retry_timers;
  std::map<uint32_t , pfcp_procedure> pending_procedures;

  static const char*                  msg_type2cstr[256];

  uint32_t get_next_seq_num();

  static uint64_t generate_trxn_id() {
    return util::uint_uid_generator<uint64_t>::get_instance().get_uid();
  }

  static bool check_request_type(const uint8_t initial);
  static bool check_response_type(const uint8_t initial, const uint8_t triggered);
  void start_proc_cleanup_timer(pfcp_procedure& p, uint32_t time_out_milli_seconds, const task_id_t& task_id, const uint32_t& seq_num);
  void start_msg_retry_timer(pfcp_procedure& p, uint32_t time_out_milli_seconds, const task_id_t& task_id, const uint32_t& seq_num);
  void stop_msg_retry_timer(pfcp_procedure& p);
  void stop_msg_retry_timer(timer_id_t& t);
  void stop_proc_cleanup_timer(pfcp_procedure& p);
  void notify_ul_error(const pfcp_procedure& p, const ::cause_value_e cause);

public:
  static const uint8_t version = 2;
  pfcp_l4_stack(const std::string& ip_address, const unsigned short port_num, const util::thread_sched_params& sched_params);
  virtual void handle_receive(char* recv_buffer, const std::size_t bytes_transferred, endpoint& remote_endpoint);
  void handle_receive_message_cb(const pfcp_msg& msg, const endpoint& remote_endpoint, const task_id_t& task_id, bool& error, uint64_t& trxn_id);

  // Node related messages
//  virtual uint32_t send_request(const endpoint& dest, const uint64_t seid, const pfcp_pfd_management_request& pfcp_ies, const task_id_t& task_id, const uint64_t trxn_id);
  virtual uint32_t send_request(const endpoint& dest, const pfcp_heartbeat_request& pfcp_ies, const task_id_t& task_id, const uint64_t trxn_id);
  virtual uint32_t send_request(const endpoint& dest, const pfcp_association_setup_request& pfcp_ies, const task_id_t& task_id, const uint64_t trxn_id);
//  virtual uint32_t send_request(const endpoint& dest, const uint64_t seid, const pfcp_association_update_request& pfcp_ies, const task_id_t& task_id, const uint64_t trxn_id);
  virtual uint32_t send_request(const endpoint& dest, const pfcp_association_release_request& pfcp_ies, const task_id_t& task_id, const uint64_t trxn_id);
  virtual uint32_t send_request(const endpoint& dest, const uint64_t seid, const pfcp_node_report_request& pfcp_ies, const task_id_t& task_id, const uint64_t trxn_id);

  // session related messages
  virtual uint32_t send_request(const endpoint& dest, const uint64_t seid, const pfcp_session_establishment_request& pfcp_ies, const task_id_t& task_id, const uint64_t trxn_id);
  virtual uint32_t send_request(const endpoint& dest, const uint64_t seid, const pfcp_session_modification_request& pfcp_ies, const task_id_t& task_id, const uint64_t trxn_id);
  virtual uint32_t send_request(const endpoint& dest, const uint64_t seid, const pfcp_session_deletion_request& pfcp_ies, const task_id_t& task_id, const uint64_t trxn_id);
  virtual uint32_t send_request(const endpoint& dest, const uint64_t seid, const pfcp_session_report_request& pfcp_ies, const task_id_t& task_id, const uint64_t trxn_id);

  // Node related messages
  virtual void send_response(const endpoint& dest, const pfcp_heartbeat_response& pfcp_ies, const uint64_t trxn_id, const pfcp_transaction_action& a = DELETE_TX);
  virtual void send_response(const endpoint& dest, const pfcp_association_setup_response& pfcp_ies, const uint64_t trxn_id, const pfcp_transaction_action& a = DELETE_TX);
  virtual void send_response(const endpoint& dest, const pfcp_association_release_response& pfcp_ies, const uint64_t trxn_id, const pfcp_transaction_action& a = DELETE_TX);

  // session related messages
  virtual void send_response(const endpoint& dest, const uint64_t seid, const pfcp_session_establishment_response& pfcp_ies, const uint64_t trxn_id, const pfcp_transaction_action& a = DELETE_TX);
  virtual void send_response(const endpoint& dest, const uint64_t seid, const pfcp_session_modification_response& pfcp_ies, const uint64_t trxn_id, const pfcp_transaction_action& a = DELETE_TX);
  virtual void send_response(const endpoint& dest, const uint64_t seid, const pfcp_session_deletion_response& pfcp_ies, const uint64_t trxn_id, const pfcp_transaction_action& a = DELETE_TX);
  virtual void send_response(const endpoint& dest, const uint64_t seid, const pfcp_session_report_response& pfcp_ies, const uint64_t trxn_id, const pfcp_transaction_action& a = DELETE_TX);

  void time_out_event(const uint32_t timer_id, const task_id_t& task_id, bool &error);
};
} // namespace pfcp

#endif /* FILE_PFCP_HPP_SEEN */
