/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 mozilla_widget_IMEData_h_
#define mozilla_widget_IMEData_h_

#include "mozilla/CheckedInt.h"
#include "mozilla/EventForwards.h"
#include "mozilla/NativeKeyBindingsType.h"
#include "mozilla/ToString.h"

#include "nsCOMPtr.h"
#include "nsIURI.h"
#include "nsPoint.h"
#include "nsRect.h"
#include "nsString.h"
#include "nsXULAppAPI.h"
#include "Units.h"

class nsIWidget;

namespace mozilla {

class ContentSelection;
class WritingMode;

template <class T>
class Maybe;

// Helper class to logging string which may contain various Unicode characters
// and/or may be too long string for logging.
class MOZ_STACK_CLASS PrintStringDetail : public nsAutoCString {
 public:
  static constexpr uint32_t kMaxLengthForCompositionString = 8;
  static constexpr uint32_t kMaxLengthForSelectedString = 12;
  static constexpr uint32_t kMaxLengthForEditor = 20;

  PrintStringDetail() = delete;
  explicit PrintStringDetail(const nsAString& aString,
                             uint32_t aMaxLength = UINT32_MAX);
  template <typename StringType>
  explicit PrintStringDetail(const Maybe<StringType>& aMaybeString,
                             uint32_t aMaxLength = UINT32_MAX);

 private:
  static nsCString PrintCharData(char32_t aChar);
};

// StartAndEndOffsets represents a range in flat-text.
template <typename IntType>
class StartAndEndOffsets {
 protected:
  static IntType MaxOffset() { return std::numeric_limits<IntType>::max(); }

 public:
  StartAndEndOffsets() = delete;
  explicit StartAndEndOffsets(IntType aStartOffset, IntType aEndOffset)
      : mStartOffset(aStartOffset),
        mEndOffset(aStartOffset <= aEndOffset ? aEndOffset : aStartOffset) {
    MOZ_ASSERT(aStartOffset <= mEndOffset);
  }

  IntType StartOffset() const { return mStartOffset; }
  IntType Length() const { return mEndOffset - mStartOffset; }
  IntType EndOffset() const { return mEndOffset; }

  bool IsOffsetInRange(IntType aOffset) const {
    return aOffset >= mStartOffset && aOffset < mEndOffset;
  }
  bool IsOffsetInRangeOrEndOffset(IntType aOffset) const {
    return aOffset >= mStartOffset && aOffset <= mEndOffset;
  }

  void MoveTo(IntType aNewStartOffset) {
    auto delta = static_cast<int64_t>(mStartOffset) - aNewStartOffset;
    mStartOffset += delta;
    mEndOffset += delta;
  }
  void SetOffsetAndLength(IntType aNewOffset, IntType aNewLength) {
    mStartOffset = aNewOffset;
    CheckedInt<IntType> endOffset(aNewOffset + aNewLength);
    mEndOffset = endOffset.isValid() ? endOffset.value() : MaxOffset();
  }
  void SetEndOffset(IntType aEndOffset) {
    MOZ_ASSERT(mStartOffset <= aEndOffset);
    mEndOffset = std::max(aEndOffset, mStartOffset);
  }
  void SetStartAndEndOffsets(IntType aStartOffset, IntType aEndOffset) {
    MOZ_ASSERT(aStartOffset <= aEndOffset);
    mStartOffset = aStartOffset;
    mEndOffset = aStartOffset <= aEndOffset ? aEndOffset : aStartOffset;
  }
  void SetLength(IntType aNewLength) {
    CheckedInt<IntType> endOffset(mStartOffset + aNewLength);
    mEndOffset = endOffset.isValid() ? endOffset.value() : MaxOffset();
  }

  friend std::ostream& operator<<(
      std::ostream& aStream,
      const StartAndEndOffsets<IntType>& aStartAndEndOffsets) {
    aStream << "{ mStartOffset=" << aStartAndEndOffsets.mStartOffset
            << ", mEndOffset=" << aStartAndEndOffsets.mEndOffset
            << ", Length()=" << aStartAndEndOffsets.Length() << " }";
    return aStream;
  }

 private:
  IntType mStartOffset;
  IntType mEndOffset;
};

// OffsetAndData class is designed for storing composition string and its
// start offset.  Length() and EndOffset() return only valid length or
// offset.  I.e., if the string is too long for inserting at the offset,
// the length is shrunken.  However, the string itself is not shrunken.
// Therefore, moving it to where all of the string can be contained,
// they will return longer/bigger value.
enum class OffsetAndDataFor {
  CompositionString,
  SelectedString,
  EditorString,
};
template <typename IntType>
class OffsetAndData {
 protected:
  static IntType MaxOffset() { return std::numeric_limits<IntType>::max(); }

 public:
  OffsetAndData() = delete;
  explicit OffsetAndData(
      IntType aStartOffset, const nsAString& aData,
      OffsetAndDataFor aFor = OffsetAndDataFor::CompositionString)
      : mData(aData), mOffset(aStartOffset), mFor(aFor) {}

