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

#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/first_run/first_run_dialog.h"
#include "chrome/browser/first_run/first_run_import_observer.h"
#include "chrome/browser/importer/external_process_importer_host.h"
#include "chrome/browser/importer/importer_host.h"
#include "chrome/browser/importer/importer_list.h"
#include "chrome/browser/importer/importer_progress_dialog.h"
#include "chrome/browser/importer/importer_progress_observer.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/process_singleton.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/search_engines/template_url_model.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/installer/util/master_preferences.h"
#include "chrome/installer/util/master_preferences_constants.h"
#include "chrome/installer/util/util_constants.h"
#include "googleurl/src/gurl.h"

#if defined(OS_WIN)
// TODO(port): move more code in back from the first_run_win.cc module.
#include "chrome/installer/util/google_update_settings.h"
#include "chrome/installer/util/install_util.h"
#endif

namespace {

// The kSentinelFile file absence will tell us it is a first run.
const char kSentinelFile[] = "First Run";

FilePath GetDefaultPrefFilePath(bool create_profile_dir,
                                const FilePath& user_data_dir) {
  FilePath default_pref_dir =
      ProfileManager::GetDefaultProfileDir(user_data_dir);
  if (create_profile_dir) {
    if (!file_util::PathExists(default_pref_dir)) {
      if (!file_util::CreateDirectory(default_pref_dir))
        return FilePath();
    }
  }
  return ProfileManager::GetProfilePrefsPath(default_pref_dir);
}

}  // namespace

// FirstRun -------------------------------------------------------------------

FirstRun::FirstRunState FirstRun::first_run_ = FIRST_RUN_UNKNOWN;

FirstRun::MasterPrefs::MasterPrefs()
    : ping_delay(0),
      homepage_defined(false),
      do_import_items(0),
      dont_import_items(0),
      run_search_engine_experiment(false),
      randomize_search_engine_experiment(false),
      make_chrome_default(false) {
}

FirstRun::MasterPrefs::~MasterPrefs() {}

// TODO(port): Import switches need to be ported to both Mac and Linux. Not all
// import switches here are implemented for Linux. None are implemented for Mac
// (as this function will not be called on Mac).
int FirstRun::ImportNow(Profile* profile, const CommandLine& cmdline) {
  int return_code = true;
  if (cmdline.HasSwitch(switches::kImportFromFile)) {
    // Silently import preset bookmarks from file.
    // This is an OEM scenario.
    return_code = ImportFromFile(profile, cmdline);
  }
  if (cmdline.HasSwitch(switches::kImport)) {
#if defined(OS_WIN)
    return_code = ImportFromBrowser(profile, cmdline);
#else
    NOTIMPLEMENTED();
#endif
  }
  return return_code;
}

