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

#include <vector>

#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/i18n/break_iterator.h"
#include "base/logging.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/autocomplete/autocomplete_field_trial.h"
#include "chrome/browser/history/history.h"
#include "chrome/browser/history/in_memory_url_index.h"
#include "chrome/browser/history/in_memory_url_index_types.h"
#include "chrome/browser/net/url_fixer_upper.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "googleurl/src/url_parse.h"
#include "googleurl/src/url_util.h"
#include "net/base/escape.h"
#include "net/base/net_util.h"

using history::InMemoryURLIndex;
using history::ScoredHistoryMatch;
using history::ScoredHistoryMatches;

bool HistoryQuickProvider::disabled_ = false;

HistoryQuickProvider::HistoryQuickProvider(ACProviderListener* listener,
                                           Profile* profile)
    : HistoryProvider(listener, profile, "HistoryQuickProvider"),
      languages_(profile_->GetPrefs()->GetString(prefs::kAcceptLanguages)) {
  enum InliningOption {
    INLINING_PROHIBITED = 0,
    INLINING_ALLOWED = 1,
    INLINING_AUTO_BUT_NOT_IN_FIELD_TRIAL = 2,
    INLINING_FIELD_TRIAL_DEFAULT_GROUP = 3,
    INLINING_FIELD_TRIAL_EXPERIMENT_GROUP = 4,
    NUM_OPTIONS = 5
  };
  // should always be overwritten
  InliningOption inlining_option = NUM_OPTIONS;

  const std::string switch_value = CommandLine::ForCurrentProcess()->
      GetSwitchValueASCII(switches::kOmniboxInlineHistoryQuickProvider);
  if (switch_value == switches::kOmniboxInlineHistoryQuickProviderAllowed) {
    inlining_option = INLINING_ALLOWED;
    always_prevent_inline_autocomplete_ = false;
  } else if (switch_value ==
             switches::kOmniboxInlineHistoryQuickProviderProhibited) {
    inlining_option = INLINING_PROHIBITED;
    always_prevent_inline_autocomplete_ = true;
  } else {
    // We'll assume any other flag means automatic.
    // Automatic means eligible for the field trial.

    // For the field trial stuff to work correctly, we must be running
    // on the same thread as the thread that created the field trial,
    // which happens via a call to AutocompleteFieldTrial::Active in
    // chrome_browser_main.cc on the main thread.  Let's check this to
    // be sure.  We check "if we've heard of the UI thread then we'd better
    // be on it."  The first part is necessary so unit tests pass.  (Many
    // unit tests don't set up the threading naming system; hence
    // CurrentlyOn(UI thread) will fail.)
    DCHECK(!content::BrowserThread::IsWellKnownThread(
               content::BrowserThread::UI) ||
           content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    if (AutocompleteFieldTrial::InDisallowInlineHQPFieldTrial()) {
      if (AutocompleteFieldTrial::
          InDisallowInlineHQPFieldTrialExperimentGroup()) {
        always_prevent_inline_autocomplete_ = true;
        inlining_option = INLINING_FIELD_TRIAL_EXPERIMENT_GROUP;
      } else {
        always_prevent_inline_autocomplete_ = false;
        inlining_option = INLINING_FIELD_TRIAL_DEFAULT_GROUP;
      }
    } else {
      always_prevent_inline_autocomplete_ = false;
      inlining_option = INLINING_AUTO_BUT_NOT_IN_FIELD_TRIAL;
    }
  }

  // Add a beacon to the logs that'll allow us to identify later what
  // inlining state a user is in.  Do this by incrementing a bucket in
  // a histogram, where the bucket represents the user's inlining state.
  UMA_HISTOGRAM_ENUMERATION(
      "Omnibox.InlineHistoryQuickProviderFieldTrialBeacon",
      inlining_option, NUM_OPTIONS);
}

HistoryQuickProvider::~HistoryQuickProvider() {}

void HistoryQuickProvider::Start(const AutocompleteInput& input,
                                 bool minimal_changes) {
  matches_.clear();
  if (disabled_)
    return;

  // Don't bother with INVALID and FORCED_QUERY.  Also pass when looking for
  // BEST_MATCH and there is no inline autocompletion because none of the HQP
  // matches can score highly enough to qualify.
  if ((input.type() == AutocompleteInput::INVALID) ||
      (input.type() == AutocompleteInput::FORCED_QUERY) ||
      (input.matches_requested() == AutocompleteInput::BEST_MATCH &&
       input.prevent_inline_autocomplete()))
    return;

  autocomplete_input_ = input;

  // TODO(pkasting): We should just block here until this loads.  Any time
  // someone unloads the history backend, we'll get inconsistent inline
  // autocomplete behavior here.
  if (GetIndex()) {
    base::TimeTicks start_time = base::TimeTicks::Now();
    DoAutocomplete();
    if (input.text().length() < 6) {
      base::TimeTicks end_time = base::TimeTicks::Now();
      std::string name = "HistoryQuickProvider.QueryIndexTime." +
          base::IntToString(input.text().length());
      base::Histogram* counter = base::Histogram::FactoryGet(
          name, 1, 1000, 50, base::Histogram::kUmaTargetedHistogramFlag);
      counter->Add(static_cast<int>((end_time - start_time).InMilliseconds()));
    }
    UpdateStarredStateOfMatches();
  }
}

// TODO(mrossetti): Implement this function. (Will happen in next CL.)
void HistoryQuickProvider::DeleteMatch(const AutocompleteMatch& match) {}

void HistoryQuickProvider::DoAutocomplete() {
  // Get the matching URLs from the DB.
  string16 term_string = autocomplete_input_.text();
  ScoredHistoryMatches matches = GetIndex()->HistoryItemsForTerms(term_string);
  if (matches.empty())
    return;

  // Loop over every result and add it to matches_.  In the process,
  // guarantee that scores are decreasing.  |max_match_score| keeps
  // track of the highest score we can assign to any later results we
  // see.  Also, if we're not allowing inline autocompletions in
  // general, artificially reduce the starting |max_match_score|
  // (which therefore applies to all results) to something low enough
  // that guarantees no result will be offered as an autocomplete
  // suggestion.  In addition, even if we allow inlining of
  // suggestions in general, we also reduce the starting
  // |max_match_score| if our top suggestion is not inlineable to make
  // sure it never gets attempted to be offered as an inline
  // suggestion.  Note that this strategy will allow a funky case:
  // suppose we're allowing inlining in general.  If the second result
  // is marked as cannot inline yet has a score that would make it
  // inlineable, it will keep its score.  This is a bit odd--a
  // non-inlineable result with a score high enough to make it
  // eligible for inlining will keep its high score--but it's okay
  // because there is a higher scoring result that is required to be
  // shown before this result.  Hence, this result, the second in the
  // set, will never be inlined.  (The autocomplete UI keeps results
  // in relevance score order.)
  int max_match_score = (PreventInlineAutocomplete(autocomplete_input_) ||
      !matches.begin()->can_inline) ?
      (AutocompleteResult::kLowestDefaultScore - 1) :
      matches.begin()->raw_score;
  for (ScoredHistoryMatches::const_iterator match_iter = matches.begin();
       match_iter != matches.end(); ++match_iter) {
    const ScoredHistoryMatch& history_match(*match_iter);
    // Set max_match_score to the score we'll assign this result:
    max_match_score = std::min(max_match_score, history_match.raw_score);
    matches_.push_back(QuickMatchToACMatch(history_match, max_match_score));
    // Mark this max_match_score as being used:
    max_match_score--;
  }
}

AutocompleteMatch HistoryQuickProvider::QuickMatchToACMatch(
    const ScoredHistoryMatch& history_match,
    int score) {
  const history::URLRow& info = history_match.url_info;
  AutocompleteMatch match(this, score, !!info.visit_count(),
      history_match.url_matches.empty() ?
          AutocompleteMatch::HISTORY_TITLE : AutocompleteMatch::HISTORY_URL);
  match.destination_url = info.url();
  DCHECK(match.destination_url.is_valid());

  // Format the URL autocomplete presentation.
  std::vector<size_t> offsets =
      OffsetsFromTermMatches(history_match.url_matches);
  const net::FormatUrlTypes format_types = net::kFormatUrlOmitAll &
      ~(!history_match.match_in_scheme ? 0 : net::kFormatUrlOmitHTTP);
  match.fill_into_edit =
      AutocompleteInput::FormattedStringWithEquivalentMeaning(info.url(),
          net::FormatUrlWithOffsets(info.url(), languages_, format_types,
              net::UnescapeRule::SPACES, NULL, NULL, &offsets));
  history::TermMatches new_matches =
      ReplaceOffsetsInTermMatches(history_match.url_matches, offsets);
  match.contents = net::FormatUrl(info.url(), languages_, format_types,
              net::UnescapeRule::SPACES, NULL, NULL, NULL);
  match.contents_class =
      SpansFromTermMatch(new_matches, match.contents.length(), true);

  if (!history_match.can_inline) {
    match.inline_autocomplete_offset = string16::npos;
  } else {
    DCHECK(!new_matches.empty());
    match.inline_autocomplete_offset = new_matches[0].offset +
        new_matches[0].length;
    // The following will happen if the user has typed an URL with a scheme
    // and the last character typed is a slash because that slash is removed
    // by the FormatURLWithOffsets call above.
    if (match.inline_autocomplete_offset > match.fill_into_edit.length())
      match.inline_autocomplete_offset = match.fill_into_edit.length();
  }

  // Format the description autocomplete presentation.
  match.description = info.title();
  match.description_class = SpansFromTermMatch(
      history_match.title_matches, match.description.length(), false);

  return match;
}

history::InMemoryURLIndex* HistoryQuickProvider::GetIndex() {
  if (index_for_testing_.get())
    return index_for_testing_.get();

  HistoryService* const history_service =
      profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
  if (!history_service)
    return NULL;

  return history_service->InMemoryIndex();
}

// static
ACMatchClassifications HistoryQuickProvider::SpansFromTermMatch(
    const history::TermMatches& matches,
    size_t text_length,
    bool is_url) {
  ACMatchClassification::Style url_style =
      is_url ? ACMatchClassification::URL : ACMatchClassification::NONE;
  ACMatchClassifications spans;
  if (matches.empty()) {
    if (text_length)
      spans.push_back(ACMatchClassification(0, url_style));
    return spans;
  }
  if (matches[0].offset)
    spans.push_back(ACMatchClassification(0, url_style));
  size_t match_count = matches.size();
  for (size_t i = 0; i < match_count;) {
    size_t offset = matches[i].offset;
    spans.push_back(ACMatchClassification(offset,
        ACMatchClassification::MATCH | url_style));
    // Skip all adjacent matches.
    do {
      offset += matches[i].length;
      ++i;
    } while ((i < match_count) && (offset == matches[i].offset));
    if (offset < text_length)
      spans.push_back(ACMatchClassification(offset, url_style));
  }

  return spans;
}