  bool IsValid() const {
    CheckedInt<IntType> offset(mOffset);
    offset += mData.Length();
    return offset.isValid();
  }
  IntType StartOffset() const { return mOffset; }
  IntType Length() const {
    CheckedInt<IntType> endOffset(CheckedInt<IntType>(mOffset) +
                                  mData.Length());
    return endOffset.isValid() ? mData.Length() : MaxOffset() - mOffset;
  }
  IntType EndOffset() const { return mOffset + Length(); }
  StartAndEndOffsets<IntType> CreateStartAndEndOffsets() const {
    return StartAndEndOffsets<IntType>(StartOffset(), EndOffset());
  }
  const nsString& DataRef() const {
    // In strictly speaking, we should return substring which may be shrunken
    // for rounding to the max offset.  However, it's unrealistic edge case,
    // and creating new string is not so cheap job in a hot path.  Therefore,
    // this just returns the data as-is.
    return mData;
  }
  bool IsDataEmpty() const { return mData.IsEmpty(); }

  bool IsOffsetInRange(IntType aOffset) const {
    return aOffset >= mOffset && aOffset < EndOffset();
  }
  bool IsOffsetInRangeOrEndOffset(IntType aOffset) const {
    return aOffset >= mOffset && aOffset <= EndOffset();
  }

  void Collapse(IntType aOffset) {
    mOffset = aOffset;
    mData.Truncate();
  }
  void MoveTo(IntType aNewOffset) { mOffset = aNewOffset; }
  void SetOffsetAndData(IntType aStartOffset, const nsAString& aData) {
    mOffset = aStartOffset;
    mData = aData;
  }
  void SetData(const nsAString& aData) { mData = aData; }
  void TruncateData(uint32_t aLength = 0) { mData.Truncate(aLength); }
  void ReplaceData(nsAString::size_type aCutStart,
                   nsAString::size_type aCutLength,
                   const nsAString& aNewString) {
    mData.Replace(aCutStart, aCutLength, aNewString);
  }

  friend std::ostream& operator<<(
      std::ostream& aStream, const OffsetAndData<IntType>& aOffsetAndData) {
    const auto maxDataLength =
        aOffsetAndData.mFor == OffsetAndDataFor::CompositionString
            ? PrintStringDetail::kMaxLengthForCompositionString
            : (aOffsetAndData.mFor == OffsetAndDataFor::SelectedString
                   ? PrintStringDetail::kMaxLengthForSelectedString
                   : PrintStringDetail::kMaxLengthForEditor);
    aStream << "{ mOffset=" << aOffsetAndData.mOffset << ", mData="
            << PrintStringDetail(aOffsetAndData.mData, maxDataLength).get()
            << ", Length()=" << aOffsetAndData.Length()
            << ", EndOffset()=" << aOffsetAndData.EndOffset() << " }";
    return aStream;
  }

 private:
  nsString mData;
  IntType mOffset;
  OffsetAndDataFor mFor;
};

namespace widget {

/**
 * Preference for receiving IME updates
 *
 * If mWantUpdates is not NOTIFY_NOTHING, nsTextStateManager will observe text
 * change and/or selection change and call nsIWidget::NotifyIME() with
 * NOTIFY_IME_OF_SELECTION_CHANGE and/or NOTIFY_IME_OF_TEXT_CHANGE.
 * Please note that the text change observing cost is very expensive especially
 * on an HTML editor has focus.
 * If the IME implementation on a particular platform doesn't care about
 * NOTIFY_IME_OF_SELECTION_CHANGE and/or NOTIFY_IME_OF_TEXT_CHANGE,
 * they should set mWantUpdates to NOTIFY_NOTHING to avoid the cost.
 * If the IME implementation needs notifications even while our process is
 * deactive, it should also set NOTIFY_DURING_DEACTIVE.
 */
struct IMENotificationRequests final {
  typedef uint8_t Notifications;

  enum : Notifications {
    NOTIFY_NOTHING = 0,
    NOTIFY_TEXT_CHANGE = 1 << 1,
    NOTIFY_POSITION_CHANGE = 1 << 2,
    // NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR is used when mouse button is pressed
    // or released on a character in the focused editor.  The notification is
    // notified to IME as a mouse event.  If it's consumed by IME, NotifyIME()
    // returns NS_SUCCESS_EVENT_CONSUMED.  Otherwise, it returns NS_OK if it's
    // handled without any error.
    NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR = 1 << 3,
    // NOTE: NOTIFY_DURING_DEACTIVE isn't supported in environments where two
    //       or more compositions are possible.  E.g., Mac and Linux (GTK).
    NOTIFY_DURING_DEACTIVE = 1 << 7,

    NOTIFY_ALL = NOTIFY_TEXT_CHANGE | NOTIFY_POSITION_CHANGE |
                 NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR,
  };

  IMENotificationRequests() : mWantUpdates(NOTIFY_NOTHING) {}

  explicit IMENotificationRequests(Notifications aWantUpdates)
      : mWantUpdates(aWantUpdates) {}

  IMENotificationRequests operator|(
      const IMENotificationRequests& aOther) const {
    return IMENotificationRequests(aOther.mWantUpdates | mWantUpdates);
  }
  IMENotificationRequests& operator|=(const IMENotificationRequests& aOther) {
    mWantUpdates |= aOther.mWantUpdates;
    return *this;
  }
  bool operator==(const IMENotificationRequests& aOther) const {
    return mWantUpdates == aOther.mWantUpdates;
  }

