// 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 "content/browser/devtools/protocol/system_info_handler.h"

#include <stdint.h>

#include <utility>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "content/browser/gpu/compositor_util.h"
#include "content/browser/gpu/gpu_data_manager_impl.h"
#include "content/browser/gpu/gpu_process_host.h"
#include "gpu/config/gpu_feature_type.h"
#include "gpu/config/gpu_info.h"
#include "gpu/config/gpu_switches.h"
#if defined(OS_CHROMEOS)
#include "gpu/config/gpu_util.h"
#endif

namespace content {
namespace protocol {

namespace {

using SystemInfo::GPUDevice;
using SystemInfo::GPUInfo;
using GetInfoCallback = SystemInfo::Backend::GetInfoCallback;

// Give the GPU process a few seconds to provide GPU info.
// Linux Debug builds need more time -- see Issue 796437.
#if defined(OS_LINUX) && !defined(NDEBUG)
const int kGPUInfoWatchdogTimeoutMs = 20000;
#else
const int kGPUInfoWatchdogTimeoutMs = 5000;
#endif

class AuxGPUInfoEnumerator : public gpu::GPUInfo::Enumerator {
 public:
  AuxGPUInfoEnumerator(protocol::DictionaryValue* dictionary)
      : dictionary_(dictionary),
        in_aux_attributes_(false) { }

  void AddInt64(const char* name, int64_t value) override {
    if (in_aux_attributes_)
      dictionary_->setDouble(name, value);
  }

  void AddInt(const char* name, int value) override {
    if (in_aux_attributes_)
      dictionary_->setInteger(name, value);
  }

  void AddString(const char* name, const std::string& value) override {
    if (in_aux_attributes_)
      dictionary_->setString(name, value);
  }

  void AddBool(const char* name, bool value) override {
    if (in_aux_attributes_)
      dictionary_->setBoolean(name, value);
  }

  void AddTimeDeltaInSecondsF(const char* name,
                              const base::TimeDelta& value) override {
    if (in_aux_attributes_)
      dictionary_->setDouble(name, value.InSecondsF());
  }

  void BeginGPUDevice() override {}

  void EndGPUDevice() override {}

  void BeginVideoDecodeAcceleratorSupportedProfile() override {}

  void EndVideoDecodeAcceleratorSupportedProfile() override {}

  void BeginVideoEncodeAcceleratorSupportedProfile() override {}

  void EndVideoEncodeAcceleratorSupportedProfile() override {}

  void BeginOverlayCapability() override {}

  void EndOverlayCapability() override {}

  void BeginAuxAttributes() override {
    in_aux_attributes_ = true;
  }

  void EndAuxAttributes() override {
    in_aux_attributes_ = false;
  }

