/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 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 mozilla_layers_APZInputBridge_h
#define mozilla_layers_APZInputBridge_h

#include "Units.h"                  // for LayoutDeviceIntPoint
#include "mozilla/EventForwards.h"  // for WidgetInputEvent, nsEventStatus
#include "mozilla/layers/APZPublicUtils.h"  // for APZWheelAction, DispatchToContent
#include "mozilla/layers/LayersTypes.h"          // for ScrollDirections
#include "mozilla/layers/ScrollableLayerGuid.h"  // for ScrollableLayerGuid

namespace mozilla {

class InputData;

namespace layers {

class APZInputBridgeParent;
class AsyncPanZoomController;
class InputBlockState;
class TouchBlockState;
struct ScrollableLayerGuid;
struct TargetConfirmationFlags;
struct PointerEventsConsumableFlags;

enum class APZHandledPlace : uint8_t {
  Unhandled = 0,         // we know for sure that the event will not be handled
                         // by either the root APZC or others
  HandledByRoot = 1,     // we know for sure that the event will be handled
                         // by the root content APZC
  HandledByContent = 2,  // we know for sure it will be handled by a non-root
                         // APZC or by an event listener using preventDefault()
                         // in a document
  Invalid = 3,
  Last = Invalid
};

struct APZHandledResult {
  APZHandledPlace mPlace = APZHandledPlace::Invalid;
  SideBits mScrollableDirections = SideBits::eNone;
  ScrollDirections mOverscrollDirections = ScrollDirections();

  APZHandledResult() = default;
  // A constructor for cases where we have the target of the input block this
  // event is part of, the target might be adjusted to be the root in the
  // ScrollingDownWillMoveDynamicToolbar case.
  //
  // NOTE: There's a case where |aTarget| is the APZC for the root content but
  // |aPlace| has to be `HandledByContent`, for example, the root content has
  // an event handler using preventDefault() in the callback, so call sites of
  // this function should be responsible to set a proper |aPlace|.
  APZHandledResult(APZHandledPlace aPlace,
                   const AsyncPanZoomController* aTarget,
                   bool aPopulateDirectionsForUnhandled = false);
  APZHandledResult(APZHandledPlace aPlace, SideBits aScrollableDirections,
                   ScrollDirections aOverscrollDirections)
      : mPlace(aPlace),
        mScrollableDirections(aScrollableDirections),
        mOverscrollDirections(aOverscrollDirections) {}

  bool IsHandledByContent() const {
    return mPlace == APZHandledPlace::HandledByContent;
  }
  bool IsHandledByRoot() const {
    return mPlace == APZHandledPlace::HandledByRoot;
  }
  bool operator==(const APZHandledResult& aOther) const {
    return mPlace == aOther.mPlace &&
           mScrollableDirections == aOther.mScrollableDirections &&
           mOverscrollDirections == aOther.mOverscrollDirections;
  }

  // Compute an initial (optional) APZHandledResult for an event.
  // If aDispatchToContent==false, the result is guaranteed to not be empty.
  static Maybe<APZHandledResult> Initialize(
      const AsyncPanZoomController* aInitialTarget,
      DispatchToContent aDispatchToContent);

  // Update |aHandledResult| with information specific to touch events.
  // If aDispatchToContent==false, the resulting value of |aHandledResult|
  // is guarateed to not be empty.
  static void UpdateForTouchEvent(Maybe<APZHandledResult>& aHandledResult,
                                  const InputBlockState& aBlock,
                                  PointerEventsConsumableFlags aConsumableFlags,
                                  const AsyncPanZoomController* aTarget,
                                  DispatchToContent aDispatchToContent);
};

/**
 * Represents the outcome of APZ receiving and processing an input event.
 * This is returned from APZInputBridge::ReceiveInputEvent() and related APIs.
 */
struct APZEventResult {
  /**
   * Creates a default result with a status of eIgnore, no block ID, and empty
   * target guid.
   */
  APZEventResult();

  /**
   * Creates a result with a status of eIgnore, no block ID, the guid of the
   * given initial target, and an APZHandledResult if we are sure the event
   * is not going to be dispatched to contents.
   */
  APZEventResult(const RefPtr<AsyncPanZoomController>& aInitialTarget,
                 TargetConfirmationFlags aFlags);