  bool WantTextChange() const { return !!(mWantUpdates & NOTIFY_TEXT_CHANGE); }

  bool WantPositionChanged() const {
    return !!(mWantUpdates & NOTIFY_POSITION_CHANGE);
  }

  bool WantChanges() const { return WantTextChange(); }

  bool WantMouseButtonEventOnChar() const {
    return !!(mWantUpdates & NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR);
  }

  bool WantDuringDeactive() const {
    return !!(mWantUpdates & NOTIFY_DURING_DEACTIVE);
  }

  Notifications mWantUpdates;
};

/**
 * IME enabled states.
 *
 * WARNING: If you change these values, you also need to edit:
 *   nsIDOMWindowUtils.idl
 */
enum class IMEEnabled {
  /**
   * 'Disabled' means the user cannot use IME. So, the IME open state should
   * be 'closed' during 'disabled'.
   */
  Disabled,
  /**
   * 'Enabled' means the user can use IME.
   */
  Enabled,
  /**
   * 'Password' state is a special case for the password editors.
   * E.g., on mac, the password editors should disable the non-Roman
   * keyboard layouts at getting focus. Thus, the password editor may have
   * special rules on some platforms.
   */
  Password,
  /**
   * 'Unknown' is useful when you cache this enum.  So, this shouldn't be
   * used with nsIWidget::SetInputContext().
   */
  Unknown,
};

/**
 * Contains IMEStatus plus information about the current
 * input context that the IME can use as hints if desired.
 */

struct IMEState final {
  IMEEnabled mEnabled;

  /**
   * IME open states the mOpen value of SetInputContext() should be one value of
   * OPEN, CLOSE or DONT_CHANGE_OPEN_STATE.  GetInputContext() should return
   * OPEN, CLOSE or OPEN_STATE_NOT_SUPPORTED.
   */
  enum Open {
    /**
     * 'Unsupported' means the platform cannot return actual IME open state.
     * This value is used only by GetInputContext().
     */
    OPEN_STATE_NOT_SUPPORTED,
    /**
     * 'Don't change' means the widget shouldn't change IME open state when
     * SetInputContext() is called.
     */
    DONT_CHANGE_OPEN_STATE = OPEN_STATE_NOT_SUPPORTED,
    /**
     * 'Open' means that IME should compose in its primary language (or latest
     * input mode except direct ASCII character input mode).  Even if IME is
     * opened by this value, users should be able to close IME by theirselves.
     * Web contents can specify this value by |ime-mode: active;|.
     */
    OPEN,
    /**
     * 'Closed' means that IME shouldn't handle key events (or should handle
     * as ASCII character inputs on mobile device).  Even if IME is closed by
     * this value, users should be able to open IME by theirselves.
     * Web contents can specify this value by |ime-mode: inactive;|.
     */
    CLOSED
  };
  Open mOpen;

  IMEState() : mEnabled(IMEEnabled::Enabled), mOpen(DONT_CHANGE_OPEN_STATE) {}

  explicit IMEState(IMEEnabled aEnabled, Open aOpen = DONT_CHANGE_OPEN_STATE)
      : mEnabled(aEnabled), mOpen(aOpen) {}

  // Returns true if the user can input characters.
  // This means that a plain text editor, an HTML editor, a password editor or
  // a plain text editor whose ime-mode is "disabled".
  bool IsEditable() const {
    return mEnabled == IMEEnabled::Enabled || mEnabled == IMEEnabled::Password;
  }
};

// NS_ONLY_ONE_NATIVE_IME_CONTEXT is a special value of native IME context.
// If there can be only one IME composition in a process, this can be used.
#define NS_ONLY_ONE_NATIVE_IME_CONTEXT \
  (reinterpret_cast<void*>(static_cast<intptr_t>(-1)))

struct NativeIMEContext final {
  // Pointer to native IME context.  Typically this is the result of
  // nsIWidget::GetNativeData(NS_RAW_NATIVE_IME_CONTEXT) in the parent process.
  // See also NS_ONLY_ONE_NATIVE_IME_CONTEXT.
  uintptr_t mRawNativeIMEContext;
  // Process ID of the origin of mNativeIMEContext.
  // static_cast<uint64_t>(-1) if the instance is not initialized properly.
  // 0 if the instance is originated in the parent process.
  // 1 or greater if the instance is originated in a content process.
  uint64_t mOriginProcessID;

  NativeIMEContext() : mRawNativeIMEContext(0), mOriginProcessID(0) {
    Init(nullptr);
  }

  explicit NativeIMEContext(nsIWidget* aWidget)
      : mRawNativeIMEContext(0), mOriginProcessID(0) {
    Init(aWidget);
  }

  bool IsValid() const {
    return mRawNativeIMEContext &&
           mOriginProcessID != static_cast<uint64_t>(-1);
  }

  bool IsOriginatedInParentProcess() const {
    return mOriginProcessID != 0 &&
           mOriginProcessID != static_cast<uint64_t>(-1);
  }

