// 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 "chrome/browser/chromeos/input_method/input_method_manager_impl.h"

#include <algorithm>  // std::find

#include <sstream>

#include "base/basictypes.h"
#include "base/bind.h"
#include "base/hash.h"
#include "base/location.h"
#include "base/memory/scoped_ptr.h"
#include "base/metrics/histogram.h"
#include "base/metrics/sparse_histogram.h"
#include "base/prefs/pref_service.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/sys_info.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/input_method/candidate_window_controller.h"
#include "chrome/browser/chromeos/input_method/component_extension_ime_manager_impl.h"
#include "chrome/browser/chromeos/input_method/input_method_switch_recorder.h"
#include "chrome/browser/chromeos/language_preferences.h"
#include "chrome/browser/chromeos/login/session/user_session_manager.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/pref_names.h"
#include "components/user_manager/user_manager.h"
#include "third_party/icu/source/common/unicode/uloc.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/ime/chromeos/component_extension_ime_manager.h"
#include "ui/base/ime/chromeos/extension_ime_util.h"
#include "ui/base/ime/chromeos/fake_ime_keyboard.h"
#include "ui/base/ime/chromeos/ime_keyboard.h"
#include "ui/base/ime/chromeos/input_method_delegate.h"
#include "ui/base/ime/ime_bridge.h"
#include "ui/chromeos/ime/input_method_menu_item.h"
#include "ui/chromeos/ime/input_method_menu_manager.h"
#include "ui/keyboard/keyboard_controller.h"
#include "ui/keyboard/keyboard_util.h"

