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

/**
 * Defines a test fixture used for testing multiple APZCs interacting in
 * an APZCTreeManager.
 */

#include "APZTestAccess.h"
#include "APZTestCommon.h"
#include "gfxPlatform.h"
#include "MockHitTester.h"
#include "apz/src/WRHitTester.h"

#include "mozilla/layers/APZSampler.h"
#include "mozilla/layers/APZUpdater.h"
#include "mozilla/layers/WebRenderScrollDataWrapper.h"

class APZCTreeManagerTester : public APZCTesterBase {
 protected:
  APZCTreeManagerTester() : mHitTester(MakeUnique<WRHitTester>()) {}

  virtual void SetUp() {
    APZCTesterBase::SetUp();

    APZThreadUtils::SetThreadAssertionsEnabled(false);
    APZThreadUtils::SetControllerThread(NS_GetCurrentThread());

    manager = new TestAPZCTreeManager(mcc, std::move(mHitTester));
    updater = new APZUpdater(manager, false);
    sampler = new APZSampler(manager, false);
  }

  virtual void TearDown() {
    while (mcc->RunThroughDelayedTasks());
    manager->ClearTree();
    manager->ClearContentController();

    APZCTesterBase::TearDown();
  }

  /**
   * Sample animations once for all APZCs, 1 ms later than the last sample and
   * return whether there is still any active animations or not.
   */
  bool SampleAnimationsOnce() {
    const TimeDuration increment = TimeDuration::FromMilliseconds(1);
    ParentLayerPoint pointOut;
    AsyncTransform viewTransformOut;
    mcc->AdvanceBy(increment);

    bool activeAnimations = false;

    for (size_t i = 0; i < layers.GetLayerCount(); ++i) {
      if (TestAsyncPanZoomController* apzc = ApzcOf(layers[i])) {
        activeAnimations |=
            apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
      }
    }

    return activeAnimations;
  }

  // A convenience function for letting a test modify the frame metrics
  // stored on a particular layer.
  template <typename Callback>
  void ModifyFrameMetrics(WebRenderLayerScrollData* aLayer,
                          Callback aCallback) {
    MOZ_ASSERT(aLayer->GetScrollMetadataCount() == 1);
    ScrollMetadata& metadataRef =
        APZTestAccess::GetScrollMetadataMut(*aLayer, layers, 0);
    aCallback(metadataRef, metadataRef.GetMetrics());
  }

  // A convenience wrapper for manager->UpdateHitTestingTree().
  void UpdateHitTestingTree(uint32_t aPaintSequenceNumber = 0) {
    manager->UpdateHitTestingTree(WebRenderScrollDataWrapper{*updater, &layers},
                                  LayersId{0}, aPaintSequenceNumber);
  }

  void CreateScrollData(const char* aTreeShape,
                        const LayerIntRect* aVisibleRects = nullptr,
                        const gfx::Matrix4x4* aTransforms = nullptr) {
    layers = TestWRScrollData::Create(aTreeShape, *updater, aVisibleRects,
                                      aTransforms);
    root = layers[0];
  }

  void CreateMockHitTester() {
    mHitTester = MakeUnique<MockHitTester>();
    // Save a pointer in a separate variable, because SetUp() will
    // move the value out of mHitTester.
    mMockHitTester = static_cast<MockHitTester*>(mHitTester.get());
  }
  void QueueMockHitResult(ScrollableLayerGuid::ViewID aScrollId,
                          gfx::CompositorHitTestInfo aHitInfo =
                              gfx::CompositorHitTestFlags::eVisibleToHitTest) {
    QueueMockHitResult(ScrollableLayerGuid(LayersId{0}, 0, aScrollId),
                       aHitInfo);
  }

  // This overload allows customizing the LayersId as well.
  void QueueMockHitResult(ScrollableLayerGuid aGuid,
                          gfx::CompositorHitTestInfo aHitInfo =
                              gfx::CompositorHitTestFlags::eVisibleToHitTest) {
    MOZ_ASSERT(mMockHitTester);
    mMockHitTester->QueueHitResult(aGuid, aHitInfo);
  }

  RefPtr<TestAPZCTreeManager> manager;
  RefPtr<APZSampler> sampler;
  RefPtr<APZUpdater> updater;
  TestWRScrollData layers;
  WebRenderLayerScrollData* root = nullptr;

  UniquePtr<IAPZHitTester> mHitTester;
  MockHitTester* mMockHitTester = nullptr;

