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

#ifndef COMPONENTS_MEDIA_ROUTER_BROWSER_PRESENTATION_LOCAL_PRESENTATION_MANAGER_H_
#define COMPONENTS_MEDIA_ROUTER_BROWSER_PRESENTATION_LOCAL_PRESENTATION_MANAGER_H_

#include <map>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>

#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/threading/thread_checker.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/media_router/common/media_route.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/presentation_service_delegate.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "third_party/blink/public/mojom/presentation/presentation.mojom.h"

namespace content {
class WebContents;
}

namespace media_router {
// Manages all local presentations started in the associated Profile and
// facilitates communication between the controllers and the receiver of a
// local presentation. A local presentation may be an offscreen presentation for
// Presentation API 1-UA mode, or a presentation to a wired display.
//
// Design doc:
// https://docs.google.com/document/d/1XM3jhMJTQyhEC5PDAAJFNIaKh6UUEihqZDz_ztEe4Co/edit#heading=h.hadpx5oi0gml
//
// Example usage:
//
// Receiver is created to host the local presentation and registers itself
// so that controller frames can connect to it:
//
//   LocalPresentationManager* manager =
//       LocalPresentationManagerFactory::GetOrCreateForBrowserContext(
//           web_contents_->GetBrowserContext());
//   manager->OnLocalPresentationReceiverCreated(presentation_info,
//       base::BindRepeating(
//           &PresentationServiceImpl::OnReceiverConnectionAvailable));
//
// Controlling frame establishes connection with the receiver side, resulting
// in a connection with the two endpoints being the controller
// PresentationConnectionRemote and receiver PresentationConnectionReceiver.
// Note calling this will trigger receiver frame's
// PresentationServiceImpl::OnReceiverConnectionAvailable.
//
//   manager->RegisterLocalPresentationController(
//       presentation_info,
//       std::move(controller_connection_remote),
//       std::move(receiver_connection_receiver));
//
// Invoked on receiver's PresentationServiceImpl when controller connection is
// established.
//
//   |presentation_receiver_remote_|: mojo::Remote<T> for the implementation of
//   the blink::mojom::PresentationService interface in the renderer process.
//   |controller_connection_remote|: mojo::PendingRemote<T> for
//   blink::PresentationConnection object in controlling frame's render process.
//   |receiver_connection_receiver|: mojo::PendingReceiver<T> to be bind to
//   blink::PresentationConnection object in receiver frame's render process.
//   void PresentationServiceImpl::OnReceiverConnectionAvailable(
//       const blink::mojom::PresentationInfo& presentation_info,
//       PresentationConnectionRemote controller_connection_remote,
//       PresentationConnectionReceiver receiver_connection_receiver) {
//     presentation_receiver_remote_->OnReceiverConnectionAvailable(
//         blink::mojom::PresentationInfo::From(presentation_info),
//         std::move(controller_connection_remote),
//         std::move(receiver_connection_receiver));
//   }
//
// Send message from controlling/receiver frame to receiver/controlling frame:
//
//   |target_connection_|: member variable of mojo::PendingRemote<T> for
//                         blink::PresentationConnection type, referring to
//                         remote PresentationConnectionProxy object on
//                         receiver/controlling frame.
//   |message|: Text message to be sent.
//   PresentationConnctionPtr::SendString(
//       const blink::WebString& message) {
//     target_connection_->OnMessage(
//         blink::mojom::PresentationConnectionMessage::NewMessage(
//             message.Utf8()),
//         base::BindOnce(&OnMessageReceived));
//   }
//
// A controller or receiver leaves the local presentation (e.g., due to
// navigation) by unregistering themselves from LocalPresentation object.
//
// When the receiver is no longer associated with a local presentation, it
// shall unregister itself with LocalPresentationManager. Unregistration
// will prevent additional controllers from establishing a connection with the
// receiver:
//
//   In receiver's PSImpl::Reset() {
//     local_presentation_manager->
//         OnLocalPresentationReceiverTerminated(presentation_id);
//   }
//
// This class is not thread safe. All functions must be invoked on the UI
// thread. All callbacks passed into this class will also be invoked on UI
// thread.
class LocalPresentationManager : public KeyedService {
 public:
  // Used by
  // LocalPresentationManagerFactory::BuildServiceInstanceForBrowserContext.
  LocalPresentationManager();
  LocalPresentationManager(const LocalPresentationManager&) = delete;
  LocalPresentationManager& operator=(const LocalPresentationManager&) = delete;

  ~LocalPresentationManager() override;

  // Registers controller PresentationConnectionPtr to presentation with
  // |presentation_id| and |render_frame_id|.
  // Creates a new presentation if no presentation with |presentation_id|
  // exists.
  // |controller_connection_remote|, |receiver_connection_receiver|: Not owned
  // by this class. Ownership is transferred to presentation receiver via
  // |receiver_callback| passed below.
  virtual void RegisterLocalPresentationController(
      const blink::mojom::PresentationInfo& presentation_info,
      const content::GlobalRenderFrameHostId& render_frame_id,
      mojo::PendingRemote<blink::mojom::PresentationConnection>
          controller_connection_remote,
      mojo::PendingReceiver<blink::mojom::PresentationConnection>
          receiver_connection_receiver,
      const MediaRoute& route);

