// Copyright (c) 2018 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 "chromeos/components/nearby/scheduled_executor_impl.h"

#include <utility>

#include "base/stl_util.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/components/nearby/atomic_boolean_impl.h"

namespace chromeos {

namespace nearby {

namespace {

class CancelableTask : public location::nearby::Cancelable {
 public:
  explicit CancelableTask(base::OnceCallback<bool()> cancel_callback)
      : cancel_callback_(std::move(cancel_callback)) {}
  CancelableTask() = default;
  ~CancelableTask() override = default;

  // location::nearby::Cancelable:
  bool cancel() override {
    if (cancel_callback_.is_null())
      return false;

    return std::move(cancel_callback_).Run();
  }

 private:
  base::OnceCallback<bool()> cancel_callback_;
};

}  // namespace

ScheduledExecutorImpl::PendingTaskWithTimer::PendingTaskWithTimer(
    std::shared_ptr<location::nearby::Runnable> runnable,
    std::unique_ptr<base::OneShotTimer> timer)
    : runnable(runnable), timer(std::move(timer)) {}

ScheduledExecutorImpl::PendingTaskWithTimer::~PendingTaskWithTimer() = default;

ScheduledExecutorImpl::ScheduledExecutorImpl(
    scoped_refptr<base::SequencedTaskRunner> timer_task_runner)
    : timer_task_runner_(timer_task_runner),
      is_shut_down_(std::make_unique<AtomicBooleanImpl>()),
      weak_factory_(this) {
  DETACH_FROM_SEQUENCE(timer_sequence_checker_);
}

ScheduledExecutorImpl::~ScheduledExecutorImpl() = default;

bool ScheduledExecutorImpl::TryCancelTask(
    base::WeakPtr<ScheduledExecutorImpl> executor,
    const base::UnguessableToken& id) {
  if (!executor)
    return false;

  return executor->OnTaskCancelled(id);
}

void ScheduledExecutorImpl::shutdown() {
  if (!is_shut_down_->get())
    is_shut_down_->set(true);
}

std::shared_ptr<location::nearby::Cancelable> ScheduledExecutorImpl::schedule(
    std::shared_ptr<location::nearby::Runnable> runnable,
    int64_t delay_millis) {
  if (is_shut_down_->get())
    return std::make_shared<CancelableTask>();

  auto delayed_task = std::make_unique<PendingTaskWithTimer>(
      runnable, std::make_unique<base::OneShotTimer>());
  delayed_task->timer->SetTaskRunner(timer_task_runner_);

  base::UnguessableToken id = base::UnguessableToken::Create();
  {
    base::AutoLock al(map_lock_);
    id_to_task_map_[id] = std::move(delayed_task);
  }

  timer_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&ScheduledExecutorImpl::StartTimerWithId,
                     base::Unretained(this), id,
                     base::TimeDelta::FromMilliseconds(delay_millis)));

  return std::make_shared<CancelableTask>(
      base::BindOnce(&TryCancelTask, weak_factory_.GetWeakPtr(), id));
}

void ScheduledExecutorImpl::StartTimerWithId(const base::UnguessableToken& id,
                                             const base::TimeDelta& delay) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(timer_sequence_checker_);
  base::AutoLock al(map_lock_);

  // If the id no longer exists, it means the task has already been cancelled.
  auto it = id_to_task_map_.find(id);
  if (it == id_to_task_map_.end())
    return;

  it->second->timer->Start(FROM_HERE, delay,
                           base::BindOnce(&ScheduledExecutorImpl::RunTaskWithId,
                                          base::Unretained(this), id));
}

void ScheduledExecutorImpl::StopTimerWithIdAndDeleteTaskEntry(
    const base::UnguessableToken& id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(timer_sequence_checker_);
  base::AutoLock al(map_lock_);

  // If the id no longer exists, it means the task has either already been run,
  // or the task has already been cancelled.
  auto it = id_to_task_map_.find(id);
  if (it == id_to_task_map_.end())
    return;

  it->second->timer->Stop();
  id_to_task_map_.erase(id);
}

void ScheduledExecutorImpl::RunTaskWithId(const base::UnguessableToken& id) {
  base::AutoLock al(map_lock_);

  auto it = id_to_task_map_.find(id);
  if (it == id_to_task_map_.end())
    return;

  it->second->runnable->run();
  id_to_task_map_.erase(id);
}

bool ScheduledExecutorImpl::OnTaskCancelled(const base::UnguessableToken& id) {
  {
    base::AutoLock al(map_lock_);
    auto it = id_to_task_map_.find(id);
    if (it == id_to_task_map_.end())
      return false;
  }

  timer_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&ScheduledExecutorImpl::StopTimerWithIdAndDeleteTaskEntry,
                     base::Unretained(this), id));
  return true;
}

}  // namespace nearby

}  // namespace chromeos
