/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef TextInputHandler_h_
#define TextInputHandler_h_

#include "nsCocoaUtils.h"

#import <Carbon/Carbon.h>
#import <Cocoa/Cocoa.h>
#include "mozView.h"
#include "nsString.h"
#include "nsCOMPtr.h"
#include "nsITimer.h"
#include "nsTArray.h"
#include "mozilla/EventForwards.h"
#include "mozilla/TextEventDispatcherListener.h"
#include "WritingModes.h"

class nsChildView;

namespace mozilla {
namespace widget {

// Key code constants
enum
{
#if !defined(MAC_OS_X_VERSION_10_12) || \
  MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
  kVK_RightCommand    = 0x36, // right command key
#endif

  kVK_PC_PrintScreen     = kVK_F13,
  kVK_PC_ScrollLock      = kVK_F14,
  kVK_PC_Pause           = kVK_F15,

  kVK_PC_Insert          = kVK_Help,
  kVK_PC_Backspace       = kVK_Delete,
  kVK_PC_Delete          = kVK_ForwardDelete,

  kVK_PC_ContextMenu     = 0x6E,

  kVK_Powerbook_KeypadEnter = 0x34  // Enter on Powerbook's keyboard is different
};

/**
 * TISInputSourceWrapper is a wrapper for the TISInputSourceRef.  If we get the
 * TISInputSourceRef from InputSourceID, we need to release the CFArray instance
 * which is returned by TISCreateInputSourceList.  However, when we release the
 * list, we cannot access the TISInputSourceRef.  So, it's not usable, and it
 * may cause the memory leak bugs.  nsTISInputSource automatically releases the
 * list when the instance is destroyed.
 */
class TISInputSourceWrapper
{
public:
  static TISInputSourceWrapper& CurrentInputSource();
  /**
   * Shutdown() should be called when nobody doesn't need to use this class.
   */
  static void Shutdown();

  TISInputSourceWrapper()
    : mInputSource{nullptr}
    , mKeyboardLayout{nullptr}
    , mUCKeyboardLayout{nullptr}
    , mIsRTL{0}
    , mOverrideKeyboard{false}
  {
    mInputSourceList = nullptr;
    Clear();
  }

  explicit TISInputSourceWrapper(const char* aID)
    : mInputSource{nullptr}
    , mKeyboardLayout{nullptr}
    , mUCKeyboardLayout{nullptr}
    , mIsRTL{0}
    , mOverrideKeyboard{false}
  {
    mInputSourceList = nullptr;
    InitByInputSourceID(aID);
  }

  explicit TISInputSourceWrapper(SInt32 aLayoutID)
    : mInputSource{nullptr}
    , mKeyboardLayout{nullptr}
    , mUCKeyboardLayout{nullptr}
    , mIsRTL{0}
    , mOverrideKeyboard{false}
  {
    mInputSourceList = nullptr;
    InitByLayoutID(aLayoutID);
  }

  explicit TISInputSourceWrapper(TISInputSourceRef aInputSource)
    : mInputSource{nullptr}
    , mKeyboardLayout{nullptr}
    , mUCKeyboardLayout{nullptr}
    , mIsRTL{0}
    , mOverrideKeyboard{false}
  {
    mInputSourceList = nullptr;
    InitByTISInputSourceRef(aInputSource);
  }

  ~TISInputSourceWrapper() { Clear(); }

  void InitByInputSourceID(const char* aID);
  void InitByInputSourceID(const nsString& aID);
  void InitByInputSourceID(const CFStringRef aID);
  /**
   * InitByLayoutID() initializes the keyboard layout by the layout ID.
   *
   * @param aLayoutID             An ID of keyboard layout.
   *                                0: US
   *                                1: Greek
   *                                2: German
   *                                3: Swedish-Pro
   *                                4: Dvorak-Qwerty Cmd
   *                                5: Thai
   *                                6: Arabic
   *                                7: French
   *                                8: Hebrew
   *                                9: Lithuanian
   *                               10: Norwegian
   *                               11: Spanish
   * @param aOverrideKeyboard     When testing set to TRUE, otherwise, set to
   *                              FALSE.  When TRUE, we use an ANSI keyboard
   *                              instead of the actual keyboard.
   */
  void InitByLayoutID(SInt32 aLayoutID, bool aOverrideKeyboard = false);
  void InitByCurrentInputSource();
  void InitByCurrentKeyboardLayout();
  void InitByCurrentASCIICapableInputSource();
  void InitByCurrentASCIICapableKeyboardLayout();
  void InitByCurrentInputMethodKeyboardLayoutOverride();
  void InitByTISInputSourceRef(TISInputSourceRef aInputSource);
  void InitByLanguage(CFStringRef aLanguage);

  /**
   * If the instance is initialized with a keyboard layout input source,
   * returns it.
   * If the instance is initialized with an IME mode input source, the result
   * references the keyboard layout for the IME mode.  However, this can be
   * initialized only when the IME mode is actually selected.  I.e, if IME mode
   * input source is initialized with LayoutID or SourceID, this returns null.
   */
  TISInputSourceRef GetKeyboardLayoutInputSource() const
  {
    return mKeyboardLayout;
  }
  const UCKeyboardLayout* GetUCKeyboardLayout();

  bool IsOpenedIMEMode();
  bool IsIMEMode();
  bool IsKeyboardLayout();

