/* 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 AccEvent_H_
#define AccEvent_H_

#include "nsIAccessibleEvent.h"

#include "mozilla/a11y/LocalAccessible.h"

class nsEventShell;
namespace mozilla {

namespace dom {
class Selection;
}

namespace a11y {

class DocAccessible;
class EventQueue;
class TextRange;

// Constants used to point whether the event is from user input.
enum EIsFromUserInput {
  // eNoUserInput: event is not from user input
  eNoUserInput = 0,
  // eFromUserInput: event is from user input
  eFromUserInput = 1,
  // eAutoDetect: the value should be obtained from event state manager
  eAutoDetect = -1
};

/**
 * Generic accessible event.
 */
class AccEvent {
 public:
  // Rule for accessible events.
  // The rule will be applied when flushing pending events.
  enum EEventRule {
    // eAllowDupes : More than one event of the same type is allowed.
    //    This event will always be emitted. This flag is used for events that
    //    don't support coalescence.
    eAllowDupes,

    // eCoalesceReorder : For reorder events from the same subtree or the same
    //    node, only the umbrella event on the ancestor will be emitted.
    eCoalesceReorder,

    // eCoalesceOfSameType : For events of the same type, only the newest event
    // will be processed.
    eCoalesceOfSameType,

    // eCoalesceSelectionChange: coalescence of selection change events.
    eCoalesceSelectionChange,

    // eCoalesceStateChange: coalesce state change events.
    eCoalesceStateChange,

    // eCoalesceTextSelChange: coalescence of text selection change events.
    eCoalesceTextSelChange,

    // eRemoveDupes : For repeat events, only the newest event in queue
    //    will be emitted.
    eRemoveDupes,

    // eDoNotEmit : This event is confirmed as a duplicate, do not emit it.
    eDoNotEmit
  };

  // Initialize with an accessible.
  AccEvent(uint32_t aEventType, LocalAccessible* aAccessible,
           EIsFromUserInput aIsFromUserInput = eAutoDetect,
           EEventRule aEventRule = eRemoveDupes);

  // AccEvent
  uint32_t GetEventType() const { return mEventType; }
  EEventRule GetEventRule() const { return mEventRule; }
  bool IsFromUserInput() const { return mIsFromUserInput; }
  EIsFromUserInput FromUserInput() const {
    return static_cast<EIsFromUserInput>(mIsFromUserInput);
  }

  LocalAccessible* GetAccessible() const { return mAccessible; }
  DocAccessible* Document() const { return mAccessible->Document(); }

  /**
   * Down casting.
   */
  enum EventGroup {
    eGenericEvent,
    eStateChangeEvent,
    eTextChangeEvent,
    eTreeMutationEvent,
    eMutationEvent,
    eReorderEvent,
    eHideEvent,
    eShowEvent,
    eCaretMoveEvent,
    eTextSelChangeEvent,
    eSelectionChangeEvent,
    eObjectAttrChangedEvent,
    eScrollingEvent,
    eAnnouncementEvent,
  };

  static const EventGroup kEventGroup = eGenericEvent;
  virtual unsigned int GetEventGroups() const { return 1U << eGenericEvent; }

  /**
   * Reference counting and cycle collection.
   */
  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AccEvent)
  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AccEvent)

 protected:
  virtual ~AccEvent() = default;

  bool mIsFromUserInput;
  uint32_t mEventType;
  EEventRule mEventRule;
  RefPtr<LocalAccessible> mAccessible;

  friend class EventQueue;
  friend class EventTree;
  friend class ::nsEventShell;
  friend class NotificationController;
};

/**
 * Accessible state change event.
 */
class AccStateChangeEvent : public AccEvent {
 public:
  AccStateChangeEvent(LocalAccessible* aAccessible, uint64_t aState,
                      bool aIsEnabled,
                      EIsFromUserInput aIsFromUserInput = eAutoDetect)
      : AccEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible,
                 aIsFromUserInput, eCoalesceStateChange),
        mState(aState),
        mIsEnabled(aIsEnabled) {}

  AccStateChangeEvent(LocalAccessible* aAccessible, uint64_t aState)
      : AccEvent(::nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible,
                 eAutoDetect, eCoalesceStateChange),
        mState(aState) {
    mIsEnabled = (mAccessible->State() & mState) != 0;
  }

  // AccEvent
  static const EventGroup kEventGroup = eStateChangeEvent;
  virtual unsigned int GetEventGroups() const override {
    return AccEvent::GetEventGroups() | (1U << eStateChangeEvent);
  }

  // AccStateChangeEvent
  uint64_t GetState() const { return mState; }
  bool IsStateEnabled() const { return mIsEnabled; }

 private:
  uint64_t mState;
  bool mIsEnabled;

  friend class EventQueue;
};

