// Copyright 2017 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 "media/audio/alive_checker.h"

#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/threading/thread_task_runner_handle.h"

namespace media {

AliveChecker::AliveChecker(base::RepeatingClosure dead_callback,
                           base::TimeDelta check_interval,
                           base::TimeDelta timeout,
                           bool stop_at_first_alive_notification,
                           bool pause_check_during_suspend)
    : AliveChecker(std::move(dead_callback),
                   check_interval,
                   timeout,
                   stop_at_first_alive_notification,
                   pause_check_during_suspend,
                   PowerObserverHelperFactoryCallback()) {}

AliveChecker::AliveChecker(
    base::RepeatingClosure dead_callback,
    base::TimeDelta check_interval,
    base::TimeDelta timeout,
    bool stop_at_first_alive_notification,
    PowerObserverHelperFactoryCallback power_observer_helper_factory_callback)
    : AliveChecker(std::move(dead_callback),
                   check_interval,
                   timeout,
                   stop_at_first_alive_notification,
                   true,
                   std::move(power_observer_helper_factory_callback)) {}

// The private constructor called by the above public constructors.
AliveChecker::AliveChecker(
    base::RepeatingClosure dead_callback,
    base::TimeDelta check_interval,
    base::TimeDelta timeout,
    bool stop_at_first_alive_notification,
    bool pause_check_during_suspend,
    PowerObserverHelperFactoryCallback power_observer_helper_factory_callback)
    : check_interval_(check_interval),
      timeout_(timeout),
      task_runner_(base::ThreadTaskRunnerHandle::Get()),
      dead_callback_(std::move(dead_callback)),
      stop_at_first_alive_notification_(stop_at_first_alive_notification) {
  DCHECK(!dead_callback_.is_null());
  DCHECK_GT(check_interval_, base::TimeDelta());
  DCHECK_GT(timeout_, check_interval_);

  if (pause_check_during_suspend) {
    // When suspending, we don't need to take any action. When resuming, we
    // reset |last_alive_notification_time_| to avoid false alarms.
    // Unretained is safe since the PowerObserverHelper runs the callback on
    // the task runner the AliveChecker (and consequently the
    // PowerObserverHelper) is destroyed on.
    if (power_observer_helper_factory_callback.is_null()) {
      power_observer_ = std::make_unique<PowerObserverHelper>(
          task_runner_, base::DoNothing(),
          base::BindRepeating(
              &AliveChecker::SetLastAliveNotificationTimeToNowOnTaskRunner,
              base::Unretained(this)));
    } else {
      power_observer_ =
          std::move(power_observer_helper_factory_callback)
              .Run(task_runner_, base::DoNothing(),
                   base::BindRepeating(
                       &AliveChecker::
                           SetLastAliveNotificationTimeToNowOnTaskRunner,
                       base::Unretained(this)));
    }
  } else {
    // If |pause_check_during_suspend| is false, we expect an empty factory
    // callback.
    DCHECK(power_observer_helper_factory_callback.is_null());
  }
}

AliveChecker::~AliveChecker() {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
}

void AliveChecker::Start() {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());

  SetLastAliveNotificationTimeToNowOnTaskRunner();
  detected_dead_ = false;

  DCHECK(!check_alive_timer_);
  check_alive_timer_ = std::make_unique<base::RepeatingTimer>();
  check_alive_timer_->Start(FROM_HERE, check_interval_, this,
                            &AliveChecker::CheckIfAlive);
  DCHECK(check_alive_timer_->IsRunning());
}

void AliveChecker::Stop() {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  check_alive_timer_.reset();
}

bool AliveChecker::DetectedDead() {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  return detected_dead_;
}

void AliveChecker::NotifyAlive() {
  if (!task_runner_->RunsTasksInCurrentSequence()) {
    // We don't need high precision for setting |last_alive_notification_time_|
    // so we don't have to care about the delay added with posting the task.
    task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&AliveChecker::NotifyAlive, weak_factory_.GetWeakPtr()));
    return;
  }

  SetLastAliveNotificationTimeToNowOnTaskRunner();
  if (stop_at_first_alive_notification_)
    Stop();
}

void AliveChecker::CheckIfAlive() {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());

  // The reason we check a flag instead of stopping the timer that runs this
  // function at suspend is that it would require knowing what state we're in
  // when resuming and maybe start the timer. Also, we would still need this
  // flag anyway to maybe start the timer at stream creation.
  // TODO(grunell): Suspend/resume notifications are not supported on Linux. We
  // could possibly use wall clock time as a complement to be able to detect
  // time jumps that probably are caused by suspend/resume.
  if (power_observer_ && power_observer_->IsSuspending())
    return;

  if (base::TimeTicks::Now() - last_alive_notification_time_ > timeout_) {
    Stop();
    detected_dead_ = true;
    dead_callback_.Run();
  }
}

void AliveChecker::SetLastAliveNotificationTimeToNowOnTaskRunner() {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  last_alive_notification_time_ = base::TimeTicks::Now();
}

}  // namespace media
