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

#include "FrameMetrics.h"
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/layers/WebRenderBridgeParent.h"
#include "mozilla/layers/WebRenderScrollData.h"

namespace mozilla {
namespace layers {

/*
 * This class is a wrapper to walk through a WebRenderScrollData object, with
 * an exposed API that is template-compatible to LayerMetricsWrapper. This allows
 * APZ to walk through both layer trees and WebRender scroll metadata structures
 * without a lot of code duplication.
 * (Note that not all functions from LayerMetricsWrapper are implemented here,
 * only the ones we've needed in APZ code so far.)
 *
 * A WebRenderScrollData object is basically a flattened layer tree, with a
 * number of WebRenderLayerScrollData objects that have a 1:1 correspondence
 * to layers in a layer tree. Therefore the mLayer pointer in this class can
 * be considered equivalent to the mLayer pointer in the LayerMetricsWrapper.
 * There are some extra fields (mData, mLayerIndex, mContainingSubtreeLastIndex)
 * to move around between these "layers" given the flattened representation.
 * The mMetadataIndex field in this class corresponds to the mIndex field in
 * LayerMetricsWrapper, as both classes also need to manage walking through
 * "virtual" container layers implied by the list of ScrollMetadata objects.
 *
 * One important note here is that this class holds a pointer to the "owning"
 * WebRenderScrollData. The caller must ensure that this class does not outlive
 * the owning WebRenderScrollData, or this may result in use-after-free errors.
 * This class being declared a MOZ_STACK_CLASS should help with that.
 *
 * Refer to LayerMetricsWrapper.h for actual documentation on the exposed API.
 */
class MOZ_STACK_CLASS WebRenderScrollDataWrapper {
public:
  // Basic constructor for external callers. Starts the walker at the root of
  // the tree.
  explicit WebRenderScrollDataWrapper(const WebRenderScrollData* aData = nullptr)
    : mData(aData)
    , mLayerIndex(0)
    , mContainingSubtreeLastIndex(0)
    , mLayer(nullptr)
    , mMetadataIndex(0)
  {
    if (!mData) {
      return;
    }
    mLayer = mData->GetLayerData(mLayerIndex);
    if (!mLayer) {
      return;
    }

    // sanity check on the data
    MOZ_ASSERT(mData->GetLayerCount() == (size_t)(1 + mLayer->GetDescendantCount()));
    mContainingSubtreeLastIndex = mData->GetLayerCount();

    // See documentation in LayerMetricsWrapper.h about this. mMetadataIndex
    // in this class is equivalent to mIndex in that class.
    mMetadataIndex = mLayer->GetScrollMetadataCount();
    if (mMetadataIndex > 0) {
      mMetadataIndex--;
    }
  }

private:
  // Internal constructor for walking from one WebRenderLayerScrollData to
  // another. In this case we need to recompute the mMetadataIndex to be the
  // "topmost" scroll metadata on the new layer.
  WebRenderScrollDataWrapper(const WebRenderScrollData* aData,
                             size_t aLayerIndex,
                             size_t aContainingSubtreeLastIndex)
    : mData(aData)
    , mLayerIndex(aLayerIndex)
    , mContainingSubtreeLastIndex(aContainingSubtreeLastIndex)
    , mLayer(nullptr)
    , mMetadataIndex(0)
  {
    MOZ_ASSERT(mData);
    mLayer = mData->GetLayerData(mLayerIndex);
    MOZ_ASSERT(mLayer);

    // See documentation in LayerMetricsWrapper.h about this. mMetadataIndex
    // in this class is equivalent to mIndex in that class.
    mMetadataIndex = mLayer->GetScrollMetadataCount();
    if (mMetadataIndex > 0) {
      mMetadataIndex--;
    }
  }

