// Copyright 2010-2012, Google Inc.
// Copyright 2012-2017, Weng Xuetian <wengxt@gmail.com>
// 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/fcitx5/fcitx_key_event_handler.h"

#include <map>

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

namespace fcitx {

namespace {
// TODO(hsumita): Removes this class, and moves |data_| into member
// variables of KeyEventhandler.
class AdditionalModifiersData {
 public:
  AdditionalModifiersData() {
    data_[mozc::commands::KeyEvent::LEFT_ALT] = mozc::commands::KeyEvent::ALT;
    data_[mozc::commands::KeyEvent::RIGHT_ALT] = mozc::commands::KeyEvent::ALT;
    data_[mozc::commands::KeyEvent::LEFT_CTRL] = mozc::commands::KeyEvent::CTRL;
    data_[mozc::commands::KeyEvent::RIGHT_CTRL] =
        mozc::commands::KeyEvent::CTRL;
    data_[mozc::commands::KeyEvent::LEFT_SHIFT] =
        mozc::commands::KeyEvent::SHIFT;
    data_[mozc::commands::KeyEvent::RIGHT_SHIFT] =
        mozc::commands::KeyEvent::SHIFT;
  }
  const std::map<uint32, mozc::commands::KeyEvent::ModifierKey> &data() {
    return data_;
  }

