// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/display_list/benchmarking/dl_complexity.h"
#include "flutter/display_list/display_list.h"
#include "flutter/display_list/dl_builder.h"
#include "flutter/display_list/testing/dl_test_snippets.h"
#include "flutter/flow/layers/container_layer.h"
#include "flutter/flow/layers/display_list_layer.h"
#include "flutter/flow/layers/image_filter_layer.h"
#include "flutter/flow/layers/layer_tree.h"
#include "flutter/flow/layers/transform_layer.h"
#include "flutter/flow/raster_cache.h"
#include "flutter/flow/raster_cache_item.h"
#include "flutter/flow/testing/layer_test.h"
#include "flutter/flow/testing/mock_raster_cache.h"
#include "flutter/testing/assertions_skia.h"
#include "gtest/gtest.h"
#include "third_party/skia/include/core/SkMatrix.h"
#include "third_party/skia/include/core/SkPoint.h"

// TODO(zanderso): https://github.com/flutter/flutter/issues/127701
// NOLINTBEGIN(bugprone-unchecked-optional-access)

namespace flutter {
namespace testing {

TEST(RasterCache, SimpleInitialization) {
  flutter::RasterCache cache;
  ASSERT_TRUE(true);
}

TEST(RasterCache, MetricsOmitUnpopulatedEntries) {
  size_t threshold = 2;
  flutter::RasterCache cache(threshold);

  SkMatrix matrix = SkMatrix::I();

  auto display_list = GetSampleDisplayList();

  MockCanvas dummy_canvas(1000, 1000);
  DlPaint paint;

  LayerStateStack preroll_state_stack;
  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);
  LayerStateStack paint_state_stack;
  preroll_state_stack.set_delegate(&dummy_canvas);

  FixedRefreshRateStopwatch raster_time;
  FixedRefreshRateStopwatch ui_time;
  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(
      preroll_state_stack, &cache, &raster_time, &ui_time);
  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(
      paint_state_stack, &cache, &raster_time, &ui_time);
  auto& preroll_context = preroll_context_holder.preroll_context;
  auto& paint_context = paint_context_holder.paint_context;

  cache.BeginFrame();
  DisplayListRasterCacheItem display_list_item(display_list, SkPoint(), true,
                                               false);

  // 1st access.
  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, matrix));
  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));

  cache.EndFrame();
  ASSERT_EQ(cache.picture_metrics().total_count(), 0u);
  ASSERT_EQ(cache.picture_metrics().total_bytes(), 0u);
  cache.BeginFrame();

  // 2nd access.
  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, matrix));
  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));

  cache.EndFrame();
  ASSERT_EQ(cache.picture_metrics().total_count(), 0u);
  ASSERT_EQ(cache.picture_metrics().total_bytes(), 0u);
  cache.BeginFrame();

  // Now Prepare should cache it.
  ASSERT_TRUE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, matrix));
  ASSERT_TRUE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));

  cache.EndFrame();
  ASSERT_EQ(cache.picture_metrics().total_count(), 1u);
  // 80w * 80h * 4bpp + image object overhead
  ASSERT_EQ(cache.picture_metrics().total_bytes(), 25624u);
}

TEST(RasterCache, ThresholdIsRespectedForDisplayList) {
  size_t threshold = 2;
  flutter::RasterCache cache(threshold);

  SkMatrix matrix = SkMatrix::I();

  auto display_list = GetSampleDisplayList();

  MockCanvas dummy_canvas(1000, 1000);
  DlPaint paint;

  LayerStateStack preroll_state_stack;
  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);
  LayerStateStack paint_state_stack;
  preroll_state_stack.set_delegate(&dummy_canvas);

  FixedRefreshRateStopwatch raster_time;
  FixedRefreshRateStopwatch ui_time;
  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(
      preroll_state_stack, &cache, &raster_time, &ui_time);
  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(
      paint_state_stack, &cache, &raster_time, &ui_time);
  auto& preroll_context = preroll_context_holder.preroll_context;
  auto& paint_context = paint_context_holder.paint_context;

  cache.BeginFrame();

  DisplayListRasterCacheItem display_list_item(display_list, SkPoint(), true,
                                               false);

  // 1st access.
  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, matrix));
  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));

  cache.EndFrame();
  cache.BeginFrame();

  // 2nd access.
  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, matrix));
  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));

  cache.EndFrame();
  cache.BeginFrame();

  // Now Prepare should cache it.
  ASSERT_TRUE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, matrix));
  ASSERT_TRUE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));
}

