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

#include "mozilla/a11y/DocAccessible.h"
#include "mozilla/a11y/PDocAccessibleChild.h"
#include "nsISupportsImpl.h"

namespace mozilla {
namespace a11y {

class LocalAccessible;
class AccShowEvent;

/**
 * These objects handle content side communication for an accessible document,
 * and their lifetime is the same as the document they represent.
 */
class DocAccessibleChild : public PDocAccessibleChild {
 public:
  DocAccessibleChild(DocAccessible* aDoc,
                     mozilla::ipc::IRefCountedProtocol* aManager)
      : mDoc(aDoc) {
    MOZ_COUNT_CTOR(DocAccessibleChild);
    SetManager(aManager);
  }

  ~DocAccessibleChild() {
    // Shutdown() should have been called, but maybe it isn't if the process is
    // killed?
    MOZ_ASSERT(!mDoc);
    if (mDoc) {
      mDoc->SetIPCDoc(nullptr);
    }

    MOZ_COUNT_DTOR(DocAccessibleChild);
  }

  void Shutdown() {
    DetachDocument();
    SendShutdown();
  }

  /**
   * Serializes a shown tree and pushes the show event data to the mutation
   * event queue with PushMutationEventData. This function may push multiple
   * show events depending on the size of the flattened tree.
   */
  void InsertIntoIpcTree(LocalAccessible* aChild, bool aSuppressShowEvent);
  void ShowEvent(AccShowEvent* aShowEvent);

  /**
   * Append the mutation event to mutation event queue, potentially creating a
   * new batch of mutation events. This function may send queued mutation events
   * if the number of batches meets or exceeds a set limit.
   */
  void PushMutationEventData(MutationEventData aData, uint32_t aAccCount = 1);
  void SendQueuedMutationEvents();
  size_t MutationEventQueueLength() const;

  bool HasUnackedMutationEvents() const { return mHasUnackedMutationEvents; }

  virtual void ActorDestroy(ActorDestroyReason) override {
    if (!mDoc) {
      return;
    }

    mDoc->SetIPCDoc(nullptr);
    mDoc = nullptr;
  }

  mozilla::ipc::IPCResult RecvTakeFocus(const uint64_t& aID);

  MOZ_CAN_RUN_SCRIPT_BOUNDARY
  mozilla::ipc::IPCResult RecvScrollTo(const uint64_t& aID,
                                       const uint32_t& aScrollType);

  mozilla::ipc::IPCResult RecvTakeSelection(const uint64_t& aID);
  mozilla::ipc::IPCResult RecvSetSelected(const uint64_t& aID,
                                          const bool& aSelect);

  mozilla::ipc::IPCResult RecvVerifyCache(const uint64_t& aID,
                                          const uint64_t& aCacheDomain,
                                          AccAttributes* aFields);

  mozilla::ipc::IPCResult RecvDoActionAsync(const uint64_t& aID,
                                            const uint8_t& aIndex);

  MOZ_CAN_RUN_SCRIPT_BOUNDARY
  mozilla::ipc::IPCResult RecvSetTextSelection(const uint64_t& aStartID,
                                               const int32_t& aStartOffset,
                                               const uint64_t& aEndID,
                                               const int32_t& aEndOffset,
                                               const int32_t& aSelectionNum,
                                               const bool& aSetFocus);

  MOZ_CAN_RUN_SCRIPT_BOUNDARY
  mozilla::ipc::IPCResult RecvScrollTextLeafRangeIntoView(
      const uint64_t& aStartID, const int32_t& aStartOffset,
      const uint64_t& aEndID, const int32_t& aEndOffset,
      const uint32_t& aScrollType);

  mozilla::ipc::IPCResult RecvRemoveTextSelection(const uint64_t& aID,
                                                  const int32_t& aSelectionNum);

  mozilla::ipc::IPCResult RecvSetCurValue(const uint64_t& aID,
                                          const double& aValue);

  mozilla::ipc::IPCResult RecvReplaceText(const uint64_t& aID,
                                          const nsAString& aText);

  mozilla::ipc::IPCResult RecvInsertText(const uint64_t& aID,
                                         const nsAString& aText,
                                         const int32_t& aPosition);

  mozilla::ipc::IPCResult RecvCopyText(const uint64_t& aID,
                                       const int32_t& aStartPos,
                                       const int32_t& aEndPos);

  mozilla::ipc::IPCResult RecvCutText(const uint64_t& aID,
                                      const int32_t& aStartPos,
                                      const int32_t& aEndPos);

  mozilla::ipc::IPCResult RecvDeleteText(const uint64_t& aID,
                                         const int32_t& aStartPos,
                                         const int32_t& aEndPos);

  MOZ_CAN_RUN_SCRIPT_BOUNDARY
  mozilla::ipc::IPCResult RecvPasteText(const uint64_t& aID,
                                        const int32_t& aPosition);

  mozilla::ipc::IPCResult RecvRestoreFocus();

  mozilla::ipc::IPCResult RecvScrollToPoint(const uint64_t& aID,
                                            const uint32_t& aScrollType,
                                            const int32_t& aX,
                                            const int32_t& aY);

  mozilla::ipc::IPCResult RecvScrollSubstringToPoint(
      const uint64_t& aID, const int32_t& aStartOffset,
      const int32_t& aEndOffset, const uint32_t& aCoordinateType,
      const int32_t& aX, const int32_t& aY);

  mozilla::ipc::IPCResult RecvAckMutationEvents();

  /**
   * Get the caret rect suitable to be sent via IPC. This is used with
   * SendCaretMoveEvent and SendFocusEvent.
   */
  static mozilla::LayoutDeviceIntRect GetCaretRectForIPCEvent(
      LocalAccessible* aAcc);

 private:
  // Set to true if we have sent mutation events that have not yet been
  // acknowledged by the parent process. We only request and receive one ACK per
  // tick, regardless of how many mutation events we send. Additional ticks
  // cannot occur (and thus additional mutation events cannot be sent) before we
  // receive this ACK.
  bool mHasUnackedMutationEvents = false;

 protected:
  static void FlattenTree(LocalAccessible* aRoot,
                          nsTArray<LocalAccessible*>& aTree);

  static AccessibleData SerializeAcc(LocalAccessible* aAcc);

  void DetachDocument() {
    if (mDoc) {
      mDoc->SetIPCDoc(nullptr);
      mDoc = nullptr;
    }
  }

  LocalAccessible* IdToAccessible(const uint64_t& aID) const;
  HyperTextAccessible* IdToHyperTextAccessible(const uint64_t& aID) const;

  DocAccessible* mDoc;

  // Utility structure that encapsulates mutation event batching.
  struct MutationEventBatcher {
    void PushMutationEventData(MutationEventData aData, uint32_t aAccCount,
                               DocAccessibleChild& aDocAcc);
    void SendQueuedMutationEvents(DocAccessibleChild& aDocAcc);
    uint32_t AccCount() const { return mAccCount; }
    size_t EventCount() const { return mMutationEventData.Length(); }

   private:
    // A batch of mutation events to be sent in one IPC message.
    nsTArray<MutationEventData> mMutationEventData;

    // The number of accessibles in the mutation event data batch. A show event
    // may have many accessibles shown, where each accessible in the show event
    // counts separately here. Every other mutation event adds one to this
    // count.
    uint32_t mAccCount = 0;
  };
  MutationEventBatcher mMutationEventBatcher;

  friend void DocAccessible::DoInitialUpdate();
};

}  // namespace a11y
}  // namespace mozilla

#endif  // mozilla_a11y_DocAccessibleChild_h
