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

#include "ash/fast_ink/fast_ink_host.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/fast_ink/fast_ink_host_test_api.h"
#include "ash/frame_sink/frame_sink_holder.h"
#include "ash/frame_sink/test/frame_sink_host_test_base.h"
#include "ash/frame_sink/test/test_begin_frame_source.h"
#include "ash/frame_sink/test/test_layer_tree_frame_sink.h"
#include "ash/frame_sink/ui_resource.h"
#include "ash/frame_sink/ui_resource_manager.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/ash_test_helper.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "cc/base/math_util.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/compositor_render_pass.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/common/resources/resource_id.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/display/display_switches.h"
#include "ui/display/screen.h"
#include "ui/gfx/buffer_types.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/views/widget/widget.h"

namespace ash {
namespace {

struct FastInkHostTestParams {
  std::string first_display_specs;
  bool auto_update = false;
  gfx::Rect content_rect;
  gfx::Rect expected_quad_rect;
  gfx::Rect expected_quad_layer_rect;
};

class FastInkHostFrameSubmissionTest
    : public FrameSinkHostTestBase<FastInkHost>,
      public ::testing::WithParamInterface<
          std::tuple<FastInkHostTestParams, bool>> {
 public:
  FastInkHostFrameSubmissionTest() {
    scoped_feature_list_.InitWithFeatureStates({
        {features::kFrameSinkHostNewBackend, is_new_backend()},
        {features::kFastInkHostNewBackend, is_new_backend()},
    });
  }

  bool is_new_backend() const { return std::get<1>(GetParam()); }
  const FastInkHostTestParams& test_params() const {
    return std::get<0>(GetParam());
  }

  void SetUp() override {
    SetDisplaySpecs(test_params().first_display_specs);
    FrameSinkHostTestBase<FastInkHost>::SetUp();
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_P(FastInkHostFrameSubmissionTest,
       CorrectFrameSubmittedToLayerTreeFrameSink) {
  // Request the first frame.
  OnBeginFrame();

  const auto& params = test_params();

  SCOPED_TRACE(base::StringPrintf(
      "Test params: first_display_specs=%s | auto_update=%s | content_rect=%s "
      "| expected_quad_rect=%s | expected_quad_layer_rect=%s",
      params.first_display_specs.c_str(), params.auto_update ? "true" : "false",
      params.content_rect.ToString().c_str(),
      params.expected_quad_rect.ToString().c_str(),
      params.expected_quad_layer_rect.ToString().c_str()));

  constexpr gfx::Rect kTestTotalDamageRectInDIP = gfx::Rect(0, 0, 50, 25);

  if (params.auto_update) {
    frame_sink_host()->AutoUpdateSurface(params.content_rect,
                                         kTestTotalDamageRectInDIP);

    // When host auto updates, it only submits a frame when requested by
    // LayerTreeFrameSink via a BeginFrameSource.
    OnBeginFrame();
  } else {
    frame_sink_host()->UpdateSurface(params.content_rect,
                                     kTestTotalDamageRectInDIP,
                                     /*synchronous_draw=*/true);
  }

  const viz::CompositorFrame& frame =
      layer_tree_frame_sink()->GetLatestReceivedFrame();

  const viz::CompositorRenderPassList& render_pass_list =
      frame.render_pass_list;

  ASSERT_EQ(render_pass_list.size(), 1u);
  auto& quad_list = render_pass_list.front()->quad_list;

  ASSERT_EQ(quad_list.size(), 1u);

  viz::DrawQuad* quad = quad_list.back();
  EXPECT_EQ(quad->material, viz::DrawQuad::Material::kTextureContent);

  EXPECT_EQ(quad->rect, params.expected_quad_rect);
  EXPECT_EQ(quad->visible_rect, params.expected_quad_rect);

  ASSERT_EQ(render_pass_list.front()->shared_quad_state_list.size(), 1u);
  auto* shared_quad_state =
      render_pass_list.front()->shared_quad_state_list.front();

  EXPECT_EQ(shared_quad_state->quad_layer_rect,
            params.expected_quad_layer_rect);
  EXPECT_EQ(shared_quad_state->visible_quad_layer_rect,
            params.expected_quad_layer_rect);

  auto* texture_quad = viz::TextureDrawQuad::MaterialCast(quad);
  if (features::IsFastInkHostLowPriorityHintEnabled()) {
    EXPECT_EQ(texture_quad->overlay_priority_hint,
              params.auto_update ? viz::OverlayPriority::kRegular
                                 : viz::OverlayPriority::kNone);
  } else {
    EXPECT_EQ(frame.resource_list.back().GetIsOverlayCandidate(),
              params.auto_update);
  }
}

INSTANTIATE_TEST_SUITE_P(
    /* no prefix */,
    FastInkHostFrameSubmissionTest,
    testing::Combine(
        testing::Values(
            // When auto updating surface, we update the full surface, ignoring
            // the content_rect.
            FastInkHostTestParams{
                .first_display_specs = "1000x500",
                .auto_update = true,
                .content_rect = gfx::Rect(10, 10),
                .expected_quad_rect = gfx::Rect(0, 0, 1000, 500),
                .expected_quad_layer_rect = gfx::Rect(0, 0, 1000, 500)},
            FastInkHostTestParams{
                .first_display_specs = "1000x500*2",
                .auto_update = true,
                .content_rect = gfx::Rect(10, 10),
                .expected_quad_rect = gfx::Rect(0, 0, 1000, 500),
                .expected_quad_layer_rect = gfx::Rect(0, 0, 1000, 500)},
            FastInkHostTestParams{
                .first_display_specs = "1000x500*2/r",
                .auto_update = true,
                .content_rect = gfx::Rect(10, 10),
                .expected_quad_rect = gfx::Rect(0, 0, 1000, 500),
                .expected_quad_layer_rect = gfx::Rect(0, 0, 500, 1000)},
            // When auto updating is off, we update the surface enclosed by
            // content_rect.
            FastInkHostTestParams{
                .first_display_specs = "1000x500",
                .auto_update = false,
                .content_rect = gfx::Rect(10, 10),
                .expected_quad_rect = gfx::Rect(0, 0, 10, 10),
                .expected_quad_layer_rect = gfx::Rect(0, 0, 1000, 500)},
            FastInkHostTestParams{
                .first_display_specs = "1000x500*2",
                .auto_update = false,
                .content_rect = gfx::Rect(10, 10),
                .expected_quad_rect = gfx::Rect(0, 0, 20, 20),
                .expected_quad_layer_rect = gfx::Rect(0, 0, 1000, 500)},
            FastInkHostTestParams{
                .first_display_specs = "1000x500*2/l",
                .auto_update = false,
                .content_rect = gfx::Rect(10, 15),
                .expected_quad_rect = gfx::Rect(0, 480, 30, 20),
                .expected_quad_layer_rect = gfx::Rect(0, 0, 500, 1000)},
            // If content rect is partially outside of the buffer, quad rect is
            // clipped by buffer size.
            FastInkHostTestParams{
                .first_display_specs = "1000x500",
                .auto_update = false,
                .content_rect = gfx::Rect(995, 0, 10, 10),
                .expected_quad_rect = gfx::Rect(995, 0, 5, 10),
                .expected_quad_layer_rect = gfx::Rect(0, 0, 1000, 500)},
            FastInkHostTestParams{
                .first_display_specs = "3000x2000*2.252252",
                .auto_update = false,
                .content_rect = gfx::Rect(0, 0, 1332, 888),
                .expected_quad_rect = gfx::Rect(0, 0, 3000, 2000),
                .expected_quad_layer_rect = gfx::Rect(0, 0, 3000, 2000)}),
        testing::Bool()));

class FastInkHostTest : public FrameSinkHostTestBase<FastInkHost>,
                        public ::testing::WithParamInterface<bool> {
 public:
  FastInkHostTest() {
    scoped_feature_list_.InitWithFeatureStates({
        {features::kFrameSinkHostNewBackend, GetParam()},
        {features::kFastInkHostNewBackend, GetParam()},
    });
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_P(FastInkHostTest, RecreateGpuBufferOnLosingFrameSink) {
  FastInkHostTestApi fast_ink_host_test(frame_sink_host());

  // Buffer is not initialized when there is no begin frame received.
  ASSERT_FALSE(fast_ink_host_test.client_shared_image());

  // Request the first frame. It will call
  // `FrameSinkHost::OnFirstFrameRequested()` initializing the GPU buffer.
  OnBeginFrame();

  // MappableSI should be initialized after receiving the first begin frame.
  ASSERT_TRUE(fast_ink_host_test.client_shared_image());

  // A new frame-sink will be created. FastInkHost should also create a new
  // shared image.
  frame_sink_host()
      ->frame_sink_holder_for_testing()
      ->DidLoseLayerTreeFrameSink();

  // MappableSI should be destroyed after losing a frame sink.
  EXPECT_FALSE(fast_ink_host_test.client_shared_image());

  // A new MappableSI should be initialized once
  // `FrameSinkHost::OnFirstFrameRequested()` is called for the new
  // FrameSinkHolder.
  OnBeginFrame();

  EXPECT_TRUE(fast_ink_host_test.client_shared_image());
}

TEST_P(FastInkHostTest, DelayPaintingUntilReceivingFirstBeginFrame) {
  FastInkHostTestApi fast_ink_host_test(frame_sink_host());

  // Buffer is not initialized when there is no begin frame received.
  ASSERT_FALSE(fast_ink_host_test.client_shared_image());
  EXPECT_EQ(fast_ink_host_test.pending_bitmaps_size(), 0);

  int pending_bitmaps_size = 0;
  for (SkColor color : {SK_ColorRED, SK_ColorYELLOW, SK_ColorGREEN}) {
    {
      const gfx::Rect damage_rect_in_window =
          gfx::Rect(host_window()->bounds().size());
      auto paint = frame_sink_host()->CreateScopedPaint(damage_rect_in_window);
      paint->canvas().DrawRect(gfx::RectF(damage_rect_in_window), color);
    }
    // The bitmap is waiting to be drawn because no gpu memory buffer is
    // initialized.
    ++pending_bitmaps_size;
    EXPECT_EQ(fast_ink_host_test.pending_bitmaps_size(), pending_bitmaps_size);
  }

  // Request the first frame.
  OnBeginFrame();

  // MappableSI should be initialized after receiving the first begin frame.
  ASSERT_TRUE(fast_ink_host_test.client_shared_image());
  // Pending bitmaps should be drawn and cleared.
  EXPECT_EQ(fast_ink_host_test.pending_bitmaps_size(), 0);

  auto mapping = fast_ink_host_test.client_shared_image()->Map();
  ASSERT_TRUE(mapping);
  // Pending bitmaps should be correctly copied to the MappableSI's buffer.
  EXPECT_EQ(*reinterpret_cast<SkColor*>(mapping->GetMemoryForPlane(0).data()),
            SK_ColorGREEN);
}

INSTANTIATE_TEST_SUITE_P(All, FastInkHostTest, testing::Bool());

}  // namespace
}  // namespace ash
