#define MS_CLASS "RTC::RTP::RtpStreamRecv"
// #define MS_LOG_DEV_LEVEL 3

#include "RTC/RTP/RtpStreamRecv.hpp"
#include "Logger.hpp"
#include "RTC/RTP/Codecs/Tools.hpp"
#include "Utils.hpp"

namespace RTC
{
	namespace RTP
	{
		/* Static. */

		static constexpr uint64_t InactivityCheckInterval{ 1500u };        // In ms.
		static constexpr uint64_t InactivityCheckIntervalWithDtx{ 5000u }; // In ms.

		/* TransmissionCounter methods. */

		RtpStreamRecv::TransmissionCounter::TransmissionCounter(
		  SharedInterface* shared, uint8_t spatialLayers, uint8_t temporalLayers, size_t windowSize)
		{
			MS_TRACE();

			// Reserve vectors capacity.
			this->spatialLayerCounters = std::vector<std::vector<RTC::RtpDataCounter>>(spatialLayers);

			for (auto& spatialLayerCounter : this->spatialLayerCounters)
			{
				for (uint8_t tIdx{ 0u }; tIdx < temporalLayers; ++tIdx)
				{
					spatialLayerCounter.emplace_back(shared, /*ignorePaddingOnlyPackets*/ true, windowSize);
				}
			}
		}

		void RtpStreamRecv::TransmissionCounter::Update(const RTP::Packet* packet)
		{
			MS_TRACE();

			auto spatialLayer  = packet->GetSpatialLayer();
			auto temporalLayer = packet->GetTemporalLayer();

			// Sanity check. Do not allow spatial layers higher than defined.
			spatialLayer =
			  std::min(static_cast<size_t>(spatialLayer), this->spatialLayerCounters.size() - 1);

			// Sanity check. Do not allow temporal layers higher than defined.
			temporalLayer =
			  std::min(static_cast<size_t>(temporalLayer), this->spatialLayerCounters[0].size() - 1);

			auto& counter = this->spatialLayerCounters[spatialLayer][temporalLayer];

			counter.Update(packet);
		}

		uint32_t RtpStreamRecv::TransmissionCounter::GetBitrate(uint64_t nowMs)
		{
			MS_TRACE();

			uint32_t rate{ 0u };

			for (auto& spatialLayerCounter : this->spatialLayerCounters)
			{
				for (auto& temporalLayerCounter : spatialLayerCounter)
				{
					rate += temporalLayerCounter.GetBitrate(nowMs);
				}
			}

			return rate;
		}

		uint32_t RtpStreamRecv::TransmissionCounter::GetBitrate(
		  uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer)
		{
			MS_TRACE();

			MS_ASSERT(spatialLayer < this->spatialLayerCounters.size(), "spatialLayer too high");
			MS_ASSERT(
			  temporalLayer < this->spatialLayerCounters[spatialLayer].size(), "temporalLayer too high");

			// Return 0 if specified layers are not being received.
			auto& counter = this->spatialLayerCounters[spatialLayer][temporalLayer];

			if (counter.GetBitrate(nowMs) == 0)
			{
				return 0u;
			}

			uint32_t rate{ 0u };

			// Iterate all temporal layers of spatial layers previous to the given one.
			for (uint8_t sIdx{ 0u }; sIdx < spatialLayer; ++sIdx)
			{
				for (size_t tIdx{ 0u }; tIdx < this->spatialLayerCounters[sIdx].size(); ++tIdx)
				{
					auto& temporalLayerCounter = this->spatialLayerCounters[sIdx][tIdx];

					rate += temporalLayerCounter.GetBitrate(nowMs);
				}
			}

			// Add the given spatial layer with up to the given temporal layer.
			for (uint8_t tIdx{ 0u }; tIdx <= temporalLayer; ++tIdx)
			{
				auto& temporalLayerCounter = this->spatialLayerCounters[spatialLayer][tIdx];

				rate += temporalLayerCounter.GetBitrate(nowMs);
			}

			return rate;
		}

