// 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/browser/prerender/prerender_field_trial.h"

#include "base/command_line.h"
#include "base/logging.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "chrome/browser/predictors/autocomplete_action_predictor.h"
#include "chrome/browser/prerender/prerender_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_version_info.h"
#include "components/metrics/metrics_service.h"
#include "components/variations/variations_associated_data.h"

using base::FieldTrial;
using base::FieldTrialList;
using base::StringToInt;
using std::string;
using std::vector;

namespace prerender {

namespace {

const char kOmniboxTrialName[] = "PrerenderFromOmnibox";
int g_omnibox_trial_default_group_number = kint32min;

const char kDisabledGroup[] = "Disabled";
const char kEnabledGroup[] = "Enabled";

const char kLocalPredictorSpecTrialName[] = "PrerenderLocalPredictorSpec";
const char kLocalPredictorKeyName[] = "LocalPredictor";
const char kLocalPredictorUnencryptedSyncOnlyKeyName[] =
    "LocalPredictorUnencryptedSyncOnly";
const char kSideEffectFreeWhitelistKeyName[] = "SideEffectFreeWhitelist";
const char kPrerenderLaunchKeyName[] = "PrerenderLaunch";
const char kPrerenderAlwaysControlKeyName[] = "PrerenderAlwaysControl";
const char kPrerenderPrefetchKeyName[] = "PrerenderPrefetch";
const char kPrerenderQueryPrerenderServiceKeyName[] =
    "PrerenderQueryPrerenderService";
const char kPrerenderQueryPrerenderServiceCurrentURLKeyName[] =
    "PrerenderQueryPrerenderServiceCurrentURL";
const char kPrerenderQueryPrerenderServiceCandidateURLsKeyName[] =
    "PrerenderQueryPrerenderServiceCandidateURLs";
const char kPrerenderServiceBehaviorIDKeyName[] = "PrerenderServiceBehaviorID";
const char kPrerenderServiceFetchTimeoutKeyName[] =
    "PrerenderServiceFetchTimeoutMs";
const char kPrerenderTTLKeyName[] = "PrerenderTTLSeconds";
const char kPrerenderPriorityHalfLifeTimeKeyName[] =
    "PrerenderPriorityHalfLifeTimeSeconds";
const char kMaxConcurrentPrerenderKeyName[] = "MaxConcurrentPrerenders";
const char kMaxLaunchPrerenderKeyName[] = "MaxLaunchPrerenders";
const char kSkipFragment[] = "SkipFragment";
const char kSkipHTTPS[] = "SkipHTTPS";
const char kSkipWhitelist[] = "SkipWhitelist";
const char kSkipServiceWhitelist[] = "SkipServiceWhitelist";
const char kSkipLoggedIn[] = "SkipLoggedIn";
const char kSkipDefaultNoPrerender[] = "SkipDefaultNoPrerender";
const char kPrerenderServiceURLPrefixParameterName[] =
    "PrerenderServiceURLPrefix";
const char kDefaultPrerenderServiceURLPrefix[] =
    "https://clients4.google.com/prerenderservice/?q=";
const int kMinPrerenderServiceTimeoutMs = 1;
const int kMaxPrerenderServiceTimeoutMs = 10000;
const int kDefaultPrerenderServiceTimeoutMs = 1000;
const char kSkipPrerenderLocalCanadidates[] = "SkipPrerenderLocalCandidates";
const char kSkipPrerenderServiceCanadidates[] =
    "SkipPrerenderServiceCandidates";
const char kDisableSessionStorageNamespaceMerging[] =
    "DisableSessionStorageNamespaceMerging";
const char kPrerenderCookieStore[] = "PrerenderCookieStore";

void SetupPrerenderFieldTrial() {
  const FieldTrial::Probability divisor = 1000;

  FieldTrial::Probability control_probability;
  FieldTrial::Probability experiment_multi_prerender_probability;
  FieldTrial::Probability experiment_15min_ttl_probability;
  FieldTrial::Probability experiment_no_use_probability;

  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
  if (channel == chrome::VersionInfo::CHANNEL_STABLE ||
      channel == chrome::VersionInfo::CHANNEL_BETA) {
    // Use very conservatives and stable settings in beta and stable.
    const FieldTrial::Probability release_prerender_enabled_probability = 980;
    const FieldTrial::Probability release_control_probability = 10;
    const FieldTrial::Probability
        release_experiment_multi_prerender_probability = 0;
    const FieldTrial::Probability release_experiment_15min_ttl_probability = 10;
    const FieldTrial::Probability release_experiment_no_use_probability = 0;
    COMPILE_ASSERT(
        release_prerender_enabled_probability + release_control_probability +
        release_experiment_multi_prerender_probability +
        release_experiment_15min_ttl_probability +
        release_experiment_no_use_probability == divisor,
        release_experiment_probabilities_must_equal_divisor);

    control_probability = release_control_probability;
    experiment_multi_prerender_probability =
        release_experiment_multi_prerender_probability;
    experiment_15min_ttl_probability = release_experiment_15min_ttl_probability;
    experiment_no_use_probability = release_experiment_no_use_probability;
  } else {
    // In testing channels, use more experiments and a larger control group to
    // improve quality of data.
    const FieldTrial::Probability dev_prerender_enabled_probability = 250;
    const FieldTrial::Probability dev_control_probability = 250;
    const FieldTrial::Probability
        dev_experiment_multi_prerender_probability = 250;
    const FieldTrial::Probability dev_experiment_15min_ttl_probability = 125;
    const FieldTrial::Probability dev_experiment_no_use_probability = 125;
    COMPILE_ASSERT(dev_prerender_enabled_probability + dev_control_probability +
                   dev_experiment_multi_prerender_probability +
                   dev_experiment_15min_ttl_probability +
                   dev_experiment_no_use_probability == divisor,
                   dev_experiment_probabilities_must_equal_divisor);

    control_probability = dev_control_probability;
    experiment_multi_prerender_probability =
        dev_experiment_multi_prerender_probability;
    experiment_15min_ttl_probability = dev_experiment_15min_ttl_probability;
    experiment_no_use_probability = dev_experiment_no_use_probability;
  }

  int prerender_enabled_group = -1;
  scoped_refptr<FieldTrial> trial(
      FieldTrialList::FactoryGetFieldTrial(
          "Prerender", divisor, "PrerenderEnabled",
          2014, 12, 31, FieldTrial::SESSION_RANDOMIZED,
          &prerender_enabled_group));
  const int control_group =
      trial->AppendGroup("PrerenderControl",
                         control_probability);
  const int experiment_multi_prerender_group =
      trial->AppendGroup("PrerenderMulti",
                         experiment_multi_prerender_probability);
  const int experiment_15_min_TTL_group =
      trial->AppendGroup("Prerender15minTTL",
                         experiment_15min_ttl_probability);
  const int experiment_no_use_group =
      trial->AppendGroup("PrerenderNoUse",
                         experiment_no_use_probability);

  const int trial_group = trial->group();
  if (trial_group == prerender_enabled_group) {
    PrerenderManager::SetMode(
        PrerenderManager::PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP);
  } else if (trial_group == control_group) {
    PrerenderManager::SetMode(
        PrerenderManager::PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP);
  } else if (trial_group == experiment_multi_prerender_group) {
    PrerenderManager::SetMode(
        PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP);
  } else if (trial_group == experiment_15_min_TTL_group) {
    PrerenderManager::SetMode(
        PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP);
  } else if (trial_group == experiment_no_use_group) {
    PrerenderManager::SetMode(
        PrerenderManager::PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP);
  } else {
    NOTREACHED();
  }
}

}  // end namespace

void ConfigureOmniboxPrerender();

void ConfigurePrerender(const CommandLine& command_line) {
  enum PrerenderOption {
    PRERENDER_OPTION_AUTO,
    PRERENDER_OPTION_DISABLED,
    PRERENDER_OPTION_ENABLED,
  };

  PrerenderOption prerender_option = PRERENDER_OPTION_AUTO;
  if (command_line.HasSwitch(switches::kPrerenderMode)) {
    const string switch_value =
        command_line.GetSwitchValueASCII(switches::kPrerenderMode);

    if (switch_value == switches::kPrerenderModeSwitchValueAuto) {
      prerender_option = PRERENDER_OPTION_AUTO;
    } else if (switch_value == switches::kPrerenderModeSwitchValueDisabled) {
      prerender_option = PRERENDER_OPTION_DISABLED;
    } else if (switch_value.empty() ||
               switch_value == switches::kPrerenderModeSwitchValueEnabled) {
      // The empty string means the option was provided with no value, and that
      // means enable.
      prerender_option = PRERENDER_OPTION_ENABLED;
    } else {
      prerender_option = PRERENDER_OPTION_DISABLED;
      LOG(ERROR) << "Invalid --prerender option received on command line: "
                 << switch_value;
      LOG(ERROR) << "Disabling prerendering!";
    }
  }

  switch (prerender_option) {
    case PRERENDER_OPTION_AUTO:
      SetupPrerenderFieldTrial();
      break;
    case PRERENDER_OPTION_DISABLED:
      PrerenderManager::SetMode(PrerenderManager::PRERENDER_MODE_DISABLED);
      break;
    case PRERENDER_OPTION_ENABLED:
      PrerenderManager::SetMode(PrerenderManager::PRERENDER_MODE_ENABLED);
      break;
    default:
      NOTREACHED();
  }

  ConfigureOmniboxPrerender();
}

void ConfigureOmniboxPrerender() {
  // Field trial to see if we're enabled.
  const FieldTrial::Probability kDivisor = 100;

  FieldTrial::Probability kDisabledProbability = 10;
  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
  if (channel == chrome::VersionInfo::CHANNEL_STABLE ||
      channel == chrome::VersionInfo::CHANNEL_BETA) {
    kDisabledProbability = 1;
  }
  scoped_refptr<FieldTrial> omnibox_prerender_trial(
      FieldTrialList::FactoryGetFieldTrial(
          kOmniboxTrialName, kDivisor, "OmniboxPrerenderEnabled",
          2014, 12, 31, FieldTrial::SESSION_RANDOMIZED,
          &g_omnibox_trial_default_group_number));
  omnibox_prerender_trial->AppendGroup("OmniboxPrerenderDisabled",
                                       kDisabledProbability);
}

bool IsOmniboxEnabled(Profile* profile) {
  if (!profile)
    return false;

  if (!PrerenderManager::IsPrerenderingPossible())
    return false;

  // Override any field trial groups if the user has set a command line flag.
  if (CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kPrerenderFromOmnibox)) {
    const string switch_value =
        CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
            switches::kPrerenderFromOmnibox);

