// Copyright 2014 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 "services/device/battery/battery_status_service.h"

#include <memory>
#include <utility>

#include "base/bind.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "services/device/battery/battery_status_manager.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace device {

namespace {

class FakeBatteryManager : public BatteryStatusManager {
 public:
  explicit FakeBatteryManager(
      const BatteryStatusService::BatteryUpdateCallback& callback)
      : callback_(callback), start_invoked_count_(0), stop_invoked_count_(0) {}
  ~FakeBatteryManager() override {}

  // Methods from Battery Status Manager
  bool StartListeningBatteryChange() override {
    start_invoked_count_++;
    return true;
  }

  void StopListeningBatteryChange() override { stop_invoked_count_++; }

  void InvokeUpdateCallback(const mojom::BatteryStatus& status) {
    callback_.Run(status);
  }

  int start_invoked_count() const { return start_invoked_count_; }
  int stop_invoked_count() const { return stop_invoked_count_; }

 private:
  BatteryStatusService::BatteryUpdateCallback callback_;
  int start_invoked_count_;
  int stop_invoked_count_;

  DISALLOW_COPY_AND_ASSIGN(FakeBatteryManager);
};

}  // namespace

class BatteryStatusServiceTest : public testing::Test {
 public:
  BatteryStatusServiceTest()
      : battery_manager_(nullptr),
        callback1_invoked_count_(0),
        callback2_invoked_count_(0) {}
  ~BatteryStatusServiceTest() override {}

 protected:
  typedef BatteryStatusService::BatteryUpdateSubscription BatterySubscription;

  void SetUp() override {
    callback1_ = base::Bind(&BatteryStatusServiceTest::Callback1,
                            base::Unretained(this));
    callback2_ = base::Bind(&BatteryStatusServiceTest::Callback2,
                            base::Unretained(this));

    // We keep a raw pointer to the FakeBatteryManager, which we expect to
    // remain valid for the lifetime of the BatteryStatusService.
    std::unique_ptr<FakeBatteryManager> battery_manager(
        new FakeBatteryManager(battery_service_.GetUpdateCallbackForTesting()));
    battery_manager_ = battery_manager.get();

    battery_service_.SetBatteryManagerForTesting(std::move(battery_manager));
  }

  void TearDown() override { base::RunLoop().RunUntilIdle(); }

  FakeBatteryManager* battery_manager() { return battery_manager_; }

  std::unique_ptr<BatterySubscription> AddCallback(
      const BatteryStatusService::BatteryUpdateCallback& callback) {
    return battery_service_.AddCallback(callback);
  }

  int callback1_invoked_count() const { return callback1_invoked_count_; }

  int callback2_invoked_count() const { return callback2_invoked_count_; }

  const mojom::BatteryStatus& battery_status() const { return battery_status_; }

  const BatteryStatusService::BatteryUpdateCallback& callback1() const {
    return callback1_;
  }

  const BatteryStatusService::BatteryUpdateCallback& callback2() const {
    return callback2_;
  }

 private:
  void Callback1(const mojom::BatteryStatus& status) {
    callback1_invoked_count_++;
    battery_status_ = status;
  }

  void Callback2(const mojom::BatteryStatus& status) {
    callback2_invoked_count_++;
    battery_status_ = status;
  }

  base::MessageLoop message_loop_;
  BatteryStatusService battery_service_;
  FakeBatteryManager* battery_manager_;
  BatteryStatusService::BatteryUpdateCallback callback1_;
  BatteryStatusService::BatteryUpdateCallback callback2_;
  int callback1_invoked_count_;
  int callback2_invoked_count_;
  mojom::BatteryStatus battery_status_;

  DISALLOW_COPY_AND_ASSIGN(BatteryStatusServiceTest);
};

TEST_F(BatteryStatusServiceTest, AddFirstCallback) {
  std::unique_ptr<BatterySubscription> subscription1 = AddCallback(callback1());
  EXPECT_EQ(1, battery_manager()->start_invoked_count());
  EXPECT_EQ(0, battery_manager()->stop_invoked_count());
  subscription1.reset();
  EXPECT_EQ(1, battery_manager()->start_invoked_count());
  EXPECT_EQ(1, battery_manager()->stop_invoked_count());
}

TEST_F(BatteryStatusServiceTest, AddCallbackAfterUpdate) {
  std::unique_ptr<BatterySubscription> subscription1 = AddCallback(callback1());
  mojom::BatteryStatus status;
  battery_manager()->InvokeUpdateCallback(status);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1, callback1_invoked_count());
  EXPECT_EQ(0, callback2_invoked_count());

  std::unique_ptr<BatterySubscription> subscription2 = AddCallback(callback2());
  EXPECT_EQ(1, callback1_invoked_count());
  EXPECT_EQ(1, callback2_invoked_count());
}

TEST_F(BatteryStatusServiceTest, TwoCallbacksUpdate) {
  std::unique_ptr<BatterySubscription> subscription1 = AddCallback(callback1());
  std::unique_ptr<BatterySubscription> subscription2 = AddCallback(callback2());

  mojom::BatteryStatus status;
  status.charging = true;
  status.charging_time = 100;
  status.discharging_time = 200;
  status.level = 0.5;
  battery_manager()->InvokeUpdateCallback(status);
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(1, callback1_invoked_count());
  EXPECT_EQ(1, callback2_invoked_count());
  EXPECT_EQ(status.charging, battery_status().charging);
  EXPECT_EQ(status.charging_time, battery_status().charging_time);
  EXPECT_EQ(status.discharging_time, battery_status().discharging_time);
  EXPECT_EQ(status.level, battery_status().level);
}

TEST_F(BatteryStatusServiceTest, RemoveOneCallback) {
  std::unique_ptr<BatterySubscription> subscription1 = AddCallback(callback1());
  std::unique_ptr<BatterySubscription> subscription2 = AddCallback(callback2());

  mojom::BatteryStatus status;
  battery_manager()->InvokeUpdateCallback(status);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1, callback1_invoked_count());
  EXPECT_EQ(1, callback2_invoked_count());

  subscription1.reset();
  battery_manager()->InvokeUpdateCallback(status);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1, callback1_invoked_count());
  EXPECT_EQ(2, callback2_invoked_count());
}

}  // namespace device
