// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/viz/common/quads/render_pass_io.h"

#include <array>
#include <memory>
#include <string>

#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/path_service.h"
#include "base/values.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/surface_draw_quad.h"
#include "components/viz/common/quads/tile_draw_quad.h"
#include "components/viz/common/quads/video_hole_draw_quad.h"
#include "components/viz/test/paths.h"
#include "components/viz/test/test_surface_id_allocator.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/modules/skcms/skcms.h"

#if BUILDFLAG(ARKWEB_UNITTESTS)
#include "arkweb/chromium_ext/components/viz/common/quads/render_pass_io_unittest_ext.h"
#endif

#include "ui/gfx/geometry/rrect_f.h"
#include "ui/gfx/geometry/skia_conversions.h"

namespace gfx {
struct HDRMetadata;
}

namespace viz {
namespace {

TEST(RenderPassIOTest, Default) {
  auto render_pass0 = CompositorRenderPass::Create();
  base::Value::Dict dict0 = CompositorRenderPassToDict(*render_pass0);
  auto render_pass1 = CompositorRenderPassFromDict(dict0);
  EXPECT_TRUE(render_pass1);
  base::Value::Dict dict1 = CompositorRenderPassToDict(*render_pass1);
  EXPECT_EQ(dict0, dict1);
}

TEST(RenderPassIOTest, FilterOperations) {
  auto render_pass0 = CompositorRenderPass::Create();
  {
    // Add two filters.
    cc::FilterOperation grayscale =
        cc::FilterOperation::CreateGrayscaleFilter(0.25f);
    render_pass0->filters.Append(grayscale);
    cc::FilterOperation opacity =
        cc::FilterOperation::CreateOpacityFilter(0.8f);
    render_pass0->filters.Append(opacity);
  }
  {
    // Add three backdrop filters.
    cc::FilterOperation drop_shadow =
        cc::FilterOperation::CreateDropShadowFilter(gfx::Point(1.0f, 2.0f),
                                                    0.8f, SkColors::kYellow);
    render_pass0->backdrop_filters.Append(drop_shadow);
    cc::FilterOperation invert = cc::FilterOperation::CreateInvertFilter(0.64f);
    render_pass0->backdrop_filters.Append(invert);
    cc::FilterOperation zoom = cc::FilterOperation::CreateZoomFilter(2.1f, 10);
    render_pass0->backdrop_filters.Append(zoom);
  }
  {
    // Set backdrop filter bounds.
    gfx::RRectF rrect(gfx::RectF(2.f, 3.f, 4.f, 5.f), 1.5f);
    ASSERT_EQ(gfx::RRectF::Type::kSingle, rrect.GetType());
    render_pass0->backdrop_filter_bounds = SkPath::RRect(SkRRect::MakeRectXY(
        gfx::RectFToSkRect(rrect.rect()), rrect.GetSimpleRadii().x(),
        rrect.GetSimpleRadii().y()));
  }
  base::Value::Dict dict0 = CompositorRenderPassToDict(*render_pass0);
  auto render_pass1 = CompositorRenderPassFromDict(dict0);
  EXPECT_TRUE(render_pass1);
  {
    // Verify two filters are as expected.
    EXPECT_EQ(render_pass0->filters, render_pass1->filters);
    EXPECT_EQ(2u, render_pass1->filters.size());
    EXPECT_EQ(cc::FilterOperation::GRAYSCALE,
              render_pass1->filters.at(0).type());
    EXPECT_EQ(0.25f, render_pass1->filters.at(0).amount());
    EXPECT_EQ(cc::FilterOperation::OPACITY, render_pass1->filters.at(1).type());
    EXPECT_EQ(0.8f, render_pass1->filters.at(1).amount());
  }
  {
    // Verify three backdrop filters are as expected.
    EXPECT_EQ(render_pass0->backdrop_filters, render_pass1->backdrop_filters);
    EXPECT_EQ(3u, render_pass1->backdrop_filters.size());
    EXPECT_EQ(cc::FilterOperation::DROP_SHADOW,
              render_pass1->backdrop_filters.at(0).type());
    EXPECT_EQ(0.8f, render_pass1->backdrop_filters.at(0).amount());
    EXPECT_EQ(SkColors::kYellow,
              render_pass1->backdrop_filters.at(0).drop_shadow_color());
    EXPECT_EQ(gfx::Point(1.0f, 2.0f),
              render_pass1->backdrop_filters.at(0).offset());
    EXPECT_EQ(cc::FilterOperation::INVERT,
              render_pass1->backdrop_filters.at(1).type());
    EXPECT_EQ(0.64f, render_pass1->backdrop_filters.at(1).amount());
    EXPECT_EQ(cc::FilterOperation::ZOOM,
              render_pass1->backdrop_filters.at(2).type());
    EXPECT_EQ(2.1f, render_pass1->backdrop_filters.at(2).amount());
    EXPECT_EQ(10, render_pass1->backdrop_filters.at(2).zoom_inset());
  }
  {
    // Verify backdrop filter bounds are as expected.
    EXPECT_TRUE(render_pass1->backdrop_filter_bounds.has_value());
    SkRRect backdrop_filter_as_rect_0;
    SkRRect backdrop_filter_as_rect_1;
    EXPECT_TRUE(render_pass0->backdrop_filter_bounds->isRRect(
        &backdrop_filter_as_rect_0));
    EXPECT_TRUE(render_pass1->backdrop_filter_bounds->isRRect(
        &backdrop_filter_as_rect_1));
    EXPECT_EQ(backdrop_filter_as_rect_0, backdrop_filter_as_rect_1);
    EXPECT_EQ(backdrop_filter_as_rect_1.type(), SkRRect::kSimple_Type);
    EXPECT_EQ(1.5f, backdrop_filter_as_rect_1.getSimpleRadii().x());
    EXPECT_EQ(SkRect::MakeXYWH(2.f, 3.f, 4.f, 5.f),
              backdrop_filter_as_rect_1.rect());
  }
  base::Value::Dict dict1 = CompositorRenderPassToDict(*render_pass1);
  EXPECT_EQ(dict0, dict1);
}

TEST(RenderPassIOTest, SharedQuadStateList) {
  auto render_pass0 = CompositorRenderPass::Create();
  {
    // Add two SQS.
    SharedQuadState* sqs0 = render_pass0->CreateAndAppendSharedQuadState();
    ASSERT_TRUE(sqs0);
    SharedQuadState* sqs1 = render_pass0->CreateAndAppendSharedQuadState();
    ASSERT_TRUE(sqs1);
    gfx::Transform transform;
    transform.MakeIdentity();
    gfx::LinearGradient gradient_mask(40);
    gradient_mask.AddStep(/*fraction=*/0, /*alpha=*/0);
    gradient_mask.AddStep(1, 255);
    sqs1->SetAll(
        transform, gfx::Rect(0, 0, 640, 480), gfx::Rect(10, 10, 600, 400),
        gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(2.f, 3.f, 4.f, 5.f), 1.5f),
                            gradient_mask),
        gfx::Rect(5, 20, 1000, 200), /*contents_opaque=*/false,
        /*opacity_f=*/0.5f, SkBlendMode::kDstOver, /*sorting_context=*/101,
        /*layer_id=*/0u, /*fast_rounded_corner=*/true);
  }
  base::Value::Dict dict0 = CompositorRenderPassToDict(*render_pass0);
  auto render_pass1 = CompositorRenderPassFromDict(dict0);
  ASSERT_TRUE(render_pass1);
  {
    // Verify two SQS.
    EXPECT_EQ(2u, render_pass1->shared_quad_state_list.size());
    const SharedQuadState* sqs0 =
        render_pass1->shared_quad_state_list.ElementAt(0);
    EXPECT_TRUE(sqs0);
    EXPECT_TRUE(sqs0->quad_to_target_transform.IsIdentity());
    EXPECT_EQ(gfx::Rect(), sqs0->quad_layer_rect);
    EXPECT_EQ(gfx::Rect(), sqs0->visible_quad_layer_rect);
    EXPECT_FALSE(sqs0->mask_filter_info.HasRoundedCorners());
    EXPECT_FALSE(sqs0->mask_filter_info.HasGradientMask());
    EXPECT_EQ(std::nullopt, sqs0->clip_rect);
    EXPECT_TRUE(sqs0->are_contents_opaque);
    EXPECT_EQ(1.0f, sqs0->opacity);
    EXPECT_EQ(SkBlendMode::kSrcOver, sqs0->blend_mode);
    EXPECT_EQ(0, sqs0->sorting_context_id);
    EXPECT_FALSE(sqs0->is_fast_rounded_corner);

    const SharedQuadState* sqs1 =
        render_pass1->shared_quad_state_list.ElementAt(1);
    EXPECT_TRUE(sqs1);
    EXPECT_TRUE(sqs1->quad_to_target_transform.IsIdentity());
    EXPECT_EQ(gfx::Rect(0, 0, 640, 480), sqs1->quad_layer_rect);
    EXPECT_EQ(gfx::Rect(10, 10, 600, 400), sqs1->visible_quad_layer_rect);
    EXPECT_EQ(gfx::RRectF::Type::kSingle,
              sqs1->mask_filter_info.rounded_corner_bounds().GetType());
    EXPECT_EQ(1.5f,
              sqs1->mask_filter_info.rounded_corner_bounds().GetSimpleRadius());
    ASSERT_TRUE(sqs1->mask_filter_info.HasGradientMask());
    EXPECT_EQ(40, sqs1->mask_filter_info.gradient_mask()->angle());
    EXPECT_EQ(2u, sqs1->mask_filter_info.gradient_mask()->step_count());
    EXPECT_EQ(gfx::LinearGradient::Step({0, 0}),
              sqs1->mask_filter_info.gradient_mask()->steps()[0]);
    EXPECT_EQ(gfx::LinearGradient::Step({1, 255}),
              sqs1->mask_filter_info.gradient_mask()->steps()[1]);
    EXPECT_EQ(gfx::RectF(2.f, 3.f, 4.f, 5.f), sqs1->mask_filter_info.bounds());
    EXPECT_EQ(gfx::Rect(5, 20, 1000, 200), sqs1->clip_rect);
    EXPECT_FALSE(sqs1->are_contents_opaque);
    EXPECT_EQ(0.5f, sqs1->opacity);
    EXPECT_EQ(SkBlendMode::kDstOver, sqs1->blend_mode);
    EXPECT_EQ(101, sqs1->sorting_context_id);
    EXPECT_TRUE(sqs1->is_fast_rounded_corner);
  }
  base::Value::Dict dict1 = CompositorRenderPassToDict(*render_pass1);
  EXPECT_EQ(dict0, dict1);
}