		uint32_t RtpStreamRecv::TransmissionCounter::GetSpatialLayerBitrate(
		  uint64_t nowMs, uint8_t spatialLayer)
		{
			MS_TRACE();

			MS_ASSERT(spatialLayer < this->spatialLayerCounters.size(), "spatialLayer too high");

			uint32_t rate{ 0u };

			for (size_t tIdx{ 0u }; tIdx < this->spatialLayerCounters[spatialLayer].size(); ++tIdx)
			{
				auto& temporalLayerCounter = this->spatialLayerCounters[spatialLayer][tIdx];

				rate += temporalLayerCounter.GetBitrate(nowMs);
			}

			return rate;
		}

		uint32_t RtpStreamRecv::TransmissionCounter::GetLayerBitrate(
		  uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer)
		{
			MS_TRACE();

			MS_ASSERT(spatialLayer < this->spatialLayerCounters.size(), "spatialLayer too high");
			MS_ASSERT(
			  temporalLayer < this->spatialLayerCounters[spatialLayer].size(), "temporalLayer too high");

			auto& counter = this->spatialLayerCounters[spatialLayer][temporalLayer];

			return counter.GetBitrate(nowMs);
		}

		size_t RtpStreamRecv::TransmissionCounter::GetPacketCount() const
		{
			MS_TRACE();

			size_t packetCount{ 0u };

			for (const auto& spatialLayerCounter : this->spatialLayerCounters)
			{
				for (const auto& temporalLayerCounter : spatialLayerCounter)
				{
					packetCount += temporalLayerCounter.GetPacketCount();
				}
			}

			return packetCount;
		}

		size_t RtpStreamRecv::TransmissionCounter::GetBytes() const
		{
			MS_TRACE();

			size_t bytes{ 0u };

			for (const auto& spatialLayerCounter : this->spatialLayerCounters)
			{
				for (const auto& temporalLayerCounter : spatialLayerCounter)
				{
					bytes += temporalLayerCounter.GetBytes();
				}
			}

			return bytes;
		}

		/* Instance methods. */

		RtpStreamRecv::RtpStreamRecv(
		  RTP::RtpStreamRecv::Listener* listener,
		  SharedInterface* shared,
		  RTP::RtpStream::Params& params,
		  uint32_t sendNackDelayMs,
		  bool useRtpInactivityCheck)
		  : RTP::RtpStream::RtpStream(listener, shared, params, 10),
		    sendNackDelayMs(sendNackDelayMs),
		    useRtpInactivityCheck(useRtpInactivityCheck),
		    transmissionCounter(
		      shared, params.spatialLayers, params.temporalLayers, this->params.useDtx ? 6000 : 2500),
		    mediaTransmissionCounter(shared, /*ignorePaddingOnlyPackets*/ true)
		{
			MS_TRACE();

			if (this->params.useNack)
			{
				this->nackGenerator.reset(new RTC::NackGenerator(this, this->shared, this->sendNackDelayMs));
			}

			this->inactive = false;

			if (this->useRtpInactivityCheck)
			{
				// Run the RTP inactivity periodic timer (use a different timeout if DTX is
				// enabled).
				this->inactivityCheckPeriodicTimer = this->shared->CreateTimer(this);

				this->inactivityCheckPeriodicTimer->Start(
				  this->params.useDtx ? InactivityCheckIntervalWithDtx : InactivityCheckInterval);
			}
		}

		RtpStreamRecv::~RtpStreamRecv()
		{
			MS_TRACE();

			// Close the RTP inactivity check periodic timer.
			delete this->inactivityCheckPeriodicTimer;
			this->inactivityCheckPeriodicTimer = nullptr;
		}

		flatbuffers::Offset<FBS::RtpStream::Stats> RtpStreamRecv::FillBufferStats(
		  flatbuffers::FlatBufferBuilder& builder)
		{
			MS_TRACE();

			const uint64_t nowMs = this->shared->GetTimeMs();

			auto baseStats = RTP::RtpStream::FillBufferStats(builder);

			std::vector<flatbuffers::Offset<FBS::RtpStream::BitrateByLayer>> bitrateByLayer;

			if (GetSpatialLayers() > 1 || GetTemporalLayers() > 1)
			{
				for (uint8_t sIdx = 0; sIdx < GetSpatialLayers(); ++sIdx)
				{
					for (uint8_t tIdx = 0; tIdx < GetTemporalLayers(); ++tIdx)
					{
						auto layer = std::to_string(sIdx) + "." + std::to_string(tIdx);

						bitrateByLayer.emplace_back(
						  FBS::RtpStream::CreateBitrateByLayerDirect(
						    builder, layer.c_str(), GetBitrate(nowMs, sIdx, tIdx)));
					}
				}
			}

			auto stats = FBS::RtpStream::CreateRecvStatsDirect(
			  builder,
			  baseStats,
			  this->transmissionCounter.GetPacketCount(),
			  this->transmissionCounter.GetBytes(),
			  this->transmissionCounter.GetBitrate(nowMs),
			  &bitrateByLayer);

			return FBS::RtpStream::CreateStats(builder, FBS::RtpStream::StatsData::RecvStats, stats.Union());
		}

