// 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 "content/browser/picture_in_picture/picture_in_picture_session.h"

#include <utility>

#include "base/functional/callback_helpers.h"
#include "content/browser/picture_in_picture/picture_in_picture_service_impl.h"
#include "content/browser/picture_in_picture/video_picture_in_picture_window_controller_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"

namespace content {

PictureInPictureSession::PictureInPictureSession(
    PictureInPictureServiceImpl* service,
    const MediaPlayerId& player_id,
    mojo::PendingAssociatedRemote<media::mojom::MediaPlayer> player_remote,
    mojo::PendingReceiver<blink::mojom::PictureInPictureSession> receiver,
    mojo::PendingRemote<blink::mojom::PictureInPictureSessionObserver> observer)
    : service_(service),
      receiver_(this, std::move(receiver)),
      player_id_(player_id),
      media_player_remote_(std::move(player_remote)),
      observer_(std::move(observer)) {
  receiver_.set_disconnect_handler(base::BindOnce(
      &PictureInPictureSession::OnConnectionError, base::Unretained(this)));
  media_player_remote_.set_disconnect_handler(base::BindOnce(
      &PictureInPictureSession::OnPlayerGone, base::Unretained(this)));
}

PictureInPictureSession::~PictureInPictureSession() {
  DCHECK(is_stopping_);
}

void PictureInPictureSession::Stop(StopCallback callback) {
  StopInternal(std::move(callback));
}

void PictureInPictureSession::Update(
    uint32_t player_id,
    mojo::PendingAssociatedRemote<media::mojom::MediaPlayer> player_remote,
    const viz::SurfaceId& surface_id,
    const gfx::Size& natural_size,
    bool show_play_pause_button) {
  player_id_ =
      MediaPlayerId(service_->render_frame_host().GetGlobalId(), player_id);

  media_player_remote_.reset();
  media_player_remote_.Bind(std::move(player_remote));
  media_player_remote_.set_disconnect_handler(base::BindOnce(
      &PictureInPictureSession::OnPlayerGone, base::Unretained(this)));

  GetController().EmbedSurface(surface_id, natural_size);
  GetController().SetShowPlayPauseButton(show_play_pause_button);
}

void PictureInPictureSession::UpdateMediaPosition(
    const media_session::MediaPosition& media_position) {
  GetController().SetMediaPosition(media_position);
}

void PictureInPictureSession::OnPlayerGone() {
  player_id_.reset();
  GetController().SetPlaybackControlsVisibility(false);
}

void PictureInPictureSession::NotifyWindowResized(const gfx::Size& size) {
  observer_->OnWindowSizeChanged(size);
}

mojo::AssociatedRemote<media::mojom::MediaPlayer>&
PictureInPictureSession::GetMediaPlayerRemote() {
  DCHECK(media_player_remote_.is_bound());
  return media_player_remote_;
}

void PictureInPictureSession::Disconnect() {
  // |is_stopping_| shouldn't be true for the implementation in //chrome but if
  // the WebContentsDelegate's Picture-in-Picture calls are empty, it's possible
  // for `Disconnect()` to be called even after `StopInternal()` as the
  // expectation of self-destruction will no longer be true.
  if (is_stopping_)
    return;

  is_stopping_ = true;
  observer_->OnStopped();
}

void PictureInPictureSession::Shutdown() {
  if (is_stopping_)
    return;

  StopInternal(base::NullCallback());
}

void PictureInPictureSession::StopInternal(StopCallback callback) {
  DCHECK(!is_stopping_);

  is_stopping_ = true;

  // `OnStopped()` should only be called if there is no callback to run, as a
  // contract in the API.
  if (callback)
    std::move(callback).Run();
  else
    observer_->OnStopped();

  // |this| will be deleted after this call.
  GetWebContentsImpl()->ExitPictureInPicture();
}

void PictureInPictureSession::OnConnectionError() {
  // There is possibility that OnConnectionError arrives between StopInternal()
  // is called and |this| is deleted. As a result, DCHECK in StopInternal()
  // will fail.
  if (is_stopping_)
    return;

  // StopInternal() will self destruct which will close the bindings.
  StopInternal(base::NullCallback());
}

WebContentsImpl* PictureInPictureSession::GetWebContentsImpl() {
  return static_cast<WebContentsImpl*>(
      WebContents::FromRenderFrameHost(&service_->render_frame_host()));
}

VideoPictureInPictureWindowControllerImpl&
PictureInPictureSession::GetController() {
  return *VideoPictureInPictureWindowControllerImpl::GetOrCreateForWebContents(
      GetWebContentsImpl());
}

}  // namespace content