  bool IsASCIICapable()
  {
    NS_ENSURE_TRUE(mInputSource, false);
    return GetBoolProperty(kTISPropertyInputSourceIsASCIICapable);
  }

  bool IsEnabled()
  {
    NS_ENSURE_TRUE(mInputSource, false);
    return GetBoolProperty(kTISPropertyInputSourceIsEnabled);
  }

  bool GetLanguageList(CFArrayRef &aLanguageList);
  bool GetPrimaryLanguage(CFStringRef &aPrimaryLanguage);
  bool GetPrimaryLanguage(nsAString &aPrimaryLanguage);

  bool GetLocalizedName(CFStringRef &aName)
  {
    NS_ENSURE_TRUE(mInputSource, false);
    return GetStringProperty(kTISPropertyLocalizedName, aName);
  }

  bool GetLocalizedName(nsAString &aName)
  {
    NS_ENSURE_TRUE(mInputSource, false);
    return GetStringProperty(kTISPropertyLocalizedName, aName);
  }

  bool GetInputSourceID(CFStringRef &aID)
  {
    NS_ENSURE_TRUE(mInputSource, false);
    return GetStringProperty(kTISPropertyInputSourceID, aID);
  }

  bool GetInputSourceID(nsAString &aID)
  {
    NS_ENSURE_TRUE(mInputSource, false);
    return GetStringProperty(kTISPropertyInputSourceID, aID);
  }

  bool GetBundleID(CFStringRef &aBundleID)
  {
    NS_ENSURE_TRUE(mInputSource, false);
    return GetStringProperty(kTISPropertyBundleID, aBundleID);
  }

  bool GetBundleID(nsAString &aBundleID)
  {
    NS_ENSURE_TRUE(mInputSource, false);
    return GetStringProperty(kTISPropertyBundleID, aBundleID);
  }

  bool GetInputSourceType(CFStringRef &aType)
  {
    NS_ENSURE_TRUE(mInputSource, false);
    return GetStringProperty(kTISPropertyInputSourceType, aType);
  }

  bool GetInputSourceType(nsAString &aType)
  {
    NS_ENSURE_TRUE(mInputSource, false);
    return GetStringProperty(kTISPropertyInputSourceType, aType);
  }

  bool IsForRTLLanguage();
  bool IsInitializedByCurrentInputSource();

  enum {
    // 40 is an actual result of the ::LMGetKbdType() when we connect an
    // unknown keyboard and set the keyboard type to ANSI manually on the
    // set up dialog.
    eKbdType_ANSI = 40
  };

  void Select();
  void Clear();

  /**
   * InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent.
   *
   * @param aNativeKeyEvent       A native key event for which you want to
   *                              dispatch a Gecko key event.
   * @param aKeyEvent             The result -- a Gecko key event initialized
   *                              from the native key event.
   * @param aInsertString         If caller expects that the event will cause
   *                              a character to be input (say in an editor),
   *                              the caller should set this.  Otherwise,
   *                              if caller sets null to this, this method will
   *                              compute the character to be input from
   *                              characters of aNativeKeyEvent.
   */
  void InitKeyEvent(NSEvent *aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
                    const nsAString *aInsertString = nullptr);

  /**
   * WillDispatchKeyboardEvent() computes aKeyEvent.mAlternativeCharCodes and
   * recompute aKeyEvent.mCharCode if it's necessary.
   *
   * @param aNativeKeyEvent       A native key event for which you want to
   *                              dispatch a Gecko key event.
   * @param aInsertString         If caller expects that the event will cause
   *                              a character to be input (say in an editor),
   *                              the caller should set this.  Otherwise,
   *                              if caller sets null to this, this method will
   *                              compute the character to be input from
   *                              characters of aNativeKeyEvent.
   * @param aKeyEvent             The result -- a Gecko key event initialized
   *                              from the native key event.  This must be
   *                              eKeyPress event.
   */
  void WillDispatchKeyboardEvent(NSEvent* aNativeKeyEvent,
                                 const nsAString* aInsertString,
                                 WidgetKeyboardEvent& aKeyEvent);

  /**
   * ComputeGeckoKeyCode() returns Gecko keycode for aNativeKeyCode on current
   * keyboard layout.
   *
   * @param aNativeKeyCode        A native keycode.
   * @param aKbType               A native Keyboard Type value.  Typically,
   *                              this is a result of ::LMGetKbdType().
   * @param aCmdIsPressed         TRUE if Cmd key is pressed.  Otherwise, FALSE.
   * @return                      The computed Gecko keycode.
   */
  uint32_t ComputeGeckoKeyCode(UInt32 aNativeKeyCode, UInt32 aKbType,
                               bool aCmdIsPressed);

  /**
   * ComputeGeckoKeyNameIndex() returns Gecko key name index for the key.
   *
   * @param aNativeKeyCode        A native keycode.
   */
  static KeyNameIndex ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode);

  /**
   * ComputeGeckoCodeNameIndex() returns Gecko code name index for the key.
   *
   * @param aNativeKeyCode        A native keycode.
   * @param aKbType               A native Keyboard Type value.  Typically,
   *                              this is a result of ::LMGetKbdType().
   */
  static CodeNameIndex ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode,
                                                 UInt32 aKbType);

protected:
  /**
   * TranslateToString() computes the inputted text from the native keyCode,
   * modifier flags and keyboard type.
   *
   * @param aKeyCode              A native keyCode.
   * @param aModifiers            Combination of native modifier flags.
   * @param aKbType               A native Keyboard Type value.  Typically,
   *                              this is a result of ::LMGetKbdType().
   * @param aStr                  Result, i.e., inputted text.
   *                              The result can be two or more characters.
   * @return                      If succeeded, TRUE.  Otherwise, FALSE.
   *                              Even if TRUE, aStr can be empty string.
   */
  bool TranslateToString(UInt32 aKeyCode, UInt32 aModifiers,
                           UInt32 aKbType, nsAString &aStr);

