/*
 *  Copyright 2018 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.
 */

#include <cstdint>
#include <optional>
#include <span>
#include <string>
#include <vector>

#include "api/environment/environment.h"
#include "api/rtp_parameters.h"
#include "api/test/video/function_video_encoder_factory.h"
#include "api/units/timestamp.h"
#include "api/video/video_codec_type.h"
#include "api/video/video_frame.h"
#include "api/video/video_sink_interface.h"
#include "api/video_codecs/sdp_video_format.h"
#include "call/video_receive_stream.h"
#include "call/video_send_stream.h"
#include "modules/video_coding/codecs/vp8/include/vp8.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/thread_annotations.h"
#include "system_wrappers/include/clock.h"
#include "system_wrappers/include/metrics.h"
#include "test/call_test.h"
#include "test/gtest.h"
#include "test/rtp_rtcp_observer.h"
#include "test/video_test_constants.h"
#include "video/config/video_encoder_config.h"

namespace webrtc {
namespace {
enum : int {  // The first valid value is 1.
  kTransportSequenceNumberExtensionId = 1,
  kVideoContentTypeExtensionId,
};
}  // namespace

class HistogramTest : public test::CallTest {
 public:
  HistogramTest() {
    RegisterRtpExtension(RtpExtension(RtpExtension::kTransportSequenceNumberUri,
                                      kTransportSequenceNumberExtensionId));
    RegisterRtpExtension(RtpExtension(RtpExtension::kVideoContentTypeUri,
                                      kVideoContentTypeExtensionId));
  }

