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

#include "base/task/sequence_manager/task_queue.h"

#include <utility>

#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/task/sequence_manager/associated_thread_id.h"
#include "base/task/sequence_manager/sequence_manager_impl.h"
#include "base/task/sequence_manager/task_queue_impl.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_checker_impl.h"
#include "base/time/time.h"
#include "base/trace_event/base_tracing.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace base {
namespace sequence_manager {

TaskQueue::QueueEnabledVoter::QueueEnabledVoter(
    scoped_refptr<TaskQueue> task_queue)
    : task_queue_(std::move(task_queue)), enabled_(true) {
  task_queue_->AddQueueEnabledVoter(enabled_);
}

TaskQueue::QueueEnabledVoter::~QueueEnabledVoter() {
  task_queue_->RemoveQueueEnabledVoter(enabled_);
}

void TaskQueue::QueueEnabledVoter::SetVoteToEnable(bool enabled) {
  if (enabled == enabled_)
    return;
  enabled_ = enabled;
  task_queue_->OnQueueEnabledVoteChanged(enabled_);
}

void TaskQueue::AddQueueEnabledVoter(bool voter_is_enabled) {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  ++voter_count_;
  if (voter_is_enabled)
    ++enabled_voter_count_;
}

void TaskQueue::RemoveQueueEnabledVoter(bool voter_is_enabled) {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  // Voters and task queues are often stored in pairs, and the voter is often
  // destroyed after the queue is shut down.
  if (!impl_) {
    return;
  }

  bool was_enabled = AreAllQueueEnabledVotersEnabled();
  if (voter_is_enabled) {
    --enabled_voter_count_;
    DCHECK_GE(enabled_voter_count_, 0);
  }

  --voter_count_;
  DCHECK_GE(voter_count_, 0);

  bool is_enabled = AreAllQueueEnabledVotersEnabled();
  if (was_enabled != is_enabled)
    impl_->SetQueueEnabled(is_enabled);
}

bool TaskQueue::AreAllQueueEnabledVotersEnabled() const {
  return enabled_voter_count_ == voter_count_;
}

void TaskQueue::OnQueueEnabledVoteChanged(bool enabled) {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  bool was_enabled = AreAllQueueEnabledVotersEnabled();
  if (enabled) {
    ++enabled_voter_count_;
    DCHECK_LE(enabled_voter_count_, voter_count_);
  } else {
    --enabled_voter_count_;
    DCHECK_GE(enabled_voter_count_, 0);
  }

  bool is_enabled = AreAllQueueEnabledVotersEnabled();
  if (was_enabled != is_enabled)
    impl_->SetQueueEnabled(is_enabled);
}

TaskQueue::TaskQueue(std::unique_ptr<internal::TaskQueueImpl> impl,
                     const TaskQueue::Spec& spec)
    : impl_(std::move(impl)),
      sequence_manager_(impl_->GetSequenceManagerWeakPtr()),
      associated_thread_((impl_->sequence_manager())
                             ? impl_->sequence_manager()->associated_thread()
                             : MakeRefCounted<internal::AssociatedThreadId>()),
      default_task_runner_(impl_->CreateTaskRunner(kTaskTypeNone)),
      name_(impl_->GetProtoName()) {}

TaskQueue::~TaskQueue() {
  ShutdownTaskQueueGracefully();
}

void TaskQueue::ShutdownTaskQueueGracefully() {
  // scoped_refptr guarantees us that this object isn't used.
  if (!impl_)
    return;
  if (impl_->IsUnregistered())
    return;

  // If we've not been unregistered then this must occur on the main thread.
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  impl_->ResetThrottler();
  impl_->sequence_manager()->ShutdownTaskQueueGracefully(TakeTaskQueueImpl());
}

TaskQueue::TaskTiming::TaskTiming(bool has_wall_time, bool has_thread_time)
    : has_wall_time_(has_wall_time), has_thread_time_(has_thread_time) {}

void TaskQueue::TaskTiming::RecordTaskStart(LazyNow* now) {
  DCHECK_EQ(State::NotStarted, state_);
  state_ = State::Running;

  if (has_wall_time())
    start_time_ = now->Now();
  if (has_thread_time())
    start_thread_time_ = base::ThreadTicks::Now();
}

void TaskQueue::TaskTiming::RecordTaskEnd(LazyNow* now) {
  DCHECK(state_ == State::Running || state_ == State::Finished);
  if (state_ == State::Finished)
    return;
  state_ = State::Finished;

  if (has_wall_time())
    end_time_ = now->Now();
  if (has_thread_time())
    end_thread_time_ = base::ThreadTicks::Now();
}

void TaskQueue::ShutdownTaskQueue() {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  // TODO(crbug.com/1413795): Fix that some task queues get shut down more than
  // once.
  if (!impl_) {
    return;
  }
  if (!sequence_manager_) {
    TakeTaskQueueImpl().reset();
    return;
  }
  sequence_manager_->UnregisterTaskQueueImpl(TakeTaskQueueImpl());
}

scoped_refptr<SingleThreadTaskRunner> TaskQueue::CreateTaskRunner(
    TaskType task_type) {
  // We only need to lock if we're not on the main thread.
  base::internal::CheckedAutoLockMaybe lock(IsOnMainThread() ? &impl_lock_
                                                             : nullptr);
  DCHECK(impl_);
  return impl_->CreateTaskRunner(task_type);
}

std::unique_ptr<TaskQueue::QueueEnabledVoter>
TaskQueue::CreateQueueEnabledVoter() {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  return WrapUnique(new QueueEnabledVoter(this));
}

bool TaskQueue::IsQueueEnabled() const {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  return impl_->IsQueueEnabled();
}

bool TaskQueue::IsEmpty() const {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  return impl_->IsEmpty();
}

size_t TaskQueue::GetNumberOfPendingTasks() const {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  return impl_->GetNumberOfPendingTasks();
}

bool TaskQueue::HasTaskToRunImmediatelyOrReadyDelayedTask() const {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  return impl_->HasTaskToRunImmediatelyOrReadyDelayedTask();
}

absl::optional<WakeUp> TaskQueue::GetNextDesiredWakeUp() {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  return impl_->GetNextDesiredWakeUp();
}

void TaskQueue::UpdateWakeUp(LazyNow* lazy_now) {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  impl_->UpdateWakeUp(lazy_now);
}

void TaskQueue::SetQueuePriority(TaskQueue::QueuePriority priority) {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  impl_->SetQueuePriority(priority);
}

TaskQueue::QueuePriority TaskQueue::GetQueuePriority() const {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  // TODO(crbug.com/1413795): change this to DCHECK(impl_) since task queues
  // should not be used after shutdown.
  DCHECK(impl_);
  return impl_->GetQueuePriority();
}

void TaskQueue::AddTaskObserver(TaskObserver* task_observer) {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  impl_->AddTaskObserver(task_observer);
}

void TaskQueue::RemoveTaskObserver(TaskObserver* task_observer) {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  impl_->RemoveTaskObserver(task_observer);
}

void TaskQueue::InsertFence(InsertFencePosition position) {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  impl_->InsertFence(position);
}

void TaskQueue::InsertFenceAt(TimeTicks time) {
  impl_->InsertFenceAt(time);
}

void TaskQueue::RemoveFence() {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  impl_->RemoveFence();
}

bool TaskQueue::HasActiveFence() {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  return impl_->HasActiveFence();
}

bool TaskQueue::BlockedByFence() const {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  return impl_->BlockedByFence();
}

const char* TaskQueue::GetName() const {
  return perfetto::protos::pbzero::SequenceManagerTask::QueueName_Name(name_);
}

void TaskQueue::WriteIntoTrace(perfetto::TracedValue context) const {
  auto dict = std::move(context).WriteDictionary();
  dict.Add("name", name_);
}

void TaskQueue::SetThrottler(Throttler* throttler) {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  // |throttler| is guaranteed to outlive TaskQueue and TaskQueueImpl lifecycle
  // is controlled by |this|.
  impl_->SetThrottler(throttler);
}

void TaskQueue::ResetThrottler() {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  impl_->ResetThrottler();
}

void TaskQueue::SetShouldReportPostedTasksWhenDisabled(bool should_report) {
  impl_->SetShouldReportPostedTasksWhenDisabled(should_report);
}

bool TaskQueue::IsOnMainThread() const {
  return associated_thread_->IsBoundToCurrentThread();
}

std::unique_ptr<internal::TaskQueueImpl> TaskQueue::TakeTaskQueueImpl() {
  base::internal::CheckedAutoLock lock(impl_lock_);
  DCHECK(impl_);
  return std::move(impl_);
}

void TaskQueue::SetOnTaskStartedHandler(OnTaskStartedHandler handler) {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  impl_->SetOnTaskStartedHandler(std::move(handler));
}

void TaskQueue::SetOnTaskCompletedHandler(OnTaskCompletedHandler handler) {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  impl_->SetOnTaskCompletedHandler(std::move(handler));
}

std::unique_ptr<TaskQueue::OnTaskPostedCallbackHandle>
TaskQueue::AddOnTaskPostedHandler(OnTaskPostedHandler handler) {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  return impl_->AddOnTaskPostedHandler(std::move(handler));
}

void TaskQueue::SetTaskExecutionTraceLogger(TaskExecutionTraceLogger logger) {
  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
  DCHECK(impl_);
  impl_->SetTaskExecutionTraceLogger(std::move(logger));
}

}  // namespace sequence_manager
}  // namespace base