		bool RtpStreamRecv::ReceivePacket(RTP::Packet* packet)
		{
			MS_TRACE();

			// Call the parent method.
			if (!RTP::RtpStream::ReceiveStreamPacket(packet))
			{
				MS_WARN_TAG(rtp, "packet discarded");

				return false;
			}

			// Process the packet at codec level.
			if (packet->GetPayloadType() == GetPayloadType())
			{
				RTP::Codecs::Tools::ProcessRtpPacket(packet, GetMimeType(), this->templateDependencyStructure);
			}

			// Pass the packet to the NackGenerator.
			if (this->params.useNack)
			{
				// If there is RTX just provide the NackGenerator with the packet.
				if (HasRtx())
				{
					this->nackGenerator->ReceivePacket(packet, /*isRecovered*/ false);
				}
				// If there is no RTX and NackGenerator returns true it means that it
				// was a NACKed packet.
				else if (this->nackGenerator->ReceivePacket(packet, /*isRecovered*/ false))
				{
					// Mark the packet as retransmitted and repaired.
					RTP::RtpStream::PacketRetransmitted(packet);
					RTP::RtpStream::PacketRepaired(packet);
				}
			}

			// Calculate jitter.
			CalculateJitter(packet->GetTimestamp());

			// Increase transmission counter.
			this->transmissionCounter.Update(packet);

			// Increase media transmission counter.
			this->mediaTransmissionCounter.Update(packet);

			// Padding only packet, do not consider it for stream activation.
			if (packet->GetPayloadLength() == 0)
			{
				return true;
			}

			// Not inactive anymore.
			if (this->inactive)
			{
				this->inactive = false;

				ResetScore(10, /*notify*/ true);
			}

			// Restart the inactivityCheckPeriodicTimer.
			if (this->inactivityCheckPeriodicTimer)
			{
				this->inactivityCheckPeriodicTimer->Restart();
			}

			return true;
		}

		bool RtpStreamRecv::ReceiveRtxPacket(RTP::Packet* packet)
		{
			MS_TRACE();

			if (!this->params.useNack)
			{
				MS_WARN_TAG(rtx, "NACK not supported");

				return false;
			}

			MS_ASSERT(packet->GetSsrc() == this->params.rtxSsrc, "invalid ssrc on RTX packet");

			// Check that the payload type corresponds to the one negotiated.
			if (packet->GetPayloadType() != this->params.rtxPayloadType)
			{
				MS_WARN_TAG(
				  rtx,
				  "ignoring RTX packet with invalid payload type [ssrc:%" PRIu32 ", seq:%" PRIu16
				  ", pt:%" PRIu8 "]",
				  packet->GetSsrc(),
				  packet->GetSequenceNumber(),
				  packet->GetPayloadType());

				return false;
			}

			if (HasRtx())
			{
				if (!this->rtxStream->ReceivePacket(packet))
				{
					MS_WARN_TAG(rtx, "RTX packet discarded");

					return false;
				}
			}

#if MS_LOG_DEV_LEVEL == 3
			// Get the RTX packet sequence number for logging purposes.
			auto rtxSeq = packet->GetSequenceNumber();
#endif

			// Get the original RTP packet.
			if (!packet->RtxDecode(this->params.payloadType, this->params.ssrc))
			{
				MS_DEBUG_DEV(
				  "ignoring empty RTX packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", pt:%" PRIu8 "]",
				  packet->GetSsrc(),
				  packet->GetSequenceNumber(),
				  packet->GetPayloadType());

				return false;
			}

			MS_DEBUG_DEV(
			  "received RTX packet [ssrc:%" PRIu32 ", seq:%" PRIu16 "] recovering original [ssrc:%" PRIu32
			  ", seq:%" PRIu16 "]",
			  this->params.rtxSsrc,
			  rtxSeq,
			  packet->GetSsrc(),
			  packet->GetSequenceNumber());

