// Copyright (c) 2011 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 "chrome/browser/gpu_data_manager.h"

#include "base/command_line.h"
#include "base/metrics/histogram.h"
#include "base/string_number_conversions.h"
#include "chrome/common/child_process_logging.h"
#include "chrome/common/chrome_switches.h"
#include "content/browser/browser_thread.h"
#include "content/browser/gpu_blacklist.h"
#include "content/browser/gpu_process_host.h"
#include "content/common/gpu_messages.h"
#include "content/gpu/gpu_info_collector.h"
#include "ui/gfx/gl/gl_implementation.h"
#include "ui/gfx/gl/gl_switches.h"

GpuDataManager::GpuDataManager()
    : complete_gpu_info_already_requested_(false) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  GPUInfo gpu_info;
  gpu_info_collector::CollectPreliminaryGraphicsInfo(&gpu_info);
  UpdateGpuInfo(gpu_info);
}

GpuDataManager::~GpuDataManager() { }

GpuDataManager* GpuDataManager::GetInstance() {
  return Singleton<GpuDataManager>::get();
}

void GpuDataManager::RequestCompleteGpuInfoIfNeeded() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (complete_gpu_info_already_requested_)
    return;
  complete_gpu_info_already_requested_ = true;

  GpuProcessHost::SendOnIO(
      0,
      content::CAUSE_FOR_GPU_LAUNCH_GPUDATAMANAGER_REQUESTCOMPLETEGPUINFOIFNEEDED,
      new GpuMsg_CollectGraphicsInfo());
}

void GpuDataManager::UpdateGpuInfo(const GPUInfo& gpu_info) {
  base::AutoLock auto_lock(gpu_info_lock_);
  if (!gpu_info_.Merge(gpu_info))
    return;
  child_process_logging::SetGpuInfo(gpu_info_);
}

const GPUInfo& GpuDataManager::gpu_info() const {
  base::AutoLock auto_lock(gpu_info_lock_);
  return gpu_info_;
}

Value* GpuDataManager::GetFeatureStatus() {
  const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
  if (gpu_blacklist_.get())
    return gpu_blacklist_->GetFeatureStatus(GpuAccessAllowed(),
        browser_command_line.HasSwitch(switches::kDisableAcceleratedCompositing),
        browser_command_line.HasSwitch(switches::kEnableAccelerated2dCanvas),
        browser_command_line.HasSwitch(switches::kDisableExperimentalWebGL),
        browser_command_line.HasSwitch(switches::kDisableGLMultisampling));
  return NULL;
}

std::string GpuDataManager::GetBlacklistVersion() const {
  if (gpu_blacklist_.get() != NULL) {
    uint16 version_major, version_minor;
    if (gpu_blacklist_->GetVersion(&version_major,
                                   &version_minor)) {
      std::string version_string =
          base::UintToString(static_cast<unsigned>(version_major)) +
          "." +
          base::UintToString(static_cast<unsigned>(version_minor));
      return version_string;
    }
  }
  return "";
}

void GpuDataManager::AddLogMessage(Value* msg) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  log_messages_.Append(msg);
}

const ListValue& GpuDataManager::log_messages() const {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  return log_messages_;
}

GpuFeatureFlags GpuDataManager::GetGpuFeatureFlags() {
  return gpu_feature_flags_;
}

bool GpuDataManager::GpuAccessAllowed() {
  uint32 flags = gpu_feature_flags_.flags();

  // This will in effect block access to all GPU features if any of them
  // is blacklisted.
  // TODO(vangelis): Restructure the code to make it possible to selectively
  // blaclist gpu features.
  return !(flags & GpuFeatureFlags::kGpuFeatureAccelerated2dCanvas ||
           flags & GpuFeatureFlags::kGpuFeatureAcceleratedCompositing ||
           flags & GpuFeatureFlags::kGpuFeatureWebgl);
}

void GpuDataManager::AddGpuInfoUpdateCallback(Callback0::Type* callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  gpu_info_update_callbacks_.insert(callback);
}

