// 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 "third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client.h"

#include <memory>

#include "base/metrics/histogram_macros.h"
#include "base/task/single_thread_task_runner.h"
#include "third_party/blink/public/platform/scheduler/web_agent_group_scheduler.h"
#include "third_party/blink/renderer/core/animation/worklet_animation_controller.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/web_frame_widget_impl.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/workers/worker_thread.h"
#include "third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl.h"
#include "third_party/blink/renderer/platform/scheduler/public/non_main_thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_copier_base.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"

namespace blink {

namespace {

static const wtf_size_t kMaxMutateCountToSwitch = 10u;

}  // end namespace

/* static */
const char AnimationWorkletProxyClient::kSupplementName[] =
    "AnimationWorkletProxyClient";

/* static */
const int8_t AnimationWorkletProxyClient::kNumStatelessGlobalScopes = 2;

AnimationWorkletProxyClient::AnimationWorkletProxyClient(
    int worklet_id,
    base::WeakPtr<AnimationWorkletMutatorDispatcherImpl>
        compositor_mutator_dispatcher,
    scoped_refptr<base::SingleThreadTaskRunner> compositor_mutator_runner,
    base::WeakPtr<AnimationWorkletMutatorDispatcherImpl>
        main_thread_mutator_dispatcher,
    scoped_refptr<base::SingleThreadTaskRunner> main_thread_mutator_runner)
    : Supplement(nullptr),
      worklet_id_(worklet_id),
      state_(RunState::kUninitialized),
      next_global_scope_switch_countdown_(0),
      current_global_scope_index_(0) {
  DCHECK(IsMainThread());

  // The dispatchers are weak pointers that may come from another thread. It's
  // illegal to check them here. Instead, the task runners are checked.
  if (compositor_mutator_runner) {
    mutator_items_.emplace_back(std::move(compositor_mutator_dispatcher),
                                std::move(compositor_mutator_runner));
  }
  if (main_thread_mutator_runner) {
    mutator_items_.emplace_back(std::move(main_thread_mutator_dispatcher),
                                std::move(main_thread_mutator_runner));
  }
}

void AnimationWorkletProxyClient::Trace(Visitor* visitor) const {
  Supplement<WorkerClients>::Trace(visitor);
  AnimationWorkletMutator::Trace(visitor);
}

void AnimationWorkletProxyClient::SynchronizeAnimatorName(
    const String& animator_name) {
  if (state_ == RunState::kDisposed)
    return;
  // Only proceed to synchronization when the animator has been registered on
  // all global scopes.
  auto* it = registered_animators_.insert(animator_name, 0).stored_value;
  ++it->value;
  if (it->value != kNumStatelessGlobalScopes) {
    DCHECK_LT(it->value, kNumStatelessGlobalScopes)
        << "We should not have registered the same name more than the number "
           "of scopes times.";
    return;
  }

  // Animator registration is processed before the loading promise being
  // resolved which is also done with a posted task (See
  // WorkletModuleTreeClient::NotifyModuleTreeLoadFinished). Since both are
  // posted task and a SequencedTaskRunner is used, we are guaranteed that
  // registered names are synced before resolving the load promise therefore it
  // is safe to use a post task here.
  for (auto& mutator_item : mutator_items_) {
    PostCrossThreadTask(
        *mutator_item.mutator_runner, FROM_HERE,
        CrossThreadBindOnce(
            &AnimationWorkletMutatorDispatcherImpl::SynchronizeAnimatorName,
            mutator_item.mutator_dispatcher, animator_name));
  }
}

void AnimationWorkletProxyClient::AddGlobalScope(
    WorkletGlobalScope* global_scope) {
  DCHECK(global_scope);
  DCHECK(global_scope->IsContextThread());
  if (state_ == RunState::kDisposed)
    return;

  global_scopes_.push_back(To<AnimationWorkletGlobalScope>(global_scope));

  if (state_ != RunState::kUninitialized) {
    return;
  }

  // Wait for all global scopes to load before proceeding with registration.
  if (global_scopes_.size() < kNumStatelessGlobalScopes) {
    return;
  }

  // TODO(majidvp): Add an AnimationWorklet task type when the spec is final.
  scoped_refptr<base::SingleThreadTaskRunner> global_scope_runner =
      global_scope->GetThread()->GetTaskRunner(TaskType::kMiscPlatformAPI);
  state_ = RunState::kWorking;

  for (auto& mutator_item : mutator_items_) {
    PostCrossThreadTask(
        *mutator_item.mutator_runner, FROM_HERE,
        CrossThreadBindOnce(&AnimationWorkletMutatorDispatcherImpl::
                                RegisterAnimationWorkletMutator,
                            mutator_item.mutator_dispatcher,
                            WrapCrossThreadPersistent(this),
                            global_scope_runner));
  }
}

void AnimationWorkletProxyClient::Dispose() {
  if (state_ == RunState::kWorking) {
    // At worklet scope termination break the reference to the clients if it is
    // still alive.
    for (auto& mutator_item : mutator_items_) {
      PostCrossThreadTask(
          *mutator_item.mutator_runner, FROM_HERE,
          CrossThreadBindOnce(&AnimationWorkletMutatorDispatcherImpl::
                                  UnregisterAnimationWorkletMutator,
                              mutator_item.mutator_dispatcher,
                              WrapCrossThreadPersistent(this)));
    }
  }
  state_ = RunState::kDisposed;

  // At worklet scope termination break the reference cycle between
  // AnimationWorkletGlobalScope and AnimationWorkletProxyClient.
  global_scopes_.clear();
  mutator_items_.clear();
  registered_animators_.clear();
}

std::unique_ptr<AnimationWorkletOutput> AnimationWorkletProxyClient::Mutate(
    std::unique_ptr<AnimationWorkletInput> input) {
  std::unique_ptr<AnimationWorkletOutput> output =
      std::make_unique<AnimationWorkletOutput>();

  if (state_ == RunState::kDisposed)
    return output;

  DCHECK(input);
#if DCHECK_IS_ON()
  DCHECK(input->ValidateId(worklet_id_))
      << "Input has state that does not belong to this global scope: "
      << worklet_id_;
#endif

  AnimationWorkletGlobalScope* global_scope =
      SelectGlobalScopeAndUpdateAnimatorsIfNecessary();
  DCHECK(global_scope);
  // Create or destroy instances of animators on current global scope.
  global_scope->UpdateAnimatorsList(*input);

  global_scope->UpdateAnimators(*input, output.get(),
                                [](Animator* animator) { return true; });
  return output;
}

AnimationWorkletGlobalScope*
AnimationWorkletProxyClient::SelectGlobalScopeAndUpdateAnimatorsIfNecessary() {
  if (--next_global_scope_switch_countdown_ < 0) {
    int last_global_scope_index = current_global_scope_index_;
    current_global_scope_index_ =
        (current_global_scope_index_ + 1) % global_scopes_.size();
    global_scopes_[last_global_scope_index]->MigrateAnimatorsTo(
        global_scopes_[current_global_scope_index_]);
    // Introduce an element of randomness in the switching interval to make
    // stateful dependences easier to spot.
    next_global_scope_switch_countdown_ =
        base::RandIntInclusive(0, kMaxMutateCountToSwitch - 1);
  }
  return global_scopes_[current_global_scope_index_];
}

void AnimationWorkletProxyClient::AddGlobalScopeForTesting(
    WorkletGlobalScope* global_scope) {
  DCHECK(global_scope);
  DCHECK(global_scope->IsContextThread());
  global_scopes_.push_back(To<AnimationWorkletGlobalScope>(global_scope));
}

// static
AnimationWorkletProxyClient* AnimationWorkletProxyClient::FromDocument(
    Document* document,
    int worklet_id) {
  WebLocalFrameImpl* local_frame =
      WebLocalFrameImpl::FromFrame(document->GetFrame());

  // By default web tests run without threaded compositing. See
  // https://crbug.com/770028. If threaded compositing is disabled, we
  // run on the main thread's compositor task runner otherwise we run
  // tasks on the compositor thread's default task runner.
  scoped_refptr<base::SingleThreadTaskRunner> compositor_host_queue =
      Thread::CompositorThread()
          ? Thread::CompositorThread()->GetTaskRunner()
          : local_frame->GetAgentGroupScheduler()->CompositorTaskRunner();
  base::WeakPtr<AnimationWorkletMutatorDispatcherImpl>
      compositor_mutator_dispatcher =
          local_frame->LocalRootFrameWidget()
              ->EnsureCompositorMutatorDispatcher(compositor_host_queue);

  scoped_refptr<base::SingleThreadTaskRunner> main_thread_host_queue =
      local_frame->GetAgentGroupScheduler()->CompositorTaskRunner();
  base::WeakPtr<AnimationWorkletMutatorDispatcherImpl>
      main_thread_mutator_dispatcher =
          document->GetWorkletAnimationController()
              .EnsureMainThreadMutatorDispatcher(main_thread_host_queue);

  return MakeGarbageCollected<AnimationWorkletProxyClient>(
      worklet_id, std::move(compositor_mutator_dispatcher),
      std::move(compositor_host_queue),
      std::move(main_thread_mutator_dispatcher),
      std::move(main_thread_host_queue));
}

AnimationWorkletProxyClient* AnimationWorkletProxyClient::From(
    WorkerClients* clients) {
  return Supplement<WorkerClients>::From<AnimationWorkletProxyClient>(clients);
}

void ProvideAnimationWorkletProxyClientTo(WorkerClients* clients,
                                          AnimationWorkletProxyClient* client) {
  clients->ProvideSupplement(client);
}

}  // namespace blink