  void Init(nsIWidget* aWidget);
  void InitWithRawNativeIMEContext(const void* aRawNativeIMEContext) {
    InitWithRawNativeIMEContext(const_cast<void*>(aRawNativeIMEContext));
  }
  void InitWithRawNativeIMEContext(void* aRawNativeIMEContext);

  bool operator==(const NativeIMEContext& aOther) const {
    return mRawNativeIMEContext == aOther.mRawNativeIMEContext &&
           mOriginProcessID == aOther.mOriginProcessID;
  }
  bool operator!=(const NativeIMEContext& aOther) const {
    return !(*this == aOther);
  }
};

struct InputContext final {
  InputContext()
      : mOrigin(XRE_IsParentProcess() ? ORIGIN_MAIN : ORIGIN_CONTENT),
        mHasHandledUserInput(false),
        mInPrivateBrowsing(false) {}

  // If InputContext instance is a static variable, any heap allocated stuff
  // of its members need to be deleted at XPCOM shutdown.  Otherwise, it's
  // detected as memory leak.
  void ShutDown() {
    mURI = nullptr;
    mHTMLInputType.Truncate();
    mHTMLInputMode.Truncate();
    mActionHint.Truncate();
    mAutocapitalize.Truncate();
  }

  bool IsPasswordEditor() const {
    return mHTMLInputType.LowerCaseEqualsLiteral("password");
  }

  NativeKeyBindingsType GetNativeKeyBindingsType() const {
    MOZ_DIAGNOSTIC_ASSERT(mIMEState.IsEditable());
    // See GetInputType in IMEStateManager.cpp
    if (mHTMLInputType.IsEmpty()) {
      return NativeKeyBindingsType::RichTextEditor;
    }
    return mHTMLInputType.EqualsLiteral("textarea")
               ? NativeKeyBindingsType::MultiLineEditor
               : NativeKeyBindingsType::SingleLineEditor;
  }

  // https://html.spec.whatwg.org/dev/interaction.html#autocapitalization
  bool IsAutocapitalizeSupported() const {
    return !mHTMLInputType.EqualsLiteral("password") &&
           !mHTMLInputType.EqualsLiteral("url") &&
           !mHTMLInputType.EqualsLiteral("email");
  }

  bool IsInputAttributeChanged(const InputContext& aOldContext) const {
    return mIMEState.mEnabled != aOldContext.mIMEState.mEnabled ||
#if defined(ANDROID) || defined(MOZ_WIDGET_GTK) || defined(XP_WIN)
           // input type and inputmode are supported by Windows IME API, GTK
           // IME API and Android IME API
           mHTMLInputType != aOldContext.mHTMLInputType ||
           mHTMLInputMode != aOldContext.mHTMLInputMode ||
#endif
#if defined(ANDROID) || defined(MOZ_WIDGET_GTK)
           // autocapitalize is supported by Android IME API and GTK IME API
           mAutocapitalize != aOldContext.mAutocapitalize ||
#endif
#if defined(ANDROID)
           // enterkeyhint is only supported by Android IME API.
           mActionHint != aOldContext.mActionHint ||
#endif
           false;
  }

  IMEState mIMEState;

  // The URI of the document which has the editable element.
  nsCOMPtr<nsIURI> mURI;

  /* The type of the input if the input is a html input field */
  nsString mHTMLInputType;

  // The value of the inputmode
  nsString mHTMLInputMode;

  /* A hint for the action that is performed when the input is submitted */
  nsString mActionHint;

  /* A hint for autocapitalize */
  nsString mAutocapitalize;

  /**
   * mOrigin indicates whether this focus event refers to main or remote
   * content.
   */
  enum Origin {
    // Adjusting focus of content on the main process
    ORIGIN_MAIN,
    // Adjusting focus of content in a remote process
    ORIGIN_CONTENT
  };
  Origin mOrigin;

  /**
   * True if the document has ever received user input
   */
  bool mHasHandledUserInput;

  /* Whether the owning document of the input element has been loaded
   * in private browsing mode. */
  bool mInPrivateBrowsing;

  bool IsOriginMainProcess() const { return mOrigin == ORIGIN_MAIN; }

  bool IsOriginContentProcess() const { return mOrigin == ORIGIN_CONTENT; }

  bool IsOriginCurrentProcess() const {
    if (XRE_IsParentProcess()) {
      return IsOriginMainProcess();
    }
    return IsOriginContentProcess();
  }
};

// FYI: Implemented in nsBaseWidget.cpp
const char* ToChar(InputContext::Origin aOrigin);

struct InputContextAction final {
  /**
   * mCause indicates what action causes calling nsIWidget::SetInputContext().
   * It must be one of following values.
   */
  enum Cause {
    // The cause is unknown but originated from content. Focus might have been
    // changed by content script.
    CAUSE_UNKNOWN,
    // The cause is unknown but originated from chrome. Focus might have been
    // changed by chrome script.
    CAUSE_UNKNOWN_CHROME,
    // The cause is user's keyboard operation.
    CAUSE_KEY,
    // The cause is user's mouse operation.
    CAUSE_MOUSE,
    // The cause is user's touch operation (implies mouse)
    CAUSE_TOUCH,
    // The cause is users' long press operation.
    CAUSE_LONGPRESS,
    // The cause is unknown but it occurs during user input except keyboard
    // input.  E.g., an event handler of a user input event moves focus.
    CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT,
    // The cause is unknown but it occurs during keyboard input.
    CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT,
  };
  Cause mCause;

