// Copyright 2015 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/extensions/api/language_settings_private/language_settings_private_delegate.h"

#include <string>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/memory/linked_ptr.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/spellchecker/spellcheck_factory.h"
#include "chrome/browser/spellchecker/spellcheck_service.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/notification_source.h"

namespace extensions {

namespace language_settings_private = api::language_settings_private;

LanguageSettingsPrivateDelegate::LanguageSettingsPrivateDelegate(
    content::BrowserContext* context)
    : custom_dictionary_(nullptr),
      context_(context),
      listening_spellcheck_(false),
      profile_added_(false) {
  // Register with the event router so we know when renderers are listening to
  // our events. We first check and see if there *is* an event router, because
  // some unit tests try to create all context services, but don't initialize
  // the event router first.
  EventRouter* event_router = EventRouter::Get(context_);
  if (!event_router)
    return;

  event_router->RegisterObserver(this,
      language_settings_private::OnSpellcheckDictionariesChanged::kEventName);
  event_router->RegisterObserver(this,
      language_settings_private::OnCustomDictionaryChanged::kEventName);

  // SpellcheckService cannot be created until Profile::DoFinalInit() has been
  // called. http://crbug.com/171406
  notification_registrar_.Add(this,
      chrome::NOTIFICATION_PROFILE_ADDED,
      content::Source<Profile>(Profile::FromBrowserContext(context_)));

  pref_change_registrar_.Init(Profile::FromBrowserContext(context_)->
      GetPrefs());

  StartOrStopListeningForSpellcheckChanges();
}

LanguageSettingsPrivateDelegate::~LanguageSettingsPrivateDelegate() {
  DCHECK(!listening_spellcheck_);
  pref_change_registrar_.RemoveAll();
  notification_registrar_.RemoveAll();
}

LanguageSettingsPrivateDelegate* LanguageSettingsPrivateDelegate::Create(
    content::BrowserContext* context) {
  return new LanguageSettingsPrivateDelegate(context);
}

ScopedVector<language_settings_private::SpellcheckDictionaryStatus>
LanguageSettingsPrivateDelegate::GetHunspellDictionaryStatuses() {
  ScopedVector<language_settings_private::SpellcheckDictionaryStatus> statuses;
  for (const auto& dictionary : GetHunspellDictionaries()) {
    if (!dictionary)
      continue;
    scoped_ptr<language_settings_private::SpellcheckDictionaryStatus> status(
        new language_settings_private::SpellcheckDictionaryStatus());
    status->language_code = dictionary->GetLanguage();
    status->is_ready = dictionary->IsReady();
    if (!status->is_ready) {
      if (dictionary->IsDownloadInProgress())
        status->is_downloading.reset(new bool(true));
      if (dictionary->IsDownloadFailure())
        status->download_failed.reset(new bool(true));
    }
    statuses.push_back(std::move(status));
  }
  return statuses;
}

void LanguageSettingsPrivateDelegate::Shutdown() {
  // Unregister with the event router. We first check and see if there *is* an
  // event router, because some unit tests try to shutdown all context services,
  // but didn't initialize the event router first.
  EventRouter* event_router = EventRouter::Get(context_);
  if (event_router)
    event_router->UnregisterObserver(this);

  if (listening_spellcheck_) {
    RemoveDictionaryObservers();
    listening_spellcheck_ = false;
  }
}

void LanguageSettingsPrivateDelegate::OnListenerAdded(
    const EventListenerInfo& details) {
  // Start listening to spellcheck change events.
  if (details.event_name ==
      language_settings_private::OnSpellcheckDictionariesChanged::kEventName ||
      details.event_name ==
      language_settings_private::OnCustomDictionaryChanged::kEventName) {
    StartOrStopListeningForSpellcheckChanges();
  }
}

void LanguageSettingsPrivateDelegate::OnListenerRemoved(
    const EventListenerInfo& details) {
  // Stop listening to events if there are no more listeners.
  StartOrStopListeningForSpellcheckChanges();
}

void LanguageSettingsPrivateDelegate::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  profile_added_ = true;
  StartOrStopListeningForSpellcheckChanges();
}

void LanguageSettingsPrivateDelegate::OnHunspellDictionaryInitialized(
    const std::string& language) {
  BroadcastDictionariesChangedEvent();
}

void LanguageSettingsPrivateDelegate::OnHunspellDictionaryDownloadBegin(
    const std::string& language) {
  BroadcastDictionariesChangedEvent();
}

void LanguageSettingsPrivateDelegate::OnHunspellDictionaryDownloadSuccess(
    const std::string& language) {
  BroadcastDictionariesChangedEvent();
}

void LanguageSettingsPrivateDelegate::OnHunspellDictionaryDownloadFailure(
    const std::string& language) {
  BroadcastDictionariesChangedEvent();
}

void LanguageSettingsPrivateDelegate::OnCustomDictionaryLoaded() {
}