// static
bool FirstRun::ProcessMasterPreferences(const FilePath& user_data_dir,
                                        MasterPrefs* out_prefs) {
  DCHECK(!user_data_dir.empty());

  // The standard location of the master prefs is next to the chrome binary.
  FilePath master_prefs;
  if (!PathService::Get(base::DIR_EXE, &master_prefs))
    return true;
  master_prefs = master_prefs.AppendASCII(installer::kDefaultMasterPrefs);

  installer::MasterPreferences prefs(master_prefs);
  if (!prefs.read_from_file())
    return true;

  out_prefs->new_tabs = prefs.GetFirstRunTabs();

  bool value = false;

#if defined(OS_WIN)
  // RLZ is currently a Windows-only phenomenon.  When it comes to the Mac/
  // Linux, enable it here.
  if (!prefs.GetInt(installer::master_preferences::kDistroPingDelay,
                    &out_prefs->ping_delay)) {
    // 90 seconds is the default that we want to use in case master
    // preferences is missing, corrupt or ping_delay is missing.
    out_prefs->ping_delay = 90;
  }

  if (prefs.GetBool(installer::master_preferences::kRequireEula, &value) &&
      value) {
    // Show the post-installation EULA. This is done by setup.exe and the
    // result determines if we continue or not. We wait here until the user
    // dismisses the dialog.

    // The actual eula text is in a resource in chrome. We extract it to
    // a text file so setup.exe can use it as an inner frame.
    FilePath inner_html;
    if (WriteEULAtoTempFile(&inner_html)) {
      int retcode = 0;
      if (!LaunchSetupWithParam(installer::switches::kShowEula,
                                inner_html.value(), &retcode) ||
          (retcode == installer::EULA_REJECTED)) {
        LOG(WARNING) << "EULA rejected. Fast exit.";
        ::ExitProcess(1);
      }
      if (retcode == installer::EULA_ACCEPTED) {
        VLOG(1) << "EULA : no collection";
        GoogleUpdateSettings::SetCollectStatsConsent(false);
      } else if (retcode == installer::EULA_ACCEPTED_OPT_IN) {
        VLOG(1) << "EULA : collection consent";
        GoogleUpdateSettings::SetCollectStatsConsent(true);
      }
    }
  }
#endif

  if (prefs.GetBool(installer::master_preferences::kAltFirstRunBubble,
                    &value) && value) {
    FirstRun::SetOEMFirstRunBubblePref();
  }

  FilePath user_prefs = GetDefaultPrefFilePath(true, user_data_dir);
  if (user_prefs.empty())
    return true;

  // The master prefs are regular prefs so we can just copy the file
  // to the default place and they just work.
  if (!file_util::CopyFile(master_prefs, user_prefs))
    return true;

#if defined(OS_WIN)
  DictionaryValue* extensions = 0;
  if (prefs.GetExtensionsBlock(&extensions)) {
    VLOG(1) << "Extensions block found in master preferences";
    DoDelayedInstallExtensions();
  }
#endif

  if (prefs.GetBool(installer::master_preferences::kDistroImportSearchPref,
                    &value)) {
    if (value) {
      out_prefs->do_import_items |= importer::SEARCH_ENGINES;
    } else {
      out_prefs->dont_import_items |= importer::SEARCH_ENGINES;
    }
  }

  // Check to see if search engine logos should be randomized.
  if (prefs.GetBool(
          installer::master_preferences::
              kSearchEngineExperimentRandomizePref,
          &value) && value) {
    out_prefs->randomize_search_engine_experiment = true;
  }

  // If we're suppressing the first-run bubble, set that preference now.
  // Otherwise, wait until the user has completed first run to set it, so the
  // user is guaranteed to see the bubble iff he or she has completed the first
  // run process.
  if (prefs.GetBool(
          installer::master_preferences::kDistroSuppressFirstRunBubble,
          &value) && value)
    FirstRun::SetShowFirstRunBubblePref(false);

  if (prefs.GetBool(
          installer::master_preferences::kDistroImportHistoryPref,
          &value)) {
    if (value) {
      out_prefs->do_import_items |= importer::HISTORY;
    } else {
      out_prefs->dont_import_items |= importer::HISTORY;
    }
  }

  std::string not_used;
  out_prefs->homepage_defined = prefs.GetString(prefs::kHomePage, &not_used);

  if (prefs.GetBool(
          installer::master_preferences::kDistroImportHomePagePref,
          &value)) {
    if (value) {
      out_prefs->do_import_items |= importer::HOME_PAGE;
    } else {
      out_prefs->dont_import_items |= importer::HOME_PAGE;
    }
  }

  // Bookmarks are never imported unless specifically turned on.
  if (prefs.GetBool(
          installer::master_preferences::kDistroImportBookmarksPref,
          &value) && value) {
    out_prefs->do_import_items |= importer::FAVORITES;
  }

  if (prefs.GetBool(
          installer::master_preferences::kMakeChromeDefaultForUser,
          &value) && value) {
    out_prefs->make_chrome_default = true;
  }

  // TODO(mirandac): Refactor skip-first-run-ui process into regular first run
  // import process.  http://crbug.com/49647
  // Note we are skipping all other master preferences if skip-first-run-ui
  // is *not* specified. (That is, we continue only if skipping first run ui.)
  if (!prefs.GetBool(
          installer::master_preferences::kDistroSkipFirstRunPref,
          &value) || !value) {
    return true;
  }

#if !defined(OS_WIN)
  // From here on we won't show first run so we need to do the work to show the
  // bubble anyway, unless it's already been explicitly suppressed.
  FirstRun::SetShowFirstRunBubblePref(true);
#endif

  // We need to be able to create the first run sentinel or else we cannot
  // proceed because ImportSettings will launch the importer process which
  // would end up here if the sentinel is not present.
  if (!FirstRun::CreateSentinel())
    return false;

  if (prefs.GetBool(installer::master_preferences::kDistroShowWelcomePage,
                    &value) && value) {
    FirstRun::SetShowWelcomePagePref();
  }

  std::string import_bookmarks_path;
  prefs.GetString(
      installer::master_preferences::kDistroImportBookmarksFromFilePref,
      &import_bookmarks_path);

#if defined(OS_WIN)
  if (!IsOrganicFirstRun()) {
    // If search engines aren't explicitly imported, don't import.
    if (!(out_prefs->do_import_items & importer::SEARCH_ENGINES)) {
      out_prefs->dont_import_items |= importer::SEARCH_ENGINES;
    }
    // If home page isn't explicitly imported, don't import.
    if (!(out_prefs->do_import_items & importer::HOME_PAGE)) {
      out_prefs->dont_import_items |= importer::HOME_PAGE;
    }
    // If history isn't explicitly forbidden, do import.
    if (!(out_prefs->dont_import_items & importer::HISTORY)) {
      out_prefs->do_import_items |= importer::HISTORY;
    }
  }

  if (out_prefs->do_import_items || !import_bookmarks_path.empty()) {
    // There is something to import from the default browser. This launches
    // the importer process and blocks until done or until it fails.
    scoped_refptr<ImporterList> importer_list(new ImporterList);
    importer_list->DetectSourceProfilesHack();
    if (!FirstRun::ImportSettings(NULL,
          importer_list->GetSourceProfileAt(0).importer_type,
          out_prefs->do_import_items,
          FilePath::FromWStringHack(UTF8ToWide(import_bookmarks_path)),
          true, NULL)) {
      LOG(WARNING) << "silent import failed";
    }
  }
#else
  if (!import_bookmarks_path.empty()) {
    // There are bookmarks to import from a file.
    FilePath path = FilePath::FromWStringHack(UTF8ToWide(
        import_bookmarks_path));
    if (!FirstRun::ImportBookmarks(path)) {
      LOG(WARNING) << "silent bookmark import failed";
    }
  }
#endif

  // Even on the first run we only allow for the user choice to take effect if
  // no policy has been set by the admin.
  if (!g_browser_process->local_state()->IsManagedPreference(
          prefs::kDefaultBrowserSettingEnabled)) {
    if (prefs.GetBool(
            installer::master_preferences::kMakeChromeDefaultForUser,
            &value) && value) {
      ShellIntegration::SetAsDefaultBrowser();
    }
  } else {
    if (g_browser_process->local_state()->GetBoolean(
            prefs::kDefaultBrowserSettingEnabled)) {
      ShellIntegration::SetAsDefaultBrowser();
    }
  }

  return false;
}

