// Copyright (c) 2010 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_process_host.h"

#include "app/app_switches.h"
#include "base/command_line.h"
#include "base/thread.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_thread.h"
#include "chrome/browser/gpu_process_host_ui_shim.h"
#include "chrome/browser/renderer_host/render_view_host.h"
#include "chrome/browser/renderer_host/render_widget_host_view.h"
#include "chrome/browser/renderer_host/resource_message_filter.h"
#include "chrome/common/child_process_logging.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/gpu_info.h"
#include "chrome/common/gpu_messages.h"
#include "chrome/common/render_messages.h"
#include "ipc/ipc_channel_handle.h"
#include "ipc/ipc_switches.h"
#include "media/base/media_switches.h"

#if defined(OS_LINUX)
#include "app/x11_util.h"
#include "gfx/gtk_native_view_id_manager.h"
#endif

namespace {

// Tasks used by this file
class RouteOnUIThreadTask : public Task {
 public:
  explicit RouteOnUIThreadTask(const IPC::Message& msg) : msg_(msg) {
  }

 private:
  void Run() {
    GpuProcessHostUIShim::Get()->OnMessageReceived(msg_);
  }
  IPC::Message msg_;
};

// Global GpuProcessHost instance.
// We can not use Singleton<GpuProcessHost> because that gets
// terminated on the wrong thread (main thread). We need the
// GpuProcessHost to be terminated on the same thread on which it is
// initialized, the IO thread.
static GpuProcessHost* sole_instance_ = NULL;

}  // anonymous namespace

GpuProcessHost::GpuProcessHost()
    : BrowserChildProcessHost(GPU_PROCESS, NULL),
      initialized_(false),
      initialized_successfully_(false) {
  DCHECK_EQ(sole_instance_, static_cast<GpuProcessHost*>(NULL));
}

GpuProcessHost::~GpuProcessHost() {
  while (!queued_synchronization_replies_.empty()) {
    delete queued_synchronization_replies_.front().reply;
    queued_synchronization_replies_.pop();
  }
  DCHECK_EQ(sole_instance_, this);
  sole_instance_ = NULL;
}

bool GpuProcessHost::EnsureInitialized() {
  if (!initialized_) {
    initialized_ = true;
    initialized_successfully_ = Init();
  }
  return initialized_successfully_;
}

bool GpuProcessHost::Init() {
  if (!CreateChannel())
    return false;

  const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
  CommandLine::StringType gpu_launcher =
      browser_command_line.GetSwitchValueNative(switches::kGpuLauncher);

  FilePath exe_path = ChildProcessHost::GetChildPath(gpu_launcher.empty());
  if (exe_path.empty())
    return false;

  CommandLine* cmd_line = new CommandLine(exe_path);
  cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kGpuProcess);
  cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id());

  // Propagate relevant command line switches.
  static const char* const kSwitchNames[] = {
    switches::kUseGL,
    switches::kDisableGpuVsync,
    switches::kDisableGpuWatchdog,
    switches::kDisableLogging,
    switches::kEnableAcceleratedDecoding,
    switches::kEnableLogging,
    switches::kGpuStartupDialog,
    switches::kLoggingLevel,
  };
  cmd_line->CopySwitchesFrom(browser_command_line, kSwitchNames,
                             arraysize(kSwitchNames));

  // If specified, prepend a launcher program to the command line.
  if (!gpu_launcher.empty())
    cmd_line->PrependWrapper(gpu_launcher);

  Launch(
#if defined(OS_WIN)
      FilePath(),
#elif defined(OS_POSIX)
      false,  // Never use the zygote (GPU plugin can't be sandboxed).
      base::environment_vector(),
#endif
      cmd_line);

  return true;
}

// static
GpuProcessHost* GpuProcessHost::Get() {
  if (sole_instance_ == NULL)
    sole_instance_ = new GpuProcessHost();
  return sole_instance_;
}

bool GpuProcessHost::Send(IPC::Message* msg) {
  if (!EnsureInitialized())
    return false;

  return BrowserChildProcessHost::Send(msg);
}

