// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	
	
	
	
)

// https://www.rfc-editor.org/rfc/rfc8888.html#name-rtcp-congestion-control-fee
//  0                   1                   2                   3
//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |V=2|P| FMT=11  |   PT = 205    |          length               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                 SSRC of RTCP packet sender                    |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                   SSRC of 1st RTP Stream                      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          begin_seq            |          num_reports          |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |R|ECN|  Arrival time offset    | ...                           .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// .                                                               .
// .                                                               .
// .                                                               .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                   SSRC of nth RTP Stream                      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          begin_seq            |          num_reports          |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |R|ECN|  Arrival time offset    | ...                           |
// .                                                               .
// .                                                               .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                 Report Timestamp (32 bits)                    |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

var (
	errReportBlockLength   = errors.New("feedback report blocks must be at least 8 bytes")
	errIncorrectNumReports = errors.New("feedback report block contains less reports than num_reports")
	errMetricBlockLength   = errors.New("feedback report metric blocks must be exactly 2 bytes")
)

// ECN represents the two ECN bits
type ECN uint8

const (
	//nolint:misspell
	// ECNNonECT signals Non ECN-Capable Transport, Non-ECT
	ECNNonECT ECN = iota // 00

	//nolint:misspell
	// ECNECT1 signals ECN Capable Transport, ECT(0)
	ECNECT1 // 01

	//nolint:misspell
	// ECNECT0 signals ECN Capable Transport, ECT(1)
	ECNECT0 // 10

	// ECNCE signals ECN Congestion Encountered, CE
	ECNCE // 11
)

const (
	reportTimestampLength = 4
	reportBlockOffset     = 8
)

// CCFeedbackReport is a Congestion Control Feedback Report as defined in
// https://www.rfc-editor.org/rfc/rfc8888.html#name-rtcp-congestion-control-fee
type CCFeedbackReport struct {
	// SSRC of sender
	SenderSSRC uint32

	// Report Blocks
	ReportBlocks []CCFeedbackReportBlock

	// Basetime
	ReportTimestamp uint32
}

// DestinationSSRC returns an array of SSRC values that this packet refers to.
func ( CCFeedbackReport) () []uint32 {
	 := make([]uint32, len(.ReportBlocks))
	for ,  := range .ReportBlocks {
		[] = .MediaSSRC
	}
	return 
}

// Len returns the length of the report in bytes
func ( *CCFeedbackReport) () int {
	return .MarshalSize()
}

// MarshalSize returns the size of the packet once marshaled
func ( *CCFeedbackReport) () int {
	 := 0
	for ,  := range .ReportBlocks {
		 += .len()
	}
	return reportBlockOffset +  + reportTimestampLength
}

// Header returns the Header associated with this packet.
func ( *CCFeedbackReport) () Header {
	return Header{
		Padding: false,
		Count:   FormatCCFB,
		Type:    TypeTransportSpecificFeedback,
		Length:  uint16(.MarshalSize()/4 - 1),
	}
}

// Marshal encodes the Congestion Control Feedback Report in binary
func ( CCFeedbackReport) () ([]byte, error) {
	 := .Header()
	,  := .Marshal()
	if  != nil {
		return nil, 
	}
	 := 4 * (.Length + 1)
	 := make([]byte, )
	copy([:headerLength], )
	binary.BigEndian.PutUint32([headerLength:], .SenderSSRC)
	 := reportBlockOffset
	for ,  := range .ReportBlocks {
		,  := .marshal()
		if  != nil {
			return nil, 
		}
		copy([:], )
		 += .len()
	}

	binary.BigEndian.PutUint32([:], .ReportTimestamp)
	return , nil
}

func ( CCFeedbackReport) () string {
	 := fmt.Sprintf("CCFB:\n\tHeader %v\n", .Header())
	 += fmt.Sprintf("CCFB:\n\tSender SSRC %d\n", .SenderSSRC)
	 += fmt.Sprintf("\tReport Timestamp %d\n", .ReportTimestamp)
	 += "\tFeedback Reports \n"
	for ,  := range .ReportBlocks {
		 += fmt.Sprintf("%v ", )
	}
	 += "\n"
	return 
}

// Unmarshal decodes the Congestion Control Feedback Report from binary
func ( *CCFeedbackReport) ( []byte) error {
	if len() < headerLength+ssrcLength+reportTimestampLength {
		return errPacketTooShort
	}

	var  Header
	if  := .Unmarshal();  != nil {
		return 
	}
	if .Type != TypeTransportSpecificFeedback {
		return errWrongType
	}

	.SenderSSRC = binary.BigEndian.Uint32([headerLength:])

	 := len() - reportTimestampLength
	.ReportTimestamp = binary.BigEndian.Uint32([:])

	 := reportBlockOffset
	.ReportBlocks = []CCFeedbackReportBlock{}
	for  <  {
		var  CCFeedbackReportBlock
		if  := .unmarshal([:]);  != nil {
			return 
		}
		.ReportBlocks = append(.ReportBlocks, )
		 += .len()
	}

	return nil
}

