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

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

#include <sstream>

#include "ash/shell.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/string_util.h"
#include "chrome/browser/chromeos/input_method/input_method_descriptor.h"
#include "chrome/browser/chromeos/input_method/input_method_manager.h"
#include "chrome/browser/chromeos/input_method/input_method_util.h"
#include "third_party/mozc/session/candidates_lite.pb.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/root_window.h"
#include "ui/base/ime/input_method_ibus.h"

namespace chromeos {
namespace input_method {
namespace {

bool IsActive(const std::string& input_method_id,
              const InputMethodDescriptors* descriptors) {
  for (size_t i = 0; i < descriptors->size(); ++i) {
    if (descriptors->at(i).id() == input_method_id) {
      return true;
    }
  }
  return false;
}

}  // namespace

InputMethodLookupTable::InputMethodLookupTable()
    : visible(false),
      cursor_absolute_index(0),
      page_size(0),
      orientation(kHorizontal) {
}

InputMethodLookupTable::~InputMethodLookupTable() {
}

std::string InputMethodLookupTable::ToString() const {
  std::stringstream stream;
  stream << "visible: " << visible << "\n";
  stream << "cursor_absolute_index: " << cursor_absolute_index << "\n";
  stream << "page_size: " << page_size << "\n";
  stream << "orientation: " << orientation << "\n";
  stream << "candidates:";
  for (size_t i = 0; i < candidates.size(); ++i) {
    stream << " [" << candidates[i] << "]";
  }
  stream << "\nlabels:";
  for (size_t i = 0; i < labels.size(); ++i) {
    stream << " [" << labels[i] << "]";
  }
  return stream.str();
}

#if defined(HAVE_IBUS)

// Returns an string representation of |table| for debugging.
std::string IBusLookupTableToString(IBusLookupTable* table) {
  std::stringstream stream;
  stream << "page_size: " << table->page_size << "\n";
  stream << "cursor_pos: " << table->cursor_pos << "\n";
  stream << "cursor_visible: " << table->cursor_visible << "\n";
  stream << "round: " << table->round << "\n";
  stream << "orientation: " << table->orientation << "\n";
  stream << "candidates:";
  for (int i = 0; ; i++) {
    IBusText *text = ibus_lookup_table_get_candidate(table, i);
    if (!text) {
      break;
    }
    stream << " " << text->text;
  }
  return stream.str();
}

// The real implementation of the IBusUiController.
class IBusUiControllerImpl : public IBusUiController {
 public:
  IBusUiControllerImpl()
      : ibus_(NULL),
        ibus_panel_service_(NULL) {
    ui::InputMethodIBus* input_method = GetChromeInputMethod();
    DCHECK(input_method);
    input_method->set_ibus_client(scoped_ptr<ui::internal::IBusClient>(
        new IBusChromeOSClientImpl(this)).Pass());
  }

  ~IBusUiControllerImpl() {
    ui::InputMethodIBus* input_method = GetChromeInputMethod();
    if (input_method) {
      ui::internal::IBusClient* client = input_method->ibus_client();
      // We assume that no objects other than |this| set an IBus client.
      DCHECK(client);
      static_cast<IBusChromeOSClientImpl*>(client)->set_ui(NULL);
    }
    // ibus_panel_service_ depends on ibus_, thus unref it first.
    if (ibus_panel_service_) {
      DisconnectPanelServiceSignals();
      g_object_unref(ibus_panel_service_);
    }
    if (ibus_) {
      DisconnectIBusSignals();
      g_object_unref(ibus_);
    }
  }

  // Creates IBusBus object if it's not created yet, and tries to connect to
  // ibus-daemon. Returns true if IBusBus is successfully connected to the
  // daemon.
  bool ConnectToIBus() {
    if (ibus_) {
      return true;
    }
    ibus_init();
    ibus_ = ibus_bus_new();
    CHECK(ibus_) << "ibus_bus_new() failed. Out of memory?";

    bool result = false;
    // Check the IBus connection status.
    if (ibus_bus_is_connected(ibus_)) {
      DVLOG(1) << "ibus_bus_is_connected(). IBus connection is ready.";
      FOR_EACH_OBSERVER(Observer, observers_, OnConnectionChange(true));
      result = true;
    }

    // Start listening the gobject signals regardless of the bus connection
    // status.
    ConnectIBusSignals();
    return result;
  }

