// 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/ibus_controller.h"

#if defined(HAVE_IBUS)
#include <ibus.h>
#endif

#include <algorithm>  // for std::reverse.
#include <cstdio>
#include <cstring>  // for std::strcmp.
#include <set>
#include <sstream>
#include <stack>
#include <utility>

#include "base/memory/scoped_ptr.h"
#include "base/observer_list.h"
#include "chrome/browser/chromeos/input_method/input_method_engine.h"
#include "chrome/browser/chromeos/input_method/input_method_manager.h"
#include "chrome/browser/chromeos/input_method/input_method_util.h"
#include "chrome/browser/chromeos/input_method/input_method_whitelist.h"
#include "chrome/browser/chromeos/input_method/input_methods.h"

namespace chromeos {
namespace input_method {

#if defined(HAVE_IBUS)
const char kPanelObjectKey[] = "panel-object";

namespace {

// Also defined in chrome/browser/chromeos/language_preferences.h (Chrome tree).
const char kGeneralSectionName[] = "general";
const char kPreloadEnginesConfigName[] = "preload_engines";

// The list of input method property keys that we don't handle.
const char* kInputMethodPropertyKeysBlacklist[] = {
  "setup",  // menu for showing setup dialog used in anthy and hangul.
  "chewing_settings_prop",  // menu for showing setup dialog used in chewing.
  "status",  // used in m17n.
};

const char* Or(const char* str1, const char* str2) {
  return str1 ? str1 : str2;
}

// Returns true if |key| is blacklisted.
bool PropertyKeyIsBlacklisted(const char* key) {
  for (size_t i = 0; i < arraysize(kInputMethodPropertyKeysBlacklist); ++i) {
    if (!std::strcmp(key, kInputMethodPropertyKeysBlacklist[i])) {
      return true;
    }
  }
  return false;
}

// Returns IBusInputContext for |input_context_path|. NULL on errors.
IBusInputContext* GetInputContext(
    const std::string& input_context_path, IBusBus* ibus) {
  GDBusConnection* connection = ibus_bus_get_connection(ibus);
  if (!connection) {
    LOG(ERROR) << "IBusConnection is null";
    return NULL;
  }
  // This function does not issue an IBus IPC.
  IBusInputContext* context = ibus_input_context_get_input_context(
      input_context_path.c_str(), connection);
  if (!context) {
    LOG(ERROR) << "IBusInputContext is null: " << input_context_path;
  }
  return context;
}

// Returns true if |prop| has children.
bool PropertyHasChildren(IBusProperty* prop) {
  return prop && prop->sub_props && ibus_prop_list_get(prop->sub_props, 0);
}

// This function is called by and FlattenProperty() and converts IBus
// representation of a property, |ibus_prop|, to our own and push_back the
// result to |out_prop_list|. This function returns true on success, and
// returns false if sanity checks for |ibus_prop| fail.
bool ConvertProperty(IBusProperty* ibus_prop,
                     int selection_item_id,
                     InputMethodPropertyList* out_prop_list) {
  DCHECK(ibus_prop);
  DCHECK(ibus_prop->key);
  DCHECK(out_prop_list);

  // Sanity checks.
  const bool has_sub_props = PropertyHasChildren(ibus_prop);
  if (has_sub_props && (ibus_prop->type != PROP_TYPE_MENU)) {
    LOG(ERROR) << "The property has sub properties, "
               << "but the type of the property is not PROP_TYPE_MENU";
    return false;
  }
  if ((!has_sub_props) && (ibus_prop->type == PROP_TYPE_MENU)) {
    // This is usually not an error. ibus-daemon sometimes sends empty props.
    VLOG(1) << "Property list is empty";
    return false;
  }
  if (ibus_prop->type == PROP_TYPE_SEPARATOR ||
      ibus_prop->type == PROP_TYPE_MENU) {
    // This is not an error, but we don't push an item for these types.
    return true;
  }

  const bool is_selection_item = (ibus_prop->type == PROP_TYPE_RADIO);
  selection_item_id = is_selection_item ?
      selection_item_id : InputMethodProperty::kInvalidSelectionItemId;

  bool is_selection_item_checked = false;
  if (ibus_prop->state == PROP_STATE_INCONSISTENT) {
    LOG(WARNING) << "The property is in PROP_STATE_INCONSISTENT, "
                 << "which is not supported.";
  } else if ((!is_selection_item) && (ibus_prop->state == PROP_STATE_CHECKED)) {
    LOG(WARNING) << "PROP_STATE_CHECKED is meaningful only if the type is "
                 << "PROP_TYPE_RADIO.";
  } else {
    is_selection_item_checked = (ibus_prop->state == PROP_STATE_CHECKED);
  }

  if (!ibus_prop->key) {
    LOG(ERROR) << "key is NULL";
  }
  if (ibus_prop->tooltip && (!ibus_prop->tooltip->text)) {
    LOG(ERROR) << "tooltip is NOT NULL, but tooltip->text IS NULL: key="
               << Or(ibus_prop->key, "");
  }
  if (ibus_prop->label && (!ibus_prop->label->text)) {
    LOG(ERROR) << "label is NOT NULL, but label->text IS NULL: key="
               << Or(ibus_prop->key, "");
  }

  // This label will be localized on Chrome side.
  // See src/chrome/browser/chromeos/status/language_menu_l10n_util.h.
  std::string label =
      ((ibus_prop->tooltip &&
        ibus_prop->tooltip->text) ? ibus_prop->tooltip->text : "");
  if (label.empty()) {
    // Usually tooltips are more descriptive than labels.
    label = (ibus_prop->label && ibus_prop->label->text)
        ? ibus_prop->label->text : "";
  }
  if (label.empty()) {
    // ibus-pinyin has a property whose label and tooltip are empty. Fall back
    // to the key.
    label = Or(ibus_prop->key, "");
  }

  out_prop_list->push_back(InputMethodProperty(ibus_prop->key,
                                       label,
                                       is_selection_item,
                                       is_selection_item_checked,
                                       selection_item_id));
  return true;
}

// Converts |ibus_prop| to |out_prop_list|. Please note that |ibus_prop|
// may or may not have children. See the comment for FlattenPropertyList
// for details. Returns true if no error is found.
// TODO(yusukes): Write unittest.
bool FlattenProperty(IBusProperty* ibus_prop,
                     InputMethodPropertyList* out_prop_list) {
  DCHECK(ibus_prop);
  DCHECK(out_prop_list);

  int selection_item_id = -1;
  std::stack<std::pair<IBusProperty*, int> > prop_stack;
  prop_stack.push(std::make_pair(ibus_prop, selection_item_id));

  while (!prop_stack.empty()) {
    IBusProperty* prop = prop_stack.top().first;
    const int current_selection_item_id = prop_stack.top().second;
    prop_stack.pop();

    // Filter out unnecessary properties.
    if (PropertyKeyIsBlacklisted(prop->key)) {
      continue;
    }

    // Convert |prop| to InputMethodProperty and push it to |out_prop_list|.
    if (!ConvertProperty(prop, current_selection_item_id, out_prop_list)) {
      return false;
    }

    // Process childrens iteratively (if any). Push all sub properties to the
    // stack.
    if (PropertyHasChildren(prop)) {
      ++selection_item_id;
      for (int i = 0;; ++i) {
        IBusProperty* sub_prop = ibus_prop_list_get(prop->sub_props, i);
        if (!sub_prop) {
          break;
        }
        prop_stack.push(std::make_pair(sub_prop, selection_item_id));
      }
      ++selection_item_id;
    }
  }
  std::reverse(out_prop_list->begin(), out_prop_list->end());

  return true;
}

// Converts IBus representation of a property list, |ibus_prop_list| to our
// own. This function also flatten the original list (actually it's a tree).
// Returns true if no error is found. The conversion to our own type is
// necessary since our language switcher in Chrome tree don't (or can't) know
// IBus types. Here is an example:
//
// ======================================================================
// Input:
//
// --- Item-1
//  |- Item-2
//  |- SubMenuRoot --- Item-3-1
//  |               |- Item-3-2
//  |               |- Item-3-3
//  |- Item-4
//
// (Note: Item-3-X is a selection item since they're on a sub menu.)
//
// Output:
//
// Item-1, Item-2, Item-3-1, Item-3-2, Item-3-3, Item-4
// (Note: SubMenuRoot does not appear in the output.)
// ======================================================================
// TODO(yusukes): Write unittest.
bool FlattenPropertyList(
    IBusPropList* ibus_prop_list, InputMethodPropertyList* out_prop_list) {
  DCHECK(ibus_prop_list);
  DCHECK(out_prop_list);

  IBusProperty* fake_root_prop = ibus_property_new("Dummy.Key",
                                                   PROP_TYPE_MENU,
                                                   NULL, /* label */
                                                   "", /* icon */
                                                   NULL, /* tooltip */
                                                   FALSE, /* sensitive */
                                                   FALSE, /* visible */
                                                   PROP_STATE_UNCHECKED,
                                                   ibus_prop_list);
  g_return_val_if_fail(fake_root_prop, false);
  // Increase the ref count so it won't get deleted when |fake_root_prop|
  // is deleted.
  g_object_ref(ibus_prop_list);
  const bool result = FlattenProperty(fake_root_prop, out_prop_list);
  g_object_unref(fake_root_prop);

  return result;
}

// Debug print function.
const char* PropTypeToString(int prop_type) {
  switch (static_cast<IBusPropType>(prop_type)) {
    case PROP_TYPE_NORMAL:
      return "NORMAL";
    case PROP_TYPE_TOGGLE:
      return "TOGGLE";
    case PROP_TYPE_RADIO:
      return "RADIO";
    case PROP_TYPE_MENU:
      return "MENU";
    case PROP_TYPE_SEPARATOR:
      return "SEPARATOR";
  }
  return "UNKNOWN";
}

// Debug print function.
const char* PropStateToString(int prop_state) {
  switch (static_cast<IBusPropState>(prop_state)) {
    case PROP_STATE_UNCHECKED:
      return "UNCHECKED";
    case PROP_STATE_CHECKED:
      return "CHECKED";
    case PROP_STATE_INCONSISTENT:
      return "INCONSISTENT";
  }
  return "UNKNOWN";
}

// Debug print function.
std::string Spacer(int n) {
  return std::string(n, ' ');
}

std::string PrintPropList(IBusPropList *prop_list, int tree_level);
// Debug print function.
std::string PrintProp(IBusProperty *prop, int tree_level) {
  if (!prop) {
    return "";
  }

  std::stringstream stream;
  stream << Spacer(tree_level) << "=========================" << std::endl;
  stream << Spacer(tree_level) << "key: " << Or(prop->key, "<none>")
         << std::endl;
  stream << Spacer(tree_level) << "icon: " << Or(prop->icon, "<none>")
         << std::endl;
  stream << Spacer(tree_level) << "label: "
         << ((prop->label && prop->label->text) ? prop->label->text : "<none>")
         << std::endl;
  stream << Spacer(tree_level) << "tooptip: "
         << ((prop->tooltip && prop->tooltip->text)
             ? prop->tooltip->text : "<none>") << std::endl;
  stream << Spacer(tree_level) << "sensitive: "
         << (prop->sensitive ? "YES" : "NO") << std::endl;
  stream << Spacer(tree_level) << "visible: " << (prop->visible ? "YES" : "NO")
         << std::endl;
  stream << Spacer(tree_level) << "type: " << PropTypeToString(prop->type)
         << std::endl;
  stream << Spacer(tree_level) << "state: " << PropStateToString(prop->state)
         << std::endl;
  stream << Spacer(tree_level) << "sub_props: "
         << (PropertyHasChildren(prop) ? "" : "<none>") << std::endl;
  stream << PrintPropList(prop->sub_props, tree_level + 1);
  stream << Spacer(tree_level) << "=========================" << std::endl;

  return stream.str();
}

// Debug print function.
std::string PrintPropList(IBusPropList *prop_list, int tree_level) {
  if (!prop_list) {
    return "";
  }

  std::stringstream stream;
  for (int i = 0;; ++i) {
    IBusProperty* prop = ibus_prop_list_get(prop_list, i);
    if (!prop) {
      break;
    }
    stream << PrintProp(prop, tree_level);
  }
  return stream.str();
}

}  // namespace

// The real implementation of the IBusController.
class IBusControllerImpl : public IBusController {
 public:
  IBusControllerImpl()
      : ibus_(NULL),
        ibus_config_(NULL) {
  }

