text: // 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 <algorithm>
  
  #include "base/basictypes.h"
  #include "base/compiler_specific.h"
  #include "base/logging.h"
  #include "base/memory/scoped_ptr.h"
  #include "base/metrics/histogram.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/protector/base_setting_change.h"
  #include "chrome/browser/protector/histograms.h"
  #include "chrome/browser/protector/protector_service.h"
  #include "chrome/browser/protector/protector_service_factory.h"
  #include "chrome/browser/search_engines/template_url.h"
  #include "chrome/browser/search_engines/template_url_prepopulate_data.h"
  #include "chrome/browser/search_engines/template_url_service.h"
  #include "chrome/browser/search_engines/template_url_service_factory.h"
  #include "chrome/browser/search_engines/template_url_service_observer.h"
  #include "chrome/browser/webdata/keyword_table.h"
  #include "chrome/common/chrome_notification_types.h"
  #include "chrome/common/url_constants.h"
  #include "content/public/browser/notification_observer.h"
  #include "content/public/browser/notification_registrar.h"
  #include "content/public/browser/notification_service.h"
  #include "googleurl/src/gurl.h"
  #include "grit/chromium_strings.h"
  #include "grit/generated_resources.h"
  #include "grit/theme_resources.h"
  #include "ui/base/l10n/l10n_util.h"
  
  namespace protector {
  
  namespace {
  
  // Maximum length of the search engine name to be displayed.
  const size_t kMaxDisplayedNameLength = 16;
  
  // Matches TemplateURL with all fields set from the prepopulated data equal
  // to fields in another TemplateURL.
  class TemplateURLIsSame {
   public:
    // Creates a matcher based on |other|.
    explicit TemplateURLIsSame(const TemplateURL* other) : other_(other) {
    }
  
    // Returns true if both |other| and |url| are NULL or have same field values.
    bool operator()(const TemplateURL* url) {
      if (url == other_ )
        return true;
      if (!url || !other_)
        return false;
      return url->short_name() == other_->short_name() &&
          url->HasSameKeywordAs(*other_) &&
          url->url() == other_->url() &&
          url->suggestions_url() == other_->suggestions_url() &&
          url->instant_url() == other_->instant_url() &&
          url->safe_for_autoreplace() == other_->safe_for_autoreplace() &&
          url->favicon_url() == other_->favicon_url() &&
          url->show_in_default_list() == other_->show_in_default_list() &&
          url->input_encodings() == other_->input_encodings() &&
          url->prepopulate_id() == other_->prepopulate_id();
    }
  
   private:
    const TemplateURL* other_;
  };
  
  }  // namespace
  
  // Default search engine change tracked by Protector.
  class DefaultSearchProviderChange : public BaseSettingChange,
                                      public TemplateURLServiceObserver,
                                      public content::NotificationObserver {
   public:
    DefaultSearchProviderChange(TemplateURL* new_search_provider,
                                TemplateURL* backup_search_provider);
  
    // BaseSettingChange overrides:
    virtual bool Init(Profile* profile) OVERRIDE;
    virtual void InitWhenDisabled(Profile* profile) OVERRIDE;
    virtual void Apply(Browser* browser) OVERRIDE;
    virtual void Discard(Browser* browser) OVERRIDE;
    virtual void Timeout() OVERRIDE;
    virtual int GetBadgeIconID() const OVERRIDE;
    virtual int GetMenuItemIconID() const OVERRIDE;
    virtual int GetBubbleIconID() const OVERRIDE;
    virtual string16 GetBubbleTitle() const OVERRIDE;
    virtual string16 GetBubbleMessage() const OVERRIDE;
    virtual string16 GetApplyButtonText() const OVERRIDE;
    virtual string16 GetDiscardButtonText() const OVERRIDE;
    virtual DisplayName GetApplyDisplayName() const OVERRIDE;
    virtual GURL GetNewSettingURL() const OVERRIDE;
  
   private:
    virtual ~DefaultSearchProviderChange();
  
    // TemplateURLServiceObserver overrides:
    virtual void OnTemplateURLServiceChanged() OVERRIDE;
  
    // content::NotificationObserver overrides:
    virtual void Observe(int type,
                         const content::NotificationSource& source,
                         const content::NotificationDetails& details) OVERRIDE;
  
    // Sets the default search provider to |*search_provider|. If a matching
    // TemplateURL already exists, it is reused and |*search_provider| is reset.
    // Otherwise, adds |*search_provider| to keywords and releases it.
    // In both cases, |*search_provider| is NULL after this call.
    // Returns the new default search provider (either |*search_provider| or the
    // reused TemplateURL).
    const TemplateURL* SetDefaultSearchProvider(
        scoped_ptr<TemplateURL>* search_provider);
  
    // Returns true if |new_search_provider_| can be used as the default search
    // provider.
    bool NewSearchProviderValid() const;
  
    // Opens the Search engine settings page in a new tab.
    void OpenSearchEngineSettings(Browser* browser);
  
    // Returns the TemplateURLService instance for the Profile this change is
    // related to.
    TemplateURLService* GetTemplateURLService();
  
    // Histogram ID of the new search provider.
    int new_histogram_id_;
    // Indicates that the default search was restored to the prepopulated default
    // search engines.
    bool is_fallback_;
    // Indicates that the the prepopulated default search is the same as
    // |new_search_provider_|.
    bool fallback_is_new_;
    // ID of |new_search_provider_|.
    TemplateURLID new_id_;
    // The default search at the moment the change was detected. Will be used to
    // restore the new default search back if Apply is called. Will be set to
    // |NULL| if removed from the TemplateURLService.
    TemplateURL* new_search_provider_;
    // Default search provider set by Init for the period until user makes a
    // choice and either Apply or Discard is performed. Never is |NULL| during
    // that period since the change will dismiss itself if this provider gets
    // deleted or stops being the default.
    const TemplateURL* default_search_provider_;
    // Stores backup of the default search until it becomes owned by the
    // TemplateURLService or deleted.
    scoped_ptr<TemplateURL> backup_search_provider_;
    content::NotificationRegistrar registrar_;
  
    DISALLOW_COPY_AND_ASSIGN(DefaultSearchProviderChange);
  };
  
  DefaultSearchProviderChange::DefaultSearchProviderChange(
      TemplateURL* new_search_provider,
      TemplateURL* backup_search_provider)
      : new_histogram_id_(GetSearchProviderHistogramID(new_search_provider)),
        is_fallback_(false),
        fallback_is_new_(false),
        new_search_provider_(new_search_provider),
        default_search_provider_(NULL),
        backup_search_provider_(backup_search_provider) {
    if (backup_search_provider) {
      UMA_HISTOGRAM_ENUMERATION(
          kProtectorHistogramSearchProviderHijacked,
          new_histogram_id_,
          kProtectorMaxSearchProviderID);
    } else {
      UMA_HISTOGRAM_ENUMERATION(
          kProtectorHistogramSearchProviderCorrupt,
          new_histogram_id_,
          kProtectorMaxSearchProviderID);
    }
  }
  
  DefaultSearchProviderChange::~DefaultSearchProviderChange() {
    if (profile())
      GetTemplateURLService()->RemoveObserver(this);
  }
  
  bool DefaultSearchProviderChange::Init(Profile* profile) {
    if (!BaseSettingChange::Init(profile))
      return false;
  
    if (!backup_search_provider_.get() ||
        !backup_search_provider_->SupportsReplacement()) {
      // Fallback to a prepopulated default search provider, ignoring any
      // overrides in Prefs.
      backup_search_provider_.reset(
          TemplateURLPrepopulateData::GetPrepopulatedDefaultSearch(NULL));
      is_fallback_ = true;
      VLOG(1) << "Fallback search provider: "
              << backup_search_provider_->short_name();
    }
  
    default_search_provider_ = SetDefaultSearchProvider(&backup_search_provider_);
    DCHECK(default_search_provider_);
    // |backup_search_provider_| should be |NULL| since now.
    DCHECK(!backup_search_provider_.get());
    if (is_fallback_ && default_search_provider_ == new_search_provider_)
      fallback_is_new_ = true;
  
    int restored_histogram_id =
        GetSearchProviderHistogramID(default_search_provider_);
    UMA_HISTOGRAM_ENUMERATION(
        kProtectorHistogramSearchProviderRestored,
        restored_histogram_id,
        kProtectorMaxSearchProviderID);
  
    if (is_fallback_) {
      VLOG(1) << "Fallback to search provider: "
              << default_search_provider_->short_name();
      UMA_HISTOGRAM_ENUMERATION(
          kProtectorHistogramSearchProviderFallback,
          restored_histogram_id,
          kProtectorMaxSearchProviderID);
    }
  
    // Listen for the default search provider changes.
    GetTemplateURLService()->AddObserver(this);
  
    if (new_search_provider_) {
      // Listen for removal of |new_search_provider_|.
      new_id_ = new_search_provider_->id();
      registrar_.Add(
          this, chrome::NOTIFICATION_TEMPLATE_URL_REMOVED,
          content::Source<Profile>(profile->GetOriginalProfile()));
    }
  
    return true;
  }
  
  void DefaultSearchProviderChange::InitWhenDisabled(Profile* profile) {
    // The --no-protector case is handled in TemplateURLService internals.
    // TODO(ivankr): move it here.
    NOTREACHED();
  }
  
  void DefaultSearchProviderChange::Apply(Browser* browser) {
    UMA_HISTOGRAM_ENUMERATION(
        kProtectorHistogramSearchProviderApplied,
        new_histogram_id_,
        kProtectorMaxSearchProviderID);
  
    GetTemplateURLService()->RemoveObserver(this);
    if (NewSearchProviderValid()) {
      GetTemplateURLService()->SetDefaultSearchProvider(new_search_provider_);
    } else {
      // Open settings page in case the new setting is invalid.
      OpenSearchEngineSettings(browser);
    }
  }
  
  void DefaultSearchProviderChange::Discard(Browser* browser) {
    UMA_HISTOGRAM_ENUMERATION(
        kProtectorHistogramSearchProviderDiscarded,
        new_histogram_id_,
        kProtectorMaxSearchProviderID);
  
    GetTemplateURLService()->RemoveObserver(this);
    if (is_fallback_) {
      // Open settings page in case the old setting is invalid.
      OpenSearchEngineSettings(browser);
    }
    // Nothing to do otherwise since we have already set the search engine
    // to |old_id_| in |Init|.
  }
  
  void DefaultSearchProviderChange::Timeout() {
    UMA_HISTOGRAM_ENUMERATION(
        kProtectorHistogramSearchProviderTimeout,
        new_histogram_id_,
        kProtectorMaxSearchProviderID);
  }
  
  int DefaultSearchProviderChange::GetBadgeIconID() const {
    return IDR_SEARCH_ENGINE_CHANGE_BADGE;
  }
  
  int DefaultSearchProviderChange::GetMenuItemIconID() const {
    return IDR_SEARCH_ENGINE_CHANGE_MENU;
  }
  
  int DefaultSearchProviderChange::GetBubbleIconID() const {
    return IDR_SEARCH_ENGINE_CHANGE_ALERT;
  }
  
  string16 DefaultSearchProviderChange::GetBubbleTitle() const {
    return l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_CHANGE_TITLE);
  }
  
  string16 DefaultSearchProviderChange::GetBubbleMessage() const {
    if (is_fallback_) {
      return l10n_util::GetStringFUTF16(
          IDS_SEARCH_ENGINE_CHANGE_NO_BACKUP_MESSAGE,
          default_search_provider_->short_name());
    }
    return l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_CHANGE_MESSAGE);
  }
  
  string16 DefaultSearchProviderChange::GetApplyButtonText() const {
    if (NewSearchProviderValid()) {
      // If backup search engine is lost and fallback was made to the current
      // search provider then there is no need to show this button.
      if (fallback_is_new_)
        return string16();
      string16 name = new_search_provider_->short_name();
      return (name.length() > kMaxDisplayedNameLength) ?
          l10n_util::GetStringUTF16(IDS_CHANGE_SEARCH_ENGINE_NO_NAME) :
          l10n_util::GetStringFUTF16(IDS_CHANGE_SEARCH_ENGINE, name);
    }
    if (is_fallback_) {
      // Both settings are lost: don't show this button.
      return string16();
    }
    // New setting is lost, offer to go to settings.
    return l10n_util::GetStringUTF16(IDS_SELECT_SEARCH_ENGINE);
  }
  
  string16 DefaultSearchProviderChange::GetDiscardButtonText() const {
    if (!is_fallback_) {
      string16 name = default_search_provider_->short_name();
      return (name.length() > kMaxDisplayedNameLength) ?
          l10n_util::GetStringUTF16(IDS_KEEP_SETTING) :
          l10n_util::GetStringFUTF16(IDS_KEEP_SEARCH_ENGINE, name);
    }
    // Old setting is lost, offer to go to settings.
    return l10n_util::GetStringUTF16(IDS_SELECT_SEARCH_ENGINE);
  }
  
  BaseSettingChange::DisplayName
  DefaultSearchProviderChange::GetApplyDisplayName() const {
    if (NewSearchProviderValid() &&
        new_search_provider_->short_name().length() <= kMaxDisplayedNameLength) {
      return DisplayName(kDefaultSearchProviderChangeNamePriority,
                         new_search_provider_->short_name());
    }
    // Return the default empty name.
    return BaseSettingChange::GetApplyDisplayName();
  }
  
  GURL DefaultSearchProviderChange::GetNewSettingURL() const {
    if (is_fallback_) {
      // Do not collide this change when fallback was made, since the message
      // and Discard behaviour is pretty different.
      return GURL();
    }
    if (!NewSearchProviderValid())
      return GURL();
    return TemplateURLService::GenerateSearchURL(new_search_provider_);
  }
  
  void DefaultSearchProviderChange::OnTemplateURLServiceChanged() {
    TemplateURLService* url_service = GetTemplateURLService();
    if (url_service->GetDefaultSearchProvider() != default_search_provider_) {
      VLOG(1) << "Default search provider has been changed by user";
      default_search_provider_ = NULL;
      url_service->RemoveObserver(this);
      // Will delete this DefaultSearchProviderChange instance.
      ProtectorServiceFactory::GetForProfile(profile())->DismissChange(this);
    }
  }
  
  void DefaultSearchProviderChange::Observe(
      int type,
      const content::NotificationSource& source,
      const content::NotificationDetails& details) {
    DCHECK(type == chrome::NOTIFICATION_TEMPLATE_URL_REMOVED);
    TemplateURLID id = *content::Details<TemplateURLID>(details).ptr();
    if (id == new_id_)
      new_search_provider_ = NULL;
    registrar_.Remove(this, type, source);
    // TODO(ivankr): should the change be dismissed as well? Probably not,
    // since this may happend due to Sync or whatever. In that case, if user
    // clicks on 'Change to...', the Search engine settings page will be opened.
  }
  
  const TemplateURL* DefaultSearchProviderChange::SetDefaultSearchProvider(
      scoped_ptr<TemplateURL>* search_provider) {
    TemplateURLService* url_service = GetTemplateURLService();
    TemplateURLService::TemplateURLVector urls = url_service->GetTemplateURLs();
    TemplateURL* new_default_provider = NULL;
  
    // Check if this provider already exists and add it otherwise.
    TemplateURLService::TemplateURLVector::const_iterator i =
        std::find_if(urls.begin(), urls.end(),
                     TemplateURLIsSame(search_provider->get()));
    if (i != urls.end()) {
      VLOG(1) << "Provider already exists";
      new_default_provider = *i;
      search_provider->reset();
    } else {
      VLOG(1) << "No match, adding new provider";
      new_default_provider = search_provider->get();
      url_service->Add(search_provider->release());
      UMA_HISTOGRAM_ENUMERATION(
          kProtectorHistogramSearchProviderMissing,
          GetSearchProviderHistogramID(new_default_provider),
          kProtectorMaxSearchProviderID);
    }
  
    // TODO(ivankr): handle keyword conflicts with existing providers.
    DCHECK(new_default_provider);
    VLOG(1) << "Default search provider set to: "
            << new_default_provider->short_name();
    url_service->SetDefaultSearchProvider(new_default_provider);
    return new_default_provider;
  }
  
  bool DefaultSearchProviderChange::NewSearchProviderValid() const {
    return new_search_provider_ && new_search_provider_->SupportsReplacement();
  }
  
  void DefaultSearchProviderChange::OpenSearchEngineSettings(Browser* browser) {
    ProtectorServiceFactory::GetForProfile(profile())->OpenTab(
        GURL(std::string(chrome::kChromeUISettingsURL) +
             chrome::kSearchEnginesSubPage), browser);
  }
  
  TemplateURLService* DefaultSearchProviderChange::GetTemplateURLService() {
    TemplateURLService* url_service =
        TemplateURLServiceFactory::GetForProfile(profile());
    DCHECK(url_service);
    return url_service;
  }
  
  BaseSettingChange* CreateDefaultSearchProviderChange(TemplateURL* actual,
                                                       TemplateURL* backup) {
    return new DefaultSearchProviderChange(actual, backup);
  }
  
  }  // namespace protector
  