// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#define FML_USED_ON_EMBEDDER

#include <thread>
#include <utility>

#include "flutter/fml/message_loop_task_queues.h"
#include "flutter/fml/synchronization/count_down_latch.h"
#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/fml/time/chrono_timestamp_provider.h"
#include "gtest/gtest.h"

namespace fml {
namespace testing {

class TestWakeable : public fml::Wakeable {
 public:
  using WakeUpCall = std::function<void(const fml::TimePoint)>;

  explicit TestWakeable(WakeUpCall call) : wake_up_call_(std::move(call)) {}

  void WakeUp(fml::TimePoint time_point) override { wake_up_call_(time_point); }

 private:
  WakeUpCall wake_up_call_;
};

static int CountRemainingTasks(fml::MessageLoopTaskQueues* task_queue,
                               const TaskQueueId& queue_id,
                               bool run_invocation = false) {
  const auto now = ChronoTicksSinceEpoch();
  int count = 0;
  fml::closure invocation;
  do {
    invocation = task_queue->GetNextTaskToRun(queue_id, now);
    if (!invocation) {
      break;
    }
    count++;
    if (run_invocation) {
      invocation();
    }
  } while (invocation);
  return count;
}

TEST(MessageLoopTaskQueueMergeUnmerge,
     AfterMergePrimaryTasksServicedOnPrimary) {
  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();

  auto queue_id_1 = task_queue->CreateTaskQueue();
  auto queue_id_2 = task_queue->CreateTaskQueue();

  task_queue->RegisterTask(
      queue_id_1, []() {}, ChronoTicksSinceEpoch());
  ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_1));

  task_queue->Merge(queue_id_1, queue_id_2);
  task_queue->RegisterTask(
      queue_id_1, []() {}, ChronoTicksSinceEpoch());

  ASSERT_EQ(2u, task_queue->GetNumPendingTasks(queue_id_1));
  ASSERT_EQ(0u, task_queue->GetNumPendingTasks(queue_id_2));
}

TEST(MessageLoopTaskQueueMergeUnmerge,
     AfterMergeSecondaryTasksAlsoServicedOnPrimary) {
  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();

  auto queue_id_1 = task_queue->CreateTaskQueue();
  auto queue_id_2 = task_queue->CreateTaskQueue();

  task_queue->RegisterTask(
      queue_id_2, []() {}, ChronoTicksSinceEpoch());
  ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_2));

  task_queue->Merge(queue_id_1, queue_id_2);
  ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_1));
  ASSERT_EQ(0u, task_queue->GetNumPendingTasks(queue_id_2));
}

TEST(MessageLoopTaskQueueMergeUnmerge, MergeUnmergeTasksPreserved) {
  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();

  auto queue_id_1 = task_queue->CreateTaskQueue();
  auto queue_id_2 = task_queue->CreateTaskQueue();

  task_queue->RegisterTask(
      queue_id_1, []() {}, ChronoTicksSinceEpoch());
  task_queue->RegisterTask(
      queue_id_2, []() {}, ChronoTicksSinceEpoch());

  ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_1));
  ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_2));

  task_queue->Merge(queue_id_1, queue_id_2);

  ASSERT_EQ(2u, task_queue->GetNumPendingTasks(queue_id_1));
  ASSERT_EQ(0u, task_queue->GetNumPendingTasks(queue_id_2));

  task_queue->Unmerge(queue_id_1, queue_id_2);

  ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_1));
  ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_2));
}