void GpuProcessHost::OnMessageReceived(const IPC::Message& message) {
  if (message.routing_id() == MSG_ROUTING_CONTROL) {
    OnControlMessageReceived(message);
  } else {
    // Need to transfer this message to the UI thread and the
    // GpuProcessHostUIShim for dispatching via its message router.
    BrowserThread::PostTask(BrowserThread::UI,
                            FROM_HERE,
                            new RouteOnUIThreadTask(message));
  }
}

void GpuProcessHost::EstablishGpuChannel(int renderer_id,
                                         ResourceMessageFilter* filter) {
  if (Send(new GpuMsg_EstablishChannel(renderer_id))) {
    sent_requests_.push(ChannelRequest(filter));
  } else {
    SendEstablishChannelReply(IPC::ChannelHandle(), GPUInfo(), filter);
  }
}

void GpuProcessHost::Synchronize(IPC::Message* reply,
                                 ResourceMessageFilter* filter) {
  if (Send(new GpuMsg_Synchronize())) {
    queued_synchronization_replies_.push(SynchronizationRequest(reply, filter));
  } else {
    SendSynchronizationReply(reply, filter);
  }
}

const GPUInfo& GpuProcessHost::gpu_info() const {
  return gpu_info_;
}

GpuProcessHost::ChannelRequest::ChannelRequest(ResourceMessageFilter* filter)
    : filter(filter) {
}

GpuProcessHost::ChannelRequest::~ChannelRequest() {}

GpuProcessHost::SynchronizationRequest::SynchronizationRequest(
    IPC::Message* reply,
    ResourceMessageFilter* filter)
    : reply(reply),
      filter(filter) {
}

GpuProcessHost::SynchronizationRequest::~SynchronizationRequest() {}

void GpuProcessHost::OnControlMessageReceived(const IPC::Message& message) {
  IPC_BEGIN_MESSAGE_MAP(GpuProcessHost, message)
    IPC_MESSAGE_HANDLER(GpuHostMsg_ChannelEstablished, OnChannelEstablished)
    IPC_MESSAGE_HANDLER(GpuHostMsg_SynchronizeReply, OnSynchronizeReply)
    IPC_MESSAGE_HANDLER(GpuHostMsg_GraphicsInfoCollected,
                        OnGraphicsInfoCollected)
#if defined(OS_LINUX)
    IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuHostMsg_GetViewXID, OnGetViewXID)
#elif defined(OS_MACOSX)
    IPC_MESSAGE_HANDLER(GpuHostMsg_AcceleratedSurfaceSetIOSurface,
                        OnAcceleratedSurfaceSetIOSurface)
    IPC_MESSAGE_HANDLER(GpuHostMsg_AcceleratedSurfaceBuffersSwapped,
                        OnAcceleratedSurfaceBuffersSwapped)
#endif
    IPC_MESSAGE_UNHANDLED_ERROR()
  IPC_END_MESSAGE_MAP()
}

void GpuProcessHost::OnChannelEstablished(
    const IPC::ChannelHandle& channel_handle,
    const GPUInfo& gpu_info) {
  const ChannelRequest& request = sent_requests_.front();
  SendEstablishChannelReply(channel_handle, gpu_info, request.filter);
  sent_requests_.pop();
  gpu_info_ = gpu_info;
  child_process_logging::SetGpuInfo(gpu_info);
}

void GpuProcessHost::OnSynchronizeReply() {
  const SynchronizationRequest& request =
      queued_synchronization_replies_.front();
  SendSynchronizationReply(request.reply, request.filter);
  queued_synchronization_replies_.pop();
}

void GpuProcessHost::OnGraphicsInfoCollected(const GPUInfo& gpu_info) {
  gpu_info_ = gpu_info;
}

#if defined(OS_LINUX)

namespace {

void SendDelayedReply(IPC::Message* reply_msg) {
  GpuProcessHost::Get()->Send(reply_msg);
}

void GetViewXIDDispatcher(gfx::NativeViewId id, IPC::Message* reply_msg) {
  XID xid;

  GtkNativeViewManager* manager = Singleton<GtkNativeViewManager>::get();
  if (!manager->GetPermanentXIDForId(&xid, id)) {
    DLOG(ERROR) << "Can't find XID for view id " << id;
    xid = 0;
  }

  GpuHostMsg_GetViewXID::WriteReplyParams(reply_msg, xid);

  // Have to reply from IO thread.
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      NewRunnableFunction(&SendDelayedReply, reply_msg));
}

} // namespace

