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

#ifndef SERVICES_VIDEO_CAPTURE_BROADCASTING_RECEIVER_H_
#define SERVICES_VIDEO_CAPTURE_BROADCASTING_RECEIVER_H_

#include <map>

#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "build/build_config.h"
#include "media/capture/video/video_frame_receiver.h"
#include "media/capture/video_capture_types.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/video_capture/public/cpp/video_frame_access_handler.h"
#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"

namespace video_capture {

// Implementation of media::VideoFrameReceiver that distributes frames to
// potentially multiple clients.
class BroadcastingReceiver : public media::VideoFrameReceiver {
 public:
  class BufferContext {
   public:
    BufferContext(int32_t buffer_id,
                  media::mojom::VideoBufferHandlePtr buffer_handle);
    ~BufferContext();
    BufferContext(BufferContext&& other);
    BufferContext& operator=(BufferContext&& other);
    int32_t buffer_context_id() const { return buffer_context_id_; }
    int32_t buffer_id() const { return buffer_id_; }
    void IncreaseConsumerCount();
    void DecreaseConsumerCount();
    bool IsStillBeingConsumed() const;
    bool is_retired() const { return is_retired_; }
    void set_retired() { is_retired_ = true; }
    media::mojom::VideoBufferHandlePtr CloneBufferHandle(
        media::VideoCaptureBufferType target_buffer_type);

   private:
    int32_t buffer_context_id_;
    int32_t buffer_id_;
    media::mojom::VideoBufferHandlePtr buffer_handle_;
    // Indicates how many consumers are currently relying on
    // |access_permission_|.
    int32_t consumer_hold_count_;
    bool is_retired_;
  };

  BroadcastingReceiver();
  ~BroadcastingReceiver() override;

  base::WeakPtr<media::VideoFrameReceiver> GetWeakPtr();

  // Indicates to the BroadcastingReceiver that we want to restart the source
  // without letting connected clients know about the restart. The
  // BroadcastingReceiver will hide the OnStopped() event sent by the source
  // from the connected clients and instead invoke the given
  // |on_stopped_handler|. It will also not forward the subsequent
  // OnStarted() and possibly OnStartedUsingGpuDecode() events to clients who
  // have already received these events.
  void HideSourceRestartFromClients(base::OnceClosure on_stopped_handler);

  void SetOnStoppedHandler(base::OnceClosure on_stopped_handler);

  // Returns a client_id that can be used for a call to Suspend/Resume/Remove.
  int32_t AddClient(mojo::PendingRemote<mojom::VideoFrameHandler> client,
                    media::VideoCaptureBufferType target_buffer_type);
  void SuspendClient(int32_t client_id);
  void ResumeClient(int32_t client_id);
  // Returns ownership of the client back to the caller.
  mojo::Remote<mojom::VideoFrameHandler> RemoveClient(int32_t client_id);

  // media::VideoFrameReceiver:
  void OnCaptureConfigurationChanged() override;
  void OnNewBuffer(int32_t buffer_id,
                   media::mojom::VideoBufferHandlePtr buffer_handle) override;
  void OnFrameReadyInBuffer(media::ReadyFrameInBuffer frame) override;
  void OnBufferRetired(int32_t buffer_id) override;
  void OnError(media::VideoCaptureError error) override;
  void OnFrameDropped(media::VideoCaptureFrameDropReason reason) override;
  void OnNewCaptureVersion(media::CaptureVersion capture_version) override;
  void OnFrameWithEmptyRegionCapture() override;
  void OnLog(const std::string& message) override;
  void OnStarted() override;
  void OnStartedUsingGpuDecode() override;
  void OnStopped() override;

 private:
  friend class BroadcastingReceiverTest;

  enum class Status {
    kOnStartedHasNotYetBeenCalled,
    kOnStartedHasBeenCalled,
    kOnStartedUsingGpuDecodeHasBeenCalled,
    kDeviceIsRestarting,
    kOnErrorHasBeenCalled,
    kOnStoppedHasBeenCalled
  };

  // Wrapper that suppresses calls to OnStarted() and OnStartedUsingGpuDecode()
  // after they have already been called once. Keeps track of whether or not
  // a client is suspended.
  class ClientContext {
   public:
    ClientContext(mojo::PendingRemote<mojom::VideoFrameHandler> client,
                  media::VideoCaptureBufferType target_buffer_type);
    ~ClientContext();
    ClientContext(ClientContext&& other);
    ClientContext& operator=(ClientContext&& other);
    void OnStarted();
    void OnStartedUsingGpuDecode();

    mojo::Remote<mojom::VideoFrameHandler>& client() { return client_; }
    media::VideoCaptureBufferType target_buffer_type() {
      return target_buffer_type_;
    }
    void set_is_suspended(bool suspended) { is_suspended_ = suspended; }
    bool is_suspended() const { return is_suspended_; }
    void set_has_client_frame_access_handler_remote() {
      has_client_frame_access_handler_remote_ = true;
    }
    bool has_client_frame_access_handler_remote() const {
      return has_client_frame_access_handler_remote_;
    }

   private:
    mojo::Remote<mojom::VideoFrameHandler> client_;
    media::VideoCaptureBufferType target_buffer_type_;
    bool is_suspended_;
    bool on_started_has_been_called_;
    bool on_started_using_gpu_decode_has_been_called_;
    bool has_client_frame_access_handler_remote_;
  };

  class ClientVideoFrameAccessHandler;

  void OnClientFinishedConsumingFrame(int32_t buffer_context_id);
  void OnClientDisconnected(int32_t client_id);
  std::vector<BufferContext>::iterator FindUnretiredBufferContextFromBufferId(
      int32_t buffer_id);

  SEQUENCE_CHECKER(sequence_checker_);
  scoped_refptr<VideoFrameAccessHandlerRemote> frame_access_handler_remote_;
  std::map<int32_t /*client_id*/, ClientContext> clients_;
  std::vector<BufferContext> buffer_contexts_;
  Status status_;
  base::OnceClosure on_stopped_handler_;

  // Keeps track of the last VideoCaptureError that arrived via OnError().
  // This is used for relaying the error event to clients that connect after the
  // OnError() call has been received.
  media::VideoCaptureError error_;

  std::map<
      int32_t,
      std::unique_ptr<
          media::VideoCaptureDevice::Client::Buffer::ScopedAccessPermission>>
      scoped_access_permissions_by_buffer_context_id_;

  // Keeps track of the id value for the next client to be added. This member is
  // incremented each time a client is added and represents a unique identifier
  // for each client.
  int32_t next_client_id_;

  base::WeakPtrFactory<BroadcastingReceiver> weak_factory_{this};
};

}  // namespace video_capture

#endif  // SERVICES_VIDEO_CAPTURE_BROADCASTING_RECEIVER_H_