  ~IBusControllerImpl() {
    // Disconnect signals so the handler functions will not be called with
    // |this| which is already freed.
    if (ibus_) {
      g_signal_handlers_disconnect_by_func(
          ibus_,
          reinterpret_cast<gpointer>(G_CALLBACK(IBusBusConnectedThunk)),
          this);
      g_signal_handlers_disconnect_by_func(
          ibus_,
          reinterpret_cast<gpointer>(G_CALLBACK(IBusBusDisconnectedThunk)),
          this);
      g_signal_handlers_disconnect_by_func(
          ibus_,
          reinterpret_cast<gpointer>(G_CALLBACK(IBusBusNameOwnerChangedThunk)),
          this);

      // Disconnect signals for the panel service as well.
      IBusPanelService* ibus_panel_service = IBUS_PANEL_SERVICE(
          g_object_get_data(G_OBJECT(ibus_), kPanelObjectKey));
      if (ibus_panel_service) {
        g_signal_handlers_disconnect_by_func(
            ibus_panel_service,
            reinterpret_cast<gpointer>(G_CALLBACK(FocusInThunk)),
            this);
        g_signal_handlers_disconnect_by_func(
            ibus_panel_service,
            reinterpret_cast<gpointer>(G_CALLBACK(RegisterPropertiesThunk)),
            this);
        g_signal_handlers_disconnect_by_func(
            ibus_panel_service,
            reinterpret_cast<gpointer>(G_CALLBACK(UpdatePropertyThunk)),
            this);
      }
    }
  }