 protected:
  void VerifyHistogramStats(bool use_rtx, bool use_fec, bool screenshare);
};

void HistogramTest::VerifyHistogramStats(bool use_rtx,
                                         bool use_fec,
                                         bool screenshare) {
  class FrameObserver : public test::EndToEndTest,
                        public VideoSinkInterface<VideoFrame> {
   public:
    FrameObserver(bool use_rtx, bool use_fec, bool screenshare)
        : EndToEndTest(test::VideoTestConstants::kLongTimeout),
          use_rtx_(use_rtx),
          use_fec_(use_fec),
          screenshare_(screenshare),
          // This test uses NACK, so to send FEC we can't use a fake encoder.
          encoder_factory_(
              [](const Environment& env, const SdpVideoFormat& format) {
                return CreateVp8Encoder(env);
              }),
          num_frames_received_(0) {}

   private:
    void OnFrame(const VideoFrame& video_frame) override {
      // The RTT is needed to estimate `ntp_time_ms` which is used by
      // end-to-end delay stats. Therefore, start counting received frames once
      // `ntp_time_ms` is valid.
      if (video_frame.ntp_time_ms() > 0 &&
          Clock::GetRealTimeClock()->CurrentNtpInMilliseconds() >=
              video_frame.ntp_time_ms()) {
        MutexLock lock(&mutex_);
        ++num_frames_received_;
      }
    }

    Action OnSendRtp(std::span<const uint8_t> packet) override {
      if (MinMetricRunTimePassed() && MinNumberOfFramesReceived())
        observation_complete_.Set();

      return SEND_PACKET;
    }

    bool MinMetricRunTimePassed() {
      Timestamp now = Clock::GetRealTimeClock()->CurrentTime();
      if (!start_runtime_)
        start_runtime_ = now;

      return now - *start_runtime_ > metrics::kMinRunTime * 2;
    }

    bool MinNumberOfFramesReceived() const {
      const int kMinRequiredHistogramSamples = 200;
      MutexLock lock(&mutex_);
      return num_frames_received_ > kMinRequiredHistogramSamples;
    }

    void ModifyVideoConfigs(
        VideoSendStream::Config* send_config,
        std::vector<VideoReceiveStreamInterface::Config>* receive_configs,
        VideoEncoderConfig* encoder_config) override {
      // NACK
      send_config->rtp.nack.rtp_history_ms =
          test::VideoTestConstants::kNackRtpHistoryMs;
      (*receive_configs)[0].rtp.nack.rtp_history_ms =
          test::VideoTestConstants::kNackRtpHistoryMs;
      (*receive_configs)[0].renderer = this;
      // FEC
      if (use_fec_) {
        send_config->rtp.ulpfec.ulpfec_payload_type =
            test::VideoTestConstants::kUlpfecPayloadType;
        send_config->rtp.ulpfec.red_payload_type =
            test::VideoTestConstants::kRedPayloadType;
        send_config->encoder_settings.encoder_factory = &encoder_factory_;
        send_config->rtp.payload_name = "VP8";
        encoder_config->codec_type = kVideoCodecVP8;
        (*receive_configs)[0].decoders[0].video_format = SdpVideoFormat::VP8();
        (*receive_configs)[0].rtp.red_payload_type =
            test::VideoTestConstants::kRedPayloadType;
        (*receive_configs)[0].rtp.ulpfec_payload_type =
            test::VideoTestConstants::kUlpfecPayloadType;
      }
      // RTX
      if (use_rtx_) {
        send_config->rtp.rtx.ssrcs.push_back(
            test::VideoTestConstants::kSendRtxSsrcs[0]);
        send_config->rtp.rtx.payload_type =
            test::VideoTestConstants::kSendRtxPayloadType;
        (*receive_configs)[0].rtp.rtx_ssrc =
            test::VideoTestConstants::kSendRtxSsrcs[0];
        (*receive_configs)[0].rtp.rtx_associated_payload_types
            [test::VideoTestConstants::kSendRtxPayloadType] =
            test::VideoTestConstants::kFakeVideoSendPayloadType;
        if (use_fec_) {
          send_config->rtp.ulpfec.red_rtx_payload_type =
              test::VideoTestConstants::kRtxRedPayloadType;
          (*receive_configs)[0].rtp.rtx_associated_payload_types
              [test::VideoTestConstants::kRtxRedPayloadType] =
              test::VideoTestConstants::kSendRtxPayloadType;
        }
      }
      // RTT needed for RemoteNtpTimeEstimator for the receive stream.
      (*receive_configs)[0].rtp.rtcp_xr.receiver_reference_time_report = true;
      encoder_config->content_type =
          screenshare_ ? VideoEncoderConfig::ContentType::kScreen
                       : VideoEncoderConfig::ContentType::kRealtimeVideo;
    }

    void PerformTest() override {
      EXPECT_TRUE(Wait()) << "Timed out waiting for min frames to be received.";
    }

    mutable Mutex mutex_;
    const bool use_rtx_;
    const bool use_fec_;
    const bool screenshare_;
    test::FunctionVideoEncoderFactory encoder_factory_;
    std::optional<Timestamp> start_runtime_;
    int num_frames_received_ RTC_GUARDED_BY(&mutex_);
  } test(use_rtx, use_fec, screenshare);

  metrics::Reset();
  RunBaseTest(&test);

  const std::string video_prefix =
      screenshare ? "WebRTC.Video.Screenshare." : "WebRTC.Video.";

  // Verify that stats have been updated once.
  EXPECT_METRIC_EQ(2, metrics::NumSamples("WebRTC.Call.LifetimeInSeconds"));
  EXPECT_METRIC_EQ(1, metrics::NumSamples(
                          "WebRTC.Call.TimeReceivingVideoRtpPacketsInSeconds"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples("WebRTC.Call.VideoBitrateReceivedInKbps"));
  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples("WebRTC.Call.RtcpBitrateReceivedInBps"));
  EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Call.BitrateReceivedInKbps"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples("WebRTC.Call.EstimatedSendBitrateInKbps"));
  EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Call.PacerBitrateInKbps"));

  EXPECT_METRIC_EQ(
      1, metrics::NumSamples("WebRTC.Video.SendStreamLifetimeInSeconds"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples("WebRTC.Video.ReceiveStreamLifetimeInSeconds"));

  EXPECT_METRIC_EQ(
      1, metrics::NumSamples("WebRTC.Video.NackPacketsSentPerMinute"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples(video_prefix + "NackPacketsReceivedPerMinute"));
  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples("WebRTC.Video.FirPacketsSentPerMinute"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples(video_prefix + "FirPacketsReceivedPerMinute"));
  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples("WebRTC.Video.PliPacketsSentPerMinute"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples(video_prefix + "PliPacketsReceivedPerMinute"));

  EXPECT_METRIC_EQ(
      1, metrics::NumSamples(video_prefix + "KeyFramesSentInPermille"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples("WebRTC.Video.KeyFramesReceivedInPermille"));

  EXPECT_METRIC_EQ(
      1, metrics::NumSamples(video_prefix + "SentPacketsLostInPercent"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples("WebRTC.Video.ReceivedPacketsLostInPercent"));

  EXPECT_METRIC_EQ(1, metrics::NumSamples(video_prefix + "InputWidthInPixels"));
  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples(video_prefix + "InputHeightInPixels"));
  EXPECT_METRIC_EQ(1, metrics::NumSamples(video_prefix + "SentWidthInPixels"));
  EXPECT_METRIC_EQ(1, metrics::NumSamples(video_prefix + "SentHeightInPixels"));
  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples(video_prefix + "ReceivedWidthInPixels"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples(video_prefix + "ReceivedHeightInPixels"));

  EXPECT_METRIC_EQ(1,
                   metrics::NumEvents(video_prefix + "InputWidthInPixels",
                                      test::VideoTestConstants::kDefaultWidth));
  EXPECT_METRIC_EQ(
      1, metrics::NumEvents(video_prefix + "InputHeightInPixels",
                            test::VideoTestConstants::kDefaultHeight));
  EXPECT_METRIC_EQ(1,
                   metrics::NumEvents(video_prefix + "SentWidthInPixels",
                                      test::VideoTestConstants::kDefaultWidth));
  EXPECT_METRIC_EQ(
      1, metrics::NumEvents(video_prefix + "SentHeightInPixels",
                            test::VideoTestConstants::kDefaultHeight));
  EXPECT_METRIC_EQ(1,
                   metrics::NumEvents(video_prefix + "ReceivedWidthInPixels",
                                      test::VideoTestConstants::kDefaultWidth));
  EXPECT_METRIC_EQ(
      1, metrics::NumEvents(video_prefix + "ReceivedHeightInPixels",
                            test::VideoTestConstants::kDefaultHeight));

  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples(video_prefix + "InputFramesPerSecond"));
  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples(video_prefix + "SentFramesPerSecond"));
  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples("WebRTC.Video.DecodedFramesPerSecond"));
  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples("WebRTC.Video.RenderFramesPerSecond"));
  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples("WebRTC.Video.DelayedFramesToRenderer"));

  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples("WebRTC.Video.JitterBufferDelayInMs"));
  EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.TargetDelayInMs"));
  EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.CurrentDelayInMs"));
  EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.OnewayDelayInMs"));

  EXPECT_METRIC_EQ(1, metrics::NumSamples(video_prefix + "EndToEndDelayInMs"));
  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples(video_prefix + "EndToEndDelayMaxInMs"));
  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples(video_prefix + "InterframeDelayInMs"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples(video_prefix + "InterframeDelayMaxInMs"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples("WebRTC.Video.RenderSqrtPixelsPerSecond"));

  EXPECT_METRIC_EQ(1, metrics::NumSamples(video_prefix + "EncodeTimeInMs"));
  EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.DecodeTimeInMs"));

  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples(video_prefix + "NumberOfPauseEvents"));
  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples(video_prefix + "PausedTimeInPercent"));

  EXPECT_METRIC_EQ(1, metrics::NumSamples(video_prefix + "BitrateSentInKbps"));
  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples("WebRTC.Video.BitrateReceivedInKbps"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples(video_prefix + "MediaBitrateSentInKbps"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples("WebRTC.Video.MediaBitrateReceivedInKbps"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples(video_prefix + "PaddingBitrateSentInKbps"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples("WebRTC.Video.PaddingBitrateReceivedInKbps"));
  EXPECT_METRIC_EQ(
      1, metrics::NumSamples(video_prefix + "RetransmittedBitrateSentInKbps"));
  EXPECT_METRIC_EQ(1, metrics::NumSamples(
                          "WebRTC.Video.RetransmittedBitrateReceivedInKbps"));

  EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.SendDelayInMs"));
  EXPECT_METRIC_EQ(1, metrics::NumSamples(video_prefix + "SendSideDelayInMs"));
  EXPECT_METRIC_EQ(1,
                   metrics::NumSamples(video_prefix + "SendSideDelayMaxInMs"));

  int num_rtx_samples = use_rtx ? 1 : 0;
  EXPECT_METRIC_EQ(num_rtx_samples,
                   metrics::NumSamples("WebRTC.Video.RtxBitrateSentInKbps"));
  EXPECT_METRIC_EQ(
      num_rtx_samples,
      metrics::NumSamples("WebRTC.Video.RtxBitrateReceivedInKbps"));

  int num_red_samples = use_fec ? 1 : 0;
  EXPECT_METRIC_EQ(num_red_samples,
                   metrics::NumSamples("WebRTC.Video.FecBitrateSentInKbps"));
  EXPECT_METRIC_EQ(
      num_red_samples,
      metrics::NumSamples("WebRTC.Video.FecBitrateReceivedInKbps"));
  EXPECT_METRIC_EQ(
      num_red_samples,
      metrics::NumSamples("WebRTC.Video.ReceivedFecPacketsInPercent"));
}

TEST_F(HistogramTest, VerifyStatsWithRtx) {
  const bool kEnabledRtx = true;
  const bool kEnabledRed = false;
  const bool kScreenshare = false;
  VerifyHistogramStats(kEnabledRtx, kEnabledRed, kScreenshare);
}

TEST_F(HistogramTest, VerifyStatsWithRed) {
  const bool kEnabledRtx = false;
  const bool kEnabledRed = true;
  const bool kScreenshare = false;
  VerifyHistogramStats(kEnabledRtx, kEnabledRed, kScreenshare);
}

TEST_F(HistogramTest, VerifyStatsWithScreenshare) {
  const bool kEnabledRtx = false;
  const bool kEnabledRed = false;
  const bool kScreenshare = true;
  VerifyHistogramStats(kEnabledRtx, kEnabledRed, kScreenshare);
}

}  // namespace webrtc
