/* 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/. */

#include "TestWRScrollData.h"
#include "APZTestAccess.h"
#include "gtest/gtest.h"
#include "FrameMetrics.h"
#include "gfxPlatform.h"
#include "mozilla/layers/APZUpdater.h"
#include "mozilla/layers/LayersTypes.h"
#include "mozilla/layers/ScrollableLayerGuid.h"
#include "mozilla/layers/WebRenderScrollDataWrapper.h"
#include "apz/src/APZCTreeManager.h"

#include <stack>

using mozilla::layers::APZCTreeManager;
using mozilla::layers::APZUpdater;
using mozilla::layers::LayersId;
using mozilla::layers::ScrollableLayerGuid;
using mozilla::layers::ScrollMetadata;
using mozilla::layers::TestWRScrollData;
using mozilla::layers::WebRenderLayerScrollData;
using mozilla::layers::WebRenderScrollDataWrapper;

/* static */
TestWRScrollData TestWRScrollData::Create(const char* aTreeShape,
                                          const APZUpdater& aUpdater,
                                          const LayerIntRect* aVisibleRects,
                                          const gfx::Matrix4x4* aTransforms) {
  // The WebRenderLayerScrollData tree needs to be created in a fairly
  // particular way (for example, each node needs to know the number of
  // descendants it has), so this function takes care to create the nodes
  // in the same order as WebRenderCommandBuilder would.
  TestWRScrollData result;
  const size_t len = strlen(aTreeShape);
  // "Layer index" in this function refers to the index by which a layer will
  // be accessible via TestWRScrollData::GetLayer(), and matches the order
  // in which the layer appears in |aTreeShape|.
  size_t currentLayerIndex = 0;
  struct LayerEntry {
    size_t mLayerIndex;
    int32_t mDescendantCount = 0;
  };
  // Layers we have encountered in |aTreeShape|, but have not built a
  // WebRenderLayerScrollData for. (It can only be built after its
  // descendants have been encountered and counted.)
  std::stack<LayerEntry> pendingLayers;
  std::vector<WebRenderLayerScrollData> finishedLayers;
  // Tracks the level of nesting of '(' characters. Starts at 1 to account
  // for the root layer.
  size_t depth = 1;
  // Helper function for finishing a layer once all its descendants have been
  // encountered.
  auto finishLayer = [&] {
    MOZ_ASSERT(!pendingLayers.empty());
    LayerEntry entry = pendingLayers.top();

    WebRenderLayerScrollData layer;
    APZTestAccess::InitializeForTest(layer, entry.mDescendantCount);
    if (aVisibleRects) {
      layer.SetVisibleRect(aVisibleRects[entry.mLayerIndex]);
    }
    if (aTransforms) {
      layer.SetTransform(aTransforms[entry.mLayerIndex]);
    }
    finishedLayers.push_back(std::move(layer));

    // |finishedLayers| stores the layers in a different order than they
    // appeared in |aTreeShape|. To be able to access layers by their layer
    // index, keep a mapping from layer index to index in |finishedLayers|.
    result.mIndexMap.emplace(entry.mLayerIndex, finishedLayers.size() - 1);

    pendingLayers.pop();

    // Keep track of descendant counts. The +1 is for the layer just finished.
    if (!pendingLayers.empty()) {
      pendingLayers.top().mDescendantCount += (entry.mDescendantCount + 1);
    }
  };
  for (size_t i = 0; i < len; ++i) {
    if (aTreeShape[i] == '(') {
      ++depth;
    } else if (aTreeShape[i] == ')') {
      if (pendingLayers.size() <= 1) {
        printf("Invalid tree shape: too many ')'\n");
        MOZ_CRASH();
      }
      finishLayer();  // finish last layer at current depth
      --depth;
    } else {
      if (aTreeShape[i] != 'x') {
        printf("The only allowed character to represent a layer is 'x'\n");
        MOZ_CRASH();
      }
      if (depth == pendingLayers.size()) {
        // We have a previous layer at this same depth to finish.
        if (depth <= 1) {
          printf("The tree is only allowed to have one root\n");
          MOZ_CRASH();
        }
        finishLayer();
      }
      MOZ_ASSERT(depth == pendingLayers.size() + 1);
      pendingLayers.push({currentLayerIndex});
      ++currentLayerIndex;
    }
  }
  if (pendingLayers.size() != 1) {
    printf("Invalid tree shape: '(' and ')' not balanced\n");
    MOZ_CRASH();
  }
  finishLayer();  // finish root layer

  // As in WebRenderCommandBuilder, the layers need to be added to the
  // WebRenderScrollData in reverse of the order in which they were built.
  for (auto it = finishedLayers.rbegin(); it != finishedLayers.rend(); ++it) {
    result.AddLayerData(std::move(*it));
  }
  // mIndexMap also needs to be adjusted to accout for the reversal above.
  for (auto& [layerIndex, storedIndex] : result.mIndexMap) {
    (void)layerIndex;  // suppress -Werror=unused-variable
    storedIndex = result.GetLayerCount() - storedIndex - 1;
  }

  return result;
}

const WebRenderLayerScrollData* TestWRScrollData::operator[](
    size_t aLayerIndex) const {
  auto it = mIndexMap.find(aLayerIndex);
  if (it == mIndexMap.end()) {
    return nullptr;
  }
  return GetLayerData(it->second);
}

WebRenderLayerScrollData* TestWRScrollData::operator[](size_t aLayerIndex) {
  auto it = mIndexMap.find(aLayerIndex);
  if (it == mIndexMap.end()) {
    return nullptr;
  }
  return GetLayerData(it->second);
}

