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

#include "video/timing/simulator/decodability_simulator.h"

#include <cstdint>
#include <memory>
#include <optional>
#include <utility>

#include "absl/base/nullability.h"
#include "absl/container/flat_hash_map.h"
#include "api/environment/environment.h"
#include "api/sequence_checker.h"
#include "api/units/data_size.h"
#include "api/units/timestamp.h"
#include "api/video/encoded_frame.h"
#include "logging/rtc_event_log/rtc_event_log_parser.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/sequence_number_unwrapper.h"
#include "rtc_base/thread_annotations.h"
#include "video/timing/simulator/assembler.h"
#include "video/timing/simulator/decodability_tracker.h"
#include "video/timing/simulator/frame_base.h"
#include "video/timing/simulator/receiver.h"
#include "video/timing/simulator/rtc_event_log_driver.h"
#include "video/timing/simulator/rtp_packet_simulator.h"

namespace webrtc::video_timing_simulator {

namespace {

// Observes the `Assembler` and `DecodabilityTracker` in order to collect
// frame metadata for decodable frames.
class DecodableFrameCollector : public AssemblerEvents,
                                public DecodabilityTrackerEvents {
 public:
  DecodableFrameCollector(const Environment& env, uint32_t ssrc)
      : env_(env), ssrc_(ssrc) {
    RTC_DCHECK_NE(ssrc_, 0);
  }
  ~DecodableFrameCollector() override = default;

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

  // Implements `AssemblerEvents`.
  void OnAssembledFrame(const EncodedFrame& assembled_frame) override {
    RTC_DCHECK_RUN_ON(&sequence_checker_);
    Timestamp now = env_.clock().CurrentTime();
    if (!creation_timestamp_) {
      creation_timestamp_ = now;
    }
    int64_t frame_id = assembled_frame.Id();
    if (frames_.contains(frame_id)) {
      RTC_LOG(LS_WARNING) << "Assembled frame_id=" << frame_id
                          << " on ssrc=" << ssrc_
                          << " had already been collected. Dropping it."
                          << " (simulated_ts=" << env_.clock().CurrentTime()
                          << ")";
      return;
    }
    auto& frame = frames_[frame_id];
    RTC_DCHECK(!assembled_frame.PacketInfos().empty());
    frame.num_packets = static_cast<int>(assembled_frame.PacketInfos().size());
    frame.size = DataSize::Bytes(assembled_frame.size());
    frame.unwrapped_rtp_timestamp =
        rtp_timestamp_unwrapper_.Unwrap(assembled_frame.RtpTimestamp());
    frame.assembled_timestamp = now;
  }

  // Implements `DecodabilityTrackerEvents`.
  void OnDecodableFrame(const EncodedFrame& decodable_frame) override {
    RTC_DCHECK_RUN_ON(&sequence_checker_);
    int64_t frame_id = decodable_frame.Id();
    auto it = frames_.find(frame_id);
    if (it == frames_.end()) {
      RTC_LOG(LS_WARNING)
          << "Decodable frame_id=" << frame_id << " on ssrc=" << ssrc_
          << " had no assembly information collected. Dropping it."
          << " (simulated_ts=" << env_.clock().CurrentTime() << ")";
      return;
    }
    auto& frame = it->second;
    frame.decodable_timestamp = env_.clock().CurrentTime();
  }

  DecodabilitySimulator::Stream GetStream() const {
    RTC_DCHECK_RUN_ON(&sequence_checker_);
    DecodabilitySimulator::Stream stream;
    stream.ssrc = ssrc_;
    if (creation_timestamp_) {
      stream.creation_timestamp = *creation_timestamp_;
    }
    stream.frames.reserve(frames_.size());
    for (const auto& [key, value] : frames_) {
      stream.frames.push_back(value);
    }
    SortByArrivalOrder(stream.frames);
    return stream;
  }

 private:
  SequenceChecker sequence_checker_;
  const Environment env_;
  const uint32_t ssrc_;

  std::optional<Timestamp> creation_timestamp_
      RTC_GUARDED_BY(sequence_checker_);
  SeqNumUnwrapper<uint32_t> rtp_timestamp_unwrapper_
      RTC_GUARDED_BY(sequence_checker_);
  absl::flat_hash_map</*frame_id*/ int64_t, DecodabilitySimulator::Frame>
      frames_ RTC_GUARDED_BY(sequence_checker_);
};

// Combines all objects needed to perform decodability simulation of a single
// stream. Inserts the streams results to the `results` pointer when `Close()`
// is called (at the end of simulation).
class DecodabilitySimulatorStream : public RtcEventLogDriver::StreamInterface {
 public:
  DecodabilitySimulatorStream(const Environment& env,
                              uint32_t ssrc,
                              uint32_t rtx_ssrc,
                              DecodabilitySimulator::Results* absl_nonnull
                                  results)
      : collector_(env, ssrc),
        tracker_(env, DecodabilityTracker::Config{.ssrc = ssrc}, &collector_),
        assembler_(env, ssrc, &collector_, &tracker_),
        receiver_(env, ssrc, rtx_ssrc, &assembler_),
        results_(*results) {
    RTC_DCHECK_RUN_ON(&sequence_checker_);
    tracker_.SetDecodedFrameIdCallback(&assembler_);
  }
  ~DecodabilitySimulatorStream() override = default;

  // Implements `RtcEventLogDriver::StreamInterface`.
  void InsertSimulatedPacket(
      const RtpPacketSimulator::SimulatedPacket& simulated_packet) override {
    RTC_DCHECK_RUN_ON(&sequence_checker_);
    receiver_.InsertSimulatedPacket(simulated_packet);
  }

  void Close() override {
    RTC_DCHECK_RUN_ON(&sequence_checker_);
    DecodabilitySimulator::Stream stream = collector_.GetStream();
    if (!stream.IsEmpty()) {
      RTC_DCHECK_NE(stream.ssrc, 0u);
      results_.streams.push_back(stream);
    }
  }

 private:
  SequenceChecker sequence_checker_;
  DecodableFrameCollector collector_ RTC_GUARDED_BY(sequence_checker_);
  DecodabilityTracker tracker_ RTC_GUARDED_BY(sequence_checker_);
  Assembler assembler_ RTC_GUARDED_BY(sequence_checker_);
  Receiver receiver_ RTC_GUARDED_BY(sequence_checker_);
  DecodabilitySimulator::Results& results_;
};

}  // namespace

DecodabilitySimulator::DecodabilitySimulator(Config config) : config_(config) {}

DecodabilitySimulator::~DecodabilitySimulator() = default;

DecodabilitySimulator::Results DecodabilitySimulator::Simulate(
    const ParsedRtcEventLog& parsed_log) const {
  // Outputs.
  Results results;

  // Simulation.
  auto stream_factory = [&results](const Environment& env, uint32_t ssrc,
                                   uint32_t rtx_ssrc) {
    return std::make_unique<DecodabilitySimulatorStream>(env, ssrc, rtx_ssrc,
                                                         &results);
  };
  // Decodability should not be a function of any field trials, so we pass the
  // empty string here.
  RtcEventLogDriver rtc_event_log_simulator(
      {.reuse_streams = config_.reuse_streams,
       .ssrc_filter = config_.ssrc_filter},
      &parsed_log,
      /*field_trials_string=*/"", std::move(stream_factory));
  rtc_event_log_simulator.Simulate();

  // Return.
  SortByStreamOrder(results.streams);
  return results;
}

}  // namespace webrtc::video_timing_simulator