/**
 * Accessible text change event.
 */
class AccTextChangeEvent : public AccEvent {
 public:
  AccTextChangeEvent(LocalAccessible* aAccessible, int32_t aStart,
                     const nsAString& aModifiedText, bool aIsInserted,
                     EIsFromUserInput aIsFromUserInput = eAutoDetect);

  // AccEvent
  static const EventGroup kEventGroup = eTextChangeEvent;
  virtual unsigned int GetEventGroups() const override {
    return AccEvent::GetEventGroups() | (1U << eTextChangeEvent);
  }

  // AccTextChangeEvent
  int32_t GetStartOffset() const { return mStart; }
  uint32_t GetLength() const { return mModifiedText.Length(); }
  bool IsTextInserted() const { return mIsInserted; }
  void GetModifiedText(nsAString& aModifiedText) {
    aModifiedText = mModifiedText;
  }
  const nsString& ModifiedText() const { return mModifiedText; }

 private:
  int32_t mStart;
  bool mIsInserted;
  nsString mModifiedText;

  friend class EventTree;
  friend class NotificationController;
};

/**
 * A base class for events related to tree mutation, either an AccMutation
 * event, or an AccReorderEvent.
 */
class AccTreeMutationEvent : public AccEvent {
 public:
  AccTreeMutationEvent(uint32_t aEventType, LocalAccessible* aTarget)
      : AccEvent(aEventType, aTarget, eAutoDetect, eCoalesceReorder),
        mGeneration(0) {}

  // Event
  static const EventGroup kEventGroup = eTreeMutationEvent;
  virtual unsigned int GetEventGroups() const override {
    return AccEvent::GetEventGroups() | (1U << eTreeMutationEvent);
  }

  void SetNextEvent(AccTreeMutationEvent* aNext) { mNextEvent = aNext; }
  void SetPrevEvent(AccTreeMutationEvent* aPrev) { mPrevEvent = aPrev; }
  AccTreeMutationEvent* NextEvent() const { return mNextEvent; }
  AccTreeMutationEvent* PrevEvent() const { return mPrevEvent; }

  /**
   * A sequence number to know when this event was fired.
   */
  uint32_t EventGeneration() const { return mGeneration; }
  void SetEventGeneration(uint32_t aGeneration) { mGeneration = aGeneration; }

 private:
  RefPtr<AccTreeMutationEvent> mNextEvent;
  RefPtr<AccTreeMutationEvent> mPrevEvent;
  uint32_t mGeneration;
};

/**
 * Base class for show and hide accessible events.
 */
class AccMutationEvent : public AccTreeMutationEvent {
 public:
  AccMutationEvent(uint32_t aEventType, LocalAccessible* aTarget)
      : AccTreeMutationEvent(aEventType, aTarget) {
    // Don't coalesce these since they are coalesced by reorder event. Coalesce
    // contained text change events.
    mParent = mAccessible->LocalParent();
  }
  virtual ~AccMutationEvent() = default;

  // Event
  static const EventGroup kEventGroup = eMutationEvent;
  virtual unsigned int GetEventGroups() const override {
    return AccTreeMutationEvent::GetEventGroups() | (1U << eMutationEvent);
  }

  // MutationEvent
  bool IsShow() const { return mEventType == nsIAccessibleEvent::EVENT_SHOW; }
  bool IsHide() const { return mEventType == nsIAccessibleEvent::EVENT_HIDE; }

  LocalAccessible* LocalParent() const { return mParent; }

 protected:
  RefPtr<LocalAccessible> mParent;
  RefPtr<AccTextChangeEvent> mTextChangeEvent;

  friend class EventTree;
  friend class NotificationController;
};

/**
 * Accessible hide event.
 */
class AccHideEvent : public AccMutationEvent {
 public:
  explicit AccHideEvent(LocalAccessible* aTarget, bool aNeedsShutdown = true);

  // Event
  static const EventGroup kEventGroup = eHideEvent;
  virtual unsigned int GetEventGroups() const override {
    return AccMutationEvent::GetEventGroups() | (1U << eHideEvent);
  }

  // AccHideEvent
  LocalAccessible* TargetParent() const { return mParent; }
  LocalAccessible* TargetNextSibling() const { return mNextSibling; }
  LocalAccessible* TargetPrevSibling() const { return mPrevSibling; }
  bool NeedsShutdown() const { return mNeedsShutdown; }