			// If not a valid packet ignore it.
			if (!RTP::RtpStream::UpdateSeq(packet))
			{
				MS_WARN_TAG(
				  rtx,
				  "invalid RTX packet [ssrc:%" PRIu32 ", seq:%" PRIu16 "]",
				  packet->GetSsrc(),
				  packet->GetSequenceNumber());

				return false;
			}

			// Process the packet at codec level.
			if (packet->GetPayloadType() == GetPayloadType())
			{
				RTP::Codecs::Tools::ProcessRtpPacket(packet, GetMimeType(), this->templateDependencyStructure);
			}

			// Mark the packet as retransmitted.
			RTP::RtpStream::PacketRetransmitted(packet);

			// Pass the packet to the NackGenerator and return true just if this was a
			// NACKed packet or a RTX packet containing a non yet seen original RTP
			// packet.
			if (this->nackGenerator->ReceivePacket(packet, /*isRecovered*/ true))
			{
				// Mark the packet as repaired.
				RTP::RtpStream::PacketRepaired(packet);

				// Increase transmission counter.
				this->transmissionCounter.Update(packet);

				// Padding only packet, do not consider it for stream activation.
				if (packet->GetPayloadLength() == 0)
				{
					return true;
				}

				// Not inactive anymore.
				if (this->inactive)
				{
					this->inactive = false;

					ResetScore(10, /*notify*/ true);
				}

				// Restart the inactivityCheckPeriodicTimer.
				if (this->inactivityCheckPeriodicTimer)
				{
					this->inactivityCheckPeriodicTimer->Restart();
				}

				return true;
			}

			return false;
		}

		RTC::RTCP::ReceiverReport* RtpStreamRecv::GetRtcpReceiverReport()
		{
			MS_TRACE();

			uint8_t worstRemoteFractionLost{ 0 };

			if (this->params.useInBandFec)
			{
				// Notify the listener so we'll get the worst remote fraction lost.
				static_cast<RTP::RtpStreamRecv::Listener*>(this->listener)
				  ->OnRtpStreamNeedWorstRemoteFractionLost(this, worstRemoteFractionLost);

				if (worstRemoteFractionLost > 0)
				{
					MS_DEBUG_TAG(rtcp, "using worst remote fraction lost:%" PRIu8, worstRemoteFractionLost);
				}
			}

			auto* report = new RTC::RTCP::ReceiverReport();

			report->SetSsrc(GetSsrc());

			const int32_t prevPacketsLost = this->packetsLost;

			// Calculate packets expected and lost.
			auto expected = GetExpectedPackets();

			if (expected > this->mediaTransmissionCounter.GetPacketCount())
			{
				this->packetsLost = expected - this->mediaTransmissionCounter.GetPacketCount();
			}
			else
			{
				this->packetsLost = 0;
			}

			// Calculate fraction lost.
			const uint32_t expectedInterval = expected - this->expectedPrior;

			this->expectedPrior = expected;

			const uint32_t receivedInterval =
			  this->mediaTransmissionCounter.GetPacketCount() - this->receivedPrior;

			this->receivedPrior = this->mediaTransmissionCounter.GetPacketCount();

			const int32_t lostInterval = expectedInterval - receivedInterval;

			if (expectedInterval == 0 || lostInterval <= 0)
			{
				this->fractionLost = 0;
			}
			else
			{
				this->fractionLost = std::round((static_cast<double>(lostInterval << 8) / expectedInterval));
			}

			// Worst remote fraction lost is not worse than local one.
			if (worstRemoteFractionLost <= this->fractionLost)
			{
				this->reportedPacketsLost += (this->packetsLost - prevPacketsLost);

				report->SetTotalLost(this->reportedPacketsLost);
				report->SetFractionLost(this->fractionLost);
			}
			else
			{
				// Recalculate packetsLost.
				const uint32_t newLostInterval = (worstRemoteFractionLost * expectedInterval) >> 8;

				this->reportedPacketsLost += newLostInterval;

				report->SetTotalLost(this->reportedPacketsLost);
				report->SetFractionLost(worstRemoteFractionLost);
			}

			// Fill the rest of the report.
			report->SetLastSeq(static_cast<uint32_t>(this->maxSeq) + this->cycles);
			report->SetJitter(this->jitter);

			if (this->lastSrReceived != 0)
			{
				// Get delay in milliseconds.
				auto delayMs = static_cast<uint32_t>(this->shared->GetTimeMs() - this->lastSrReceived);
				// Express delay in units of 1/65536 seconds.
				uint32_t dlsr = (delayMs / 1000) << 16;

				dlsr |= uint32_t{ (delayMs % 1000) * 65536 / 1000 };

				report->SetDelaySinceLastSenderReport(dlsr);
				report->SetLastSenderReport(this->lastSrTimestamp);
			}
			else
			{
				report->SetDelaySinceLastSenderReport(0);
				report->SetLastSenderReport(0);
			}

			return report;
		}

