// 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 "content/child/npapi/webplugin_ime_win.h"

#include <stddef.h>
#include <string.h>

#include <cstring>
#include <string>
#include <vector>

#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "content/child/npapi/plugin_instance.h"
#include "content/common/plugin_constants_win.h"

#pragma comment(lib, "imm32.lib")

namespace content {

// A critical section that prevents two or more plugins from accessing a
// WebPluginIMEWin instance through our patch function.
base::LazyInstance<base::Lock>::Leaky
    g_webplugin_ime_lock = LAZY_INSTANCE_INITIALIZER;

WebPluginIMEWin* WebPluginIMEWin::instance_ = NULL;

WebPluginIMEWin::WebPluginIMEWin()
    : cursor_position_(0),
      delta_start_(0),
      composing_text_(false),
      support_ime_messages_(false),
      status_updated_(false),
      input_type_(1) {
  memset(result_clauses_, 0, sizeof(result_clauses_));
}

WebPluginIMEWin::~WebPluginIMEWin() {
}

void WebPluginIMEWin::CompositionUpdated(const base::string16& text,
                                         std::vector<int> clauses,
                                         std::vector<int> target,
                                         int cursor_position) {
  // Send a WM_IME_STARTCOMPOSITION message when a user starts a composition.
  NPEvent np_event;
  if (!composing_text_) {
    composing_text_ = true;
    result_text_.clear();

    np_event.event = WM_IME_STARTCOMPOSITION;
    np_event.wParam = 0;
    np_event.lParam = 0;
    events_.push_back(np_event);
  }

  // We can update the following values from this event: GCS_COMPSTR,
  // GCS_COMPATTR, GCSCOMPCLAUSE, GCS_CURSORPOS, and GCS_DELTASTART. We send a
  // WM_IME_COMPOSITION message to notify the list of updated values.
  np_event.event = WM_IME_COMPOSITION;
  np_event.wParam = 0;
  np_event.lParam = GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE |
      GCS_CURSORPOS | GCS_DELTASTART;
  events_.push_back(np_event);

  // Converts this event to the IMM32 data so we do not have to convert it every
  // time when a plugin call an IMM32 function.
  composition_text_ = text;

  // Create the composition clauses returned when a plugin calls
  // ImmGetCompositionString() with GCS_COMPCLAUSE.
  composition_clauses_.clear();
  for (size_t i = 0; i < clauses.size(); ++i)
    composition_clauses_.push_back(clauses[i]);

  // Create the composition attributes used by GCS_COMPATTR.
  if (target.size() == 2) {
    composition_attributes_.assign(text.length(), ATTR_CONVERTED);
    for (int i = target[0]; i < target[1]; ++i)
        composition_attributes_[i] = ATTR_TARGET_CONVERTED;
  } else {
    composition_attributes_.assign(text.length(), ATTR_INPUT);
  }

  cursor_position_ = cursor_position;
  delta_start_ = cursor_position;
}

void WebPluginIMEWin::CompositionCompleted(const base::string16& text) {
  composing_text_ = false;

  // We should update the following values when we finish a composition:
  // GCS_RESULTSTR, GCS_RESULTCLAUSE, GCS_CURSORPOS, and GCS_DELTASTART. We
  // send a WM_IME_COMPOSITION message to notify the list of updated values.
  NPEvent np_event;
  np_event.event = WM_IME_COMPOSITION;
  np_event.wParam = 0;
  np_event.lParam = GCS_CURSORPOS | GCS_DELTASTART | GCS_RESULTSTR |
      GCS_RESULTCLAUSE;
  events_.push_back(np_event);

  // We also send a WM_IME_ENDCOMPOSITION message after the final
  // WM_IME_COMPOSITION message (i.e. after finishing a composition).
  np_event.event = WM_IME_ENDCOMPOSITION;
  np_event.wParam = 0;
  np_event.lParam = 0;
  events_.push_back(np_event);

  // If the target plugin does not seem to support IME messages, we send
  // each character in IME text with a WM_CHAR message so the plugin can
  // insert the IME text.
  if (!support_ime_messages_) {
    np_event.event = WM_CHAR;
    np_event.wParam = 0;
    np_event.lParam = 0;
    for (size_t i = 0; i < result_text_.length(); ++i) {
      np_event.wParam = result_text_[i];
      events_.push_back(np_event);
    }
  }

  // Updated the result text and its clause. (Unlike composition clauses, a
  // result clause consists of only one region.)
  result_text_ = text;

  result_clauses_[0] = 0;
  result_clauses_[1] = result_text_.length();

  cursor_position_ = result_clauses_[1];
  delta_start_ = result_clauses_[1];
}

bool WebPluginIMEWin::SendEvents(PluginInstance* instance) {
  // We allow the patch functions to access this WebPluginIMEWin instance only
  // while we send IME events to the plugin.
  ScopedLock lock(this);

  bool ret = true;
  for (std::vector<NPEvent>::iterator it = events_.begin();
       it != events_.end(); ++it) {
    if (!instance->NPP_HandleEvent(&(*it)))
      ret = false;
  }

  events_.clear();
  return ret;
}

bool WebPluginIMEWin::GetStatus(int* input_type, gfx::Rect* caret_rect) {
  *input_type = input_type_;
  *caret_rect = caret_rect_;
  return true;
}

// static
FARPROC WebPluginIMEWin::GetProcAddress(LPCSTR name) {
  static const struct {
    const char* name;
    FARPROC function;
  } kImm32Functions[] = {
    { "ImmAssociateContextEx",
        reinterpret_cast<FARPROC>(ImmAssociateContextEx) },
    { "ImmGetCompositionStringW",
        reinterpret_cast<FARPROC>(ImmGetCompositionStringW) },
    { "ImmGetContext", reinterpret_cast<FARPROC>(ImmGetContext) },
    { "ImmReleaseContext", reinterpret_cast<FARPROC>(ImmReleaseContext) },
    { "ImmSetCandidateWindow",
        reinterpret_cast<FARPROC>(ImmSetCandidateWindow) },
    { "ImmSetOpenStatus", reinterpret_cast<FARPROC>(ImmSetOpenStatus) },
  };
  for (size_t i = 0; i < arraysize(kImm32Functions); ++i) {
    if (!lstrcmpiA(name, kImm32Functions[i].name))
      return kImm32Functions[i].function;
  }
  return NULL;
}

void WebPluginIMEWin::Lock() {
  g_webplugin_ime_lock.Pointer()->Acquire();
  instance_ = this;
}

void WebPluginIMEWin::Unlock() {
  instance_ = NULL;
  g_webplugin_ime_lock.Pointer()->Release();
}

// static
WebPluginIMEWin* WebPluginIMEWin::GetInstance(HIMC context) {
  return instance_ && context == reinterpret_cast<HIMC>(instance_) ?
      instance_ : NULL;
}

// static
BOOL WINAPI WebPluginIMEWin::ImmAssociateContextEx(HWND window,
                                                   HIMC context,
                                                   DWORD flags) {
  WebPluginIMEWin* instance = GetInstance(context);
  if (!instance)
    return ::ImmAssociateContextEx(window, context, flags);

  int input_type = !context && !flags;
  instance->input_type_ = input_type;
  instance->status_updated_ = true;
  return TRUE;
}

// static
LONG WINAPI WebPluginIMEWin::ImmGetCompositionStringW(HIMC context,
                                                      DWORD index,
                                                      LPVOID dst_data,
                                                      DWORD dst_size) {
  WebPluginIMEWin* instance = GetInstance(context);
  if (!instance)
    return ::ImmGetCompositionStringW(context, index, dst_data, dst_size);

  const void* src_data = NULL;
  DWORD src_size = 0;
  switch (index) {
    case GCS_COMPSTR:
      src_data = instance->composition_text_.c_str();
      src_size = instance->composition_text_.length() * sizeof(wchar_t);
      break;

    case GCS_COMPATTR:
      src_data = instance->composition_attributes_.c_str();
      src_size = instance->composition_attributes_.length();
      break;

    case GCS_COMPCLAUSE:
      src_data = &instance->composition_clauses_[0];
      src_size = instance->composition_clauses_.size() * sizeof(uint32_t);
      break;

    case GCS_CURSORPOS:
      return instance->cursor_position_;

    case GCS_DELTASTART:
      return instance->delta_start_;

    case GCS_RESULTSTR:
      src_data = instance->result_text_.c_str();
      src_size = instance->result_text_.length() * sizeof(wchar_t);
      break;

    case GCS_RESULTCLAUSE:
      src_data = &instance->result_clauses_[0];
      src_size = sizeof(instance->result_clauses_);
      break;

    default:
      break;
  }
  if (!src_data || !src_size)
    return IMM_ERROR_NODATA;

  if (dst_size >= src_size)
    memcpy(dst_data, src_data, src_size);

  return src_size;
}

// static
HIMC WINAPI WebPluginIMEWin::ImmGetContext(HWND window) {
  // Call the original ImmGetContext() function if the given window is the one
  // created in WebPluginDelegateImpl::WindowedCreatePlugin(). (We attached IME
  // context only with the windows created in this function.) On the other hand,
  // some windowless plugins (such as Flash) call this function with a dummy
  // window handle. We return our dummy IME context for these plugins so they
  // can use our IME emulator.
  if (IsWindow(window)) {
    wchar_t name[128];
    GetClassName(window, &name[0], arraysize(name));
    if (!wcscmp(&name[0], kNativeWindowClassName))
      return ::ImmGetContext(window);
  }

  WebPluginIMEWin* instance = instance_;
  if (instance)
    instance->support_ime_messages_ = true;
  return reinterpret_cast<HIMC>(instance);
}

// static
BOOL WINAPI WebPluginIMEWin::ImmReleaseContext(HWND window, HIMC context) {
  if (!GetInstance(context))
    return ::ImmReleaseContext(window, context);
  return TRUE;
}

// static
BOOL WINAPI WebPluginIMEWin::ImmSetCandidateWindow(HIMC context,
                                                   CANDIDATEFORM* candidate) {
  WebPluginIMEWin* instance = GetInstance(context);
  if (!instance)
    return ::ImmSetCandidateWindow(context, candidate);

  gfx::Rect caret_rect(candidate->rcArea);
  if ((candidate->dwStyle & CFS_EXCLUDE) &&
      instance->caret_rect_ != caret_rect) {
    instance->caret_rect_ = caret_rect;
    instance->status_updated_ = true;
  }
  return TRUE;
}

// static
BOOL WINAPI WebPluginIMEWin::ImmSetOpenStatus(HIMC context, BOOL open) {
  WebPluginIMEWin* instance = GetInstance(context);
  if (!instance)
    return ::ImmSetOpenStatus(context, open);

  int input_type = open ? 1 : 0;
  if (instance->input_type_ != input_type) {
    instance->input_type_ = input_type;
    instance->status_updated_ = true;
  }

  return TRUE;
}

}  // namespace content