  /**
   * TranslateToChar() computes the inputted character from the native keyCode,
   * modifier flags and keyboard type.  If two or more characters would be
   * input, this returns 0.
   *
   * @param aKeyCode              A native keyCode.
   * @param aModifiers            Combination of native modifier flags.
   * @param aKbType               A native Keyboard Type value.  Typically,
   *                              this is a result of ::LMGetKbdType().
   * @return                      If succeeded and the result is one character,
   *                              returns the charCode of it.  Otherwise,
   *                              returns 0.
   */
  uint32_t TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType);

  /**
   * TranslateToChar() checks if aKeyCode with aModifiers is a dead key.
   *
   * @param aKeyCode              A native keyCode.
   * @param aModifiers            Combination of native modifier flags.
   * @param aKbType               A native Keyboard Type value.  Typically,
   *                              this is a result of ::LMGetKbdType().
   * @return                      Returns true if the key with specified
   *                              modifier state isa dead key.  Otherwise,
   *                              false.
   */
  bool IsDeadKey(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType);

  /**
   * ComputeInsertString() computes string to be inserted with the key event.
   *
   * @param aNativeKeyEvent     The native key event which causes our keyboard
   *                            event(s).
   * @param aKeyEvent           A Gecko key event which was partially
   *                            initialized with aNativeKeyEvent.
   * @param aInsertString       The string to be inputting by aNativeKeyEvent.
   *                            This should be specified by InsertText().
   *                            In other words, if the key event doesn't cause
   *                            a call of InsertText(), this can be nullptr.
   * @param aResult             The string which should be set to charCode of
   *                            keypress event(s).
   */
  void ComputeInsertStringForCharCode(NSEvent* aNativeKeyEvent,
                                      const WidgetKeyboardEvent& aKeyEvent,
                                      const nsAString* aInsertString,
                                      nsAString& aResult);

  /**
   * IsPrintableKeyEvent() returns true if aNativeKeyEvent is caused by
   * a printable key.  Otherwise, returns false.
   */
  bool IsPrintableKeyEvent(NSEvent* aNativeKeyEvent) const;

  /**
   * GetKbdType() returns physical keyboard type.
   */
  UInt32 GetKbdType() const;

  bool GetBoolProperty(const CFStringRef aKey);
  bool GetStringProperty(const CFStringRef aKey, CFStringRef &aStr);
  bool GetStringProperty(const CFStringRef aKey, nsAString &aStr);

  TISInputSourceRef mInputSource;
  TISInputSourceRef mKeyboardLayout;
  CFArrayRef mInputSourceList;
  const UCKeyboardLayout* mUCKeyboardLayout;
  int8_t mIsRTL;

  bool mOverrideKeyboard;

  static TISInputSourceWrapper* sCurrentInputSource;
};

/**
 * TextInputHandlerBase is a base class of IMEInputHandler and TextInputHandler.
 * Utility methods should be implemented this level.
 */

class TextInputHandlerBase : public TextEventDispatcherListener
{
public:
  /**
   * Other TextEventDispatcherListener methods should be implemented in
   * IMEInputHandler.
   */
  NS_DECL_ISUPPORTS

  /**
   * DispatchEvent() dispatches aEvent on mWidget.
   *
   * @param aEvent                An event which you want to dispatch.
   * @return                      TRUE if the event is consumed by web contents
   *                              or chrome contents.  Otherwise, FALSE.
   */
  bool DispatchEvent(WidgetGUIEvent& aEvent);

  /**
   * SetSelection() dispatches eSetSelection event for the aRange.
   *
   * @param aRange                The range which will be selected.
   * @return                      TRUE if setting selection is succeeded and
   *                              the widget hasn't been destroyed.
   *                              Otherwise, FALSE.
   */
  bool SetSelection(NSRange& aRange);

  /**
   * InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent.
   *
   * @param aNativeKeyEvent       A native key event for which you want to
   *                              dispatch a Gecko key event.
   * @param aKeyEvent             The result -- a Gecko key event initialized
   *                              from the native key event.
   * @param aInsertString         If caller expects that the event will cause
   *                              a character to be input (say in an editor),
   *                              the caller should set this.  Otherwise,
   *                              if caller sets null to this, this method will
   *                              compute the character to be input from
   *                              characters of aNativeKeyEvent.
   */
  void InitKeyEvent(NSEvent *aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
                    const nsAString *aInsertString = nullptr);

