// Copyright 2013 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 "chromeos/ime/component_extension_ime_manager.h"

#include "base/logging.h"
#include "base/strings/string_util.h"
#include "chromeos/ime/extension_ime_util.h"

namespace chromeos {

namespace {

// The whitelist for enabling extension based xkb keyboards at login session.
const char* kLoginLayoutWhitelist[] = {
  "be",
  "br",
  "ca",
  "ca(eng)",
  "ca(multix)",
  "ch",
  "ch(fr)",
  "cz",
  "cz(qwerty)",
  "de",
  "de(neo)",
  "dk",
  "ee",
  "es",
  "es(cat)",
  "fi",
  "fr",
  "gb(dvorak)",
  "gb(extd)",
  "hr",
  "hu",
  "is",
  "it",
  "jp",
  "latam",
  "lt",
  "lv(apostrophe)",
  "no",
  "pl",
  "pt",
  "ro",
  "se",
  "si",
  "tr",
  "us",
  "us(altgr-intl)",
  "us(colemak)",
  "us(dvorak)",
  "us(intl)"
};

// Gets the input method category according to the given input method id.
// This is used for sorting a list of input methods.
int GetInputMethodCategory(const std::string& id) {
  const std::string engine_id =
      chromeos::extension_ime_util::GetComponentIDByInputMethodID(id);
  if (StartsWithASCII(engine_id, "xkb:", true))
    return 0;
  if (StartsWithASCII(engine_id, "vkd_", true))
    return 1;
  if (engine_id.find("-t-i0-") != std::string::npos &&
      !StartsWithASCII(engine_id, "zh-", true)) {
    return 2;
  }
  return 3;
}

bool InputMethodCompare(const input_method::InputMethodDescriptor& im1,
                        const input_method::InputMethodDescriptor& im2) {
  return GetInputMethodCategory(im1.id()) < GetInputMethodCategory(im2.id());
}

} // namespace

ComponentExtensionEngine::ComponentExtensionEngine() {
}

ComponentExtensionEngine::~ComponentExtensionEngine() {
}

ComponentExtensionIME::ComponentExtensionIME() {
}

ComponentExtensionIME::~ComponentExtensionIME() {
}

ComponentExtensionIMEManagerDelegate::ComponentExtensionIMEManagerDelegate() {
}

ComponentExtensionIMEManagerDelegate::~ComponentExtensionIMEManagerDelegate() {
}

ComponentExtensionIMEManager::ComponentExtensionIMEManager() {
  for (size_t i = 0; i < arraysize(kLoginLayoutWhitelist); ++i) {
    login_layout_set_.insert(kLoginLayoutWhitelist[i]);
  }
}

ComponentExtensionIMEManager::~ComponentExtensionIMEManager() {
}

void ComponentExtensionIMEManager::Initialize(
    scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate) {
  delegate_ = delegate.Pass();
  std::vector<ComponentExtensionIME> ext_list = delegate_->ListIME();
  for (size_t i = 0; i < ext_list.size(); ++i) {
    ComponentExtensionIME& ext = ext_list[i];
    bool extension_exists = IsWhitelistedExtension(ext.id);
    if (!extension_exists)
      component_extension_imes_[ext.id] = ext;
    for (size_t j = 0; j < ext.engines.size(); ++j) {
      ComponentExtensionEngine& ime = ext.engines[j];
      const std::string input_method_id =
          extension_ime_util::GetComponentInputMethodID(ext.id, ime.engine_id);
      if (extension_exists && !IsWhitelisted(input_method_id))
        component_extension_imes_[ext.id].engines.push_back(ime);
      input_method_id_set_.insert(input_method_id);
    }
  }
}

bool ComponentExtensionIMEManager::LoadComponentExtensionIME(
    Profile* profile,
    const std::string& input_method_id) {
  ComponentExtensionIME ime;
  if (FindEngineEntry(input_method_id, &ime)) {
    delegate_->Load(profile, ime.id, ime.manifest, ime.path);
    return true;
  }
  return false;
}

bool ComponentExtensionIMEManager::UnloadComponentExtensionIME(
    Profile* profile,
    const std::string& input_method_id) {
  ComponentExtensionIME ime;
  if (!FindEngineEntry(input_method_id, &ime))
    return false;
  delegate_->Unload(profile, ime.id, ime.path);
  return true;
}

bool ComponentExtensionIMEManager::IsWhitelisted(
    const std::string& input_method_id) {
  return input_method_id_set_.find(input_method_id) !=
         input_method_id_set_.end();
}

bool ComponentExtensionIMEManager::IsWhitelistedExtension(
    const std::string& extension_id) {
  return component_extension_imes_.find(extension_id) !=
         component_extension_imes_.end();
}

input_method::InputMethodDescriptors
    ComponentExtensionIMEManager::GetAllIMEAsInputMethodDescriptor() {
  input_method::InputMethodDescriptors result;
  for (std::map<std::string, ComponentExtensionIME>::const_iterator it =
          component_extension_imes_.begin();
       it != component_extension_imes_.end(); ++it) {
    const ComponentExtensionIME& ext = it->second;
    for (size_t j = 0; j < ext.engines.size(); ++j) {
      const ComponentExtensionEngine& ime = ext.engines[j];
      const std::string input_method_id =
          extension_ime_util::GetComponentInputMethodID(
              ext.id, ime.engine_id);
      const std::vector<std::string>& layouts = ime.layouts;
      result.push_back(
          input_method::InputMethodDescriptor(
              input_method_id,
              ime.display_name,
              std::string(), // TODO(uekawa): Set short name.
              layouts,
              ime.language_codes,
              // Enables extension based xkb keyboards on login screen.
              extension_ime_util::IsKeyboardLayoutExtension(
                  input_method_id) && IsInLoginLayoutWhitelist(layouts),
              ime.options_page_url,
              ime.input_view_url));
    }
  }
  std::stable_sort(result.begin(), result.end(), InputMethodCompare);
  return result;
}

input_method::InputMethodDescriptors
ComponentExtensionIMEManager::GetXkbIMEAsInputMethodDescriptor() {
  input_method::InputMethodDescriptors result;
  const input_method::InputMethodDescriptors& descriptors =
      GetAllIMEAsInputMethodDescriptor();
  for (size_t i = 0; i < descriptors.size(); ++i) {
    if (extension_ime_util::IsKeyboardLayoutExtension(descriptors[i].id()))
      result.push_back(descriptors[i]);
  }
  return result;
}

bool ComponentExtensionIMEManager::FindEngineEntry(
    const std::string& input_method_id,
    ComponentExtensionIME* out_extension) {
  if (!IsWhitelisted(input_method_id))
    return false;

  std::string extension_id =
      extension_ime_util::GetExtensionIDFromInputMethodID(input_method_id);
  std::map<std::string, ComponentExtensionIME>::iterator it =
      component_extension_imes_.find(extension_id);
  if (it == component_extension_imes_.end())
    return false;

  if (out_extension)
    *out_extension = it->second;
  return true;
}

bool ComponentExtensionIMEManager::IsInLoginLayoutWhitelist(
    const std::vector<std::string>& layouts) {
  for (size_t i = 0; i < layouts.size(); ++i) {
    if (login_layout_set_.find(layouts[i]) != login_layout_set_.end())
      return true;
  }
  return false;
}

}  // namespace chromeos