/*
 *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#ifndef TEST_VIDEO_CODEC_TESTER_H_
#define TEST_VIDEO_CODEC_TESTER_H_

#include <cstdint>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "absl/strings/string_view.h"
#include "api/environment/environment.h"
#include "api/numerics/samples_stats_counter.h"
#include "api/test/metrics/metrics_logger.h"
#include "api/units/data_rate.h"
#include "api/units/data_size.h"
#include "api/units/frequency.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "api/video/encoded_image.h"
#include "api/video/resolution.h"
#include "api/video_codecs/scalability_mode.h"
#include "api/video_codecs/sdp_video_format.h"
#include "api/video_codecs/video_codec.h"
#include "api/video_codecs/video_decoder_factory.h"
#include "api/video_codecs/video_encoder_factory.h"

namespace webrtc {
namespace test {

class VideoCodecTester {
 public:
  struct LayerId {
    int spatial_idx = 0;
    int temporal_idx = 0;

    bool operator==(const LayerId& o) const {
      return spatial_idx == o.spatial_idx && temporal_idx == o.temporal_idx;
    }
    bool operator<(const LayerId& o) const {
      return spatial_idx < o.spatial_idx ||
             (spatial_idx == o.spatial_idx && temporal_idx < o.temporal_idx);
    }
  };

  struct EncodingSettings {
    SdpVideoFormat sdp_video_format = SdpVideoFormat::VP8();
    ScalabilityMode scalability_mode = ScalabilityMode::kL1T1;
    VideoCodecMode content_type = VideoCodecMode::kRealtimeVideo;
    bool frame_drop = true;
    bool keyframe = false;

    struct LayerSettings {
      Resolution resolution;
      Frequency framerate;
      DataRate bitrate;
    };
    std::map<LayerId, LayerSettings> layers_settings;
  };

  class VideoCodecStats {
   public:
    struct Filter {
      uint32_t min_timestamp_rtp = std::numeric_limits<uint32_t>::min();
      uint32_t max_timestamp_rtp = std::numeric_limits<uint32_t>::max();
      std::optional<LayerId> layer_id;
    };

    struct Frame {
      uint32_t timestamp_rtp = 0;
      LayerId layer_id;
      bool encoded = false;
      bool decoded = false;
      int width = 0;
      int height = 0;
      DataSize frame_size = DataSize::Zero();
      bool keyframe = false;
      std::optional<int> qp;
      Timestamp encode_start = Timestamp::Zero();
      TimeDelta encode_time = TimeDelta::Zero();
      Timestamp decode_start = Timestamp::Zero();
      TimeDelta decode_time = TimeDelta::Zero();
      std::optional<DataRate> target_bitrate;
      std::optional<Frequency> target_framerate;

      struct Psnr {
        double y = 0.0;
        double u = 0.0;
        double v = 0.0;
      };
      std::optional<Psnr> psnr;
    };

    struct Stream {
      SamplesStatsCounter width;
      SamplesStatsCounter height;
      SamplesStatsCounter frame_size_bytes;
      SamplesStatsCounter keyframe;
      SamplesStatsCounter qp;
      SamplesStatsCounter encode_time_us;
      SamplesStatsCounter decode_time_us;
      SamplesStatsCounter target_bitrate_kbps;
      SamplesStatsCounter target_framerate_fps;
      SamplesStatsCounter encoded_bitrate_kbps;
      SamplesStatsCounter encoded_framerate_fps;
      SamplesStatsCounter bitrate_mismatch_pct;
      SamplesStatsCounter framerate_mismatch_pct;
      SamplesStatsCounter buffer_delay_ms;

      struct Psnr {
        SamplesStatsCounter y;
        SamplesStatsCounter u;
        SamplesStatsCounter v;
      } psnr;

      // Logs `Stream` metrics to provided `MetricsLogger`.
      void LogMetrics(MetricsLogger* logger,
                      std::string test_case_name,
                      std::string prefix,
                      std::map<std::string, std::string> metadata = {}) const;
    };

    virtual ~VideoCodecStats() = default;

    // Returns frames for the slice specified by `filter`. If `merge` is true,
    // also merges frames belonging to the same temporal unit into one
    // superframe.
    virtual std::vector<Frame> Slice(Filter filter, bool merge) const = 0;

    // Returns video statistics aggregated for the slice specified by `filter`.
    virtual Stream Aggregate(Filter filter) const = 0;

    // Write metrics to a CSV file.
    virtual void LogMetrics(
        absl::string_view csv_path,
        std::vector<Frame> frames,
        std::map<std::string, std::string> metadata) const = 0;
  };

  // Pacing settings for codec input.
  struct PacingSettings {
    enum PacingMode {
      // Pacing is not used. Frames are sent to codec back-to-back.
      kNoPacing,
      // Pace with the rate equal to the target video frame rate. Pacing time is
      // derived from RTP timestamp.
      kRealTime,
      // Pace with the explicitly provided rate.
      kConstantRate,
    };
    PacingMode mode = PacingMode::kNoPacing;
    // Pacing rate for `kConstantRate` mode.
    Frequency constant_rate = Frequency::Zero();
  };

  struct VideoSourceSettings {
    std::string file_path;
    Resolution resolution;
    Frequency framerate;
  };

  struct DecoderSettings {
    PacingSettings pacing_settings;
    std::optional<std::string> decoder_input_base_path;
    std::optional<std::string> decoder_output_base_path;
    int num_cores = 1;
  };

  struct EncoderSettings {
    PacingSettings pacing_settings;
    std::optional<std::string> encoder_input_base_path;
    std::optional<std::string> encoder_output_base_path;
    int num_cores = 1;
  };

  virtual ~VideoCodecTester() = default;

  // Interface for a coded video frames source.
  class CodedVideoSource {
   public:
    virtual ~CodedVideoSource() = default;

    // Returns next frame. Returns `std::nullopt` if the end-of-stream is
    // reached. Frames should have RTP timestamps representing desired frame
    // rate.
    virtual std::optional<EncodedImage> PullFrame() = 0;
  };

  // A helper function that creates `EncodingSettings` from the given
  // parameters. `bitrate` is either total, or per-spatial layer or per-spatial
  // and per-temporal layer. If layer bitrates are not explicitly specified,
  // then the codec-specific rate allocators used to distribute the total
  // bitrate across spatial or/and temporal layers.
  static EncodingSettings CreateEncodingSettings(const Environment& env,
                                                 std::string codec_type,
                                                 std::string scalability_name,
                                                 int width,
                                                 int height,
                                                 std::vector<DataRate> bitrate,
                                                 Frequency framerate,
                                                 bool screencast = false,
                                                 bool frame_drop = true);

  // A helper function that creates a map of RTP timestamps to
  // `EncodingSettings` for the given number of frames.
  static std::map<uint32_t, EncodingSettings> CreateFrameSettings(
      const EncodingSettings& encoding_settings,
      int num_frames,
      uint32_t first_timestamp_rtp = 90000);

  // Decodes video, collects and returns decode metrics.
  static std::unique_ptr<VideoCodecStats> RunDecodeTest(
      const Environment& env,
      CodedVideoSource* video_source,
      VideoDecoderFactory* decoder_factory,
      const DecoderSettings& decoder_settings,
      const SdpVideoFormat& sdp_video_format);

  // Encodes video, collects and returns encode metrics.
  static std::unique_ptr<VideoCodecStats> RunEncodeTest(
      const Environment& env,
      const VideoSourceSettings& source_settings,
      VideoEncoderFactory* encoder_factory,
      const EncoderSettings& encoder_settings,
      const std::map<uint32_t, EncodingSettings>& encoding_settings);

  // Encodes and decodes video, collects and returns encode and decode metrics.
  static std::unique_ptr<VideoCodecStats> RunEncodeDecodeTest(
      const Environment& env,
      const VideoSourceSettings& source_settings,
      VideoEncoderFactory* encoder_factory,
      VideoDecoderFactory* decoder_factory,
      const EncoderSettings& encoder_settings,
      const DecoderSettings& decoder_settings,
      const std::map<uint32_t, EncodingSettings>& encoding_settings);
};

}  // namespace test
}  // namespace webrtc

#endif  // TEST_VIDEO_CODEC_TESTER_H_