  // Internal constructor for walking from one metadata to another metadata on
  // the same WebRenderLayerScrollData.
  WebRenderScrollDataWrapper(const WebRenderScrollData* aData,
                             size_t aLayerIndex,
                             size_t aContainingSubtreeLastIndex,
                             const WebRenderLayerScrollData* aLayer,
                             uint32_t aMetadataIndex)
    : mData(aData)
    , mLayerIndex(aLayerIndex)
    , mContainingSubtreeLastIndex(aContainingSubtreeLastIndex)
    , mLayer(aLayer)
    , mMetadataIndex(aMetadataIndex)
  {
    MOZ_ASSERT(mData);
    MOZ_ASSERT(mLayer);
    MOZ_ASSERT(mLayer == mData->GetLayerData(mLayerIndex));
    MOZ_ASSERT(mMetadataIndex == 0 || mMetadataIndex < mLayer->GetScrollMetadataCount());
  }

public:
  bool IsValid() const
  {
    return mLayer != nullptr;
  }

  explicit operator bool() const
  {
    return IsValid();
  }

  WebRenderScrollDataWrapper GetLastChild() const
  {
    MOZ_ASSERT(IsValid());

    if (!AtBottomLayer()) {
      // If we're still walking around in the virtual container layers created
      // by the ScrollMetadata array, we just need to update the metadata index
      // and that's it.
      return WebRenderScrollDataWrapper(mData, mLayerIndex,
          mContainingSubtreeLastIndex, mLayer, mMetadataIndex - 1);
    }

    // Otherwise, we need to walk to a different WebRenderLayerScrollData in
    // mData.

    // Since mData contains the layer in depth-first, last-to-first order,
    // the index after mLayerIndex must be mLayerIndex's last child, if it
    // has any children (indicated by GetDescendantCount() > 0). Furthermore
    // we compute the first index outside the subtree rooted at this node
    // (in |subtreeLastIndex|) and pass that in to the child wrapper to use as
    // its mContainingSubtreeLastIndex.
    if (mLayer->GetDescendantCount() > 0) {
      size_t prevSiblingIndex = mLayerIndex + 1 + mLayer->GetDescendantCount();
      size_t subtreeLastIndex = std::min(mContainingSubtreeLastIndex, prevSiblingIndex);
      return WebRenderScrollDataWrapper(mData, mLayerIndex + 1, subtreeLastIndex);
    }

    // We've run out of descendants. But! If the original layer was a RefLayer,
    // then it connects to another layer tree and we need to traverse that too.
    // So return a WebRenderScrollDataWrapper for the root of the child layer
    // tree.
    if (mLayer->GetReferentId()) {
      CompositorBridgeParent::LayerTreeState* lts =
          CompositorBridgeParent::GetIndirectShadowTree(mLayer->GetReferentId().value());
      if (lts && lts->mWrBridge) {
        return WebRenderScrollDataWrapper(&(lts->mWrBridge->GetScrollData()));
      }
    }

    return WebRenderScrollDataWrapper();
  }

  WebRenderScrollDataWrapper GetPrevSibling() const
  {
    MOZ_ASSERT(IsValid());

    if (!AtTopLayer()) {
      // The virtual container layers don't have siblings
      return WebRenderScrollDataWrapper();
    }

    // Skip past the descendants to get to the previous sibling. However, we
    // might be at the last sibling already.
    size_t prevSiblingIndex = mLayerIndex + 1 + mLayer->GetDescendantCount();
    if (prevSiblingIndex < mContainingSubtreeLastIndex) {
      return WebRenderScrollDataWrapper(mData, prevSiblingIndex, mContainingSubtreeLastIndex);
    }
    return WebRenderScrollDataWrapper();
  }

  const ScrollMetadata& Metadata() const
  {
    MOZ_ASSERT(IsValid());

    if (mMetadataIndex >= mLayer->GetScrollMetadataCount()) {
      return *ScrollMetadata::sNullMetadata;
    }
    return mLayer->GetScrollMetadata(*mData, mMetadataIndex);
  }

  const FrameMetrics& Metrics() const
  {
    return Metadata().GetMetrics();
  }

  AsyncPanZoomController* GetApzc() const
  {
    return nullptr;
  }

  void SetApzc(AsyncPanZoomController* aApzc) const
  {
  }

  const char* Name() const
  {
    return "WebRenderScrollDataWrapper";
  }

