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

#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBCODECS_VIDEO_ENCODER_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBCODECS_VIDEO_ENCODER_H_

#include <memory>
#include <optional>

#include "base/containers/flat_map.h"
#include "base/time/time.h"
#include "media/base/video_codecs.h"
#include "media/base/video_color_space.h"
#include "media/base/video_decoder_config.h"
#include "media/base/video_encoder.h"
#include "media/base/video_frame_pool.h"
#include "media/video/video_encoder_info.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_chunk_output_callback.h"
#include "third_party/blink/renderer/modules/webcodecs/encoder_base.h"
#include "third_party/blink/renderer/modules/webcodecs/hardware_preference.h"
#include "third_party/blink/renderer/modules/webcodecs/video_frame.h"
#include "ui/gfx/color_space.h"

namespace media {
class GpuVideoAcceleratorFactories;
class VideoEncoderMetricsProvider;
class VideoEncoder;
struct VideoEncoderOutput;
}  // namespace media

namespace blink {

class VideoEncoderBuffer;
class VideoEncoderConfig;
class VideoEncoderInit;
class VideoEncoderEncodeOptions;
class VideoEncoderSupport;
class WebGraphicsContext3DVideoFramePool;
class BackgroundReadback;

MODULES_EXPORT gfx::ColorSpace GetReadbackYuvColorSpace(
    const gfx::ColorSpace& source_color_space);

class MODULES_EXPORT VideoEncoderTraits {
 public:
  struct ParsedConfig final : public GarbageCollected<ParsedConfig> {
    media::VideoCodec codec;
    media::VideoCodecProfile profile;
    uint8_t level;

    HardwarePreference hw_pref;

    media::VideoEncoder::Options options;
    String codec_string;
    std::optional<gfx::Size> display_size;

    std::optional<String> not_supported_error_message;

    String ToString();
    void Trace(Visitor*) const {}
  };

  using Init = VideoEncoderInit;
  using Config = VideoEncoderConfig;
  using InternalConfig = ParsedConfig;
  using Input = VideoFrame;
  using EncodeOptions = VideoEncoderEncodeOptions;
  using OutputChunk = EncodedVideoChunk;
  using OutputCallback = V8EncodedVideoChunkOutputCallback;
  using MediaEncoder = media::VideoEncoder;

  // Can't be a virtual method, because it's used from base ctor.
  static const char* GetName();
};

class MODULES_EXPORT VideoEncoder : public EncoderBase<VideoEncoderTraits> {
  DEFINE_WRAPPERTYPEINFO();

 public:
  static VideoEncoder* Create(ScriptState*,
                              const VideoEncoderInit*,
                              ExceptionState&);
  VideoEncoder(ScriptState*, const VideoEncoderInit*, ExceptionState&);
  ~VideoEncoder() override;

  static ScriptPromise<VideoEncoderSupport>
  isConfigSupported(ScriptState*, const VideoEncoderConfig*, ExceptionState&);

  HeapVector<Member<VideoEncoderBuffer>> getAllFrameBuffers(ScriptState*,
                                                            ExceptionState&);

  // EventTarget interface
  const AtomicString& InterfaceName() const override;

  // ScriptWrappable override.
  bool HasPendingActivity() const override;

  // GarbageCollected override.
  void Trace(Visitor*) const override;

  // If `is_error_message_from_software_codec` is true, `error_message` will be
  // updated to include `status.message()` if non-empty.
  void ReportError(const char* error_message,
                   const media::EncoderStatus& status,
                   bool is_error_message_from_software_codec);

  std::unique_ptr<media::VideoEncoderMetricsProvider> encoder_metrics_provider_
      GUARDED_BY_CONTEXT(sequence_checker_);

 protected:
  using Base = EncoderBase<VideoEncoderTraits>;
  using ParsedConfig = VideoEncoderTraits::ParsedConfig;