  // Unregisters controller PresentationConnectionPtr to presentation with
  // |presentation_id|, |render_frame_id|. It does nothing if there is no
  // controller that matches the provided arguments. It removes presentation
  // that matches the arguments if the presentation has no |receiver_callback|
  // and any other pending controller.
  virtual void UnregisterLocalPresentationController(
      const std::string& presentation_id,
      const content::GlobalRenderFrameHostId& render_frame_id);

  // Registers |receiver_callback| and set |receiver_web_contents| to
  // presentation with |presentation_info|.
  virtual void OnLocalPresentationReceiverCreated(
      const blink::mojom::PresentationInfo& presentation_info,
      const content::ReceiverConnectionAvailableCallback& receiver_callback,
      content::WebContents* receiver_web_contents);

  // Unregisters ReceiverConnectionAvailableCallback associated with
  // |presentation_id|.
  virtual void OnLocalPresentationReceiverTerminated(
      const std::string& presentation_id);

  // Returns true if this class has a local presentation with
  // |presentation_id|.
  virtual bool IsLocalPresentation(const std::string& presentation_id);

  // Returns true if this class has a local presentation with |web_contents|.
  virtual bool IsLocalPresentation(content::WebContents* web_contents);

  // Returns nullptr if |presentation_id| is not associated with a local
  // presentation.
  virtual const MediaRoute* GetRoute(const std::string& presentation_id);

 private:
  // Represents a local presentation registered with
  // LocalPresentationManager. Contains callback to the receiver to inform
  // it of new connections established from a controller. Contains set of
  // controllers registered to LocalPresentationManager before corresponding
  // receiver.
  class LocalPresentation {
   public:
    explicit LocalPresentation(
        const blink::mojom::PresentationInfo& presentation_info);

    LocalPresentation(const LocalPresentation&) = delete;
    LocalPresentation& operator=(const LocalPresentation&) = delete;

    ~LocalPresentation();

    // Register controller with |render_frame_id|. If |receiver_callback_| has
    // been set, invoke |receiver_callback_| with |controller_connection_remote|
    // and |receiver_connection_receiver| as parameter, else creates a
    // ControllerConnection object with |controller_connection_remote| and
    // |receiver_connection_receiver|, and store it in |pending_controllers_|
    // map.
    void RegisterController(
        const content::GlobalRenderFrameHostId& render_frame_id,
        mojo::PendingRemote<blink::mojom::PresentationConnection>
            controller_connection_remote,
        mojo::PendingReceiver<blink::mojom::PresentationConnection>
            receiver_connection_receiver,
        const MediaRoute& route);

    // Unregister controller with |render_frame_id|. Do nothing if there is no
    // pending controller with |render_frame_id|.
    void UnregisterController(
        const content::GlobalRenderFrameHostId& render_frame_id);

    // Register |receiver_callback| and set |receiver_web_contents| to current
    // local_presentation object. For each controller in |pending_controllers_|
    // map, invoke |receiver_callback| with controller as parameter. Clear
    // |pending_controllers_| map afterwards.
    void RegisterReceiver(
        const content::ReceiverConnectionAvailableCallback& receiver_callback,
        content::WebContents* receiver_web_contents);

   private:
    friend class LocalPresentationManagerTest;
    friend class LocalPresentationManager;

    // Returns false if receiver_callback_ is null and there are no pending
    // controllers.
    bool IsValid() const;

    const blink::mojom::PresentationInfo presentation_info_;
    std::optional<MediaRoute> route_;
    raw_ptr<content::WebContents, DanglingUntriaged> receiver_web_contents_ =
        nullptr;

    // Callback to invoke whenever a receiver connection is available.
    content::ReceiverConnectionAvailableCallback receiver_callback_;

    // Stores controller information.
    // |controller_connection_remote|: mojo::PendingRemote<T> to
    // blink::PresentationConnection object in controlling frame;
    // |receiver_connection_receiver|: mojo::PendingReceiver<T> to be bind to
    // blink::PresentationConnection object in receiver frame.
    struct ControllerConnection {
     public:
      ControllerConnection(
          mojo::PendingRemote<blink::mojom::PresentationConnection>
              controller_connection_remote,
          mojo::PendingReceiver<blink::mojom::PresentationConnection>
              receiver_connection_receiver);
      ~ControllerConnection();

      mojo::PendingRemote<blink::mojom::PresentationConnection>
          controller_connection_remote;
      mojo::PendingReceiver<blink::mojom::PresentationConnection>
          receiver_connection_receiver;
    };

    // Contains ControllerConnection objects registered via
    // |RegisterController()| before |receiver_callback_| is set.
    std::unordered_map<content::GlobalRenderFrameHostId,
                       std::unique_ptr<ControllerConnection>,
                       content::GlobalRenderFrameHostIdHasher>
        pending_controllers_;
  };

 private:
  friend class LocalPresentationManagerTest;
  friend class MockLocalPresentationManager;
  FRIEND_TEST_ALL_PREFIXES(ControllerPresentationServiceDelegateImplTest,
                           ConnectToLocalPresentation);

  using LocalPresentationMap =
      std::map<std::string, std::unique_ptr<LocalPresentation>>;

  // Creates a local presentation with |presentation_info|.
  LocalPresentation* GetOrCreateLocalPresentation(
      const blink::mojom::PresentationInfo& presentation_info);

  // Maps from presentation ID to LocalPresentation.
  LocalPresentationMap local_presentations_;

  THREAD_CHECKER(thread_checker_);
};

}  // namespace media_router

#endif  // COMPONENTS_MEDIA_ROUTER_BROWSER_PRESENTATION_LOCAL_PRESENTATION_MANAGER_H_