TEST(RasterCache, SetCheckboardCacheImages) {
  size_t threshold = 1;
  flutter::RasterCache cache(threshold);

  SkMatrix matrix = SkMatrix::I();
  auto display_list = GetSampleDisplayList();

  LayerStateStack preroll_state_stack;
  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);

  FixedRefreshRateStopwatch raster_time;
  FixedRefreshRateStopwatch ui_time;
  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(
      preroll_state_stack, &cache, &raster_time, &ui_time);
  auto& paint_context = paint_context_holder.paint_context;
  auto dummy_draw_function = [](DlCanvas* canvas) {};
  bool did_draw_checkerboard = false;
  auto draw_checkerboard = [&](DlCanvas* canvas, const SkRect&) {
    did_draw_checkerboard = true;
  };
  RasterCache::Context r_context = {
      // clang-format off
      .gr_context         = paint_context.gr_context,
      .dst_color_space    = paint_context.dst_color_space,
      .matrix             = matrix,
      .logical_rect       = display_list->bounds(),
      .flow_type          = "RasterCacheFlow::DisplayList",
      // clang-format on
  };

  cache.SetCheckboardCacheImages(false);
  cache.Rasterize(r_context, nullptr, dummy_draw_function, draw_checkerboard);
  ASSERT_FALSE(did_draw_checkerboard);

  cache.SetCheckboardCacheImages(true);
  cache.Rasterize(r_context, nullptr, dummy_draw_function, draw_checkerboard);
  ASSERT_TRUE(did_draw_checkerboard);
}

TEST(RasterCache, AccessThresholdOfZeroDisablesCachingForDisplayList) {
  size_t threshold = 0;
  flutter::RasterCache cache(threshold);

  SkMatrix matrix = SkMatrix::I();

  auto display_list = GetSampleDisplayList();

  MockCanvas dummy_canvas(1000, 1000);
  DlPaint paint;

  LayerStateStack preroll_state_stack;
  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);
  LayerStateStack paint_state_stack;
  preroll_state_stack.set_delegate(&dummy_canvas);

  FixedRefreshRateStopwatch raster_time;
  FixedRefreshRateStopwatch ui_time;
  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(
      preroll_state_stack, &cache, &raster_time, &ui_time);
  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(
      paint_state_stack, &cache, &raster_time, &ui_time);
  auto& preroll_context = preroll_context_holder.preroll_context;
  auto& paint_context = paint_context_holder.paint_context;

  cache.BeginFrame();

  DisplayListRasterCacheItem display_list_item(display_list, SkPoint(), true,
                                               false);
  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, matrix));
  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));
}

TEST(RasterCache, PictureCacheLimitPerFrameIsRespectedWhenZeroForDisplayList) {
  size_t picture_cache_limit_per_frame = 0;
  flutter::RasterCache cache(3, picture_cache_limit_per_frame);

  SkMatrix matrix = SkMatrix::I();

  auto display_list = GetSampleDisplayList();

  MockCanvas dummy_canvas(1000, 1000);
  DlPaint paint;

  LayerStateStack preroll_state_stack;
  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);
  LayerStateStack paint_state_stack;
  preroll_state_stack.set_delegate(&dummy_canvas);

  FixedRefreshRateStopwatch raster_time;
  FixedRefreshRateStopwatch ui_time;
  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(
      preroll_state_stack, &cache, &raster_time, &ui_time);
  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(
      paint_state_stack, &cache, &raster_time, &ui_time);
  auto& preroll_context = preroll_context_holder.preroll_context;
  auto& paint_context = paint_context_holder.paint_context;

  cache.BeginFrame();

  DisplayListRasterCacheItem display_list_item(display_list, SkPoint(), true,
                                               false);
  // 1st access.
  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, matrix));
  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));
  // 2nd access.
  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, matrix));
  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));
  // the picture_cache_limit_per_frame = 0, so don't cache it
  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, matrix));
  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));
}