  /**
   * mFocusChange indicates what happened for focus.
   */
  enum FocusChange {
    FOCUS_NOT_CHANGED,
    // A content got focus.
    GOT_FOCUS,
    // Focused content lost focus.
    LOST_FOCUS,
    // Menu got pseudo focus that means focused content isn't changed but
    // keyboard events will be handled by menu.
    MENU_GOT_PSEUDO_FOCUS,
    // Menu lost pseudo focus that means focused content will handle keyboard
    // events.
    MENU_LOST_PSEUDO_FOCUS,
    // The widget is created.  When a widget is crated, it may need to notify
    // IME module to initialize its native IME context.  In such case, this is
    // used.  I.e., this isn't used by IMEStateManager.
    WIDGET_CREATED
  };
  FocusChange mFocusChange;

  bool ContentGotFocusByTrustedCause() const {
    return (mFocusChange == GOT_FOCUS && mCause != CAUSE_UNKNOWN);
  }

  bool UserMightRequestOpenVKB() const {
    // If focus is changed, user must not request to open VKB.
    if (mFocusChange != FOCUS_NOT_CHANGED) {
      return false;
    }
    switch (mCause) {
      // If user clicks or touches focused editor, user must request to open
      // VKB.
      case CAUSE_MOUSE:
      case CAUSE_TOUCH:
      // If script does something during a user input and that causes changing
      // input context, user might request to open VKB.  E.g., user clicks
      // dummy editor and JS moves focus to an actual editable node.  However,
      // this should return false if the user input is a keyboard event since
      // physical keyboard operation shouldn't cause opening VKB.
      case CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT:
        return true;
      default:
        return false;
    }
  }

  /**
   * IsHandlingUserInput() returns true if it's caused by a user action directly
   * or it's caused by script or something but it occurred while we're handling
   * a user action.  E.g., when it's caused by Element.focus() in an event
   * handler of a user input, this returns true.
   */
  static bool IsHandlingUserInput(Cause aCause) {
    switch (aCause) {
      case CAUSE_KEY:
      case CAUSE_MOUSE:
      case CAUSE_TOUCH:
      case CAUSE_LONGPRESS:
      case CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT:
      case CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT:
        return true;
      default:
        return false;
    }
  }

  bool IsHandlingUserInput() const { return IsHandlingUserInput(mCause); }

  InputContextAction()
      : mCause(CAUSE_UNKNOWN), mFocusChange(FOCUS_NOT_CHANGED) {}

  explicit InputContextAction(Cause aCause,
                              FocusChange aFocusChange = FOCUS_NOT_CHANGED)
      : mCause(aCause), mFocusChange(aFocusChange) {}
};

// IMEMessage is shared by IMEStateManager and TextComposition.
// Update values in GeckoEditable.java if you make changes here.
// XXX Negative values are used in Android...
typedef int8_t IMEMessageType;
enum IMEMessage : IMEMessageType {
  // This is used by IMENotification internally.  This means that the instance
  // hasn't been initialized yet.
  NOTIFY_IME_OF_NOTHING,
  // An editable content is getting focus
  NOTIFY_IME_OF_FOCUS,
  // An editable content is losing focus
  NOTIFY_IME_OF_BLUR,
  // Selection in the focused editable content is changed
  NOTIFY_IME_OF_SELECTION_CHANGE,
  // Text in the focused editable content is changed
  NOTIFY_IME_OF_TEXT_CHANGE,
  // Notified when a dispatched composition event is handled by the
  // contents.  This must be notified after the other notifications.
  // Note that if a remote process has focus, this is notified only once when
  // all dispatched events are handled completely.  So, the receiver shouldn't
  // count number of received this notification for comparing with the number
  // of dispatched events.
  // NOTE: If a composition event causes moving focus from the focused editor,
  //       this notification may not be notified as usual.  Even in such case,
  //       NOTIFY_IME_OF_BLUR is always sent.  So, notification listeners
  //       should tread the blur notification as including this if there is
  //       pending composition events.
  NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED,
  // Position or size of focused element may be changed.
  NOTIFY_IME_OF_POSITION_CHANGE,
  // Mouse button event is fired on a character in focused editor
  NOTIFY_IME_OF_MOUSE_BUTTON_EVENT,
  // Request to commit current composition to IME
  // (some platforms may not support)
  REQUEST_TO_COMMIT_COMPOSITION,
  // Request to cancel current composition to IME
  // (some platforms may not support)
  REQUEST_TO_CANCEL_COMPOSITION
};

// FYI: Implemented in nsBaseWidget.cpp
const char* ToChar(IMEMessage aIMEMessage);

struct IMENotification final {
  IMENotification() : mMessage(NOTIFY_IME_OF_NOTHING), mSelectionChangeData() {}

