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

#include "components/omnibox/browser/on_device_head_model.h"

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "components/omnibox/browser/omnibox_field_trial.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::ElementsAre;
using testing::Pair;

namespace {

// The test head model used for unittests contains 14 queries and their scores
// shown below; the test model uses 3-bytes address and 2-bytes score so the
// highest score is 32767:
// ----------------------
// Query            Score
// ----------------------
// g                32767
// gmail            32766
// google maps      32765
// google           32764
// get out          32763
// googler          32762
// gamestop         32761
// maps             32761
// mail             32760
// map              32759
// 谷歌              32759
// ガツガツしてる人    32759
// 비데 두꺼비         32759
// переводчик       32759
// ----------------------
// The tree structure for queries above is similar as this:
//  [ g | ma | 谷歌 | ガツガツしてる人| 비데 두꺼비 | переводчик ]
//    |   |
//    | [ p | il ]
//    |   |
//    | [ # | s ]
//    |
//  [ # | oogle | mail | et out | amestop ]
//          |
//        [ # | _maps | er ]

base::FilePath GetTestModelPath() {
  base::FilePath file_path;
  base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &file_path);
  file_path = file_path.AppendASCII(
      "components/test/data/omnibox/on_device_head_test_model_index.bin");
  return file_path;
}

}  // namespace

class OnDeviceHeadModelTest : public testing::Test {
 protected:
  void SetUp() override {
    base::FilePath file_path = GetTestModelPath();
    ASSERT_TRUE(base::PathExists(file_path));
#if BUILDFLAG(IS_WIN)
    model_filename_ = base::WideToUTF8(file_path.value());
#else
    model_filename_ = file_path.value();
#endif
    ASSERT_FALSE(model_filename_.empty());
  }

  void TearDown() override { model_filename_.clear(); }

  std::string model_filename_;
};

TEST_F(OnDeviceHeadModelTest, GetSuggestions) {
  auto suggestions =
      OnDeviceHeadModel::GetSuggestionsForPrefix(model_filename_, 4, "go");
  EXPECT_THAT(suggestions,
              ElementsAre(Pair("google maps", 32765), Pair("google", 32764),
                          Pair("googler", 32762)));

  suggestions =
      OnDeviceHeadModel::GetSuggestionsForPrefix(model_filename_, 4, "ge");
  EXPECT_THAT(suggestions, ElementsAre(Pair("get out", 32763)));

  suggestions =
      OnDeviceHeadModel::GetSuggestionsForPrefix(model_filename_, 4, "ga");
  EXPECT_THAT(suggestions, ElementsAre(Pair("gamestop", 32761)));
}

TEST_F(OnDeviceHeadModelTest, NoMatch) {
  auto suggestions =
      OnDeviceHeadModel::GetSuggestionsForPrefix(model_filename_, 4, "x");
  EXPECT_TRUE(suggestions.empty());
}

TEST_F(OnDeviceHeadModelTest, MatchTheEndOfSuggestion) {
  auto suggestions =
      OnDeviceHeadModel::GetSuggestionsForPrefix(model_filename_, 4, "ap");
  EXPECT_TRUE(suggestions.empty());
}

TEST_F(OnDeviceHeadModelTest, MatchAtTheMiddleOfSuggestion) {
  auto suggestions =
      OnDeviceHeadModel::GetSuggestionsForPrefix(model_filename_, 4, "st");
  EXPECT_TRUE(suggestions.empty());
}

TEST_F(OnDeviceHeadModelTest, EmptyInput) {
  auto suggestions =
      OnDeviceHeadModel::GetSuggestionsForPrefix(model_filename_, 4, "");
  EXPECT_TRUE(suggestions.empty());
}

TEST_F(OnDeviceHeadModelTest, SetMaxSuggestionsToReturn) {
  auto suggestions =
      OnDeviceHeadModel::GetSuggestionsForPrefix(model_filename_, 5, "g");
  EXPECT_THAT(suggestions,
              ElementsAre(Pair("g", 32767), Pair("gmail", 32766),
                          Pair("google maps", 32765), Pair("google", 32764),
                          Pair("get out", 32763)));

  suggestions =
      OnDeviceHeadModel::GetSuggestionsForPrefix(model_filename_, 2, "ma");
  EXPECT_THAT(suggestions,
              ElementsAre(Pair("maps", 32761), Pair("mail", 32760)));
}

TEST_F(OnDeviceHeadModelTest, NonEnglishLanguage) {
  // Chinese.
  auto suggestions =
      OnDeviceHeadModel::GetSuggestionsForPrefix(model_filename_, 4, "谷");
  EXPECT_THAT(suggestions, ElementsAre(Pair("谷歌", 32759)));

  // Japanese.
  suggestions = OnDeviceHeadModel::GetSuggestionsForPrefix(model_filename_, 4,
                                                           "ガツガツ");
  EXPECT_THAT(suggestions, ElementsAre(Pair("ガツガツしてる人", 32759)));

  // Korean.
  suggestions =
      OnDeviceHeadModel::GetSuggestionsForPrefix(model_filename_, 4, "비데 ");
  EXPECT_THAT(suggestions, ElementsAre(Pair("비데 두꺼비", 32759)));

  // Russian.
  suggestions =
      OnDeviceHeadModel::GetSuggestionsForPrefix(model_filename_, 4, "пере");
  EXPECT_THAT(suggestions, ElementsAre(Pair("переводчик", 32759)));
}

// Test for https://crbug.com/1506547. Similar to
// OnDeviceHeadModelTest.GetSuggestions but search results are collected from
// deeper and wider subtree. Closer to what is done in real model.
TEST(OnDeviceHeadDeepModelTest, SearchSuggestions) {
  // Test model contents.
  // ----------------------
  // Query            Score
  // ----------------------
  // ping 11           1008
  // pong 11           1007
  // ping 12           1006
  // pong 12           1005
  // ping 21           1004
  // pong 21           1003
  // ping 22           1002
  // pong 22           1001

  base::FilePath file_path;
  base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &file_path);
  file_path = file_path.AppendASCII(
      "components/test/data/omnibox/on_device_head_test_deep_model.bin");
  ASSERT_TRUE(base::PathExists(file_path));
  std::string model_filename;
#if BUILDFLAG(IS_WIN)
  model_filename = base::WideToUTF8(file_path.value());
#else
  model_filename = file_path.value();
#endif

  std::vector<std::pair<std::string, uint32_t>> reference_suggestions{
      {"ping 11", 1008}, {"pong 11", 1007}, {"ping 12", 1006},
      {"pong 12", 1005}, {"ping 21", 1004}, {"pong 21", 1003},
      {"ping 22", 1002}, {"pong 22", 1001},
  };

  // Check that for any number of requested matches OnDeviceHeadModel returns
  // top entries.
  while (!reference_suggestions.empty()) {
    auto suggestions = OnDeviceHeadModel::GetSuggestionsForPrefix(
        model_filename, reference_suggestions.size(), "p");
    EXPECT_EQ(suggestions, reference_suggestions);
    reference_suggestions.pop_back();
  }
}