TEST(RasterCache, EvictUnusedCacheEntries) {
  size_t threshold = 1;
  flutter::RasterCache cache(threshold);

  SkMatrix matrix = SkMatrix::I();

  auto display_list_1 = GetSampleDisplayList();
  auto display_list_2 = GetSampleDisplayList();

  MockCanvas dummy_canvas(1000, 1000);
  DlPaint paint;

  LayerStateStack preroll_state_stack;
  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);
  LayerStateStack paint_state_stack;
  preroll_state_stack.set_delegate(&dummy_canvas);

  FixedRefreshRateStopwatch raster_time;
  FixedRefreshRateStopwatch ui_time;
  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(
      preroll_state_stack, &cache, &raster_time, &ui_time);
  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(
      paint_state_stack, &cache, &raster_time, &ui_time);
  auto& preroll_context = preroll_context_holder.preroll_context;
  auto& paint_context = paint_context_holder.paint_context;

  DisplayListRasterCacheItem display_list_item_1(display_list_1, SkPoint(),
                                                 true, false);
  DisplayListRasterCacheItem display_list_item_2(display_list_2, SkPoint(),
                                                 true, false);

  cache.BeginFrame();
  RasterCacheItemPreroll(display_list_item_1, preroll_context, matrix);
  RasterCacheItemPreroll(display_list_item_2, preroll_context, matrix);
  cache.EvictUnusedCacheEntries();
  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 0u);
  ASSERT_FALSE(
      RasterCacheItemTryToRasterCache(display_list_item_1, paint_context));
  ASSERT_FALSE(
      RasterCacheItemTryToRasterCache(display_list_item_2, paint_context));
  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 0u);
  ASSERT_FALSE(display_list_item_1.Draw(paint_context, &dummy_canvas, &paint));
  ASSERT_FALSE(display_list_item_2.Draw(paint_context, &dummy_canvas, &paint));
  cache.EndFrame();

  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 0u);
  ASSERT_EQ(cache.picture_metrics().total_count(), 0u);
  ASSERT_EQ(cache.picture_metrics().total_bytes(), 0u);

  cache.BeginFrame();
  RasterCacheItemPreroll(display_list_item_1, preroll_context, matrix);
  RasterCacheItemPreroll(display_list_item_2, preroll_context, matrix);
  cache.EvictUnusedCacheEntries();
  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 0u);
  ASSERT_TRUE(
      RasterCacheItemTryToRasterCache(display_list_item_1, paint_context));
  ASSERT_TRUE(
      RasterCacheItemTryToRasterCache(display_list_item_2, paint_context));
  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 51248u);
  ASSERT_TRUE(display_list_item_1.Draw(paint_context, &dummy_canvas, &paint));
  ASSERT_TRUE(display_list_item_2.Draw(paint_context, &dummy_canvas, &paint));
  cache.EndFrame();

  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 51248u);
  ASSERT_EQ(cache.picture_metrics().total_count(), 2u);
  ASSERT_EQ(cache.picture_metrics().total_bytes(), 51248u);

  cache.BeginFrame();
  RasterCacheItemPreroll(display_list_item_1, preroll_context, matrix);
  cache.EvictUnusedCacheEntries();
  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 25624u);
  ASSERT_TRUE(
      RasterCacheItemTryToRasterCache(display_list_item_1, paint_context));
  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 25624u);
  ASSERT_TRUE(display_list_item_1.Draw(paint_context, &dummy_canvas, &paint));
  cache.EndFrame();

  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 25624u);
  ASSERT_EQ(cache.picture_metrics().total_count(), 1u);
  ASSERT_EQ(cache.picture_metrics().total_bytes(), 25624u);

  cache.BeginFrame();
  cache.EvictUnusedCacheEntries();
  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 0u);
  cache.EndFrame();

  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 0u);
  ASSERT_EQ(cache.picture_metrics().total_count(), 0u);
  ASSERT_EQ(cache.picture_metrics().total_bytes(), 0u);

  cache.BeginFrame();
  ASSERT_FALSE(
      cache.Draw(display_list_item_1.GetId().value(), dummy_canvas, &paint));
  ASSERT_FALSE(display_list_item_1.Draw(paint_context, &dummy_canvas, &paint));
  ASSERT_FALSE(
      cache.Draw(display_list_item_2.GetId().value(), dummy_canvas, &paint));
  ASSERT_FALSE(display_list_item_2.Draw(paint_context, &dummy_canvas, &paint));
  cache.EndFrame();
}