  /**
   * SynthesizeNativeKeyEvent() is an implementation of
   * nsIWidget::SynthesizeNativeKeyEvent().  See the document in nsIWidget.h
   * for the detail.
   */
  nsresult SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
                                    int32_t aNativeKeyCode,
                                    uint32_t aModifierFlags,
                                    const nsAString& aCharacters,
                                    const nsAString& aUnmodifiedCharacters);

  /**
   * Utility method intended for testing. Attempts to construct a native key
   * event that would have been generated during an actual key press. This
   * *does not dispatch* the native event. Instead, it is attached to the
   * |mNativeKeyEvent| field of the Gecko event that is passed in.
   * @param aKeyEvent  Gecko key event to attach the native event to
   */
  NS_IMETHOD AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent);

  /**
   * GetWindowLevel() returns the window level of current focused (in Gecko)
   * window.  E.g., if an <input> element in XUL panel has focus, this returns
   * the XUL panel's window level.
   */
  NSInteger GetWindowLevel();

  /**
   * IsSpecialGeckoKey() checks whether aNativeKeyCode is mapped to a special
   * Gecko keyCode.  A key is "special" if it isn't used for text input.
   *
   * @param aNativeKeyCode        A native keycode.
   * @return                      If the keycode is mapped to a special key,
   *                              TRUE.  Otherwise, FALSE.
   */
  static bool IsSpecialGeckoKey(UInt32 aNativeKeyCode);


  /**
   * EnableSecureEventInput() and DisableSecureEventInput() wrap the Carbon
   * Event Manager APIs with the same names.  In addition they keep track of
   * how many times we've called them (in the same process) -- unlike the
   * Carbon Event Manager APIs, which only keep track of how many times they've
   * been called from any and all processes.
   *
   * The Carbon Event Manager's IsSecureEventInputEnabled() returns whether
   * secure event input mode is enabled (in any process).  This class's
   * IsSecureEventInputEnabled() returns whether we've made any calls to
   * EnableSecureEventInput() that are not (yet) offset by the calls we've
   * made to DisableSecureEventInput().
   */
  static void EnableSecureEventInput();
  static void DisableSecureEventInput();
  static bool IsSecureEventInputEnabled();

  /**
   * EnsureSecureEventInputDisabled() calls DisableSecureEventInput() until
   * our call count becomes 0.
   */
  static void EnsureSecureEventInputDisabled();

public:
   /**
   * mWidget must not be destroyed without OnDestroyWidget being called.
   *
   * @param aDestroyingWidget     Destroying widget.  This might not be mWidget.
   * @return                      This result doesn't have any meaning for
   *                              callers.  When aDstroyingWidget isn't the same
   *                              as mWidget, FALSE.  Then, inherited methods in
   *                              sub classes should return from this method
   *                              without cleaning up.
   */
  virtual bool OnDestroyWidget(nsChildView* aDestroyingWidget);

