// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <inttypes.h>
#include <stddef.h>

#include "base/command_line.h"
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_future.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/memory_dump_request_args.h"
#include "build/build_config.h"
#include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
#include "chrome/browser/autocomplete/in_memory_url_index_factory.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/location_bar/location_bar.h"
#include "chrome/browser/ui/omnibox/omnibox_controller.h"
#include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
#include "chrome/browser/ui/omnibox/omnibox_view.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/search_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/history/core/browser/history_service.h"
#include "components/omnibox/browser/autocomplete_input.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/unpacked_installer.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"

namespace {

std::u16string AutocompleteResultAsString(const AutocompleteResult& result) {
  std::string output(base::StringPrintf("{%" PRIuS "} ", result.size()));
  for (size_t i = 0; i < result.size(); ++i) {
    AutocompleteMatch match = result.match_at(i);
    output.append(base::StringPrintf("[\"%s\" by \"%s\"] ",
                                     base::UTF16ToUTF8(match.contents).c_str(),
                                     match.provider->GetName()));
  }
  return base::UTF8ToUTF16(output);
}

class AutocompleteBrowserTest : public extensions::ExtensionBrowserTest {
 protected:
  void WaitForTemplateURLServiceToLoad() {
    search_test_utils::WaitForTemplateURLServiceToLoad(
        TemplateURLServiceFactory::GetForProfile(browser()->GetProfile()));
  }

  LocationBar* GetLocationBar() const {
    return BrowserWindow::FromBrowser(browser())->GetLocationBar();
  }

  AutocompleteController* GetAutocompleteController() const {
    return GetLocationBar()->GetOmniboxController()->autocomplete_controller();
  }

