// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_ACCESSIBILITY_AX_EVENT_GENERATOR_H_
#define UI_ACCESSIBILITY_AX_EVENT_GENERATOR_H_

#include <bitset>
#include <map>
#include <memory>
#include <ostream>
#include <set>
#include <string>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "ui/accessibility/ax_event_intent.h"
#include "ui/accessibility/ax_export.h"
#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_tree_observer.h"

namespace ui {

// Subclass of AXTreeObserver that automatically generates AXEvents to fire
// based on changes to an accessibility tree.  Every platform
// tends to want different events, so this class lets each platform
// handle the events it wants and ignore the others.
class AX_EXPORT AXEventGenerator : public AXTreeObserver {
 public:
  enum class Event : int32_t {
    NONE,
    ACCESS_KEY_CHANGED,
    ACTIVE_DESCENDANT_CHANGED,
    ALERT,
    ARIA_CURRENT_CHANGED,

    // ATK treats alignment, indentation, and other format-related attributes as
    // text attributes even when they are only applicable to the entire object.
    // And it lacks an event for use when object attributes have changed.
    ATK_TEXT_OBJECT_ATTRIBUTE_CHANGED,
    ATOMIC_CHANGED,
    AUTO_COMPLETE_CHANGED,
    AUTOFILL_AVAILABILITY_CHANGED,
    BUSY_CHANGED,
    CARET_BOUNDS_CHANGED,
    CHECKED_STATE_CHANGED,
    CHECKED_STATE_DESCRIPTION_CHANGED,
    CHILDREN_CHANGED,
    CLASS_NAME_CHANGED,
    COLLAPSED,
    CONTROLS_CHANGED,
    DETAILS_CHANGED,
    DESCRIBED_BY_CHANGED,
    DESCRIPTION_CHANGED,
    DOCUMENT_SELECTION_CHANGED,
    DOCUMENT_TITLE_CHANGED,
    DROPEFFECT_CHANGED,

    // TODO(nektar): Deprecate this event and replace it with
    // "VALUE_IN_TEXT_FIELD_CHANGED".
    EDITABLE_TEXT_CHANGED,
    ENABLED_CHANGED,
    EXPANDED,
    FOCUS_CHANGED,
    FLOW_FROM_CHANGED,
    FLOW_TO_CHANGED,
    GRABBED_CHANGED,
    HASPOPUP_CHANGED,
    HIERARCHICAL_LEVEL_CHANGED,
    IGNORED_CHANGED,
    IMAGE_ANNOTATION_CHANGED,
    INVALID_STATUS_CHANGED,
    KEY_SHORTCUTS_CHANGED,
    LABELED_BY_CHANGED,
    LANGUAGE_CHANGED,
    LAYOUT_INVALIDATED,  // Fired when aria-busy turns from true to false.

    // Fired only on the root of the ARIA live region.
    LIVE_REGION_CHANGED,
    // Fired only on the root of the ARIA live region.
    LIVE_REGION_CREATED,
    // Fired on all the nodes within the ARIA live region excluding its root.
    LIVE_REGION_NODE_CHANGED,
    // Fired only on the root of the ARIA live region.
    LIVE_RELEVANT_CHANGED,
    // Fired only on the root of the ARIA live region.
    LIVE_STATUS_CHANGED,
    MENU_ITEM_SELECTED,
    MENU_POPUP_END,
    MENU_POPUP_START,
    MULTILINE_STATE_CHANGED,
    MULTISELECTABLE_STATE_CHANGED,
    NAME_CHANGED,
    OBJECT_ATTRIBUTE_CHANGED,
    OTHER_ATTRIBUTE_CHANGED,
    PARENT_CHANGED,
    PLACEHOLDER_CHANGED,
    PORTAL_ACTIVATED,
    POSITION_IN_SET_CHANGED,
    RANGE_VALUE_CHANGED,
    RANGE_VALUE_MAX_CHANGED,
    RANGE_VALUE_MIN_CHANGED,
    RANGE_VALUE_STEP_CHANGED,
    READONLY_CHANGED,
    RELATED_NODE_CHANGED,
    REQUIRED_STATE_CHANGED,
    ROLE_CHANGED,
    ROW_COUNT_CHANGED,
    SCROLL_HORIZONTAL_POSITION_CHANGED,
    SCROLL_VERTICAL_POSITION_CHANGED,
    SELECTED_CHANGED,
    SELECTED_CHILDREN_CHANGED,
    SELECTED_VALUE_CHANGED,
    SET_SIZE_CHANGED,
    SORT_CHANGED,
    STATE_CHANGED,
    SUBTREE_CREATED,
    TEXT_ATTRIBUTE_CHANGED,
    TEXT_SELECTION_CHANGED,
    VALUE_IN_TEXT_FIELD_CHANGED,