  virtual void Connect() {
    MaybeRestoreConnections();
  }

  // IBusController override.
  virtual bool StopInputMethodProcess() {
    if (!IBusConnectionsAreAlive()) {
      LOG(ERROR) << "StopInputMethodProcess: IBus connection is not alive";
      return false;
    }

    // Ask IBus to exit *asynchronously*.
    ibus_bus_exit_async(ibus_,
                        FALSE  /* do not restart */,
                        -1  /* timeout */,
                        NULL  /* cancellable */,
                        NULL  /* callback */,
                        NULL  /* user_data */);

    if (ibus_config_) {
      // Release |ibus_config_| unconditionally to make sure next
      // IBusConnectionsAreAlive() call will return false.
      g_object_unref(ibus_config_);
      ibus_config_ = NULL;
    }
    return true;
  }

  // IBusController override.
  virtual void SetImePropertyActivated(const std::string& key,
                                       bool activated) {
    if (!IBusConnectionsAreAlive()) {
      LOG(ERROR) << "SetImePropertyActivated: IBus connection is not alive";
      return;
    }
    if (key.empty()) {
      return;
    }
    if (input_context_path_.empty()) {
      LOG(ERROR) << "Input context is unknown";
      return;
    }

    IBusInputContext* context = GetInputContext(input_context_path_, ibus_);
    if (!context) {
      return;
    }
    // Activate the property *asynchronously*.
    ibus_input_context_property_activate(
        context, key.c_str(),
        (activated ? PROP_STATE_CHECKED : PROP_STATE_UNCHECKED));

    // We don't have to call ibus_proxy_destroy(context) explicitly here,
    // i.e. we can just call g_object_unref(context), since g_object_unref can
    // trigger both dispose, which is overridden by src/ibusproxy.c, and
    // finalize functions. For details, see
    // http://library.gnome.org/devel/gobject/stable/gobject-memory.html
    g_object_unref(context);
  }