		RTC::RTCP::ReceiverReport* RtpStreamRecv::GetRtxRtcpReceiverReport()
		{
			MS_TRACE();

			if (HasRtx())
			{
				return this->rtxStream->GetRtcpReceiverReport();
			}

			return nullptr;
		}

		void RtpStreamRecv::ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report)
		{
			MS_TRACE();

			this->lastSrReceived  = this->shared->GetTimeMs();
			this->lastSrTimestamp = report->GetNtpSec() << 16;
			this->lastSrTimestamp += report->GetNtpFrac() >> 16;

			// Update info about last Sender Report.
			Utils::Time::Ntp ntp{}; // NOLINT(cppcoreguidelines-pro-type-member-init)

			ntp.seconds   = report->GetNtpSec();
			ntp.fractions = report->GetNtpFrac();

			this->lastSenderReportNtpMs = Utils::Time::Ntp2TimeMs(ntp);
			this->lastSenderReportTs    = report->GetRtpTs();

			// Update the score with the current RR.
			UpdateScore();
		}

		void RtpStreamRecv::ReceiveRtxRtcpSenderReport(RTC::RTCP::SenderReport* report)
		{
			MS_TRACE();

			if (HasRtx())
			{
				this->rtxStream->ReceiveRtcpSenderReport(report);
			}
		}

		void RtpStreamRecv::ReceiveRtcpXrDelaySinceLastRr(RTC::RTCP::DelaySinceLastRr::SsrcInfo* ssrcInfo)
		{
			MS_TRACE();

			/* Calculate RTT. */

			// Get the NTP representation of the current timestamp.
			const uint64_t nowMs = this->shared->GetTimeMs();
			auto ntp             = Utils::Time::TimeMs2Ntp(nowMs);

			// Get the compact NTP representation of the current timestamp.
			uint32_t compactNtp = (ntp.seconds & 0x0000FFFF) << 16;

			compactNtp |= (ntp.fractions & 0xFFFF0000) >> 16;

			const uint32_t lastRr = ssrcInfo->GetLastReceiverReport();
			const uint32_t dlrr   = ssrcInfo->GetDelaySinceLastReceiverReport();

			// RTT in 1/2^16 second fractions.
			uint32_t rtt{ 0 };

			// If no Receiver Extended Report was received by the remote endpoint yet,
			// ignore lastRr and dlrr values in the Sender Extended Report.
			if (lastRr && dlrr && (compactNtp > dlrr + lastRr))
			{
				rtt = compactNtp - dlrr - lastRr;
			}

			// RTT in milliseconds.
			this->rtt = static_cast<float>(rtt >> 16) * 1000;
			this->rtt += (static_cast<float>(rtt & 0x0000FFFF) / 65536) * 1000;

			// Avoid negative RTT value since it doesn't make sense.
			this->rtt = std::max(this->rtt, 0.0f);

			// Tell it to the NackGenerator.
			if (this->params.useNack)
			{
				this->nackGenerator->UpdateRtt(static_cast<uint32_t>(this->rtt));
			}
		}

		void RtpStreamRecv::RequestKeyFrame()
		{
			MS_TRACE();

			if (this->params.usePli)
			{
				MS_DEBUG_2TAGS(rtcp, rtx, "sending PLI [ssrc:%" PRIu32 "]", GetSsrc());

				// Sender SSRC should be 0 since there is no media sender involved, but
				// some implementations like gstreamer will fail to process it otherwise.
				RTC::RTCP::FeedbackPsPliPacket packet(GetSsrc(), GetSsrc());

				packet.Serialize(RTC::RTCP::SerializationBuffer);

				this->pliCount++;

				// Notify the listener.
				static_cast<RTP::RtpStreamRecv::Listener*>(this->listener)
				  ->OnRtpStreamSendRtcpPacket(this, &packet);
			}
			else if (this->params.useFir)
			{
				MS_DEBUG_2TAGS(rtcp, rtx, "sending FIR [ssrc:%" PRIu32 "]", GetSsrc());

				// Sender SSRC should be 0 since there is no media sender involved, but
				// some implementations like gstreamer will fail to process it otherwise.
				RTC::RTCP::FeedbackPsFirPacket packet(GetSsrc(), GetSsrc());
				auto* item = new RTC::RTCP::FeedbackPsFirItem(GetSsrc(), ++this->firSeqNumber);

				packet.AddItem(item);
				packet.Serialize(RTC::RTCP::SerializationBuffer);

				this->firCount++;

				// Notify the listener.
				static_cast<RTP::RtpStreamRecv::Listener*>(this->listener)
				  ->OnRtpStreamSendRtcpPacket(this, &packet);
			}
		}

		void RtpStreamRecv::Pause()
		{
			MS_TRACE();

			if (this->inactivityCheckPeriodicTimer)
			{
				this->inactivityCheckPeriodicTimer->Stop();
			}

			if (this->params.useNack)
			{
				this->nackGenerator->Reset();
			}

			// Reset jitter.
			this->transit = 0;
			this->jitter  = 0;
		}

		void RtpStreamRecv::Resume()
		{
			MS_TRACE();

			if (this->inactivityCheckPeriodicTimer && !this->inactive)
			{
				this->inactivityCheckPeriodicTimer->Restart();
			}
		}

		void RtpStreamRecv::CalculateJitter(uint32_t rtpTimestamp)
		{
			MS_TRACE();

			if (GetClockRate() == 0u)
			{
				return;
			}

			// NOTE: Based on https://github.com/versatica/mediasoup/issues/1018.
			auto transit =
			  static_cast<int>((this->shared->GetTimeMs() * GetClockRate() / 1000) - rtpTimestamp);
			int d = transit - this->transit;

			// First transit calculation, save and return.
			if (this->transit == 0)
			{
				this->transit = transit;

				return;
			}

			this->transit = transit;

			if (d < 0)
			{
				d = -d;
			}

			this->jitter += (1. / 16.) * (static_cast<float>(d) - this->jitter);
		}

		void RtpStreamRecv::UpdateScore()
		{
			MS_TRACE();

			// Calculate number of packets expected in this interval.
			const auto totalExpected = GetExpectedPackets();
			const uint32_t expected  = totalExpected - this->expectedPriorScore;

			this->expectedPriorScore = totalExpected;

			// Calculate number of packets received in this interval.
			const auto totalReceived = this->mediaTransmissionCounter.GetPacketCount();
			const uint32_t received  = totalReceived - this->receivedPriorScore;

			this->receivedPriorScore = totalReceived;

			// Calculate number of packets lost in this interval.
			uint32_t lost;

			if (expected < received)
			{
				lost = 0;
			}
			else
			{
				lost = expected - received;
			}

			// Calculate number of packets repaired in this interval.
			const auto totalRepaired = this->packetsRepaired;
			uint32_t repaired        = totalRepaired - this->repairedPriorScore;

			this->repairedPriorScore = totalRepaired;

			// Calculate number of packets retransmitted in this interval.
			const auto totatRetransmitted = this->packetsRetransmitted;
			uint32_t retransmitted        = totatRetransmitted - this->retransmittedPriorScore;

			this->retransmittedPriorScore = totatRetransmitted;

			if (this->inactive)
			{
				return;
			}

			// We didn't expect more packets to come.
			if (expected == 0)
			{
				RTP::RtpStream::UpdateScore(10);

				return;
			}

			lost = std::min(lost, received);

			if (repaired > lost)
			{
				if (HasRtx())
				{
					repaired = lost;
					retransmitted -= repaired - lost;
				}
				else
				{
					lost = repaired;
				}
			}

#if MS_LOG_DEV_LEVEL == 3
			MS_DEBUG_TAG(
			  score,
			  "[totalExpected:%" PRIu32 ", totalReceived:%zu, totalRepaired:%zu",
			  totalExpected,
			  totalReceived,
			  totalRepaired);

			MS_DEBUG_TAG(
			  score,
			  "fixed values [expected:%" PRIu32 ", received:%" PRIu32 ", lost:%" PRIu32
			  ", repaired:%" PRIu32 ", retransmitted:%" PRIu32,
			  expected,
			  received,
			  lost,
			  repaired,
			  retransmitted);
#endif

			auto repairedRatio  = static_cast<float>(repaired) / static_cast<float>(received);
			auto repairedWeight = std::pow(1 / (repairedRatio + 1), 4);

			MS_ASSERT(retransmitted >= repaired, "repaired packets cannot be more than retransmitted ones");

			if (retransmitted > 0)
			{
				repairedWeight *= static_cast<float>(repaired) / retransmitted;
			}

			lost -= repaired * repairedWeight;

			auto deliveredRatio = static_cast<float>(received - lost) / static_cast<float>(received);
			auto score          = static_cast<uint8_t>(std::round(std::pow(deliveredRatio, 4) * 10));

#if MS_LOG_DEV_LEVEL == 3
			MS_DEBUG_TAG(
			  score,
			  "[deliveredRatio:%f, repairedRatio:%f, repairedWeight:%f, new lost:%" PRIu32
			  ", score:%" PRIu8 "]",
			  deliveredRatio,
			  repairedRatio,
			  repairedWeight,
			  lost,
			  score);
#endif

			// Call the parent method for update score.
			RTP::RtpStream::UpdateScore(score);
		}

		void RtpStreamRecv::UserOnSequenceNumberReset()
		{
			MS_TRACE();

			// Nothing to do.
		}

		inline void RtpStreamRecv::OnTimer(TimerHandleInterface* timer)
		{
			MS_TRACE();

			if (timer == this->inactivityCheckPeriodicTimer)
			{
				this->inactive = true;

				if (GetScore() != 0)
				{
					MS_WARN_2TAGS(
					  rtp, score, "RTP inactivity detected, resetting score to 0 [ssrc:%" PRIu32 "]", GetSsrc());
				}

				ResetScore(0, /*notify*/ true);
			}
		}

		inline void RtpStreamRecv::OnNackGeneratorNackRequired(const std::vector<uint16_t>& seqNumbers)
		{
			MS_TRACE();

			MS_ASSERT(this->params.useNack, "NACK required but not supported");

			MS_DEBUG_TAG(
			  rtx,
			  "triggering NACK [ssrc:%" PRIu32 ", first seq:%" PRIu16 ", num packets:%zu]",
			  this->params.ssrc,
			  seqNumbers[0],
			  seqNumbers.size());

			RTC::RTCP::FeedbackRtpNackPacket packet(0, GetSsrc());

			auto it        = seqNumbers.begin();
			const auto end = seqNumbers.end();
			size_t numPacketsRequested{ 0 };

			while (it != end)
			{
				uint16_t seq;
				uint16_t bitmask{ 0 };

				seq = *it;
				++it;

				while (it != end)
				{
					const uint16_t shift = *it - seq - 1;

					if (shift > 15)
					{
						break;
					}

					bitmask |= (1 << shift);
					++it;
				}

				auto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(seq, bitmask);

				packet.AddItem(nackItem);

				numPacketsRequested += nackItem->CountRequestedPackets();
			}

			// Ensure that the RTCP packet fits into the RTCP buffer.
			if (packet.GetSize() > RTC::RTCP::SerializationBufferSize)
			{
				MS_WARN_TAG(rtx, "cannot send RTCP NACK packet, size too big (%zu bytes)", packet.GetSize());

				return;
			}

			this->nackCount++;
			this->nackPacketCount += numPacketsRequested;

			packet.Serialize(RTC::RTCP::SerializationBuffer);

			// Notify the listener.
			static_cast<RTP::RtpStreamRecv::Listener*>(this->listener)->OnRtpStreamSendRtcpPacket(this, &packet);
		}

		inline void RtpStreamRecv::OnNackGeneratorKeyFrameRequired()
		{
			MS_TRACE();

			MS_DEBUG_TAG(rtx, "requesting key frame [ssrc:%" PRIu32 "]", this->params.ssrc);

			RequestKeyFrame();
		}
	} // namespace RTP
} // namespace RTC