void TestWRScrollData::SetScrollMetadata(
    size_t aLayerIndex, const nsTArray<ScrollMetadata>& aMetadata) {
  WebRenderLayerScrollData* layer = operator[](aLayerIndex);
  MOZ_ASSERT(layer);
  for (const ScrollMetadata& metadata : aMetadata) {
    layer->AppendScrollMetadata(*this, metadata);
  }
}

class WebRenderScrollDataWrapperTester : public ::testing::Test {
 protected:
  virtual void SetUp() {
    // This ensures ScrollMetadata::sNullMetadata is initialized.
    gfxPlatform::GetPlatform();

    mManager = APZCTreeManager::Create(LayersId{0});
    mUpdater = new APZUpdater(mManager, false);
  }

  RefPtr<APZCTreeManager> mManager;
  RefPtr<APZUpdater> mUpdater;
};

TEST_F(WebRenderScrollDataWrapperTester, SimpleTree) {
  auto layers = TestWRScrollData::Create("x(x(x(xx)x(x)))", *mUpdater);
  WebRenderScrollDataWrapper w0(*mUpdater, &layers);

  ASSERT_EQ(layers[0], w0.GetLayer());
  WebRenderScrollDataWrapper w1 = w0.GetLastChild();
  ASSERT_EQ(layers[1], w1.GetLayer());
  ASSERT_FALSE(w1.GetPrevSibling().IsValid());
  WebRenderScrollDataWrapper w5 = w1.GetLastChild();
  ASSERT_EQ(layers[5], w5.GetLayer());
  WebRenderScrollDataWrapper w6 = w5.GetLastChild();
  ASSERT_EQ(layers[6], w6.GetLayer());
  ASSERT_FALSE(w6.GetLastChild().IsValid());
  WebRenderScrollDataWrapper w2 = w5.GetPrevSibling();
  ASSERT_EQ(layers[2], w2.GetLayer());
  ASSERT_FALSE(w2.GetPrevSibling().IsValid());
  WebRenderScrollDataWrapper w4 = w2.GetLastChild();
  ASSERT_EQ(layers[4], w4.GetLayer());
  ASSERT_FALSE(w4.GetLastChild().IsValid());
  WebRenderScrollDataWrapper w3 = w4.GetPrevSibling();
  ASSERT_EQ(layers[3], w3.GetLayer());
  ASSERT_FALSE(w3.GetLastChild().IsValid());
  ASSERT_FALSE(w3.GetPrevSibling().IsValid());
}

static ScrollMetadata MakeMetadata(ScrollableLayerGuid::ViewID aId) {
  ScrollMetadata metadata;
  metadata.GetMetrics().SetScrollId(aId);
  return metadata;
}

TEST_F(WebRenderScrollDataWrapperTester, MultiFramemetricsTree) {
  auto layers = TestWRScrollData::Create("x(x(x(xx)x(x)))", *mUpdater);

  nsTArray<ScrollMetadata> metadata;
  metadata.InsertElementAt(0,
                           MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID +
                                        0));  // topmost of root layer
  metadata.InsertElementAt(0,
                           MakeMetadata(ScrollableLayerGuid::NULL_SCROLL_ID));
  metadata.InsertElementAt(
      0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 1));
  metadata.InsertElementAt(
      0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 2));
  metadata.InsertElementAt(0,
                           MakeMetadata(ScrollableLayerGuid::NULL_SCROLL_ID));
  metadata.InsertElementAt(
      0, MakeMetadata(
             ScrollableLayerGuid::NULL_SCROLL_ID));  // bottom of root layer
  layers.SetScrollMetadata(0, metadata);

  metadata.Clear();
  metadata.InsertElementAt(
      0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 3));
  layers.SetScrollMetadata(1, metadata);

  metadata.Clear();
  metadata.InsertElementAt(
      0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 4));
  layers.SetScrollMetadata(2, metadata);

  metadata.Clear();
  metadata.InsertElementAt(
      0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 5));
  layers.SetScrollMetadata(4, metadata);

  metadata.Clear();
  metadata.InsertElementAt(0,
                           MakeMetadata(ScrollableLayerGuid::NULL_SCROLL_ID));
  metadata.InsertElementAt(
      0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 6));
  layers.SetScrollMetadata(5, metadata);

  WebRenderScrollDataWrapper wrapper(*mUpdater, &layers);
  nsTArray<WebRenderLayerScrollData*> expectedLayers;
  expectedLayers.AppendElement(layers[0]);
  expectedLayers.AppendElement(layers[0]);
  expectedLayers.AppendElement(layers[0]);
  expectedLayers.AppendElement(layers[0]);
  expectedLayers.AppendElement(layers[0]);
  expectedLayers.AppendElement(layers[0]);
  expectedLayers.AppendElement(layers[1]);
  expectedLayers.AppendElement(layers[5]);
  expectedLayers.AppendElement(layers[5]);
  expectedLayers.AppendElement(layers[6]);
  nsTArray<ScrollableLayerGuid::ViewID> expectedIds;
  expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 0);
  expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID);
  expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 1);
  expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 2);
  expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID);
  expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID);
  expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 3);
  expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID);
  expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 6);
  expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID);
  for (int i = 0; i < 10; i++) {
    ASSERT_EQ(expectedLayers[i], wrapper.GetLayer());
    ASSERT_EQ(expectedIds[i], wrapper.Metrics().GetScrollId());
    wrapper = wrapper.GetLastChild();
  }
  ASSERT_FALSE(wrapper.IsValid());
}
