/*
 *  Copyright 2019 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 "rtc_base/task_utils/repeating_task.h"

#include <atomic>
#include <memory>

#include "absl/functional/any_invocable.h"
#include "api/task_queue/task_queue_base.h"
#include "api/task_queue/test/mock_task_queue_base.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "rtc_base/event.h"
#include "rtc_base/task_queue_for_test.h"
#include "system_wrappers/include/clock.h"
#include "test/gmock.h"
#include "test/gtest.h"

// NOTE: Since these tests rely on real time behavior, they will be flaky
// if run on heavily loaded systems.
namespace webrtc {
namespace {
using ::testing::AtLeast;
using ::testing::Invoke;
using ::testing::MockFunction;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::WithArg;

constexpr TimeDelta kTimeout = TimeDelta::Millis(1000);

class MockClosure {
 public:
  MOCK_METHOD(TimeDelta, Call, ());
  MOCK_METHOD(void, Delete, ());
};

class MockTaskQueue : public MockTaskQueueBase {
 public:
  MockTaskQueue() : task_queue_setter_(this) {}

 private:
  CurrentTaskQueueSetter task_queue_setter_;
};

class FakeTaskQueue : public TaskQueueBase {
 public:
  explicit FakeTaskQueue(SimulatedClock* clock)
      : task_queue_setter_(this), clock_(clock) {}

  void Delete() override {}

  void PostTaskImpl(absl::AnyInvocable<void() &&> task,
                    const PostTaskTraits& /*traits*/,
                    const Location& /*location*/) override {
    last_task_ = std::move(task);
    last_precision_ = absl::nullopt;
    last_delay_ = TimeDelta::Zero();
  }

  void PostDelayedTaskImpl(absl::AnyInvocable<void() &&> task,
                           TimeDelta delay,
                           const PostDelayedTaskTraits& traits,
                           const Location& /*location*/) override {
    last_task_ = std::move(task);
    last_precision_ = traits.high_precision
                          ? TaskQueueBase::DelayPrecision::kHigh
                          : TaskQueueBase::DelayPrecision::kLow;
    last_delay_ = delay;
  }

  bool AdvanceTimeAndRunLastTask() {
    EXPECT_TRUE(last_task_);
    EXPECT_TRUE(last_delay_.IsFinite());
    clock_->AdvanceTime(last_delay_);
    last_delay_ = TimeDelta::MinusInfinity();
    auto task = std::move(last_task_);
    std::move(task)();
    return last_task_ == nullptr;
  }

  bool IsTaskQueued() { return !!last_task_; }

  TimeDelta last_delay() const {
    EXPECT_TRUE(last_delay_.IsFinite());
    return last_delay_;
  }

  absl::optional<TaskQueueBase::DelayPrecision> last_precision() const {
    return last_precision_;
  }

 private:
  CurrentTaskQueueSetter task_queue_setter_;
  SimulatedClock* clock_;
  absl::AnyInvocable<void() &&> last_task_;
  TimeDelta last_delay_ = TimeDelta::MinusInfinity();
  absl::optional<TaskQueueBase::DelayPrecision> last_precision_;
};

// NOTE: Since this utility class holds a raw pointer to a variable that likely
// lives on the stack, it's important that any repeating tasks that use this
// class be explicitly stopped when the test criteria have been met. If the
// task is not stopped, an instance of this class can be deleted when the
// pointed-to MockClosure has been deleted and we end up trying to call a
// virtual method on a deleted object in the dtor.
class MoveOnlyClosure {
 public:
  explicit MoveOnlyClosure(MockClosure* mock) : mock_(mock) {}
  MoveOnlyClosure(const MoveOnlyClosure&) = delete;
  MoveOnlyClosure(MoveOnlyClosure&& other) : mock_(other.mock_) {
    other.mock_ = nullptr;
  }
  ~MoveOnlyClosure() {
    if (mock_)
      mock_->Delete();
  }
  TimeDelta operator()() { return mock_->Call(); }

 private:
  MockClosure* mock_;
};
}  // namespace

