// 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/extensions/api/input_ime/input_ime_api.h"

#include "base/lazy_instance.h"
#include "chrome/common/extensions/api/input_ime.h"
#include "extensions/browser/extension_registry.h"

namespace input_ime = extensions::api::input_ime;
namespace KeyEventHandled = extensions::api::input_ime::KeyEventHandled;
namespace SetComposition = extensions::api::input_ime::SetComposition;
namespace CommitText = extensions::api::input_ime::CommitText;
namespace SendKeyEvents = extensions::api::input_ime::SendKeyEvents;
using ui::IMEEngineHandlerInterface;
using input_method::InputMethodEngineBase;

namespace {
const char kErrorEngineNotAvailable[] = "Engine is not available";
const char kErrorSetKeyEventsFail[] = "Could not send key events";
}
namespace ui {

ImeObserver::ImeObserver(const std::string& extension_id, Profile* profile)
    : extension_id_(extension_id), profile_(profile) {}

void ImeObserver::OnActivate(const std::string& component_id) {
  if (extension_id_.empty() || !HasListener(input_ime::OnActivate::kEventName))
    return;

  scoped_ptr<base::ListValue> args(input_ime::OnActivate::Create(
    component_id,
    input_ime::ParseScreenType(GetCurrentScreenType())));

  DispatchEventToExtension(extensions::events::INPUT_IME_ON_ACTIVATE,
                           input_ime::OnActivate::kEventName,
                           std::move(args));
}

void ImeObserver::OnFocus(
    const IMEEngineHandlerInterface::InputContext& context) {
  if (extension_id_.empty() || !HasListener(input_ime::OnFocus::kEventName))
    return;

  input_ime::InputContext context_value;
  context_value.context_id = context.id;
  context_value.type =
      input_ime::ParseInputContextType(ConvertInputContextType(context));
  context_value.auto_correct = ConvertInputContextAutoCorrect(context);
  context_value.auto_complete = ConvertInputContextAutoComplete(context);
  context_value.spell_check = ConvertInputContextSpellCheck(context);

  scoped_ptr<base::ListValue> args(input_ime::OnFocus::Create(context_value));

  DispatchEventToExtension(extensions::events::INPUT_IME_ON_FOCUS,
                           input_ime::OnFocus::kEventName, std::move(args));
}

void ImeObserver::OnBlur(int context_id) {
  if (extension_id_.empty() || !HasListener(input_ime::OnBlur::kEventName))
    return;

  scoped_ptr<base::ListValue> args(input_ime::OnBlur::Create(context_id));

  DispatchEventToExtension(extensions::events::INPUT_IME_ON_BLUR,
                           input_ime::OnBlur::kEventName, std::move(args));
}

void ImeObserver::OnKeyEvent(
    const std::string& component_id,
    const InputMethodEngineBase::KeyboardEvent& event,
    IMEEngineHandlerInterface::KeyEventDoneCallback& key_data) {
  if (extension_id_.empty())
    return;

  // If there is no listener for the event, no need to dispatch the event to
  // extension. Instead, releases the key event for default system behavior.
  if (!ShouldForwardKeyEvent()) {
    // Continue processing the key event so that the physical keyboard can
    // still work.
    key_data.Run(false);
    return;
  }

  extensions::InputImeEventRouter* event_router =
      extensions::GetInputImeEventRouter(profile_);
  if (!event_router || !event_router->GetActiveEngine(extension_id_))
    return;
  const std::string request_id = event_router->GetActiveEngine(extension_id_)
                                     ->AddRequest(component_id, key_data);

  input_ime::KeyboardEvent key_data_value;
  key_data_value.type = input_ime::ParseKeyboardEventType(event.type);
  key_data_value.request_id = request_id;
  if (!event.extension_id.empty())
      key_data_value.extension_id.reset(new std::string(event.extension_id));
  key_data_value.key = event.key;
  key_data_value.code = event.code;
  key_data_value.alt_key.reset(new bool(event.alt_key));
  key_data_value.ctrl_key.reset(new bool(event.ctrl_key));
  key_data_value.shift_key.reset(new bool(event.shift_key));
  key_data_value.caps_lock.reset(new bool(event.caps_lock));

  scoped_ptr<base::ListValue> args(
      input_ime::OnKeyEvent::Create(component_id, key_data_value));

  DispatchEventToExtension(extensions::events::INPUT_IME_ON_KEY_EVENT,
                           input_ime::OnKeyEvent::kEventName, std::move(args));
}

void ImeObserver::OnReset(const std::string& component_id) {
  if (extension_id_.empty() || !HasListener(input_ime::OnReset::kEventName))
    return;

  scoped_ptr<base::ListValue> args(input_ime::OnReset::Create(component_id));

  DispatchEventToExtension(extensions::events::INPUT_IME_ON_RESET,
                           input_ime::OnReset::kEventName, std::move(args));
}

void ImeObserver::OnDeactivated(const std::string& component_id) {
  if (extension_id_.empty() ||
      !HasListener(input_ime::OnDeactivated::kEventName))
    return;

  scoped_ptr<base::ListValue> args(
      input_ime::OnDeactivated::Create(component_id));

  DispatchEventToExtension(extensions::events::INPUT_IME_ON_DEACTIVATED,
                           input_ime::OnDeactivated::kEventName,
                           std::move(args));
}

// TODO(azurewei): This function implementation should be shared on all
// platforms, while with some changing on the current code on ChromeOS.
void ImeObserver::OnCompositionBoundsChanged(
    const std::vector<gfx::Rect>& bounds) {}

bool ImeObserver::IsInterestedInKeyEvent() const {
  return ShouldForwardKeyEvent();
}

void ImeObserver::OnSurroundingTextChanged(const std::string& component_id,
                                           const std::string& text,
                                           int cursor_pos,
                                           int anchor_pos,
                                           int offset_pos) {
  if (extension_id_.empty() ||
      !HasListener(input_ime::OnSurroundingTextChanged::kEventName))
    return;

  input_ime::OnSurroundingTextChanged::SurroundingInfo info;
  info.text = text;
  info.focus = cursor_pos;
  info.anchor = anchor_pos;
  info.offset = offset_pos;
  scoped_ptr<base::ListValue> args(
    input_ime::OnSurroundingTextChanged::Create(component_id, info));

  DispatchEventToExtension(
    extensions::events::INPUT_IME_ON_SURROUNDING_TEXT_CHANGED,
    input_ime::OnSurroundingTextChanged::kEventName, std::move(args));
}

bool ImeObserver::ShouldForwardKeyEvent() const {
  // Only forward key events to extension if there are non-lazy listeners
  // for onKeyEvent. Because if something wrong with the lazy background
  // page which doesn't register listener for onKeyEvent, it will not handle
  // the key events, and therefore, all key events will be eaten.
  // This is for error-tolerance, and it means that onKeyEvent will never wake
  // up lazy background page.
  const extensions::EventListenerMap::ListenerList& listener_list =
      extensions::EventRouter::Get(profile_)
          ->listeners()
          .GetEventListenersByName(input_ime::OnKeyEvent::kEventName);
  for (extensions::EventListenerMap::ListenerList::const_iterator it =
           listener_list.begin();
       it != listener_list.end(); ++it) {
    if ((*it)->extension_id() == extension_id_ && !(*it)->IsLazy())
      return true;
  }
  return false;
}

bool ImeObserver::HasListener(const std::string& event_name) const {
  return extensions::EventRouter::Get(profile_)->HasEventListener(event_name);
}

std::string ImeObserver::ConvertInputContextType(
    ui::IMEEngineHandlerInterface::InputContext input_context) {
  std::string input_context_type = "text";
  switch (input_context.type) {
    case ui::TEXT_INPUT_TYPE_SEARCH:
      input_context_type = "search";
      break;
    case ui::TEXT_INPUT_TYPE_TELEPHONE:
      input_context_type = "tel";
      break;
    case ui::TEXT_INPUT_TYPE_URL:
      input_context_type = "url";
      break;
    case ui::TEXT_INPUT_TYPE_EMAIL:
      input_context_type = "email";
      break;
    case ui::TEXT_INPUT_TYPE_NUMBER:
      input_context_type = "number";
      break;
    case ui::TEXT_INPUT_TYPE_PASSWORD:
      input_context_type = "password";
      break;
    default:
      input_context_type = "text";
      break;
  }
  return input_context_type;
}

bool ImeObserver::ConvertInputContextAutoCorrect(
    ui::IMEEngineHandlerInterface::InputContext input_context) {
  return !(input_context.flags & ui::TEXT_INPUT_FLAG_AUTOCORRECT_OFF);
}

bool ImeObserver::ConvertInputContextAutoComplete(
    ui::IMEEngineHandlerInterface::InputContext input_context) {
  return !(input_context.flags & ui::TEXT_INPUT_FLAG_AUTOCOMPLETE_OFF);
}

bool ImeObserver::ConvertInputContextSpellCheck(
    ui::IMEEngineHandlerInterface::InputContext input_context) {
  return !(input_context.flags & ui::TEXT_INPUT_FLAG_SPELLCHECK_OFF);
}

}  // namespace ui