  void FocusSearchCheckPreconditions() const {
    LocationBar* location_bar = GetLocationBar();
    OmniboxView* omnibox_view = location_bar->GetOmniboxView();
    OmniboxEditModel* omnibox_model =
        location_bar->GetOmniboxController()->edit_model();

    EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
    EXPECT_EQ(url::kAboutBlankURL16, omnibox_view->GetText());
    EXPECT_EQ(std::u16string(), omnibox_model->keyword());
    EXPECT_FALSE(omnibox_model->is_keyword_hint());
    EXPECT_FALSE(omnibox_model->is_keyword_selected());
  }
};

IN_PROC_BROWSER_TEST_F(AutocompleteBrowserTest, Basic) {
  WaitForTemplateURLServiceToLoad();
  LocationBar* location_bar = GetLocationBar();
  OmniboxView* omnibox_view = location_bar->GetOmniboxView();

  EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
  EXPECT_EQ(url::kAboutBlankURL16, omnibox_view->GetText());
  // TODO(phajdan.jr): check state of IsSelectAll when it's consistent across
  // platforms.

  location_bar->FocusLocation(/*is_user_initiated=*/true,
                              /*clear_focus_if_failed=*/false);

  EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
  EXPECT_EQ(url::kAboutBlankURL16, omnibox_view->GetText());
  EXPECT_TRUE(omnibox_view->IsSelectAll());

  omnibox_view->SetUserText(u"chrome");

  EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
  EXPECT_EQ(u"chrome", omnibox_view->GetText());
  EXPECT_FALSE(omnibox_view->IsSelectAll());

  omnibox_view->RevertAll();

  EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
  EXPECT_EQ(url::kAboutBlankURL16, omnibox_view->GetText());
  EXPECT_FALSE(omnibox_view->IsSelectAll());

  omnibox_view->SetUserText(u"chrome");
  location_bar->Revert();

  EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
  EXPECT_EQ(url::kAboutBlankURL16, omnibox_view->GetText());
  EXPECT_FALSE(omnibox_view->IsSelectAll());
}

// Autocomplete test is flaky on ChromeOS.
// http://crbug.com/41200172
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_Autocomplete DISABLED_Autocomplete
#else
#define MAYBE_Autocomplete Autocomplete
#endif

IN_PROC_BROWSER_TEST_F(AutocompleteBrowserTest, MAYBE_Autocomplete) {
  WaitForTemplateURLServiceToLoad();
  // The results depend on the history backend being loaded. Make sure it is
  // loaded so that the autocomplete results are consistent.
  ui_test_utils::WaitForHistoryToLoad(HistoryServiceFactory::GetForProfile(
      browser()->GetProfile(), ServiceAccessType::EXPLICIT_ACCESS));

  LocationBar* location_bar = GetLocationBar();
  OmniboxView* omnibox_view = location_bar->GetOmniboxView();
  AutocompleteController* autocomplete_controller = GetAutocompleteController();

  {
    location_bar->GetOmniboxController()->edit_model()->SetInputInProgress(
        true);
    AutocompleteInput input(
        u"chrome", metrics::OmniboxEventProto::NTP,
        ChromeAutocompleteSchemeClassifier(browser()->GetProfile()));
    input.set_prevent_inline_autocomplete(true);
    input.set_omit_asynchronous_matches(true);
    autocomplete_controller->Start(input);

    EXPECT_TRUE(autocomplete_controller->done());
    EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
    EXPECT_TRUE(omnibox_view->GetText().empty());
    EXPECT_FALSE(omnibox_view->IsSelectAll());
    const AutocompleteResult& result = autocomplete_controller->result();
    ASSERT_GE(result.size(), 1U) << AutocompleteResultAsString(result);
    AutocompleteMatch match = result.match_at(0);
    EXPECT_EQ(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, match.type);
    EXPECT_FALSE(match.deletable);
  }

  {
    location_bar->Revert();
    EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
    EXPECT_EQ(url::kAboutBlankURL16, omnibox_view->GetText());
    EXPECT_FALSE(omnibox_view->IsSelectAll());
    const AutocompleteResult& result = autocomplete_controller->result();
    EXPECT_TRUE(result.empty()) << AutocompleteResultAsString(result);
  }
}

IN_PROC_BROWSER_TEST_F(AutocompleteBrowserTest, TabAwayRevertSelect) {
  WaitForTemplateURLServiceToLoad();
  // http://code.google.com/p/chromium/issues/detail?id=38385
  // Make sure that tabbing away from an empty omnibox causes a revert
  // and select all.
  LocationBar* location_bar = GetLocationBar();
  OmniboxView* omnibox_view = location_bar->GetOmniboxView();
  EXPECT_EQ(url::kAboutBlankURL16, omnibox_view->GetText());
  omnibox_view->SetUserText(std::u16string());
  content::CreateAndLoadWebContentsObserver observer;
  chrome::AddSelectedTabWithURL(browser(),
                                GURL(url::kAboutBlankURL),
                                ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
  observer.Wait();
  EXPECT_EQ(url::kAboutBlankURL16, omnibox_view->GetText());
  chrome::CloseTab(browser());
  EXPECT_EQ(url::kAboutBlankURL16, omnibox_view->GetText());
  EXPECT_TRUE(omnibox_view->IsSelectAll());
}

IN_PROC_BROWSER_TEST_F(AutocompleteBrowserTest, FocusSearch) {
  if (base::FeatureList::IsEnabled(features::kWebUILocationBar)) {
    GTEST_SKIP() << "WebUILocationBar handles FocusSearch asynchronously; "
                    "tested in WebUILocationBarInteractiveUiTest.";
  }
  WaitForTemplateURLServiceToLoad();
  LocationBar* location_bar = GetLocationBar();
  OmniboxView* omnibox_view = location_bar->GetOmniboxView();
  OmniboxEditModel* omnibox_model =
      location_bar->GetOmniboxController()->edit_model();

  TemplateURLService* template_url_service =
      TemplateURLServiceFactory::GetForProfile(browser()->GetProfile());
  std::u16string default_search_keyword =
      template_url_service->GetDefaultSearchProvider()->keyword();

  std::u16string query_text = u"foo";

  // Focus search when omnibox is blank.
  {
    FocusSearchCheckPreconditions();

    location_bar->FocusSearch();
    EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
    EXPECT_EQ(std::u16string(), omnibox_view->GetText());
    EXPECT_EQ(default_search_keyword, omnibox_model->keyword());
    EXPECT_FALSE(omnibox_model->is_keyword_hint());
    EXPECT_TRUE(omnibox_model->is_keyword_selected());

    gfx::Range selection = omnibox_view->GetSelectionBounds();
    EXPECT_EQ(0U, selection.start());
    EXPECT_EQ(0U, selection.end());

    omnibox_view->RevertAll();
  }

  // Focus search when omnibox is _not_ already in keyword mode.
  {
    FocusSearchCheckPreconditions();

    omnibox_view->SetUserText(query_text);
    EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
    EXPECT_EQ(query_text, omnibox_view->GetText());
    EXPECT_EQ(std::u16string(), omnibox_model->keyword());
    EXPECT_FALSE(omnibox_model->is_keyword_hint());
    EXPECT_FALSE(omnibox_model->is_keyword_selected());

    location_bar->FocusSearch();
    EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
    EXPECT_EQ(query_text, omnibox_view->GetText());
    EXPECT_EQ(default_search_keyword, omnibox_model->keyword());
    EXPECT_FALSE(omnibox_model->is_keyword_hint());
    EXPECT_TRUE(omnibox_model->is_keyword_selected());

    gfx::Range selection = omnibox_view->GetSelectionBounds();
    EXPECT_EQ(0U, selection.GetMin());
    EXPECT_EQ(query_text.size(), selection.GetMax());

    omnibox_view->RevertAll();
  }

  // Focus search when omnibox _is_ already in keyword mode, but no query
  // has been typed.
  {
    FocusSearchCheckPreconditions();

    location_bar->FocusSearch();
    EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
    EXPECT_EQ(std::u16string(), omnibox_view->GetText());
    EXPECT_EQ(default_search_keyword, omnibox_model->keyword());
    EXPECT_FALSE(omnibox_model->is_keyword_hint());
    EXPECT_TRUE(omnibox_model->is_keyword_selected());

    gfx::Range selection = omnibox_view->GetSelectionBounds();
    EXPECT_EQ(0U, selection.start());
    EXPECT_EQ(0U, selection.end());

    location_bar->FocusSearch();
    EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
    EXPECT_EQ(std::u16string(), omnibox_view->GetText());
    EXPECT_EQ(default_search_keyword, omnibox_model->keyword());
    EXPECT_FALSE(omnibox_model->is_keyword_hint());
    EXPECT_TRUE(omnibox_model->is_keyword_selected());

    selection = omnibox_view->GetSelectionBounds();
    EXPECT_EQ(0U, selection.start());
    EXPECT_EQ(0U, selection.end());

    omnibox_view->RevertAll();
  }

  // Focus search when omnibox _is_ already in keyword mode, and some query
  // has been typed.
  {
    FocusSearchCheckPreconditions();

    omnibox_view->SetUserText(query_text);
    location_bar->FocusSearch();
    EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
    EXPECT_EQ(query_text, omnibox_view->GetText());
    EXPECT_EQ(default_search_keyword, omnibox_model->keyword());
    EXPECT_FALSE(omnibox_model->is_keyword_hint());
    EXPECT_TRUE(omnibox_model->is_keyword_selected());

    gfx::Range selection = omnibox_view->GetSelectionBounds();
    EXPECT_EQ(0U, selection.GetMin());
    EXPECT_EQ(query_text.size(), selection.GetMax());

    location_bar->FocusSearch();
    EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
    EXPECT_EQ(query_text, omnibox_view->GetText());
    EXPECT_EQ(default_search_keyword, omnibox_model->keyword());
    EXPECT_FALSE(omnibox_model->is_keyword_hint());
    EXPECT_TRUE(omnibox_model->is_keyword_selected());

    selection = omnibox_view->GetSelectionBounds();
    EXPECT_EQ(0U, selection.GetMin());
    EXPECT_EQ(query_text.size(), selection.GetMax());

    omnibox_view->RevertAll();
  }

  // If the user gets into keyword mode using a keyboard shortcut, and presses
  // backspace, they should be left with their original query without their dsp
  // keyword.
  {
    FocusSearchCheckPreconditions();

    omnibox_view->SetUserText(query_text);
    // The user presses Ctrl-K.
    location_bar->FocusSearch();
    // The user presses backspace.
    omnibox_model->ClearKeyword();

    EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
    EXPECT_EQ(query_text, omnibox_view->GetText());
    EXPECT_EQ(std::u16string(), omnibox_model->keyword());
    EXPECT_FALSE(omnibox_model->is_keyword_hint());
    EXPECT_FALSE(omnibox_model->is_keyword_selected());

    omnibox_view->RevertAll();
  }

  // Calling FocusSearch() when the permanent URL is showing should result in an
  // empty query string.
  {
    FocusSearchCheckPreconditions();

    omnibox_model->ResetDisplayTexts();
    EXPECT_EQ(url::kAboutBlankURL16, omnibox_view->GetText());

    location_bar->FocusSearch();
    EXPECT_FALSE(location_bar->navigation_params().destination_url.is_valid());
    EXPECT_EQ(std::u16string(), omnibox_view->GetText());
    EXPECT_EQ(default_search_keyword, omnibox_model->keyword());
    EXPECT_FALSE(omnibox_model->is_keyword_hint());
    EXPECT_TRUE(omnibox_model->is_keyword_selected());

    omnibox_view->RevertAll();
  }
}

IN_PROC_BROWSER_TEST_F(AutocompleteBrowserTest, MemoryTracing) {
  auto* in_memory_url_index = InMemoryURLIndexFactory::GetForProfile(profile());
  auto* autocomplete_controller = GetAutocompleteController();

  const std::vector<std::string> expected_names{
      base::StringPrintf("omnibox/in_memory_url_index/0x%" PRIXPTR,
                         reinterpret_cast<uintptr_t>(in_memory_url_index)),
      base::StringPrintf("omnibox/autocomplete_controller/0x%" PRIXPTR,
                         reinterpret_cast<uintptr_t>(autocomplete_controller))};

  base::trace_event::MemoryDumpRequestArgs args{
      1 /* dump_guid*/, base::trace_event::MemoryDumpType::kExplicitlyTriggered,
      base::trace_event::MemoryDumpLevelOfDetail::kBackground};

  base::test::TestFuture<base::trace_event::ProcessMemoryDumpOutcome, uint64_t,
                         std::unique_ptr<base::trace_event::ProcessMemoryDump>>
      future;
  base::trace_event::MemoryDumpManager::GetInstance()->CreateProcessDump(
      args, future.GetSequenceBoundCallback());
  ASSERT_TRUE(future.Wait());
  ASSERT_EQ(base::trace_event::ProcessMemoryDumpOutcome::kSuccess,
            future.Get<base::trace_event::ProcessMemoryDumpOutcome>());

  const auto& allocator_dumps =
      future.Get<std::unique_ptr<base::trace_event::ProcessMemoryDump>>()
          ->allocator_dumps();
  for (const auto& expected_dump_name : expected_names) {
    EXPECT_TRUE(allocator_dumps.count(expected_dump_name));
  }
}

}  // namespace