const (
	ssrcOffset          = 0
	beginSequenceOffset = 4
	numReportsOffset    = 6
	reportsOffset       = 8

	maxMetricBlocks = 16384
)

// CCFeedbackReportBlock is a Feedback Report Block
type CCFeedbackReportBlock struct {
	// SSRC of the RTP stream on which this block is reporting
	MediaSSRC     uint32
	BeginSequence uint16
	MetricBlocks  []CCFeedbackMetricBlock
}

// len returns the length of the report block in bytes
func ( *CCFeedbackReportBlock) () int {
	 := len(.MetricBlocks)
	if %2 != 0 {
		++
	}
	return reportsOffset + 2*
}

func ( CCFeedbackReportBlock) () string {
	 := fmt.Sprintf("\tReport Block Media SSRC %d\n", .MediaSSRC)
	 += fmt.Sprintf("\tReport Begin Sequence Nr %d\n", .BeginSequence)
	 += fmt.Sprintf("\tReport length %d\n\t", len(.MetricBlocks))
	for ,  := range .MetricBlocks {
		 += fmt.Sprintf("{nr: %d, rx: %v, ts: %v} ", .BeginSequence+uint16(), .Received, .ArrivalTimeOffset)
	}
	 += "\n"
	return 
}

// marshal encodes the Congestion Control Feedback Report Block in binary
func ( CCFeedbackReportBlock) () ([]byte, error) {
	if len(.MetricBlocks) > maxMetricBlocks {
		return nil, errTooManyReports
	}

	 := make([]byte, .len())
	binary.BigEndian.PutUint32([ssrcOffset:], .MediaSSRC)
	binary.BigEndian.PutUint16([beginSequenceOffset:], .BeginSequence)

	 := uint16(len(.MetricBlocks))
	if  > 0 {
		--
	}

	binary.BigEndian.PutUint16([numReportsOffset:], )

	for ,  := range .MetricBlocks {
		,  := .marshal()
		if  != nil {
			return nil, 
		}
		copy([reportsOffset+*2:], )
	}

	return , nil
}

// Unmarshal decodes the Congestion Control Feedback Report Block from binary
func ( *CCFeedbackReportBlock) ( []byte) error {
	if len() < reportsOffset {
		return errReportBlockLength
	}
	.MediaSSRC = binary.BigEndian.Uint32([:beginSequenceOffset])
	.BeginSequence = binary.BigEndian.Uint16([beginSequenceOffset:numReportsOffset])
	 := binary.BigEndian.Uint16([numReportsOffset:])
	if  == 0 {
		return nil
	}

	if int(.BeginSequence)+int() > math.MaxUint16 {
		return errIncorrectNumReports
	}

	 := .BeginSequence + 
	 := int( - .BeginSequence + 1)

	if len() < reportsOffset+*2 {
		return errIncorrectNumReports
	}

	.MetricBlocks = make([]CCFeedbackMetricBlock, )
	for  := int(0);  < ; ++ {
		var  CCFeedbackMetricBlock
		 := reportsOffset + 2*
		if  := .unmarshal([ : +2]);  != nil {
			return 
		}
		.MetricBlocks[] = 
	}
	return nil
}

const (
	metricBlockLength = 2
)

// CCFeedbackMetricBlock is a Feedback Metric Block
type CCFeedbackMetricBlock struct {
	Received bool
	ECN      ECN

	// Offset in 1/1024 seconds before Report Timestamp
	ArrivalTimeOffset uint16
}

// Marshal encodes the Congestion Control Feedback Metric Block in binary
func ( CCFeedbackMetricBlock) () ([]byte, error) {
	 := make([]byte, 2)
	 := uint16(0)
	if .Received {
		 = 1
	}
	,  := setNBitsOfUint16(0, 1, 0, )
	if  != nil {
		return nil, 
	}
	,  = setNBitsOfUint16(, 2, 1, uint16(.ECN))
	if  != nil {
		return nil, 
	}
	,  = setNBitsOfUint16(, 13, 3, .ArrivalTimeOffset)
	if  != nil {
		return nil, 
	}

	binary.BigEndian.PutUint16(, )
	return , nil
}

// Unmarshal decodes the Congestion Control Feedback Metric Block from binary
func ( *CCFeedbackMetricBlock) ( []byte) error {
	if len() != metricBlockLength {
		return errMetricBlockLength
	}
	.Received = [0]&0x80 != 0
	if !.Received {
		.ECN = ECNNonECT
		.ArrivalTimeOffset = 0
		return nil
	}
	.ECN = ECN([0] >> 5 & 0x03)
	.ArrivalTimeOffset = binary.BigEndian.Uint16() & 0x1FFF
	return nil
}