namespace extensions {

InputImeEventRouterFactory* InputImeEventRouterFactory::GetInstance() {
  return base::Singleton<InputImeEventRouterFactory>::get();
}

InputImeEventRouterFactory::InputImeEventRouterFactory() {
}

InputImeEventRouterFactory::~InputImeEventRouterFactory() {
}

InputImeEventRouter* InputImeEventRouterFactory::GetRouter(Profile* profile) {
  if (!profile)
    return nullptr;
  InputImeEventRouter* router = router_map_[profile];
  if (!router) {
    router = new InputImeEventRouter(profile);
    router_map_[profile] = router;
  }
  return router;
}

ExtensionFunction::ResponseAction InputImeKeyEventHandledFunction::Run() {
  scoped_ptr<KeyEventHandled::Params> params(
      KeyEventHandled::Params::Create(*args_));
  InputImeEventRouter* event_router =
      GetInputImeEventRouter(Profile::FromBrowserContext(browser_context()));
  InputMethodEngineBase* engine =
      event_router ? event_router->GetActiveEngine(extension_id()) : nullptr;
  if (engine) {
    engine->KeyEventHandled(extension_id(), params->request_id,
                            params->response);
  }
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction InputImeSetCompositionFunction::Run() {
  bool success = false;
  InputImeEventRouter* event_router =
      GetInputImeEventRouter(Profile::FromBrowserContext(browser_context()));
  InputMethodEngineBase* engine =
      event_router ? event_router->GetActiveEngine(extension_id()) : nullptr;
  if (engine) {
    scoped_ptr<SetComposition::Params> parent_params(
        SetComposition::Params::Create(*args_));
    const SetComposition::Params::Parameters& params =
        parent_params->parameters;
    std::vector<InputMethodEngineBase::SegmentInfo> segments;
    if (params.segments) {
      const std::vector<
          linked_ptr<SetComposition::Params::Parameters::SegmentsType>>&
          segments_args = *params.segments;
      for (const auto& segments_arg : segments_args) {
        EXTENSION_FUNCTION_VALIDATE(segments_arg->style !=
                                    input_ime::UNDERLINE_STYLE_NONE);
        InputMethodEngineBase::SegmentInfo segment_info;
        segment_info.start = segments_arg->start;
        segment_info.end = segments_arg->end;
        if (segments_arg->style == input_ime::UNDERLINE_STYLE_UNDERLINE) {
          segment_info.style = InputMethodEngineBase::SEGMENT_STYLE_UNDERLINE;
        } else if (segments_arg->style ==
                   input_ime::UNDERLINE_STYLE_DOUBLEUNDERLINE) {
          segment_info.style =
              InputMethodEngineBase::SEGMENT_STYLE_DOUBLE_UNDERLINE;
        } else {
          segment_info.style =
              InputMethodEngineBase::SEGMENT_STYLE_NO_UNDERLINE;
        }
        segments.push_back(segment_info);
      }
    }
    int selection_start =
        params.selection_start ? *params.selection_start : params.cursor;
    int selection_end =
        params.selection_end ? *params.selection_end : params.cursor;
    success = engine->SetComposition(params.context_id, params.text.c_str(),
                                     selection_start, selection_end,
                                     params.cursor, segments, &error_);
  }
  scoped_ptr<base::ListValue> output = SetComposition::Results::Create(success);
  return RespondNow(ArgumentList(std::move(output)));
}

ExtensionFunction::ResponseAction InputImeCommitTextFunction::Run() {
  bool success = false;
  InputImeEventRouter* event_router =
      GetInputImeEventRouter(Profile::FromBrowserContext(browser_context()));
  InputMethodEngineBase* engine =
      event_router ? event_router->GetActiveEngine(extension_id()) : nullptr;
  if (engine) {
    scoped_ptr<CommitText::Params> parent_params(
        CommitText::Params::Create(*args_));
    const CommitText::Params::Parameters& params = parent_params->parameters;
    success =
        engine->CommitText(params.context_id, params.text.c_str(), &error_);
  }
  scoped_ptr<base::ListValue> output = CommitText::Results::Create(success);
  return RespondNow(ArgumentList(std::move(output)));
}

ExtensionFunction::ResponseAction InputImeSendKeyEventsFunction::Run() {
  InputImeEventRouter* event_router =
      GetInputImeEventRouter(Profile::FromBrowserContext(browser_context()));
  InputMethodEngineBase* engine =
      event_router ? event_router->GetActiveEngine(extension_id()) : nullptr;
  if (!engine)
    return RespondNow(Error(kErrorEngineNotAvailable));

  scoped_ptr<SendKeyEvents::Params> parent_params(
      SendKeyEvents::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(parent_params);
  const SendKeyEvents::Params::Parameters& params = parent_params->parameters;
  const std::vector<linked_ptr<input_ime::KeyboardEvent>>& key_data =
      params.key_data;
  std::vector<InputMethodEngineBase::KeyboardEvent> key_data_out;

  for (const auto& key_event : key_data) {
    InputMethodEngineBase::KeyboardEvent event;
    event.type = input_ime::ToString(key_event->type);
    event.key = key_event->key;
    event.code = key_event->code;
    event.key_code = key_event->key_code.get() ? *(key_event->key_code) : 0;
    event.alt_key = key_event->alt_key ? *(key_event->alt_key) : false;
    event.ctrl_key = key_event->ctrl_key ? *(key_event->ctrl_key) : false;
    event.shift_key = key_event->shift_key ? *(key_event->shift_key) : false;
    event.caps_lock = key_event->caps_lock ? *(key_event->caps_lock) : false;
    key_data_out.push_back(event);
  }
  if (!engine->SendKeyEvents(params.context_id, key_data_out))
    return RespondNow(Error(kErrorSetKeyEventsFail));
  return RespondNow(NoArguments());
}

InputImeAPI::InputImeAPI(content::BrowserContext* context)
    : browser_context_(context), extension_registry_observer_(this) {
  extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));

  EventRouter* event_router = EventRouter::Get(browser_context_);
  event_router->RegisterObserver(this, input_ime::OnFocus::kEventName);
}

InputImeAPI::~InputImeAPI() {
  EventRouter::Get(browser_context_)->UnregisterObserver(this);
}

static base::LazyInstance<BrowserContextKeyedAPIFactory<InputImeAPI> >
    g_factory = LAZY_INSTANCE_INITIALIZER;

// static
BrowserContextKeyedAPIFactory<InputImeAPI>* InputImeAPI::GetFactoryInstance() {
  return g_factory.Pointer();
}

InputImeEventRouter* GetInputImeEventRouter(Profile* profile) {
  if (!profile)
    return nullptr;
  if (profile->HasOffTheRecordProfile())
    profile = profile->GetOffTheRecordProfile();
  return extensions::InputImeEventRouterFactory::GetInstance()->GetRouter(
      profile);
}

}  // namespace extensions