protected:
  // The creator of this instance, client and its text event dispatcher.
  // These members must not be nullptr after initialized until
  // OnDestroyWidget() is called.
  nsChildView* mWidget; // [WEAK]
  RefPtr<TextEventDispatcher> mDispatcher;

  // The native view for mWidget.
  // This view handles the actual text inputting.
  NSView<mozView>* mView; // [STRONG]

  TextInputHandlerBase(nsChildView* aWidget, NSView<mozView> *aNativeView);
  virtual ~TextInputHandlerBase();

  bool Destroyed() { return !mWidget; }

  /**
   * mCurrentKeyEvent indicates what key event we are handling.  While
   * handling a native keydown event, we need to store the event for insertText,
   * doCommandBySelector and various action message handlers of NSResponder
   * such as [NSResponder insertNewline:sender].
   */
  struct KeyEventState
  {
    // Handling native key event
    NSEvent* mKeyEvent;
    // String specified by InsertText().  This is not null only during a
    // call of InsertText().
    nsAString* mInsertString;
    // String which are included in [mKeyEvent characters] and already handled
    // by InsertText() call(s).
    nsString mInsertedString;
    // Unique id associated with a keydown / keypress event. It's ok if this
    // wraps over long periods.
    uint32_t mUniqueId;
    // Whether keydown event was consumed by web contents or chrome contents.
    bool mKeyDownHandled;
    // Whether keypress event was dispatched for mKeyEvent.
    bool mKeyPressDispatched;
    // Whether keypress event was consumed by web contents or chrome contents.
    bool mKeyPressHandled;
    // Whether the key event causes other key events via IME or something.
    bool mCausedOtherKeyEvents;
    // Whether the key event causes composition change or committing
    // composition.  So, even if InsertText() is called, this may be false
    // if it dispatches keypress event.
    bool mCompositionDispatched;

    KeyEventState()
      : mKeyEvent(nullptr)
      , mUniqueId(0)
    {
      Clear();
    }

    explicit KeyEventState(NSEvent* aNativeKeyEvent, uint32_t aUniqueId = 0)
      : mKeyEvent(nullptr)
      , mUniqueId(0)
    {
      Clear();
      Set(aNativeKeyEvent, aUniqueId);
    }

    KeyEventState(const KeyEventState &aOther) = delete;

    ~KeyEventState()
    {
      Clear();
    }

    void Set(NSEvent* aNativeKeyEvent, uint32_t aUniqueId = 0)
    {
      NS_PRECONDITION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
      Clear();
      mKeyEvent = [aNativeKeyEvent retain];
      mUniqueId = aUniqueId;
    }

    void Clear()
    {
      if (mKeyEvent) {
        [mKeyEvent release];
        mKeyEvent = nullptr;
        mUniqueId = 0;
      }
      mInsertString = nullptr;
      mInsertedString.Truncate();
      mKeyDownHandled = false;
      mKeyPressDispatched = false;
      mKeyPressHandled = false;
      mCausedOtherKeyEvents = false;
      mCompositionDispatched = false;
    }

    bool IsDefaultPrevented() const
    {
      return mKeyDownHandled || mKeyPressHandled || mCausedOtherKeyEvents ||
             mCompositionDispatched;
    }

    bool CanDispatchKeyPressEvent() const
    {
      return !mKeyPressDispatched && !IsDefaultPrevented();
    }

    bool CanHandleCommand() const
    {
      return !mKeyDownHandled && !mKeyPressHandled;
    }

    bool IsEnterKeyEvent() const
    {
      if (NS_WARN_IF(!mKeyEvent)) {
        return false;
      }
      KeyNameIndex keyNameIndex =
        TISInputSourceWrapper::ComputeGeckoKeyNameIndex([mKeyEvent keyCode]);
      return keyNameIndex == KEY_NAME_INDEX_Enter;
    }

    void InitKeyEvent(TextInputHandlerBase* aHandler,
                      WidgetKeyboardEvent& aKeyEvent);

    /**
     * GetUnhandledString() returns characters of the event which have not been
     * handled with InsertText() yet. For example, if there is a composition
     * caused by a dead key press like '`' and it's committed by some key
     * combinations like |Cmd+v|, then, the |v|'s KeyDown event's |characters|
     * is |`v|.  Then, after |`| is committed with a call of InsertString(),
     * this returns only 'v'.
     */
    void GetUnhandledString(nsAString& aUnhandledString) const;
  };

  /**
   * Helper classes for guaranteeing cleaning mCurrentKeyEvent
   */
  class AutoKeyEventStateCleaner
  {
  public:
    explicit AutoKeyEventStateCleaner(TextInputHandlerBase* aHandler) :
      mHandler(aHandler)
    {
    }

    ~AutoKeyEventStateCleaner()
    {
      mHandler->RemoveCurrentKeyEvent();
    }
  private:
    RefPtr<TextInputHandlerBase> mHandler;
  };

  class MOZ_STACK_CLASS AutoInsertStringClearer
  {
  public:
    explicit AutoInsertStringClearer(KeyEventState* aState)
      : mState(aState)
    {
    }
    ~AutoInsertStringClearer();

  private:
    KeyEventState* mState;
  };

  /**
   * mCurrentKeyEvents stores all key events which are being processed.
   * When we call interpretKeyEvents, IME may generate other key events.
   * mCurrentKeyEvents[0] is the latest key event.
   */
  nsTArray<KeyEventState*> mCurrentKeyEvents;

  /**
   * mFirstKeyEvent must be used for first key event.  This member prevents
   * memory fragmentation for most key events.
   */
  KeyEventState mFirstKeyEvent;

  /**
   * PushKeyEvent() adds the current key event to mCurrentKeyEvents.
   */
  KeyEventState* PushKeyEvent(NSEvent* aNativeKeyEvent, uint32_t aUniqueId = 0)
  {
    uint32_t nestCount = mCurrentKeyEvents.Length();
    for (uint32_t i = 0; i < nestCount; i++) {
      // When the key event is caused by another key event, all key events
      // which are being handled should be marked as "consumed".
      mCurrentKeyEvents[i]->mCausedOtherKeyEvents = true;
    }

    KeyEventState* keyEvent = nullptr;
    if (nestCount == 0) {
      mFirstKeyEvent.Set(aNativeKeyEvent, aUniqueId);
      keyEvent = &mFirstKeyEvent;
    } else {
      keyEvent = new KeyEventState(aNativeKeyEvent, aUniqueId);
    }
    return *mCurrentKeyEvents.AppendElement(keyEvent);
  }

  /**
   * RemoveCurrentKeyEvent() removes the current key event from
   * mCurrentKeyEvents.
   */
  void RemoveCurrentKeyEvent()
  {
    NS_ASSERTION(mCurrentKeyEvents.Length() > 0,
                 "RemoveCurrentKeyEvent() is called unexpectedly");
    KeyEventState* keyEvent = GetCurrentKeyEvent();
    mCurrentKeyEvents.RemoveElementAt(mCurrentKeyEvents.Length() - 1);
    if (keyEvent == &mFirstKeyEvent) {
      keyEvent->Clear();
    } else {
      delete keyEvent;
    }
  }

  /**
   * GetCurrentKeyEvent() returns current processing key event.
   */
  KeyEventState* GetCurrentKeyEvent()
  {
    if (mCurrentKeyEvents.Length() == 0) {
      return nullptr;
    }
    return mCurrentKeyEvents[mCurrentKeyEvents.Length() - 1];
  }

  struct KeyboardLayoutOverride final
  {
    int32_t mKeyboardLayout;
    bool mOverrideEnabled;

    KeyboardLayoutOverride() :
      mKeyboardLayout(0), mOverrideEnabled(false)
    {
    }
  };

  const KeyboardLayoutOverride& KeyboardLayoutOverrideRef() const
  {
    return mKeyboardOverride;
  }

  /**
   * IsPrintableChar() checks whether the unicode character is
   * a non-printable ASCII character or not.  Note that this returns
   * TRUE even if aChar is a non-printable UNICODE character.
   *
   * @param aChar                 A unicode character.
   * @return                      TRUE if aChar is a printable ASCII character
   *                              or a unicode character.  Otherwise, i.e,
   *                              if aChar is a non-printable ASCII character,
   *                              FALSE.
   */
  static bool IsPrintableChar(char16_t aChar);

  /**
   * IsNormalCharInputtingEvent() checks whether aKeyEvent causes text input.
   *
   * @param aKeyEvent             A key event.
   * @return                      TRUE if the key event causes text input.
   *                              Otherwise, FALSE.
   */
  static bool IsNormalCharInputtingEvent(const WidgetKeyboardEvent& aKeyEvent);

  /**
   * IsModifierKey() checks whether the native keyCode is for a modifier key.
   *
   * @param aNativeKeyCode        A native keyCode.
   * @return                      TRUE if aNativeKeyCode is for a modifier key.
   *                              Otherwise, FALSE.
   */
  static bool IsModifierKey(UInt32 aNativeKeyCode);