TEST(RepeatingTaskTest, TaskIsStoppedOnStop) {
  const TimeDelta kShortInterval = TimeDelta::Millis(50);

  SimulatedClock clock(Timestamp::Zero());
  FakeTaskQueue task_queue(&clock);
  std::atomic_int counter(0);
  auto handle = RepeatingTaskHandle::Start(
      &task_queue,
      [&] {
        counter++;
        return kShortInterval;
      },
      TaskQueueBase::DelayPrecision::kLow, &clock);
  EXPECT_EQ(task_queue.last_delay(), TimeDelta::Zero());
  EXPECT_FALSE(task_queue.AdvanceTimeAndRunLastTask());
  EXPECT_EQ(counter.load(), 1);

  // The handle reposted at the short interval.
  EXPECT_EQ(task_queue.last_delay(), kShortInterval);

  // Stop the handle. This prevernts the counter from incrementing.
  handle.Stop();
  EXPECT_TRUE(task_queue.AdvanceTimeAndRunLastTask());
  EXPECT_EQ(counter.load(), 1);
}

TEST(RepeatingTaskTest, CompensatesForLongRunTime) {
  const TimeDelta kRepeatInterval = TimeDelta::Millis(2);
  // Sleeping inside the task for longer than the repeat interval once, should
  // be compensated for by repeating the task faster to catch up.
  const TimeDelta kSleepDuration = TimeDelta::Millis(20);

  std::atomic_int counter(0);
  SimulatedClock clock(Timestamp::Zero());
  FakeTaskQueue task_queue(&clock);
  RepeatingTaskHandle::Start(
      &task_queue,
      [&] {
        ++counter;
        // Task takes longer than the repeat duration.
        clock.AdvanceTime(kSleepDuration);
        return kRepeatInterval;
      },
      TaskQueueBase::DelayPrecision::kLow, &clock);

  EXPECT_EQ(task_queue.last_delay(), TimeDelta::Zero());
  EXPECT_FALSE(task_queue.AdvanceTimeAndRunLastTask());

  // Task is posted right away since it took longer to run then the repeat
  // interval.
  EXPECT_EQ(task_queue.last_delay(), TimeDelta::Zero());
  EXPECT_EQ(counter.load(), 1);
}

TEST(RepeatingTaskTest, CompensatesForShortRunTime) {
  SimulatedClock clock(Timestamp::Zero());
  FakeTaskQueue task_queue(&clock);
  std::atomic_int counter(0);
  RepeatingTaskHandle::Start(
      &task_queue,
      [&] {
        // Simulate the task taking 100ms, which should be compensated for.
        counter++;
        clock.AdvanceTime(TimeDelta::Millis(100));
        return TimeDelta::Millis(300);
      },
      TaskQueueBase::DelayPrecision::kLow, &clock);

  // Expect instant post task.
  EXPECT_EQ(task_queue.last_delay(), TimeDelta::Zero());
  // Task should be retained by the handler since it is not cancelled.
  EXPECT_FALSE(task_queue.AdvanceTimeAndRunLastTask());
  // New delay should be 200ms since repeat delay was 300ms but task took 100ms.
  EXPECT_EQ(task_queue.last_delay(), TimeDelta::Millis(200));
}

TEST(RepeatingTaskTest, CancelDelayedTaskBeforeItRuns) {
  rtc::Event done;
  MockClosure mock;
  EXPECT_CALL(mock, Call).Times(0);
  EXPECT_CALL(mock, Delete).WillOnce(Invoke([&done] { done.Set(); }));
  TaskQueueForTest task_queue("queue");
  auto handle = RepeatingTaskHandle::DelayedStart(
      task_queue.Get(), TimeDelta::Millis(100), MoveOnlyClosure(&mock));
  task_queue.PostTask(
      [handle = std::move(handle)]() mutable { handle.Stop(); });
  EXPECT_TRUE(done.Wait(kTimeout));
}

TEST(RepeatingTaskTest, CancelTaskAfterItRuns) {
  rtc::Event done;
  MockClosure mock;
  EXPECT_CALL(mock, Call).WillOnce(Return(TimeDelta::Millis(100)));
  EXPECT_CALL(mock, Delete).WillOnce(Invoke([&done] { done.Set(); }));
  TaskQueueForTest task_queue("queue");
  auto handle =
      RepeatingTaskHandle::Start(task_queue.Get(), MoveOnlyClosure(&mock));
  task_queue.PostTask(
      [handle = std::move(handle)]() mutable { handle.Stop(); });
  EXPECT_TRUE(done.Wait(kTimeout));
}

TEST(RepeatingTaskTest, TaskCanStopItself) {
  std::atomic_int counter(0);
  SimulatedClock clock(Timestamp::Zero());
  FakeTaskQueue task_queue(&clock);
  RepeatingTaskHandle handle = RepeatingTaskHandle::Start(&task_queue, [&] {
    ++counter;
    handle.Stop();
    return TimeDelta::Millis(2);
  });
  EXPECT_EQ(task_queue.last_delay(), TimeDelta::Zero());
  // Task cancelled itself so wants to be released.
  EXPECT_TRUE(task_queue.AdvanceTimeAndRunLastTask());
  EXPECT_EQ(counter.load(), 1);
}