/// Multiple standalone engines scene
TEST(MessageLoopTaskQueueMergeUnmerge,
     OneCanOwnMultipleQueuesAndUnmergeIndependently) {
  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();
  auto queue_id_1 = task_queue->CreateTaskQueue();
  auto queue_id_2 = task_queue->CreateTaskQueue();
  auto queue_id_3 = task_queue->CreateTaskQueue();

  // merge
  ASSERT_TRUE(task_queue->Merge(queue_id_1, queue_id_2));
  ASSERT_TRUE(task_queue->Owns(queue_id_1, queue_id_2));
  ASSERT_FALSE(task_queue->Owns(queue_id_1, queue_id_3));

  ASSERT_TRUE(task_queue->Merge(queue_id_1, queue_id_3));
  ASSERT_TRUE(task_queue->Owns(queue_id_1, queue_id_2));
  ASSERT_TRUE(task_queue->Owns(queue_id_1, queue_id_3));

  // unmerge
  ASSERT_TRUE(task_queue->Unmerge(queue_id_1, queue_id_2));
  ASSERT_FALSE(task_queue->Owns(queue_id_1, queue_id_2));
  ASSERT_TRUE(task_queue->Owns(queue_id_1, queue_id_3));

  ASSERT_TRUE(task_queue->Unmerge(queue_id_1, queue_id_3));
  ASSERT_FALSE(task_queue->Owns(queue_id_1, queue_id_2));
  ASSERT_FALSE(task_queue->Owns(queue_id_1, queue_id_3));
}

TEST(MessageLoopTaskQueueMergeUnmerge,
     CannotMergeSameQueueToTwoDifferentOwners) {
  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();
  auto queue = task_queue->CreateTaskQueue();
  auto owner_1 = task_queue->CreateTaskQueue();
  auto owner_2 = task_queue->CreateTaskQueue();

  ASSERT_TRUE(task_queue->Merge(owner_1, queue));
  ASSERT_FALSE(task_queue->Merge(owner_2, queue));
}

TEST(MessageLoopTaskQueueMergeUnmerge, MergeFailIfAlreadySubsumed) {
  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();

  auto queue_id_1 = task_queue->CreateTaskQueue();
  auto queue_id_2 = task_queue->CreateTaskQueue();
  auto queue_id_3 = task_queue->CreateTaskQueue();

  ASSERT_TRUE(task_queue->Merge(queue_id_1, queue_id_2));
  ASSERT_FALSE(task_queue->Merge(queue_id_2, queue_id_3));
  ASSERT_FALSE(task_queue->Merge(queue_id_2, queue_id_1));
}

TEST(MessageLoopTaskQueueMergeUnmerge,
     MergeFailIfAlreadyOwnsButTryToBeSubsumed) {
  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();

  auto queue_id_1 = task_queue->CreateTaskQueue();
  auto queue_id_2 = task_queue->CreateTaskQueue();
  auto queue_id_3 = task_queue->CreateTaskQueue();

  task_queue->Merge(queue_id_1, queue_id_2);
  // A recursively linked merging will fail
  ASSERT_FALSE(task_queue->Merge(queue_id_3, queue_id_1));
}

TEST(MessageLoopTaskQueueMergeUnmerge, UnmergeFailsOnSubsumedOrNeverMerged) {
  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();

  auto queue_id_1 = task_queue->CreateTaskQueue();
  auto queue_id_2 = task_queue->CreateTaskQueue();
  auto queue_id_3 = task_queue->CreateTaskQueue();

  task_queue->Merge(queue_id_1, queue_id_2);
  ASSERT_FALSE(task_queue->Unmerge(queue_id_2, queue_id_3));
  ASSERT_FALSE(task_queue->Unmerge(queue_id_1, queue_id_3));
  ASSERT_FALSE(task_queue->Unmerge(queue_id_3, queue_id_1));
  ASSERT_FALSE(task_queue->Unmerge(queue_id_2, queue_id_1));
}

TEST(MessageLoopTaskQueueMergeUnmerge, MergeInvokesBothWakeables) {
  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();

  auto queue_id_1 = task_queue->CreateTaskQueue();
  auto queue_id_2 = task_queue->CreateTaskQueue();

  fml::CountDownLatch latch(2);

  auto wakeable1 = std::make_unique<TestWakeable>(
      [&](fml::TimePoint wake_time) { latch.CountDown(); });
  auto wakeable2 = std::make_unique<TestWakeable>(
      [&](fml::TimePoint wake_time) { latch.CountDown(); });

  task_queue->SetWakeable(queue_id_1, wakeable1.get());
  task_queue->SetWakeable(queue_id_2, wakeable2.get());

  task_queue->RegisterTask(
      queue_id_1, []() {}, ChronoTicksSinceEpoch());

  task_queue->Merge(queue_id_1, queue_id_2);

  CountRemainingTasks(task_queue, queue_id_1);

  latch.Wait();
}