private:
  KeyboardLayoutOverride mKeyboardOverride;

  static int32_t sSecureEventInputCount;
};

/**
 * IMEInputHandler manages:
 *   1. The IME/keyboard layout statement of nsChildView.
 *   2. The IME composition statement of nsChildView.
 * And also provides the methods which controls the current IME transaction of
 * the instance.
 *
 * Note that an nsChildView handles one or more NSView's events.  E.g., even if
 * a text editor on XUL panel element, the input events handled on the parent
 * (or its ancestor) widget handles it (the native focus is set to it).  The
 * actual focused view is notified by OnFocusChangeInGecko.
 */

class IMEInputHandler : public TextInputHandlerBase
{
public:
  // TextEventDispatcherListener methods
  NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
                       const IMENotification& aNotification) override;
  NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;
  NS_IMETHOD_(void) OnRemovedFrom(
                      TextEventDispatcher* aTextEventDispatcher) override;
  NS_IMETHOD_(void) WillDispatchKeyboardEvent(
                      TextEventDispatcher* aTextEventDispatcher,
                      WidgetKeyboardEvent& aKeyboardEvent,
                      uint32_t aIndexOfKeypress,
                      void* aData) override;

public:
  virtual bool OnDestroyWidget(nsChildView* aDestroyingWidget) override;

  virtual void OnFocusChangeInGecko(bool aFocus);

  void OnSelectionChange(const IMENotification& aIMENotification);
  void OnLayoutChange();

  /**
   * Call [NSTextInputContext handleEvent] for mouse event support of IME
   */
  bool OnHandleEvent(NSEvent* aEvent);

  /**
   * SetMarkedText() is a handler of setMarkedText of NSTextInput.
   *
   * @param aAttrString           This mut be an instance of NSAttributedString.
   *                              If the aString parameter to
   *                              [ChildView setMarkedText:setSelectedRange:]
   *                              isn't an instance of NSAttributedString,
   *                              create an NSAttributedString from it and pass
   *                              that instead.
   * @param aSelectedRange        Current selected range (or caret position).
   * @param aReplacementRange     The range which will be replaced with the
   *                              aAttrString instead of current marked range.
   */
  void SetMarkedText(NSAttributedString* aAttrString,
                     NSRange& aSelectedRange,
                     NSRange* aReplacementRange = nullptr);

  /**
   * GetAttributedSubstringFromRange() returns an NSAttributedString instance
   * which is allocated as autorelease for aRange.
   *
   * @param aRange                The range of string which you want.
   * @param aActualRange          The actual range of the result.
   * @return                      The string in aRange.  If the string is empty,
   *                              this returns nil.  If succeeded, this returns
   *                              an instance which is allocated as autorelease.
   *                              If this has some troubles, returns nil.
   */
  NSAttributedString* GetAttributedSubstringFromRange(
                        NSRange& aRange,
                        NSRange* aActualRange = nullptr);

  /**
   * SelectedRange() returns current selected range.
   *
   * @return                      If an editor has focus, this returns selection
   *                              range in the editor.  Otherwise, this returns
   *                              selection range  in the focused document.
   */
  NSRange SelectedRange();

  /**
   * DrawsVerticallyForCharacterAtIndex() returns whether the character at
   * the given index is being rendered vertically.
   *
   * @param aCharIndex            The character offset to query.
   *
   * @return                      True if writing-mode is vertical at the given
   *                              character offset; otherwise false.
   */
  bool DrawsVerticallyForCharacterAtIndex(uint32_t aCharIndex);

  /**
   * FirstRectForCharacterRange() returns first *character* rect in the range.
   * Cocoa needs the first line rect in the range, but we cannot compute it
   * on current implementation.
   *
   * @param aRange                A range of text to examine.  Its position is
   *                              an offset from the beginning of the focused
   *                              editor or document.
   * @param aActualRange          If this is not null, this returns the actual
   *                              range used for computing the result.
   * @return                      An NSRect containing the first character in
   *                              aRange, in screen coordinates.
   *                              If the length of aRange is 0, the width will
   *                              be 0.
   */
  NSRect FirstRectForCharacterRange(NSRange& aRange,
                                    NSRange* aActualRange = nullptr);

  /**
   * CharacterIndexForPoint() returns an offset of a character at aPoint.
   * XXX This isn't implemented, always returns 0.
   *
   * @param                       The point in screen coordinates.
   * @return                      The offset of the character at aPoint from
   *                              the beginning of the focused editor or
   *                              document.
   */
  NSUInteger CharacterIndexForPoint(NSPoint& aPoint);

  /**
   * GetValidAttributesForMarkedText() returns attributes which we support.
   *
   * @return                      Always empty array for now.
   */
  NSArray* GetValidAttributesForMarkedText();

  bool HasMarkedText();
  NSRange MarkedRange();

  bool IsIMEComposing() { return mIsIMEComposing; }
  bool IsIMEOpened();
  bool IsIMEEnabled() { return mIsIMEEnabled; }
  bool IsASCIICapableOnly() { return mIsASCIICapableOnly; }
  bool IgnoreIMECommit() { return mIgnoreIMECommit; }

  void CommitIMEComposition();
  void CancelIMEComposition();

  void EnableIME(bool aEnableIME);
  void SetIMEOpenState(bool aOpen);
  void SetASCIICapableOnly(bool aASCIICapableOnly);

  /**
   * True if OSX believes that our view has keyboard focus.
   */
  bool IsFocused();

  static CFArrayRef CreateAllIMEModeList();
  static void DebugPrintAllIMEModes();

  // Don't use ::TSMGetActiveDocument() API directly, the document may not
  // be what you want.
  static TSMDocumentID GetCurrentTSMDocumentID();

