// Copyright 2014 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 "base/memory/scoped_ptr.h"
#include "base/metrics/field_trial.h"
#include "base/prefs/pref_service.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/extensions/test_extension_service.h"
#include "chrome/browser/search/hotword_service.h"
#include "chrome/browser/search/hotword_service_factory.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/manifest.h"
#include "extensions/common/one_shot_event.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

class MockHotwordService : public HotwordService {
 public:
  explicit MockHotwordService(Profile* profile)
      : HotwordService(profile),
        uninstall_count_(0) {
  }

  virtual bool UninstallHotwordExtension(
      ExtensionService* extension_service) OVERRIDE {
    uninstall_count_++;
    return HotwordService::UninstallHotwordExtension(extension_service);
  }

  virtual void InstallHotwordExtensionFromWebstore() OVERRIDE{
    scoped_ptr<base::DictionaryValue> manifest =
        extensions::DictionaryBuilder()
        .Set("name", "Hotword Test Extension")
        .Set("version", "1.0")
        .Set("manifest_version", 2)
        .Build();
    scoped_refptr<extensions::Extension> extension =
        extensions::ExtensionBuilder().SetManifest(manifest.Pass())
        .AddFlags(extensions::Extension::FROM_WEBSTORE
                  | extensions::Extension::WAS_INSTALLED_BY_DEFAULT)
        .SetID(extension_misc::kHotwordExtensionId)
        .SetLocation(extensions::Manifest::EXTERNAL_COMPONENT)
        .Build();
    ASSERT_TRUE(extension.get());
    service_->OnExtensionInstalled(extension, syncer::StringOrdinal());
  }


  int uninstall_count() { return uninstall_count_; }

  void SetExtensionService(ExtensionService* service) { service_ = service; }

  ExtensionService* extension_service() { return service_; }

 private:
  ExtensionService* service_;
  int uninstall_count_;
};

KeyedService* BuildMockHotwordService(content::BrowserContext* context) {
  return new MockHotwordService(static_cast<Profile*>(context));
}

}  // namespace

class HotwordServiceTest : public extensions::ExtensionServiceTestBase {
 protected:
  HotwordServiceTest() : field_trial_list_(NULL) {}
  virtual ~HotwordServiceTest() {}

  void SetApplicationLocale(Profile* profile, const std::string& new_locale) {
#if defined(OS_CHROMEOS)
        // On ChromeOS locale is per-profile.
    profile->GetPrefs()->SetString(prefs::kApplicationLocale, new_locale);
#else
    g_browser_process->SetApplicationLocale(new_locale);
#endif
  }

 private:
  base::FieldTrialList field_trial_list_;
};

TEST_F(HotwordServiceTest, IsHotwordAllowedBadFieldTrial) {
  TestingProfile::Builder profile_builder;
  TestingProfile::Builder otr_profile_builder;
  otr_profile_builder.SetIncognito();
  scoped_ptr<TestingProfile> profile = profile_builder.Build();
  scoped_ptr<TestingProfile> otr_profile = otr_profile_builder.Build();

  HotwordServiceFactory* hotword_service_factory =
      HotwordServiceFactory::GetInstance();

  // Check that the service exists so that a NULL service be ruled out in
  // following tests.
  HotwordService* hotword_service =
      hotword_service_factory->GetForProfile(profile.get());
  EXPECT_TRUE(hotword_service != NULL);

  // When the field trial is empty or Disabled, it should not be allowed.
  std::string group = base::FieldTrialList::FindFullName(
      hotword_internal::kHotwordFieldTrialName);
  EXPECT_TRUE(group.empty());
  EXPECT_FALSE(HotwordServiceFactory::IsHotwordAllowed(profile.get()));

  ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
     hotword_internal::kHotwordFieldTrialName,
     hotword_internal::kHotwordFieldTrialDisabledGroupName));
  group = base::FieldTrialList::FindFullName(
      hotword_internal::kHotwordFieldTrialName);
  EXPECT_TRUE(group ==hotword_internal::kHotwordFieldTrialDisabledGroupName);
  EXPECT_FALSE(HotwordServiceFactory::IsHotwordAllowed(profile.get()));

  // Set a valid locale with invalid field trial to be sure it is
  // still false.
  SetApplicationLocale(static_cast<Profile*>(profile.get()), "en");
  EXPECT_FALSE(HotwordServiceFactory::IsHotwordAllowed(profile.get()));

  // Test that incognito returns false as well.
  EXPECT_FALSE(HotwordServiceFactory::IsHotwordAllowed(otr_profile.get()));
}

