// Copyright 2016 The Chromium 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 "base/task_scheduler/delayed_task_manager.h"

#include <algorithm>

#include "base/bind.h"
#include "base/logging.h"
#include "base/task_runner.h"
#include "base/task_scheduler/task.h"

namespace base {
namespace internal {

DelayedTaskManager::DelayedTaskManager(
    std::unique_ptr<const TickClock> tick_clock)
    : tick_clock_(std::move(tick_clock)) {
  DCHECK(tick_clock_);
}

DelayedTaskManager::~DelayedTaskManager() = default;

void DelayedTaskManager::Start(
    scoped_refptr<TaskRunner> service_thread_task_runner) {
  DCHECK(service_thread_task_runner);

  decltype(tasks_added_before_start_) tasks_added_before_start;

  {
    AutoSchedulerLock auto_lock(lock_);
    DCHECK(!service_thread_task_runner_);
    DCHECK(!started_.IsSet());
    service_thread_task_runner_ = std::move(service_thread_task_runner);
    tasks_added_before_start = std::move(tasks_added_before_start_);
    // |service_thread_task_runner_| must not change after |started_| is set
    // (cf. comment above |lock_| in header file).
    started_.Set();
  }

  const TimeTicks now = tick_clock_->NowTicks();
  for (auto& task_and_callback : tasks_added_before_start) {
    const TimeDelta delay =
        std::max(TimeDelta(), task_and_callback.first.delayed_run_time - now);
    AddDelayedTaskNow(std::move(task_and_callback.first), delay,
                      std::move(task_and_callback.second));
  }
}

void DelayedTaskManager::AddDelayedTask(
    Task task,
    PostTaskNowCallback post_task_now_callback) {
  DCHECK(task.task);

  const TimeDelta delay = task.delay;
  DCHECK(!delay.is_zero());

  // Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
  // for details.
  CHECK(task.task);

  // If |started_| is set, the DelayedTaskManager is in a stable state and
  // AddDelayedTaskNow() can be called without synchronization. Otherwise, it is
  // necessary to acquire |lock_| and recheck.
  if (started_.IsSet()) {
    AddDelayedTaskNow(std::move(task), delay,
                      std::move(post_task_now_callback));
  } else {
    AutoSchedulerLock auto_lock(lock_);
    if (started_.IsSet()) {
      AddDelayedTaskNow(std::move(task), delay,
                        std::move(post_task_now_callback));
    } else {
      tasks_added_before_start_.push_back(
          {std::move(task), std::move(post_task_now_callback)});
    }
  }
}

void DelayedTaskManager::AddDelayedTaskNow(
    Task task,
    TimeDelta delay,
    PostTaskNowCallback post_task_now_callback) {
  DCHECK(task.task);
  DCHECK(started_.IsSet());
  // TODO(fdoray): Use |task->delayed_run_time| on the service thread
  // MessageLoop rather than recomputing it from |delay|.
  service_thread_task_runner_->PostDelayedTask(
      FROM_HERE, BindOnce(std::move(post_task_now_callback), std::move(task)),
      delay);
}

}  // namespace internal
}  // namespace base
