// Copyright 2013 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/ui/search/instant_search_prerenderer.h"

#include "chrome/browser/prerender/prerender_handle.h"
#include "chrome/browser/prerender/prerender_manager.h"
#include "chrome/browser/prerender/prerender_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/instant_service.h"
#include "chrome/browser/search/instant_service_factory.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/search/search_tab_helper.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/search/search.h"
#include "components/search_engines/template_url_service.h"

namespace {

// Returns true if the underlying page supports Instant search.
bool PageSupportsInstantSearch(content::WebContents* contents) {
  // Search results page supports Instant search.
  return SearchTabHelper::FromWebContents(contents)->IsSearchResultsPage();
}

// Returns true if |match| is associated with the default search provider.
bool MatchIsFromDefaultSearchProvider(const AutocompleteMatch& match,
                                      Profile* profile) {
  DCHECK(profile);
  TemplateURLService* template_url_service =
      TemplateURLServiceFactory::GetForProfile(profile);
  return match.GetTemplateURL(template_url_service, false) ==
      template_url_service->GetDefaultSearchProvider();
}

}  // namespace

InstantSearchPrerenderer::InstantSearchPrerenderer(Profile* profile,
                                                   const GURL& url)
    : profile_(profile),
      prerender_url_(url) {
}

InstantSearchPrerenderer::~InstantSearchPrerenderer() {
  if (prerender_handle_)
    prerender_handle_->OnCancel();
}

// static
InstantSearchPrerenderer* InstantSearchPrerenderer::GetForProfile(
    Profile* profile) {
  DCHECK(profile);
  InstantService* instant_service =
      InstantServiceFactory::GetForProfile(profile);
  return instant_service ? instant_service->instant_search_prerenderer() : NULL;
}

void InstantSearchPrerenderer::Init(
    content::SessionStorageNamespace* session_storage_namespace,
    const gfx::Size& size) {
  // TODO(kmadhusu): Enable Instant for Incognito profile.
  if (profile_->IsOffTheRecord())
    return;

  // Only cancel the old prerender after starting the new one, so if the URLs
  // are the same, the underlying prerender will be reused.
  scoped_ptr<prerender::PrerenderHandle> old_prerender_handle(
      prerender_handle_.release());
  prerender::PrerenderManager* prerender_manager =
      prerender::PrerenderManagerFactory::GetForProfile(profile_);
  if (prerender_manager) {
    prerender_handle_.reset(prerender_manager->AddPrerenderForInstant(
        prerender_url_, session_storage_namespace, size));
  }
  if (old_prerender_handle)
    old_prerender_handle->OnCancel();
}

void InstantSearchPrerenderer::Cancel() {
  if (!prerender_handle_)
    return;

  last_instant_suggestion_ = InstantSuggestion();
  prerender_handle_->OnCancel();
  prerender_handle_.reset();
}

void InstantSearchPrerenderer::Prerender(const InstantSuggestion& suggestion) {
  if (!prerender_handle_)
    return;

  if (last_instant_suggestion_.text == suggestion.text)
    return;

  if (last_instant_suggestion_.text.empty() &&
      !prerender_handle_->IsFinishedLoading())
    return;

  if (!prerender_contents())
    return;

  last_instant_suggestion_ = suggestion;
  SearchTabHelper::FromWebContents(prerender_contents())->
      SetSuggestionToPrefetch(suggestion);
}

void InstantSearchPrerenderer::Commit(
    const base::string16& query,
    const EmbeddedSearchRequestParams& params) {
  DCHECK(prerender_handle_);
  DCHECK(prerender_contents());
  SearchTabHelper::FromWebContents(prerender_contents())->Submit(query, params);
}

bool InstantSearchPrerenderer::CanCommitQuery(
    content::WebContents* source,
    const base::string16& query) const {
  if (!source || query.empty() || !prerender_handle_ ||
      !prerender_handle_->IsFinishedLoading() ||
      !prerender_contents() || !QueryMatchesPrefetch(query)) {
    return false;
  }

  // InstantSearchPrerenderer can commit query to the prerendered page only if
  // the underlying |source| page doesn't support Instant search.
  return !PageSupportsInstantSearch(source);
}

bool InstantSearchPrerenderer::UsePrerenderedPage(
    const GURL& url,
    chrome::NavigateParams* params) {
  base::string16 search_terms =
      search::ExtractSearchTermsFromURL(profile_, url);
  prerender::PrerenderManager* prerender_manager =
      prerender::PrerenderManagerFactory::GetForProfile(profile_);
  if (search_terms.empty() || !params->target_contents ||
      !prerender_contents() || !prerender_manager ||
      !QueryMatchesPrefetch(search_terms) ||
      params->disposition != CURRENT_TAB) {
    Cancel();
    return false;
  }

  // Do not use prerendered page for renderer initiated search requests.
  if (params->is_renderer_initiated &&
      params->transition == ui::PAGE_TRANSITION_LINK) {
    Cancel();
    return false;
  }

  bool success = prerender_manager->MaybeUsePrerenderedPage(
      prerender_contents()->GetURL(), params);
  prerender_handle_.reset();
  return success;
}

bool InstantSearchPrerenderer::IsAllowed(const AutocompleteMatch& match,
                                         content::WebContents* source) const {
  // We block prerendering for anything but search-type matches associated with
  // the default search provider.
  //
  // This is more restrictive than necessary.  All that's really needed to be
  // able to successfully prerender is that the |destination_url| of |match| be
  // from the same origin and path as the default search engine, and the params
  // to be sent to the server be a subset of the params we can pass to the
  // prerenderer.  So for example, if we normally prerender search URLs like
  // https://google.com/search?q=foo&x=bar, then any match with a URL like that,
  // potentially with the q and/or x params omitted, is prerenderable.
  //
  // However, if the URL has other params _not_ normally in the prerendered URL,
  // there's no way to pass them to the prerendered page, and worse, if the URL
  // does something like specifying params in both the query and ref sections of
  // the URL (as Google URLs often do), it can quickly become impossible to
  // figure out how to correctly tease out the right param names and values to
  // send.  Rather than try and write parsing code to deal with all these kinds
  // of cases, for various different search engines, including accommodating
  // changing behavior over time, we do the simple restriction described above.
  // This handles the by-far-the-most-common cases while still being simple and
  // maintainable.
  return source && AutocompleteMatch::IsSearchType(match.type) &&
      MatchIsFromDefaultSearchProvider(match, profile_) &&
      !PageSupportsInstantSearch(source);
}

content::WebContents* InstantSearchPrerenderer::prerender_contents() const {
  return (prerender_handle_ && prerender_handle_->contents()) ?
      prerender_handle_->contents()->prerender_contents() : NULL;
}

bool InstantSearchPrerenderer::QueryMatchesPrefetch(
    const base::string16& query) const {
  if (search::ShouldReuseInstantSearchBasePage())
    return true;
  return last_instant_suggestion_.text == query;
}