TEST_F(HotwordServiceTest, IsHotwordAllowedLocale) {
  TestingProfile::Builder profile_builder;
  TestingProfile::Builder otr_profile_builder;
  otr_profile_builder.SetIncognito();
  scoped_ptr<TestingProfile> profile = profile_builder.Build();
  scoped_ptr<TestingProfile> otr_profile = otr_profile_builder.Build();

  HotwordServiceFactory* hotword_service_factory =
      HotwordServiceFactory::GetInstance();

  // Check that the service exists so that a NULL service be ruled out in
  // following tests.
  HotwordService* hotword_service =
      hotword_service_factory->GetForProfile(profile.get());
  EXPECT_TRUE(hotword_service != NULL);

  // Set the field trial to a valid one.
  ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
      hotword_internal::kHotwordFieldTrialName, "Good"));

  // Set the language to an invalid one.
  SetApplicationLocale(static_cast<Profile*>(profile.get()), "non-valid");
  EXPECT_FALSE(HotwordServiceFactory::IsHotwordAllowed(profile.get()));

  // Now with valid locales it should be fine.
  SetApplicationLocale(static_cast<Profile*>(profile.get()), "en");
  EXPECT_TRUE(HotwordServiceFactory::IsHotwordAllowed(profile.get()));
  SetApplicationLocale(static_cast<Profile*>(profile.get()), "en-US");
  EXPECT_TRUE(HotwordServiceFactory::IsHotwordAllowed(profile.get()));
  SetApplicationLocale(static_cast<Profile*>(profile.get()), "en_us");
  EXPECT_TRUE(HotwordServiceFactory::IsHotwordAllowed(profile.get()));
  SetApplicationLocale(static_cast<Profile*>(profile.get()), "de_DE");
  EXPECT_TRUE(HotwordServiceFactory::IsHotwordAllowed(profile.get()));
  SetApplicationLocale(static_cast<Profile*>(profile.get()), "fr_fr");
  EXPECT_TRUE(HotwordServiceFactory::IsHotwordAllowed(profile.get()));

  // Test that incognito even with a valid locale and valid field trial
  // still returns false.
  SetApplicationLocale(static_cast<Profile*>(otr_profile.get()), "en");
  EXPECT_FALSE(HotwordServiceFactory::IsHotwordAllowed(otr_profile.get()));
}

TEST_F(HotwordServiceTest, AudioLoggingPrefSetCorrectly) {
  TestingProfile::Builder profile_builder;
  scoped_ptr<TestingProfile> profile = profile_builder.Build();

  HotwordServiceFactory* hotword_service_factory =
      HotwordServiceFactory::GetInstance();
  HotwordService* hotword_service =
      hotword_service_factory->GetForProfile(profile.get());
  EXPECT_TRUE(hotword_service != NULL);

  // If it's a fresh profile, although the default value is true,
  // it should return false if the preference has never been set.
  EXPECT_FALSE(hotword_service->IsOptedIntoAudioLogging());
}

TEST_F(HotwordServiceTest, ShouldReinstallExtension) {
  // Set the field trial to a valid one.
  ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
      hotword_internal::kHotwordFieldTrialName, "Install"));

  InitializeEmptyExtensionService();

  HotwordServiceFactory* hotword_service_factory =
      HotwordServiceFactory::GetInstance();

  MockHotwordService* hotword_service = static_cast<MockHotwordService*>(
      hotword_service_factory->SetTestingFactoryAndUse(
          profile(), BuildMockHotwordService));
  EXPECT_TRUE(hotword_service != NULL);

  // If no locale has been set, no reason to uninstall.
  EXPECT_FALSE(hotword_service->ShouldReinstallHotwordExtension());

  SetApplicationLocale(profile(), "en");
  hotword_service->SetPreviousLanguagePref();

  // Now a locale is set, but it hasn't changed.
  EXPECT_FALSE(hotword_service->ShouldReinstallHotwordExtension());

  SetApplicationLocale(profile(), "fr_fr");

  // Now it's a different locale so it should uninstall.
  EXPECT_TRUE(hotword_service->ShouldReinstallHotwordExtension());
}

