/*
 * 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 itti.hpp
 \brief
 \author  Lionel GAUTHIER
 \date 2018
 \email: lionel.gauthier@eurecom.fr
 */
#ifndef SRC_OAI_ITTI_ITTI_HPP_INCLUDED_
#define SRC_OAI_ITTI_ITTI_HPP_INCLUDED_

#include <chrono>
#include <condition_variable>
//#include <iomanip>
#include <stdint.h>

#include <iostream>
#include <memory>
#include <mutex>
#include <queue>
#include <set>
#include <thread>

#include "itti_msg.hpp"
#include "thread_sched.hpp"

typedef volatile enum task_state_s {
  TASK_STATE_NOT_CONFIGURED,
  TASK_STATE_STARTING,
  TASK_STATE_READY,
  TASK_STATE_ENDED,
  TASK_STATE_MAX,
} task_state_t;

typedef uint32_t timer_id_t;
#define ITTI_INVALID_TIMER_ID (timer_id_t) 0

class itti_timer {
 public:
  itti_timer(
      const timer_id_t id, const task_id_t task_id, const uint32_t interval_sec,
      const uint32_t interval_us, uint64_t arg1_user, uint64_t arg2_user)
      : id(id), task_id(task_id), arg1_user(arg1_user), arg2_user(arg2_user) {
    time_out = std::chrono::system_clock::now() +
               std::chrono::seconds(interval_sec) +
               std::chrono::microseconds(interval_us);
  }
  itti_timer(
      const timer_id_t id, const task_id_t task_id,
      const std::chrono::system_clock::time_point time_out, uint64_t arg1_user,
      uint64_t arg2_user)
      : id(id),
        task_id(task_id),
        time_out(time_out),
        arg1_user(arg1_user),
        arg2_user(arg2_user) {}
  itti_timer(const itti_timer& t)
      : id(t.id),
        task_id(t.task_id),
        time_out(t.time_out),
        arg1_user(t.arg1_user),
        arg2_user(t.arg2_user) {}
  // itti_timer(itti_timer&& t) noexcept : id(std::move(t.id)),
  // task_id(std::move(t.task_id)) , time_out(std::move(t.time_out)) {}

  bool operator<(const itti_timer& t) const { return time_out < t.time_out; }
  ~itti_timer() {}
  timer_id_t id;
  task_id_t task_id;
  std::chrono::system_clock::time_point time_out;
  uint64_t arg1_user;
  uint64_t arg2_user;
};

//------------------------------------------------------------------------------
struct timer_comparator {
  bool operator()(const itti_timer& left, const itti_timer& right) const {
    return (left.time_out < right.time_out);
  }
};

class itti_task_ctxt {
 public:
  explicit itti_task_ctxt(const task_id_t task_id)
      : task_id(task_id),
        m_state(),
        task_state(TASK_STATE_STARTING),
        msg_queue(),
        m_queue(),
        c_queue() {}
  ~itti_task_ctxt() {}

  const task_id_t task_id;
  /*
   * pthread associated with the thread
   */
  // std::thread::id                      thread_id;
  std::thread thread;
  /*
   * State of the thread
   */
  std::mutex m_state;
  volatile task_state_t task_state;

  std::queue<std::shared_ptr<itti_msg>> msg_queue;
  std::mutex m_queue;
  std::condition_variable c_queue;
};

class itti_mw {
 private:
  itti_task_ctxt* itti_task_ctxts[TASK_MAX];

  /*
   * Current message number. Incremented every call to send_msg_to_task
   */
  unsigned long msg_number;
  timer_id_t timer_id;
  std::mutex m_timer_id;
  std::thread timer_thread;

  std::atomic<int> created_tasks;
  std::atomic<int> ready_tasks;

  std::set<itti_timer, timer_comparator> timers;
  itti_timer current_timer;
  std::mutex m_timers;
  std::condition_variable c_timers;

  std::mutex m_timeout;
  std::condition_variable c_timeout;

  bool terminate;

  static void timer_manager_task(const util::thread_sched_params& sched_params);

 public:
  itti_mw();
  itti_mw(itti_mw const&) = delete;
  void operator=(itti_mw const&) = delete;
  ~itti_mw();

  void start(const util::thread_sched_params& sched_params);

  timer_id_t increment_timer_id();
  unsigned int increment_message_number();

  /** \brief Send a broadcast message to every task
   \param message_p Pointer to the message to send
   @returns < 0 on failure, 0 otherwise
   **/
  int send_broadcast_msg(std::shared_ptr<itti_msg> message);

  /** \brief Send a message to a task (could be itself)
   \param message message to send
   @returns -1 on failure, 0 otherwise
   **/
  int send_msg(std::shared_ptr<itti_msg> message);

  /** \brief Retrieves a message in the queue associated to task_id.
   * If the queue is empty, the thread is blocked till a new message arrives.
   \param task_id Task ID of the receiving task
   \param received_msg Pointer to the allocated message
   **/
  std::shared_ptr<itti_msg> receive_msg(task_id_t task_id);

  /** \brief Try to retrieves a message in the queue associated to task_id.
   \param task_id Task ID of the receiving task
   \param received_msg Pointer to the allocated message
   **/
  std::shared_ptr<itti_msg> poll_msg(task_id_t task_id);

  /** \brief Start thread associated to the task
   * \param task_id task to start
   * \param start_routine entry point for the task
   * \param args_p Optional argument to pass to the start routine
   * @returns -1 on failure, 0 otherwise
   **/
  int create_task(
      const task_id_t task_id, void (*start_routine)(void*), void* args_p);

  /** \brief Notify ITTI of a started thread
   * \param task_id of started task
   * \param start_routine entry point for the task
   * \param args_p Optional argument to pass to the start routine
   * @returns -1 on failure, 0 otherwise
   **/
  int notify_task_ready(const task_id_t task_id);

  /** \brief Indicates to ITTI if newly created tasks should wait for all tasks
   *to be ready \param wait_tasks non 0 to make new created tasks to wait, 0 to
   *let created tasks to run
   **/
  // void wait_ready(int wait_tasks);
  /** \brief Mark the task as in ready state
   * \param task_id task to mark as ready
   **/
  // void mark_task_ready(task_id_t task_id);
  /** \brief handle signals and wait for all threads to join when the process
   *complete. This function should be called from the main thread after having
   *created all ITTI tasks.
   **/
  void wait_tasks_end(void);

  /** \brief Send a termination message to all tasks.
   * \param src_task_id task that is broadcasting the message.
   **/
  int send_terminate_msg(task_id_t src_task_id);

  /** \brief Request a new timer
   *  \param interval_sec timer interval in seconds
   *  \param interval_us  timer interval in micro seconds
   *  \param task_id      task id of the task requesting the timer
   *  @returns 0 on failure, timer id otherwise
   **/
  timer_id_t timer_setup(
      uint32_t interval_sec, uint32_t interval_us, task_id_t task_id,
      uint64_t arg1_user = 0, uint64_t arg2_user = 0);

  /** \brief Remove the timer from list
   *  \param timer_id unique timer id
   *  \param task_id task id of the task that requested the timer
   *  @returns -1 on failure, 0 otherwise
   **/
  int timer_remove(timer_id_t timer_id);

  static void signal_handler(int signum);
};

#endif /* SRC_OAI_ITTI_ITTI_HPP_INCLUDED_ */