 protected:
  bool mNeedsShutdown;
  RefPtr<LocalAccessible> mNextSibling;
  RefPtr<LocalAccessible> mPrevSibling;

  friend class EventTree;
  friend class NotificationController;
};

/**
 * Accessible show event.
 */
class AccShowEvent : public AccMutationEvent {
 public:
  explicit AccShowEvent(LocalAccessible* aTarget)
      : AccMutationEvent(::nsIAccessibleEvent::EVENT_SHOW, aTarget) {}

  // Event
  static const EventGroup kEventGroup = eShowEvent;
  virtual unsigned int GetEventGroups() const override {
    return AccMutationEvent::GetEventGroups() | (1U << eShowEvent);
  }
};

/**
 * Class for reorder accessible event. Takes care about
 */
class AccReorderEvent : public AccTreeMutationEvent {
 public:
  explicit AccReorderEvent(LocalAccessible* aTarget)
      : AccTreeMutationEvent(::nsIAccessibleEvent::EVENT_REORDER, aTarget) {}
  virtual ~AccReorderEvent() = default;

  // Event
  static const EventGroup kEventGroup = eReorderEvent;
  virtual unsigned int GetEventGroups() const override {
    return AccTreeMutationEvent::GetEventGroups() | (1U << eReorderEvent);
  }

  /*
   * Make this an inner reorder event that is coalesced into
   * a reorder event of an ancestor.
   */
  void SetInner() { mEventType = ::nsIAccessibleEvent::EVENT_INNER_REORDER; }
};

/**
 * Accessible caret move event.
 */
class AccCaretMoveEvent : public AccEvent {
 public:
  AccCaretMoveEvent(LocalAccessible* aAccessible, int32_t aCaretOffset,
                    bool aIsSelectionCollapsed, bool aIsAtEndOfLine,
                    int32_t aGranularity,
                    EIsFromUserInput aIsFromUserInput = eAutoDetect)
      : AccEvent(::nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED, aAccessible,
                 aIsFromUserInput),
        mCaretOffset(aCaretOffset),
        mIsSelectionCollapsed(aIsSelectionCollapsed),
        mIsAtEndOfLine(aIsAtEndOfLine),
        mGranularity(aGranularity) {}
  virtual ~AccCaretMoveEvent() = default;

  // AccEvent
  static const EventGroup kEventGroup = eCaretMoveEvent;
  virtual unsigned int GetEventGroups() const override {
    return AccEvent::GetEventGroups() | (1U << eCaretMoveEvent);
  }

  // AccCaretMoveEvent
  int32_t GetCaretOffset() const { return mCaretOffset; }

  bool IsSelectionCollapsed() const { return mIsSelectionCollapsed; }
  bool IsAtEndOfLine() { return mIsAtEndOfLine; }

  int32_t GetGranularity() const { return mGranularity; }

 private:
  int32_t mCaretOffset;

  bool mIsSelectionCollapsed;
  bool mIsAtEndOfLine;
  int32_t mGranularity;
};

/**
 * Accessible text selection change event.
 */
class AccTextSelChangeEvent : public AccEvent {
 public:
  AccTextSelChangeEvent(HyperTextAccessible* aTarget,
                        dom::Selection* aSelection, int32_t aReason,
                        int32_t aGranularity);
  virtual ~AccTextSelChangeEvent();

  // AccEvent
  static const EventGroup kEventGroup = eTextSelChangeEvent;
  virtual unsigned int GetEventGroups() const override {
    return AccEvent::GetEventGroups() | (1U << eTextSelChangeEvent);
  }

  // AccTextSelChangeEvent

  /**
   * Return true if the text selection change wasn't caused by pure caret move.
   */
  bool IsCaretMoveOnly() const;

  int32_t GetGranularity() const { return mGranularity; }

  /**
   * Return selection ranges in document/control.
   */
  void SelectionRanges(nsTArray<a11y::TextRange>* aRanges) const;

 private:
  RefPtr<dom::Selection> mSel;
  int32_t mReason;
  int32_t mGranularity;

  friend class EventQueue;
  friend class SelectionManager;
};

/**
 * Accessible widget selection change event.
 */
class AccSelChangeEvent : public AccEvent {
 public:
  enum SelChangeType { eSelectionAdd, eSelectionRemove };

  AccSelChangeEvent(LocalAccessible* aWidget, LocalAccessible* aItem,
                    SelChangeType aSelChangeType);