 private:
  std::map<uint32, mozc::commands::KeyEvent::ModifierKey> data_;
};

// TODO(hsumita): Moves this function into member functions of
// KeyEventHandler.
void AddAdditionalModifiers(
    std::set<mozc::commands::KeyEvent::ModifierKey> *modifier_keys_set) {
  DCHECK(modifier_keys_set);

  const std::map<uint32, mozc::commands::KeyEvent::ModifierKey> &data =
      mozc::Singleton<AdditionalModifiersData>::get()->data();

  // Adds MODIFIER if there are (LEFT|RIGHT)_MODIFIER like LEFT_SHIFT.
  for (std::set<mozc::commands::KeyEvent::ModifierKey>::const_iterator it =
           modifier_keys_set->begin();
       it != modifier_keys_set->end(); ++it) {
    std::map<uint32, mozc::commands::KeyEvent::ModifierKey>::const_iterator
        item = data.find(*it);
    if (item != data.end()) {
      modifier_keys_set->insert(item->second);
    }
  }
}

bool IsModifierToBeSentOnKeyUp(const mozc::commands::KeyEvent &key_event) {
  if (key_event.modifier_keys_size() == 0) {
    return false;
  }

  if (key_event.modifier_keys_size() == 1 &&
      key_event.modifier_keys(0) == mozc::commands::KeyEvent::CAPS) {
    return false;
  }

  return true;
}
}  // namespace

KeyEventHandler::KeyEventHandler() : key_translator_(new KeyTranslator) {
  Clear();
}

bool KeyEventHandler::GetKeyEvent(
    KeySym keyval, uint32 keycode, KeyStates modifiers,
    mozc::config::Config::PreeditMethod preedit_method, bool layout_is_jp,
    bool is_key_up, mozc::commands::KeyEvent *key) {
  DCHECK(key);
  key->Clear();

  if (!key_translator_->Translate(keyval, keycode, modifiers, preedit_method,
                                  layout_is_jp, key)) {
    LOG(ERROR) << "Translate failed";
    return false;
  }

  return ProcessModifiers(is_key_up, keyval, key);
}

void KeyEventHandler::Clear() {
  is_non_modifier_key_pressed_ = false;
  currently_pressed_modifiers_.clear();
  modifiers_to_be_sent_.clear();
}

bool KeyEventHandler::ProcessModifiers(bool is_key_up, uint32 keyval,
                                       mozc::commands::KeyEvent *key_event) {
  // Manage modifier key event.
  // Modifier key event is sent on key up if non-modifier key has not been
  // pressed since key down of modifier keys and no modifier keys are pressed
  // anymore.
  // Following examples are expected behaviors.
  //
  // E.g.) Shift key is special. If Shift + printable key is pressed, key event
  //       does NOT have shift modifiers. It is handled by KeyTranslator class.
  //    <Event from ibus> <Event to server>
  //     Shift down      | None
  //     "a" down        | A
  //     "a" up          | None
  //     Shift up        | None
  //
  // E.g.) Usual key is sent on key down.  Modifier keys are not sent if usual
  //       key is sent.
  //    <Event from ibus> <Event to server>
  //     Ctrl down       | None
  //     "a" down        | Ctrl+a
  //     "a" up          | None
  //     Ctrl up         | None
  //
  // E.g.) Modifier key is sent on key up.
  //    <Event from ibus> <Event to server>
  //     Shift down      | None
  //     Shift up        | Shift
  //
  // E.g.) Multiple modifier keys are sent on the last key up.
  //    <Event from ibus> <Event to server>
  //     Shift down      | None
  //     Control down    | None
  //     Shift up        | None
  //     Control up      | Control+Shift
  //
  // Essentialy we cannot handle modifier key evnet perfectly because
  // - We cannot get current keyboard status with ibus. If some modifiers
  //   are pressed or released without focusing the target window, we
  //   cannot handle it.
  // E.g.)
  //    <Event from ibus> <Event to server>
  //     Ctrl down       | None
  //     (focuses out, Ctrl up, focuses in)
  //     Shift down      | None
  //     Shift up        | None (But we should send Shift key)
  // To avoid a inconsistent state as much as possible, we clear states
  // when key event without modifier keys is sent.

  const bool is_modifier_only =
      !(key_event->has_key_code() || key_event->has_special_key());

  // We may get only up/down key event when a user moves a focus.
  // This code handles such situation as much as possible.
  // This code has a bug. If we send Shift + 'a', KeyTranslator removes a shift
  // modifier and converts 'a' to 'A'. This codes does NOT consider these
  // situation since we don't have enough data to handle it.
  // TODO(hsumita): Moves the logic about a handling of Shift or Caps keys from
  // KeyTranslator to MozcEngine.
  if (key_event->modifier_keys_size() == 0) {
    Clear();
  }

  if (!currently_pressed_modifiers_.empty() && !is_modifier_only) {
    is_non_modifier_key_pressed_ = true;
  }
  if (is_non_modifier_key_pressed_) {
    modifiers_to_be_sent_.clear();
  }

  if (is_key_up) {
    currently_pressed_modifiers_.erase(keyval);
    if (!is_modifier_only) {
      return false;
    }
    if (!currently_pressed_modifiers_.empty() ||
        modifiers_to_be_sent_.empty()) {
      is_non_modifier_key_pressed_ = false;
      return false;
    }
    if (is_non_modifier_key_pressed_) {
      return false;
    }
    DCHECK(!is_non_modifier_key_pressed_);

    // Modifier key event fires
    key_event->mutable_modifier_keys()->Clear();
    for (std::set<mozc::commands::KeyEvent::ModifierKey>::const_iterator it =
             modifiers_to_be_sent_.begin();
         it != modifiers_to_be_sent_.end(); ++it) {
      key_event->add_modifier_keys(*it);
    }
    modifiers_to_be_sent_.clear();
  } else if (is_modifier_only) {
    // TODO(hsumita): Supports a key sequence below.
    // - Ctrl down
    // - a down
    // - Alt down
    // We should add Alt key to |currently_pressed_modifiers|, but current
    // implementation does NOT do it.
    if (currently_pressed_modifiers_.empty() ||
        !modifiers_to_be_sent_.empty()) {
      for (size_t i = 0; i < key_event->modifier_keys_size(); ++i) {
        modifiers_to_be_sent_.insert(key_event->modifier_keys(i));
      }
      AddAdditionalModifiers(&modifiers_to_be_sent_);
    }
    currently_pressed_modifiers_.insert(keyval);
    return false;
  }

  // Clear modifier data just in case if |key| has no modifier keys.
  if (!IsModifierToBeSentOnKeyUp(*key_event)) {
    Clear();
  }

  return true;
}

}  // namespace fcitx
