// 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/demo/client/demo_client.h"

#include <array>
#include <memory>
#include <utility>
#include <vector>

#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/surface_draw_quad.h"

namespace demo {

DemoClient::DemoClient(const viz::FrameSinkId& frame_sink_id,
                       const viz::LocalSurfaceId& local_surface_id,
                       const gfx::Rect& bounds)
    : thread_(frame_sink_id.ToString()),
      frame_sink_id_(frame_sink_id),
      local_surface_id_(local_surface_id),
      bounds_(bounds) {
  CHECK(thread_.Start());
}

DemoClient::~DemoClient() = default;

void DemoClient::Initialize(
    mojo::PendingReceiver<viz::mojom::CompositorFrameSinkClient> receiver,
    mojo::PendingAssociatedRemote<viz::mojom::CompositorFrameSink>
        sink_remote) {
  thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&DemoClient::InitializeOnThread,
                                base::Unretained(this), std::move(receiver),
                                std::move(sink_remote), mojo::NullRemote()));
}

void DemoClient::Initialize(
    mojo::PendingReceiver<viz::mojom::CompositorFrameSinkClient> receiver,
    mojo::PendingRemote<viz::mojom::CompositorFrameSink> sink_remote) {
  thread_.task_runner()->PostTask(
      FROM_HERE,
      base::BindOnce(&DemoClient::InitializeOnThread, base::Unretained(this),
                     std::move(receiver), mojo::NullAssociatedRemote(),
                     std::move(sink_remote)));
}

viz::LocalSurfaceId DemoClient::Embed(const viz::FrameSinkId& frame_sink_id,
                                      const gfx::Rect& bounds) {
  // |embeds_| is used on the client-thread in CreateFrame(). So this needs to
  // be mutated under a lock.
  base::AutoLock lock(lock_);
  allocator_.GenerateId();
  embeds_[frame_sink_id] = {allocator_.GetCurrentLocalSurfaceId(), bounds};
  return embeds_[frame_sink_id].lsid;
}

void DemoClient::Resize(const gfx::Size& size,
                        const viz::LocalSurfaceId& local_surface_id) {
  // |bounds_| and |local_surface_id_| are used on the client-thread in
  // CreateFrame(). So these need to be mutated under a lock.
  base::AutoLock lock(lock_);
  bounds_.set_size(size);
  local_surface_id_ = local_surface_id;
}