bool GpuDataManager::RemoveGpuInfoUpdateCallback(Callback0::Type* callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  std::set<Callback0::Type*>::iterator i =
      gpu_info_update_callbacks_.find(callback);
  if (i != gpu_info_update_callbacks_.end()) {
    gpu_info_update_callbacks_.erase(i);
    return true;
  }
  return false;
}

void GpuDataManager::AppendRendererCommandLine(
    CommandLine* command_line) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(command_line);

  uint32 flags = gpu_feature_flags_.flags();
  if ((flags & GpuFeatureFlags::kGpuFeatureWebgl) &&
      !command_line->HasSwitch(switches::kDisableExperimentalWebGL))
    command_line->AppendSwitch(switches::kDisableExperimentalWebGL);
  if ((flags & GpuFeatureFlags::kGpuFeatureMultisampling) &&
      !command_line->HasSwitch(switches::kDisableGLMultisampling))
    command_line->AppendSwitch(switches::kDisableGLMultisampling);
  // If we have kGpuFeatureAcceleratedCompositing, we disable all GPU features.
  if (flags & GpuFeatureFlags::kGpuFeatureAcceleratedCompositing) {
    const char* switches[] = {
        switches::kDisableAcceleratedCompositing,
        switches::kDisableExperimentalWebGL
    };
    const int switch_count = sizeof(switches) / sizeof(char*);
    for (int i = 0; i < switch_count; ++i) {
      if (!command_line->HasSwitch(switches[i]))
        command_line->AppendSwitch(switches[i]);
    }
  }
}

void GpuDataManager::UpdateGpuBlacklist(GpuBlacklist* gpu_blacklist) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  gpu_blacklist_.reset(gpu_blacklist);
  UpdateGpuFeatureFlags();
}

void GpuDataManager::RunGpuInfoUpdateCallbacks() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  std::set<Callback0::Type*>::iterator i = gpu_info_update_callbacks_.begin();
  for (; i != gpu_info_update_callbacks_.end(); ++i) {
    (*i)->Run();
  }
}

void GpuDataManager::UpdateGpuFeatureFlags() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  GpuBlacklist* gpu_blacklist = GetGpuBlacklist();
  if (gpu_blacklist == NULL)
    return;

  // We don't set a lock around modifying gpu_feature_flags_ since it's just an
  // int.
  if (!gpu_blacklist) {
    gpu_feature_flags_.set_flags(0);
    return;
  }

  {
    base::AutoLock auto_lock(gpu_info_lock_);
    gpu_feature_flags_ = gpu_blacklist->DetermineGpuFeatureFlags(
        GpuBlacklist::kOsAny, NULL, gpu_info_);

    // If gpu is blacklisted, no further GPUInfo will be collected.
    gpu_info_.finalized = true;
  }

  uint32 max_entry_id = gpu_blacklist->max_entry_id();
  if (!gpu_feature_flags_.flags()) {
    UMA_HISTOGRAM_ENUMERATION("GPU.BlacklistTestResultsPerEntry",
        0, max_entry_id + 1);
    return;
  }

  // Notify clients that GpuInfo state has changed
  RunGpuInfoUpdateCallbacks();

  // TODO(zmo): move histograming to GpuBlacklist::DetermineGpuFeatureFlags.
  std::vector<uint32> flag_entries;
  gpu_blacklist->GetGpuFeatureFlagEntries(
      GpuFeatureFlags::kGpuFeatureAll, flag_entries);
  DCHECK_GT(flag_entries.size(), 0u);
  for (size_t i = 0; i < flag_entries.size(); ++i) {
    UMA_HISTOGRAM_ENUMERATION("GPU.BlacklistTestResultsPerEntry",
        flag_entries[i], max_entry_id + 1);
  }
}

GpuBlacklist* GpuDataManager::GetGpuBlacklist() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
  if (browser_command_line.HasSwitch(switches::kIgnoreGpuBlacklist) ||
      browser_command_line.GetSwitchValueASCII(
          switches::kUseGL) == gfx::kGLImplementationOSMesaName)
    return NULL;
  // No need to return an empty blacklist.
  if (gpu_blacklist_.get() != NULL && gpu_blacklist_->max_entry_id() == 0)
    return NULL;
  return gpu_blacklist_.get();
}