  IMENotification(const IMENotification& aOther)
      : mMessage(NOTIFY_IME_OF_NOTHING) {
    Assign(aOther);
  }

  ~IMENotification() { Clear(); }

  MOZ_IMPLICIT IMENotification(IMEMessage aMessage)
      : mMessage(aMessage), mSelectionChangeData() {
    switch (aMessage) {
      case NOTIFY_IME_OF_SELECTION_CHANGE:
        mSelectionChangeData.mString = new nsString();
        mSelectionChangeData.Clear();
        break;
      case NOTIFY_IME_OF_TEXT_CHANGE:
        mTextChangeData.Clear();
        break;
      case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
        mMouseButtonEventData.mEventMessage = eVoidEvent;
        mMouseButtonEventData.mOffset = UINT32_MAX;
        mMouseButtonEventData.mCursorPos.MoveTo(0, 0);
        mMouseButtonEventData.mCharRect.SetRect(0, 0, 0, 0);
        mMouseButtonEventData.mButton = -1;
        mMouseButtonEventData.mButtons = 0;
        mMouseButtonEventData.mModifiers = 0;
        break;
      default:
        break;
    }
  }

  void Assign(const IMENotification& aOther) {
    bool changingMessage = mMessage != aOther.mMessage;
    if (changingMessage) {
      Clear();
      mMessage = aOther.mMessage;
    }
    switch (mMessage) {
      case NOTIFY_IME_OF_SELECTION_CHANGE:
        if (changingMessage) {
          mSelectionChangeData.mString = new nsString();
        }
        mSelectionChangeData.Assign(aOther.mSelectionChangeData);
        break;
      case NOTIFY_IME_OF_TEXT_CHANGE:
        mTextChangeData = aOther.mTextChangeData;
        break;
      case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
        mMouseButtonEventData = aOther.mMouseButtonEventData;
        break;
      default:
        break;
    }
  }

  IMENotification& operator=(const IMENotification& aOther) {
    Assign(aOther);
    return *this;
  }

  void Clear() {
    if (mMessage == NOTIFY_IME_OF_SELECTION_CHANGE) {
      MOZ_ASSERT(mSelectionChangeData.mString);
      delete mSelectionChangeData.mString;
      mSelectionChangeData.mString = nullptr;
    }
    mMessage = NOTIFY_IME_OF_NOTHING;
  }

  bool HasNotification() const { return mMessage != NOTIFY_IME_OF_NOTHING; }

  void MergeWith(const IMENotification& aNotification) {
    switch (mMessage) {
      case NOTIFY_IME_OF_NOTHING:
        MOZ_ASSERT(aNotification.mMessage != NOTIFY_IME_OF_NOTHING);
        Assign(aNotification);
        break;
      case NOTIFY_IME_OF_SELECTION_CHANGE:
        MOZ_ASSERT(aNotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE);
        mSelectionChangeData.Assign(aNotification.mSelectionChangeData);
        break;
      case NOTIFY_IME_OF_TEXT_CHANGE:
        MOZ_ASSERT(aNotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE);
        mTextChangeData += aNotification.mTextChangeData;
        break;
      case NOTIFY_IME_OF_POSITION_CHANGE:
      case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
        MOZ_ASSERT(aNotification.mMessage == mMessage);
        break;
      default:
        MOZ_CRASH("Merging notification isn't supported");
        break;
    }
  }

  IMEMessage mMessage;

  // NOTIFY_IME_OF_SELECTION_CHANGE specific data
  struct SelectionChangeDataBase {
    // Selection range.
    uint32_t mOffset;

    // Selected string
    nsString* mString;

    // Writing mode at the selection.
    uint8_t mWritingModeBits;

    bool mIsInitialized;
    bool mHasRange;
    bool mReversed;
    bool mCausedByComposition;
    bool mCausedBySelectionEvent;
    bool mOccurredDuringComposition;

    void SetWritingMode(const WritingMode& aWritingMode);
    WritingMode GetWritingMode() const;