  // IBusController override.
  virtual bool ChangeInputMethod(const std::string& name) {
    DCHECK(!InputMethodUtil::IsKeyboardLayout(name));

    if (!IBusConnectionsAreAlive()) {
      LOG(ERROR) << "ChangeInputMethod: IBus connection is not alive";
      return false;
    }
    if (name.empty()) {
      return false;
    }
    if (!InputMethodIdIsWhitelisted(name) &&
        name.find(kExtensionImePrefix) != 0) {
      return false;
    }

    // Clear all input method properties unconditionally.
    //
    // When switching to another input method and no text area is focused,
    // RegisterProperties signal for the new input method will NOT be sent
    // until a text area is focused. Therefore, we have to clear the old input
    // method properties here to keep the input method switcher status
    // consistent.
    DoRegisterProperties(NULL);

    // Change the global engine *asynchronously*.
    ibus_bus_set_global_engine_async(ibus_,
                                     name.c_str(),
                                     -1,  // use the default ibus timeout
                                     NULL,  // cancellable
                                     NULL,  // callback
                                     NULL);  // user_data

    UpdateUI(name.c_str());
    return true;
  }

  // IBusController override.
  virtual bool SetInputMethodConfig(const std::string& section,
                                    const std::string& config_name,
                                    const InputMethodConfigValue& value) {
    // See comments in GetImeConfig() where ibus_config_get_value() is used.
    if (!IBusConnectionsAreAlive()) {
      LOG(ERROR) << "SetInputMethodConfig: IBus connection is not alive";
      return false;
    }

    bool is_preload_engines = false;

    // Sanity check: do not preload unknown/unsupported input methods.
    std::vector<std::string> string_list;
    if ((value.type == InputMethodConfigValue::kValueTypeStringList) &&
        (section == kGeneralSectionName) &&
        (config_name == kPreloadEnginesConfigName)) {
      FilterInputMethods(value.string_list_value, &string_list);
      if (string_list.empty()) {
        return true;
      }
      is_preload_engines = true;
    } else {
      string_list = value.string_list_value;
    }

    // Convert the type of |value| from our structure to GVariant.
    GVariant* variant = NULL;
    switch (value.type) {
      case InputMethodConfigValue::kValueTypeString:
        variant = g_variant_new_string(value.string_value.c_str());
        break;
      case InputMethodConfigValue::kValueTypeInt:
        variant = g_variant_new_int32(value.int_value);
        break;
      case InputMethodConfigValue::kValueTypeBool:
        variant = g_variant_new_boolean(value.bool_value);
        break;
      case InputMethodConfigValue::kValueTypeStringList:
        GVariantBuilder variant_builder;
        g_variant_builder_init(&variant_builder, G_VARIANT_TYPE("as"));
        DCHECK(!string_list.empty());
        const size_t size = string_list.size();  // don't use string_list_value.
        for (size_t i = 0; i < size; ++i) {
          g_variant_builder_add(&variant_builder, "s", string_list[i].c_str());
        }
        variant = g_variant_builder_end(&variant_builder);
        break;
    }

    if (!variant) {
      LOG(ERROR) << "SetInputMethodConfig: variant is NULL";
      return false;
    }
    DCHECK(g_variant_is_floating(variant));

    // Set an ibus configuration value *asynchronously*.
    ibus_config_set_value_async(ibus_config_,
                                section.c_str(),
                                config_name.c_str(),
                                variant,
                                -1,  // use the default ibus timeout
                                NULL,  // cancellable
                                SetInputMethodConfigCallback,
                                g_object_ref(ibus_config_));

    // Since |variant| is floating, ibus_config_set_value_async consumes
    // (takes ownership of) the variable.

    if (is_preload_engines) {
      VLOG(1) << "SetInputMethodConfig: " << section << "/" << config_name
              << ": " << value.ToString();
    }
    return true;
  }

  // IBusController override.
  virtual void SendHandwritingStroke(const HandwritingStroke& stroke) {
    if (stroke.size() < 2) {
      LOG(WARNING) << "Empty stroke data or a single dot is passed.";
      return;
    }

    IBusInputContext* context = GetInputContext(input_context_path_, ibus_);
    if (!context) {
      return;
    }

    const size_t raw_stroke_size = stroke.size() * 2;
    scoped_array<double> raw_stroke(new double[raw_stroke_size]);
    for (size_t n = 0; n < stroke.size(); ++n) {
      raw_stroke[n * 2] = stroke[n].first;  // x
      raw_stroke[n * 2 + 1] = stroke[n].second;  // y
    }
    ibus_input_context_process_hand_writing_event(
        context, raw_stroke.get(), raw_stroke_size);
    g_object_unref(context);
  }

