// Copyright 2022 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/battery_state_sampler.h"

#include "base/power_monitor/power_monitor_buildflags.h"

#if !BUILDFLAG(IS_MAC)
#include "base/power_monitor/timer_sampling_event_source.h"
#endif

namespace base {

namespace {

// Singleton instance of the BatteryStateSampler.
BatteryStateSampler* g_battery_state_sampler = nullptr;
bool g_test_instance_installed = false;

}  // namespace

BatteryStateSampler::BatteryStateSampler(
    std::unique_ptr<SamplingEventSource> sampling_event_source,
    std::unique_ptr<BatteryLevelProvider> battery_level_provider)
    : sampling_event_source_(std::move(sampling_event_source)),
      battery_level_provider_(std::move(battery_level_provider)) {
  DCHECK(sampling_event_source_);
  DCHECK(battery_level_provider_);

  DCHECK(!g_battery_state_sampler);
  g_battery_state_sampler = this;

  // Get an initial sample.
  battery_level_provider_->GetBatteryState(
      base::BindOnce(&BatteryStateSampler::OnInitialBatteryStateSampled,
                     base::Unretained(this)));

  // Start the periodic sampling.
  sampling_event_source_->Start(base::BindRepeating(
      &BatteryStateSampler::OnSamplingEvent, base::Unretained(this)));
}

BatteryStateSampler::~BatteryStateSampler() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(g_battery_state_sampler, this);
  g_battery_state_sampler = nullptr;
  g_test_instance_installed = false;
}

// static
BatteryStateSampler* BatteryStateSampler::Get() {
  // On a platform with a BatteryLevelProvider implementation, the global
  // instance must be created before accessing it.
  // TODO(crbug.com/1373560): ChromeOS currently doesn't define
  // `HAS_BATTERY_LEVEL_PROVIDER_IMPL` but it should once the locations of the
  // providers and sampling sources are consolidated.
#if BUILDFLAG(HAS_BATTERY_LEVEL_PROVIDER_IMPL) || BUILDFLAG(IS_CHROMEOS_ASH)
  DCHECK(g_battery_state_sampler);
#endif
  return g_battery_state_sampler;
}

void BatteryStateSampler::AddObserver(Observer* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  observer_list_.AddObserver(observer);

  // Send the last sample available.
  if (has_last_battery_state_)
    observer->OnBatteryStateSampled(last_battery_state_);
}

void BatteryStateSampler::RemoveObserver(Observer* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  observer_list_.RemoveObserver(observer);
}

void BatteryStateSampler::Shutdown() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  sampling_event_source_.reset();
  battery_level_provider_.reset();
}

// static
std::unique_ptr<base::BatteryStateSampler>
BatteryStateSampler::CreateInstanceForTesting(
    std::unique_ptr<SamplingEventSource> sampling_event_source,
    std::unique_ptr<BatteryLevelProvider> battery_level_provider) {
  g_test_instance_installed = true;
  return std::make_unique<BatteryStateSampler>(
      std::move(sampling_event_source), std::move(battery_level_provider));
}

// static
bool BatteryStateSampler::HasTestingInstance() {
  return g_test_instance_installed;
}

#if !BUILDFLAG(IS_MAC)
// static
std::unique_ptr<SamplingEventSource>
BatteryStateSampler::CreateSamplingEventSource() {
  // On platforms where the OS does not provide a notification when an updated
  // battery level is available, simply sample on a regular 1 minute interval.
  return std::make_unique<TimerSamplingEventSource>(Minutes(1));
}
#endif  // !BUILDFLAG(IS_MAC)

void BatteryStateSampler::OnInitialBatteryStateSampled(
    const absl::optional<BatteryLevelProvider::BatteryState>& battery_state) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  DCHECK(!has_last_battery_state_);
  has_last_battery_state_ = true;
  last_battery_state_ = battery_state;

  for (auto& observer : observer_list_)
    observer.OnBatteryStateSampled(battery_state);
}

void BatteryStateSampler::OnSamplingEvent() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(battery_level_provider_);

  battery_level_provider_->GetBatteryState(base::BindOnce(
      &BatteryStateSampler::OnBatteryStateSampled, base::Unretained(this)));
}

void BatteryStateSampler::OnBatteryStateSampled(
    const absl::optional<BatteryLevelProvider::BatteryState>& battery_state) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  DCHECK(has_last_battery_state_);
  last_battery_state_ = battery_state;

  for (auto& observer : observer_list_)
    observer.OnBatteryStateSampled(battery_state);
}

}  // namespace base