TEST(RasterCache, ComputeDeviceRectBasedOnFractionalTranslation) {
  SkRect logical_rect = SkRect::MakeLTRB(0, 0, 300.2, 300.3);
  SkMatrix ctm = SkMatrix::MakeAll(2.0, 0, 0, 0, 2.0, 0, 0, 0, 1);
  auto result = RasterCacheUtil::GetDeviceBounds(logical_rect, ctm);
  ASSERT_EQ(result, SkRect::MakeLTRB(0.0, 0.0, 600.4, 600.6));
}

// Construct a cache result whose device target rectangle rounds out to be one
// pixel wider than the cached image.  Verify that it can be drawn without
// triggering any assertions.
TEST(RasterCache, DeviceRectRoundOutForDisplayList) {
  size_t threshold = 1;
  flutter::RasterCache cache(threshold);

  SkRect logical_rect = SkRect::MakeLTRB(28, 0, 354.56731, 310.288);
  DisplayListBuilder builder(logical_rect);
  builder.DrawRect(logical_rect, DlPaint(DlColor::kRed()));
  sk_sp<DisplayList> display_list = builder.Build();

  SkMatrix ctm = SkMatrix::MakeAll(1.3312, 0, 233, 0, 1.3312, 206, 0, 0, 1);
  DlPaint paint;

  MockCanvas canvas(1000, 1000);
  canvas.SetTransform(ctm);

  LayerStateStack preroll_state_stack;
  preroll_state_stack.set_preroll_delegate(kGiantRect, ctm);
  LayerStateStack paint_state_stack;
  preroll_state_stack.set_delegate(&canvas);

  FixedRefreshRateStopwatch raster_time;
  FixedRefreshRateStopwatch ui_time;
  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(
      preroll_state_stack, &cache, &raster_time, &ui_time);
  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(
      paint_state_stack, &cache, &raster_time, &ui_time);
  auto& preroll_context = preroll_context_holder.preroll_context;
  auto& paint_context = paint_context_holder.paint_context;

  cache.BeginFrame();
  DisplayListRasterCacheItem display_list_item(display_list, SkPoint(), true,
                                               false);

  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, ctm));
  ASSERT_FALSE(display_list_item.Draw(paint_context, &canvas, &paint));

  cache.EndFrame();
  cache.BeginFrame();

  ASSERT_TRUE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, ctm));
  ASSERT_TRUE(display_list_item.Draw(paint_context, &canvas, &paint));

  canvas.Translate(248, 0);
  ASSERT_TRUE(cache.Draw(display_list_item.GetId().value(), canvas, &paint));
  ASSERT_TRUE(display_list_item.Draw(paint_context, &canvas, &paint));
}