  // IBusController override.
  virtual void CancelHandwriting(int n_strokes) {
    IBusInputContext* context = GetInputContext(input_context_path_, ibus_);
    if (!context) {
      return;
    }
    ibus_input_context_cancel_hand_writing(context, n_strokes);
    g_object_unref(context);
  }

  virtual bool InputMethodIdIsWhitelisted(const std::string& input_method_id) {
    return whitelist_.InputMethodIdIsWhitelisted(input_method_id);
  }

  virtual bool XkbLayoutIsSupported(const std::string& xkb_layout) {
    return whitelist_.XkbLayoutIsSupported(xkb_layout);
  }

  virtual InputMethodDescriptor CreateInputMethodDescriptor(
      const std::string& id,
      const std::string& name,
      const std::string& raw_layout,
      const std::string& language_code) {
    return InputMethodDescriptor(whitelist_, id, name, raw_layout,
                                 language_code);
  }

  // IBusController override.
  virtual void AddObserver(Observer* observer) {
    observers_.AddObserver(observer);
  }

  // IBusController override.
  virtual void RemoveObserver(Observer* observer) {
    observers_.RemoveObserver(observer);
  }

 private:
  // Functions that end with Thunk are used to deal with glib callbacks.
  //
  // Note that we cannot use CHROMEG_CALLBACK_0() here as we'll define
  // IBusBusConnected() inline. If we are to define the function outside
  // of the class definition, we should use CHROMEG_CALLBACK_0() here.
  //
  // CHROMEG_CALLBACK_0(Impl,
  //                    void, IBusBusConnected, IBusBus*);
  static void IBusBusConnectedThunk(IBusBus* sender, gpointer userdata) {
    return reinterpret_cast<IBusControllerImpl*>(userdata)
        ->IBusBusConnected(sender);
  }
  static void IBusBusDisconnectedThunk(IBusBus* sender, gpointer userdata) {
    return reinterpret_cast<IBusControllerImpl*>(userdata)
        ->IBusBusDisconnected(sender);
  }

  static void IBusBusNameOwnerChangedThunk(IBusBus* sender,
                                           const gchar* name,
                                           const gchar* old_name,
                                           const gchar* new_name,
                                           gpointer userdata) {
    return reinterpret_cast<IBusControllerImpl*>(userdata)
        ->IBusBusNameOwnerChanged(sender, name, old_name, new_name);
  }
  static void FocusInThunk(IBusPanelService* sender,
                           const gchar* input_context_path,
                           gpointer userdata) {
    return reinterpret_cast<IBusControllerImpl*>(userdata)
        ->FocusIn(sender, input_context_path);
  }
  static void RegisterPropertiesThunk(IBusPanelService* sender,
                                      IBusPropList* prop_list,
                                      gpointer userdata) {
    return reinterpret_cast<IBusControllerImpl*>(userdata)
        ->RegisterProperties(sender, prop_list);
  }
  static void UpdatePropertyThunk(IBusPanelService* sender,
                                  IBusProperty* ibus_prop,
                                  gpointer userdata) {
    return reinterpret_cast<IBusControllerImpl*>(userdata)
        ->UpdateProperty(sender, ibus_prop);
  }

  // Checks if |ibus_| and |ibus_config_| connections are alive.
  bool IBusConnectionsAreAlive() {
    return ibus_ && ibus_bus_is_connected(ibus_) && ibus_config_;
  }

  // Restores connections to ibus-daemon and ibus-memconf if they are not ready.
  // If both |ibus_| and |ibus_config_| become ready, the function sends a
  // notification to Chrome.
  void MaybeRestoreConnections() {
    if (IBusConnectionsAreAlive()) {
      return;
    }
    MaybeCreateIBus();
    MaybeRestoreIBusConfig();
    if (IBusConnectionsAreAlive()) {
      ConnectPanelServiceSignals();
      VLOG(1) << "Notifying Chrome that IBus is ready.";
      FOR_EACH_OBSERVER(Observer, observers_, OnConnectionChange(true));
    }
  }

  // Creates IBusBus object if it's not created yet.
  void MaybeCreateIBus() {
    if (ibus_) {
      return;
    }

    ibus_init();
    // Establish IBus connection between ibus-daemon to retrieve the list of
    // available input method engines, change the current input method engine,
    // and so on.
    ibus_ = ibus_bus_new();

    // Check the IBus connection status.
    if (!ibus_) {
      LOG(ERROR) << "ibus_bus_new() failed";
      return;
    }
    // Register callback functions for IBusBus signals.
    ConnectIBusSignals();

    // Ask libibus to watch the NameOwnerChanged signal *asynchronously*.
    ibus_bus_set_watch_dbus_signal(ibus_, TRUE);

    if (ibus_bus_is_connected(ibus_)) {
      VLOG(1) << "IBus connection is ready.";
    }
  }