  void SetStatusAsConsumeNoDefault() {
    mStatus = nsEventStatus_eConsumeNoDefault;
  }

  void SetStatusAsIgnore() { mStatus = nsEventStatus_eIgnore; }

  // Set mStatus to nsEventStatus_eConsumeDoDefault and set mHandledResult
  // depending on |aTarget|.
  void SetStatusAsConsumeDoDefault(
      const RefPtr<AsyncPanZoomController>& aTarget);

  // Set mStatus to nsEventStatus_eConsumeDoDefault, unlike above
  // SetStatusAsConsumeDoDefault(const RefPtr<AsyncPanZoomController>&) this
  // function doesn't mutate mHandledResult.
  void SetStatusAsConsumeDoDefault() {
    mStatus = nsEventStatus_eConsumeDoDefault;
  }

  // Set mStatus to nsEventStatus_eConsumeDoDefault and set mHandledResult
  // depending on |aBlock|'s target APZC.
  void SetStatusAsConsumeDoDefault(const InputBlockState& aBlock);
  // Set mStatus and mHandledResult for a touch event which is not dropped
  // altogether (i.e. the status is not eConsumeNoDefault).
  void SetStatusForTouchEvent(const InputBlockState& aBlock,
                              TargetConfirmationFlags aFlags,
                              PointerEventsConsumableFlags aConsumableFlags,
                              const AsyncPanZoomController* aTarget);

  // Set mStatus and mHandledResult during in a stat of fast fling.
  void SetStatusForFastFling(const TouchBlockState& aBlock,
                             TargetConfirmationFlags aFlags,
                             PointerEventsConsumableFlags aConsumableFlags,
                             const AsyncPanZoomController* aTarget);

  // DO NOT USE THIS UpdateStatus DIRECTLY. THIS FUNCTION IS ONLY FOR
  // SERIALIZATION / DESERIALIZATION OF THIS STRUCT IN IPC.
  void UpdateStatus(nsEventStatus aStatus) { mStatus = aStatus; }
  nsEventStatus GetStatus() const { return mStatus; };

  // DO NOT USE THIS UpdateHandledResult DIRECTLY. THIS FUNCTION IS ONLY FOR
  // SERIALIZATION / DESERIALIZATION OF THIS STRUCT IN IPC.
  void UpdateHandledResult(const Maybe<APZHandledResult>& aHandledResult) {
    mHandledResult = aHandledResult;
  }
  const Maybe<APZHandledResult>& GetHandledResult() const {
    return mHandledResult;
  }

  bool WillHaveDelayedResult() const {
    return GetStatus() != nsEventStatus_eConsumeNoDefault &&
           !GetHandledResult();
  }

 private:
  /**
   * A status flag indicated how APZ handled the event.
   * The interpretation of each value is as follows:
   *
   * nsEventStatus_eConsumeNoDefault is returned to indicate the
   *   APZ is consuming this event and the caller should discard the event with
   *   extreme prejudice. The exact scenarios under which this is returned is
   *   implementation-dependent and may vary.
   * nsEventStatus_eIgnore is returned to indicate that the APZ code didn't
   *   use this event. This might be because it was directed at a point on
   *   the screen where there was no APZ, or because the thing the user was
   *   trying to do was not allowed. (For example, attempting to pan a
   *   non-pannable document).
   * nsEventStatus_eConsumeDoDefault is returned to indicate that the APZ
   *   code may have used this event to do some user-visible thing. Note that
   *   in some cases CONSUMED is returned even if the event was NOT used. This
   *   is because we cannot always know at the time of event delivery whether
   *   the event will be used or not. So we err on the side of sending
   *   CONSUMED when we are uncertain.
   */
  nsEventStatus mStatus;

  /**
   * This is:
   *  - set to HandledByRoot if we know for sure that the event will be handled
   *    by the root content APZC;
   *  - set to HandledByContent if we know for sure it will not be;
   *  - left empty if we are unsure.
   */
  Maybe<APZHandledResult> mHandledResult;