TEST(RasterCache, NestedOpCountMetricUsedForDisplayList) {
  size_t threshold = 1;
  flutter::RasterCache cache(threshold);

  SkMatrix matrix = SkMatrix::I();

  auto display_list = GetSampleNestedDisplayList();
  ASSERT_EQ(display_list->op_count(), 1u);
  ASSERT_EQ(display_list->op_count(true), 36u);

  MockCanvas dummy_canvas(1000, 1000);
  DlPaint paint;

  LayerStateStack preroll_state_stack;
  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);
  LayerStateStack paint_state_stack;
  preroll_state_stack.set_delegate(&dummy_canvas);

  FixedRefreshRateStopwatch raster_time;
  FixedRefreshRateStopwatch ui_time;
  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(
      preroll_state_stack, &cache, &raster_time, &ui_time);
  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(
      paint_state_stack, &cache, &raster_time, &ui_time);
  auto& preroll_context = preroll_context_holder.preroll_context;
  auto& paint_context = paint_context_holder.paint_context;

  cache.BeginFrame();

  DisplayListRasterCacheItem display_list_item(display_list, SkPoint(), false,
                                               false);

  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, matrix));
  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));

  cache.EndFrame();
  cache.BeginFrame();

  ASSERT_TRUE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, matrix));
  ASSERT_TRUE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));
}

TEST(RasterCache, NaiveComplexityScoringDisplayList) {
  DisplayListComplexityCalculator* calculator =
      DisplayListNaiveComplexityCalculator::GetInstance();

  size_t threshold = 1;
  flutter::RasterCache cache(threshold);

  SkMatrix matrix = SkMatrix::I();

  // Five raster ops will not be cached
  auto display_list = GetSampleDisplayList(5);
  unsigned int complexity_score = calculator->Compute(display_list.get());

  ASSERT_EQ(complexity_score, 5u);
  ASSERT_EQ(display_list->op_count(), 5u);
  ASSERT_FALSE(calculator->ShouldBeCached(complexity_score));

  MockCanvas dummy_canvas(1000, 1000);
  DlPaint paint;

  LayerStateStack preroll_state_stack;
  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);
  LayerStateStack paint_state_stack;
  preroll_state_stack.set_delegate(&dummy_canvas);

  FixedRefreshRateStopwatch raster_time;
  FixedRefreshRateStopwatch ui_time;
  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(
      preroll_state_stack, &cache, &raster_time, &ui_time);
  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(
      paint_state_stack, &cache, &raster_time, &ui_time);
  auto& preroll_context = preroll_context_holder.preroll_context;
  auto& paint_context = paint_context_holder.paint_context;

  cache.BeginFrame();

  DisplayListRasterCacheItem display_list_item(display_list, SkPoint(), false,
                                               false);

  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, matrix));
  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));

  cache.EndFrame();
  cache.BeginFrame();

  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item, preroll_context, paint_context, matrix));
  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));

  // Six raster ops should be cached
  display_list = GetSampleDisplayList(6);
  complexity_score = calculator->Compute(display_list.get());

  ASSERT_EQ(complexity_score, 6u);
  ASSERT_EQ(display_list->op_count(), 6u);
  ASSERT_TRUE(calculator->ShouldBeCached(complexity_score));

  DisplayListRasterCacheItem display_list_item_2 =
      DisplayListRasterCacheItem(display_list, SkPoint(), false, false);
  cache.BeginFrame();

  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item_2, preroll_context, paint_context, matrix));
  ASSERT_FALSE(display_list_item_2.Draw(paint_context, &dummy_canvas, &paint));

  cache.EndFrame();
  cache.BeginFrame();

  ASSERT_TRUE(RasterCacheItemPrerollAndTryToRasterCache(
      display_list_item_2, preroll_context, paint_context, matrix));
  ASSERT_TRUE(display_list_item_2.Draw(paint_context, &dummy_canvas, &paint));
}