  void OnMediaEncoderInfoChanged(const media::VideoEncoderInfo& encoder_info);
  void CallOutputCallback(
      ParsedConfig* active_config,
      uint32_t reset_count,
      media::VideoEncoderOutput output,
      std::optional<media::VideoEncoder::CodecDescription> codec_desc);
  bool ReadyToProcessNextRequest() override;
  void ProcessEncode(Request* request) override;
  void ProcessConfigure(Request* request) override;
  void ProcessReconfigure(Request* request) override;
  void ResetInternal(DOMException* ex) override;
  void OnNewEncode(VideoFrame* input, ExceptionState& exception_state) override;

  void OnEncodeDone(Request* request, media::EncoderStatus status);
  media::VideoEncoder::EncodeOptions CreateEncodeOptions(Request* request);
  // This will execute shortly after the async readback completes.
  void OnReadbackDone(Request* request,
                      scoped_refptr<media::VideoFrame> txt_frame,
                      media::VideoEncoder::EncoderStatusCB done_callback,
                      scoped_refptr<media::VideoFrame> result_frame);
  static media::EncoderStatus::Or<std::unique_ptr<media::VideoEncoder>>
  CreateSoftwareVideoEncoder(VideoEncoder* self,
                             bool fallback,
                             media::VideoCodec codec);

  ParsedConfig* OnNewConfigure(const VideoEncoderConfig*,
                               ExceptionState&) override;
  bool VerifyCodecSupport(ParsedConfig*, String* js_error_message) override;

  // Virtual for UTs.
  // Returns the VideoEncoder.
  virtual media::EncoderStatus::Or<std::unique_ptr<media::VideoEncoder>>
  CreateMediaVideoEncoder(const ParsedConfig& config,
                          media::GpuVideoAcceleratorFactories* gpu_factories,
                          bool& is_platform_encoder);
  virtual std::unique_ptr<media::VideoEncoderMetricsProvider>
  CreateVideoEncoderMetricsProvider() const;

  void ContinueConfigureWithGpuFactories(
      Request* request,
      media::GpuVideoAcceleratorFactories* gpu_factories);
  void ContinueConfigureAfterFlush(Request* request);
  media::EncoderStatus::Or<std::unique_ptr<media::VideoEncoder>>
  CreateAcceleratedVideoEncoder(
      media::VideoCodecProfile profile,
      const media::VideoEncoder::Options& options,
      media::GpuVideoAcceleratorFactories* gpu_factories,
      HardwarePreference hw_pref);
  bool CanReconfigure(ParsedConfig& original_config,
                      ParsedConfig& new_config) override;

  using ReadbackDoneCallback =
      base::OnceCallback<void(scoped_refptr<media::VideoFrame>)>;
  bool StartReadback(scoped_refptr<media::VideoFrame> frame,
                     ReadbackDoneCallback result_cb);

  std::unique_ptr<WebGraphicsContext3DVideoFramePool> accelerated_frame_pool_;
  Member<BackgroundReadback> background_readback_;

  // True if an error occurs during frame pool usage.
  bool disable_accelerated_frame_pool_ = false;

  // The number of encoding requests currently handled by |media_encoder_|
  // Should not exceed |max_active_encodes_|.
  int active_encodes_ = 0;

  // The current upper limit on |active_encodes_|.
  int max_active_encodes_;

  // True if a running video encoder is hardware accelerated.
  bool is_platform_encoder_ = false;

  // Per-frame metadata to be applied to outputs, linked by timestamp.
  struct FrameMetadata {
    base::TimeDelta duration;
    media::VideoTransformation transformation;
  };
  base::flat_map<base::TimeDelta, FrameMetadata> frame_metadata_;

  // Buffers returned by getAllFrameBuffers()
  HeapVector<Member<VideoEncoderBuffer>> frame_reference_buffers_;

  // The transformation corresponding to the last input received by
  // ProcessEncode(). Used to request a key frame.
  std::optional<media::VideoTransformation> first_input_transformation_;

  // Latest VideoEncoderInfo reported by encoder
  media::VideoEncoderInfo encoder_info_;

  // The last emitted decoder config.
  std::optional<media::VideoDecoderConfig> last_decoder_config_;
};

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBCODECS_VIDEO_ENCODER_H_