protected:
  // We cannot do some jobs in the given stack by some reasons.
  // Following flags and the timer provide the execution pending mechanism,
  // See the comment in nsCocoaTextInputHandler.mm.
  nsCOMPtr<nsITimer> mTimer;
  enum {
    kNotifyIMEOfFocusChangeInGecko = 1,
    kSyncASCIICapableOnly          = 2
  };
  uint32_t mPendingMethods;

  IMEInputHandler(nsChildView* aWidget, NSView<mozView> *aNativeView);
  virtual ~IMEInputHandler();

  void ResetTimer();

  virtual void ExecutePendingMethods();

  /**
   * InsertTextAsCommittingComposition() commits current composition.  If there
   * is no composition, this starts a composition and commits it immediately.
   *
   * @param aAttrString           A string which is committed.
   * @param aReplacementRange     The range which will be replaced with the
   *                              aAttrString instead of current selection.
   */
  void InsertTextAsCommittingComposition(NSAttributedString* aAttrString,
                                         NSRange* aReplacementRange);

private:
  // If mIsIMEComposing is true, the composition string is stored here.
  NSString* mIMECompositionString;
  // If mIsIMEComposing is true, the start offset of the composition string.
  uint32_t mIMECompositionStart;

  NSRange mMarkedRange;
  NSRange mSelectedRange;

  NSRange mRangeForWritingMode; // range within which mWritingMode applies
  mozilla::WritingMode mWritingMode;

  bool mIsIMEComposing;
  bool mIsIMEEnabled;
  bool mIsASCIICapableOnly;
  bool mIgnoreIMECommit;
  bool mIMEHasFocus;

  void KillIMEComposition();
  void SendCommittedText(NSString *aString);
  void OpenSystemPreferredLanguageIME();

  // Pending methods
  void NotifyIMEOfFocusChangeInGecko();
  void SyncASCIICapableOnly();

  static bool sStaticMembersInitialized;
  static CFStringRef sLatestIMEOpenedModeInputSourceID;
  static void InitStaticMembers();
  static void OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
                                             void* aObserver,
                                             CFStringRef aName,
                                             const void* aObject,
                                             CFDictionaryRef aUserInfo);

  static void FlushPendingMethods(nsITimer* aTimer, void* aClosure);

  /**
   * ConvertToTextRangeStyle converts the given native underline style to
   * our defined text range type.
   *
   * @param aUnderlineStyle       NSUnderlineStyleSingle or
   *                              NSUnderlineStyleThick.
   * @param aSelectedRange        Current selected range (or caret position).
   * @return                      NS_TEXTRANGE_*.
   */
  TextRangeType ConvertToTextRangeType(uint32_t aUnderlineStyle,
                                       NSRange& aSelectedRange);

  /**
   * GetRangeCount() computes the range count of aAttrString.
   *
   * @param aAttrString           An NSAttributedString instance whose number of
   *                              NSUnderlineStyleAttributeName ranges you with
   *                              to know.
   * @return                      The count of NSUnderlineStyleAttributeName
   *                              ranges in aAttrString.
   */
  uint32_t GetRangeCount(NSAttributedString *aString);

  /**
   * CreateTextRangeArray() returns text ranges for clauses and/or caret.
   *
   * @param aAttrString           An NSAttributedString instance which indicates
   *                              current composition string.
   * @param aSelectedRange        Current selected range (or caret position).
   * @return                      The result is set to the
   *                              NSUnderlineStyleAttributeName ranges in
   *                              aAttrString.
   */
  already_AddRefed<mozilla::TextRangeArray>
    CreateTextRangeArray(NSAttributedString *aAttrString,
                         NSRange& aSelectedRange);

  /**
   * DispatchCompositionStartEvent() dispatches a compositionstart event and
   * initializes the members indicating composition state.
   *
   * @return                      true if it can continues handling composition.
   *                              Otherwise, e.g., canceled by the web page,
   *                              this returns false.
   */
  bool DispatchCompositionStartEvent();

  /**
   * DispatchCompositionChangeEvent() dispatches a compositionchange event on
   * mWidget and modifies the members indicating composition state.
   *
   * @param aText                 User text input.
   * @param aAttrString           An NSAttributedString instance which indicates
   *                              current composition string.
   * @param aSelectedRange        Current selected range (or caret position).
   *
   * @return                      true if it can continues handling composition.
   *                              Otherwise, e.g., canceled by the web page,
   *                              this returns false.
   */
  bool DispatchCompositionChangeEvent(const nsString& aText,
                                      NSAttributedString* aAttrString,
                                      NSRange& aSelectedRange);

  /**
   * DispatchCompositionCommitEvent() dispatches a compositioncommit event or
   * compositioncommitasis event.  If aCommitString is null, dispatches
   * compositioncommitasis event.  I.e., if aCommitString is null, this
   * commits the composition with the last data.  Otherwise, commits the
   * composition with aCommitString value.
   *
   * @return                      true if the widget isn't destroyed.
   *                              Otherwise, false.
   */
  bool DispatchCompositionCommitEvent(const nsAString* aCommitString = nullptr);

  // The focused IME handler.  Please note that the handler might lost the
  // actual focus by deactivating the application.  If we are active, this
  // must have the actual focused handle.
  // We cannot access to the NSInputManager during we aren't active, so, the
  // focused handler can have an IME transaction even if we are deactive.
  static IMEInputHandler* sFocusedIMEHandler;

  static bool sCachedIsForRTLLangage;
};

