// 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 <utility>

#include "base/macros.h"
#include "base/thread_task_runner_handle.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "device/battery/battery_status_manager.h"
#include "device/battery/battery_status_service.h"

// These tests run against the default implementation of the BatteryMonitor
// service, with a dummy BatteryManager set as a source of the battery
// information. They can be run only on platforms that use the default service
// implementation, ie. on the platforms where BatteryStatusService is used.

namespace content {

namespace {

class FakeBatteryManager : public device::BatteryStatusManager {
 public:
  explicit FakeBatteryManager(
      const device::BatteryStatusService::BatteryUpdateCallback& callback)
      : callback_(callback), battery_status_available_(true), started_(false) {}
  ~FakeBatteryManager() override {}

  // Methods from BatteryStatusManager.
  bool StartListeningBatteryChange() override {
    started_ = true;
    if (battery_status_available_)
      InvokeUpdateCallback();
    return battery_status_available_;
  }

  void StopListeningBatteryChange() override {}

  void InvokeUpdateCallback() {
    // Invoke asynchronously to mimic the OS-specific battery managers.
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::Bind(callback_, status_));
  }

  void set_battery_status(const device::BatteryStatus& status) {
    status_ = status;
  }

  void set_battery_status_available(bool value) {
    battery_status_available_ = value;
  }

  bool started() { return started_; }

 private:
  device::BatteryStatusService::BatteryUpdateCallback callback_;
  bool battery_status_available_;
  bool started_;
  device::BatteryStatus status_;

  DISALLOW_COPY_AND_ASSIGN(FakeBatteryManager);
};

class BatteryMonitorImplTest : public ContentBrowserTest {
 public:
  BatteryMonitorImplTest() : battery_manager_(NULL), battery_service_(NULL) {}

  void SetUpOnMainThread() override {
    battery_service_ = device::BatteryStatusService::GetInstance();

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

    battery_service_->SetBatteryManagerForTesting(std::move(battery_manager));
  }

  void TearDown() override {
    battery_service_->SetBatteryManagerForTesting(
        scoped_ptr<device::BatteryStatusManager>());
    battery_manager_ = NULL;
  }

  FakeBatteryManager* battery_manager() { return battery_manager_; }

 private:
  FakeBatteryManager* battery_manager_;
  device::BatteryStatusService* battery_service_;

  DISALLOW_COPY_AND_ASSIGN(BatteryMonitorImplTest);
};

IN_PROC_BROWSER_TEST_F(BatteryMonitorImplTest, BatteryManagerDefaultValues) {
  // Set the fake battery manager to return false on start. From JavaScript
  // request a promise for the battery status information and once it resolves
  // check the default values and navigate to #pass.
  battery_manager()->set_battery_status_available(false);
  GURL test_url =
      GetTestUrl("battery_status", "battery_status_default_test.html");
  NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 2);
  EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
  EXPECT_TRUE(battery_manager()->started());
}

IN_PROC_BROWSER_TEST_F(BatteryMonitorImplTest, BatteryManagerResolvePromise) {
  // Set the fake battery manager to return predefined battery status values.
  // From JavaScript request a promise for the battery status information and
  // once it resolves check the values and navigate to #pass.
  device::BatteryStatus status;
  status.charging = true;
  status.charging_time = 100;
  status.discharging_time = std::numeric_limits<double>::infinity();
  status.level = 0.5;
  battery_manager()->set_battery_status(status);

  GURL test_url = GetTestUrl("battery_status",
                             "battery_status_promise_resolution_test.html");
  NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 2);
  EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
  EXPECT_TRUE(battery_manager()->started());
}

IN_PROC_BROWSER_TEST_F(BatteryMonitorImplTest,
                       BatteryManagerWithEventListener) {
  // Set the fake battery manager to return default battery status values.
  // From JavaScript request a promise for the battery status information.
  // Once it resolves add an event listener for battery level change. Set
  // battery level to 0.6 and invoke update. Check that the event listener
  // is invoked with the correct value for level and navigate to #pass.
  device::BatteryStatus status;
  battery_manager()->set_battery_status(status);

  TestNavigationObserver same_tab_observer(shell()->web_contents(), 2);
  GURL test_url =
      GetTestUrl("battery_status", "battery_status_event_listener_test.html");
  shell()->LoadURL(test_url);
  same_tab_observer.Wait();
  EXPECT_EQ("resolved", shell()->web_contents()->GetLastCommittedURL().ref());

  TestNavigationObserver same_tab_observer2(shell()->web_contents(), 1);
  status.level = 0.6;
  battery_manager()->set_battery_status(status);
  battery_manager()->InvokeUpdateCallback();
  same_tab_observer2.Wait();
  EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
  EXPECT_TRUE(battery_manager()->started());
}

}  //  namespace

}  //  namespace content
