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

// This file is for APZ-related utilities that are used by code in gfx/layers
// only. For APZ-related utilities used by the Rest of the World (widget/,
// layout/, dom/, IPDL protocols, etc.), use APZPublicUtils.h.
// Do not include this header from source files outside of gfx/layers.

#include <stdint.h>  // for uint32_t
#include "gfxTypes.h"
#include "FrameMetrics.h"
#include "LayersTypes.h"
#include "UnitTransforms.h"
#include "mozilla/gfx/CompositorHitTestInfo.h"
#include "mozilla/gfx/Point.h"
#include "mozilla/layers/APZPublicUtils.h"  // for DispatchToContent
#include "mozilla/EnumSet.h"
#include "mozilla/FloatingPoint.h"

namespace mozilla {

namespace layers {

enum CancelAnimationFlags : uint32_t {
  Default = 0,                    /* Cancel all animations */
  ExcludeOverscroll = (1 << 0),   /* Don't clear overscroll */
  ScrollSnap = (1 << 1),          /* Snap to snap points */
  TriggeredExternally = (1 << 2), /* Cancellation was not triggered by APZ in
                                     response to an input event */
  ExcludeAutoscroll = (1 << 3)    /* Don't cancel overscroll animations */
};

inline CancelAnimationFlags operator|(CancelAnimationFlags a,
                                      CancelAnimationFlags b) {
  return static_cast<CancelAnimationFlags>(static_cast<int>(a) |
                                           static_cast<int>(b));
}

// clang-format off
enum class ScrollSource {
  // Touch-screen.
  Touchscreen,

  // Touchpad with gesture support.
  Touchpad,

  // Mouse wheel.
  Wheel,

  // Keyboard
  Keyboard,
};
// clang-format on

inline bool ScrollSourceRespectsDisregardedDirections(ScrollSource aSource) {
  return aSource == ScrollSource::Wheel || aSource == ScrollSource::Touchpad;
}

inline bool ScrollSourceAllowsOverscroll(ScrollSource aSource) {
  return aSource == ScrollSource::Touchpad ||
         aSource == ScrollSource::Touchscreen;
}

// Epsilon to be used when comparing 'float' coordinate values
// with FuzzyEqualsAdditive. The rationale is that 'float' has 7 decimal
// digits of precision, and coordinate values should be no larger than in the
// ten thousands. Note also that the smallest legitimate difference in page
// coordinates is 1 app unit, which is 1/60 of a (CSS pixel), so this epsilon
// isn't too large.
const CSSCoord COORDINATE_EPSILON = 0.01f;

inline bool IsZero(const CSSPoint& aPoint) {
  return FuzzyEqualsAdditive(aPoint.x, CSSCoord(), COORDINATE_EPSILON) &&
         FuzzyEqualsAdditive(aPoint.y, CSSCoord(), COORDINATE_EPSILON);
}

// Represents async transforms consisting of a scale and a translation.
struct AsyncTransform {
  explicit AsyncTransform(
      LayerToParentLayerScale aScale = LayerToParentLayerScale(),
      ParentLayerPoint aTranslation = ParentLayerPoint())
      : mScale(aScale), mTranslation(aTranslation) {}

  operator AsyncTransformComponentMatrix() const {
    return AsyncTransformComponentMatrix::Scaling(mScale.scale, mScale.scale, 1)
        .PostTranslate(mTranslation.x, mTranslation.y, 0);
  }

  bool operator==(const AsyncTransform& rhs) const {
    return mTranslation == rhs.mTranslation && mScale == rhs.mScale;
  }

  bool operator!=(const AsyncTransform& rhs) const { return !(*this == rhs); }

  LayerToParentLayerScale mScale;
  ParentLayerPoint mTranslation;
};

// Deem an AsyncTransformComponentMatrix (obtained by multiplying together
// one or more AsyncTransformComponentMatrix objects) as constituting a
// complete async transform.
inline AsyncTransformMatrix CompleteAsyncTransform(
    const AsyncTransformComponentMatrix& aMatrix) {
  return ViewAs<AsyncTransformMatrix>(
      aMatrix, PixelCastJustification::MultipleAsyncTransforms);
}

enum class FastPathApzAwareListener : bool { No, Yes };

struct TargetConfirmationFlags final {
  explicit TargetConfirmationFlags(bool aTargetConfirmed)
      : mTargetConfirmed(aTargetConfirmed),
        mRequiresTargetConfirmation(false),
        mHitScrollbar(false),
        mHitScrollThumb(false),
        mDispatchToContent(false),
        mFastPathApzAwareListener(false) {}

  explicit TargetConfirmationFlags(
      const gfx::CompositorHitTestInfo& aHitTestInfo,
      FastPathApzAwareListener aFastPathApzAwareListener =
          FastPathApzAwareListener::No)
      : mTargetConfirmed(
            (aHitTestInfo != gfx::CompositorHitTestInvisibleToHit) &&
            (aHitTestInfo & gfx::CompositorHitTestDispatchToContent).isEmpty()),
        mRequiresTargetConfirmation(aHitTestInfo.contains(
            gfx::CompositorHitTestFlags::eRequiresTargetConfirmation)),
        mHitScrollbar(
            aHitTestInfo.contains(gfx::CompositorHitTestFlags::eScrollbar)),
        mHitScrollThumb(aHitTestInfo.contains(
            gfx::CompositorHitTestFlags::eScrollbarThumb)),
        mDispatchToContent(
            !(aHitTestInfo & gfx::CompositorHitTestDispatchToContent)
                 .isEmpty()),
        mFastPathApzAwareListener(aFastPathApzAwareListener ==
                                  FastPathApzAwareListener::Yes) {}