// static
bool FirstRun::IsChromeFirstRun() {
  if (first_run_ != FIRST_RUN_UNKNOWN)
    return first_run_ == FIRST_RUN_TRUE;

  FilePath first_run_sentinel;
  if (!GetFirstRunSentinelFilePath(&first_run_sentinel) ||
      file_util::PathExists(first_run_sentinel)) {
    first_run_ = FIRST_RUN_FALSE;
    return false;
  }
  first_run_ = FIRST_RUN_TRUE;
  return true;
}

// static
bool FirstRun::RemoveSentinel() {
  FilePath first_run_sentinel;
  if (!GetFirstRunSentinelFilePath(&first_run_sentinel))
    return false;
  return file_util::Delete(first_run_sentinel, false);
}

// static
bool FirstRun::CreateSentinel() {
  FilePath first_run_sentinel;
  if (!GetFirstRunSentinelFilePath(&first_run_sentinel))
    return false;
  return file_util::WriteFile(first_run_sentinel, "", 0) != -1;
}

// static
bool FirstRun::SetShowFirstRunBubblePref(bool show_bubble) {
  PrefService* local_state = g_browser_process->local_state();
  if (!local_state)
    return false;
  if (!local_state->FindPreference(prefs::kShouldShowFirstRunBubble)) {
    local_state->RegisterBooleanPref(prefs::kShouldShowFirstRunBubble, false);
    local_state->SetBoolean(prefs::kShouldShowFirstRunBubble, show_bubble);
  }
  return true;
}

// static
bool FirstRun::SetShowWelcomePagePref() {
  PrefService* local_state = g_browser_process->local_state();
  if (!local_state)
    return false;
  if (!local_state->FindPreference(prefs::kShouldShowWelcomePage)) {
    local_state->RegisterBooleanPref(prefs::kShouldShowWelcomePage, false);
    local_state->SetBoolean(prefs::kShouldShowWelcomePage, true);
  }
  return true;
}

// static
bool FirstRun::SetPersonalDataManagerFirstRunPref() {
  PrefService* local_state = g_browser_process->local_state();
  if (!local_state)
    return false;
  if (!local_state->FindPreference(
          prefs::kAutofillPersonalDataManagerFirstRun)) {
    local_state->RegisterBooleanPref(
        prefs::kAutofillPersonalDataManagerFirstRun, false);
    local_state->SetBoolean(prefs::kAutofillPersonalDataManagerFirstRun, true);
  }
  return true;
}