 private:
  protocol::DictionaryValue* dictionary_;
  bool in_aux_attributes_;
};

std::unique_ptr<GPUDevice> GPUDeviceToProtocol(
    const gpu::GPUInfo::GPUDevice& device) {
  return GPUDevice::Create().SetVendorId(device.vendor_id)
                            .SetDeviceId(device.device_id)
                            .SetVendorString(device.vendor_string)
                            .SetDeviceString(device.device_string)
                            .Build();
}

void SendGetInfoResponse(std::unique_ptr<GetInfoCallback> callback) {
  gpu::GPUInfo gpu_info = GpuDataManagerImpl::GetInstance()->GetGPUInfo();
  std::unique_ptr<protocol::Array<GPUDevice>> devices =
      protocol::Array<GPUDevice>::create();
  devices->addItem(GPUDeviceToProtocol(gpu_info.gpu));
  for (const auto& device : gpu_info.secondary_gpus)
    devices->addItem(GPUDeviceToProtocol(device));

  std::unique_ptr<protocol::DictionaryValue> aux_attributes =
      protocol::DictionaryValue::create();
  AuxGPUInfoEnumerator enumerator(aux_attributes.get());
  gpu_info.EnumerateFields(&enumerator);
  enumerator.BeginAuxAttributes();
  enumerator.AddInt("processCrashCount", GpuProcessHost::GetGpuCrashCount());
  enumerator.EndAuxAttributes();

  std::unique_ptr<base::DictionaryValue> base_feature_status =
      GetFeatureStatus();
  std::unique_ptr<protocol::DictionaryValue> feature_status =
      protocol::DictionaryValue::cast(
          protocol::toProtocolValue(base_feature_status.get(), 1000));

  std::unique_ptr<protocol::Array<std::string>> driver_bug_workarounds =
      protocol::Array<std::string>::create();
  for (const std::string& s : GetDriverBugWorkarounds())
      driver_bug_workarounds->addItem(s);

  std::unique_ptr<GPUInfo> gpu = GPUInfo::Create()
      .SetDevices(std::move(devices))
      .SetAuxAttributes(std::move(aux_attributes))
      .SetFeatureStatus(std::move(feature_status))
      .SetDriverBugWorkarounds(std::move(driver_bug_workarounds))
      .Build();

  base::CommandLine* command = base::CommandLine::ForCurrentProcess();
#if defined(OS_WIN)
  std::string command_string =
      base::WideToUTF8(command->GetCommandLineString());
#else
  std::string command_string = command->GetCommandLineString();
#endif

  callback->sendSuccess(std::move(gpu), gpu_info.machine_model_name,
                        gpu_info.machine_model_version, command_string);
}

}  // namespace

class SystemInfoHandlerGpuObserver : public content::GpuDataManagerObserver {
 public:
  explicit SystemInfoHandlerGpuObserver(
      std::unique_ptr<GetInfoCallback> callback)
      : callback_(std::move(callback)),
        weak_factory_(this) {
    BrowserThread::PostDelayedTask(
        BrowserThread::UI, FROM_HERE,
        base::BindOnce(&SystemInfoHandlerGpuObserver::ObserverWatchdogCallback,
                       weak_factory_.GetWeakPtr()),
        base::TimeDelta::FromMilliseconds(kGPUInfoWatchdogTimeoutMs));

    GpuDataManagerImpl::GetInstance()->AddObserver(this);
    OnGpuInfoUpdate();
  }

  void OnGpuInfoUpdate() override {
    if (GpuDataManagerImpl::GetInstance()->IsGpuFeatureInfoAvailable())
      UnregisterAndSendResponse();
  }

  void OnGpuProcessCrashed(base::TerminationStatus exit_code) override {
    UnregisterAndSendResponse();
  }

  void ObserverWatchdogCallback() {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
#if defined(OS_CHROMEOS)
    // TODO(zmo): CHECK everywhere once https://crbug.com/796386 is fixed.
    gpu::GpuFeatureInfo gpu_feature_info =
        gpu::ComputeGpuFeatureInfoWithHardwareAccelerationDisabled();
    GpuDataManagerImpl::GetInstance()->UpdateGpuFeatureInfo(gpu_feature_info,
                                                            base::nullopt);
    UnregisterAndSendResponse();
#else
    CHECK(false) << "Gathering system GPU info took more than 5 seconds.";
#endif
  }

  void UnregisterAndSendResponse() {
    GpuDataManagerImpl::GetInstance()->RemoveObserver(this);
    SendGetInfoResponse(std::move(callback_));
    delete this;
  }

 private:
  std::unique_ptr<GetInfoCallback> callback_;
  base::WeakPtrFactory<SystemInfoHandlerGpuObserver> weak_factory_;
};

SystemInfoHandler::SystemInfoHandler()
    : DevToolsDomainHandler(SystemInfo::Metainfo::domainName) {
}

SystemInfoHandler::~SystemInfoHandler() {
}

void SystemInfoHandler::Wire(UberDispatcher* dispatcher) {
  SystemInfo::Dispatcher::wire(dispatcher, this);
}

void SystemInfoHandler::GetInfo(std::unique_ptr<GetInfoCallback> callback) {
  // We will be able to get more information from the GpuDataManager.
  // Register a transient observer with it to call us back when the
  // information is available.
  new SystemInfoHandlerGpuObserver(std::move(callback));
}

}  // namespace protocol
}  // namespace content
