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

#include "content/browser/xr/service/xr_frame_sink_client_impl.h"

#include <memory>

#include "base/functional/callback.h"
#include "build/build_config.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "content/browser/browser_thread_impl.h"
#include "content/browser/compositor/surface_utils.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "services/viz/privileged/mojom/compositing/frame_sink_manager.mojom.h"

#if BUILDFLAG(IS_ANDROID)
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#endif

namespace content {
XrFrameSinkClientImpl::XrFrameSinkClientImpl(
    const content::GlobalRenderFrameHostId& global_frame_id)
    : ui_thread_task_runner_(GetUIThreadTaskRunner({})),
      global_frame_id_(global_frame_id) {
  DCHECK(IsOnUiThread())
      << "XrFrameSinkClientImpl must be constructed on the UI thread.";
}

XrFrameSinkClientImpl::~XrFrameSinkClientImpl() {
  DCHECK(IsOnUiThread())
      << "XrFrameSinkClientImpl must be destructed on the UI thread.";
  if (!initialized_)
    return;

  SurfaceDestroyed();
}

bool XrFrameSinkClientImpl::IsOnUiThread() const {
  return ui_thread_task_runner_->BelongsToCurrentThread();
}

void XrFrameSinkClientImpl::SurfaceDestroyed() {
  DCHECK(IsOnUiThread());
  if (!initialized_)
    return;

  auto* frame_sink_manager = GetHostFrameSinkManager();

  // Since this code can be run during destruction, it's theoretically possible,
  // though unlikely, that the FrameSinkManager no longer exists.
  if (frame_sink_manager) {
    if (registered_dom_frame_sink_id_.is_valid()) {
      frame_sink_manager->UnregisterFrameSinkHierarchy(
          root_frame_sink_id_, registered_dom_frame_sink_id_);
      registered_dom_frame_sink_id_ = viz::FrameSinkId();
    }
    frame_sink_manager->InvalidateFrameSinkId(root_frame_sink_id_, this, {});
  }

  // Reset the initialized state and the root FrameSinkId to an invalid value.
  initialized_ = false;
  root_frame_sink_id_ = viz::FrameSinkId();
}

std::optional<viz::SurfaceId> XrFrameSinkClientImpl::GetDOMSurface() {
  base::AutoLock lock(dom_surface_lock_);
  return dom_surface_id_;
}

viz::FrameSinkId XrFrameSinkClientImpl::FrameSinkId() {
  return root_frame_sink_id_;
}

void XrFrameSinkClientImpl::InitializeRootCompositorFrameSink(
    viz::mojom::RootCompositorFrameSinkParamsPtr root_params,
    device::DomOverlaySetup dom_setup,
    base::OnceClosure on_initialized) {
  DCHECK(!initialized_);
  DVLOG(1) << __func__;

  ui_thread_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&XrFrameSinkClientImpl::InitializeOnUiThread,
                     weak_ptr_factory_.GetWeakPtr(), std::move(root_params),
                     dom_setup, std::move(on_initialized)));
}

void XrFrameSinkClientImpl::InitializeOnUiThread(
    viz::mojom::RootCompositorFrameSinkParamsPtr root_params,
    device::DomOverlaySetup dom_setup,
    base::OnceClosure on_initialized) {
  // AllocateFrameSinkId needs to be called from the UI thread.
  DCHECK(IsOnUiThread());
  DVLOG(1) << __func__;

  root_frame_sink_id_ = AllocateFrameSinkId();
  root_params->frame_sink_id = root_frame_sink_id_;

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

  if (dom_setup != device::DomOverlaySetup::kNone) {
    ConfigureDOMOverlay();
  }

  initialized_ = true;
  std::move(on_initialized).Run();
}

void XrFrameSinkClientImpl::ConfigureDOMOverlay() {
  DCHECK(IsOnUiThread());
  base::AutoLock lock(dom_surface_lock_);

  // This is left outside of the OS_ANDROID ifdef to prevent warnings about the
  // global_frame_id_ from being unused. Since we check the render_frame_host
  // for an early return, it is in fact used.
  auto* render_frame_host = RenderFrameHostImpl::FromID(global_frame_id_);
  if (!render_frame_host)
    return;

  RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
      render_frame_host->GetOutermostMainFrameOrEmbedder()->GetView());
  CHECK(!root_view || !root_view->IsRenderWidgetHostViewChildFrame());

// Since we don't have the ability to get updates to the surface id on non-
// Android OS's, we let it stay null, which callers can use to as a signal that
// DOMOverlay will not work.
#if BUILDFLAG(IS_ANDROID)
  RenderWidgetHostViewAndroid* view =
      static_cast<RenderWidgetHostViewAndroid*>(root_view);
  if (!view)
    return;

  // The returned CallbackListSubscription manages the lifetime of this callback
  // and thus makes Unretained safe.
  surface_id_changed_subscription_ =
      view->SubscribeToSurfaceIdChanges(base::BindRepeating(
          &XrFrameSinkClientImpl::OnSurfaceIdUpdated, base::Unretained(this)));
  dom_surface_id_ = view->GetCurrentSurfaceId();
#endif

  if (dom_surface_id_ && dom_surface_id_->is_valid()) {
    const viz::FrameSinkId dom_frame_sink_id = dom_surface_id_->frame_sink_id();
    if (GetHostFrameSinkManager()->RegisterFrameSinkHierarchy(
            root_frame_sink_id_, dom_frame_sink_id)) {
      registered_dom_frame_sink_id_ = dom_frame_sink_id;
    }
  }
}

void XrFrameSinkClientImpl::OnSurfaceIdUpdated(
    const viz::SurfaceId& dom_surface_id) {
  base::AutoLock lock(dom_surface_lock_);
  dom_surface_id_ = dom_surface_id;
}

}  // namespace content
