/*
 *  Copyright 2018 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */
#ifndef TEST_SCENARIO_SCENARIO_H_
#define TEST_SCENARIO_SCENARIO_H_
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "api/test/time_controller.h"
#include "rtc_base/constructor_magic.h"
#include "rtc_base/fake_clock.h"
#include "rtc_base/task_queue.h"
#include "rtc_base/task_utils/repeating_task.h"
#include "test/gtest.h"
#include "test/logging/log_writer.h"
#include "test/network/network_emulation_manager.h"
#include "test/scenario/audio_stream.h"
#include "test/scenario/call_client.h"
#include "test/scenario/column_printer.h"
#include "test/scenario/network_node.h"
#include "test/scenario/scenario_config.h"
#include "test/scenario/video_stream.h"

namespace webrtc {
namespace test {
// Scenario is a class owning everything for a test scenario. It creates and
// holds network nodes, call clients and media streams. It also provides methods
// for changing behavior at runtime. Since it always keeps ownership of the
// created components, it generally returns non-owning pointers. It maintains
// the life of its objects until it is destroyed.
// For methods accepting configuration structs, a modifier function interface is
// generally provided. This allows simple partial overriding of the default
// configuration.
class Scenario {
 public:
  Scenario();
  explicit Scenario(const testing::TestInfo* test_info);
  explicit Scenario(std::string file_name);
  Scenario(std::string file_name, bool real_time);
  Scenario(std::unique_ptr<LogWriterFactoryInterface> log_writer_manager,
           bool real_time);
  RTC_DISALLOW_COPY_AND_ASSIGN(Scenario);
  ~Scenario();
  NetworkEmulationManagerImpl* net() { return &network_manager_; }

  EmulatedNetworkNode* CreateSimulationNode(NetworkSimulationConfig config);
  EmulatedNetworkNode* CreateSimulationNode(
      std::function<void(NetworkSimulationConfig*)> config_modifier);

  SimulationNode* CreateMutableSimulationNode(NetworkSimulationConfig config);
  SimulationNode* CreateMutableSimulationNode(
      std::function<void(NetworkSimulationConfig*)> config_modifier);

  CallClient* CreateClient(std::string name, CallClientConfig config);
  CallClient* CreateClient(
      std::string name,
      std::function<void(CallClientConfig*)> config_modifier);

  CallClientPair* CreateRoutes(CallClient* first,
                               std::vector<EmulatedNetworkNode*> send_link,
                               CallClient* second,
                               std::vector<EmulatedNetworkNode*> return_link);

  CallClientPair* CreateRoutes(CallClient* first,
                               std::vector<EmulatedNetworkNode*> send_link,
                               DataSize first_overhead,
                               CallClient* second,
                               std::vector<EmulatedNetworkNode*> return_link,
                               DataSize second_overhead);

  void ChangeRoute(std::pair<CallClient*, CallClient*> clients,
                   std::vector<EmulatedNetworkNode*> over_nodes);

  void ChangeRoute(std::pair<CallClient*, CallClient*> clients,
                   std::vector<EmulatedNetworkNode*> over_nodes,
                   DataSize overhead);

  VideoStreamPair* CreateVideoStream(
      std::pair<CallClient*, CallClient*> clients,
      std::function<void(VideoStreamConfig*)> config_modifier);
  VideoStreamPair* CreateVideoStream(
      std::pair<CallClient*, CallClient*> clients,
      VideoStreamConfig config);

  AudioStreamPair* CreateAudioStream(
      std::pair<CallClient*, CallClient*> clients,
      std::function<void(AudioStreamConfig*)> config_modifier);
  AudioStreamPair* CreateAudioStream(
      std::pair<CallClient*, CallClient*> clients,
      AudioStreamConfig config);

  // Runs the provided function with a fixed interval. For real time tests,
  // |function| starts being called after |interval| from the call to Every().
  void Every(TimeDelta interval, std::function<void(TimeDelta)> function);
  void Every(TimeDelta interval, std::function<void()> function);

  // Runs the provided function on the internal task queue. This ensure that
  // it's run on the main thread for simulated time tests.
  void Post(std::function<void()> function);

  // Runs the provided function after given duration has passed. For real time
  // tests, |function| is called after |target_time_since_start| from the call
  // to Every().
  void At(TimeDelta offset, std::function<void()> function);

  // Sends a packet over the nodes and runs |action| when it has been delivered.
  void NetworkDelayedAction(std::vector<EmulatedNetworkNode*> over_nodes,
                            size_t packet_size,
                            std::function<void()> action);

  // Runs the scenario for the given time.
  void RunFor(TimeDelta duration);
  // Runs the scenario until |target_time_since_start|.
  void RunUntil(TimeDelta target_time_since_start);
  // Runs the scenario until |target_time_since_start| or |exit_function|
  // returns true. |exit_function| is polled after each |check_interval| has
  // passed.
  void RunUntil(TimeDelta target_time_since_start,
                TimeDelta check_interval,
                std::function<bool()> exit_function);
  void Start();
  void Stop();

  // Triggers sending of dummy packets over the given nodes.
  void TriggerPacketBurst(std::vector<EmulatedNetworkNode*> over_nodes,
                          size_t num_packets,
                          size_t packet_size);

  ColumnPrinter TimePrinter();
  StatesPrinter* CreatePrinter(std::string name,
                               TimeDelta interval,
                               std::vector<ColumnPrinter> printers);

  // Returns the current time.
  Timestamp Now();
  // Return the duration of the current session so far.
  TimeDelta TimeSinceStart();

  std::unique_ptr<RtcEventLogOutput> GetLogWriter(std::string name) {
    if (!log_writer_factory_ || name.empty())
      return nullptr;
    return log_writer_factory_->Create(name);
  }
  std::unique_ptr<LogWriterFactoryInterface> GetLogWriterFactory(
      std::string name) {
    if (!log_writer_factory_ || name.empty())
      return nullptr;
    return std::make_unique<LogWriterFactoryAddPrefix>(
        log_writer_factory_.get(), name);
  }

 private:
  TimeDelta TimeUntilTarget(TimeDelta target_time_offset);

  const std::unique_ptr<LogWriterFactoryInterface> log_writer_factory_;
  NetworkEmulationManagerImpl network_manager_;
  Clock* clock_;

  std::vector<std::unique_ptr<CallClient>> clients_;
  std::vector<std::unique_ptr<CallClientPair>> client_pairs_;
  std::vector<std::unique_ptr<VideoStreamPair>> video_streams_;
  std::vector<std::unique_ptr<AudioStreamPair>> audio_streams_;
  std::vector<std::unique_ptr<SimulationNode>> simulation_nodes_;
  std::vector<std::unique_ptr<StatesPrinter>> printers_;

  rtc::scoped_refptr<AudioDecoderFactory> audio_decoder_factory_;
  rtc::scoped_refptr<AudioEncoderFactory> audio_encoder_factory_;

  Timestamp start_time_ = Timestamp::PlusInfinity();
  // Defined last so it's destroyed first.
  rtc::TaskQueue task_queue_;
};
}  // namespace test
}  // namespace webrtc

#endif  // TEST_SCENARIO_SCENARIO_H_