// Copyright (c) 2012 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/installer/util/product.h"

#include <algorithm>

#include "base/command_line.h"
#include "base/logging.h"
#include "base/process/launch.h"
#include "base/win/registry.h"
#include "chrome/installer/util/chrome_app_host_operations.h"
#include "chrome/installer/util/chrome_binaries_operations.h"
#include "chrome/installer/util/chrome_browser_operations.h"
#include "chrome/installer/util/chrome_browser_sxs_operations.h"
#include "chrome/installer/util/chrome_frame_operations.h"
#include "chrome/installer/util/google_update_constants.h"
#include "chrome/installer/util/helper.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/master_preferences.h"
#include "chrome/installer/util/master_preferences_constants.h"
#include "chrome/installer/util/product_operations.h"

using base::win::RegKey;
using installer::MasterPreferences;

namespace installer {

Product::Product(BrowserDistribution* distribution)
    : distribution_(distribution) {
  switch (distribution->GetType()) {
    case BrowserDistribution::CHROME_BROWSER:
      operations_.reset(InstallUtil::IsChromeSxSProcess() ?
          new ChromeBrowserSxSOperations() :
          new ChromeBrowserOperations());
      break;
    case BrowserDistribution::CHROME_FRAME:
      operations_.reset(new ChromeFrameOperations());
      break;
    case BrowserDistribution::CHROME_APP_HOST:
      operations_.reset(new ChromeAppHostOperations());
      break;
    case BrowserDistribution::CHROME_BINARIES:
      operations_.reset(new ChromeBinariesOperations());
      break;
    default:
      NOTREACHED() << "Unsupported BrowserDistribution::Type: "
                   << distribution->GetType();
  }
}

Product::~Product() {
}

void Product::InitializeFromPreferences(const MasterPreferences& prefs) {
  operations_->ReadOptions(prefs, &options_);
}

void Product::InitializeFromUninstallCommand(
    const CommandLine& uninstall_command) {
  operations_->ReadOptions(uninstall_command, &options_);
}

bool Product::LaunchChrome(const base::FilePath& application_path) const {
  bool success = !application_path.empty();
  if (success) {
    CommandLine cmd(application_path.Append(installer::kChromeExe));
    success = base::LaunchProcess(cmd, base::LaunchOptions(), NULL);
  }
  return success;
}

bool Product::LaunchChromeAndWait(const base::FilePath& application_path,
                                  const CommandLine& options,
                                  int32* exit_code) const {
  if (application_path.empty())
    return false;

  CommandLine cmd(application_path.Append(installer::kChromeExe));
  cmd.AppendArguments(options, false);

  bool success = false;
  STARTUPINFOW si = { sizeof(si) };
  PROCESS_INFORMATION pi = {0};
  // Create a writable copy of the command line string, since CreateProcess may
  // modify the string (insert \0 to separate the program from the arguments).
  std::wstring writable_command_line_string(cmd.GetCommandLineString());
  if (!::CreateProcess(cmd.GetProgram().value().c_str(),
                       &writable_command_line_string[0],
                       NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL,
                       &si, &pi)) {
    PLOG(ERROR) << "Failed to launch: " << cmd.GetCommandLineString();
  } else {
    ::CloseHandle(pi.hThread);

    DWORD ret = ::WaitForSingleObject(pi.hProcess, INFINITE);
    DLOG_IF(ERROR, ret != WAIT_OBJECT_0)
        << "Unexpected return value from WaitForSingleObject: " << ret;
    if (::GetExitCodeProcess(pi.hProcess, &ret)) {
      DCHECK(ret != STILL_ACTIVE);
      success = true;
      if (exit_code)
        *exit_code = ret;
    } else {
      PLOG(ERROR) << "GetExitCodeProcess failed";
    }

    ::CloseHandle(pi.hProcess);
  }

  return success;
}

bool Product::SetMsiMarker(bool system_install, bool set) const {
  HKEY reg_root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
  RegKey client_state_key;
  LONG result = client_state_key.Open(reg_root,
                                      distribution_->GetStateKey().c_str(),
                                      KEY_READ | KEY_WRITE | KEY_WOW64_32KEY);
  if (result == ERROR_SUCCESS) {
    result = client_state_key.WriteValue(google_update::kRegMSIField,
                                         set ? 1 : 0);
  }

  LOG_IF(ERROR, result != ERROR_SUCCESS) << "Failed to Open or Write MSI value"
      "to client state key. error: " << result;

  return (result == ERROR_SUCCESS);
}

bool Product::ShouldCreateUninstallEntry() const {
  return operations_->ShouldCreateUninstallEntry(options_);
}

void Product::AddKeyFiles(std::vector<base::FilePath>* key_files) const {
  operations_->AddKeyFiles(options_, key_files);
}

void Product::AddComDllList(std::vector<base::FilePath>* com_dll_list) const {
  operations_->AddComDllList(options_, com_dll_list);
}

void Product::AppendProductFlags(CommandLine* command_line) const {
  operations_->AppendProductFlags(options_, command_line);
}

void Product::AppendRenameFlags(CommandLine* command_line) const {
  operations_->AppendRenameFlags(options_, command_line);
}

bool Product::SetChannelFlags(bool set, ChannelInfo* channel_info) const {
  return operations_->SetChannelFlags(options_, set, channel_info);
}

void Product::AddDefaultShortcutProperties(
    const base::FilePath& target_exe,
    ShellUtil::ShortcutProperties* properties) const {
  return operations_->AddDefaultShortcutProperties(
      distribution_, target_exe, properties);
}

void Product::LaunchUserExperiment(const base::FilePath& setup_path,
                                   InstallStatus status,
                                   bool system_level) const {
  if (distribution_->HasUserExperiments()) {
    VLOG(1) << "LaunchUserExperiment status: " << status << " product: "
            << distribution_->GetDisplayName()
            << " system_level: " << system_level;
    operations_->LaunchUserExperiment(
        setup_path, options_, status, system_level);
  }
}

}  // namespace installer