// 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 "content/browser/gpu/compositor_util.h"
#include "content/public/browser/gpu_data_manager.h"
#include "gpu/config/gpu_info.h"

namespace content {
namespace devtools {
namespace system_info {

namespace {

using Response = DevToolsProtocolClient::Response;

// Give the GPU process a few seconds to provide GPU info.
const int kGPUInfoWatchdogTimeoutMs = 5000;

class AuxGPUInfoEnumerator : public gpu::GPUInfo::Enumerator {
 public:
  AuxGPUInfoEnumerator(base::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 BeginAuxAttributes() override {
    in_aux_attributes_ = true;
  }

  void EndAuxAttributes() override {
    in_aux_attributes_ = false;
  }

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

scoped_refptr<GPUDevice> GPUDeviceToProtocol(
    const gpu::GPUInfo::GPUDevice& device) {
  return GPUDevice::Create()->set_vendor_id(device.vendor_id)
                            ->set_device_id(device.device_id)
                            ->set_vendor_string(device.vendor_string)
                            ->set_device_string(device.device_string);
}

}  // namespace

class SystemInfoHandlerGpuObserver : public content::GpuDataManagerObserver {
 public:
  SystemInfoHandlerGpuObserver(base::WeakPtr<SystemInfoHandler> handler,
                               DevToolsCommandId command_id)
      : handler_(handler),
        command_id_(command_id),
        observer_id_(++next_observer_id_)
  {
    if (handler_) {
      handler_->AddActiveObserverId(observer_id_);
    }
  }

  void OnGpuInfoUpdate() override {
    UnregisterAndSendResponse();
  }

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

  void UnregisterAndSendResponse() {
    GpuDataManager::GetInstance()->RemoveObserver(this);
    if (handler_.get()) {
      if (handler_->RemoveActiveObserverId(observer_id_)) {
        handler_->SendGetInfoResponse(command_id_);
      }
    }
    delete this;
  }

  int GetObserverId() {
    return observer_id_;
  }

 private:
  base::WeakPtr<SystemInfoHandler> handler_;
  DevToolsCommandId command_id_;
  int observer_id_;

  static int next_observer_id_;
};

int SystemInfoHandlerGpuObserver::next_observer_id_ = 0;

SystemInfoHandler::SystemInfoHandler()
    : weak_factory_(this) {
}

SystemInfoHandler::~SystemInfoHandler() {
}

void SystemInfoHandler::SetClient(scoped_ptr<Client> client) {
  client_.swap(client);
}

Response SystemInfoHandler::GetInfo(DevToolsCommandId command_id) {
  std::string reason;
  if (!GpuDataManager::GetInstance()->GpuAccessAllowed(&reason) ||
      GpuDataManager::GetInstance()->IsEssentialGpuInfoAvailable()) {
    // The GpuDataManager already has all of the information needed to make
    // GPU-based blacklisting decisions. Post a task to give it to the
    // client asynchronously.
    //
    // Waiting for complete GPU info in the if-test above seems to
    // frequently hit internal timeouts in the launching of the unsandboxed
    // GPU process in debug builds on Windows.
    BrowserThread::PostTask(
        BrowserThread::UI,
        FROM_HERE,
        base::Bind(&SystemInfoHandler::SendGetInfoResponse,
                   weak_factory_.GetWeakPtr(),
                   command_id));
  } else {
    // 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.
    SystemInfoHandlerGpuObserver* observer = new SystemInfoHandlerGpuObserver(
        weak_factory_.GetWeakPtr(), command_id);
    BrowserThread::PostDelayedTask(
        BrowserThread::UI,
        FROM_HERE,
        base::Bind(&SystemInfoHandler::ObserverWatchdogCallback,
                   weak_factory_.GetWeakPtr(),
                   observer->GetObserverId(),
                   command_id),
        base::TimeDelta::FromMilliseconds(kGPUInfoWatchdogTimeoutMs));
    GpuDataManager::GetInstance()->AddObserver(observer);
    // There's no other method available to request just essential GPU info.
    GpuDataManager::GetInstance()->RequestCompleteGpuInfoIfNeeded();
  }

  return Response::OK();
}

void SystemInfoHandler::SendGetInfoResponse(DevToolsCommandId command_id) {
  gpu::GPUInfo gpu_info = GpuDataManager::GetInstance()->GetGPUInfo();
  std::vector<scoped_refptr<GPUDevice>> devices;
  devices.push_back(GPUDeviceToProtocol(gpu_info.gpu));
  for (const auto& device : gpu_info.secondary_gpus)
    devices.push_back(GPUDeviceToProtocol(device));

  scoped_ptr<base::DictionaryValue> aux_attributes(new base::DictionaryValue);
  AuxGPUInfoEnumerator enumerator(aux_attributes.get());
  gpu_info.EnumerateFields(&enumerator);

  scoped_refptr<GPUInfo> gpu =
      GPUInfo::Create()
          ->set_devices(devices)
          ->set_aux_attributes(std::move(aux_attributes))
          ->set_feature_status(make_scoped_ptr(GetFeatureStatus()))
          ->set_driver_bug_workarounds(GetDriverBugWorkarounds());

  client_->SendGetInfoResponse(
      command_id,
      GetInfoResponse::Create()->set_gpu(gpu)
      ->set_model_name(gpu_info.machine_model_name)
      ->set_model_version(gpu_info.machine_model_version));
}

void SystemInfoHandler::AddActiveObserverId(int observer_id) {
  base::AutoLock auto_lock(lock_);
  active_observers_.insert(observer_id);
}

bool SystemInfoHandler::RemoveActiveObserverId(int observer_id) {
  base::AutoLock auto_lock(lock_);
  int num_removed = active_observers_.erase(observer_id);
  return (num_removed != 0);
}

void SystemInfoHandler::ObserverWatchdogCallback(int observer_id,
                                                 DevToolsCommandId command_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (RemoveActiveObserverId(observer_id)) {
    SendGetInfoResponse(command_id);
    // For the time being we want to know about this event in the test logs.
    LOG(ERROR) << "SystemInfoHandler: request for GPU info timed out!"
               << " Most recent info sent.";
  }
}

}  // namespace system_info
}  // namespace devtools
}  // namespace content