TEST(RenderPassIOTest, QuadList) {
  const size_t kSharedQuadStateCount = 3;
  size_t quad_count = 0;
  const std::array<DrawQuad::Material, 8> kQuadMaterials = {
      DrawQuad::Material::kSolidColor,
      DrawQuad::Material::kVideoHole,
      DrawQuad::Material::kTextureContent,
      DrawQuad::Material::kCompositorRenderPass,
      DrawQuad::Material::kTiledContent,
      DrawQuad::Material::kSurfaceContent,
      DrawQuad::Material::kSurfaceContent,
  };
  TestSurfaceIdAllocator kSurfaceId1(FrameSinkId(1, 1));
  TestSurfaceIdAllocator kSurfaceId2(FrameSinkId(2, 2));
  auto render_pass0 = CompositorRenderPass::Create();
  {
    // Add to shared_quad_state_list.
    for (size_t ii = 0; ii < kSharedQuadStateCount; ++ii) {
      SharedQuadState* sqs = render_pass0->CreateAndAppendSharedQuadState();
      ASSERT_TRUE(sqs);
    }

    size_t sqs_index = 0;

    // Add to quad_list.
    {
      // 1. SolidColorDrawQuad
      SolidColorDrawQuad* quad =
          render_pass0->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
      quad->SetAll(render_pass0->shared_quad_state_list.ElementAt(sqs_index),
                   gfx::Rect(0, 0, 30, 40), gfx::Rect(1, 2, 20, 30), true,
                   SkColors::kRed, false);
      ++quad_count;
    }
    {
      // 2. VideoHoleDrawQuad
      VideoHoleDrawQuad* quad =
          render_pass0->CreateAndAppendDrawQuad<VideoHoleDrawQuad>();
      quad->SetAll(render_pass0->shared_quad_state_list.ElementAt(sqs_index),
                   gfx::Rect(5, 5, 305, 405), gfx::Rect(15, 15, 205, 305),
                   false, base::UnguessableToken::Create());
      ++quad_count;
    }
    {
      // 3. TextureDrawQuad
      TextureDrawQuad* quad =
          render_pass0->CreateAndAppendDrawQuad<TextureDrawQuad>();
      quad->SetAll(render_pass0->shared_quad_state_list.ElementAt(sqs_index),
                   gfx::Rect(0, 0, 100, 50), gfx::Rect(0, 0, 100, 50), false,
                   ResourceId(9u), gfx::PointF(0.f, 0.f), gfx::PointF(1.f, 1.f),
                   SkColors::kBlue, true, false,
                   gfx::ProtectedVideoType::kHardwareProtected);

      ++sqs_index;
      ++quad_count;
    }
    {
      // 4. CompositorRenderPassDrawQuad
      CompositorRenderPassDrawQuad* quad =
          render_pass0->CreateAndAppendDrawQuad<CompositorRenderPassDrawQuad>();
      quad->SetAll(render_pass0->shared_quad_state_list.ElementAt(sqs_index),
                   gfx::Rect(2, 3, 100, 50), gfx::Rect(2, 3, 100, 50), true,
                   CompositorRenderPassId{198u}, ResourceId(81u),
                   gfx::RectF(0.1f, 0.2f, 0.5f, 0.6f), gfx::Size(800, 600),
                   gfx::Vector2dF(1.1f, 0.9f), gfx::PointF(0.01f, 0.02f),
                   gfx::RectF(0.2f, 0.3f, 0.3f, 0.4f), true, 0.88f, true);
      ++sqs_index;
      ++quad_count;
    }
    {
      // 5. TileDrawQuad
      TileDrawQuad* quad =
          render_pass0->CreateAndAppendDrawQuad<TileDrawQuad>();
      quad->SetAll(render_pass0->shared_quad_state_list.ElementAt(sqs_index),
                   gfx::Rect(0, 0, 256, 512), gfx::Rect(2, 2, 250, 500), true,
                   ResourceId(512u), gfx::RectF(0.0f, 0.0f, 0.9f, 0.8f), true,
                   true);
      ++quad_count;
    }
    {
      // 6. SurfaceDrawQuad
      SurfaceDrawQuad* quad =
          render_pass0->CreateAndAppendDrawQuad<SurfaceDrawQuad>();
      quad->SetAll(render_pass0->shared_quad_state_list.ElementAt(sqs_index),
                   gfx::Rect(0, 0, 512, 256), gfx::Rect(2, 2, 500, 250), true,
                   SurfaceRange(kSurfaceId1, kSurfaceId2), SkColors::kWhite,
                   false, false, true);
      ++quad_count;
    }
    {
      // 7. SurfaceDrawQuad with no starting SurfaceId
      SurfaceDrawQuad* quad =
          render_pass0->CreateAndAppendDrawQuad<SurfaceDrawQuad>();
      quad->SetAll(render_pass0->shared_quad_state_list.ElementAt(sqs_index),
                   gfx::Rect(10, 10, 512, 256), gfx::Rect(12, 12, 500, 250),
                   true, SurfaceRange(std::nullopt, kSurfaceId1),
                   SkColors::kBlack, true, true, false);
      ++quad_count;
    }
    DCHECK_EQ(kSharedQuadStateCount, sqs_index + 1);
  }
  base::Value::Dict dict0 = CompositorRenderPassToDict(*render_pass0);
  auto render_pass1 = CompositorRenderPassFromDict(dict0);
  EXPECT_TRUE(render_pass1);
  EXPECT_EQ(kSharedQuadStateCount, render_pass1->shared_quad_state_list.size());
  EXPECT_EQ(quad_count, render_pass1->quad_list.size());
  for (size_t ii = 0; ii < quad_count; ++ii) {
    EXPECT_EQ(kQuadMaterials[ii],
              render_pass1->quad_list.ElementAt(ii)->material);
  }
  base::Value::Dict dict1 = CompositorRenderPassToDict(*render_pass1);
  EXPECT_EQ(dict0, dict1);
}