// static
bool FirstRun::SearchEngineSelectorDisallowed() {
  // For now, the only case in which the search engine dialog should never be
  // shown is if the locale is Russia.
  std::string locale = g_browser_process->GetApplicationLocale();
  return (locale == "ru");
}

// static
bool FirstRun::SetOEMFirstRunBubblePref() {
  PrefService* local_state = g_browser_process->local_state();
  if (!local_state)
    return false;
  if (!local_state->FindPreference(prefs::kShouldUseOEMFirstRunBubble)) {
    local_state->RegisterBooleanPref(prefs::kShouldUseOEMFirstRunBubble,
                                     false);
    local_state->SetBoolean(prefs::kShouldUseOEMFirstRunBubble, true);
  }
  return true;
}

// static
bool FirstRun::SetMinimalFirstRunBubblePref() {
  PrefService* local_state = g_browser_process->local_state();
  if (!local_state)
    return false;
  if (!local_state->FindPreference(prefs::kShouldUseMinimalFirstRunBubble)) {
    local_state->RegisterBooleanPref(prefs::kShouldUseMinimalFirstRunBubble,
                                     false);
    local_state->SetBoolean(prefs::kShouldUseMinimalFirstRunBubble, true);
  }
  return true;
}

// static
int FirstRun::ImportFromFile(Profile* profile, const CommandLine& cmdline) {
  FilePath file_path = cmdline.GetSwitchValuePath(switches::kImportFromFile);
  if (file_path.empty()) {
    NOTREACHED();
    return false;
  }
  scoped_refptr<ImporterHost> importer_host(new ImporterHost);
  importer_host->set_headless();

  importer::SourceProfile source_profile;
  source_profile.importer_type = importer::BOOKMARKS_HTML;
  source_profile.source_path = file_path;

  FirstRunImportObserver importer_observer;
  importer::ShowImportProgressDialog(NULL,
                                     importer::FAVORITES,
                                     importer_host,
                                     &importer_observer,
                                     source_profile,
                                     profile,
                                     true);

  importer_observer.RunLoop();
  return importer_observer.import_result();
}

// static
bool FirstRun::GetFirstRunSentinelFilePath(FilePath* path) {
  FilePath first_run_sentinel;

#if defined(OS_WIN)
  FilePath exe_path;
  if (!PathService::Get(base::DIR_EXE, &exe_path))
    return false;
  if (InstallUtil::IsPerUserInstall(exe_path.value().c_str())) {
    first_run_sentinel = exe_path;
  } else {
    if (!PathService::Get(chrome::DIR_USER_DATA, &first_run_sentinel))
      return false;
  }
#else
  if (!PathService::Get(chrome::DIR_USER_DATA, &first_run_sentinel))
    return false;
#endif

  *path = first_run_sentinel.AppendASCII(kSentinelFile);
  return true;
}

