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

#include <utility>

#include "base/command_line.h"
#include "base/rand_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "components/viz/demo/client/demo_client.h"
#include "components/viz/demo/common/switches.h"
#include "components/viz/demo/host/demo_host_display_client.h"
#include "components/viz/host/renderer_settings_creation.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"

namespace demo {

DemoHost::DemoHost(
    gfx::AcceleratedWidget widget,
    const gfx::Size& size,
    mojo::PendingReceiver<viz::mojom::FrameSinkManagerClient> client_receiver,
    mojo::PendingRemote<viz::mojom::FrameSinkManager> frame_sink_manager_remote)
    : widget_(widget), size_(size), thread_("DemoHost") {
  CHECK(thread_.Start());
  thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&DemoHost::Initialize, base::Unretained(this),
                                std::move(client_receiver),
                                std::move(frame_sink_manager_remote)));
}

DemoHost::~DemoHost() = default;

void DemoHost::Resize(const gfx::Size& size) {
  thread_.task_runner()->PostTask(
      FROM_HERE,
      base::BindOnce(&DemoHost::ResizeOnThread, base::Unretained(this), size));
}

void DemoHost::ResizeOnThread(const gfx::Size& size) {
  if (size_ == size)
    return;
  size_ = size;
  display_private_->Resize(size);

  // Every size change for a client needs a new LocalSurfaceId.
  allocator_.GenerateId();
  root_client_->Resize(size_, allocator_.GetCurrentLocalSurfaceId());
}

void DemoHost::EmbedClients(DemoClient* embedder_client,
                            const gfx::Rect& child_bounds) {
  // Generate a FrameSinkId for the client. Each client can have any number of
  // frame-sinks, and these frame-sinks should share the same |client_id|
  // component for the FrameSinkId. In this demo however, each client has a
  // single FrameSink, so the client-id can just be randomly generated, and it
  // doesn't make a difference.
  uint64_t rand = base::RandUint64();
  viz::FrameSinkId frame_sink_id(rand >> 32, rand & 0xffffffff);

  // Register the frame sink and its hierarchy.
  host_frame_sink_manager_.RegisterFrameSinkId(
      frame_sink_id, this, viz::ReportFirstSurfaceActivation::kNo);
  host_frame_sink_manager_.RegisterFrameSinkHierarchy(
      embedder_client->frame_sink_id(), frame_sink_id);

  // Next, create a mojom::CompositorFrameSink for the client, so that the
  // client is able to submit visual (and hit-test) content to the viz service.
  // Note that in this demo app, the host is setting up the message-pipes, and
  // then sending the end-points to the embedded-client and the viz-service.
  // However, it is possible for the embedded-client to initiate the creation of
  // the message-pipes, in which case, the client would need to send the
  // service-end-points to the host (via a non-viz API), so that the host can in
  // turn send them to the service.
  mojo::PendingRemote<viz::mojom::CompositorFrameSinkClient> client_remote;
  auto client_receiver = client_remote.InitWithNewPipeAndPassReceiver();

  mojo::PendingRemote<viz::mojom::CompositorFrameSink> sink_remote;
  auto sink_receiver = sink_remote.InitWithNewPipeAndPassReceiver();

  host_frame_sink_manager_.CreateCompositorFrameSink(
      frame_sink_id, std::move(sink_receiver), std::move(client_remote));

  // At this point, the host is done setting everything up. Now it is up to the
  // new client to take over the communication (i.e. the mojo message pipes)
  // with the service for the frame-sink. The embedder (i.e. the parent client)
  // also needs to know about the new client's FrameSinkId, so that it is able
  // to embed it. Both the embedder and the embedded client also need to use
  // the same LocalSurfaceId for the embedding. Typically, the embedder is the
  // one that generates the LocalSurfaceId for the embedded client. However, it
  // is possible for another source (e.g. the viz-host) to generate the
  // LocalSurfaceId, and dispatch that separately to both the embedder and the
  // embedded clients. There is no specific viz-API for communicating these
  // FrameSinkId and LocalSurfaceId between these clients. In chrome, these
  // happen through the content API (or through the window-service API in
  // ChromeOS).
  // In this demo app, the embedder-client is assigning the LocalSurfaceId
  // (through DemoClient).

  auto lsid_allocation = embedder_client->Embed(frame_sink_id, child_bounds);
  auto embedded_client = std::make_unique<DemoClient>(
      frame_sink_id, lsid_allocation, child_bounds);
  embedded_client->Initialize(std::move(client_receiver),
                              std::move(sink_remote));
  if (embedder_client == root_client_.get()) {
    // Embed another client after a second. This could embed the client
    // immediately here too if desired. The delay is to demonstrate asynchronous
    // usage of the API.
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&DemoHost::EmbedClients, base::Unretained(this),
                       embedded_client.get(), gfx::Rect(125, 125, 150, 150)),
        base::Seconds(1));
  }
  embedded_clients_.push_back(std::move(embedded_client));
}

void DemoHost::Initialize(
    mojo::PendingReceiver<viz::mojom::FrameSinkManagerClient> receiver,
    mojo::PendingRemote<viz::mojom::FrameSinkManager> remote) {
  host_frame_sink_manager_.BindAndSetManager(std::move(receiver), nullptr,
                                             std::move(remote));

  display_client_ = std::make_unique<demo::DemoHostDisplayClient>(widget_);

  auto root_params = viz::mojom::RootCompositorFrameSinkParams::New();

  // Create interfaces for a root CompositorFrameSink.
  mojo::PendingAssociatedRemote<viz::mojom::CompositorFrameSink> sink_remote;
  root_params->compositor_frame_sink =
      sink_remote.InitWithNewEndpointAndPassReceiver();
  auto client_receiver = root_params->compositor_frame_sink_client
                             .InitWithNewPipeAndPassReceiver();
  root_params->display_private =
      display_private_.BindNewEndpointAndPassReceiver();
  root_params->display_client = display_client_->GetBoundRemote(nullptr);

  constexpr viz::FrameSinkId root_frame_sink_id(0xdead, 0xbeef);
  root_params->frame_sink_id = root_frame_sink_id;
  root_params->widget = widget_;

  base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
  root_params->gpu_compositing = cmd_line->HasSwitch(switches::kVizDemoUseGPU);

  root_params->renderer_settings = viz::CreateRendererSettings();

  host_frame_sink_manager_.RegisterFrameSinkId(
      root_params->frame_sink_id, this, viz::ReportFirstSurfaceActivation::kNo);
  host_frame_sink_manager_.CreateRootCompositorFrameSink(
      std::move(root_params));

  display_private_->Resize(size_);
  display_private_->SetDisplayVisible(true);

  // Initialize as a client now, since the host has to submit compositor frames
  // like any other clients.
  // The 'root' is not embedded by anything else. However, it still needs to
  // have a valid LocalSurfaceId, which changes when root changes size (or
  // device-scale-factor etc.).
  allocator_.GenerateId();
  root_client_ = std::make_unique<DemoClient>(
      root_frame_sink_id, allocator_.GetCurrentLocalSurfaceId(),
      gfx::Rect(size_));
  root_client_->Initialize(std::move(client_receiver), std::move(sink_remote));

  // Embed a new client into the root after the first second.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&DemoHost::EmbedClients, base::Unretained(this),
                     root_client_.get(), gfx::Rect(50, 50, 300, 300)),
      base::Seconds(1));
}

void DemoHost::OnFirstSurfaceActivation(const viz::SurfaceInfo& surface_info) {}

void DemoHost::OnFrameTokenChanged(uint32_t frame_token,
                                   base::TimeTicks activation_time) {}

}  // namespace demo