  // Creates IBusPanelService object if |ibus_| is already connected.
  bool MaybeRestorePanelService() {
    if (!ibus_ || !ibus_bus_is_connected(ibus_)) {
      return false;
    }

    if (ibus_panel_service_) {
      DVLOG(1) << "IBusPanelService is already available. Remove it first.";
      g_object_set_data(G_OBJECT(ibus_), kPanelObjectKey, NULL);
      g_object_unref(ibus_panel_service_);
      ibus_panel_service_ = NULL;
    }

    // Create an IBusPanelService object.
    GDBusConnection* ibus_connection = ibus_bus_get_connection(ibus_);
    if (!ibus_connection) {
      DVLOG(1) << "ibus_bus_get_connection() failed";
      return false;
    }
    ibus_panel_service_ = ibus_panel_service_new(ibus_connection);
    if (!ibus_panel_service_) {
      DVLOG(1) << "ibus_chromeos_panel_service_new() failed";
      return false;
    }
    ConnectPanelServiceSignals();
    g_object_set_data(G_OBJECT(ibus_), kPanelObjectKey, ibus_panel_service_);
    DVLOG(1) << "IBusPanelService object is successfully (re-)created.";

    // Request the well-known name *asynchronously*.
    ibus_bus_request_name_async(ibus_,
                                IBUS_SERVICE_PANEL,
                                0  /* flags */,
                                -1  /* timeout */,
                                NULL  /* cancellable */,
                                RequestNameCallback,
                                g_object_ref(ibus_));
    return true;
  }

  // IBusUiController override.
  virtual void NotifyCandidateClicked(int index,
                                      int button,
                                      int flags) OVERRIDE {
    if (!ibus_ || !ibus_bus_is_connected(ibus_)) {
      DVLOG(1) << "NotifyCandidateClicked: bus is not connected.";
      return;
    }
    if (!ibus_panel_service_) {
      DVLOG(1) << "NotifyCandidateClicked: panel service is not available.";
      return;
    }

    /* Send a D-Bus signal to ibus-daemon *asynchronously*. */
    ibus_panel_service_candidate_clicked(ibus_panel_service_,
                                         index,
                                         button,
                                         flags);
  }

  // IBusUiController override.
  virtual void NotifyCursorUp() OVERRIDE {
    if (!ibus_ || !ibus_bus_is_connected(ibus_)) {
      DVLOG(1) << "NotifyCursorUp: bus is not connected.";
      return;
    }
    if (!ibus_panel_service_) {
      DVLOG(1) << "NotifyCursorUp: panel service is not available.";
      return;
    }

    /* Send a D-Bus signal to ibus-daemon *asynchronously*. */
    ibus_panel_service_cursor_up(ibus_panel_service_);
  }

  // IBusUiController override.
  virtual void NotifyCursorDown() OVERRIDE {
    if (!ibus_ || !ibus_bus_is_connected(ibus_)) {
      DVLOG(1) << "NotifyCursorDown: bus is not connected.";
      return;
    }
    if (!ibus_panel_service_) {
      DVLOG(1) << "NotifyCursorDown: panel service is not available.";
      return;
    }
    /* Send a D-Bus signal to ibus-daemon *asynchronously*. */
    ibus_panel_service_cursor_down(ibus_panel_service_);
  }

  // IBusUiController override.
  virtual void NotifyPageUp() OVERRIDE {
    if (!ibus_ || !ibus_bus_is_connected(ibus_)) {
      DVLOG(1) << "NotifyPageUp: bus is not connected.";
      return;
    }
    if (!ibus_panel_service_) {
      DVLOG(1) << "NotifyPageUp: panel service is not available.";
      return;
    }

    /* Send a D-Bus signal to ibus-daemon *asynchronously*. */
    ibus_panel_service_page_up(ibus_panel_service_);
  }