    uint32_t StartOffset() const {
      MOZ_ASSERT(mHasRange);
      return mOffset;
    }
    uint32_t EndOffset() const {
      MOZ_ASSERT(mHasRange);
      return mOffset + Length();
    }
    uint32_t AnchorOffset() const {
      MOZ_ASSERT(mHasRange);
      return mOffset + (mReversed ? Length() : 0);
    }
    uint32_t FocusOffset() const {
      MOZ_ASSERT(mHasRange);
      return mOffset + (mReversed ? 0 : Length());
    }
    const nsString& String() const {
      MOZ_ASSERT(mHasRange);
      return *mString;
    }
    uint32_t Length() const {
      MOZ_ASSERT(mHasRange);
      return mString->Length();
    }
    bool IsInInt32Range() const {
      return mHasRange && mOffset <= INT32_MAX && Length() <= INT32_MAX &&
             mOffset + Length() <= INT32_MAX;
    }
    bool HasRange() const { return mIsInitialized && mHasRange; }
    bool IsCollapsed() const { return !mHasRange || mString->IsEmpty(); }
    void ClearSelectionData() {
      mIsInitialized = false;
      mHasRange = false;
      mOffset = UINT32_MAX;
      mString->Truncate();
      mWritingModeBits = 0;
      mReversed = false;
    }
    void Clear() {
      ClearSelectionData();
      mCausedByComposition = false;
      mCausedBySelectionEvent = false;
      mOccurredDuringComposition = false;
    }
    bool IsInitialized() const { return mIsInitialized; }
    void Assign(const SelectionChangeDataBase& aOther) {
      mIsInitialized = aOther.mIsInitialized;
      mHasRange = aOther.mHasRange;
      if (mIsInitialized && mHasRange) {
        mOffset = aOther.mOffset;
        *mString = aOther.String();
        mReversed = aOther.mReversed;
        mWritingModeBits = aOther.mWritingModeBits;
      } else {
        mOffset = UINT32_MAX;
        mString->Truncate();
        mReversed = false;
        // Let's keep the writing mode for avoiding temporarily changing the
        // writing mode at no selection range.
      }
      AssignReason(aOther.mCausedByComposition, aOther.mCausedBySelectionEvent,
                   aOther.mOccurredDuringComposition);
    }
    void Assign(const WidgetQueryContentEvent& aQuerySelectedTextEvent);
    void AssignReason(bool aCausedByComposition, bool aCausedBySelectionEvent,
                      bool aOccurredDuringComposition) {
      mCausedByComposition = aCausedByComposition;
      mCausedBySelectionEvent = aCausedBySelectionEvent;
      mOccurredDuringComposition = aOccurredDuringComposition;
    }

    bool EqualsRange(const SelectionChangeDataBase& aOther) const {
      if (HasRange() != aOther.HasRange()) {
        return false;
      }
      if (!HasRange()) {
        return true;
      }
      return mOffset == aOther.mOffset && mString->Equals(*aOther.mString);
    }
    bool EqualsRangeAndDirection(const SelectionChangeDataBase& aOther) const {
      return EqualsRange(aOther) &&
             (!HasRange() || mReversed == aOther.mReversed);
    }
    bool EqualsRangeAndDirectionAndWritingMode(
        const SelectionChangeDataBase& aOther) const {
      return EqualsRangeAndDirection(aOther) &&
             mWritingModeBits == aOther.mWritingModeBits;
    }

    bool EqualsRange(const ContentSelection& aContentSelection) const;
    bool EqualsRangeAndWritingMode(
        const ContentSelection& aContentSelection) const;

    OffsetAndData<uint32_t> ToUint32OffsetAndData() const {
      return OffsetAndData<uint32_t>(mOffset, *mString,
                                     OffsetAndDataFor::SelectedString);
    }
  };

  // SelectionChangeDataBase cannot have constructors because it's used in
  // the union.  Therefore, SelectionChangeData should only implement
  // constructors.  In other words, add other members to
  // SelectionChangeDataBase.
  struct SelectionChangeData final : public SelectionChangeDataBase {
    SelectionChangeData() {
      mString = &mStringInstance;
      Clear();
    }
    explicit SelectionChangeData(const SelectionChangeDataBase& aOther) {
      mString = &mStringInstance;
      Assign(aOther);
    }
    SelectionChangeData(const SelectionChangeData& aOther) {
      mString = &mStringInstance;
      Assign(aOther);
    }
    SelectionChangeData& operator=(const SelectionChangeDataBase& aOther) {
      mString = &mStringInstance;
      Assign(aOther);
      return *this;
    }
    SelectionChangeData& operator=(const SelectionChangeData& aOther) {
      mString = &mStringInstance;
      Assign(aOther);
      return *this;
    }

   private:
    // When SelectionChangeData is used outside of union, it shouldn't create
    // nsString instance in the heap as far as possible.
    nsString mStringInstance;
  };

  struct TextChangeDataBase {
    // mStartOffset is the start offset of modified or removed text in
    // original content and inserted text in new content.
    uint32_t mStartOffset;
    // mRemovalEndOffset is the end offset of modified or removed text in
    // original content.  If the value is same as mStartOffset, no text hasn't
    // been removed yet.
    uint32_t mRemovedEndOffset;
    // mAddedEndOffset is the end offset of inserted text or same as
    // mStartOffset if just removed.  The vlaue is offset in the new content.
    uint32_t mAddedEndOffset;

    // Note that TextChangeDataBase may be the result of merging two or more
    // changes especially in e10s mode.

    // mCausedOnlyByComposition is true only when *all* merged changes are
    // caused by composition.
    bool mCausedOnlyByComposition;
    // mIncludingChangesDuringComposition is true if at least one change which
    // is not caused by composition occurred during the last composition.
    // Note that if after the last composition is finished and there are some
    // changes not caused by composition, this is set to false.
    bool mIncludingChangesDuringComposition;
    // mIncludingChangesWithoutComposition is true if there is at least one
    // change which did occur when there wasn't a composition ongoing.
    bool mIncludingChangesWithoutComposition;

    uint32_t OldLength() const {
      MOZ_ASSERT(IsValid());
      return mRemovedEndOffset - mStartOffset;
    }
    uint32_t NewLength() const {
      MOZ_ASSERT(IsValid());
      return mAddedEndOffset - mStartOffset;
    }