  // Creates IBusConfig object if it's not created yet AND |ibus_| connection
  // is ready.
  void MaybeRestoreIBusConfig() {
    if (!ibus_) {
      return;
    }

    // Destroy the current |ibus_config_| object. No-op if it's NULL.
    MaybeDestroyIBusConfig();

    if (!ibus_config_) {
      GDBusConnection* ibus_connection = ibus_bus_get_connection(ibus_);
      if (!ibus_connection) {
        VLOG(1) << "Couldn't create an ibus config object since "
                << "IBus connection is not ready.";
        return;
      }
      const gboolean disconnected
          = g_dbus_connection_is_closed(ibus_connection);
      if (disconnected) {
        // |ibus_| object is not NULL, but the connection between ibus-daemon
        // is not yet established. In this case, we don't create |ibus_config_|
        // object.
        LOG(ERROR) << "Couldn't create an ibus config object since "
                   << "IBus connection is closed.";
        return;
      }
      // If memconf is not successfully started yet, ibus_config_new() will
      // return NULL. Otherwise, it returns a transfer-none and non-floating
      // object. ibus_config_new() sometimes issues a D-Bus *synchronous* IPC
      // to check if the org.freedesktop.IBus.Config service is available.
      ibus_config_ = ibus_config_new(ibus_connection,
                                     NULL /* do not cancel the operation */,
                                     NULL /* do not get error information */);
      if (!ibus_config_) {
        LOG(ERROR) << "ibus_config_new() failed. ibus-memconf is not ready?";
        return;
      }

      // TODO(yusukes): g_object_weak_ref might be better since it allows
      // libcros to detect the delivery of the "destroy" glib signal the
      // |ibus_config_| object.
      g_object_ref(ibus_config_);
      VLOG(1) << "ibus_config_ is ready.";
    }
  }

  // Destroys IBusConfig object if |ibus_| connection is not ready. This
  // function does nothing if |ibus_config_| is NULL or |ibus_| connection is
  // still alive. Note that the IBusConfig object can't be used when |ibus_|
  // connection is not ready.
  void MaybeDestroyIBusConfig() {
    if (!ibus_) {
      LOG(ERROR) << "MaybeDestroyIBusConfig: ibus_ is NULL";
      return;
    }
    if (ibus_config_ && !ibus_bus_is_connected(ibus_)) {
      g_object_unref(ibus_config_);
      ibus_config_ = NULL;
    }
  }

  // Handles "RegisterProperties" signal from chromeos_input_method_ui.
  void DoRegisterProperties(IBusPropList* ibus_prop_list) {
    VLOG(1) << "RegisterProperties" << (ibus_prop_list ? "" : " (clear)");

    InputMethodPropertyList prop_list;  // our representation.
    if (ibus_prop_list) {
      // You can call
      //   LOG(INFO) << "\n" << PrintPropList(ibus_prop_list, 0);
      // here to dump |ibus_prop_list|.
      if (!FlattenPropertyList(ibus_prop_list, &prop_list)) {
        // Clear properties on errors.
        DoRegisterProperties(NULL);
        return;
      }
    }
    VLOG(1) << "RegisterProperties" << (ibus_prop_list ? "" : " (clear)");
    // Notify the change.
    FOR_EACH_OBSERVER(Observer, observers_,
                      OnRegisterImeProperties(prop_list));
  }

  // Retrieves input method status and notifies them to the UI.
  // |current_global_engine_id| is the current global engine id such as "mozc"
  // and "xkb:us::eng". If the id is unknown, an empty string "" can be passed.
  // Warning: you can call this function only from ibus callback functions
  // like FocusIn(). See http://crosbug.com/5217#c9 for details.
  void UpdateUI(const char* current_global_engine_id) {
    DCHECK(current_global_engine_id);

    const InputMethodsInfo* engine_info = NULL;
    for (size_t i = 0; i < arraysize(kInputMethods); ++i) {
      if (kInputMethods[i].input_method_id ==
          std::string(current_global_engine_id)) {
        engine_info = &kInputMethods[i];
        break;
      }
    }

    InputMethodDescriptor current_input_method;
    if (engine_info) {
      current_input_method = CreateInputMethodDescriptor(
          engine_info->input_method_id,
          "",
          engine_info->xkb_layout_id,
          engine_info->language_code);
    } else {
      if (!InputMethodManager::GetInstance()->GetExtraDescriptor(
          current_global_engine_id, &current_input_method)) {
        LOG(ERROR) << current_global_engine_id
                   << " is not found in the input method white-list.";
        return;
      }
    }


    VLOG(1) << "Updating the UI. ID:" << current_input_method.id()
            << ", keyboard_layout:" << current_input_method.keyboard_layout();

    // Notify the change to update UI.
    FOR_EACH_OBSERVER(Observer, observers_,
                      OnCurrentInputMethodChanged(current_input_method));
  }

  // Installs gobject signal handlers to |ibus_|.
  void ConnectIBusSignals() {
    if (!ibus_) {
      return;
    }

    // We use g_signal_connect_after here since the callback should be called
    // *after* the IBusBusDisconnectedCallback in chromeos_input_method_ui.cc
    // is called. chromeos_input_method_ui.cc attaches the panel service object
    // to |ibus_|, and the callback in this file use the attached object.
    g_signal_connect_after(ibus_,
                           "connected",
                           G_CALLBACK(IBusBusConnectedThunk),
                           this);

    g_signal_connect(ibus_,
                     "disconnected",
                     G_CALLBACK(IBusBusDisconnectedThunk),
                     this);

    g_signal_connect(ibus_,
                     "name-owner-changed",
                     G_CALLBACK(IBusBusNameOwnerChangedThunk),
                     this);
  }