  DispatchToContent NeedDispatchToContent() const {
    return mDispatchToContent ? DispatchToContent::Yes : DispatchToContent::No;
  }

  bool IsFastPathApzAwareDispatchToContent() const {
    return mDispatchToContent && mFastPathApzAwareListener;
  }

  bool mTargetConfirmed : 1;
  bool mRequiresTargetConfirmation : 1;
  bool mHitScrollbar : 1;
  bool mHitScrollThumb : 1;
  bool mDispatchToContent : 1;
  bool mFastPathApzAwareListener : 1;
};

enum class AsyncTransformComponent { eLayout, eVisual };

using AsyncTransformComponents = EnumSet<AsyncTransformComponent>;

constexpr AsyncTransformComponents LayoutAndVisual(
    AsyncTransformComponent::eLayout, AsyncTransformComponent::eVisual);

/**
 * Allows consumers of async transforms to specify for what purpose they are
 * using the async transform:
 *
 *   |eForEventHandling| is intended for event handling and other uses that
 *                       need the most up-to-date transform, reflecting all
 *                       events that have been processed so far, even if the
 *                       transform is not yet reflected visually.
 *   |eForCompositing| is intended for the transform that should be reflected
 *                     visually.
 *
 * For example, if an APZC has metrics with the mForceDisableApz flag set,
 * then the |eForCompositing| async transform will be empty, while the
 * |eForEventHandling| async transform will reflect processed input events
 * regardless of mForceDisableApz.
 */
enum class AsyncTransformConsumer {
  eForEventHandling,
  eForCompositing,
};

/**
 * A flag type for use by functions which return information about
 * handoff, in case they need to differentiate between handoff for
 * the purpose of scrolling and handoff for the purpose of pull-to-refresh.
 */
enum class HandoffConsumer { Scrolling, PullToRefresh };

namespace apz {

/**
 * Is aVector within the given threshold of the horizontal axis?
 * Returns false if aVector is a zero vector.
 * @param aThreshold an angle in radians in the range [0, pi/2]
 */
inline bool IsCloseToHorizontal(const ParentLayerPoint& aVector,
                                float aThreshold) {
  if (aVector == ParentLayerPoint()) {
    return false;
  }
  float angle = float(fabs(atan2(aVector.y, aVector.x)));
  return angle < aThreshold || angle > (M_PI - aThreshold);
}

/**
 * Is aVector within the given threshold of the vertical axis?
 * Returns false if aVector is a zero vector.
 * @param aThreshold an angle in radians in the range [0, pi/2]
 */
inline bool IsCloseToVertical(const ParentLayerPoint& aVector,
                              float aThreshold) {
  if (aVector == ParentLayerPoint()) {
    return false;
  }
  float angle = float(fabs(atan2(aVector.y, aVector.x)));
  return fabs(angle - float(M_PI / 2)) < aThreshold;
}

// Returns true if a sticky layer with async translation |aTranslation| is
// stuck with a bottom margin. The inner/outer ranges are produced by the main
// thread at the last paint, and so |aTranslation| only needs to be the
// async translation from the last paint.
bool IsStuckAtBottom(gfxFloat aTranslation,
                     const LayerRectAbsolute& aInnerRange,
                     const LayerRectAbsolute& aOuterRange);

// Returns true if a sticky layer with async translation |aTranslation| is
// stuck with a top margin.
bool IsStuckAtTop(gfxFloat aTranslation, const LayerRectAbsolute& aInnerRange,
                  const LayerRectAbsolute& aOuterRange);

/**
 * Takes the visible rect from the compositor metrics, adds a pref-based
 * margin around it, and checks to see if it is contained inside the painted
 * rect from the painted metrics. Returns true if it is contained, or false
 * if not. Returning false means that a (relatively) small amount of async
 * scrolling/zooming can result in the visible area going outside the painted
 * area and resulting in visual checkerboarding.
 * Note that this may return false positives for cases where the scrollframe
 * in question is nested inside other scrollframes, as the composition bounds
 * used to determine the visible rect may in fact be clipped by enclosing
 * scrollframes, but that is not accounted for in this function.
 */
bool AboutToCheckerboard(const FrameMetrics& aPaintedMetrics,
                         const FrameMetrics& aCompositorMetrics);

/**
 * Returns SideBits where the given |aOverscrollAmount| overscrolls.
 */
SideBits GetOverscrollSideBits(const ParentLayerPoint& aOverscrollAmount);

// Represents tri-state when a touch-end event received.
enum class SingleTapState : uint8_t {
  NotClick,          // The touch-block doesn't trigger a click event
  WasClick,          // The touch-block did trigger a click event
  NotYetDetermined,  // It's not yet determined whether the touch-block trigger
                     // a click event or not since double-tapping might happen
};

}  // namespace apz

}  // namespace layers
}  // namespace mozilla

#endif  // mozilla_layers_APZUtils_h