TEST(RasterCache, DisplayListWithSingularMatrixIsNotCached) {
  size_t threshold = 2;
  flutter::RasterCache cache(threshold);

  SkMatrix matrices[] = {
      SkMatrix::Scale(0, 1),
      SkMatrix::Scale(1, 0),
      SkMatrix::Skew(1, 1),
  };
  int matrix_count = sizeof(matrices) / sizeof(matrices[0]);

  auto display_list = GetSampleDisplayList();

  MockCanvas dummy_canvas(1000, 1000);
  DlPaint paint;

  LayerStateStack preroll_state_stack;
  preroll_state_stack.set_preroll_delegate(kGiantRect, SkMatrix::I());
  LayerStateStack paint_state_stack;
  preroll_state_stack.set_delegate(&dummy_canvas);

  FixedRefreshRateStopwatch raster_time;
  FixedRefreshRateStopwatch ui_time;
  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(
      preroll_state_stack, &cache, &raster_time, &ui_time);
  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(
      paint_state_stack, &cache, &raster_time, &ui_time);
  auto& preroll_context = preroll_context_holder.preroll_context;
  auto& paint_context = paint_context_holder.paint_context;

  DisplayListRasterCacheItem display_list_item(display_list, SkPoint(), true,
                                               false);

  for (int i = 0; i < 10; i++) {
    cache.BeginFrame();

    for (int j = 0; j < matrix_count; j++) {
      display_list_item.set_matrix(matrices[j]);
      ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(
          display_list_item, preroll_context, paint_context, matrices[j]));
    }

    for (int j = 0; j < matrix_count; j++) {
      dummy_canvas.SetTransform(matrices[j]);
      ASSERT_FALSE(
          display_list_item.Draw(paint_context, &dummy_canvas, &paint));
    }

    cache.EndFrame();
  }
}

TEST(RasterCache, PrepareLayerTransform) {
  SkRect child_bounds = SkRect::MakeLTRB(10, 10, 50, 50);
  SkPath child_path = SkPath().addOval(child_bounds);
  auto child_layer = MockLayer::Make(child_path);
  auto blur_filter =
      std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);
  auto blur_layer = std::make_shared<ImageFilterLayer>(blur_filter);
  SkMatrix matrix = SkMatrix::Scale(2, 2);
  auto transform_layer = std::make_shared<TransformLayer>(matrix);
  SkMatrix cache_matrix = SkMatrix::Translate(-20, -20);
  cache_matrix.preConcat(matrix);
  child_layer->set_expected_paint_matrix(cache_matrix);

  blur_layer->Add(child_layer);
  transform_layer->Add(blur_layer);

  size_t threshold = 2;
  MockRasterCache cache(threshold);
  MockCanvas dummy_canvas(1000, 1000);

  LayerStateStack preroll_state_stack;
  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);
  LayerStateStack paint_state_stack;
  preroll_state_stack.set_delegate(&dummy_canvas);

  FixedRefreshRateStopwatch raster_time;
  FixedRefreshRateStopwatch ui_time;
  std::vector<RasterCacheItem*> cache_items;

  cache.BeginFrame();

  auto preroll_holder = GetSamplePrerollContextHolder(
      preroll_state_stack, &cache, &raster_time, &ui_time);
  preroll_holder.preroll_context.raster_cached_entries = &cache_items;
  transform_layer->Preroll(&preroll_holder.preroll_context);

  auto paint_holder = GetSamplePaintContextHolder(paint_state_stack, &cache,
                                                  &raster_time, &ui_time);

  cache.EvictUnusedCacheEntries();
  LayerTree::TryToRasterCache(
      *preroll_holder.preroll_context.raster_cached_entries,
      &paint_holder.paint_context);

  // Condition tested inside MockLayer::Paint against expected paint matrix.
}

TEST(RasterCache, RasterCacheKeyHashFunction) {
  RasterCacheKey::Map<int> map;
  auto hash_function = map.hash_function();
  SkMatrix matrix = SkMatrix::I();
  uint64_t id = 5;
  RasterCacheKey layer_key(id, RasterCacheKeyType::kLayer, matrix);
  RasterCacheKey display_list_key(id, RasterCacheKeyType::kDisplayList, matrix);
  RasterCacheKey layer_children_key(id, RasterCacheKeyType::kLayerChildren,
                                    matrix);

  auto layer_cache_key_id = RasterCacheKeyID(id, RasterCacheKeyType::kLayer);
  auto layer_hash_code = hash_function(layer_key);
  ASSERT_EQ(layer_hash_code, layer_cache_key_id.GetHash());

  auto display_list_cache_key_id =
      RasterCacheKeyID(id, RasterCacheKeyType::kDisplayList);
  auto display_list_hash_code = hash_function(display_list_key);
  ASSERT_EQ(display_list_hash_code, display_list_cache_key_id.GetHash());

  auto layer_children_cache_key_id =
      RasterCacheKeyID(id, RasterCacheKeyType::kLayerChildren);
  auto layer_children_hash_code = hash_function(layer_children_key);
  ASSERT_EQ(layer_children_hash_code, layer_children_cache_key_id.GetHash());
}