TEST(MessageLoopTaskQueueMergeUnmerge,
     MergeUnmergeInvokesBothWakeablesSeparately) {
  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();

  auto queue_id_1 = task_queue->CreateTaskQueue();
  auto queue_id_2 = task_queue->CreateTaskQueue();

  fml::AutoResetWaitableEvent latch_1, latch_2;

  auto wakeable1 = std::make_unique<TestWakeable>(
      [&](fml::TimePoint wake_time) { latch_1.Signal(); });
  auto wakeable2 = std::make_unique<TestWakeable>(
      [&](fml::TimePoint wake_time) { latch_2.Signal(); });

  task_queue->SetWakeable(queue_id_1, wakeable1.get());
  task_queue->SetWakeable(queue_id_2, wakeable2.get());

  task_queue->RegisterTask(
      queue_id_1, []() {}, ChronoTicksSinceEpoch());
  task_queue->RegisterTask(
      queue_id_2, []() {}, ChronoTicksSinceEpoch());

  task_queue->Merge(queue_id_1, queue_id_2);
  task_queue->Unmerge(queue_id_1, queue_id_2);

  CountRemainingTasks(task_queue, queue_id_1);

  latch_1.Wait();

  CountRemainingTasks(task_queue, queue_id_2);

  latch_2.Wait();
}

TEST(MessageLoopTaskQueueMergeUnmerge, GetTasksToRunNowBlocksMerge) {
  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();

  auto queue_id_1 = task_queue->CreateTaskQueue();
  auto queue_id_2 = task_queue->CreateTaskQueue();

  fml::AutoResetWaitableEvent wake_up_start, wake_up_end, merge_start,
      merge_end;

  auto wakeable = std::make_unique<TestWakeable>([&](fml::TimePoint wake_time) {
    wake_up_start.Signal();
    wake_up_end.Wait();
  });

  task_queue->RegisterTask(
      queue_id_1, []() {}, ChronoTicksSinceEpoch());
  task_queue->SetWakeable(queue_id_1, wakeable.get());

  std::thread tasks_to_run_now_thread(
      [&]() { CountRemainingTasks(task_queue, queue_id_1); });

  wake_up_start.Wait();
  bool merge_done = false;

  std::thread merge_thread([&]() {
    merge_start.Signal();
    task_queue->Merge(queue_id_1, queue_id_2);
    merge_done = true;
    merge_end.Signal();
  });

  merge_start.Wait();
  ASSERT_FALSE(merge_done);
  wake_up_end.Signal();

  merge_end.Wait();
  ASSERT_TRUE(merge_done);

  tasks_to_run_now_thread.join();
  merge_thread.join();
}

TEST(MessageLoopTaskQueueMergeUnmerge,
     FollowingTasksSwitchQueueIfFirstTaskMergesThreads) {
  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();

  auto queue_id_1 = task_queue->CreateTaskQueue();
  auto queue_id_2 = task_queue->CreateTaskQueue();

  fml::CountDownLatch latch(2);

  auto wakeable1 = std::make_unique<TestWakeable>(
      [&](fml::TimePoint wake_time) { latch.CountDown(); });
  auto wakeable2 = std::make_unique<TestWakeable>(
      [&](fml::TimePoint wake_time) { latch.CountDown(); });

  task_queue->SetWakeable(queue_id_1, wakeable1.get());
  task_queue->SetWakeable(queue_id_2, wakeable2.get());

  task_queue->RegisterTask(
      queue_id_2, [&]() { task_queue->Merge(queue_id_1, queue_id_2); },
      ChronoTicksSinceEpoch());

  task_queue->RegisterTask(
      queue_id_2, []() {}, ChronoTicksSinceEpoch());

  ASSERT_EQ(CountRemainingTasks(task_queue, queue_id_2, true), 1);
  ASSERT_EQ(CountRemainingTasks(task_queue, queue_id_1, true), 1);

  latch.Wait();
}

}  // namespace testing
}  // namespace fml
