/*
 *  Copyright 2020 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.
 */

#include <memory>
#include <vector>

#include "api/test/time_controller.h"
#include "api/units/time_delta.h"
#include "rtc_base/event.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/time_controller/real_time_controller.h"
#include "test/time_controller/simulated_time_controller.h"

namespace webrtc {
namespace {

using ::testing::ElementsAreArray;
using ::testing::TestParamInfo;
using ::testing::TestWithParam;
using ::testing::Values;

enum class TimeMode { kRealTime, kSimulated };

std::unique_ptr<TimeController> CreateTimeController(TimeMode mode) {
  switch (mode) {
    case TimeMode::kRealTime:
      return std::make_unique<RealTimeController>();
    case TimeMode::kSimulated:
      // Using an offset of 100000 to get nice fixed width and readable
      // timestamps in typical test scenarios.
      constexpr Timestamp kSimulatedStartTime = Timestamp::Seconds(100000);
      return std::make_unique<GlobalSimulatedTimeController>(
          kSimulatedStartTime);
  }
}

std::string ParamsToString(const TestParamInfo<webrtc::TimeMode>& param) {
  switch (param.param) {
    case webrtc::TimeMode::kRealTime:
      return "RealTime";
    case webrtc::TimeMode::kSimulated:
      return "SimulatedTime";
    default:
      RTC_DCHECK_NOTREACHED() << "Time mode not supported";
  }
}

// Keeps order of executions. May be called from different threads.
class ExecutionOrderKeeper {
 public:
  void Executed(int execution_id) {
    MutexLock lock(&mutex_);
    order_.push_back(execution_id);
  }

  std::vector<int> order() const {
    MutexLock lock(&mutex_);
    return order_;
  }

 private:
  mutable Mutex mutex_;
  std::vector<int> order_ RTC_GUARDED_BY(mutex_);
};

// Tests conformance between real time and simulated time time controller.
class SimulatedRealTimeControllerConformanceTest
    : public TestWithParam<webrtc::TimeMode> {};

TEST_P(SimulatedRealTimeControllerConformanceTest, ThreadPostOrderTest) {
  std::unique_ptr<TimeController> time_controller =
      CreateTimeController(GetParam());
  std::unique_ptr<rtc::Thread> thread = time_controller->CreateThread("thread");

  // Tasks on thread have to be executed in order in which they were
  // posted.
  ExecutionOrderKeeper execution_order;
  thread->PostTask([&]() { execution_order.Executed(1); });
  thread->PostTask([&]() { execution_order.Executed(2); });
  time_controller->AdvanceTime(TimeDelta::Millis(100));
  EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2}));
  // Destroy `thread` before `execution_order` to be sure `execution_order`
  // is not accessed on the posted task after it is destroyed.
  thread = nullptr;
}

TEST_P(SimulatedRealTimeControllerConformanceTest, ThreadPostDelayedOrderTest) {
  std::unique_ptr<TimeController> time_controller =
      CreateTimeController(GetParam());
  std::unique_ptr<rtc::Thread> thread = time_controller->CreateThread("thread");

  ExecutionOrderKeeper execution_order;
  thread->PostDelayedTask([&]() { execution_order.Executed(2); },
                          TimeDelta::Millis(500));
  thread->PostTask([&]() { execution_order.Executed(1); });
  time_controller->AdvanceTime(TimeDelta::Millis(600));
  EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2}));
  // Destroy `thread` before `execution_order` to be sure `execution_order`
  // is not accessed on the posted task after it is destroyed.
  thread = nullptr;
}

TEST_P(SimulatedRealTimeControllerConformanceTest, ThreadPostInvokeOrderTest) {
  std::unique_ptr<TimeController> time_controller =
      CreateTimeController(GetParam());
  std::unique_ptr<rtc::Thread> thread = time_controller->CreateThread("thread");

  // Tasks on thread have to be executed in order in which they were
  // posted/invoked.
  ExecutionOrderKeeper execution_order;
  thread->PostTask([&]() { execution_order.Executed(1); });
  thread->BlockingCall([&]() { execution_order.Executed(2); });
  time_controller->AdvanceTime(TimeDelta::Millis(100));
  EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2}));
  // Destroy `thread` before `execution_order` to be sure `execution_order`
  // is not accessed on the posted task after it is destroyed.
  thread = nullptr;
}

TEST_P(SimulatedRealTimeControllerConformanceTest,
       ThreadPostInvokeFromThreadOrderTest) {
  std::unique_ptr<TimeController> time_controller =
      CreateTimeController(GetParam());
  std::unique_ptr<rtc::Thread> thread = time_controller->CreateThread("thread");

  // If task is invoked from thread X on thread X it has to be executed
  // immediately.
  ExecutionOrderKeeper execution_order;
  thread->PostTask([&]() {
    thread->PostTask([&]() { execution_order.Executed(2); });
    thread->BlockingCall([&]() { execution_order.Executed(1); });
  });
  time_controller->AdvanceTime(TimeDelta::Millis(100));
  EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2}));
  // Destroy `thread` before `execution_order` to be sure `execution_order`
  // is not accessed on the posted task after it is destroyed.
  thread = nullptr;
}

TEST_P(SimulatedRealTimeControllerConformanceTest,
       TaskQueuePostEventWaitOrderTest) {
  std::unique_ptr<TimeController> time_controller =
      CreateTimeController(GetParam());
  auto task_queue = time_controller->GetTaskQueueFactory()->CreateTaskQueue(
      "task_queue", webrtc::TaskQueueFactory::Priority::NORMAL);

  // Tasks on thread have to be executed in order in which they were
  // posted/invoked.
  ExecutionOrderKeeper execution_order;
  rtc::Event event;
  task_queue->PostTask([&]() { execution_order.Executed(1); });
  task_queue->PostTask([&]() {
    execution_order.Executed(2);
    event.Set();
  });
  EXPECT_TRUE(event.Wait(/*give_up_after=*/TimeDelta::Millis(100)));
  time_controller->AdvanceTime(TimeDelta::Millis(100));
  EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2}));
  // Destroy `task_queue` before `execution_order` to be sure `execution_order`
  // is not accessed on the posted task after it is destroyed.
  task_queue = nullptr;
}

INSTANTIATE_TEST_SUITE_P(ConformanceTest,
                         SimulatedRealTimeControllerConformanceTest,
                         Values(TimeMode::kRealTime, TimeMode::kSimulated),
                         ParamsToString);

}  // namespace
}  // namespace webrtc