  // IBusUiController override.
  virtual void NotifyPageDown() OVERRIDE {
    if (!ibus_ || !ibus_bus_is_connected(ibus_)) {
      DVLOG(1) << "NotifyPageDown: bus is not connected.";
      return;
    }
    if (!ibus_panel_service_) {
      DVLOG(1) << "NotifyPageDown: panel service is not available.";
      return;
    }

    /* Send a D-Bus signal to ibus-daemon *asynchronously*. */
    ibus_panel_service_page_down(ibus_panel_service_);
  }

  // IBusUiController override.
  virtual void Connect() OVERRIDE {
    // It's totally fine if ConnectToIBus() fails here, as we'll get
    // "connected" gobject signal once the connection becomes ready.
    if (ConnectToIBus())
      MaybeRestorePanelService();
  }

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

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

 private:
  // A class for customizing the behavior of ui::InputMethodIBus for Chrome OS.
  class IBusChromeOSClientImpl : public ui::internal::IBusClient {
   public:
    explicit IBusChromeOSClientImpl(IBusUiControllerImpl* ui)
        : ui_(ui) {
    }

    // ui::IBusClient override.
    virtual InputMethodType GetInputMethodType() OVERRIDE {
      const std::string current_input_method_id = GetCurrentInputMethodId();
      return InputMethodUtil::IsKeyboardLayout(current_input_method_id) ?
          INPUT_METHOD_XKB_LAYOUT : INPUT_METHOD_NORMAL;
    }

    virtual void SetCursorLocation(const gfx::Rect& cursor_location,
                                   const gfx::Rect& composition_head) OVERRIDE {
      if (!ui_)
        return;
      // We don't have to call ibus_input_context_set_cursor_location() on
      // Chrome OS because the candidate window for IBus is integrated with
      // Chrome.
      ui_->SetCursorLocation(NULL, cursor_location, composition_head);
    }

    void set_ui(IBusUiControllerImpl* ui) {
      ui_ = ui;
    }

   private:
    std::string GetCurrentInputMethodId() {
      InputMethodManager* manager = InputMethodManager::GetInstance();
      return manager->GetCurrentInputMethod().id();
    }

    IBusUiControllerImpl* ui_;
  };

  // Returns a ui::InputMethodIBus object which is associated with the root
  // window. Returns NULL if the Ash shell has already been destructed.
  static ui::InputMethodIBus* GetChromeInputMethod() {
    if (!ash::Shell::HasInstance())
      return NULL;
    aura::Window* root_window = ash::Shell::GetPrimaryRootWindow();
    if (!root_window)
      return NULL;
    return static_cast<ui::InputMethodIBus*>(root_window->GetProperty(
        aura::client::kRootWindowInputMethodKey));
  }

  // 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<IBusUiControllerImpl*>(userdata)
        ->IBusBusConnected(sender);
  }
  static void IBusBusDisconnectedThunk(IBusBus* sender, gpointer userdata) {
    return reinterpret_cast<IBusUiControllerImpl*>(userdata)
        ->IBusBusDisconnected(sender);
  }
  static void HideAuxiliaryTextThunk(IBusPanelService* sender,
                                     gpointer userdata) {
    return reinterpret_cast<IBusUiControllerImpl*>(userdata)
        ->HideAuxiliaryText(sender);
  }
  static void HideLookupTableThunk(IBusPanelService* sender,
                                   gpointer userdata) {
    return reinterpret_cast<IBusUiControllerImpl*>(userdata)
        ->HideLookupTable(sender);
  }
  static void UpdateAuxiliaryTextThunk(IBusPanelService* sender,
                                       IBusText* text, gboolean visible,
                                       gpointer userdata) {
    return reinterpret_cast<IBusUiControllerImpl*>(userdata)
        ->UpdateAuxiliaryText(sender, text, visible);
  }
  static void SetCursorLocationThunk(IBusPanelService* sender,
                                     gint x, gint y, gint width, gint height,
                                     gpointer userdata) {
    return reinterpret_cast<IBusUiControllerImpl*>(userdata)
        ->SetCursorLocation(sender, gfx::Rect(x, y, width, height),
                            gfx::Rect());
  }
  static void UpdateLookupTableThunk(IBusPanelService* sender,
                                     IBusLookupTable* table, gboolean visible,
                                     gpointer userdata) {
    return reinterpret_cast<IBusUiControllerImpl*>(userdata)
        ->UpdateLookupTable(sender, table, visible);
  }
  static void UpdatePreeditTextThunk(IBusPanelService* sender,
                                     IBusText* text,
                                     guint cursor_pos,
                                     gboolean visible,
                                     gpointer userdata) {
    return reinterpret_cast<IBusUiControllerImpl*>(userdata)
        ->UpdatePreeditText(sender, text, cursor_pos, visible);
  }
  static void HidePreeditTextThunk(IBusPanelService* sender,
                                   gpointer userdata) {
    return reinterpret_cast<IBusUiControllerImpl*>(userdata)
        ->HidePreeditText(sender);
  }


