// 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_frame_utils.h"

#include "ash/constants/ash_features.h"
#include "ash/frame_sink/frame_sink_host.h"
#include "ash/frame_sink/ui_resource.h"
#include "base/check.h"
#include "base/logging.h"
#include "cc/base/math_util.h"
#include "components/viz/client/client_resource_provider.h"
#include "components/viz/common/gpu/context_provider.h"
#include "components/viz/common/quads/compositor_frame.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 "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "ui/aura/env.h"
#include "ui/aura/window_tree_host.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/buffer_types.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/geometry/transform.h"

namespace ash::fast_ink_internal {
namespace {

// Get a UiResource to paint the texture. We try to reuse any
// existing resources in `resource_manager` before creating a new resource.
std::unique_ptr<UiResource> AcquireUiResource(
    UiResourceManager* resource_manager,
    const scoped_refptr<gpu::ClientSharedImage>& shared_image,
    gpu::SyncToken sync_token) {
  CHECK(shared_image);
  std::unique_ptr<UiResource> resource = resource_manager->GetResourceToReuse(
      shared_image->size(), kFastInkSharedImageFormat, kFastInkUiSourceId);

  if (resource) {
    CHECK(shared_image == resource->client_shared_image());
  } else {
    resource = CreateUiResource(kFastInkUiSourceId, shared_image, sync_token);
  }

  return resource;
}

// Configures and adds a `TextureDrawQuad` to the `render_pass_out`.
void AppendQuad(const viz::TransferableResource& resource,
                const gfx::Rect& output_rect,
                const gfx::Rect& quad_rect,
                const gfx::Size& buffer_size,
                const gfx::Transform& buffer_to_target_transform,
                bool auto_update,
                viz::CompositorRenderPass& render_pass_out) {
  viz::SharedQuadState* quad_state =
      render_pass_out.CreateAndAppendSharedQuadState();

  quad_state->SetAll(buffer_to_target_transform,
                     /*layer_rect=*/output_rect,
                     /*visible_layer_rect=*/output_rect,
                     /*filter_info=*/gfx::MaskFilterInfo(),
                     /*clip=*/std::nullopt, /*contents_opaque=*/false,
                     /*opacity_f=*/1.f,
                     /*blend=*/SkBlendMode::kSrcOver,
                     /*sorting_context=*/0,
                     /*layer_id=*/0u, /*fast_rounded_corner=*/false);

  viz::TextureDrawQuad* texture_quad =
      render_pass_out.CreateAndAppendDrawQuad<viz::TextureDrawQuad>();

  texture_quad->SetNew(
      quad_state, quad_rect, quad_rect,
      /*needs_blending=*/true, resource.id, gfx::PointF(quad_rect.origin()),
      gfx::PointF(quad_rect.bottom_right()), SkColors::kTransparent,
      /*nearest=*/false,
      /*secure_output=*/false, gfx::ProtectedVideoType::kClear,
      /*is_tex_coords_normalized=*/false);

  if (features::IsFastInkHostLowPriorityHintEnabled() && !auto_update) {
    texture_quad->overlay_priority_hint = viz::OverlayPriority::kNone;
  }
}

}  // namespace

gfx::Rect BufferRectFromWindowRect(
    const gfx::Transform& window_to_buffer_transform,
    const gfx::Size& buffer_size,
    const gfx::Rect& window_rect) {
  gfx::Rect buffer_rect = cc::MathUtil::MapEnclosingClippedRect(
      window_to_buffer_transform, window_rect);
  // Buffer rect is not bigger than actual buffer.
  buffer_rect.Intersect(gfx::Rect(buffer_size));
  return buffer_rect;
}

scoped_refptr<gpu::ClientSharedImage> CreateMappableSharedImage(
    const gfx::Size& size,
    gpu::SharedImageUsageSet shared_image_usage,
    gfx::BufferUsage buffer_usage) {
  return GetContextProvider()->SharedImageInterface()->CreateSharedImage(
      {kFastInkSharedImageFormat, size, gfx::ColorSpace(), shared_image_usage,
       "FastInkHostUIResource"},
      gpu::kNullSurfaceHandle, buffer_usage);
}

std::unique_ptr<UiResource> CreateUiResource(
    UiSourceId ui_source_id,
    const scoped_refptr<gpu::ClientSharedImage>& shared_image,
    gpu::SyncToken sync_token) {
  DCHECK(ui_source_id > 0);
  CHECK(shared_image);

  auto context_provider = GetContextProvider();

  if (!context_provider) {
    LOG(ERROR) << "Failed to acquire a context provider";
    return nullptr;
  }

  auto resource = std::make_unique<UiResource>(
      context_provider->SharedImageInterface(), shared_image);

  resource->sync_token = sync_token;
  resource->damaged = true;
  resource->ui_source_id = ui_source_id;
  return resource;
}

std::unique_ptr<viz::CompositorFrame> CreateCompositorFrame(
    const viz::BeginFrameAck& begin_frame_ack,
    const gfx::Rect& content_rect,
    const gfx::Rect& total_damage_rect,
    bool auto_update,
    const aura::Window& host_window,
    UiResourceManager& resource_manager,
    viz::ClientResourceProvider& client_resource_provider,
    const scoped_refptr<gpu::ClientSharedImage>& shared_image,
    gpu::SyncToken sync_token) {
  float device_scale_factor = host_window.layer()->device_scale_factor();
  const gfx::Transform& window_to_buffer_transform =
      host_window.GetHost()->GetRootTransform();
  gfx::Size window_size_in_dip = host_window.GetBoundsInScreen().size();

  const gfx::Size window_size_in_pixel =
      gfx::ScaleToEnclosingRectIgnoringError(gfx::Rect{window_size_in_dip},
                                             device_scale_factor)
          .size();

  // NOTE: `shared_image` is guaranteed to be non-null by contract of this
  // method.
  CHECK(shared_image);

  gfx::Transform target_to_buffer_transform(window_to_buffer_transform);
  target_to_buffer_transform.Scale(1.f / device_scale_factor,
                                   1.f / device_scale_factor);

  gfx::Transform buffer_to_target_transform =
      target_to_buffer_transform.GetCheckedInverse();

  const gfx::Rect output_rect(window_size_in_pixel);

  gfx::Rect quad_rect;
  gfx::Rect damage_rect;

  const gfx::Size buffer_size = shared_image->size();

  // Continuously redraw the full output rectangle when in auto-update mode.
  // This is necessary in order to allow single buffered updates without having
  // buffer changes outside the contents area cause artifacts.
  if (auto_update) {
    quad_rect = gfx::Rect(buffer_size);
    damage_rect = gfx::Rect(output_rect);
  } else {
    // Use minimal quad and damage rectangles when auto-refresh mode is off.
    quad_rect = BufferRectFromWindowRect(window_to_buffer_transform,
                                         buffer_size, content_rect);
    damage_rect = gfx::ToEnclosingRect(
        gfx::ConvertRectToPixels(total_damage_rect, device_scale_factor));

    // To ensure that the damage_rect is not bigger than the output_rect. We can
    // have 1px off errors when converting from dip to pixel values for certain
    // device scale factor values what can lead the damage_rect to be bigger
    // than output_rect.
    damage_rect.Intersect(output_rect);
  }

  auto frame = std::make_unique<viz::CompositorFrame>();
  frame->metadata.begin_frame_ack = begin_frame_ack;
  frame->metadata.begin_frame_ack.has_damage = true;
  frame->metadata.device_scale_factor = device_scale_factor;

  auto render_pass = viz::CompositorRenderPass::Create(
      /*shared_quad_state_list_size=*/1, /*quad_list_size=*/1);

  render_pass->SetNew(viz::CompositorRenderPassId{1}, output_rect, damage_rect,
                      buffer_to_target_transform);

  if (features::IsFrameSinkHostNewBackendEnabled() &&
      features::IsFastInkHostNewBackendEnabled()) {
    auto context_provider = GetContextProvider();
    if (!context_provider) {
      LOG(ERROR) << "Failed to acquire a context provider";
      return nullptr;
    }

    scoped_refptr<gpu::SharedImageInterface> sii =
        context_provider->SharedImageInterface();

    gpu::SyncToken updated_sync_token =
        shared_image->BackingWasExternallyUpdated(sync_token);
    sii->VerifySyncToken(updated_sync_token);

    viz::TransferableResource transferable_resource;
    if (!features::IsFastInkHostLowPriorityHintEnabled()) {
      transferable_resource = viz::TransferableResource::Make(
          shared_image, viz::TransferableResource::ResourceSource::kUI,
          updated_sync_token,
          /*override=*/{.is_overlay_candidate = auto_update});
    } else {
      transferable_resource = viz::TransferableResource::Make(
          shared_image, viz::TransferableResource::ResourceSource::kUI,
          updated_sync_token);
    }

    viz::ResourceId resource_id = client_resource_provider.ImportResource(
        transferable_resource,
        base::BindOnce([](const gpu::SyncToken& sync_token, bool is_lost) {}));

    client_resource_provider.PrepareSendToParent(
        {resource_id}, &frame->resource_list, sii.get());

    // In auto_update mode, we use hardware overlays to render the content.
    AppendQuad(frame->resource_list.back(), output_rect, quad_rect, buffer_size,
               buffer_to_target_transform, auto_update, *render_pass);

    client_resource_provider.RemoveImportedResource(resource_id);
  } else {
    auto resource =
        AcquireUiResource(&resource_manager, shared_image, sync_token);

    if (!resource) {
      return nullptr;
    }

    if (!features::IsFastInkHostLowPriorityHintEnabled()) {
      resource->is_overlay_candidate = auto_update;
    }

    if (resource->damaged) {
      resource->sync_token =
          resource->client_shared_image()->BackingWasExternallyUpdated(
              resource->sync_token);
      resource->shared_image_interface->VerifySyncToken(resource->sync_token);
      resource->damaged = false;
    }

    viz::TransferableResource transferable_resource =
        resource_manager.OfferAndPrepareResourceForExport(std::move(resource));

    // In auto_update mode, we use hardware overlays to render the content.
    AppendQuad(transferable_resource, output_rect, quad_rect, buffer_size,
               buffer_to_target_transform, auto_update, *render_pass);

    frame->resource_list.push_back(std::move(transferable_resource));
  }

  frame->render_pass_list.push_back(std::move(render_pass));

  return frame;
}

scoped_refptr<viz::RasterContextProvider> GetContextProvider() {
  return aura::Env::GetInstance()
      ->context_factory()
      ->SharedMainThreadRasterContextProvider();
}

}  // namespace ash::fast_ink_internal