  gfx::Matrix4x4 GetTransform() const
  {
    MOZ_ASSERT(IsValid());

    if (AtBottomLayer()) {
      return mLayer->GetTransform();
    }
    return gfx::Matrix4x4();
  }

  CSSTransformMatrix GetTransformTyped() const
  {
    return ViewAs<CSSTransformMatrix>(GetTransform());
  }

  bool TransformIsPerspective() const
  {
    MOZ_ASSERT(IsValid());

    if (AtBottomLayer()) {
      return mLayer->GetTransformIsPerspective();
    }
    return false;
  }

  EventRegions GetEventRegions() const
  {
    MOZ_ASSERT(IsValid());

    if (AtBottomLayer()) {
      return mLayer->GetEventRegions();
    }
    return EventRegions();
  }

  LayerIntRegion GetVisibleRegion() const
  {
    MOZ_ASSERT(IsValid());

    if (AtBottomLayer()) {
      return mLayer->GetVisibleRegion();
    }

    return ViewAs<LayerPixel>(
        TransformBy(mLayer->GetTransformTyped(), mLayer->GetVisibleRegion()),
        PixelCastJustification::MovingDownToChildren);
  }

  Maybe<uint64_t> GetReferentId() const
  {
    MOZ_ASSERT(IsValid());

    if (AtBottomLayer()) {
      return mLayer->GetReferentId();
    }
    return Nothing();
  }

  Maybe<ParentLayerIntRect> GetClipRect() const
  {
    // TODO
    return Nothing();
  }

  EventRegionsOverride GetEventRegionsOverride() const
  {
    MOZ_ASSERT(IsValid());
    return mLayer->GetEventRegionsOverride();
  }

  const ScrollThumbData& GetScrollThumbData() const
  {
    MOZ_ASSERT(IsValid());
    return mLayer->GetScrollThumbData();
  }

  uint64_t GetScrollbarAnimationId() const
  {
    MOZ_ASSERT(IsValid());
    return mLayer->GetScrollbarAnimationId();
  }

  FrameMetrics::ViewID GetScrollbarTargetContainerId() const
  {
    MOZ_ASSERT(IsValid());
    return mLayer->GetScrollbarTargetContainerId();
  }

  Maybe<ScrollDirection> GetScrollbarContainerDirection() const
  {
    MOZ_ASSERT(IsValid());
    return mLayer->GetScrollbarContainerDirection();
  }

  FrameMetrics::ViewID GetFixedPositionScrollContainerId() const
  {
    MOZ_ASSERT(IsValid());
    return mLayer->GetFixedPositionScrollContainerId();
  }

  const void* GetLayer() const
  {
    MOZ_ASSERT(IsValid());
    return mLayer;
  }

private:
  bool AtBottomLayer() const
  {
    return mMetadataIndex == 0;
  }

  bool AtTopLayer() const
  {
    return mLayer->GetScrollMetadataCount() == 0 || mMetadataIndex == mLayer->GetScrollMetadataCount() - 1;
  }

private:
  const WebRenderScrollData* mData;
  // The index (in mData->mLayerScrollData) of the WebRenderLayerScrollData this
  // wrapper is pointing to.
  size_t mLayerIndex;
  // The upper bound on the set of valid indices inside the subtree rooted at
  // the parent of this "layer". That is, any layer index |i| in the range
  // mLayerIndex <= i < mContainingSubtreeLastIndex is guaranteed to point to
  // a layer that is a descendant of "parent", where "parent" is the parent
  // layer of the layer at mLayerIndex. This is needed in order to implement
  // GetPrevSibling() correctly.
  size_t mContainingSubtreeLastIndex;
  // The WebRenderLayerScrollData this wrapper is pointing to.
  const WebRenderLayerScrollData* mLayer;
  // The index of the scroll metadata within mLayer that this wrapper is
  // pointing to.
  uint32_t mMetadataIndex;
};

} // namespace layers
} // namespace mozilla

#endif /* GFX_WEBRENDERSCROLLDATAWRAPPER_H */