  // Installs gobject signal handlers to |ibus_|.
  void ConnectIBusSignals() {
    if (!ibus_) {
      return;
    }
    g_signal_connect(ibus_,
                     "connected",
                     G_CALLBACK(IBusBusConnectedThunk),
                     this);
    g_signal_connect(ibus_,
                     "disconnected",
                     G_CALLBACK(IBusBusDisconnectedThunk),
                     this);
  }

  // Removes gobject signal handlers from |ibus_|.
  void DisconnectIBusSignals() {
    if (!ibus_) {
      return;
    }
    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);
  }

  // Installs gobject signal handlers to |ibus_panel_service_|.
  void ConnectPanelServiceSignals() {
    if (!ibus_panel_service_) {
      return;
    }
    g_signal_connect(ibus_panel_service_,
                     "hide-auxiliary-text",
                     G_CALLBACK(HideAuxiliaryTextThunk),
                     this);
    g_signal_connect(ibus_panel_service_,
                     "hide-lookup-table",
                     G_CALLBACK(HideLookupTableThunk),
                     this);
    g_signal_connect(ibus_panel_service_,
                     "update-auxiliary-text",
                     G_CALLBACK(UpdateAuxiliaryTextThunk),
                     this);
    g_signal_connect(ibus_panel_service_,
                     "set-cursor-location",
                     G_CALLBACK(SetCursorLocationThunk),
                     this);
    g_signal_connect(ibus_panel_service_,
                     "update-lookup-table",
                     G_CALLBACK(UpdateLookupTableThunk),
                     this);
    g_signal_connect(ibus_panel_service_,
                     "update-preedit-text",
                     G_CALLBACK(UpdatePreeditTextThunk),
                     this);
    g_signal_connect(ibus_panel_service_,
                     "hide-preedit-text",
                     G_CALLBACK(HidePreeditTextThunk),
                     this);
  }

  // Removes gobject signal handlers from |ibus_panel_service_|.
  void DisconnectPanelServiceSignals() {
    if (!ibus_panel_service_) {
      return;
    }
    g_signal_handlers_disconnect_by_func(
        ibus_panel_service_,
        reinterpret_cast<gpointer>(HideAuxiliaryTextThunk),
        this);
    g_signal_handlers_disconnect_by_func(
        ibus_panel_service_,
        reinterpret_cast<gpointer>(HideLookupTableThunk),
        this);
    g_signal_handlers_disconnect_by_func(
        ibus_panel_service_,
        reinterpret_cast<gpointer>(UpdateAuxiliaryTextThunk),
        this);
    g_signal_handlers_disconnect_by_func(
        ibus_panel_service_,
        reinterpret_cast<gpointer>(SetCursorLocationThunk),
        this);
    g_signal_handlers_disconnect_by_func(
        ibus_panel_service_,
        reinterpret_cast<gpointer>(UpdateLookupTableThunk),
        this);
    g_signal_handlers_disconnect_by_func(
        ibus_panel_service_,
        reinterpret_cast<gpointer>(UpdatePreeditTextThunk),
        this);
    g_signal_handlers_disconnect_by_func(
        ibus_panel_service_,
        reinterpret_cast<gpointer>(HidePreeditTextThunk),
        this);
  }

  // Handles "connected" signal from ibus-daemon.
  void IBusBusConnected(IBusBus* bus) {
    DVLOG(1) << "IBus connection is recovered.";
    if (!MaybeRestorePanelService()) {
      DVLOG(1) << "MaybeRestorePanelService() failed";
      return;
    }

    FOR_EACH_OBSERVER(Observer, observers_, OnConnectionChange(true));
  }