 public:
  /**
   * The guid of the APZC initially targeted by this event.
   * This will usually be the APZC that handles the event, but in cases
   * where the event is dispatched to content, it may end up being
   * handled by a different APZC.
   */
  ScrollableLayerGuid mTargetGuid;
  /**
   * If this event started or was added to an input block, the id of that
   * input block, otherwise InputBlockState::NO_BLOCK_ID.
   */
  uint64_t mInputBlockId;
};

/**
 * This class lives in the main process, and is accessed via the controller
 * thread (which is the process main thread for desktop, and the Java UI
 * thread for Android). This class exposes a synchronous API to deliver
 * incoming input events to APZ and modify them in-place to unapply the APZ
 * async transform. If there is a GPU process, then this class does sync IPC
 * calls over to the GPU process in order to accomplish this. Otherwise,
 * APZCTreeManager overrides and implements these methods directly.
 */
class APZInputBridge {
 public:
  using InputBlockCallback = std::function<void(
      uint64_t aInputBlockId, const APZHandledResult& aHandledResult)>;

  /**
   * General handler for incoming input events. Manipulates the frame metrics
   * based on what type of input it is. For example, a PinchGestureEvent will
   * cause scaling. This should only be called externally to this class, and
   * must be called on the controller thread.
   *
   * This function transforms |aEvent| to have its coordinates in DOM space.
   * This is so that the event can be passed through the DOM and content can
   * handle them. The event may need to be converted to a WidgetInputEvent
   * by the caller if it wants to do this.
   *
   * @param aEvent input event object; is modified in-place
   * @param aCallback an optional callback to be invoked when the input block is
   * ready for handling,
   * @return The result of processing the event. Refer to the documentation of
   * APZEventResult and its field.
   */
  virtual APZEventResult ReceiveInputEvent(
      InputData& aEvent,
      InputBlockCallback&& aCallback = InputBlockCallback()) = 0;

  /**
   * WidgetInputEvent handler. Transforms |aEvent| (which is assumed to be an
   * already-existing instance of an WidgetInputEvent which may be an
   * WidgetTouchEvent) to have its coordinates in DOM space. This is so that the
   * event can be passed through the DOM and content can handle them.
   *
   * NOTE: Be careful of invoking the WidgetInputEvent variant. This can only be
   * called on the main thread. See widget/InputData.h for more information on
   * why we have InputData and WidgetInputEvent separated. If this function is
   * used, the controller thread must be the main thread, or undefined behaviour
   * may occur.
   * NOTE: On unix, mouse events are treated as touch and are forwarded
   * to the appropriate apz as such.
   *
   * See documentation for other ReceiveInputEvent above.
   */
  APZEventResult ReceiveInputEvent(
      WidgetInputEvent& aEvent,
      InputBlockCallback&& aCallback = InputBlockCallback());

  // Returns the kind of wheel event action, if any, that will be (or was)
  // performed by APZ. If this returns true, the event must not perform a
  // synchronous scroll.
  //
  // Even if this returns Nothing(), all wheel events in APZ-aware widgets must
  // be sent through APZ so they are transformed correctly for BrowserParent.
  static Maybe<APZWheelAction> ActionForWheelEvent(WidgetWheelEvent* aEvent);

 protected:
  friend class APZInputBridgeParent;

  // Methods to help process WidgetInputEvents (or manage conversion to/from
  // InputData)

  virtual void ProcessUnhandledEvent(LayoutDeviceIntPoint* aRefPoint,
                                     ScrollableLayerGuid* aOutTargetGuid,
                                     uint64_t* aOutFocusSequenceNumber,
                                     LayersId* aOutLayersId) = 0;

  virtual void UpdateWheelTransaction(
      LayoutDeviceIntPoint aRefPoint, EventMessage aEventMessage,
      const Maybe<ScrollableLayerGuid>& aTargetGuid) = 0;

  virtual ~APZInputBridge() = default;
};

std::ostream& operator<<(std::ostream& aOut,
                         const APZHandledResult& aHandledResult);

// This enum class is used for communicating between APZ and the browser gesture
// support code. APZ needs to wait for the browser to send this response just
// like APZ waits for the content's response if there's an APZ ware event
// listener in the content process.
enum class BrowserGestureResponse : bool {
  NotConsumed = 0,  // Representing the browser doesn't consume the gesture
  Consumed = 1,  // Representing the browser has started consuming the gesture.
};

}  // namespace layers
}  // namespace mozilla

#endif  // mozilla_layers_APZInputBridge_h