TEST(RepeatingTaskTest, TaskCanStopItselfByReturningInfinity) {
  std::atomic_int counter(0);
  SimulatedClock clock(Timestamp::Zero());
  FakeTaskQueue task_queue(&clock);
  RepeatingTaskHandle handle = RepeatingTaskHandle::Start(&task_queue, [&] {
    ++counter;
    return TimeDelta::PlusInfinity();
  });
  EXPECT_EQ(task_queue.last_delay(), TimeDelta::Zero());
  // Task cancelled itself so wants to be released.
  EXPECT_TRUE(task_queue.AdvanceTimeAndRunLastTask());
  EXPECT_EQ(counter.load(), 1);
}

TEST(RepeatingTaskTest, ZeroReturnValueRepostsTheTask) {
  NiceMock<MockClosure> closure;
  rtc::Event done;
  EXPECT_CALL(closure, Call())
      .WillOnce(Return(TimeDelta::Zero()))
      .WillOnce(Invoke([&] {
        done.Set();
        return TimeDelta::PlusInfinity();
      }));
  TaskQueueForTest task_queue("queue");
  RepeatingTaskHandle::Start(task_queue.Get(), MoveOnlyClosure(&closure));
  EXPECT_TRUE(done.Wait(kTimeout));
}

TEST(RepeatingTaskTest, StartPeriodicTask) {
  MockFunction<TimeDelta()> closure;
  rtc::Event done;
  EXPECT_CALL(closure, Call())
      .WillOnce(Return(TimeDelta::Millis(20)))
      .WillOnce(Return(TimeDelta::Millis(20)))
      .WillOnce(Invoke([&] {
        done.Set();
        return TimeDelta::PlusInfinity();
      }));
  TaskQueueForTest task_queue("queue");
  RepeatingTaskHandle::Start(task_queue.Get(), closure.AsStdFunction());
  EXPECT_TRUE(done.Wait(kTimeout));
}

TEST(RepeatingTaskTest, Example) {
  class ObjectOnTaskQueue {
   public:
    void DoPeriodicTask() {}
    TimeDelta TimeUntilNextRun() { return TimeDelta::Millis(100); }
    void StartPeriodicTask(RepeatingTaskHandle* handle,
                           TaskQueueBase* task_queue) {
      *handle = RepeatingTaskHandle::Start(task_queue, [this] {
        DoPeriodicTask();
        return TimeUntilNextRun();
      });
    }
  };
  TaskQueueForTest task_queue("queue");
  auto object = std::make_unique<ObjectOnTaskQueue>();
  // Create and start the periodic task.
  RepeatingTaskHandle handle;
  object->StartPeriodicTask(&handle, task_queue.Get());
  // Restart the task
  task_queue.PostTask(
      [handle = std::move(handle)]() mutable { handle.Stop(); });
  object->StartPeriodicTask(&handle, task_queue.Get());
  task_queue.PostTask(
      [handle = std::move(handle)]() mutable { handle.Stop(); });
  struct Destructor {
    void operator()() { object.reset(); }
    std::unique_ptr<ObjectOnTaskQueue> object;
  };
  task_queue.PostTask(Destructor{std::move(object)});
  // Do not wait for the destructor closure in order to create a race between
  // task queue destruction and running the desctructor closure.
}

TEST(RepeatingTaskTest, ClockIntegration) {
  absl::AnyInvocable<void() &&> delayed_task;
  TimeDelta expected_delay = TimeDelta::Zero();
  SimulatedClock clock(Timestamp::Zero());

  NiceMock<MockTaskQueue> task_queue;
  ON_CALL(task_queue, PostDelayedTaskImpl)
      .WillByDefault([&](absl::AnyInvocable<void() &&> task, TimeDelta delay,
                         const MockTaskQueue::PostDelayedTaskTraits&,
                         const Location&) {
        EXPECT_EQ(delay, expected_delay);
        delayed_task = std::move(task);
      });

  expected_delay = TimeDelta::Millis(100);
  RepeatingTaskHandle handle = RepeatingTaskHandle::DelayedStart(
      &task_queue, TimeDelta::Millis(100),
      [&clock]() {
        EXPECT_EQ(Timestamp::Millis(100), clock.CurrentTime());
        // Simulate work happening for 10ms.
        clock.AdvanceTimeMilliseconds(10);
        return TimeDelta::Millis(100);
      },
      TaskQueueBase::DelayPrecision::kLow, &clock);

  clock.AdvanceTimeMilliseconds(100);
  absl::AnyInvocable<void()&&> task_to_run = std::move(delayed_task);
  expected_delay = TimeDelta::Millis(90);
  std::move(task_to_run)();
  EXPECT_NE(delayed_task, nullptr);
  handle.Stop();
}