  // Installs gobject signal handlers to the panel service.
  void ConnectPanelServiceSignals() {
    if (!ibus_) {
      return;
    }

    IBusPanelService* ibus_panel_service = IBUS_PANEL_SERVICE(
        g_object_get_data(G_OBJECT(ibus_), kPanelObjectKey));
    if (!ibus_panel_service) {
      LOG(ERROR) << "IBusPanelService is NOT available.";
      return;
    }
    // We don't _ref() or _weak_ref() the panel service object, since we're not
    // interested in the life time of the object.

    g_signal_connect(ibus_panel_service,
                     "focus-in",
                     G_CALLBACK(FocusInThunk),
                     this);
    g_signal_connect(ibus_panel_service,
                     "register-properties",
                     G_CALLBACK(RegisterPropertiesThunk),
                     this);
    g_signal_connect(ibus_panel_service,
                     "update-property",
                     G_CALLBACK(UpdatePropertyThunk),
                     this);
  }

  // Handles "connected" signal from ibus-daemon.
  void IBusBusConnected(IBusBus* bus) {
    LOG(WARNING) << "IBus connection is recovered.";
    MaybeRestoreConnections();
  }

  // Handles "disconnected" signal from ibus-daemon.
  void IBusBusDisconnected(IBusBus* bus) {
    LOG(WARNING) << "IBus connection is terminated.";
    // ibus-daemon might be terminated. Since |ibus_| object will automatically
    // connect to the daemon if it restarts, we don't have to set NULL on ibus_.
    // Call MaybeDestroyIBusConfig() to set |ibus_config_| to NULL temporarily.
    MaybeDestroyIBusConfig();
    VLOG(1) << "Notifying Chrome that IBus is terminated.";
    FOR_EACH_OBSERVER(Observer, observers_, OnConnectionChange(false));
  }

  // Handles "name-owner-changed" signal from ibus-daemon. The signal is sent
  // to libcros when an IBus component such as ibus-memconf, ibus-engine-*, ..
  // is started.
  void IBusBusNameOwnerChanged(IBusBus* bus,
                               const gchar* name,
                               const gchar* old_name,
                               const gchar* new_name) {
    DCHECK(name);
    DCHECK(old_name);
    DCHECK(new_name);
    VLOG(1) << "Name owner is changed: name=" << name
            << ", old_name=" << old_name << ", new_name=" << new_name;

    if (name != std::string("org.freedesktop.IBus.Config")) {
      // Not a signal for ibus-memconf.
      return;
    }

    const std::string empty_string;
    if (old_name != empty_string || new_name == empty_string) {
      // ibus-memconf died?
      LOG(WARNING) << "Unexpected name owner change: name=" << name
                   << ", old_name=" << old_name << ", new_name=" << new_name;
      // TODO(yusukes): it might be nice to set |ibus_config_| to NULL and call
      // |OnConnectionChange| with false here to allow Chrome to
      // recover all input method configurations when ibus-memconf is
      // automatically restarted by ibus-daemon. Though ibus-memconf is pretty
      // stable and unlikely crashes.
      return;
    }

    VLOG(1) << "IBus config daemon is started. Recovering ibus_config_";

    // Try to recover |ibus_config_|. If the |ibus_config_| object is
    // successfully created, |OnConnectionChange| will be called to
    // notify Chrome that IBus is ready.
    MaybeRestoreConnections();
  }

  // Handles "FocusIn" signal from chromeos_input_method_ui.
  void FocusIn(IBusPanelService* panel, const gchar* input_context_path) {
    if (!input_context_path) {
      LOG(ERROR) << "NULL context passed";
    } else {
      VLOG(1) << "FocusIn: " << input_context_path;
    }
    // Remember the current ic path.
    input_context_path_ = Or(input_context_path, "");
  }

  // Handles "RegisterProperties" signal from chromeos_input_method_ui.
  void RegisterProperties(IBusPanelService* panel, IBusPropList* prop_list) {
    DoRegisterProperties(prop_list);
  }

  // Handles "UpdateProperty" signal from chromeos_input_method_ui.
  void UpdateProperty(IBusPanelService* panel, IBusProperty* ibus_prop) {
    VLOG(1) << "UpdateProperty";
    DCHECK(ibus_prop);

    // You can call
    //   LOG(INFO) << "\n" << PrintProp(ibus_prop, 0);
    // here to dump |ibus_prop|.

    InputMethodPropertyList prop_list;  // our representation.
    if (!FlattenProperty(ibus_prop, &prop_list)) {
      // Don't update the UI on errors.
      LOG(ERROR) << "Malformed properties are detected";
      return;
    }
    // Notify the change.
    if (!prop_list.empty()) {
      FOR_EACH_OBSERVER(Observer, observers_,
                        OnUpdateImeProperty(prop_list));
    }
  }

  // Removes input methods that are not whitelisted from
  // |requested_input_methods| and stores them on |out_filtered_input_methods|.
  // TODO(yusukes): Write unittest.
  void FilterInputMethods(
      const std::vector<std::string>& requested_input_methods,
      std::vector<std::string>* out_filtered_input_methods) {
    out_filtered_input_methods->clear();
    for (size_t i = 0; i < requested_input_methods.size(); ++i) {
      const std::string& input_method = requested_input_methods[i];
      if (whitelist_.InputMethodIdIsWhitelisted(input_method.c_str())) {
        if (!InputMethodUtil::IsKeyboardLayout(input_method)) {
          out_filtered_input_methods->push_back(input_method);
        }
      } else {
        LOG(ERROR) << "Unsupported input method: " << input_method;
      }
    }
  }