  // Handles "disconnected" signal from ibus-daemon. Releases the
  // |ibus_panel_service_| object since the connection the service has will be
  // destroyed soon.
  void IBusBusDisconnected(IBusBus* bus) {
    DVLOG(1) << "IBus connection is terminated.";
    if (ibus_panel_service_) {
      DisconnectPanelServiceSignals();
      // Since the connection being disconnected is currently mutex-locked,
      // we can't unref the panel service object directly here. Because when the
      // service object is deleted, the connection, which the service also has,
      // will be locked again. To avoid deadlock, we use g_idle_add instead.
      g_object_set_data(G_OBJECT(ibus_), kPanelObjectKey, NULL);
      g_idle_add(ReleasePanelService, ibus_panel_service_);
      ibus_panel_service_ = NULL;
    }

    FOR_EACH_OBSERVER(Observer, observers_, OnConnectionChange(false));
  }

  // Releases |ibus_panel_service_|. See the comment above.
  static gboolean ReleasePanelService(gpointer user_data) {
    g_return_val_if_fail(IBUS_IS_PANEL_SERVICE(user_data), FALSE);
    g_object_unref(user_data);
    return FALSE;  // stop the idle timer.
  }

  // Handles IBusPanelService's |HideAuxiliaryText| method call.
  void HideAuxiliaryText(IBusPanelService* panel) {
    FOR_EACH_OBSERVER(Observer, observers_, OnHideAuxiliaryText());
  }

  // Handles IBusPanelService's |HideLookupTable| method call.
  void HideLookupTable(IBusPanelService *panel) {
    FOR_EACH_OBSERVER(Observer, observers_, OnHideLookupTable());
  }

  // Handles IBusPanelService's |UpdateAuxiliaryText| method call.
  void UpdateAuxiliaryText(IBusPanelService* panel,
                           IBusText* text,
                           gboolean visible) {
    g_return_if_fail(text);
    g_return_if_fail(text->text);
    // Convert IBusText to a std::string. IBusText is an attributed text,
    const std::string simple_text = text->text;
    FOR_EACH_OBSERVER(Observer, observers_,
                      OnUpdateAuxiliaryText(simple_text, visible == TRUE));
  }

  // Handles IBusPanelService's |SetCursorLocation| method call.
  void SetCursorLocation(IBusPanelService *panel,
                         const gfx::Rect& cursor_location,
                         const gfx::Rect& composition_head) {
    // Note: |panel| might be NULL. See IBusChromeOSClientImpl above.
    FOR_EACH_OBSERVER(Observer, observers_,
                      OnSetCursorLocation(cursor_location, composition_head));
  }

  // Handles IBusPanelService's |UpdatePreeditText| method call.
  void UpdatePreeditText(IBusPanelService *panel,
                         IBusText *text,
                         guint cursor_pos,
                         gboolean visible) {
    FOR_EACH_OBSERVER(Observer, observers_,
                      OnUpdatePreeditText(text->text, cursor_pos, visible));
  }

  // Handles IBusPanelService's |UpdatePreeditText| method call.
  void HidePreeditText(IBusPanelService *panel) {
    FOR_EACH_OBSERVER(Observer, observers_, OnHidePreeditText());
  }