    // This event is fired for the exact set of attributes that affect the
    // MSAA/IAccessible state on Windows. It is not needed on other platforms,
    // but it is very natural to compute in this class.
    WIN_IACCESSIBLE_STATE_CHANGED,
    MAX_VALUE = WIN_IACCESSIBLE_STATE_CHANGED,
  };

  // For distinguishing between show and hide state when a node has
  // an IGNORED_CHANGED event.
  enum class IgnoredChangedState : uint8_t { kShow, kHide, kCount = 2 };

  struct AX_EXPORT EventParams final {
    explicit EventParams(Event event);
    EventParams(Event event,
                ax::mojom::EventFrom event_from,
                ax::mojom::Action event_from_action,
                const std::vector<AXEventIntent>& event_intents);
    EventParams(const EventParams& other);
    ~EventParams();

    EventParams& operator=(const EventParams& other);
    bool operator==(const EventParams& rhs) const;
    bool operator<(const EventParams& rhs) const;

    Event event;
    ax::mojom::EventFrom event_from = ax::mojom::EventFrom::kNone;
    ax::mojom::Action event_from_action;
    std::vector<AXEventIntent> event_intents;
  };

  struct AX_EXPORT TargetedEvent final {
    TargetedEvent(AXNodeID node_id, const EventParams& event_params);
    ~TargetedEvent();

    const AXNodeID node_id;
    const EventParams& event_params;
  };

  class AX_EXPORT Iterator {
   public:
    using iterator_category = std::input_iterator_tag;
    using value_type = TargetedEvent;
    using difference_type = std::ptrdiff_t;
    using pointer = TargetedEvent*;
    using reference = TargetedEvent&;

    Iterator(
        std::map<AXNodeID, std::set<EventParams>>::const_iterator
            map_start_iter,
        std::map<AXNodeID, std::set<EventParams>>::const_iterator map_end_iter);
    Iterator(const Iterator& other);
    ~Iterator();

    Iterator& operator=(const Iterator& other);
    Iterator& operator++();
    Iterator operator++(int);  // Postfix increment.
    value_type operator*() const;

   private:
    AX_EXPORT friend bool operator==(const Iterator& lhs, const Iterator& rhs);
    AX_EXPORT friend bool operator!=(const Iterator& lhs, const Iterator& rhs);
    AX_EXPORT friend void swap(Iterator& lhs, Iterator& rhs);

    std::map<AXNodeID, std::set<EventParams>>::const_iterator map_iter_;
    std::map<AXNodeID, std::set<EventParams>>::const_iterator map_end_iter_;
    std::set<EventParams>::const_iterator set_iter_;
  };

  // For storing ignored changed states for a particular node. We use bitset as
  // the underlying data structure to improve memory usage.
  // We use the index of AXEventGenerator::IgnoredChangedState enum
  // to access the bitset data.
  // e.g. AXEventGenerator::IgnoredChangedState::kShow has index 0 in the
  // IgnoredChangedState enum. If |IgnoredChangedStatesBitset[0]| is set, it
  // means IgnoredChangedState::kShow is present. Similarly, kHide has index 1
  // in the enum, and it corresponds to |IgnoredChangedStatesBitset[1]|.
  using IgnoredChangedStatesBitset =
      std::bitset<static_cast<size_t>(IgnoredChangedState::kCount)>;
  using const_iterator = Iterator;
  using iterator = Iterator;
  using value_type = TargetedEvent;