    if (switch_value == switches::kPrerenderFromOmniboxSwitchValueEnabled)
      return true;

    if (switch_value == switches::kPrerenderFromOmniboxSwitchValueDisabled)
      return false;

    DCHECK_EQ(switches::kPrerenderFromOmniboxSwitchValueAuto, switch_value);
  }

  const int group = FieldTrialList::FindValue(kOmniboxTrialName);
  return group == FieldTrial::kNotFinalized ||
         group == g_omnibox_trial_default_group_number;
}

/*
PrerenderLocalPredictorSpec is a field trial, and its value must have the
following format:
key1=value1:key2=value2:key3=value3
eg "LocalPredictor=Enabled:SideEffectFreeWhitelist=Enabled"
The function below extracts the value corresponding to a key provided from the
LocalPredictorSpec.
*/
string GetLocalPredictorSpecValue(string spec_key) {
  vector<string> elements;
  base::SplitString(FieldTrialList::FindFullName(kLocalPredictorSpecTrialName),
                    ':', &elements);
  for (int i = 0; i < static_cast<int>(elements.size()); i++) {
    vector<string> key_value;
    base::SplitString(elements[i], '=', &key_value);
    if (key_value.size() == 2 && key_value[0] == spec_key)
      return key_value[1];
  }
  return string();
}