viz::CompositorFrame DemoClient::CreateFrame(const viz::BeginFrameArgs& args) {
  constexpr auto colors = std::to_array<SkColor4f>({
      SkColors::kRed,
      SkColors::kGreen,
      SkColors::kYellow,
  });
  viz::CompositorFrame frame;

  frame.metadata.begin_frame_ack = viz::BeginFrameAck(args, true);
  frame.metadata.device_scale_factor = 1.f;
  frame.metadata.frame_token = ++next_frame_token_;

  const viz::CompositorRenderPassId kRenderPassId{1};
  const gfx::Rect& output_rect = bounds_;
  const gfx::Rect& damage_rect = output_rect;
  auto render_pass = viz::CompositorRenderPass::Create();
  render_pass->SetNew(kRenderPassId, output_rect, damage_rect,
                      gfx::Transform());

  // The content of the client is one big solid-color rectangle, which includes
  // the other clients above it (in z-order). The embedded clients are first
  // added to the CompositorFrame using their SurfaceId (i.e. the FrameSinkId
  // and LocalSurfaceId), and then the big rectangle is added afterwards.
  for (auto& iter : embeds_) {
    const gfx::Rect& child_bounds = iter.second.bounds;
    const gfx::Vector2dF center(child_bounds.width() / 2,
                                child_bounds.height() / 2);

    // Apply a rotation so there's visual-update every frame in the demo.
    gfx::Transform transform;
    transform.Translate(center + child_bounds.OffsetFromOrigin());
    transform.Rotate(iter.second.degrees);
    iter.second.degrees += 0.3;
    transform.Translate(-center);

    viz::SharedQuadState* quad_state =
        render_pass->CreateAndAppendSharedQuadState();
    quad_state->SetAll(transform,
                       /*layer_rect=*/child_bounds,
                       /*visible_layer_rect=*/child_bounds,
                       /*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::SurfaceDrawQuad* embed =
        render_pass->CreateAndAppendDrawQuad<viz::SurfaceDrawQuad>();
    viz::SurfaceId surface_id(iter.first, iter.second.lsid);
    // |rect| and |visible_rect| needs to be in the quad's coord-space, so to
    // draw the whole quad, it needs to use origin (0, 0).
    embed->SetNew(quad_state,
                  /*rect=*/gfx::Rect(child_bounds.size()),
                  /*visible_rect=*/gfx::Rect(child_bounds.size()),
                  viz::SurfaceRange(surface_id), SkColors::kGray,
                  /*stretch_content_to_fill_bounds=*/false);
  }

  // Add a solid-color draw-quad for the big rectangle covering the entire
  // content-area of the client.
  viz::SharedQuadState* quad_state =
      render_pass->CreateAndAppendSharedQuadState();
  quad_state->SetAll(gfx::Transform(),
                     /*quad_layer_rect=*/output_rect,
                     /*visible_layer_rect=*/output_rect,
                     /*mask_filter_info=*/gfx::MaskFilterInfo(),
                     /*clip_rect=*/std::nullopt, /*are_contents_opaque=*/false,
                     /*opacity=*/1.f,
                     /*blend_mode=*/SkBlendMode::kSrcOver,
                     /*sorting_context=*/0,
                     /*layer_id=*/0u,
                     /*fast_rounded_corner=*/false);

  viz::SolidColorDrawQuad* color_quad =
      render_pass->CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>();
  color_quad->SetNew(quad_state, output_rect, output_rect,
                     colors[(++frame_count_ / 60) % std::size(colors)], false);

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

  return frame;
}

viz::mojom::CompositorFrameSink* DemoClient::GetPtr() {
  if (associated_sink_.is_bound())
    return associated_sink_.get();
  return sink_.get();
}

void DemoClient::InitializeOnThread(
    mojo::PendingReceiver<viz::mojom::CompositorFrameSinkClient> receiver,
    mojo::PendingAssociatedRemote<viz::mojom::CompositorFrameSink>
        associated_sink_remote,
    mojo::PendingRemote<viz::mojom::CompositorFrameSink> sink_remote) {
  receiver_.Bind(std::move(receiver));
  if (associated_sink_remote)
    associated_sink_.Bind(std::move(associated_sink_remote));
  else
    sink_.Bind(std::move(sink_remote));
  // Request for begin-frames.
  GetPtr()->SetNeedsBeginFrame(true);
}

void DemoClient::DidReceiveCompositorFrameAck(
    std::vector<viz::ReturnedResource> resources) {
  // See documentation in mojom for how this can be used.
}

void DemoClient::OnBeginFrame(const viz::BeginFrameArgs& args,
                              const viz::FrameTimingDetailsMap& timing_details,
                              std::vector<viz::ReturnedResource> resources) {
  // Generate a new compositor-frame for each begin-frame. This demo client
  // generates and submits the compositor-frame immediately. But it is possible
  // for the client to delay sending the compositor-frame. |args| includes the
  // deadline for the client before it needs to submit the compositor-frame.
  base::AutoLock lock(lock_);
  GetPtr()->SubmitCompositorFrame(local_surface_id_, CreateFrame(args),
                                  /*hit_test_region_list=*/std::nullopt,
                                  /*submit_time=*/0);
}
void DemoClient::OnBeginFramePausedChanged(bool paused) {}
void DemoClient::ReclaimResources(
    std::vector<viz::ReturnedResource> resources) {}

}  // namespace demo