  // If you use this constructor, you must call SetTree
  // before using this class.
  AXEventGenerator();

  // Automatically registers itself as the observer of |tree| and
  // clears it on desctruction. |tree| must be valid for the lifetime
  // of this object.
  explicit AXEventGenerator(AXTree* tree);

  ~AXEventGenerator() override;

  // Clears this class as the observer of the previous tree that was
  // being monitored, if any, and starts monitoring |new_tree|, if not
  // nullptr. Note that |new_tree| must be valid for the lifetime of
  // this object or until you call SetTree again.
  void SetTree(AXTree* new_tree);

  // Nulls-out |tree_| without accessing it or destroying it.
  void ReleaseTree();

  //
  // Methods that make this class behave like an STL container, which simplifies
  // the process of iterating through generated events.
  //

  bool empty() const;
  size_t size() const;
  Iterator begin() const;
  Iterator end() const;

  // Clear any previously added events.
  void ClearEvents();

  // This is called automatically based on changes to the tree observed
  // by AXTreeObserver, but you can also call it directly to add events
  // and retrieve them later.
  //
  // Note that events are organized by node and then by event id to
  // efficiently remove duplicates, so events won't be retrieved in the
  // same order they were added.
  void AddEvent(ui::AXNode* node, Event event);

  void AddEventsForTesting(const AXNode& node,
                           const std::set<EventParams>& events);

 protected:
  // AXTreeObserver overrides.
  void OnIgnoredWillChange(
      AXTree* tree,
      AXNode* node,
      bool is_ignored_new_value,
      bool is_changing_unignored_parents_children) override;
  void OnNodeDataChanged(AXTree* tree,
                         const AXNodeData& old_node_data,
                         const AXNodeData& new_node_data) override;
  void OnRoleChanged(AXTree* tree,
                     AXNode* node,
                     ax::mojom::Role old_role,
                     ax::mojom::Role new_role) override;
  void OnIgnoredChanged(AXTree* tree,
                        AXNode* node,
                        bool is_ignored_new_value) override;
  void OnStateChanged(AXTree* tree,
                      AXNode* node,
                      ax::mojom::State state,
                      bool new_value) override;
  void OnStringAttributeChanged(AXTree* tree,
                                AXNode* node,
                                ax::mojom::StringAttribute attr,
                                const std::string& old_value,
                                const std::string& new_value) override;
  void OnIntAttributeChanged(AXTree* tree,
                             AXNode* node,
                             ax::mojom::IntAttribute attr,
                             int32_t old_value,
                             int32_t new_value) override;
  void OnFloatAttributeChanged(AXTree* tree,
                               AXNode* node,
                               ax::mojom::FloatAttribute attr,
                               float old_value,
                               float new_value) override;
  void OnBoolAttributeChanged(AXTree* tree,
                              AXNode* node,
                              ax::mojom::BoolAttribute attr,
                              bool new_value) override;
  void OnIntListAttributeChanged(
      AXTree* tree,
      AXNode* node,
      ax::mojom::IntListAttribute attr,
      const std::vector<int32_t>& old_value,
      const std::vector<int32_t>& new_value) override;
  void OnTreeDataChanged(AXTree* tree,
                         const ui::AXTreeData& old_data,
                         const ui::AXTreeData& new_data) override;
  void OnSubtreeWillBeDeleted(AXTree* tree, AXNode* node) override;
  void OnNodeWillBeReparented(AXTree* tree, AXNode* node) override;
  void OnSubtreeWillBeReparented(AXTree* tree, AXNode* node) override;
  void OnNodeDeleted(AXTree* tree, AXNodeID node_id) override;
  void OnNodeReparented(AXTree* tree, AXNode* node) override;
  void OnNodeCreated(AXTree* tree, AXNode* node) override;
  void OnAtomicUpdateFinished(AXTree* tree,
                              bool root_changed,
                              const std::vector<Change>& changes) override;

