// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/base/queued_task_poster.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace remoting {

class QueuedTaskPosterTest : public testing::Test {
 public:
  QueuedTaskPosterTest();
  void SetUp() override;
  void TearDown() override;

 protected:
  base::OnceClosure SetSequenceStartedClosure(bool started);
  base::OnceClosure AssertExecutionOrderClosure(int order);
  base::OnceClosure AssertSequenceNotStartedClosure();

  void RunUntilPosterDone();

  scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
  scoped_refptr<base::SingleThreadTaskRunner> target_task_runner_;
  std::unique_ptr<QueuedTaskPoster> poster_;
  int current_execution_order_ = 0;

 private:
  void SetSequenceStarted(bool started);
  void AssertExecutionOrder(int order);
  void AssertSequenceNotStarted();

  base::test::TaskEnvironment task_environment_;
  bool sequence_started_ = false;
};

QueuedTaskPosterTest::QueuedTaskPosterTest() = default;

void QueuedTaskPosterTest::SetUp() {
  main_task_runner_ = base::SingleThreadTaskRunner::GetCurrentDefault();
  target_task_runner_ = base::ThreadPool::CreateSingleThreadTaskRunner({});
  poster_ = std::make_unique<QueuedTaskPoster>(target_task_runner_);
}

void QueuedTaskPosterTest::TearDown() {
  base::test::TestFuture<void> future;
  task_environment_.GetMainThreadTaskRunner()->PostTask(FROM_HERE,
                                                        future.GetCallback());
  EXPECT_TRUE(future.Wait());
}

base::OnceClosure QueuedTaskPosterTest::SetSequenceStartedClosure(
    bool started) {
  return base::BindOnce(&QueuedTaskPosterTest::SetSequenceStarted,
                        base::Unretained(this), started);
}

base::OnceClosure QueuedTaskPosterTest::AssertExecutionOrderClosure(int order) {
  return base::BindOnce(&QueuedTaskPosterTest::AssertExecutionOrder,
                        base::Unretained(this), order);
}

base::OnceClosure QueuedTaskPosterTest::AssertSequenceNotStartedClosure() {
  return base::BindOnce(&QueuedTaskPosterTest::AssertSequenceNotStarted,
                        base::Unretained(this));
}

void QueuedTaskPosterTest::RunUntilPosterDone() {
  base::test::TestFuture<void> future;
  poster_->AddTask(base::BindOnce(
      base::IgnoreResult(&base::SingleThreadTaskRunner::PostTask),
      main_task_runner_, FROM_HERE, future.GetCallback()));
  ASSERT_TRUE(future.Wait());
}

void QueuedTaskPosterTest::SetSequenceStarted(bool started) {
  sequence_started_ = started;
}

void QueuedTaskPosterTest::AssertExecutionOrder(int order) {
  ASSERT_EQ(current_execution_order_ + 1, order);
  current_execution_order_++;
}

void QueuedTaskPosterTest::AssertSequenceNotStarted() {
  ASSERT_FALSE(sequence_started_);
}

TEST_F(QueuedTaskPosterTest, TestTaskOrder) {
  poster_->AddTask(AssertExecutionOrderClosure(1));
  poster_->AddTask(AssertExecutionOrderClosure(2));
  poster_->AddTask(AssertExecutionOrderClosure(3));
  poster_->AddTask(AssertExecutionOrderClosure(4));
  poster_->AddTask(AssertExecutionOrderClosure(5));

  RunUntilPosterDone();
  EXPECT_EQ(current_execution_order_, 5);
}

TEST_F(QueuedTaskPosterTest, TestTaskSequenceNotInterfered) {
  target_task_runner_->PostTask(FROM_HERE, AssertSequenceNotStartedClosure());
  poster_->AddTask(SetSequenceStartedClosure(true));
  target_task_runner_->PostTask(FROM_HERE, AssertSequenceNotStartedClosure());
  poster_->AddTask(AssertExecutionOrderClosure(1));
  target_task_runner_->PostTask(FROM_HERE, AssertSequenceNotStartedClosure());
  poster_->AddTask(AssertExecutionOrderClosure(2));
  target_task_runner_->PostTask(FROM_HERE, AssertSequenceNotStartedClosure());
  poster_->AddTask(AssertExecutionOrderClosure(3));
  target_task_runner_->PostTask(FROM_HERE, AssertSequenceNotStartedClosure());
  poster_->AddTask(AssertExecutionOrderClosure(4));
  target_task_runner_->PostTask(FROM_HERE, AssertSequenceNotStartedClosure());
  poster_->AddTask(AssertExecutionOrderClosure(5));
  target_task_runner_->PostTask(FROM_HERE, AssertSequenceNotStartedClosure());
  poster_->AddTask(SetSequenceStartedClosure(false));
  target_task_runner_->PostTask(FROM_HERE, AssertSequenceNotStartedClosure());

  RunUntilPosterDone();
  EXPECT_EQ(current_execution_order_, 5);
}

TEST_F(QueuedTaskPosterTest, TestUsingPosterInMultipleTasks) {
  poster_->AddTask(AssertExecutionOrderClosure(1));
  poster_->AddTask(AssertExecutionOrderClosure(2));
  poster_->AddTask(AssertExecutionOrderClosure(3));

  RunUntilPosterDone();
  EXPECT_EQ(current_execution_order_, 3);

  poster_->AddTask(AssertExecutionOrderClosure(4));
  poster_->AddTask(AssertExecutionOrderClosure(5));
  poster_->AddTask(AssertExecutionOrderClosure(6));

  RunUntilPosterDone();
  EXPECT_EQ(current_execution_order_, 6);
}

}  // namespace remoting