TEST(RasterCache, RasterCacheKeySameID) {
  RasterCacheKey::Map<int> map;
  SkMatrix matrix = SkMatrix::I();
  uint64_t id = 5;
  RasterCacheKey layer_key(id, RasterCacheKeyType::kLayer, matrix);
  RasterCacheKey display_list_key(id, RasterCacheKeyType::kDisplayList, matrix);
  RasterCacheKey layer_children_key(id, RasterCacheKeyType::kLayerChildren,
                                    matrix);
  map[layer_key] = 100;
  map[display_list_key] = 300;
  map[layer_children_key] = 400;

  ASSERT_EQ(map[layer_key], 100);
  ASSERT_EQ(map[display_list_key], 300);
  ASSERT_EQ(map[layer_children_key], 400);
}

TEST(RasterCache, RasterCacheKeySameType) {
  RasterCacheKey::Map<int> map;
  SkMatrix matrix = SkMatrix::I();

  RasterCacheKeyType type = RasterCacheKeyType::kLayer;
  RasterCacheKey layer_first_key(5, type, matrix);
  RasterCacheKey layer_second_key(10, type, matrix);
  RasterCacheKey layer_third_key(15, type, matrix);
  map[layer_first_key] = 50;
  map[layer_second_key] = 100;
  map[layer_third_key] = 150;
  ASSERT_EQ(map[layer_first_key], 50);
  ASSERT_EQ(map[layer_second_key], 100);
  ASSERT_EQ(map[layer_third_key], 150);

  type = RasterCacheKeyType::kDisplayList;
  RasterCacheKey picture_first_key(20, type, matrix);
  RasterCacheKey picture_second_key(25, type, matrix);
  RasterCacheKey picture_third_key(30, type, matrix);
  map[picture_first_key] = 200;
  map[picture_second_key] = 250;
  map[picture_third_key] = 300;
  ASSERT_EQ(map[picture_first_key], 200);
  ASSERT_EQ(map[picture_second_key], 250);
  ASSERT_EQ(map[picture_third_key], 300);

  type = RasterCacheKeyType::kDisplayList;
  RasterCacheKey display_list_first_key(35, type, matrix);
  RasterCacheKey display_list_second_key(40, type, matrix);
  RasterCacheKey display_list_third_key(45, type, matrix);
  map[display_list_first_key] = 350;
  map[display_list_second_key] = 400;
  map[display_list_third_key] = 450;
  ASSERT_EQ(map[display_list_first_key], 350);
  ASSERT_EQ(map[display_list_second_key], 400);
  ASSERT_EQ(map[display_list_third_key], 450);

  type = RasterCacheKeyType::kLayerChildren;
  RasterCacheKeyID foo = RasterCacheKeyID(10, RasterCacheKeyType::kLayer);
  RasterCacheKeyID bar = RasterCacheKeyID(20, RasterCacheKeyType::kLayer);
  RasterCacheKeyID baz = RasterCacheKeyID(30, RasterCacheKeyType::kLayer);
  RasterCacheKey layer_children_first_key(
      RasterCacheKeyID({foo, bar, baz}, type), matrix);
  RasterCacheKey layer_children_second_key(
      RasterCacheKeyID({foo, baz, bar}, type), matrix);
  RasterCacheKey layer_children_third_key(
      RasterCacheKeyID({baz, bar, foo}, type), matrix);
  map[layer_children_first_key] = 100;
  map[layer_children_second_key] = 200;
  map[layer_children_third_key] = 300;
  ASSERT_EQ(map[layer_children_first_key], 100);
  ASSERT_EQ(map[layer_children_second_key], 200);
  ASSERT_EQ(map[layer_children_third_key], 300);
}

