/*
 *  Copyright (c) 2025 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 VIDEO_TIMING_SIMULATOR_RENDERING_SIMULATOR_H_
#define VIDEO_TIMING_SIMULATOR_RENDERING_SIMULATOR_H_

#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <set>
#include <span>
#include <string>
#include <vector>

#include "absl/algorithm/container.h"
#include "api/environment/environment.h"
#include "api/numerics/samples_stats_counter.h"
#include "api/units/data_size.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "logging/rtc_event_log/rtc_event_log_parser.h"
#include "modules/video_coding/timing/timing.h"
#include "rtc_base/checks.h"
#include "video/timing/simulator/frame_base.h"
#include "video/timing/simulator/results_base.h"
#include "video/timing/simulator/stream_base.h"

namespace webrtc::video_timing_simulator {

// The `RenderingSimulator` takes an `ParsedRtcEventLog` and produces a
// sequence of metadata about decoded and rendered frames that were contained in
// the log.
class RenderingSimulator {
 public:
  struct Config {
    using VideoTimingFactory =
        std::function<std::unique_ptr<VCMTiming>(Environment)>;

    std::string name = "";
    std::string field_trials_string = "";
    VideoTimingFactory video_timing_factory = [](Environment env) {
      return std::make_unique<VCMTiming>(&env.clock(), env.field_trials());
    };

    // Whether or not to reset the stream state on newly logged streams with the
    // same SSRC.
    bool reuse_streams = false;

    // If non-empty, will only simulate video streams whose main SSRCs is
    // contained in the set.
    std::set<uint32_t> ssrc_filter = {};
  };

  // Metadata about a single rendered frame.
  struct Frame : public FrameBase<Frame> {
    // -- Values --
    // Frame information.
    int num_packets = -1;              // Required.
    DataSize size = DataSize::Zero();  // Required.

    // RTP header information.
    int payload_type = -1;
    uint32_t rtp_timestamp = 0;
    int64_t unwrapped_rtp_timestamp = -1;  // Required.

    // Dependency descriptor information.
    int64_t frame_id = -1;
    int spatial_id = -1;
    int temporal_id = -1;
    int num_references = -1;

    // Packet timestamps. Both are required.
    Timestamp first_packet_arrival_timestamp = Timestamp::PlusInfinity();
    Timestamp last_packet_arrival_timestamp = Timestamp::MinusInfinity();

    // Frame timestamps.
    Timestamp assembled_timestamp = Timestamp::PlusInfinity();  // Required.
    Timestamp render_timestamp = Timestamp::PlusInfinity();
    Timestamp decoded_timestamp = Timestamp::PlusInfinity();
    Timestamp rendered_timestamp = Timestamp::PlusInfinity();

    // Jitter buffer state at the time of this frame.
    int frames_dropped = 0;
    // TODO: b/423646186 - Add `current_delay_ms`.
    // The `jitter_buffer_*` metrics below are recorded by the production code,
    // and should be compatible with the `webrtc-stats` definitions. One major
    // difference is that they are _not_ cumulative.
    // https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferminimumdelay
    TimeDelta jitter_buffer_minimum_delay = TimeDelta::PlusInfinity();
    // https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbuffertargetdelay
    TimeDelta jitter_buffer_target_delay = TimeDelta::PlusInfinity();
    // https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferdelay
    TimeDelta jitter_buffer_delay = TimeDelta::PlusInfinity();

    // -- Populated values --
    // One-way delay relative some baseline.
    TimeDelta frame_delay_variation = TimeDelta::PlusInfinity();

    // -- Value accessors --
    Timestamp ArrivalTimestampInternal() const { return rendered_timestamp; }

    // -- Per-frame metrics --
    // Time spent being assembled (waiting for all packets to arrive).
    TimeDelta PacketBufferDuration() const {
      RTC_DCHECK(assembled_timestamp.IsFinite());
      RTC_DCHECK(first_packet_arrival_timestamp.IsFinite());
      return assembled_timestamp - first_packet_arrival_timestamp;
    }

    // Time spent waiting to be decoded, after assembly. (This includes a,
    // currently, zero decode duration.) Note that this is similar to
    // `jitter_buffer_delay`, except that the latter is 1) recorded by the
    // production code; and, 2) is anchored on `first_packet_arrival_timestamp`
    // rather than `assembled_timestamp`.
    TimeDelta FrameBufferDuration() const {
      RTC_DCHECK(assembled_timestamp.IsFinite());
      return decoded_timestamp - assembled_timestamp;
    }

    // Time spent waiting to be rendered, after decode.
    TimeDelta RenderBufferDuration() const {
      if (!decoded_timestamp.IsFinite()) {
        RTC_DCHECK(!rendered_timestamp.IsFinite());
        return TimeDelta::PlusInfinity();
      }
      if (!rendered_timestamp.IsFinite()) {
        return TimeDelta::PlusInfinity();
      }
      return rendered_timestamp - decoded_timestamp;
    }

    // Total duration in all three buffers: from first packet to rendered.
    TimeDelta TotalBufferDuration() const {
      TimeDelta total_duration = PacketBufferDuration() +
                                 FrameBufferDuration() + RenderBufferDuration();
      RTC_DCHECK_EQ(total_duration,
                    rendered_timestamp - first_packet_arrival_timestamp);
      return total_duration;
    }

    // Pre-buffer margin between render timestamp (target) and
    // assembled timestamp (actual arrival):
    //   * A frame that is assembled early w.r.t. the target (<=> arriving
    //     in time from the network) has a positive margin.
    //   * A frame that is assembled on time w.r.t. the target (<=> arriving
    //     slightly late from the network) has zero margin.
    //   * A frame that is assembled late w.r.t. the target (<=> arriving
    //     very late from the network) has negative margin.
    // Positive margins mean no video freezes, at the cost of receiver delay.
    // A jitter buffer needs to strike a balance between video freezes and
    // delay. In terms of margin, that means low positive margin values, a
    // couple of frames with zero margin, and very few frames with
    // negative margin.
    // TODO: b/423646186 - Change this to be `DecodabilityMargin`.
    TimeDelta AssembledMargin() const {
      if (!render_timestamp.IsFinite()) {
        return TimeDelta::PlusInfinity();
      }
      RTC_DCHECK(assembled_timestamp.IsFinite());
      // Subtract `kRenderDelay`, since that is what `VCMTiming` and
      // `VideoRenderFrames` also do.
      return (render_timestamp - kRenderDelay) - assembled_timestamp;
    }
    std::optional<bool> AssembledInTime() const {
      TimeDelta assembled_margin = AssembledMargin();
      if (!assembled_margin.IsFinite()) {
        return std::nullopt;
      }
      return assembled_margin > kInTimeMarginThreshold;
    }
    std::optional<bool> AssembledLate() const {
      std::optional<bool> assembled_in_time = AssembledInTime();
      if (!assembled_in_time.has_value()) {
        return std::nullopt;
      }
      return !assembled_in_time.value();
    }

    // Split the assembled margin along zero: "excess margin" for positive
    // margins and "deficit margin" for negative margins. A jitter buffer would
    // generally want to minimize the number of frames with a deficit margin
    // (delayed frames/buffer underruns => video freezes), while also minimizing
    // the stream-level min/p10 of the excess margin (early frames spending a
    // long time in the buffer => high latency).
    // Frames with `render_timestamp` unset are excluded from both.
    std::optional<TimeDelta> AssembledMarginExcess() const {
      std::optional<bool> assembled_in_time = AssembledInTime();
      if (!assembled_in_time.has_value()) {
        return std::nullopt;
      }
      return *assembled_in_time ? std::optional<TimeDelta>(AssembledMargin())
                                : std::nullopt;
    }
    std::optional<TimeDelta> AssembledMarginDeficit() const {
      std::optional<bool> assembled_late = AssembledLate();
      if (!assembled_late.has_value()) {
        return std::nullopt;
      }
      return *assembled_late ? std::optional<TimeDelta>(AssembledMargin())
                             : std::nullopt;
    }

    // Post-buffer margin between render timestamp (target) and
    // rendered timestamp (actual render):
    //   * Frames should not be rendered early, if the timing works well.
    //   * A frame that is rendered on time w.r.t. the target has zero margin.
    //   * A frame that is rendered late w.r.t. the target has negative margin.
    // Frames with `render_timestamp` or `rendered_timestamp` unset are excluded
    // from all.
    TimeDelta RenderedMargin() const {
      if (!render_timestamp.IsFinite()) {
        RTC_DCHECK(!rendered_timestamp.IsFinite());
        return TimeDelta::PlusInfinity();
      }
      if (!rendered_timestamp.IsFinite()) {
        return TimeDelta::PlusInfinity();
      }
      return (render_timestamp - kRenderDelay) - rendered_timestamp;
    }
    std::optional<bool> RenderedInTime() const {
      TimeDelta rendered_margin = RenderedMargin();
      if (!rendered_margin.IsFinite()) {
        return std::nullopt;
      }
      return rendered_margin > kInTimeMarginThreshold;
    }
    std::optional<bool> RenderedLate() const {
      std::optional<bool> rendered_in_time = RenderedInTime();
      if (!rendered_in_time.has_value()) {
        return std::nullopt;
      }
      return !rendered_in_time.value();
    }

    // A well-functioning jitter buffer would have very few (or non) frames with
    // a render margin excess. Render margin deficits can happen though.
    // Frames with `render_timestamp` or `rendered_timestamp` unset are excluded
    // from both.
    std::optional<TimeDelta> RenderedMarginExcess() const {
      std::optional<bool> rendered_in_time = RenderedInTime();
      if (!rendered_in_time.has_value()) {
        return std::nullopt;
      }
      return *rendered_in_time ? std::optional<TimeDelta>(RenderedMargin())
                               : std::nullopt;
    }
    std::optional<TimeDelta> RenderedMarginDeficit() const {
      std::optional<bool> rendered_late = RenderedLate();
      if (!rendered_late.has_value()) {
        return std::nullopt;
      }
      return *rendered_late ? std::optional<TimeDelta>(RenderedMargin())
                            : std::nullopt;
    }
  };

  // All frames in one stream.
  struct Stream : public StreamBase<Stream, Frame> {
    Timestamp creation_timestamp = Timestamp::PlusInfinity();
    uint32_t ssrc = 0;
    std::vector<Frame> frames;

    // -- Per-stream metrics --

    // Total number of frames that were assembled in time or late.
    int NumAssembledInTimeFrames() const {
      return CountSetAndTrue(&Frame::AssembledInTime);
    }
    int NumAssembledLateFrames() const {
      return CountSetAndTrue(&Frame::AssembledLate);
    }

    // Total number of decoded frames.
    int NumDecodedFrames() const {
      return CountFiniteTimestamps(&Frame::decoded_timestamp);
    }

    // Total number of rendered frames.
    int NumRenderedFrames() const {
      return CountFiniteTimestamps(&Frame::rendered_timestamp);
    }

    // Total number of frames that were rendered in time or late.
    int NumRenderedInTimeFrames() const {
      return CountSetAndTrue(&Frame::RenderedInTime);
    }
    int NumRenderedLateFrames() const {
      return CountSetAndTrue(&Frame::RenderedLate);
    }

    // Total number of dropped frames in the decoder.
    int NumDecoderDroppedFrames() const {
      return SumNonNegativeIntField(&Frame::frames_dropped);
    }

    // Samples of per-frame delay variation metrics. Not `const` because they
    // sort `frames`. Defined in `.cc` for circular include order reasons.
    SamplesStatsCounter InterRenderTimeMs();
    SamplesStatsCounter InterDecodedTimeMs();
    SamplesStatsCounter InterRenderedTimeMs();

    // Samples of webrtc-stats values in ms.
    SamplesStatsCounter JitterBufferMinimumDelayMs() const {
      return BuildSamplesMs(&Frame::jitter_buffer_minimum_delay);
    }
    SamplesStatsCounter JitterBufferDelayMs() const {
      return BuildSamplesMs(&Frame::jitter_buffer_delay);
    }

    // Samples of buffer durations in ms.
    SamplesStatsCounter PacketBufferDurationMs() const {
      return BuildSamplesMs(&Frame::PacketBufferDuration);
    }
    SamplesStatsCounter FrameBufferDurationMs() const {
      return BuildSamplesMs(&Frame::FrameBufferDuration);
    }
    SamplesStatsCounter RenderBufferDurationMs() const {
      return BuildSamplesMs(&Frame::RenderBufferDuration);
    }
    SamplesStatsCounter TotalBufferDurationMs() const {
      return BuildSamplesMs(&Frame::TotalBufferDuration);
    }

    // Samples of assembled margin in ms.
    SamplesStatsCounter AssembledMarginMs() const {
      return BuildSamplesMs(&Frame::AssembledMargin);
    }
    SamplesStatsCounter AssembledMarginExcessMs() const {
      return BuildSamplesMs(&Frame::AssembledMarginExcess);
    }
    SamplesStatsCounter AssembledMarginDeficitMs() const {
      return BuildSamplesMs(&Frame::AssembledMarginDeficit);
    }

    // Samples of render margin in ms.
    SamplesStatsCounter RenderedMarginMs() const {
      return BuildSamplesMs(&Frame::RenderedMargin);
    }
    SamplesStatsCounter RenderedMarginExcessMs() const {
      return BuildSamplesMs(&Frame::RenderedMarginExcess);
    }
    SamplesStatsCounter RenderedMarginDeficitMs() const {
      return BuildSamplesMs(&Frame::RenderedMarginDeficit);
    }
  };

  // All streams.
  struct Results : public ResultsBase<Results> {
    std::string config_name;
    std::vector<Stream> streams;
  };

  // Static configuration.

  // The "render delay" that is passed through the timing component and
  // render buffer. It is added and subtracted through the pipeline, so it is
  // important to have it set.
  static constexpr TimeDelta kRenderDelay = TimeDelta::Millis(10);
  // The threshold used for "in time" calculations of assembled and rendered
  // margins. A threshold of `TimeDelta::Zero()` wouldn't work well, because
  // the recorded render timestamp of the frames have a loss of precision:
  // the scheduling is done on a microsecond level, but the
  // `EncodedFrame::RenderTimestamp()` returns values on a millisecond level.
  // See https://g-issues.webrtc.org/issues/483303559.
  static constexpr TimeDelta kInTimeMarginThreshold = TimeDelta::Micros(-500);

  explicit RenderingSimulator(Config config);
  ~RenderingSimulator();

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

  Results Simulate(const ParsedRtcEventLog& parsed_log) const;

 private:
  const Config config_;
};

// -- Comparators and sorting --
inline bool RenderOrder(const RenderingSimulator::Frame& a,
                        const RenderingSimulator::Frame& b) {
  return a.render_timestamp < b.render_timestamp;
}
inline void SortByRenderOrder(std::span<RenderingSimulator::Frame> frames) {
  absl::c_stable_sort(frames, RenderOrder);
}

inline bool DecodedOrder(const RenderingSimulator::Frame& a,
                         const RenderingSimulator::Frame& b) {
  return a.decoded_timestamp < b.decoded_timestamp;
}
inline void SortByDecodedOrder(std::span<RenderingSimulator::Frame> frames) {
  absl::c_stable_sort(frames, DecodedOrder);
}

inline bool RenderedOrder(const RenderingSimulator::Frame& a,
                          const RenderingSimulator::Frame& b) {
  return a.rendered_timestamp < b.rendered_timestamp;
}
inline void SortByRenderedOrder(std::span<RenderingSimulator::Frame> frames) {
  absl::c_stable_sort(frames, RenderedOrder);
}

// -- Inter-frame metrics --
// Difference in render time (target) between two frames.
inline TimeDelta InterRenderTime(const RenderingSimulator::Frame& cur,
                                 const RenderingSimulator::Frame& prev) {
  if (!cur.render_timestamp.IsFinite() && !prev.render_timestamp.IsFinite()) {
    return TimeDelta::PlusInfinity();
  }
  return cur.render_timestamp - prev.render_timestamp;
}

// Difference in decoded time (actual) between two frames.
inline TimeDelta InterDecodedTime(const RenderingSimulator::Frame& cur,
                                  const RenderingSimulator::Frame& prev) {
  if (!cur.decoded_timestamp.IsFinite() && !prev.decoded_timestamp.IsFinite()) {
    return TimeDelta::PlusInfinity();
  }
  return cur.decoded_timestamp - prev.decoded_timestamp;
}

// Difference in rendered time (actual) between two frames.
inline TimeDelta InterRenderedTime(const RenderingSimulator::Frame& cur,
                                   const RenderingSimulator::Frame& prev) {
  if (!cur.rendered_timestamp.IsFinite() &&
      !prev.rendered_timestamp.IsFinite()) {
    return TimeDelta::PlusInfinity();
  }
  return cur.rendered_timestamp - prev.rendered_timestamp;
}

}  // namespace webrtc::video_timing_simulator

#endif  // VIDEO_TIMING_SIMULATOR_RENDERING_SIMULATOR_H_