namespace chromeos {
namespace input_method {

namespace {

bool Contains(const std::vector<std::string>& container,
              const std::string& value) {
  return std::find(container.begin(), container.end(), value) !=
      container.end();
}

enum InputMethodCategory {
  INPUT_METHOD_CATEGORY_UNKNOWN = 0,
  INPUT_METHOD_CATEGORY_XKB,  // XKB input methods
  INPUT_METHOD_CATEGORY_ZH,   // Chinese input methods
  INPUT_METHOD_CATEGORY_JA,   // Japanese input methods
  INPUT_METHOD_CATEGORY_KO,   // Korean input methods
  INPUT_METHOD_CATEGORY_M17N, // Multilingualization input methods
  INPUT_METHOD_CATEGORY_T13N, // Transliteration input methods
  INPUT_METHOD_CATEGORY_MAX
};

InputMethodCategory GetInputMethodCategory(const std::string& input_method_id,
                                           char* first_char = NULL) {
  const std::string component_id =
      extension_ime_util::GetComponentIDByInputMethodID(input_method_id);
  InputMethodCategory category = INPUT_METHOD_CATEGORY_UNKNOWN;
  char ch = 0;
  if (base::StartsWith(component_id, "xkb:", base::CompareCase::SENSITIVE)) {
    ch = component_id[4];
    category = INPUT_METHOD_CATEGORY_XKB;
  } else if (base::StartsWith(component_id, "zh-",
                              base::CompareCase::SENSITIVE)) {
    size_t pos = component_id.find("-t-i0-");
    if (pos > 0)
      pos += 6;
    ch = component_id[pos];
    category = INPUT_METHOD_CATEGORY_ZH;
  } else if (base::StartsWith(component_id, "nacl_mozc_",
                              base::CompareCase::SENSITIVE)) {
    ch = component_id[10];
    category = INPUT_METHOD_CATEGORY_JA;
  } else if (base::StartsWith(component_id, "hangul_",
                              base::CompareCase::SENSITIVE)) {
    ch = component_id[7];
    category = INPUT_METHOD_CATEGORY_KO;
  } else if (base::StartsWith(component_id, "vkd_",
                              base::CompareCase::SENSITIVE)) {
    ch = component_id[4];
    category = INPUT_METHOD_CATEGORY_M17N;
  } else if (component_id.find("-t-i0-") > 0) {
    ch = component_id[0];
    category = INPUT_METHOD_CATEGORY_T13N;
  }

  if (first_char)
    *first_char = ch;
  return category;
}

}  // namespace

// ------------------------ InputMethodManagerImpl::StateImpl

InputMethodManagerImpl::StateImpl::StateImpl(InputMethodManagerImpl* manager,
                                             Profile* profile)
    : profile(profile), manager_(manager) {
}

InputMethodManagerImpl::StateImpl::~StateImpl() {
}

void InputMethodManagerImpl::StateImpl::InitFrom(const StateImpl& other) {
  previous_input_method = other.previous_input_method;
  current_input_method = other.current_input_method;

  active_input_method_ids = other.active_input_method_ids;

  pending_input_method_id = other.pending_input_method_id;

  enabled_extension_imes = other.enabled_extension_imes;
  extra_input_methods = other.extra_input_methods;
}

bool InputMethodManagerImpl::StateImpl::IsActive() const {
  return manager_->state_.get() == this;
}

std::string InputMethodManagerImpl::StateImpl::Dump() const {
  std::ostringstream os;

  os << "################# "
     << (profile ? profile->GetProfileUserName() : std::string("NULL"))
     << " #################\n";

  os << "previous_input_method: '"
     << previous_input_method.GetPreferredKeyboardLayout() << "'\n";
  os << "current_input_method: '"
     << current_input_method.GetPreferredKeyboardLayout() << "'\n";
  os << "active_input_method_ids (size=" << active_input_method_ids.size()
     << "):";
  for (size_t i = 0; i < active_input_method_ids.size(); ++i) {
    os << " '" << active_input_method_ids[i] << "',";
  }
  os << "\n";
  os << "enabled_extension_imes (size=" << enabled_extension_imes.size()
     << "):";
  for (size_t i = 0; i < enabled_extension_imes.size(); ++i) {
    os << " '" << enabled_extension_imes[i] << "'\n";
  }
  os << "\n";
  os << "extra_input_methods (size=" << extra_input_methods.size() << "):";
  for (std::map<std::string, InputMethodDescriptor>::const_iterator it =
           extra_input_methods.begin();
       it != extra_input_methods.end();
       ++it) {
    os << " '" << it->first << "' => '" << it->second.id() << "',\n";
  }
  os << "pending_input_method_id: '" << pending_input_method_id << "'\n";

  return os.str();
}

scoped_refptr<InputMethodManager::State>
InputMethodManagerImpl::StateImpl::Clone() const {
  scoped_refptr<StateImpl> new_state(new StateImpl(this->manager_, profile));
  new_state->InitFrom(*this);
  return scoped_refptr<InputMethodManager::State>(new_state.get());
}

scoped_ptr<InputMethodDescriptors>
InputMethodManagerImpl::StateImpl::GetActiveInputMethods() const {
  scoped_ptr<InputMethodDescriptors> result(new InputMethodDescriptors);
  // Build the active input method descriptors from the active input
  // methods cache |active_input_method_ids|.
  for (size_t i = 0; i < active_input_method_ids.size(); ++i) {
    const std::string& input_method_id = active_input_method_ids[i];
    const InputMethodDescriptor* descriptor =
        manager_->util_.GetInputMethodDescriptorFromId(input_method_id);
    if (descriptor) {
      result->push_back(*descriptor);
    } else {
      std::map<std::string, InputMethodDescriptor>::const_iterator ix =
          extra_input_methods.find(input_method_id);
      if (ix != extra_input_methods.end())
        result->push_back(ix->second);
      else
        DVLOG(1) << "Descriptor is not found for: " << input_method_id;
    }
  }
  if (result->empty()) {
    // Initially |active_input_method_ids| is empty. browser_tests might take
    // this path.
    result->push_back(
        InputMethodUtil::GetFallbackInputMethodDescriptor());
  }
  return result.Pass();
}

const std::vector<std::string>&
InputMethodManagerImpl::StateImpl::GetActiveInputMethodIds() const {
  return active_input_method_ids;
}

size_t InputMethodManagerImpl::StateImpl::GetNumActiveInputMethods() const {
  return active_input_method_ids.size();
}

const InputMethodDescriptor*
InputMethodManagerImpl::StateImpl::GetInputMethodFromId(
    const std::string& input_method_id) const {
  const InputMethodDescriptor* ime =
      manager_->util_.GetInputMethodDescriptorFromId(input_method_id);
  if (!ime) {
    std::map<std::string, InputMethodDescriptor>::const_iterator ix =
        extra_input_methods.find(input_method_id);
    if (ix != extra_input_methods.end())
      ime = &ix->second;
  }
  return ime;
}

void InputMethodManagerImpl::StateImpl::EnableLoginLayouts(
    const std::string& language_code,
    const std::vector<std::string>& initial_layouts) {
  if (manager_->ui_session_ == STATE_TERMINATING)
    return;

  // First, hardware keyboard layout should be shown.
  std::vector<std::string> candidates =
      manager_->util_.GetHardwareLoginInputMethodIds();

  // Second, locale based input method should be shown.
  // Add input methods associated with the language.
  std::vector<std::string> layouts_from_locale;
  manager_->util_.GetInputMethodIdsFromLanguageCode(
      language_code, kKeyboardLayoutsOnly, &layouts_from_locale);
  candidates.insert(candidates.end(), layouts_from_locale.begin(),
                    layouts_from_locale.end());

  std::vector<std::string> layouts;
  // First, add the initial input method ID, if it's requested, to
  // layouts, so it appears first on the list of active input
  // methods at the input language status menu.
  for (size_t i = 0; i < initial_layouts.size(); ++i) {
    if (manager_->util_.IsValidInputMethodId(initial_layouts[i])) {
      if (manager_->IsLoginKeyboard(initial_layouts[i])) {
        layouts.push_back(initial_layouts[i]);
      } else {
        DVLOG(1)
            << "EnableLoginLayouts: ignoring non-login initial keyboard layout:"
            << initial_layouts[i];
      }
    } else if (!initial_layouts[i].empty()) {
      DVLOG(1) << "EnableLoginLayouts: ignoring non-keyboard or invalid ID: "
               << initial_layouts[i];
    }
  }

  // Add candidates to layouts, while skipping duplicates.
  for (size_t i = 0; i < candidates.size(); ++i) {
    const std::string& candidate = candidates[i];
    // Not efficient, but should be fine, as the two vectors are very
    // short (2-5 items).
    if (!Contains(layouts, candidate) && manager_->IsLoginKeyboard(candidate))
      layouts.push_back(candidate);
  }

  manager_->MigrateInputMethods(&layouts);
  active_input_method_ids.swap(layouts);

  if (IsActive()) {
    // Initialize candidate window controller and widgets such as
    // candidate window, infolist and mode indicator.  Note, mode
    // indicator is used by only keyboard layout input methods.
    if (active_input_method_ids.size() > 1)
      manager_->MaybeInitializeCandidateWindowController();

    // you can pass empty |initial_layout|.
    ChangeInputMethod(initial_layouts.empty()
                          ? std::string()
                          : extension_ime_util::GetInputMethodIDByEngineID(
                                initial_layouts[0]),
                      false);
  }
}

void InputMethodManagerImpl::StateImpl::EnableLockScreenLayouts() {
  std::set<std::string> added_ids;

  const std::vector<std::string>& hardware_keyboard_ids =
      manager_->util_.GetHardwareLoginInputMethodIds();

  std::vector<std::string> new_active_input_method_ids;
  for (size_t i = 0; i < active_input_method_ids.size(); ++i) {
    const std::string& input_method_id = active_input_method_ids[i];
    // Skip if it's not a keyboard layout. Drop input methods including
    // extension ones.
    if (!manager_->IsLoginKeyboard(input_method_id) ||
        added_ids.count(input_method_id)) {
      continue;
    }
    new_active_input_method_ids.push_back(input_method_id);
    added_ids.insert(input_method_id);
  }

  // We'll add the hardware keyboard if it's not included in
  // |active_input_method_ids| so that the user can always use the hardware
  // keyboard on the screen locker.
  for (size_t i = 0; i < hardware_keyboard_ids.size(); ++i) {
    if (added_ids.count(hardware_keyboard_ids[i]))
      continue;
    new_active_input_method_ids.push_back(hardware_keyboard_ids[i]);
    added_ids.insert(hardware_keyboard_ids[i]);
  }

  active_input_method_ids.swap(new_active_input_method_ids);

  // Re-check current_input_method.
  ChangeInputMethod(current_input_method.id(), false);
}

// Adds new input method to given list.
bool InputMethodManagerImpl::StateImpl::EnableInputMethodImpl(
    const std::string& input_method_id,
    std::vector<std::string>* new_active_input_method_ids) const {
  DCHECK(new_active_input_method_ids);
  if (!manager_->util_.IsValidInputMethodId(input_method_id)) {
    DVLOG(1) << "EnableInputMethod: Invalid ID: " << input_method_id;
    return false;
  }

  if (!Contains(*new_active_input_method_ids, input_method_id))
    new_active_input_method_ids->push_back(input_method_id);

  return true;
}

bool InputMethodManagerImpl::StateImpl::EnableInputMethod(
    const std::string& input_method_id) {
  if (!EnableInputMethodImpl(input_method_id, &active_input_method_ids))
    return false;

  manager_->ReconfigureIMFramework(this);
  return true;
}

bool InputMethodManagerImpl::StateImpl::ReplaceEnabledInputMethods(
    const std::vector<std::string>& new_active_input_method_ids) {
  if (manager_->ui_session_ == STATE_TERMINATING)
    return false;

  // Filter unknown or obsolete IDs.
  std::vector<std::string> new_active_input_method_ids_filtered;

  for (size_t i = 0; i < new_active_input_method_ids.size(); ++i)
    EnableInputMethodImpl(new_active_input_method_ids[i],
                          &new_active_input_method_ids_filtered);

  if (new_active_input_method_ids_filtered.empty()) {
    DVLOG(1) << "ReplaceEnabledInputMethods: No valid input method ID";
    return false;
  }

  // Copy extension IDs to |new_active_input_method_ids_filtered|. We have to
  // keep relative order of the extension input method IDs.
  for (size_t i = 0; i < active_input_method_ids.size(); ++i) {
    const std::string& input_method_id = active_input_method_ids[i];
    if (extension_ime_util::IsExtensionIME(input_method_id))
      new_active_input_method_ids_filtered.push_back(input_method_id);
  }
  active_input_method_ids.swap(new_active_input_method_ids_filtered);
  manager_->MigrateInputMethods(&active_input_method_ids);

  manager_->ReconfigureIMFramework(this);

  // If |current_input_method| is no longer in |active_input_method_ids|,
  // ChangeInputMethod() picks the first one in |active_input_method_ids|.
  ChangeInputMethod(current_input_method.id(), false);

  // Record histogram for active input method count.
  UMA_HISTOGRAM_COUNTS("InputMethod.ActiveCount",
                       active_input_method_ids.size());

  return true;
}

void InputMethodManagerImpl::StateImpl::ChangeInputMethod(
    const std::string& input_method_id,
    bool show_message) {
  if (manager_->ui_session_ == STATE_TERMINATING)
    return;

  bool notify_menu = false;

  // Always lookup input method, even if it is the same as
  // |current_input_method| because If it is no longer in
  // |active_input_method_ids|, pick the first one in
  // |active_input_method_ids|.
  const InputMethodDescriptor* descriptor =
      manager_->LookupInputMethod(input_method_id, this);
  if (!descriptor) {
    descriptor = manager_->LookupInputMethod(
        manager_->util_.MigrateInputMethod(input_method_id), this);
    if (!descriptor)
      return;
  }

  // For 3rd party IME, when the user just logged in, SetEnabledExtensionImes
  // happens after activating the 3rd party IME.
  // So here to record the 3rd party IME to be activated, and activate it
  // when SetEnabledExtensionImes happens later.
  if (MethodAwaitsExtensionLoad(descriptor->id()))
    pending_input_method_id = descriptor->id();

  if (descriptor->id() != current_input_method.id()) {
    previous_input_method = current_input_method;
    current_input_method = *descriptor;
    notify_menu = true;
  }

  // Always change input method even if it is the same.
  // TODO(komatsu): Revisit if this is neccessary.
  if (IsActive())
    manager_->ChangeInputMethodInternal(*descriptor, profile, show_message,
                                        notify_menu);
  manager_->RecordInputMethodUsage(current_input_method.id());
}

bool InputMethodManagerImpl::StateImpl::MethodAwaitsExtensionLoad(
    const std::string& input_method_id) const {
  // For 3rd party IME, when the user just logged in, SetEnabledExtensionImes
  // happens after activating the 3rd party IME.
  // So here to record the 3rd party IME to be activated, and activate it
  // when SetEnabledExtensionImes happens later.
  return !InputMethodIsActivated(input_method_id) &&
         extension_ime_util::IsExtensionIME(input_method_id);
}

void InputMethodManagerImpl::StateImpl::AddInputMethodExtension(
    const std::string& extension_id,
    const InputMethodDescriptors& descriptors,
    ui::IMEEngineHandlerInterface* engine) {
  if (manager_->ui_session_ == STATE_TERMINATING)
    return;

  DCHECK(engine);

  manager_->engine_map_[profile][extension_id] = engine;

  bool contain = false;
  for (size_t i = 0; i < descriptors.size(); i++) {
    const InputMethodDescriptor& descriptor = descriptors[i];
    const std::string& id = descriptor.id();
    extra_input_methods[id] = descriptor;
    if (Contains(enabled_extension_imes, id)) {
      if (!Contains(active_input_method_ids, id)) {
        active_input_method_ids.push_back(id);
      } else {
        DVLOG(1) << "AddInputMethodExtension: already added: " << id << ", "
                 << descriptor.name();
      }
      contain = true;
    }
  }

  if (IsActive()) {
    if (extension_id == extension_ime_util::GetExtensionIDFromInputMethodID(
                            current_input_method.id())) {
      ui::IMEBridge::Get()->SetCurrentEngineHandler(engine);
      engine->Enable(extension_ime_util::GetComponentIDByInputMethodID(
          current_input_method.id()));
    }

    // Ensure that the input method daemon is running.
    if (contain)
      manager_->MaybeInitializeCandidateWindowController();
  }
}

void InputMethodManagerImpl::StateImpl::RemoveInputMethodExtension(
    const std::string& extension_id) {
  // Remove the active input methods with |extension_id|.
  std::vector<std::string> new_active_input_method_ids;
  for (size_t i = 0; i < active_input_method_ids.size(); ++i) {
    if (extension_id != extension_ime_util::GetExtensionIDFromInputMethodID(
                            active_input_method_ids[i]))
      new_active_input_method_ids.push_back(active_input_method_ids[i]);
  }
  active_input_method_ids.swap(new_active_input_method_ids);

  // Remove the extra input methods with |extension_id|.
  std::map<std::string, InputMethodDescriptor> new_extra_input_methods;
  for (std::map<std::string, InputMethodDescriptor>::iterator i =
           extra_input_methods.begin();
       i != extra_input_methods.end();
       ++i) {
    if (extension_id !=
        extension_ime_util::GetExtensionIDFromInputMethodID(i->first))
      new_extra_input_methods[i->first] = i->second;
  }
  extra_input_methods.swap(new_extra_input_methods);

  if (IsActive()) {
    if (ui::IMEBridge::Get()->GetCurrentEngineHandler() ==
        manager_->engine_map_[profile][extension_id]) {
      ui::IMEBridge::Get()->SetCurrentEngineHandler(NULL);
    }
    manager_->engine_map_[profile].erase(extension_id);
  }

  // If |current_input_method| is no longer in |active_input_method_ids|,
  // switch to the first one in |active_input_method_ids|.
  ChangeInputMethod(current_input_method.id(), false);
}

void InputMethodManagerImpl::StateImpl::GetInputMethodExtensions(
    InputMethodDescriptors* result) {
  // Build the extension input method descriptors from the extra input
  // methods cache |extra_input_methods|.
  std::map<std::string, InputMethodDescriptor>::iterator iter;
  for (iter = extra_input_methods.begin(); iter != extra_input_methods.end();
       ++iter) {
    if (extension_ime_util::IsExtensionIME(iter->first))
      result->push_back(iter->second);
  }
}

void InputMethodManagerImpl::StateImpl::SetEnabledExtensionImes(
    std::vector<std::string>* ids) {
  enabled_extension_imes.clear();
  enabled_extension_imes.insert(
      enabled_extension_imes.end(), ids->begin(), ids->end());
  bool active_imes_changed = false;
  bool switch_to_pending = false;

  for (std::map<std::string, InputMethodDescriptor>::iterator extra_iter =
           extra_input_methods.begin();
       extra_iter != extra_input_methods.end();
       ++extra_iter) {
    if (extension_ime_util::IsComponentExtensionIME(extra_iter->first))
      continue;  // Do not filter component extension.

    if (pending_input_method_id == extra_iter->first)
      switch_to_pending = true;

    std::vector<std::string>::iterator active_iter =
        std::find(active_input_method_ids.begin(),
                  active_input_method_ids.end(),
                  extra_iter->first);

    bool active = active_iter != active_input_method_ids.end();
    bool enabled = Contains(enabled_extension_imes, extra_iter->first);

    if (active && !enabled)
      active_input_method_ids.erase(active_iter);

    if (!active && enabled)
      active_input_method_ids.push_back(extra_iter->first);

    if (active == !enabled)
      active_imes_changed = true;
  }

  if (IsActive() && active_imes_changed) {
    manager_->MaybeInitializeCandidateWindowController();

    if (switch_to_pending) {
      ChangeInputMethod(pending_input_method_id, false);
      pending_input_method_id.clear();
    } else {
      // If |current_input_method| is no longer in |active_input_method_ids_|,
      // switch to the first one in |active_input_method_ids_|.
      ChangeInputMethod(current_input_method.id(), false);
    }
  }
}

void InputMethodManagerImpl::StateImpl::SetInputMethodLoginDefaultFromVPD(
    const std::string& locale,
    const std::string& oem_layout) {
  std::string layout;
  if (!oem_layout.empty()) {
    // If the OEM layout information is provided, use it.
    layout = oem_layout;
  } else {
    // Otherwise, determine the hardware keyboard from the locale.
    std::vector<std::string> input_method_ids;
    if (manager_->util_.GetInputMethodIdsFromLanguageCode(
            locale,
            chromeos::input_method::kKeyboardLayoutsOnly,
            &input_method_ids)) {
      // The output list |input_method_ids| is sorted by popularity, hence
      // input_method_ids[0] now contains the most popular keyboard layout
      // for the given locale.
      DCHECK_GE(input_method_ids.size(), 1U);
      layout = input_method_ids[0];
    }
  }

  if (layout.empty())
    return;

  std::vector<std::string> layouts = base::SplitString(
      layout, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
  manager_->MigrateInputMethods(&layouts);

  PrefService* prefs = g_browser_process->local_state();
  prefs->SetString(prefs::kHardwareKeyboardLayout,
                   base::JoinString(layouts, ","));

  // This asks the file thread to save the prefs (i.e. doesn't block).
  // The latest values of Local State reside in memory so we can safely
  // get the value of kHardwareKeyboardLayout even if the data is not
  // yet saved to disk.
  prefs->CommitPendingWrite();

  manager_->util_.UpdateHardwareLayoutCache();

  EnableLoginLayouts(locale, layouts);
  manager_->LoadNecessaryComponentExtensions(this);
}

void InputMethodManagerImpl::StateImpl::SetInputMethodLoginDefault() {
  // Set up keyboards. For example, when |locale| is "en-US", enable US qwerty
  // and US dvorak keyboard layouts.
  if (g_browser_process && g_browser_process->local_state()) {
    const std::string locale = g_browser_process->GetApplicationLocale();
    // If the preferred keyboard for the login screen has been saved, use it.
    PrefService* prefs = g_browser_process->local_state();
    std::string initial_input_method_id =
        prefs->GetString(chromeos::language_prefs::kPreferredKeyboardLayout);
    std::vector<std::string> input_methods_to_be_enabled;
    if (initial_input_method_id.empty()) {
      // If kPreferredKeyboardLayout is not specified, use the hardware layout.
      input_methods_to_be_enabled =
          manager_->util_.GetHardwareLoginInputMethodIds();
    } else {
      input_methods_to_be_enabled.push_back(initial_input_method_id);
    }
    EnableLoginLayouts(locale, input_methods_to_be_enabled);
    manager_->LoadNecessaryComponentExtensions(this);
  }
}

bool InputMethodManagerImpl::StateImpl::CanCycleInputMethod() {
  // Sanity checks.
  if (active_input_method_ids.empty()) {
    DVLOG(1) << "active input method is empty";
    return false;
  }

  if (current_input_method.id().empty()) {
    DVLOG(1) << "current_input_method is unknown";
    return false;
  }

  // Do not consume key event if there is only one input method is enabled.
  // Ctrl+Space or Alt+Shift may be used by other application.
  return active_input_method_ids.size() > 1;
}

void InputMethodManagerImpl::StateImpl::SwitchToNextInputMethod() {
  DCHECK(CanCycleInputMethod());
  if (!CanCycleInputMethod())
    return;

  // Find the next input method and switch to it.
  SwitchToNextInputMethodInternal(active_input_method_ids,
                                  current_input_method.id());
  InputMethodSwitchRecorder::Get()->RecordSwitch(false /* by_tray_menu*/);
}

void InputMethodManagerImpl::StateImpl::SwitchToPreviousInputMethod() {
  DCHECK(CanCycleInputMethod());
  if (!CanCycleInputMethod())
    return;

  if (previous_input_method.id().empty() ||
      previous_input_method.id() == current_input_method.id()) {
    SwitchToNextInputMethod();
    return;
  }

  std::vector<std::string>::const_iterator iter =
      std::find(active_input_method_ids.begin(),
                active_input_method_ids.end(),
                previous_input_method.id());
  if (iter == active_input_method_ids.end()) {
    // previous_input_method is not supported.
    SwitchToNextInputMethod();
    return;
  }
  ChangeInputMethod(*iter, true);
  InputMethodSwitchRecorder::Get()->RecordSwitch(false /* by_tray_menu*/);
}

bool InputMethodManagerImpl::StateImpl::CanSwitchInputMethod(
    const ui::Accelerator& accelerator) {
  // If none of the input methods associated with |accelerator| are active, we
  // should ignore the accelerator. For example, we should just ignore
  // VKEY_HANGUL when mozc-hangul is not active.
  std::vector<std::string> candidate_ids;
  GetCandidateInputMethodsForAccelerator(accelerator, &candidate_ids);
  return !candidate_ids.empty();
}

void InputMethodManagerImpl::StateImpl::SwitchInputMethod(
    const ui::Accelerator& accelerator) {
  std::vector<std::string> candidate_ids;
  GetCandidateInputMethodsForAccelerator(accelerator, &candidate_ids);
  DCHECK(!candidate_ids.empty());
  if (!candidate_ids.empty()) {
    SwitchToNextInputMethodInternal(candidate_ids, current_input_method.id());
    InputMethodSwitchRecorder::Get()->RecordSwitch(false /* by_tray_menu*/);
  }
}

void InputMethodManagerImpl::StateImpl::SwitchToNextInputMethodInternal(
    const std::vector<std::string>& input_method_ids,
    const std::string& current_input_methodid) {
  std::vector<std::string>::const_iterator iter = std::find(
      input_method_ids.begin(), input_method_ids.end(), current_input_methodid);
  if (iter != input_method_ids.end())
    ++iter;
  if (iter == input_method_ids.end())
    iter = input_method_ids.begin();
  ChangeInputMethod(*iter, true);
}

void InputMethodManagerImpl::StateImpl::GetCandidateInputMethodsForAccelerator(
    const ui::Accelerator& accelerator,
    std::vector<std::string>* out_candidate_ids) {
  out_candidate_ids->clear();

  // Sanity check.
  if (active_input_method_ids.empty()) {
    DVLOG(1) << "active input method is empty";
    return;
  }

  std::vector<std::string> input_method_ids_to_switch;
  switch (accelerator.key_code()) {
    case ui::VKEY_CONVERT:  // Henkan key on JP106 keyboard
      input_method_ids_to_switch.push_back(
          extension_ime_util::GetInputMethodIDByEngineID("nacl_mozc_jp"));
      break;
    case ui::VKEY_NONCONVERT:  // Muhenkan key on JP106 keyboard
      input_method_ids_to_switch.push_back(
          extension_ime_util::GetInputMethodIDByEngineID("xkb:jp::jpn"));
      break;
    case ui::VKEY_DBE_SBCSCHAR:  // ZenkakuHankaku key on JP106 keyboard
    case ui::VKEY_DBE_DBCSCHAR:
      input_method_ids_to_switch.push_back(
          extension_ime_util::GetInputMethodIDByEngineID("nacl_mozc_jp"));
      input_method_ids_to_switch.push_back(
          extension_ime_util::GetInputMethodIDByEngineID("xkb:jp::jpn"));
      break;
    default:
      NOTREACHED();
      break;
  }
  if (input_method_ids_to_switch.empty()) {
    DVLOG(1) << "Unexpected VKEY: " << accelerator.key_code();
    return;
  }

  // Obtain the intersection of input_method_ids_to_switch and
  // active_input_method_ids.
  for (size_t i = 0; i < input_method_ids_to_switch.size(); ++i) {
    const std::string& id = input_method_ids_to_switch[i];
    if (Contains(active_input_method_ids, id))
      out_candidate_ids->push_back(id);
  }
}

InputMethodDescriptor InputMethodManagerImpl::StateImpl::GetCurrentInputMethod()
    const {
  if (current_input_method.id().empty())
    return InputMethodUtil::GetFallbackInputMethodDescriptor();

  return current_input_method;
}

bool InputMethodManagerImpl::StateImpl::InputMethodIsActivated(
    const std::string& input_method_id) const {
  return Contains(active_input_method_ids, input_method_id);
}

// ------------------------ InputMethodManagerImpl
bool InputMethodManagerImpl::IsLoginKeyboard(
    const std::string& layout) const {
  return util_.IsLoginKeyboard(layout);
}

bool InputMethodManagerImpl::MigrateInputMethods(
    std::vector<std::string>* input_method_ids) {
  return util_.MigrateInputMethods(input_method_ids);
}

// Starts or stops the system input method framework as needed.
void InputMethodManagerImpl::ReconfigureIMFramework(
    InputMethodManagerImpl::StateImpl* state) {
  LoadNecessaryComponentExtensions(state);

  // Initialize candidate window controller and widgets such as
  // candidate window, infolist and mode indicator.  Note, mode
  // indicator is used by only keyboard layout input methods.
  if (state_.get() == state)
    MaybeInitializeCandidateWindowController();
}

void InputMethodManagerImpl::SetState(
    scoped_refptr<InputMethodManager::State> state) {
  DCHECK(state.get());
  InputMethodManagerImpl::StateImpl* new_impl_state =
      static_cast<InputMethodManagerImpl::StateImpl*>(state.get());

  state_ = new_impl_state;

  if (state_.get() && state_->active_input_method_ids.size()) {
    // Initialize candidate window controller and widgets such as
    // candidate window, infolist and mode indicator.  Note, mode
    // indicator is used by only keyboard layout input methods.
    MaybeInitializeCandidateWindowController();

    // Always call ChangeInputMethodInternal even when the input method id
    // remain unchanged, because onActivate event needs to be sent to IME
    // extension to update the current screen type correctly.
    ChangeInputMethodInternal(state_->current_input_method, state_->profile,
                              false /* show_message */, true /* notify_menu */);
  }
}

scoped_refptr<InputMethodManager::State>
InputMethodManagerImpl::GetActiveIMEState() {
  return scoped_refptr<InputMethodManager::State>(state_.get());
}

InputMethodManagerImpl::InputMethodManagerImpl(
    scoped_ptr<InputMethodDelegate> delegate,
    bool enable_extension_loading)
    : delegate_(delegate.Pass()),
      ui_session_(STATE_LOGIN_SCREEN),
      state_(NULL),
      util_(delegate_.get()),
      component_extension_ime_manager_(new ComponentExtensionIMEManager()),
      enable_extension_loading_(enable_extension_loading) {
  if (base::SysInfo::IsRunningOnChromeOS())
    keyboard_.reset(ImeKeyboard::Create());
  else
    keyboard_.reset(new FakeImeKeyboard());

  // Initializes the system IME list.
  scoped_ptr<ComponentExtensionIMEManagerDelegate> comp_delegate(
      new ComponentExtensionIMEManagerImpl());
  component_extension_ime_manager_->Initialize(comp_delegate.Pass());
  const InputMethodDescriptors& descriptors =
      component_extension_ime_manager_->GetAllIMEAsInputMethodDescriptor();
  util_.ResetInputMethods(descriptors);
}

InputMethodManagerImpl::~InputMethodManagerImpl() {
  if (candidate_window_controller_.get())
    candidate_window_controller_->RemoveObserver(this);
}

void InputMethodManagerImpl::RecordInputMethodUsage(
    const std::string& input_method_id) {
  UMA_HISTOGRAM_ENUMERATION("InputMethod.Category",
                            GetInputMethodCategory(input_method_id),
                            INPUT_METHOD_CATEGORY_MAX);
  UMA_HISTOGRAM_SPARSE_SLOWLY(
      "InputMethod.ID2", static_cast<int32_t>(base::Hash(input_method_id)));
}

void InputMethodManagerImpl::AddObserver(
    InputMethodManager::Observer* observer) {
  observers_.AddObserver(observer);
}

void InputMethodManagerImpl::AddCandidateWindowObserver(
    InputMethodManager::CandidateWindowObserver* observer) {
  candidate_window_observers_.AddObserver(observer);
}

void InputMethodManagerImpl::RemoveObserver(
    InputMethodManager::Observer* observer) {
  observers_.RemoveObserver(observer);
}

void InputMethodManagerImpl::RemoveCandidateWindowObserver(
    InputMethodManager::CandidateWindowObserver* observer) {
  candidate_window_observers_.RemoveObserver(observer);
}

InputMethodManager::UISessionState InputMethodManagerImpl::GetUISessionState() {
  return ui_session_;
}

void InputMethodManagerImpl::SetUISessionState(UISessionState new_ui_session) {
  ui_session_ = new_ui_session;
  switch (ui_session_) {
    case STATE_LOGIN_SCREEN:
      break;
    case STATE_BROWSER_SCREEN:
      break;
    case STATE_LOCK_SCREEN:
      break;
    case STATE_TERMINATING: {
      if (candidate_window_controller_.get())
        candidate_window_controller_.reset();
      break;
    }
  }
}

scoped_ptr<InputMethodDescriptors>
InputMethodManagerImpl::GetSupportedInputMethods() const {
  return scoped_ptr<InputMethodDescriptors>(new InputMethodDescriptors).Pass();
}

const InputMethodDescriptor* InputMethodManagerImpl::LookupInputMethod(
    const std::string& input_method_id,
    InputMethodManagerImpl::StateImpl* state) {
  DCHECK(state);

  std::string input_method_id_to_switch = input_method_id;

  // Sanity check
  if (!state->InputMethodIsActivated(input_method_id)) {
    scoped_ptr<InputMethodDescriptors> input_methods(
        state->GetActiveInputMethods());
    DCHECK(!input_methods->empty());
    input_method_id_to_switch = input_methods->at(0).id();
    if (!input_method_id.empty()) {
      DVLOG(1) << "Can't change the current input method to "
               << input_method_id << " since the engine is not enabled. "
               << "Switch to " << input_method_id_to_switch << " instead.";
    }
  }

  const InputMethodDescriptor* descriptor = NULL;
  if (extension_ime_util::IsExtensionIME(input_method_id_to_switch)) {
    DCHECK(state->extra_input_methods.find(input_method_id_to_switch) !=
           state->extra_input_methods.end());
    descriptor = &(state->extra_input_methods[input_method_id_to_switch]);
  } else {
    descriptor =
        util_.GetInputMethodDescriptorFromId(input_method_id_to_switch);
    if (!descriptor)
      LOG(ERROR) << "Unknown input method id: " << input_method_id_to_switch;
  }
  DCHECK(descriptor);
  return descriptor;
}

void InputMethodManagerImpl::ChangeInputMethodInternal(
    const InputMethodDescriptor& descriptor,
    Profile* profile,
    bool show_message,
    bool notify_menu) {
  // No need to switch input method when terminating.
  if (ui_session_ == STATE_TERMINATING)
    return;

  if (candidate_window_controller_.get())
    candidate_window_controller_->Hide();

  if (notify_menu) {
    // Clear property list.  Property list would be updated by
    // extension IMEs via IMEEngineHandlerInterface::(Set|Update)MenuItems.
    // If the current input method is a keyboard layout, empty
    // properties are sufficient.
    const ui::ime::InputMethodMenuItemList empty_menu_item_list;
    ui::ime::InputMethodMenuManager* input_method_menu_manager =
        ui::ime::InputMethodMenuManager::GetInstance();
    input_method_menu_manager->SetCurrentInputMethodMenuItemList(
            empty_menu_item_list);
  }

  // Disable the current engine handler.
  ui::IMEEngineHandlerInterface* engine =
      ui::IMEBridge::Get()->GetCurrentEngineHandler();
  if (engine)
    engine->Disable();

  // Configure the next engine handler.
  // This must be after |current_input_method| has been set to new input
  // method, because engine's Enable() method needs to access it.
  const std::string& extension_id =
      extension_ime_util::GetExtensionIDFromInputMethodID(descriptor.id());
  const std::string& component_id =
      extension_ime_util::GetComponentIDByInputMethodID(descriptor.id());
  engine = engine_map_[profile][extension_id];

  ui::IMEBridge::Get()->SetCurrentEngineHandler(engine);

  if (engine) {
    engine->Enable(component_id);
  } else {
    // If no engine to enable, cancel the virtual keyboard url override so that
    // it can use the fallback system virtual keyboard UI.
    keyboard::SetOverrideContentUrl(GURL());
    keyboard::KeyboardController* keyboard_controller =
        keyboard::KeyboardController::GetInstance();
    if (keyboard_controller)
      keyboard_controller->Reload();
  }

  // Change the keyboard layout to a preferred layout for the input method.
  if (!keyboard_->SetCurrentKeyboardLayoutByName(
          descriptor.GetPreferredKeyboardLayout())) {
    LOG(ERROR) << "Failed to change keyboard layout to "
               << descriptor.GetPreferredKeyboardLayout();
  }

  // Update input method indicators (e.g. "US", "DV") in Chrome windows.
  FOR_EACH_OBSERVER(InputMethodManager::Observer, observers_,
                    InputMethodChanged(this, profile, show_message));
}

void InputMethodManagerImpl::LoadNecessaryComponentExtensions(
    InputMethodManagerImpl::StateImpl* state) {
  // Load component extensions but also update |active_input_method_ids| as
  // some component extension IMEs may have been removed from the Chrome OS
  // image. If specified component extension IME no longer exists, falling back
  // to an existing IME.
  DCHECK(state);
  std::vector<std::string> unfiltered_input_method_ids;
  unfiltered_input_method_ids.swap(state->active_input_method_ids);
  for (size_t i = 0; i < unfiltered_input_method_ids.size(); ++i) {
    if (!extension_ime_util::IsComponentExtensionIME(
        unfiltered_input_method_ids[i])) {
      // Legacy IMEs or xkb layouts are alwayes active.
      state->active_input_method_ids.push_back(unfiltered_input_method_ids[i]);
    } else if (component_extension_ime_manager_->IsWhitelisted(
        unfiltered_input_method_ids[i])) {
      if (enable_extension_loading_) {
        component_extension_ime_manager_->LoadComponentExtensionIME(
            state->profile, unfiltered_input_method_ids[i]);
      }

      state->active_input_method_ids.push_back(unfiltered_input_method_ids[i]);
    }
  }
}

void InputMethodManagerImpl::ActivateInputMethodMenuItem(
    const std::string& key) {
  DCHECK(!key.empty());

  if (ui::ime::InputMethodMenuManager::GetInstance()->
      HasInputMethodMenuItemForKey(key)) {
    ui::IMEEngineHandlerInterface* engine =
        ui::IMEBridge::Get()->GetCurrentEngineHandler();
    if (engine)
      engine->PropertyActivate(key);
    return;
  }

  DVLOG(1) << "ActivateInputMethodMenuItem: unknown key: " << key;
}

bool InputMethodManagerImpl::IsISOLevel5ShiftUsedByCurrentInputMethod() const {
  return keyboard_->IsISOLevel5ShiftAvailable();
}

bool InputMethodManagerImpl::IsAltGrUsedByCurrentInputMethod() const {
  return keyboard_->IsAltGrAvailable();
}

ImeKeyboard* InputMethodManagerImpl::GetImeKeyboard() {
  return keyboard_.get();
}

InputMethodUtil* InputMethodManagerImpl::GetInputMethodUtil() {
  return &util_;
}

ComponentExtensionIMEManager*
    InputMethodManagerImpl::GetComponentExtensionIMEManager() {
  return component_extension_ime_manager_.get();
}

scoped_refptr<InputMethodManager::State> InputMethodManagerImpl::CreateNewState(
    Profile* profile) {
  StateImpl* new_state = new StateImpl(this, profile);

  // Active IM should be set to owner/user's default.
  PrefService* prefs = g_browser_process->local_state();
  PrefService* user_prefs = profile ? profile->GetPrefs() : nullptr;
  std::string initial_input_method_id;
  if (user_prefs) {
    initial_input_method_id =
        user_prefs->GetString(prefs::kLanguageCurrentInputMethod);
  }
  if (initial_input_method_id.empty()) {
    initial_input_method_id =
        prefs->GetString(chromeos::language_prefs::kPreferredKeyboardLayout);
  }

  const InputMethodDescriptor* descriptor =
      GetInputMethodUtil()->GetInputMethodDescriptorFromId(
          initial_input_method_id.empty()
              ? GetInputMethodUtil()->GetFallbackInputMethodDescriptor().id()
              : initial_input_method_id);
  if (descriptor) {
    new_state->active_input_method_ids.push_back(descriptor->id());
    new_state->current_input_method = *descriptor;
  }
  return scoped_refptr<InputMethodManager::State>(new_state);
}

void InputMethodManagerImpl::SetCandidateWindowControllerForTesting(
    CandidateWindowController* candidate_window_controller) {
  candidate_window_controller_.reset(candidate_window_controller);
  candidate_window_controller_->AddObserver(this);
}

void InputMethodManagerImpl::SetImeKeyboardForTesting(ImeKeyboard* keyboard) {
  keyboard_.reset(keyboard);
}

void InputMethodManagerImpl::InitializeComponentExtensionForTesting(
    scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate) {
  component_extension_ime_manager_->Initialize(delegate.Pass());
  util_.ResetInputMethods(
      component_extension_ime_manager_->GetAllIMEAsInputMethodDescriptor());
}

void InputMethodManagerImpl::CandidateClicked(int index) {
  ui::IMEEngineHandlerInterface* engine =
      ui::IMEBridge::Get()->GetCurrentEngineHandler();
  if (engine)
    engine->CandidateClicked(index);
}

void InputMethodManagerImpl::CandidateWindowOpened() {
  FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver,
                    candidate_window_observers_,
                    CandidateWindowOpened(this));
}

void InputMethodManagerImpl::CandidateWindowClosed() {
  FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver,
                    candidate_window_observers_,
                    CandidateWindowClosed(this));
}

void InputMethodManagerImpl::MaybeInitializeCandidateWindowController() {
  if (candidate_window_controller_.get())
    return;

  candidate_window_controller_.reset(
      CandidateWindowController::CreateCandidateWindowController());
  candidate_window_controller_->AddObserver(this);
}

}  // namespace input_method
}  // namespace chromeos