TEST(RepeatingTaskTest, CanBeStoppedAfterTaskQueueDeletedTheRepeatingTask) {
  absl::AnyInvocable<void() &&> repeating_task;

  MockTaskQueue task_queue;
  EXPECT_CALL(task_queue, PostDelayedTaskImpl)
      .WillOnce(WithArg<0>([&](absl::AnyInvocable<void() &&> task) {
        repeating_task = std::move(task);
      }));

  RepeatingTaskHandle handle =
      RepeatingTaskHandle::DelayedStart(&task_queue, TimeDelta::Millis(100),
                                        [] { return TimeDelta::Millis(100); });

  // shutdown task queue: delete all pending tasks and run 'regular' task.
  repeating_task = nullptr;
  handle.Stop();
}

TEST(RepeatingTaskTest, DefaultPrecisionIsLow) {
  SimulatedClock clock(Timestamp::Zero());
  FakeTaskQueue task_queue(&clock);
  // Closure that repeats twice.
  MockFunction<TimeDelta()> closure;
  EXPECT_CALL(closure, Call())
      .WillOnce(Return(TimeDelta::Millis(1)))
      .WillOnce(Return(TimeDelta::PlusInfinity()));
  RepeatingTaskHandle::Start(&task_queue, closure.AsStdFunction());
  // Initial task is a PostTask().
  EXPECT_FALSE(task_queue.last_precision().has_value());
  EXPECT_FALSE(task_queue.AdvanceTimeAndRunLastTask());
  // Repeated task is a delayed task with the default precision: low.
  EXPECT_TRUE(task_queue.last_precision().has_value());
  EXPECT_EQ(task_queue.last_precision().value(),
            TaskQueueBase::DelayPrecision::kLow);
  // No more tasks.
  EXPECT_TRUE(task_queue.AdvanceTimeAndRunLastTask());
}

TEST(RepeatingTaskTest, CanSpecifyToPostTasksWithLowPrecision) {
  SimulatedClock clock(Timestamp::Zero());
  FakeTaskQueue task_queue(&clock);
  // Closure that repeats twice.
  MockFunction<TimeDelta()> closure;
  EXPECT_CALL(closure, Call())
      .WillOnce(Return(TimeDelta::Millis(1)))
      .WillOnce(Return(TimeDelta::PlusInfinity()));
  RepeatingTaskHandle::Start(&task_queue, closure.AsStdFunction(),
                             TaskQueueBase::DelayPrecision::kLow);
  // Initial task is a PostTask().
  EXPECT_FALSE(task_queue.last_precision().has_value());
  EXPECT_FALSE(task_queue.AdvanceTimeAndRunLastTask());
  // Repeated task is a delayed task with the specified precision.
  EXPECT_TRUE(task_queue.last_precision().has_value());
  EXPECT_EQ(task_queue.last_precision().value(),
            TaskQueueBase::DelayPrecision::kLow);
  // No more tasks.
  EXPECT_TRUE(task_queue.AdvanceTimeAndRunLastTask());
}

TEST(RepeatingTaskTest, CanSpecifyToPostTasksWithHighPrecision) {
  SimulatedClock clock(Timestamp::Zero());
  FakeTaskQueue task_queue(&clock);
  // Closure that repeats twice.
  MockFunction<TimeDelta()> closure;
  EXPECT_CALL(closure, Call())
      .WillOnce(Return(TimeDelta::Millis(1)))
      .WillOnce(Return(TimeDelta::PlusInfinity()));
  RepeatingTaskHandle::Start(&task_queue, closure.AsStdFunction(),
                             TaskQueueBase::DelayPrecision::kHigh);
  // Initial task is a PostTask().
  EXPECT_FALSE(task_queue.last_precision().has_value());
  EXPECT_FALSE(task_queue.AdvanceTimeAndRunLastTask());
  // Repeated task is a delayed task with the specified precision.
  EXPECT_TRUE(task_queue.last_precision().has_value());
  EXPECT_EQ(task_queue.last_precision().value(),
            TaskQueueBase::DelayPrecision::kHigh);
  // No more tasks.
  EXPECT_TRUE(task_queue.AdvanceTimeAndRunLastTask());
}

}  // namespace webrtc
