// Copyright 2013 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/power_monitor/power_monitor.h"

#include <utility>

#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/power_monitor/power_monitor_source.h"
#include "base/trace_event/base_tracing.h"
#include "build/build_config.h"

namespace base {

void PowerMonitor::Initialize(std::unique_ptr<PowerMonitorSource> source) {
  DCHECK(!IsInitialized());
  PowerMonitor* power_monitor = GetInstance();
  power_monitor->source_ = std::move(source);

  // When a power source is associated with the power monitor, ensure the
  // initial state is propagated to observers, if needed.
  PowerMonitor::NotifyPowerStateChange(
      PowerMonitor::Source()->IsOnBatteryPower());

  PowerMonitor::PowerMonitor::NotifyThermalStateChange(
      PowerMonitor::Source()->GetCurrentThermalState());

  PowerMonitor::PowerMonitor::NotifySpeedLimitChange(
      PowerMonitor::Source()->GetInitialSpeedLimit());
}

bool PowerMonitor::IsInitialized() {
  return GetInstance()->source_.get() != nullptr;
}

// static
void PowerMonitor::AddPowerSuspendObserver(PowerSuspendObserver* obs) {
  GetInstance()->power_suspend_observers_->AddObserver(obs);
}

// static
void PowerMonitor::RemovePowerSuspendObserver(PowerSuspendObserver* obs) {
  GetInstance()->power_suspend_observers_->RemoveObserver(obs);
}

// static
void PowerMonitor::AddPowerStateObserver(PowerStateObserver* obs) {
  GetInstance()->power_state_observers_->AddObserver(obs);
}

// static
void PowerMonitor::RemovePowerStateObserver(PowerStateObserver* obs) {
  GetInstance()->power_state_observers_->RemoveObserver(obs);
}

// static
void PowerMonitor::AddPowerThermalObserver(PowerThermalObserver* obs) {
  GetInstance()->thermal_state_observers_->AddObserver(obs);
}

// static
void PowerMonitor::RemovePowerThermalObserver(PowerThermalObserver* obs) {
  GetInstance()->thermal_state_observers_->RemoveObserver(obs);
}

// static
bool PowerMonitor::AddPowerSuspendObserverAndReturnSuspendedState(
    PowerSuspendObserver* obs) {
  PowerMonitor* power_monitor = GetInstance();
  AutoLock auto_lock(power_monitor->is_system_suspended_lock_);
  power_monitor->power_suspend_observers_->AddObserver(obs);
  return power_monitor->is_system_suspended_;
}

// static
bool PowerMonitor::AddPowerStateObserverAndReturnOnBatteryState(
    PowerStateObserver* obs) {
  PowerMonitor* power_monitor = GetInstance();
  AutoLock auto_lock(power_monitor->on_battery_power_lock_);
  power_monitor->power_state_observers_->AddObserver(obs);
  return power_monitor->on_battery_power_;
}

// static
PowerThermalObserver::DeviceThermalState
PowerMonitor::AddPowerStateObserverAndReturnPowerThermalState(
    PowerThermalObserver* obs) {
  PowerMonitor* power_monitor = GetInstance();
  AutoLock auto_lock(power_monitor->power_thermal_state_lock_);
  power_monitor->thermal_state_observers_->AddObserver(obs);
  return power_monitor->power_thermal_state_;
}

PowerMonitorSource* PowerMonitor::Source() {
  return GetInstance()->source_.get();
}

bool PowerMonitor::IsOnBatteryPower() {
  DCHECK(IsInitialized());
  PowerMonitor* power_monitor = GetInstance();
  AutoLock auto_lock(power_monitor->on_battery_power_lock_);
  return power_monitor->on_battery_power_;
}

TimeTicks PowerMonitor::GetLastSystemResumeTime() {
  PowerMonitor* power_monitor = GetInstance();
  AutoLock auto_lock(power_monitor->is_system_suspended_lock_);
  return power_monitor->last_system_resume_time_;
}

void PowerMonitor::ShutdownForTesting() {
  GetInstance()->source_ = nullptr;

  PowerMonitor* power_monitor = GetInstance();
  {
    AutoLock auto_lock(power_monitor->is_system_suspended_lock_);
    power_monitor->is_system_suspended_ = false;
    power_monitor->last_system_resume_time_ = TimeTicks();
  }
  {
    AutoLock auto_lock(power_monitor->on_battery_power_lock_);
    power_monitor->on_battery_power_ = false;
  }
  {
    AutoLock auto_lock(power_monitor->power_thermal_state_lock_);
    power_monitor->power_thermal_state_ =
        PowerThermalObserver::DeviceThermalState::kUnknown;
  }
}

// static
PowerThermalObserver::DeviceThermalState
PowerMonitor::GetCurrentThermalState() {
  DCHECK(IsInitialized());
  return GetInstance()->source_->GetCurrentThermalState();
}

// static
void PowerMonitor::SetCurrentThermalState(
    PowerThermalObserver::DeviceThermalState state) {
  DCHECK(IsInitialized());
  GetInstance()->source_->SetCurrentThermalState(state);
}

#if BUILDFLAG(IS_ANDROID)
int PowerMonitor::GetRemainingBatteryCapacity() {
  DCHECK(IsInitialized());
  return PowerMonitor::Source()->GetRemainingBatteryCapacity();
}
#endif  // BUILDFLAG(IS_ANDROID)

void PowerMonitor::NotifyPowerStateChange(bool on_battery_power) {
  DCHECK(IsInitialized());
  DVLOG(1) << "PowerStateChange: " << (on_battery_power ? "On" : "Off")
           << " battery";

  PowerMonitor* power_monitor = GetInstance();
  AutoLock auto_lock(power_monitor->on_battery_power_lock_);
  if (power_monitor->on_battery_power_ != on_battery_power) {
    power_monitor->on_battery_power_ = on_battery_power;
    GetInstance()->power_state_observers_->Notify(
        FROM_HERE, &PowerStateObserver::OnPowerStateChange, on_battery_power);
  }
}

void PowerMonitor::NotifySuspend() {
  DCHECK(IsInitialized());
  TRACE_EVENT_INSTANT0("base", "PowerMonitor::NotifySuspend",
                       TRACE_EVENT_SCOPE_PROCESS);
  DVLOG(1) << "Power Suspending";

  PowerMonitor* power_monitor = GetInstance();
  AutoLock auto_lock(power_monitor->is_system_suspended_lock_);
  if (!power_monitor->is_system_suspended_) {
    power_monitor->is_system_suspended_ = true;
    power_monitor->last_system_resume_time_ = TimeTicks::Max();
    GetInstance()->power_suspend_observers_->Notify(
        FROM_HERE, &PowerSuspendObserver::OnSuspend);
  }
}

void PowerMonitor::NotifyResume() {
  DCHECK(IsInitialized());
  TRACE_EVENT_INSTANT0("base", "PowerMonitor::NotifyResume",
                       TRACE_EVENT_SCOPE_PROCESS);
  DVLOG(1) << "Power Resuming";

  TimeTicks resume_time = TimeTicks::Now();

  PowerMonitor* power_monitor = GetInstance();
  AutoLock auto_lock(power_monitor->is_system_suspended_lock_);
  if (power_monitor->is_system_suspended_) {
    power_monitor->is_system_suspended_ = false;
    power_monitor->last_system_resume_time_ = resume_time;
    GetInstance()->power_suspend_observers_->Notify(
        FROM_HERE, &PowerSuspendObserver::OnResume);
  }
}

void PowerMonitor::NotifyThermalStateChange(
    PowerThermalObserver::DeviceThermalState new_state) {
  DCHECK(IsInitialized());
  DVLOG(1) << "ThermalStateChange: "
           << PowerMonitorSource::DeviceThermalStateToString(new_state);

  PowerMonitor* power_monitor = GetInstance();
  AutoLock auto_lock(power_monitor->power_thermal_state_lock_);
  if (power_monitor->power_thermal_state_ != new_state) {
    power_monitor->power_thermal_state_ = new_state;
    GetInstance()->thermal_state_observers_->Notify(
        FROM_HERE, &PowerThermalObserver::OnThermalStateChange, new_state);
  }
}

void PowerMonitor::NotifySpeedLimitChange(int speed_limit) {
  DCHECK(IsInitialized());
  DVLOG(1) << "SpeedLimitChange: " << speed_limit;

  PowerMonitor* power_monitor = GetInstance();
  AutoLock auto_lock(power_monitor->power_thermal_state_lock_);
  if (power_monitor->speed_limit_ != speed_limit) {
    power_monitor->speed_limit_ = speed_limit;
    GetInstance()->thermal_state_observers_->Notify(
        FROM_HERE, &PowerThermalObserver::OnSpeedLimitChange, speed_limit);
  }
}

PowerMonitor* PowerMonitor::GetInstance() {
  static base::NoDestructor<PowerMonitor> power_monitor;
  return power_monitor.get();
}

PowerMonitor::PowerMonitor()
    : power_state_observers_(
          base::MakeRefCounted<ObserverListThreadSafe<PowerStateObserver>>()),
      power_suspend_observers_(
          base::MakeRefCounted<ObserverListThreadSafe<PowerSuspendObserver>>()),
      thermal_state_observers_(
          base::MakeRefCounted<
              ObserverListThreadSafe<PowerThermalObserver>>()) {}

PowerMonitor::~PowerMonitor() = default;

}  // namespace base