  // Frees input method names in |engines| and the list itself. Please make sure
  // that |engines| points the head of the list.
  void FreeInputMethodNames(GList* engines) {
    if (engines) {
      for (GList* cursor = engines; cursor; cursor = g_list_next(cursor)) {
        g_object_unref(IBUS_ENGINE_DESC(cursor->data));
      }
      g_list_free(engines);
    }
  }

  // Copies input method names in |engines| to |out|.
  // TODO(yusukes): Write unittest.
  void AddInputMethodNames(const GList* engines, InputMethodDescriptors* out) {
    DCHECK(out);
    for (; engines; engines = g_list_next(engines)) {
      IBusEngineDesc* engine_desc = IBUS_ENGINE_DESC(engines->data);
      const gchar* name = ibus_engine_desc_get_name(engine_desc);
      const gchar* layout = ibus_engine_desc_get_layout(engine_desc);
      const gchar* language = ibus_engine_desc_get_language(engine_desc);
      if (whitelist_.InputMethodIdIsWhitelisted(name)) {
        out->push_back(CreateInputMethodDescriptor(name, "", layout, language));
        VLOG(1) << name << " (preloaded)";
      }
    }
  }

  // A callback function that will be called when ibus_config_set_value_async()
  // request is finished.
  static void SetInputMethodConfigCallback(GObject* source_object,
                                           GAsyncResult* res,
                                           gpointer user_data) {
    IBusConfig* config = IBUS_CONFIG(user_data);
    g_return_if_fail(config);

    GError* error = NULL;
    const gboolean result =
        ibus_config_set_value_async_finish(config, res, &error);

    if (!result) {
      std::string message = "(unknown error)";
      if (error && error->message) {
        message = error->message;
      }
      LOG(ERROR) << "ibus_config_set_value_async failed: " << message;
    }

    if (error) {
      g_error_free(error);
    }
    g_object_unref(config);
  }

  // Connection to the ibus-daemon via IBus API. These objects are used to
  // call ibus-daemon's API (e.g. activate input methods, set config, ...)
  IBusBus* ibus_;
  IBusConfig* ibus_config_;

  // Current input context path.
  std::string input_context_path_;

  ObserverList<Observer> observers_;

  // An object which knows all valid input method and layout IDs.
  InputMethodWhitelist whitelist_;

  DISALLOW_COPY_AND_ASSIGN(IBusControllerImpl);
};

#endif  // defined(HAVE_IBUS)

// The stub implementation is used if IBus is not present.
//
// Note that this class is intentionally built even if HAVE_IBUS is
// defined so that we can easily tell build breakage when we change the
// IBusControllerImpl but forget to update the stub implementation.
class IBusControllerStubImpl : public IBusController {
 public:
  IBusControllerStubImpl() {
  }

  virtual void Connect() {
  };

  virtual void AddObserver(Observer* observer) {
  }

  virtual void RemoveObserver(Observer* observer) {
  }

  virtual bool StopInputMethodProcess() {
    return true;
  }

  virtual bool ChangeInputMethod(const std::string& name) {
    return true;
  }

  virtual void SetImePropertyActivated(const std::string& key,
                                       bool activated) {
  }

  virtual bool SetInputMethodConfig(const std::string& section,
                                    const std::string& config_name,
                                    const InputMethodConfigValue& value) {
    return true;
  }

  virtual void SendHandwritingStroke(const HandwritingStroke& stroke) {
  }

  virtual void CancelHandwriting(int n_strokes) {
  }

  // This is for ibus_controller_unittest.cc. Since the test is usually compiled
  // without HAVE_IBUS, we have to provide the same implementation as
  // IBusControllerImpl to test the whitelist class.
  virtual bool InputMethodIdIsWhitelisted(const std::string& input_method_id) {
    return whitelist_.InputMethodIdIsWhitelisted(input_method_id);
  }
  // See the comment above. We have to keep the implementation the same as
  // IBusControllerImpl.
  virtual bool XkbLayoutIsSupported(const std::string& xkb_layout) {
    return whitelist_.XkbLayoutIsSupported(xkb_layout);
  }
  // See the comment above. We have to keep the implementation the same as
  // IBusControllerImpl.
  virtual InputMethodDescriptor CreateInputMethodDescriptor(
      const std::string& id,
      const std::string& name,
      const std::string& raw_layout,
      const std::string& language_code) {
    return InputMethodDescriptor(whitelist_, id, name, raw_layout,
                                 language_code);
  }

 private:
  InputMethodWhitelist whitelist_;

  DISALLOW_COPY_AND_ASSIGN(IBusControllerStubImpl);
};

IBusController* IBusController::Create() {
#if defined(HAVE_IBUS)
  return new IBusControllerImpl;
#else
  return new IBusControllerStubImpl;
#endif
}

IBusController::~IBusController() {
}

}  // namespace input_method
}  // namespace chromeos