  virtual ~AccSelChangeEvent() = default;

  // AccEvent
  static const EventGroup kEventGroup = eSelectionChangeEvent;
  virtual unsigned int GetEventGroups() const override {
    return AccEvent::GetEventGroups() | (1U << eSelectionChangeEvent);
  }

  // AccSelChangeEvent
  LocalAccessible* Widget() const { return mWidget; }

 private:
  RefPtr<LocalAccessible> mWidget;
  RefPtr<LocalAccessible> mItem;
  SelChangeType mSelChangeType;
  uint32_t mPreceedingCount;
  AccSelChangeEvent* mPackedEvent;

  friend class EventQueue;
};

/**
 * Accessible object attribute changed event.
 */
class AccObjectAttrChangedEvent : public AccEvent {
 public:
  AccObjectAttrChangedEvent(LocalAccessible* aAccessible, nsAtom* aAttribute)
      : AccEvent(::nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
                 aAccessible),
        mAttribute(aAttribute) {}

  // AccEvent
  static const EventGroup kEventGroup = eObjectAttrChangedEvent;
  virtual unsigned int GetEventGroups() const override {
    return AccEvent::GetEventGroups() | (1U << eObjectAttrChangedEvent);
  }

  // AccObjectAttrChangedEvent
  nsAtom* GetAttribute() const { return mAttribute; }

 private:
  RefPtr<nsAtom> mAttribute;

  virtual ~AccObjectAttrChangedEvent() = default;
};

/**
 * Accessible scroll event.
 */
class AccScrollingEvent : public AccEvent {
 public:
  AccScrollingEvent(uint32_t aEventType, LocalAccessible* aAccessible,
                    uint32_t aScrollX, uint32_t aScrollY, uint32_t aMaxScrollX,
                    uint32_t aMaxScrollY)
      : AccEvent(aEventType, aAccessible),
        mScrollX(aScrollX),
        mScrollY(aScrollY),
        mMaxScrollX(aMaxScrollX),
        mMaxScrollY(aMaxScrollY) {}

  virtual ~AccScrollingEvent() = default;

  // AccEvent
  static const EventGroup kEventGroup = eScrollingEvent;
  virtual unsigned int GetEventGroups() const override {
    return AccEvent::GetEventGroups() | (1U << eScrollingEvent);
  }

  // The X scrolling offset of the container when the event was fired.
  uint32_t ScrollX() { return mScrollX; }
  // The Y scrolling offset of the container when the event was fired.
  uint32_t ScrollY() { return mScrollY; }
  // The max X offset of the container.
  uint32_t MaxScrollX() { return mMaxScrollX; }
  // The max Y offset of the container.
  uint32_t MaxScrollY() { return mMaxScrollY; }

 private:
  uint32_t mScrollX;
  uint32_t mScrollY;
  uint32_t mMaxScrollX;
  uint32_t mMaxScrollY;
};

/**
 * Accessible announcement event.
 */
class AccAnnouncementEvent : public AccEvent {
 public:
  AccAnnouncementEvent(LocalAccessible* aAccessible,
                       const nsAString& aAnnouncement, uint16_t aPriority)
      : AccEvent(nsIAccessibleEvent::EVENT_ANNOUNCEMENT, aAccessible),
        mAnnouncement(aAnnouncement),
        mPriority(aPriority) {}

  virtual ~AccAnnouncementEvent() = default;

  // AccEvent
  static const EventGroup kEventGroup = eAnnouncementEvent;
  virtual unsigned int GetEventGroups() const override {
    return AccEvent::GetEventGroups() | (1U << eAnnouncementEvent);
  }

  const nsString& Announcement() const { return mAnnouncement; }

  uint16_t Priority() { return mPriority; }

 private:
  nsString mAnnouncement;
  uint16_t mPriority;
};

/**
 * Downcast the generic accessible event object to derived type.
 */
class downcast_accEvent {
 public:
  explicit downcast_accEvent(AccEvent* e) : mRawPtr(e) {}

  template <class Destination>
  operator Destination*() {
    if (!mRawPtr) return nullptr;

    return mRawPtr->GetEventGroups() & (1U << Destination::kEventGroup)
               ? static_cast<Destination*>(mRawPtr)
               : nullptr;
  }

 private:
  AccEvent* mRawPtr;
};

/**
 * Return a new xpcom accessible event for the given internal one.
 */
already_AddRefed<nsIAccessibleEvent> MakeXPCEvent(AccEvent* aEvent);

}  // namespace a11y
}  // namespace mozilla

#endif