 private:
  static void GetRestrictionStates(ax::mojom::Restriction restriction,
                                   bool* is_enabled,
                                   bool* is_readonly);

  // Returns a vector of values unique to either |lhs| or |rhs|
  static std::vector<int32_t> ComputeIntListDifference(
      const std::vector<int32_t>& lhs,
      const std::vector<int32_t>& rhs);

  // Return true if this node can fire live region events when it's removed.
  bool IsRemovalRelevantInLiveRegion(AXNode* node);

  void FireLiveRegionEvents(AXNode* node, bool is_removal);
  void FireActiveDescendantEvents();
  // If the given target node is inside a text field and the node's modification
  // could affect the field's value, generates an `VALUE_IN_TEXT_FIELD_CHANGED`
  // on the text field that contains the node.
  void FireValueInTextFieldChangedEventIfNecessary(AXTree* tree,
                                                   AXNode* target_node);
  void FireRelationSourceEvents(AXTree* tree, AXNode* target_node);

  // Remove excessive events for a tree update containing node.
  // We remove certain events on a node when it flips its IGNORED state to
  // either show/hide and one of the node's ancestor has also flipped its
  // IGNORED state in the same way (show/hide) in the tree update.
  // |ancestor_has_ignored_map| contains if a node's ancestor has changed to
  // IGNORED state.
  // Map's key is an AXNode.
  // Map's value is a std::bitset containing IgnoredChangedStates(kShow/kHide).
  // - Map's value IgnoredChangedStatesBitset contains kShow if an ancestor
  //   of node removed its IGNORED state.
  // - Map's value IgnoredChangedStatesBitset contains kHide if an ancestor
  //   of node changed to IGNORED state.
  // - When IgnoredChangedStatesBitset is not set, it means neither the
  //   node nor its ancestor has IGNORED_CHANGED.
  void TrimEventsDueToAncestorIgnoredChanged(
      AXNode* node,
      std::map<AXNode*, IgnoredChangedStatesBitset>&
          ancestor_ignored_changed_map);
  void PostprocessEvents();

  raw_ptr<AXTree> tree_ = nullptr;  // Not owned.
  std::map<AXNodeID, std::set<EventParams>> tree_events_;

  // Valid between the call to OnIntAttributeChanged and the call to
  // OnAtomicUpdateFinished. List of nodes whose active descendant changed.
  std::vector<AXNode*> active_descendant_changed_;

  // Keeps track of nodes that have changed their state from ignored to
  // unignored, but which used to be in an invisible subtree. We should not fire
  // `Event::PARENT_CHANGED` on any of their children because they were
  // previously unknown to ATs.
  std::set<AXNodeID> nodes_to_suppress_parent_changed_on_;

  // Please make sure that this ScopedObservation is always declared last in
  // order to prevent any use-after-free.
  base::ScopedObservation<AXTree, AXTreeObserver> tree_event_observation_{this};
};

AX_EXPORT std::ostream& operator<<(std::ostream& os,
                                   AXEventGenerator::Event event);
AX_EXPORT const char* ToString(AXEventGenerator::Event event);

// Parses the attribute and updates |result| and returns true if a match is
// found, or returns false if no match is found.
AX_EXPORT bool MaybeParseGeneratedEvent(const char* attribute,
                                        AXEventGenerator::Event* result);

// Does a NOTREACHED if no match is found.
AX_EXPORT AXEventGenerator::Event ParseGeneratedEvent(const char* attribute);

}  // namespace ui

#endif  // UI_ACCESSIBILITY_AX_EVENT_GENERATOR_H_