void LanguageSettingsPrivateDelegate::OnCustomDictionaryChanged(
    const SpellcheckCustomDictionary::Change& change) {
  std::vector<std::string> to_add(change.to_add().begin(),
                                  change.to_add().end());
  std::vector<std::string> to_remove(change.to_remove().begin(),
                                     change.to_remove().end());
  scoped_ptr<base::ListValue> args(
      language_settings_private::OnCustomDictionaryChanged::Create(
          to_add, to_remove));
  scoped_ptr<Event> extension_event(new Event(
      events::LANGUAGE_SETTINGS_PRIVATE_ON_CUSTOM_DICTIONARY_CHANGED,
      language_settings_private::OnCustomDictionaryChanged::kEventName,
      std::move(args)));
  EventRouter::Get(context_)->BroadcastEvent(std::move(extension_event));
}

void LanguageSettingsPrivateDelegate::RefreshDictionaries(
    bool was_listening, bool should_listen) {
  if (!profile_added_)
    return;
  if (was_listening)
    RemoveDictionaryObservers();
  hunspell_dictionaries_.clear();
  SpellcheckService* service = SpellcheckServiceFactory::GetForContext(
      context_);
  if (!custom_dictionary_)
    custom_dictionary_ = service->GetCustomDictionary();

  const ScopedVector<SpellcheckHunspellDictionary>& dictionaries(
      service->GetHunspellDictionaries());
  for (const auto& dictionary: dictionaries) {
    hunspell_dictionaries_.push_back(dictionary->AsWeakPtr());
    if (should_listen)
      dictionary->AddObserver(this);
  }
}

const LanguageSettingsPrivateDelegate::WeakDictionaries&
LanguageSettingsPrivateDelegate::GetHunspellDictionaries() {
  // If there are no hunspell dictionaries, or the first is invalid, refresh.
  if (!hunspell_dictionaries_.size() || !hunspell_dictionaries_.front())
    RefreshDictionaries(listening_spellcheck_, listening_spellcheck_);
  return hunspell_dictionaries_;
}

void LanguageSettingsPrivateDelegate::
    StartOrStopListeningForSpellcheckChanges() {
  EventRouter* event_router = EventRouter::Get(context_);
  bool should_listen =
      event_router->HasEventListener(language_settings_private::
          OnSpellcheckDictionariesChanged::kEventName) ||
      event_router->HasEventListener(language_settings_private::
          OnCustomDictionaryChanged::kEventName);

  if (should_listen && !listening_spellcheck_) {
    // Update and observe the hunspell dictionaries.
    RefreshDictionaries(listening_spellcheck_, should_listen);
    // Observe the dictionaries preference.
    pref_change_registrar_.Add(prefs::kSpellCheckDictionaries, base::Bind(
        &LanguageSettingsPrivateDelegate::OnSpellcheckDictionariesChanged,
        base::Unretained(this)));
    // Observe the dictionary of custom words.
    if (custom_dictionary_)
      custom_dictionary_->AddObserver(this);
  } else if (!should_listen && listening_spellcheck_) {
    // Stop observing any dictionaries that still exist.
    RemoveDictionaryObservers();
    hunspell_dictionaries_.clear();
    pref_change_registrar_.Remove(prefs::kSpellCheckDictionaries);
    if (custom_dictionary_)
      custom_dictionary_->RemoveObserver(this);
  }

  listening_spellcheck_ = should_listen;
}

void LanguageSettingsPrivateDelegate::OnSpellcheckDictionariesChanged() {
  RefreshDictionaries(listening_spellcheck_, listening_spellcheck_);
  BroadcastDictionariesChangedEvent();
}

void LanguageSettingsPrivateDelegate::BroadcastDictionariesChangedEvent() {
  std::vector<linked_ptr<language_settings_private::SpellcheckDictionaryStatus>>
      broadcast_statuses;
  ScopedVector<language_settings_private::SpellcheckDictionaryStatus> statuses =
      GetHunspellDictionaryStatuses();

  for (language_settings_private::SpellcheckDictionaryStatus* status : statuses)
    broadcast_statuses.push_back(make_linked_ptr(status));
  statuses.weak_clear();

  scoped_ptr<base::ListValue> args(
      language_settings_private::OnSpellcheckDictionariesChanged::Create(
          broadcast_statuses));
  scoped_ptr<extensions::Event> extension_event(new extensions::Event(
      events::LANGUAGE_SETTINGS_PRIVATE_ON_SPELLCHECK_DICTIONARIES_CHANGED,
      language_settings_private::OnSpellcheckDictionariesChanged::kEventName,
      std::move(args)));
  EventRouter::Get(context_)->BroadcastEvent(std::move(extension_event));
}

void LanguageSettingsPrivateDelegate::RemoveDictionaryObservers() {
  for (const auto& dictionary : hunspell_dictionaries_) {
    if (dictionary)
      dictionary->RemoveObserver(this);
  }
}

}  // namespace extensions