/**
 * TextInputHandler implements the NSTextInput protocol.
 */
class TextInputHandler : public IMEInputHandler
{
public:
  static NSUInteger sLastModifierState;

  static CFArrayRef CreateAllKeyboardLayoutList();
  static void DebugPrintAllKeyboardLayouts();

  TextInputHandler(nsChildView* aWidget, NSView<mozView> *aNativeView);
  virtual ~TextInputHandler();

  /**
   * KeyDown event handler.
   *
   * @param aNativeEvent          A native NSKeyDown event.
   * @param aUniqueId             A unique ID for the event.
   * @return                      TRUE if the event is dispatched to web
   *                              contents or chrome contents. Otherwise, FALSE.
   */
  bool HandleKeyDownEvent(NSEvent* aNativeEvent, uint32_t aUniqueId);

  /**
   * KeyUp event handler.
   *
   * @param aNativeEvent          A native NSKeyUp event.
   */
  void HandleKeyUpEvent(NSEvent* aNativeEvent);

  /**
   * FlagsChanged event handler.
   *
   * @param aNativeEvent          A native NSFlagsChanged event.
   */
  void HandleFlagsChanged(NSEvent* aNativeEvent);

  /**
   * Insert the string to content.  I.e., this is a text input event handler.
   * If this is called during keydown event handling, this may dispatch a
   * eKeyPress event.  If this is called during composition, this commits
   * the composition by the aAttrString.
   *
   * @param aAttrString           An inserted string.
   * @param aReplacementRange     The range which will be replaced with the
   *                              aAttrString instead of current selection.
   */
  void InsertText(NSAttributedString *aAttrString,
                  NSRange* aReplacementRange = nullptr);

  /**
   * Handles "insertNewline:" command.  Due to bug 1350541, this always
   * dispatches a keypress event of Enter key unless there is composition.
   * If it's handling Enter key event, this dispatches "actual" Enter
   * keypress event.  Otherwise, dispatches "fake" Enter keypress event
   * whose code value is unidentified.
   */
  void InsertNewline();

  /**
   * doCommandBySelector event handler.
   *
   * @param aSelector             A selector of the command.
   * @return                      TRUE if the command is consumed.  Otherwise,
   *                              FALSE.
   */
  bool DoCommandBySelector(const char* aSelector);

  /**
   * KeyPressWasHandled() checks whether keypress event was handled or not.
   *
   * @return                      TRUE if keypress event for latest native key
   *                              event was handled.  Otherwise, FALSE.
   *                              If this handler isn't handling any key events,
   *                              always returns FALSE.
   */
  bool KeyPressWasHandled()
  {
    KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
    return currentKeyEvent && currentKeyEvent->mKeyPressHandled;
  }

protected:
  // Stores the association of device dependent modifier flags with a modifier
  // keyCode.  Being device dependent, this association may differ from one kind
  // of hardware to the next.
  struct ModifierKey
  {
    NSUInteger flags;
    unsigned short keyCode;

    ModifierKey(NSUInteger aFlags, unsigned short aKeyCode) :
      flags(aFlags), keyCode(aKeyCode)
    {
    }

    NSUInteger GetDeviceDependentFlags() const
    {
      return (flags & ~NSDeviceIndependentModifierFlagsMask);
    }

    NSUInteger GetDeviceIndependentFlags() const
    {
      return (flags & NSDeviceIndependentModifierFlagsMask);
    }
  };
  typedef nsTArray<ModifierKey> ModifierKeyArray;
  ModifierKeyArray mModifierKeys;

  /**
   * GetModifierKeyForNativeKeyCode() returns the stored ModifierKey for
   * the key.
   */
  const ModifierKey*
    GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const;

  /**
   * GetModifierKeyForDeviceDependentFlags() returns the stored ModifierKey for
   * the device dependent flags.
   */
  const ModifierKey*
    GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const;

  /**
   * DispatchKeyEventForFlagsChanged() dispatches keydown event or keyup event
   * for the aNativeEvent.
   *
   * @param aNativeEvent          A native flagschanged event which you want to
   *                              dispatch our key event for.
   * @param aDispatchKeyDown      TRUE if you want to dispatch a keydown event.
   *                              Otherwise, i.e., to dispatch keyup event,
   *                              FALSE.
   */
  void DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
                                       bool aDispatchKeyDown);
};

} // namespace widget
} // namespace mozilla

#endif // TextInputHandler_h_