// static
void FirstRun::AutoImport(
    Profile* profile,
    bool homepage_defined,
    int import_items,
    int dont_import_items,
    bool search_engine_experiment,
    bool randomize_search_engine_experiment,
    bool make_chrome_default,
    ProcessSingleton* process_singleton) {
  // We need to avoid dispatching new tabs when we are importing because
  // that will lead to data corruption or a crash. Because there is no UI for
  // the import process, we pass NULL as the window to bring to the foreground
  // when a CopyData message comes in; this causes the message to be silently
  // discarded, which is the correct behavior during the import process.
  process_singleton->Lock(NULL);

  PlatformSetup();

  FilePath local_state_path;
  PathService::Get(chrome::FILE_LOCAL_STATE, &local_state_path);
  bool local_state_file_exists = file_util::PathExists(local_state_path);

  scoped_refptr<ImporterHost> importer_host;
  // TODO(csilv,mirandac): Out-of-process import has only been qualified on
  // MacOS X, so we will only use it on that platform since it is required.
  // Remove this conditional logic once oop import is qualified for
  // Linux/Windows. http://crbug.com/22142
#if defined(OS_MACOSX)
  importer_host = new ExternalProcessImporterHost;
#else
  importer_host = new ImporterHost;
#endif

  scoped_refptr<ImporterList> importer_list(new ImporterList);
  importer_list->DetectSourceProfilesHack();

  // Do import if there is an available profile for us to import.
  if (importer_list->count() > 0) {
    // Don't show the warning dialog if import fails.
    importer_host->set_headless();
    int items = 0;

    // History is always imported unless turned off in master_preferences.
    if (!(dont_import_items & importer::HISTORY))
      items = items | importer::HISTORY;
    // Home page is imported in organic builds only unless turned off or
    // defined in master_preferences.
    if (IsOrganicFirstRun()) {
      if (!(dont_import_items & importer::HOME_PAGE) && !homepage_defined)
        items = items | importer::HOME_PAGE;
    } else {
      if (import_items & importer::HOME_PAGE)
        items = items | importer::HOME_PAGE;
    }
    // Search engines are only imported in certain builds unless overridden
    // in master_preferences. Search engines are not imported automatically
    // if the user already has a user preferences directory.
    if (IsOrganicFirstRun()) {
      if (!(dont_import_items & importer::SEARCH_ENGINES) &&
          !local_state_file_exists) {
        items = items | importer::SEARCH_ENGINES;
      }
    } else if (import_items & importer::SEARCH_ENGINES) {
        items = items | importer::SEARCH_ENGINES;
    }

    // Bookmarks are never imported, unless turned on in master_preferences.
    if (import_items & importer::FAVORITES)
      items = items | importer::FAVORITES;

    ImportSettings(profile, importer_host, importer_list, items);
  }

  UserMetrics::RecordAction(UserMetricsAction("FirstRunDef_Accept"));

  // Launch the search engine dialog only for certain builds, and only if the
  // user has not already set preferences.
  if (IsOrganicFirstRun() && !local_state_file_exists) {
    // The home page string may be set in the preferences, but the user should
    // initially use Chrome with the NTP as home page in organic builds.
    profile->GetPrefs()->SetBoolean(prefs::kHomePageIsNewTabPage, true);
    first_run::ShowFirstRunDialog(profile, randomize_search_engine_experiment);
  }

  if (make_chrome_default)
    ShellIntegration::SetAsDefaultBrowser();

  // Don't display the minimal bubble if there is no default search provider.
  TemplateURLModel* search_engines_model = profile->GetTemplateURLModel();
  if (search_engines_model &&
      search_engines_model->GetDefaultSearchProvider()) {
    FirstRun::SetShowFirstRunBubblePref(true);
    // Set the first run bubble to minimal.
    FirstRun::SetMinimalFirstRunBubblePref();
  }
  FirstRun::SetShowWelcomePagePref();
  FirstRun::SetPersonalDataManagerFirstRunPref();

  process_singleton->Unlock();
  FirstRun::CreateSentinel();
}

#if defined(OS_POSIX)
namespace {

// This class acts as an observer for the ImporterProgressObserver::ImportEnded
// callback. When the import process is started, certain errors may cause
// ImportEnded() to be called synchronously, but the typical case is that
// ImportEnded() is called asynchronously. Thus we have to handle both cases.
class ImportEndedObserver : public importer::ImporterProgressObserver {
 public:
  ImportEndedObserver() : ended_(false),
                          should_quit_message_loop_(false) {}
  virtual ~ImportEndedObserver() {}

  // importer::ImporterProgressObserver:
  virtual void ImportStarted() OVERRIDE {}
  virtual void ImportItemStarted(importer::ImportItem item) OVERRIDE {}
  virtual void ImportItemEnded(importer::ImportItem item) OVERRIDE {}
  virtual void ImportEnded() OVERRIDE {
    ended_ = true;
    if (should_quit_message_loop_)
      MessageLoop::current()->Quit();
  }

  void set_should_quit_message_loop() {
    should_quit_message_loop_ = true;
  }

  bool ended() {
    return ended_;
  }

 private:
  // Set if the import has ended.
  bool ended_;

  // Set by the client (via set_should_quit_message_loop) if, when the import
  // ends, this class should quit the message loop.
  bool should_quit_message_loop_;
};

}  // namespace

// static
bool FirstRun::ImportSettings(Profile* profile,
                              scoped_refptr<ImporterHost> importer_host,
                              scoped_refptr<ImporterList> importer_list,
                              int items_to_import) {
  const importer::SourceProfile& source_profile =
      importer_list->GetSourceProfileAt(0);

  // Ensure that importers aren't requested to import items that they do not
  // support.
  items_to_import &= source_profile.services_supported;

  scoped_ptr<ImportEndedObserver> observer(new ImportEndedObserver);
  importer_host->SetObserver(observer.get());
  importer_host->StartImportSettings(source_profile,
                                     profile,
                                     items_to_import,
                                     new ProfileWriter(profile),
                                     true);
  // If the import process has not errored out, block on it.
  if (!observer->ended()) {
    observer->set_should_quit_message_loop();
    MessageLoop::current()->Run();
  }

  // Unfortunately there's no success/fail signal in ImporterHost.
  return true;
}

#endif  // OS_POSIX