TEST(RenderPassIOTest, CompositorRenderPassList) {
  // Validate recorded render pass list data from https://www.espn.com/.
  base::FilePath test_data_dir;
#if BUILDFLAG(ARKWEB_UNITTESTS)
  ARKWEB_UNITTESTS_ASSERT_TRUE();
#else
  ASSERT_TRUE(base::PathService::Get(Paths::DIR_TEST_DATA, &test_data_dir));
#endif
  base::FilePath json_path =
      test_data_dir.Append(FILE_PATH_LITERAL("render_pass_data"))
          .Append(FILE_PATH_LITERAL("top_real_world_desktop"))
          .Append(FILE_PATH_LITERAL("espn_2018"))
          .Append(FILE_PATH_LITERAL("0463.json"));
  ASSERT_TRUE(base::PathExists(json_path));
  std::string json_text;
  ASSERT_TRUE(base::ReadFileToString(json_path, &json_text));

  std::optional<base::Value> dict0 =
      base::JSONReader::Read(json_text, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
  EXPECT_TRUE(dict0.has_value());
  CompositorRenderPassList render_pass_list;
  EXPECT_TRUE(
      CompositorRenderPassListFromDict(dict0->GetDict(), &render_pass_list));
  base::Value::Dict dict1 = CompositorRenderPassListToDict(render_pass_list);
  // Since the test file doesn't contain the field
  // 'intersects_damage_under' in its CompositorRenderPassDrawQuad, I'm
  // removing the field on dict1 for the exact comparison to work.
  base::Value::List* list = dict1.FindList("render_pass_list");
  for (auto& entry : *list) {
    base::Value::List* quad_list = entry.GetDict().FindList("quad_list");

    for (auto& quad_entry : *quad_list) {
      if (base::Value* extra_value =
              quad_entry.GetDict().Find("intersects_damage_under")) {
        EXPECT_FALSE(extra_value->GetBool());
        ASSERT_TRUE(quad_entry.GetDict().Remove("intersects_damage_under"));
      }
    }
  }

  EXPECT_EQ(dict0, dict1);
}

TEST(RenderPassIOTest, CompositorFrameData) {
  // Validate recorded multi-surface compositor frame data from a tab with
  // https://www.youtube.com/ focused, and 4 other tabs in the background.
  base::FilePath test_data_dir;
#if BUILDFLAG(ARKWEB_UNITTESTS)
  ARKWEB_UNITTESTS_ASSERT_TRUE();
#else
  ASSERT_TRUE(base::PathService::Get(Paths::DIR_TEST_DATA, &test_data_dir));
#endif
  base::FilePath json_path =
      test_data_dir.Append(FILE_PATH_LITERAL("render_pass_data"))
          .Append(FILE_PATH_LITERAL("multi_surface_test"))
          .Append(FILE_PATH_LITERAL("youtube_tab_focused"))
          .Append(FILE_PATH_LITERAL("1641.json"));
  ASSERT_TRUE(base::PathExists(json_path));
  std::string json_text;
  ASSERT_TRUE(base::ReadFileToString(json_path, &json_text));

  std::optional<base::Value> list0 =
      base::JSONReader::Read(json_text, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
  EXPECT_TRUE(list0.has_value());
  std::vector<FrameData> frame_data_list;
  EXPECT_TRUE(FrameDataFromList(list0->GetList(), &frame_data_list));
  base::Value::List list1 = FrameDataToList(frame_data_list);

  EXPECT_EQ(list0->GetList(), list1);
}

}  // namespace
}  // namespace viz