void GpuProcessHost::OnGetViewXID(gfx::NativeViewId id,
                                  IPC::Message *reply_msg) {
  // Have to request a permanent XID from UI thread.
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      NewRunnableFunction(&GetViewXIDDispatcher, id, reply_msg));
}

#elif defined(OS_MACOSX)

namespace {

class SetIOSurfaceDispatcher : public Task {
 public:
  SetIOSurfaceDispatcher(
      const GpuHostMsg_AcceleratedSurfaceSetIOSurface_Params& params)
      : params_(params) {
  }

  void Run() {
    RenderViewHost* host = RenderViewHost::FromID(params_.renderer_id,
                                                  params_.render_view_id);
    if (!host)
      return;
    RenderWidgetHostView* view = host->view();
    if (!view)
      return;
    view->AcceleratedSurfaceSetIOSurface(params_.window,
                                         params_.width,
                                         params_.height,
                                         params_.identifier);
  }

 private:
  GpuHostMsg_AcceleratedSurfaceSetIOSurface_Params params_;

  DISALLOW_COPY_AND_ASSIGN(SetIOSurfaceDispatcher);
};

}  // namespace

void GpuProcessHost::OnAcceleratedSurfaceSetIOSurface(
    const GpuHostMsg_AcceleratedSurfaceSetIOSurface_Params& params) {
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      new SetIOSurfaceDispatcher(params));
}

namespace {

class BuffersSwappedDispatcher : public Task {
 public:
  BuffersSwappedDispatcher(
      int32 renderer_id,
      int32 render_view_id,
      gfx::PluginWindowHandle window,
      uint64 surface_id)
      : renderer_id_(renderer_id),
        render_view_id_(render_view_id),
        window_(window),
        surface_id_(surface_id) {
  }

  void Run() {
    RenderViewHost* host = RenderViewHost::FromID(renderer_id_,
                                                  render_view_id_);
    if (!host)
      return;
    RenderWidgetHostView* view = host->view();
    if (!view)
      return;
    view->AcceleratedSurfaceBuffersSwapped(window_, surface_id_);
  }

 private:
  int32 renderer_id_;
  int32 render_view_id_;
  gfx::PluginWindowHandle window_;
  uint64 surface_id_;

  DISALLOW_COPY_AND_ASSIGN(BuffersSwappedDispatcher);
};

}  // namespace

void GpuProcessHost::OnAcceleratedSurfaceBuffersSwapped(
    int32 renderer_id,
    int32 render_view_id,
    gfx::PluginWindowHandle window,
    uint64 surface_id) {
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      new BuffersSwappedDispatcher(
          renderer_id, render_view_id, window, surface_id));
}
#endif

void GpuProcessHost::SendEstablishChannelReply(
    const IPC::ChannelHandle& channel,
    const GPUInfo& gpu_info,
    ResourceMessageFilter* filter) {
  ViewMsg_GpuChannelEstablished* message =
      new ViewMsg_GpuChannelEstablished(channel, gpu_info);
  // If the renderer process is performing synchronous initialization,
  // it needs to handle this message before receiving the reply for
  // the synchronous ViewHostMsg_SynchronizeGpu message.
  message->set_unblock(true);
  filter->Send(message);
}

// Sends the response for synchronization request to the renderer.
void GpuProcessHost::SendSynchronizationReply(
    IPC::Message* reply,
    ResourceMessageFilter* filter) {
  filter->Send(reply);
}

URLRequestContext* GpuProcessHost::GetRequestContext(
    uint32 request_id,
    const ViewHostMsg_Resource_Request& request_data) {
  return NULL;
}

bool GpuProcessHost::CanShutdown() {
  return true;
}

void GpuProcessHost::OnProcessCrashed() {
  // TODO(alokp): Update gpu process crash rate.
  BrowserChildProcessHost::OnProcessCrashed();
}