TEST(RasterCache, RasterCacheKeyIDEqual) {
  RasterCacheKeyID first = RasterCacheKeyID(1, RasterCacheKeyType::kLayer);
  RasterCacheKeyID second = RasterCacheKeyID(2, RasterCacheKeyType::kLayer);
  RasterCacheKeyID third =
      RasterCacheKeyID(1, RasterCacheKeyType::kLayerChildren);

  ASSERT_NE(first, second);
  ASSERT_NE(first, third);
  ASSERT_NE(second, third);

  RasterCacheKeyID fourth =
      RasterCacheKeyID({first, second}, RasterCacheKeyType::kLayer);
  RasterCacheKeyID fifth =
      RasterCacheKeyID({first, second}, RasterCacheKeyType::kLayerChildren);
  RasterCacheKeyID sixth =
      RasterCacheKeyID({second, first}, RasterCacheKeyType::kLayerChildren);
  ASSERT_NE(fourth, fifth);
  ASSERT_NE(fifth, sixth);
}

TEST(RasterCache, RasterCacheKeyIDHashCode) {
  uint64_t foo = 1;
  uint64_t bar = 2;
  RasterCacheKeyID first = RasterCacheKeyID(foo, RasterCacheKeyType::kLayer);
  RasterCacheKeyID second = RasterCacheKeyID(bar, RasterCacheKeyType::kLayer);
  std::size_t first_hash = first.GetHash();
  std::size_t second_hash = second.GetHash();

  ASSERT_EQ(first_hash, fml::HashCombine(foo, RasterCacheKeyType::kLayer));
  ASSERT_EQ(second_hash, fml::HashCombine(bar, RasterCacheKeyType::kLayer));

  RasterCacheKeyID third =
      RasterCacheKeyID({first, second}, RasterCacheKeyType::kLayerChildren);
  RasterCacheKeyID fourth =
      RasterCacheKeyID({second, first}, RasterCacheKeyType::kLayerChildren);
  std::size_t third_hash = third.GetHash();
  std::size_t fourth_hash = fourth.GetHash();

  ASSERT_EQ(third_hash, fml::HashCombine(RasterCacheKeyID::kDefaultUniqueID,
                                         RasterCacheKeyType::kLayerChildren,
                                         first.GetHash(), second.GetHash()));
  ASSERT_EQ(fourth_hash, fml::HashCombine(RasterCacheKeyID::kDefaultUniqueID,
                                          RasterCacheKeyType::kLayerChildren,
                                          second.GetHash(), first.GetHash()));

  // Verify that the cached hash code is correct.
  ASSERT_EQ(first_hash, first.GetHash());
  ASSERT_EQ(second_hash, second.GetHash());
  ASSERT_EQ(third_hash, third.GetHash());
  ASSERT_EQ(fourth_hash, fourth.GetHash());
}

using RasterCacheTest = LayerTest;

TEST_F(RasterCacheTest, RasterCacheKeyIDLayerChildrenIds) {
  auto layer = std::make_shared<ContainerLayer>();

  const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));
  auto mock_layer = std::make_shared<MockLayer>(child_path);
  layer->Add(mock_layer);

  auto display_list = GetSampleDisplayList();
  auto display_list_layer = std::make_shared<DisplayListLayer>(
      SkPoint::Make(0.0f, 0.0f), display_list, false, false);
  layer->Add(display_list_layer);

  auto ids = RasterCacheKeyID::LayerChildrenIds(layer.get()).value();
  std::vector<RasterCacheKeyID> expected_ids;
  expected_ids.emplace_back(
      RasterCacheKeyID(mock_layer->unique_id(), RasterCacheKeyType::kLayer));
  expected_ids.emplace_back(RasterCacheKeyID(display_list->unique_id(),
                                             RasterCacheKeyType::kDisplayList));
  ASSERT_EQ(expected_ids[0], mock_layer->caching_key_id());
  ASSERT_EQ(expected_ids[1], display_list_layer->caching_key_id());
  ASSERT_EQ(ids, expected_ids);
}

}  // namespace testing
}  // namespace flutter

// NOLINTEND(bugprone-unchecked-optional-access)
