// 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/service/service_utility_process_host.h"

#include "app/app_switches.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/message_loop.h"
#include "base/message_loop_proxy.h"
#include "base/scoped_temp_dir.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/utility_messages.h"
#include "gfx/rect.h"
#include "ipc/ipc_switches.h"
#include "printing/native_metafile.h"
#include "printing/page_range.h"

ServiceUtilityProcessHost::ServiceUtilityProcessHost(
    Client* client, base::MessageLoopProxy* client_message_loop_proxy)
        : ServiceChildProcessHost(ChildProcessInfo::UTILITY_PROCESS),
          client_(client),
          client_message_loop_proxy_(client_message_loop_proxy),
          waiting_for_reply_(false) {
}

ServiceUtilityProcessHost::~ServiceUtilityProcessHost() {
}

bool ServiceUtilityProcessHost::StartRenderPDFPagesToMetafile(
    const FilePath& pdf_path,
    const gfx::Rect& render_area,
    int render_dpi,
    const std::vector<printing::PageRange>& page_ranges) {
#if !defined(OS_WIN)
  // This is only implemented on Windows (because currently it is only needed
  // on Windows). Will add implementations on other platforms when needed.
  NOTIMPLEMENTED();
  return false;
#else  // !defined(OS_WIN)
  scratch_metafile_dir_.reset(new ScopedTempDir);
  if (!scratch_metafile_dir_->CreateUniqueTempDir())
    return false;
  if (!file_util::CreateTemporaryFileInDir(scratch_metafile_dir_->path(),
                                           &metafile_path_)) {
    return false;
  }

  if (!StartProcess(scratch_metafile_dir_->path()))
    return false;

  ScopedHandle pdf_file(
      ::CreateFile(pdf_path.value().c_str(),
                   GENERIC_READ,
                   FILE_SHARE_READ | FILE_SHARE_WRITE,
                   NULL,
                   OPEN_EXISTING,
                   FILE_ATTRIBUTE_NORMAL,
                   NULL));
  if (pdf_file == INVALID_HANDLE_VALUE)
    return false;
  HANDLE pdf_file_in_utility_process = NULL;
  ::DuplicateHandle(::GetCurrentProcess(), pdf_file, handle(),
                    &pdf_file_in_utility_process, 0, false,
                    DUPLICATE_SAME_ACCESS);
  if (!pdf_file_in_utility_process)
    return false;
  waiting_for_reply_ = true;
  return SendOnChannel(
      new UtilityMsg_RenderPDFPagesToMetafile(pdf_file_in_utility_process,
                                              metafile_path_,
                                              render_area,
                                              render_dpi,
                                              page_ranges));
#endif  // !defined(OS_WIN)
}

bool ServiceUtilityProcessHost::StartProcess(const FilePath& exposed_dir) {
  // Name must be set or metrics_service will crash in any test which
  // launches a UtilityProcessHost.
  set_name(L"utility process");

  if (!CreateChannel())
    return false;

  FilePath exe_path = GetUtilityProcessCmd();
  if (exe_path.empty()) {
    NOTREACHED() << "Unable to get utility process binary name.";
    return false;
  }

  CommandLine cmd_line(exe_path);
  cmd_line.AppendSwitchASCII(switches::kProcessType, switches::kUtilityProcess);
  cmd_line.AppendSwitchASCII(switches::kProcessChannelID, channel_id());
  cmd_line.AppendSwitch(switches::kLang);

  return Launch(&cmd_line, exposed_dir);
}

FilePath ServiceUtilityProcessHost::GetUtilityProcessCmd() {
  return GetChildPath(true);
}

void ServiceUtilityProcessHost::OnChildDied() {
  if (waiting_for_reply_) {
    // If we are yet to receive a reply then notify the client that the
    // child died.
    client_message_loop_proxy_->PostTask(
        FROM_HERE,
        NewRunnableMethod(client_.get(), &Client::OnChildDied));
  }
  // The base class implementation will delete |this|.
  ServiceChildProcessHost::OnChildDied();
}

void ServiceUtilityProcessHost::OnMessageReceived(const IPC::Message& message) {
  bool msg_is_ok = false;
  IPC_BEGIN_MESSAGE_MAP_EX(ServiceUtilityProcessHost, message, msg_is_ok)
#if defined(OS_WIN)  // This hack is Windows-specific.
    IPC_MESSAGE_HANDLER(UtilityHostMsg_PreCacheFont, OnPreCacheFont)
#endif
    IPC_MESSAGE_HANDLER(UtilityHostMsg_RenderPDFPagesToMetafile_Succeeded,
                        OnRenderPDFPagesToMetafileSucceeded)
    IPC_MESSAGE_UNHANDLED(msg_is_ok__ = MessageForClient(message))
  IPC_END_MESSAGE_MAP_EX()
}

bool ServiceUtilityProcessHost::MessageForClient(const IPC::Message& message) {
  DCHECK(waiting_for_reply_);
  bool ret = client_message_loop_proxy_->PostTask(
      FROM_HERE,
      NewRunnableMethod(client_.get(), &Client::OnMessageReceived,
                        message));
  waiting_for_reply_ = false;
  return ret;
}

#if defined(OS_WIN)  // This hack is Windows-specific.
void ServiceUtilityProcessHost::OnPreCacheFont(LOGFONT font) {
  PreCacheFont(font);
}
#endif  // OS_WIN

void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafileSucceeded(
    int highest_rendered_page_number) {
  DCHECK(waiting_for_reply_);
  // If the metafile was successfully created, we need to take our hands off the
  // scratch metafile directory. The client will delete it when it is done with
  // metafile.
  scratch_metafile_dir_->Take();
  client_message_loop_proxy_->PostTask(
      FROM_HERE,
      NewRunnableMethod(client_.get(),
                        &Client::MetafileAvailable,
                        metafile_path_,
                        highest_rendered_page_number));
  waiting_for_reply_ = false;
}

void ServiceUtilityProcessHost::Client::OnMessageReceived(
    const IPC::Message& message) {
  IPC_BEGIN_MESSAGE_MAP(ServiceUtilityProcessHost, message)
    IPC_MESSAGE_HANDLER(UtilityHostMsg_RenderPDFPagesToMetafile_Failed,
                        Client::OnRenderPDFPagesToMetafileFailed)
  IPC_END_MESSAGE_MAP_EX()
}

void ServiceUtilityProcessHost::Client::MetafileAvailable(
    const FilePath& metafile_path,
    int highest_rendered_page_number) {
  // The metafile was created in a temp folder which needs to get deleted after
  // we have processed it.
  ScopedTempDir scratch_metafile_dir;
  scratch_metafile_dir.Set(metafile_path.DirName());
#if defined(OS_WIN)
  printing::NativeMetafile metafile;
  if (!metafile.CreateFromFile(metafile_path)) {
    OnRenderPDFPagesToMetafileFailed();
  } else {
    OnRenderPDFPagesToMetafileSucceeded(metafile, highest_rendered_page_number);
    // Close it so that ScopedTempDir can delete the folder.
    metafile.CloseEmf();
  }
#endif  // defined(OS_WIN)
}