 protected:
  static ScrollMetadata BuildScrollMetadata(
      ScrollableLayerGuid::ViewID aScrollId, const CSSRect& aScrollableRect,
      const ParentLayerRect& aCompositionBounds) {
    ScrollMetadata metadata;
    FrameMetrics& metrics = metadata.GetMetrics();
    metrics.SetScrollId(aScrollId);
    // By convention in this test file, START_SCROLL_ID is the root, so mark it
    // as such.
    if (aScrollId == ScrollableLayerGuid::START_SCROLL_ID) {
      metadata.SetIsLayersIdRoot(true);
    }
    metrics.SetCompositionBounds(aCompositionBounds);
    metrics.SetScrollableRect(aScrollableRect);
    metrics.SetLayoutScrollOffset(CSSPoint(0, 0));
    metadata.SetPageScrollAmount(LayoutDeviceIntSize(50, 100));
    metadata.SetLineScrollAmount(LayoutDeviceIntSize(5, 10));
    return metadata;
  }

  void SetScrollMetadata(WebRenderLayerScrollData* aLayer,
                         const ScrollMetadata& aMetadata) {
    MOZ_ASSERT(aLayer->GetScrollMetadataCount() <= 1,
               "This function does not support multiple ScrollMetadata on a "
               "single layer");
    if (aLayer->GetScrollMetadataCount() == 0) {
      // Add new metrics
      aLayer->AppendScrollMetadata(layers, aMetadata);
    } else {
      // Overwrite existing metrics
      ModifyFrameMetrics(
          aLayer, [&](ScrollMetadata& aSm, FrameMetrics&) { aSm = aMetadata; });
    }
  }

  void SetScrollMetadata(WebRenderLayerScrollData* aLayer,
                         const nsTArray<ScrollMetadata>& aMetadata) {
    // The reason for this restriction is that WebRenderLayerScrollData does not
    // have an API to *remove* previous metadata.
    MOZ_ASSERT(aLayer->GetScrollMetadataCount() == 0,
               "This function can only be used on layers which do not yet have "
               "scroll metadata");
    for (const ScrollMetadata& metadata : aMetadata) {
      aLayer->AppendScrollMetadata(layers, metadata);
    }
  }

  void SetScrollableFrameMetrics(WebRenderLayerScrollData* aLayer,
                                 ScrollableLayerGuid::ViewID aScrollId,
                                 CSSRect aScrollableRect = CSSRect(-1, -1, -1,
                                                                   -1)) {
    auto localTransform = aLayer->GetTransformTyped() * AsyncTransformMatrix();
    ParentLayerIntRect compositionBounds = RoundedToInt(
        localTransform.TransformBounds(LayerRect(aLayer->GetVisibleRect())));
    ScrollMetadata metadata = BuildScrollMetadata(
        aScrollId, aScrollableRect, ParentLayerRect(compositionBounds));
    SetScrollMetadata(aLayer, metadata);
  }

  bool HasScrollableFrameMetrics(const WebRenderLayerScrollData* aLayer) const {
    for (uint32_t i = 0; i < aLayer->GetScrollMetadataCount(); i++) {
      if (aLayer->GetScrollMetadata(layers, i).GetMetrics().IsScrollable()) {
        return true;
      }
    }
    return false;
  }

  void SetScrollHandoff(WebRenderLayerScrollData* aChild,
                        WebRenderLayerScrollData* aParent) {
    ModifyFrameMetrics(aChild, [&](ScrollMetadata& aSm, FrameMetrics&) {
      aSm.SetScrollParentId(
          aParent->GetScrollMetadata(layers, 0).GetMetrics().GetScrollId());
    });
  }

  TestAsyncPanZoomController* ApzcOf(WebRenderLayerScrollData* aLayer) {
    EXPECT_EQ(1u, aLayer->GetScrollMetadataCount());
    return ApzcOf(aLayer, 0);
  }

  TestAsyncPanZoomController* ApzcOf(WebRenderLayerScrollData* aLayer,
                                     uint32_t aIndex) {
    EXPECT_LT(aIndex, aLayer->GetScrollMetadataCount());
    // Unlike Layer, WebRenderLayerScrollData does not store the associated
    // APZCs, so look it up using the tree manager instead.
    RefPtr<AsyncPanZoomController> apzc = manager->GetTargetAPZC(
        LayersId{0},
        aLayer->GetScrollMetadata(layers, aIndex).GetMetrics().GetScrollId());
    return (TestAsyncPanZoomController*)apzc.get();
  }

  void CreateSimpleScrollingLayer() {
    const char* treeShape = "x";
    LayerIntRect layerVisibleRect[] = {
        LayerIntRect(0, 0, 200, 200),
    };
    CreateScrollData(treeShape, layerVisibleRect);
    SetScrollableFrameMetrics(layers[0], ScrollableLayerGuid::START_SCROLL_ID,
                              CSSRect(0, 0, 500, 500));
  }
};

#endif  // mozilla_layers_APZCTreeManagerTester_h