TEST_F(HotwordServiceTest, PreviousLanguageSetOnInstall) {
  // Set the field trial to a valid one.
  ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
      hotword_internal::kHotwordFieldTrialName, "Install"));

  InitializeEmptyExtensionService();
  service_->Init();

  HotwordServiceFactory* hotword_service_factory =
      HotwordServiceFactory::GetInstance();

  MockHotwordService* hotword_service = static_cast<MockHotwordService*>(
      hotword_service_factory->SetTestingFactoryAndUse(
          profile(), BuildMockHotwordService));
  EXPECT_TRUE(hotword_service != NULL);
  hotword_service->SetExtensionService(service());

  // If no locale has been set, no reason to uninstall.
  EXPECT_FALSE(hotword_service->ShouldReinstallHotwordExtension());

  SetApplicationLocale(profile(), "test_locale");

  hotword_service->InstallHotwordExtensionFromWebstore();
  base::MessageLoop::current()->RunUntilIdle();

  EXPECT_EQ("test_locale",
            profile()->GetPrefs()->GetString(prefs::kHotwordPreviousLanguage));
}

TEST_F(HotwordServiceTest, UninstallReinstallTriggeredCorrectly) {
  // Set the field trial to a valid one.
  ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
      hotword_internal::kHotwordFieldTrialName, "Install"));

  InitializeEmptyExtensionService();
  service_->Init();

  HotwordServiceFactory* hotword_service_factory =
      HotwordServiceFactory::GetInstance();

  MockHotwordService* hotword_service = static_cast<MockHotwordService*>(
      hotword_service_factory->SetTestingFactoryAndUse(
          profile(), BuildMockHotwordService));
  EXPECT_TRUE(hotword_service != NULL);
  hotword_service->SetExtensionService(service());

  // Initialize the locale to "en".
  SetApplicationLocale(profile(), "en");

  // The previous locale should not be set. No reason to uninstall.
  EXPECT_FALSE(hotword_service->MaybeReinstallHotwordExtension());

   // Do an initial installation.
  hotword_service->InstallHotwordExtensionFromWebstore();
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ("en",
            profile()->GetPrefs()->GetString(prefs::kHotwordPreviousLanguage));

  // Verify the extension is installed but disabled.
  EXPECT_EQ(1U, registry()->disabled_extensions().size());
  EXPECT_TRUE(registry()->disabled_extensions().Contains(
      extension_misc::kHotwordExtensionId));

   // The previous locale should be set but should match the current
  // locale. No reason to uninstall.
  EXPECT_FALSE(hotword_service->MaybeReinstallHotwordExtension());

  // Switch the locale to a valid but different one.
  SetApplicationLocale(profile(), "fr_fr");
  EXPECT_TRUE(HotwordServiceFactory::IsHotwordAllowed(profile()));

   // Different but valid locale so expect uninstall.
  EXPECT_TRUE(hotword_service->MaybeReinstallHotwordExtension());
  EXPECT_EQ(1, hotword_service->uninstall_count());
  EXPECT_EQ("fr_fr",
            profile()->GetPrefs()->GetString(prefs::kHotwordPreviousLanguage));

  // Verify the extension is installed. It's still disabled.
  EXPECT_TRUE(registry()->disabled_extensions().Contains(
      extension_misc::kHotwordExtensionId));

  // Switch the locale to an invalid one.
  SetApplicationLocale(profile(), "invalid");
  EXPECT_FALSE(HotwordServiceFactory::IsHotwordAllowed(profile()));
  EXPECT_FALSE(hotword_service->MaybeReinstallHotwordExtension());
  EXPECT_EQ("fr_fr",
            profile()->GetPrefs()->GetString(prefs::kHotwordPreviousLanguage));

  // If the locale is set back to the last valid one, then an uninstall-install
  // shouldn't be needed.
  SetApplicationLocale(profile(), "fr_fr");
  EXPECT_TRUE(HotwordServiceFactory::IsHotwordAllowed(profile()));
  EXPECT_FALSE(hotword_service->MaybeReinstallHotwordExtension());
  EXPECT_EQ(1, hotword_service->uninstall_count());  // no change
}