    // Positive if text is added. Negative if text is removed.
    int64_t Difference() const { return mAddedEndOffset - mRemovedEndOffset; }

    bool IsInInt32Range() const {
      MOZ_ASSERT(IsValid());
      return mStartOffset <= INT32_MAX && mRemovedEndOffset <= INT32_MAX &&
             mAddedEndOffset <= INT32_MAX;
    }

    bool IsValid() const {
      return !(mStartOffset == UINT32_MAX && !mRemovedEndOffset &&
               !mAddedEndOffset);
    }

    void Clear() {
      mStartOffset = UINT32_MAX;
      mRemovedEndOffset = mAddedEndOffset = 0;
    }

    void MergeWith(const TextChangeDataBase& aOther);
    TextChangeDataBase& operator+=(const TextChangeDataBase& aOther) {
      MergeWith(aOther);
      return *this;
    }

#ifdef DEBUG
    void Test();
#endif  // #ifdef DEBUG
  };

  // TextChangeDataBase cannot have constructors because they are used in union.
  // Therefore, TextChangeData should only implement constructor.  In other
  // words, add other members to TextChangeDataBase.
  struct TextChangeData : public TextChangeDataBase {
    TextChangeData() { Clear(); }

    TextChangeData(uint32_t aStartOffset, uint32_t aRemovedEndOffset,
                   uint32_t aAddedEndOffset, bool aCausedByComposition,
                   bool aOccurredDuringComposition) {
      MOZ_ASSERT(aRemovedEndOffset >= aStartOffset,
                 "removed end offset must not be smaller than start offset");
      MOZ_ASSERT(aAddedEndOffset >= aStartOffset,
                 "added end offset must not be smaller than start offset");
      mStartOffset = aStartOffset;
      mRemovedEndOffset = aRemovedEndOffset;
      mAddedEndOffset = aAddedEndOffset;
      mCausedOnlyByComposition = aCausedByComposition;
      mIncludingChangesDuringComposition =
          !aCausedByComposition && aOccurredDuringComposition;
      mIncludingChangesWithoutComposition =
          !aCausedByComposition && !aOccurredDuringComposition;
    }
  };

  struct MouseButtonEventData {
    // The value of WidgetEvent::mMessage
    EventMessage mEventMessage;
    // Character offset from the start of the focused editor under the cursor
    uint32_t mOffset;
    // Cursor position in pixels relative to the widget
    LayoutDeviceIntPoint mCursorPos;
    // Character rect in pixels under the cursor relative to the widget
    LayoutDeviceIntRect mCharRect;
    // The value of WidgetMouseEventBase::button and buttons
    int16_t mButton;
    int16_t mButtons;
    // The value of WidgetInputEvent::modifiers
    Modifiers mModifiers;
  };

  union {
    // NOTIFY_IME_OF_SELECTION_CHANGE specific data
    SelectionChangeDataBase mSelectionChangeData;

    // NOTIFY_IME_OF_TEXT_CHANGE specific data
    TextChangeDataBase mTextChangeData;

    // NOTIFY_IME_OF_MOUSE_BUTTON_EVENT specific data
    MouseButtonEventData mMouseButtonEventData;
  };

  void SetData(const SelectionChangeDataBase& aSelectionChangeData) {
    MOZ_RELEASE_ASSERT(mMessage == NOTIFY_IME_OF_SELECTION_CHANGE);
    mSelectionChangeData.Assign(aSelectionChangeData);
  }

  void SetData(const TextChangeDataBase& aTextChangeData) {
    MOZ_RELEASE_ASSERT(mMessage == NOTIFY_IME_OF_TEXT_CHANGE);
    mTextChangeData = aTextChangeData;
  }
};

struct CandidateWindowPosition {
  // Upper left corner of the candidate window if mExcludeRect is false.
  // Otherwise, the position currently interested.  E.g., caret position.
  LayoutDeviceIntPoint mPoint;
  // Rect which shouldn't be overlapped with the candidate window.
  // This is valid only when mExcludeRect is true.
  LayoutDeviceIntRect mRect;
  // See explanation of mPoint and mRect.
  bool mExcludeRect;
};

std::ostream& operator<<(std::ostream& aStream, const IMEEnabled& aEnabled);
std::ostream& operator<<(std::ostream& aStream, const IMEState::Open& aOpen);
std::ostream& operator<<(std::ostream& aStream, const IMEState& aState);
std::ostream& operator<<(std::ostream& aStream,
                         const InputContext::Origin& aOrigin);
std::ostream& operator<<(std::ostream& aStream, const InputContext& aContext);
std::ostream& operator<<(std::ostream& aStream,
                         const InputContextAction::Cause& aCause);
std::ostream& operator<<(std::ostream& aStream,
                         const InputContextAction::FocusChange& aFocusChange);
std::ostream& operator<<(std::ostream& aStream,
                         const IMENotification::SelectionChangeDataBase& aData);
std::ostream& operator<<(std::ostream& aStream,
                         const IMENotification::TextChangeDataBase& aData);

}  // namespace widget
}  // namespace mozilla

#endif  // #ifndef mozilla_widget_IMEData_h_