bool IsUnencryptedSyncEnabled(Profile* profile) {
  ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
      GetForProfile(profile);
  return service && service->GetOpenTabsUIDelegate() &&
      !service->EncryptEverythingEnabled();
}

// Indicates whether the Local Predictor is enabled based on field trial
// selection.
bool IsLocalPredictorEnabled() {
#if defined(OS_ANDROID) || defined(OS_IOS)
  return false;
#endif
  return
      !CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kDisablePrerenderLocalPredictor) &&
      GetLocalPredictorSpecValue(kLocalPredictorKeyName) == kEnabledGroup;
}

bool DisableLocalPredictorBasedOnSyncAndConfiguration(Profile* profile) {
  return
      GetLocalPredictorSpecValue(kLocalPredictorUnencryptedSyncOnlyKeyName) ==
          kEnabledGroup &&
      !IsUnencryptedSyncEnabled(profile);
}

bool IsLoggedInPredictorEnabled() {
  return IsLocalPredictorEnabled();
}

bool IsSideEffectFreeWhitelistEnabled() {
  return IsLocalPredictorEnabled() &&
      GetLocalPredictorSpecValue(kSideEffectFreeWhitelistKeyName) !=
      kDisabledGroup;
}

bool IsLocalPredictorPrerenderLaunchEnabled() {
  return GetLocalPredictorSpecValue(kPrerenderLaunchKeyName) != kDisabledGroup;
}

bool IsLocalPredictorPrerenderAlwaysControlEnabled() {
  // If we prefetch rather than prerender, we automatically also prerender
  // as a control group only.
  return (GetLocalPredictorSpecValue(kPrerenderAlwaysControlKeyName) ==
          kEnabledGroup) || IsLocalPredictorPrerenderPrefetchEnabled();
}

bool IsLocalPredictorPrerenderPrefetchEnabled() {
  return GetLocalPredictorSpecValue(kPrerenderPrefetchKeyName) ==
      kEnabledGroup;
}

bool ShouldQueryPrerenderService(Profile* profile) {
  return IsUnencryptedSyncEnabled(profile) &&
      GetLocalPredictorSpecValue(kPrerenderQueryPrerenderServiceKeyName) ==
      kEnabledGroup;
}