  // Handles IBusPanelService's |UpdateLookupTable| method call.
  void UpdateLookupTable(IBusPanelService *panel,
                         IBusLookupTable *table,
                         gboolean visible) {
    g_return_if_fail(table);

    InputMethodLookupTable lookup_table;
    lookup_table.visible = (visible == TRUE);

    // Copy the orientation information.
    const gint orientation = ibus_lookup_table_get_orientation(table);
    if (orientation == IBUS_ORIENTATION_VERTICAL) {
      lookup_table.orientation = InputMethodLookupTable::kVertical;
    } else if (orientation == IBUS_ORIENTATION_HORIZONTAL) {
      lookup_table.orientation = InputMethodLookupTable::kHorizontal;
    }

    // The function ibus_serializable_get_attachment had been changed
    // to use GVariant by the commit
    // https://github.com/ibus/ibus/commit/ac9dfac13cef34288440a2ecdf067cd827fb2f8f
    GVariant* variant = ibus_serializable_get_attachment(
        IBUS_SERIALIZABLE(table), "mozc.candidates");
    if (variant != NULL) {
      gconstpointer ptr = g_variant_get_data(variant);
      if (ptr != NULL) {
        gsize size = g_variant_get_size(variant);
        GByteArray* bytearray = g_byte_array_sized_new(size);
        g_byte_array_append(
            bytearray, reinterpret_cast<const guint8*>(ptr), size);
        if (!lookup_table.mozc_candidates.ParseFromArray(
                bytearray->data, bytearray->len)) {
          lookup_table.mozc_candidates.Clear();
        }
        g_byte_array_unref(bytearray);
      }
    }

    // Copy candidates and annotations to |lookup_table|.
    for (int i = 0; ; i++) {
      IBusText *text = ibus_lookup_table_get_candidate(table, i);
      if (!text) {
        break;
      }
      lookup_table.candidates.push_back(text->text);

      const mozc::commands::Candidates &candidates =
          lookup_table.mozc_candidates;
      if ((i < candidates.candidate_size()) &&
          candidates.candidate(i).has_annotation() &&
          candidates.candidate(i).annotation().has_description()) {
        lookup_table.annotations.push_back(
            candidates.candidate(i).annotation().description());
      } else {
        lookup_table.annotations.push_back("");
      }
    }
    DCHECK_EQ(lookup_table.candidates.size(),
              lookup_table.annotations.size());

    // Copy labels to |lookup_table|.
    for (int i = 0; ; i++) {
      IBusText *text = ibus_lookup_table_get_label(table, i);
      if (!text) {
        break;
      }
      lookup_table.labels.push_back(text->text);
    }

    lookup_table.cursor_absolute_index =
        ibus_lookup_table_get_cursor_pos(table);
    lookup_table.page_size = ibus_lookup_table_get_page_size(table);
    // Ensure that the page_size is non-zero to avoid div-by-zero error.
    if (lookup_table.page_size <= 0) {
      DVLOG(1) << "Invalid page size: " << lookup_table.page_size;
      lookup_table.page_size = 1;
    }

    FOR_EACH_OBSERVER(Observer, observers_,
                      OnUpdateLookupTable(lookup_table));
  }

  // A callback function that will be called when ibus_bus_request_name_async()
  // request is finished.
  static void RequestNameCallback(GObject* source_object,
                                  GAsyncResult* res,
                                  gpointer user_data) {
    IBusBus* bus = IBUS_BUS(user_data);
    g_return_if_fail(bus);

    GError* error = NULL;
    const guint service_id =
        ibus_bus_request_name_async_finish(bus, res, &error);

    if (!service_id) {
      std::string message = "(unknown error)";
      if (error && error->message) {
        message = error->message;
      }
      DVLOG(1) << "Failed to register the panel service: " << message;
    } else {
      DVLOG(1) << "The panel service is registered: ID=" << service_id;
    }

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

  IBusBus* ibus_;
  IBusPanelService* ibus_panel_service_;
  ObserverList<Observer> observers_;
};
#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
// IBusUiControllerImpl but forget to update the stub implementation.
class IBusUiControllerStubImpl : public IBusUiController {
 public:
  IBusUiControllerStubImpl() {
  }

  virtual void Connect() {
  }

  virtual void AddObserver(Observer* observer) {
  }

  virtual void RemoveObserver(Observer* observer) {
  }

  virtual void NotifyCandidateClicked(int index, int button, int flags) {
  }

  virtual void NotifyCursorUp() {
  }

  virtual void NotifyCursorDown() {
  }

  virtual void NotifyPageUp() {
  }

  virtual void NotifyPageDown() {
  }
};

IBusUiController* IBusUiController::Create() {
#if defined(HAVE_IBUS)
  return new IBusUiControllerImpl;
#else
  return new IBusUiControllerStubImpl;
#endif
}

IBusUiController::~IBusUiController() {
}

bool IsActiveForTesting(const std::string& input_method_id,
                        const InputMethodDescriptors* descriptors) {
  return IsActive(input_method_id, descriptors);
}

}  // namespace input_method
}  // namespace chromeos