// Copyright 2010-2021, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "unix/ibus/key_translator.h"

#include <map>
#include <set>
#include <string>

#include "base/logging.h"
#include "base/port.h"

namespace mozc {
namespace {
static const auto kSpecialKeyMap =
    new std::map<guint, commands::KeyEvent::SpecialKey>({
        {IBUS_space, commands::KeyEvent::SPACE},
        {IBUS_Return, commands::KeyEvent::ENTER},
        {IBUS_Left, commands::KeyEvent::LEFT},
        {IBUS_Right, commands::KeyEvent::RIGHT},
        {IBUS_Up, commands::KeyEvent::UP},
        {IBUS_Down, commands::KeyEvent::DOWN},
        {IBUS_Escape, commands::KeyEvent::ESCAPE},
        {IBUS_Delete, commands::KeyEvent::DEL},
        {IBUS_BackSpace, commands::KeyEvent::BACKSPACE},
        {IBUS_Insert, commands::KeyEvent::INSERT},
        {IBUS_Henkan, commands::KeyEvent::HENKAN},
        {IBUS_Muhenkan, commands::KeyEvent::MUHENKAN},
        {IBUS_Hiragana, commands::KeyEvent::KANA},
        {IBUS_Hiragana_Katakana, commands::KeyEvent::KANA},
        {IBUS_Katakana, commands::KeyEvent::KATAKANA},
        {IBUS_Zenkaku, commands::KeyEvent::HANKAKU},
        {IBUS_Hankaku, commands::KeyEvent::HANKAKU},
        {IBUS_Zenkaku_Hankaku, commands::KeyEvent::HANKAKU},
        {IBUS_Eisu_toggle, commands::KeyEvent::EISU},
        {IBUS_Home, commands::KeyEvent::HOME},
        {IBUS_End, commands::KeyEvent::END},
        {IBUS_Tab, commands::KeyEvent::TAB},
        {IBUS_F1, commands::KeyEvent::F1},
        {IBUS_F2, commands::KeyEvent::F2},
        {IBUS_F3, commands::KeyEvent::F3},
        {IBUS_F4, commands::KeyEvent::F4},
        {IBUS_F5, commands::KeyEvent::F5},
        {IBUS_F6, commands::KeyEvent::F6},
        {IBUS_F7, commands::KeyEvent::F7},
        {IBUS_F8, commands::KeyEvent::F8},
        {IBUS_F9, commands::KeyEvent::F9},
        {IBUS_F10, commands::KeyEvent::F10},
        {IBUS_F11, commands::KeyEvent::F11},
        {IBUS_F12, commands::KeyEvent::F12},
        {IBUS_F13, commands::KeyEvent::F13},
        {IBUS_F14, commands::KeyEvent::F14},
        {IBUS_F15, commands::KeyEvent::F15},
        {IBUS_F16, commands::KeyEvent::F16},
        {IBUS_F17, commands::KeyEvent::F17},
        {IBUS_F18, commands::KeyEvent::F18},
        {IBUS_F19, commands::KeyEvent::F19},
        {IBUS_F20, commands::KeyEvent::F20},
        {IBUS_F21, commands::KeyEvent::F21},
        {IBUS_F22, commands::KeyEvent::F22},
        {IBUS_F23, commands::KeyEvent::F23},
        {IBUS_F24, commands::KeyEvent::F24},
        {IBUS_Page_Up, commands::KeyEvent::PAGE_UP},
        {IBUS_Page_Down, commands::KeyEvent::PAGE_DOWN},

        // Keypad (10-key).
        {IBUS_KP_0, commands::KeyEvent::NUMPAD0},
        {IBUS_KP_1, commands::KeyEvent::NUMPAD1},
        {IBUS_KP_2, commands::KeyEvent::NUMPAD2},
        {IBUS_KP_3, commands::KeyEvent::NUMPAD3},
        {IBUS_KP_4, commands::KeyEvent::NUMPAD4},
        {IBUS_KP_5, commands::KeyEvent::NUMPAD5},
        {IBUS_KP_6, commands::KeyEvent::NUMPAD6},
        {IBUS_KP_7, commands::KeyEvent::NUMPAD7},
        {IBUS_KP_8, commands::KeyEvent::NUMPAD8},
        {IBUS_KP_9, commands::KeyEvent::NUMPAD9},
        {IBUS_KP_Equal, commands::KeyEvent::EQUALS},         // [=]
        {IBUS_KP_Multiply, commands::KeyEvent::MULTIPLY},    // [*]
        {IBUS_KP_Add, commands::KeyEvent::ADD},              // [+]
        {IBUS_KP_Separator, commands::KeyEvent::SEPARATOR},  // enter
        {IBUS_KP_Subtract, commands::KeyEvent::SUBTRACT},    // [-]
        {IBUS_KP_Decimal, commands::KeyEvent::DECIMAL},      // [.]
        {IBUS_KP_Divide, commands::KeyEvent::DIVIDE},        // [/]
        {IBUS_KP_Space, commands::KeyEvent::SPACE},
        {IBUS_KP_Tab, commands::KeyEvent::TAB},
        {IBUS_KP_Enter, commands::KeyEvent::ENTER},
        {IBUS_KP_Home, commands::KeyEvent::HOME},
        {IBUS_KP_Left, commands::KeyEvent::LEFT},
        {IBUS_KP_Up, commands::KeyEvent::UP},
        {IBUS_KP_Right, commands::KeyEvent::RIGHT},
        {IBUS_KP_Down, commands::KeyEvent::DOWN},
        {IBUS_KP_Page_Up, commands::KeyEvent::PAGE_UP},
        {IBUS_KP_Page_Down, commands::KeyEvent::PAGE_DOWN},
        {IBUS_KP_End, commands::KeyEvent::END},
        {IBUS_KP_Delete, commands::KeyEvent::DEL},
        {IBUS_KP_Insert, commands::KeyEvent::INSERT},
        {IBUS_Caps_Lock, commands::KeyEvent::CAPS_LOCK},

        // Shift+TAB.
        {IBUS_ISO_Left_Tab, commands::KeyEvent::TAB},

        // TODO(mazda): Handle following keys?
        //   - IBUS_Kana_Lock? IBUS_KEY_Kana_Shift?
    });

static const auto kModifierKeyMap =
    new std::map<guint, commands::KeyEvent::ModifierKey>({
        {IBUS_Shift_L, commands::KeyEvent::SHIFT},
        {IBUS_Shift_R, commands::KeyEvent::SHIFT},
        {IBUS_Control_L, commands::KeyEvent::CTRL},
        {IBUS_Control_R, commands::KeyEvent::CTRL},
        {IBUS_Alt_L, commands::KeyEvent::ALT},
        {IBUS_Alt_R, commands::KeyEvent::ALT},
        {IBUS_LOCK_MASK, commands::KeyEvent::CAPS},
    });

static const auto kModifierMaskMap =
    new std::map<guint, commands::KeyEvent::ModifierKey>({
        {IBUS_SHIFT_MASK, commands::KeyEvent::SHIFT},
        {IBUS_CONTROL_MASK, commands::KeyEvent::CTRL},
        {IBUS_MOD1_MASK, commands::KeyEvent::ALT},
    });

// Stores a mapping from ASCII to Kana character. For example, ASCII character
// '4' is mapped to Japanese 'Hiragana Letter U' (without Shift modifier) and
// 'Hiragana Letter Small U' (with Shift modifier).
// TODO(team): Add kana_map_dv to support Dvoraklayout.
typedef std::map<guint, std::pair<const char*, const char*>> KanaMap;
static const KanaMap *kKanaJpMap = new KanaMap({
        {'1', {"ぬ", "ぬ"}},
        {'!', {"ぬ", "ぬ"}},
        {'2', {"ふ", "ふ"}},
        {'\"', {"ふ", "ふ"}},
        {'3', {"あ", "ぁ"}},
        {'#', {"あ", "ぁ"}},
        {'4', {"う", "ぅ"}},
        {'$', {"う", "ぅ"}},
        {'5', {"え", "ぇ"}},
        {'%', {"え", "ぇ"}},
        {'6', {"お", "ぉ"}},
        {'&', {"お", "ぉ"}},
        {'7', {"や", "ゃ"}},
        {'\'', {"や", "ゃ"}},
        {'8', {"ゆ", "ゅ"}},
        {'(', {"ゆ", "ゅ"}},
        {'9', {"よ", "ょ"}},
        {')', {"よ", "ょ"}},
        {'0', {"わ", "を"}},
        {'-', {"ほ", "ほ"}},
        {'=', {"ほ", "ほ"}},
        {'^', {"へ", "を"}},
        {'~', {"へ", "を"}},
        {'|', {"ー", "ー"}},
        {'q', {"た", "た"}},
        {'Q', {"た", "た"}},
        {'w', {"て", "て"}},
        {'W', {"て", "て"}},
        {'e', {"い", "ぃ"}},
        {'E', {"い", "ぃ"}},
        {'r', {"す", "す"}},
        {'R', {"す", "す"}},
        {'t', {"か", "か"}},
        {'T', {"か", "か"}},
        {'y', {"ん", "ん"}},
        {'Y', {"ん", "ん"}},
        {'u', {"な", "な"}},
        {'U', {"な", "な"}},
        {'i', {"に", "に"}},
        {'I', {"に", "に"}},
        {'o', {"ら", "ら"}},
        {'O', {"ら", "ら"}},
        {'p', {"せ", "せ"}},
        {'P', {"せ", "せ"}},
        {'@', {"゛", "゛"}},
        {'`', {"゛", "゛"}},
        {'[', {"゜", "「"}},
        {'{', {"゜", "「"}},
        {'a', {"ち", "ち"}},
        {'A', {"ち", "ち"}},
        {'s', {"と", "と"}},
        {'S', {"と", "と"}},
        {'d', {"し", "し"}},
        {'D', {"し", "し"}},
        {'f', {"は", "は"}},
        {'F', {"は", "は"}},
        {'g', {"き", "き"}},
        {'G', {"き", "き"}},
        {'h', {"く", "く"}},
        {'H', {"く", "く"}},
        {'j', {"ま", "ま"}},
        {'J', {"ま", "ま"}},
        {'k', {"の", "の"}},
        {'K', {"の", "の"}},
        {'l', {"り", "り"}},
        {'L', {"り", "り"}},
        {';', {"れ", "れ"}},
        {'+', {"れ", "れ"}},
        {':', {"け", "け"}},
        {'*', {"け", "け"}},
        {']', {"む", "」"}},
        {'}', {"む", "」"}},
        {'z', {"つ", "っ"}},
        {'Z', {"つ", "っ"}},
        {'x', {"さ", "さ"}},
        {'X', {"さ", "さ"}},
        {'c', {"そ", "そ"}},
        {'C', {"そ", "そ"}},
        {'v', {"ひ", "ひ"}},
        {'V', {"ひ", "ひ"}},
        {'b', {"こ", "こ"}},
        {'B', {"こ", "こ"}},
        {'n', {"み", "み"}},
        {'N', {"み", "み"}},
        {'m', {"も", "も"}},
        {'M', {"も", "も"}},
        {',', {"ね", "、"}},
        {'<', {"ね", "、"}},
        {'.', {"る", "。"}},
        {'>', {"る", "。"}},
        {'/', {"め", "・"}},
        {'?', {"め", "・"}},
        {'_', {"ろ", "ろ"}},
        // A backslash is handled in a special way because it is input by
        // two different keys (the one next to Backslash and the one next
        // to Right Shift).
        {'\\', {"", ""}},
    });

static const KanaMap *kKanaUsMap = new KanaMap({
      {'`', {"ろ", "ろ"}},  {'~', {"ろ", "ろ"}},  {'1', {"ぬ", "ぬ"}},
      {'!', {"ぬ", "ぬ"}},  {'2', {"ふ", "ふ"}},  {'@', {"ふ", "ふ"}},
      {'3', {"あ", "ぁ"}},  {'#', {"あ", "ぁ"}},  {'4', {"う", "ぅ"}},
      {'$', {"う", "ぅ"}},  {'5', {"え", "ぇ"}},  {'%', {"え", "ぇ"}},
      {'6', {"お", "ぉ"}},  {'^', {"お", "ぉ"}},  {'7', {"や", "ゃ"}},
      {'&', {"や", "ゃ"}},  {'8', {"ゆ", "ゅ"}},  {'*', {"ゆ", "ゅ"}},
      {'9', {"よ", "ょ"}},  {'(', {"よ", "ょ"}},  {'0', {"わ", "を"}},
      {')', {"わ", "を"}},  {'-', {"ほ", "ー"}},  {'_', {"ほ", "ー"}},
      {'=', {"へ", "へ"}},  {'+', {"へ", "へ"}},  {'q', {"た", "た"}},
      {'Q', {"た", "た"}},  {'w', {"て", "て"}},  {'W', {"て", "て"}},
      {'e', {"い", "ぃ"}},  {'E', {"い", "ぃ"}},  {'r', {"す", "す"}},
      {'R', {"す", "す"}},  {'t', {"か", "か"}},  {'T', {"か", "か"}},
      {'y', {"ん", "ん"}},  {'Y', {"ん", "ん"}},  {'u', {"な", "な"}},
      {'U', {"な", "な"}},  {'i', {"に", "に"}},  {'I', {"に", "に"}},
      {'o', {"ら", "ら"}},  {'O', {"ら", "ら"}},  {'p', {"せ", "せ"}},
      {'P', {"せ", "せ"}},  {'[', {"゛", "゛"}},  {'{', {"゛", "゛"}},
      {']', {"゜", "「"}},  {'}', {"゜", "「"}},  {'\\', {"む", "」"}},
      {'|', {"む", "」"}},  {'a', {"ち", "ち"}},  {'A', {"ち", "ち"}},
      {'s', {"と", "と"}},  {'S', {"と", "と"}},  {'d', {"し", "し"}},
      {'D', {"し", "し"}},  {'f', {"は", "は"}},  {'F', {"は", "は"}},
      {'g', {"き", "き"}},  {'G', {"き", "き"}},  {'h', {"く", "く"}},
      {'H', {"く", "く"}},  {'j', {"ま", "ま"}},  {'J', {"ま", "ま"}},
      {'k', {"の", "の"}},  {'K', {"の", "の"}},  {'l', {"り", "り"}},
      {'L', {"り", "り"}},  {';', {"れ", "れ"}},  {':', {"れ", "れ"}},
      {'\'', {"け", "け"}}, {'\"', {"け", "け"}}, {'z', {"つ", "っ"}},
      {'Z', {"つ", "っ"}},  {'x', {"さ", "さ"}},  {'X', {"さ", "さ"}},
      {'c', {"そ", "そ"}},  {'C', {"そ", "そ"}},  {'v', {"ひ", "ひ"}},
      {'V', {"ひ", "ひ"}},  {'b', {"こ", "こ"}},  {'B', {"こ", "こ"}},
      {'n', {"み", "み"}},  {'N', {"み", "み"}},  {'m', {"も", "も"}},
      {'M', {"も", "も"}},  {',', {"ね", "、"}},  {'<', {"ね", "、"}},
      {'.', {"る", "。"}},  {'>', {"る", "。"}},  {'/', {"め", "・"}},
      {'?', {"め", "・"}},
    });

}  // namespace

namespace ibus {

// TODO(nona): Fix 'Shift-0' behavior b/4338394
bool KeyTranslator::Translate(guint keyval, guint keycode, guint modifiers,
                              config::Config::PreeditMethod method,
                              bool layout_is_jp,
                              commands::KeyEvent *out_event) const {
  DCHECK(out_event) << "out_event is nullptr";
  out_event->Clear();

  // Due to historical reasons, many linux ditributions set Hiragana_Katakana
  // key as Hiragana key (which is Katkana key with shift modifier). So, we
  // translate Hiragana_Katanaka key as Hiragana key by mapping table, and
  // Shift + Hiragana_Katakana key as Katakana key by functionally.
  // TODO(nona): Fix process modifier to handle right shift
  if (IsHiraganaKatakanaKeyWithShift(keyval, keycode, modifiers)) {
    modifiers &= ~IBUS_SHIFT_MASK;
    keyval = IBUS_Katakana;
  }
  std::string kana_key_string;
  if ((method == config::Config::KANA) &&
      IsKanaAvailable(keyval, keycode, modifiers, layout_is_jp,
                      &kana_key_string)) {
    out_event->set_key_code(keyval);
    out_event->set_key_string(kana_key_string);
  } else if (IsAscii(keyval, keycode, modifiers)) {
    if (IBUS_LOCK_MASK & modifiers) {
      out_event->add_modifier_keys(commands::KeyEvent::CAPS);
    }
    out_event->set_key_code(keyval);
  } else if (auto it = kModifierKeyMap->find(keyval);
             it != kModifierKeyMap->end()) {
    out_event->add_modifier_keys(it->second);
  } else if (auto it = kSpecialKeyMap->find(keyval);
             it != kSpecialKeyMap->end()) {
    out_event->set_special_key(it->second);
  } else {
    VLOG(1) << "Unknown keyval: " << keyval;
    return false;
  }

  for (auto it = kModifierMaskMap->begin();
       it != kModifierMaskMap->end(); ++it) {
    // Do not set a SHIFT modifier when |keyval| is a printable key by following
    // the Mozc's rule.
    if ((it->second == commands::KeyEvent::SHIFT) &&
        IsPrintable(keyval, keycode, modifiers)) {
      continue;
    }

    if (it->first & modifiers) {
      // Add a modifier key if doesn't exist.
      commands::KeyEvent::ModifierKey modifier = it->second;
      bool found = false;
      for (int i = 0; i < out_event->modifier_keys_size(); ++i) {
        if (modifier == out_event->modifier_keys(i)) {
          found = true;
          break;
        }
      }
      if (!found) {
        out_event->add_modifier_keys(modifier);
      }
    }
  }

  return true;
}

bool KeyTranslator::IsHiraganaKatakanaKeyWithShift(guint keyval, guint keycode,
                                                   guint modifiers) {
  return ((modifiers & IBUS_SHIFT_MASK) && (keyval == IBUS_Hiragana_Katakana));
}

bool KeyTranslator::IsKanaAvailable(guint keyval, guint keycode,
                                    guint modifiers, bool layout_is_jp,
                                    std::string *out) const {
  if ((modifiers & IBUS_CONTROL_MASK) || (modifiers & IBUS_MOD1_MASK)) {
    return false;
  }
  const KanaMap &kana_map = layout_is_jp ? *kKanaJpMap : *kKanaUsMap;
  KanaMap::const_iterator iter = kana_map.find(keyval);
  if (iter == kana_map.end()) {
    return false;
  }

  if (out) {
    // When a Japanese keyboard is in use, the yen-sign key and the backslash
    // key generate the same |keyval|. In this case, we have to check |keycode|
    // to return an appropriate string. See the following IBus issue for
    // details: https://github.com/ibus/ibus/issues/73
    if (keyval == '\\' && layout_is_jp) {
      if (keycode == IBUS_bar) {
        *out = "ー";
      } else {
        *out = "ろ";
      }
    } else {
      *out = (modifiers & IBUS_SHIFT_MASK) ? iter->second.second
                                           : iter->second.first;
    }
  }
  return true;
}

// TODO(nona): resolve S-'0' problem (b/4338394).
// TODO(nona): Current printable detection is weak. To enhance accuracy, use xkb
// key map
bool KeyTranslator::IsPrintable(guint keyval, guint keycode, guint modifiers) {
  if ((modifiers & IBUS_CONTROL_MASK) || (modifiers & IBUS_MOD1_MASK)) {
    return false;
  }
  return IsAscii(keyval, keycode, modifiers);
}

bool KeyTranslator::IsAscii(guint keyval, guint keycode, guint modifiers) {
  return (keyval > IBUS_space &&
          // Note: Space key (0x20) is a special key in Mozc.
          keyval <= IBUS_asciitilde);  // 0x7e.
}

}  // namespace ibus
}  // namespace mozc
