// Copyright (c) 2012 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 "content/browser/power_save_blocker_impl.h"

#include <windows.h>

#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/scoped_handle.h"
#include "base/win/windows_version.h"
#include "content/public/browser/browser_thread.h"

namespace content {
namespace {

int g_blocker_count[2];

HANDLE CreatePowerRequest(POWER_REQUEST_TYPE type, const std::string& reason) {
  typedef HANDLE (WINAPI* PowerCreateRequestPtr)(PREASON_CONTEXT);
  typedef BOOL (WINAPI* PowerSetRequestPtr)(HANDLE, POWER_REQUEST_TYPE);

  if (type == PowerRequestExecutionRequired &&
      base::win::GetVersion() < base::win::VERSION_WIN8) {
    return INVALID_HANDLE_VALUE;
  }

  static PowerCreateRequestPtr PowerCreateRequestFn = NULL;
  static PowerSetRequestPtr PowerSetRequestFn = NULL;

  if (!PowerCreateRequestFn || !PowerSetRequestFn) {
    HMODULE module = GetModuleHandle(L"kernel32.dll");
    PowerCreateRequestFn = reinterpret_cast<PowerCreateRequestPtr>(
        GetProcAddress(module, "PowerCreateRequest"));
    PowerSetRequestFn = reinterpret_cast<PowerSetRequestPtr>(
        GetProcAddress(module, "PowerSetRequest"));

    if (!PowerCreateRequestFn || !PowerSetRequestFn)
      return INVALID_HANDLE_VALUE;
  }
  base::string16 wide_reason = ASCIIToUTF16(reason);
  REASON_CONTEXT context = {0};
  context.Version = POWER_REQUEST_CONTEXT_VERSION;
  context.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING;
  context.Reason.SimpleReasonString = const_cast<wchar_t*>(wide_reason.c_str());

  base::win::ScopedHandle handle(PowerCreateRequestFn(&context));
  if (!handle.IsValid())
    return INVALID_HANDLE_VALUE;

  if (PowerSetRequestFn(handle, type))
    return handle.Take();

  // Something went wrong.
  return INVALID_HANDLE_VALUE;
}

// Takes ownership of the |handle|.
void DeletePowerRequest(POWER_REQUEST_TYPE type, HANDLE handle) {
  base::win::ScopedHandle request_handle(handle);
  if (!request_handle.IsValid())
    return;

  if (type == PowerRequestExecutionRequired &&
      base::win::GetVersion() < base::win::VERSION_WIN8) {
    return;
  }

  typedef BOOL (WINAPI* PowerClearRequestPtr)(HANDLE, POWER_REQUEST_TYPE);
  HMODULE module = GetModuleHandle(L"kernel32.dll");
  PowerClearRequestPtr PowerClearRequestFn =
      reinterpret_cast<PowerClearRequestPtr>(
          GetProcAddress(module, "PowerClearRequest"));

  if (!PowerClearRequestFn)
    return;

  BOOL success = PowerClearRequestFn(request_handle, type);
  DCHECK(success);
}

void ApplySimpleBlock(PowerSaveBlocker::PowerSaveBlockerType type,
                      int delta) {
  g_blocker_count[type] += delta;
  DCHECK_GE(g_blocker_count[type], 0);

  if (g_blocker_count[type] > 1)
    return;

  DWORD this_flag = 0;
  if (type == PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension)
    this_flag |= ES_SYSTEM_REQUIRED;
  else
    this_flag |= ES_DISPLAY_REQUIRED;

  DCHECK(this_flag);

  static DWORD flags = ES_CONTINUOUS;
  if (!g_blocker_count[type])
    flags &= ~this_flag;
  else
    flags |= this_flag;

  SetThreadExecutionState(flags);
}

}  // namespace

class PowerSaveBlockerImpl::Delegate
    : public base::RefCountedThreadSafe<PowerSaveBlockerImpl::Delegate> {
 public:
  Delegate(PowerSaveBlockerType type, const std::string& reason)
      : type_(type), reason_(reason) {}

  // Does the actual work to apply or remove the desired power save block.
  void ApplyBlock();
  void RemoveBlock();

  // Returns the equivalent POWER_REQUEST_TYPE for this request.
  POWER_REQUEST_TYPE RequestType();

 private:
  friend class base::RefCountedThreadSafe<Delegate>;
  ~Delegate() {}

  PowerSaveBlockerType type_;
  const std::string reason_;
  base::win::ScopedHandle handle_;

  DISALLOW_COPY_AND_ASSIGN(Delegate);
};

void PowerSaveBlockerImpl::Delegate::ApplyBlock() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (base::win::GetVersion() < base::win::VERSION_WIN7)
    return ApplySimpleBlock(type_, 1);

  handle_.Set(CreatePowerRequest(RequestType(), reason_));
}

void PowerSaveBlockerImpl::Delegate::RemoveBlock() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (base::win::GetVersion() < base::win::VERSION_WIN7)
    return ApplySimpleBlock(type_, -1);

  DeletePowerRequest(RequestType(), handle_.Take());
}

POWER_REQUEST_TYPE PowerSaveBlockerImpl::Delegate::RequestType() {
  if (type_ == kPowerSaveBlockPreventDisplaySleep)
    return PowerRequestDisplayRequired;

  if (base::win::GetVersion() < base::win::VERSION_WIN8)
    return PowerRequestSystemRequired;

  return PowerRequestExecutionRequired;
}

PowerSaveBlockerImpl::PowerSaveBlockerImpl(PowerSaveBlockerType type,
                                           const std::string& reason)
    : delegate_(new Delegate(type, reason)) {
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&Delegate::ApplyBlock, delegate_));
}

PowerSaveBlockerImpl::~PowerSaveBlockerImpl() {
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&Delegate::RemoveBlock, delegate_));
}

}  // namespace content