// 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.

#include "impeller/renderer/backend/vulkan/fence_waiter_vk.h"

#include <algorithm>
#include <chrono>

#include "flutter/fml/thread.h"
#include "flutter/fml/trace_event.h"
#include "impeller/base/validation.h"

namespace impeller {

class WaitSetEntry {
 public:
  static std::shared_ptr<WaitSetEntry> Create(vk::UniqueFence p_fence,
                                              const fml::closure& p_callback) {
    return std::shared_ptr<WaitSetEntry>(
        new WaitSetEntry(std::move(p_fence), p_callback));
  }

  void UpdateSignalledStatus(const vk::Device& device) {
    if (is_signalled_) {
      return;
    }
    is_signalled_ = device.getFenceStatus(fence_.get()) == vk::Result::eSuccess;
  }

  const vk::Fence& GetFence() const { return fence_.get(); }

  bool IsSignalled() const { return is_signalled_; }

 private:
  vk::UniqueFence fence_;
  fml::ScopedCleanupClosure callback_;
  bool is_signalled_ = false;

  WaitSetEntry(vk::UniqueFence p_fence, const fml::closure& p_callback)
      : fence_(std::move(p_fence)),
        callback_(fml::ScopedCleanupClosure{p_callback}) {}

  FML_DISALLOW_COPY_ASSIGN_AND_MOVE(WaitSetEntry);
};

FenceWaiterVK::FenceWaiterVK(std::weak_ptr<DeviceHolder> device_holder)
    : device_holder_(std::move(device_holder)) {
  waiter_thread_ = std::make_unique<std::thread>([&]() { Main(); });
  is_valid_ = true;
}

FenceWaiterVK::~FenceWaiterVK() {
  Terminate();
  if (waiter_thread_) {
    waiter_thread_->join();
  }
}

bool FenceWaiterVK::IsValid() const {
  return is_valid_;
}

bool FenceWaiterVK::AddFence(vk::UniqueFence fence,
                             const fml::closure& callback) {
  TRACE_EVENT0("flutter", "FenceWaiterVK::AddFence");
  if (!IsValid() || !fence || !callback) {
    return false;
  }
  {
    std::scoped_lock lock(wait_set_mutex_);
    wait_set_.emplace_back(WaitSetEntry::Create(std::move(fence), callback));
  }
  wait_set_cv_.notify_one();
  return true;
}

static std::vector<vk::Fence> GetFencesForWaitSet(const WaitSet& set) {
  std::vector<vk::Fence> fences;
  for (const auto& entry : set) {
    if (!entry->IsSignalled()) {
      fences.emplace_back(entry->GetFence());
    }
  }
  return fences;
}

void FenceWaiterVK::Main() {
  fml::Thread::SetCurrentThreadName(
      fml::Thread::ThreadConfig{"io.flutter.impeller.fence_waiter"});

  using namespace std::literals::chrono_literals;

  while (true) {
    std::unique_lock lock(wait_set_mutex_);

    // If there are no fences to wait on, wait on the condition variable.
    wait_set_cv_.wait(lock, [&]() { return !wait_set_.empty() || terminate_; });

    // We don't want to check on fence status or collect wait set entries in the
    // critical section. Copy the array of entries and immediately unlock the
    // mutex.
    WaitSet wait_set = wait_set_;

    const auto terminate = terminate_;

    lock.unlock();

    if (terminate) {
      break;
    }

    // Check if the context had died in the meantime.
    auto device_holder = device_holder_.lock();
    if (!device_holder) {
      break;
    }

    const auto& device = device_holder->GetDevice();

    // Wait for one or more fences to be signaled. Any additional fences added
    // to the waiter will be serviced in the next pass. If a fence that is going
    // to be signaled at an abnormally long deadline is the only one in the set,
    // a timeout will bail out the wait.
    auto fences = GetFencesForWaitSet(wait_set);
    if (fences.empty()) {
      continue;
    }

    auto result = device.waitForFences(
        fences.size(),                           // fences count
        fences.data(),                           // fences
        false,                                   // wait for all
        std::chrono::nanoseconds{100ms}.count()  // timeout (ns)
    );
    if (!(result == vk::Result::eSuccess || result == vk::Result::eTimeout)) {
      VALIDATION_LOG << "Fence waiter encountered an unexpected error. Tearing "
                        "down the waiter thread.";
      break;
    }

    // One or more fences have been signaled. Find out which ones and update
    // their signaled statuses.
    {
      TRACE_EVENT0("impeller", "CheckFenceStatus");
      for (auto& entry : wait_set) {
        entry->UpdateSignalledStatus(device);
      }
      wait_set.clear();
    }

    // Quickly acquire the wait set lock and erase signaled entries. Make sure
    // the mutex is unlocked before calling the destructors of the erased
    // entries. These might touch allocators.
    WaitSet erased_entries;
    {
      static auto is_signalled = [](const auto& entry) {
        return entry->IsSignalled();
      };
      std::scoped_lock lock(wait_set_mutex_);
      std::copy_if(wait_set_.begin(), wait_set_.end(),
                   std::back_inserter(erased_entries), is_signalled);
      wait_set_.erase(
          std::remove_if(wait_set_.begin(), wait_set_.end(), is_signalled),
          wait_set_.end());
    }

    {
      TRACE_EVENT0("impeller", "ClearSignaledFences");
      // Erase the erased entries which will invoke callbacks.
      erased_entries.clear();  // Bit redundant because of scope but hey.
    }
  }
}

void FenceWaiterVK::Terminate() {
  {
    std::scoped_lock lock(wait_set_mutex_);
    terminate_ = true;
  }
  wait_set_cv_.notify_one();
}

}  // namespace impeller