bool ShouldQueryPrerenderServiceForCurrentURL() {
  return GetLocalPredictorSpecValue(
      kPrerenderQueryPrerenderServiceCurrentURLKeyName) != kDisabledGroup;
}

bool ShouldQueryPrerenderServiceForCandidateURLs() {
  return GetLocalPredictorSpecValue(
      kPrerenderQueryPrerenderServiceCandidateURLsKeyName) != kDisabledGroup;
}

string GetPrerenderServiceURLPrefix() {
  string prefix = chrome_variations::GetVariationParamValue(
      kLocalPredictorSpecTrialName,
      kPrerenderServiceURLPrefixParameterName);
  return prefix.empty() ? kDefaultPrerenderServiceURLPrefix : prefix;
}

int GetPrerenderServiceBehaviorID() {
  int id;
  StringToInt(GetLocalPredictorSpecValue(kPrerenderServiceBehaviorIDKeyName),
              &id);
  // The behavior ID must be non-negative.
  return std::max(id, 0);
}

int GetPrerenderServiceFetchTimeoutMs() {
  int result;
  StringToInt(GetLocalPredictorSpecValue(kPrerenderServiceFetchTimeoutKeyName),
              &result);
  // If the value is outside the valid range, use the default value.
  return (result < kMinPrerenderServiceTimeoutMs ||
          result > kMaxPrerenderServiceTimeoutMs) ?
      kDefaultPrerenderServiceTimeoutMs : result;
}

int GetLocalPredictorTTLSeconds() {
  int ttl;
  StringToInt(GetLocalPredictorSpecValue(kPrerenderTTLKeyName), &ttl);
  // If the value is outside of 10s or 600s, use a default value of 180s.
  return (ttl < 10 || ttl > 600) ? 180 : ttl;
}

int GetLocalPredictorPrerenderPriorityHalfLifeTimeSeconds() {
  int half_life_time;
  StringToInt(GetLocalPredictorSpecValue(kPrerenderPriorityHalfLifeTimeKeyName),
              &half_life_time);
  // Sanity check: Ensure the half life time is non-negative.
  return std::max(half_life_time, 0);
}

int GetLocalPredictorMaxConcurrentPrerenders() {
  int num_prerenders;
  StringToInt(GetLocalPredictorSpecValue(kMaxConcurrentPrerenderKeyName),
              &num_prerenders);
  // Sanity check: Ensure the number of prerenders is between 1 and 10.
  return std::min(std::max(num_prerenders, 1), 10);
}

int GetLocalPredictorMaxLaunchPrerenders() {
  int num_prerenders;
  StringToInt(GetLocalPredictorSpecValue(kMaxLaunchPrerenderKeyName),
              &num_prerenders);
  // Sanity check: Ensure the number of prerenders is between 1 and 10.
  return std::min(std::max(num_prerenders, 1), 10);
}

bool SkipLocalPredictorFragment() {
  return GetLocalPredictorSpecValue(kSkipFragment) == kEnabledGroup;
}

bool SkipLocalPredictorHTTPS() {
  return GetLocalPredictorSpecValue(kSkipHTTPS) == kEnabledGroup;
}

bool SkipLocalPredictorWhitelist() {
  return GetLocalPredictorSpecValue(kSkipWhitelist) == kEnabledGroup;
}

bool SkipLocalPredictorServiceWhitelist() {
  return GetLocalPredictorSpecValue(kSkipServiceWhitelist) == kEnabledGroup;
}

bool SkipLocalPredictorLoggedIn() {
  return GetLocalPredictorSpecValue(kSkipLoggedIn) == kEnabledGroup;
}

bool SkipLocalPredictorDefaultNoPrerender() {
  return GetLocalPredictorSpecValue(kSkipDefaultNoPrerender) == kEnabledGroup;
}

bool SkipLocalPredictorLocalCandidates() {
  return GetLocalPredictorSpecValue(kSkipPrerenderLocalCanadidates) ==
      kEnabledGroup;
}

bool SkipLocalPredictorServiceCandidates() {
  return GetLocalPredictorSpecValue(kSkipPrerenderServiceCanadidates) ==
      kEnabledGroup;
}

bool ShouldMergeSessionStorageNamespaces() {
  return GetLocalPredictorSpecValue(kDisableSessionStorageNamespaceMerging) !=
      kDisabledGroup;
}

bool IsPrerenderCookieStoreEnabled() {
  return GetLocalPredictorSpecValue(kPrerenderCookieStore) != kDisabledGroup &&
      FieldTrialList::FindFullName(kPrerenderCookieStore) != kDisabledGroup;
}

}  // namespace prerender