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

package sdp

import (
	
	
	
	
	
	

	
)

const (
	attributeKey = "a="
)

var (
	errExtractCodecRtpmap  = errors.New("could not extract codec from rtpmap")
	errExtractCodecFmtp    = errors.New("could not extract codec from fmtp")
	errExtractCodecRtcpFb  = errors.New("could not extract codec from rtcp-fb")
	errPayloadTypeNotFound = errors.New("payload type not found")
	errCodecNotFound       = errors.New("codec not found")
	errSyntaxError         = errors.New("SyntaxError")
)

// ConnectionRole indicates which of the end points should initiate the connection establishment.
type ConnectionRole int

const (
	// ConnectionRoleActive indicates the endpoint will initiate an outgoing connection.
	ConnectionRoleActive ConnectionRole = iota + 1

	// ConnectionRolePassive indicates the endpoint will accept an incoming connection.
	ConnectionRolePassive

	// ConnectionRoleActpass indicates the endpoint is willing to accept an incoming connection or
	// to initiate an outgoing connection.
	ConnectionRoleActpass

	// ConnectionRoleHoldconn indicates the endpoint does not want the connection to be established for the time being.
	ConnectionRoleHoldconn
)

func ( ConnectionRole) () string {
	switch  {
	case ConnectionRoleActive:
		return "active"
	case ConnectionRolePassive:
		return "passive"
	case ConnectionRoleActpass:
		return "actpass"
	case ConnectionRoleHoldconn:
		return "holdconn"
	default:
		return "Unknown"
	}
}

func newSessionID() (uint64, error) {
	// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26#section-5.2.1
	// Session ID is recommended to be constructed by generating a 64-bit
	// quantity with the highest bit set to zero and the remaining 63-bits
	// being cryptographically random.
	,  := randutil.CryptoUint64()

	return  & (^(uint64(1) << 63)), 
}

// Codec represents a codec.
type Codec struct {
	PayloadType        uint8
	Name               string
	ClockRate          uint32
	EncodingParameters string
	Fmtp               string
	RTCPFeedback       []string
}

const (
	unknown = iota
)

func ( Codec) () string {
	return fmt.Sprintf(
		"%d %s/%d/%s (%s) [%s]",
		.PayloadType,
		.Name,
		.ClockRate,
		.EncodingParameters,
		.Fmtp,
		strings.Join(.RTCPFeedback, ", "),
	)
}

func ( *Codec) ( string) {
	for ,  := range .RTCPFeedback {
		if  ==  {
			return
		}
	}

	.RTCPFeedback = append(.RTCPFeedback, )
}

func parseRtpmap( string) (Codec, error) {
	var  Codec
	 := errExtractCodecRtpmap

	// a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encoding parameters>]
	 := strings.Split(, " ")
	if len() != 2 {
		return , 
	}

	 := strings.Split([0], ":")
	if len() != 2 {
		return , 
	}

	,  := strconv.ParseUint([1], 10, 8)
	if  != nil {
		return , 
	}

	.PayloadType = uint8()

	 = strings.Split([1], "/")
	.Name = [0]
	 := len()
	if  > 1 {
		,  := strconv.ParseUint([1], 10, 32)
		if  != nil {
			return , 
		}
		.ClockRate = uint32()
	}
	if  > 2 {
		.EncodingParameters = [2]
	}

	return , nil
}

func parseFmtp( string) (Codec, error) {
	var  Codec
	 := errExtractCodecFmtp

	// a=fmtp:<format> <format specific parameters>
	 := strings.SplitN(, " ", 2)
	if len() != 2 {
		return , 
	}

	 := [1]

	 = strings.Split([0], ":")
	if len() != 2 {
		return , 
	}

	,  := strconv.ParseUint([1], 10, 8)
	if  != nil {
		return , 
	}

	.PayloadType = uint8()
	.Fmtp = 

	return , nil
}

func parseRtcpFb( string) ( Codec,  bool,  error) {
	var  uint64
	 = errExtractCodecRtcpFb

	// a=ftcp-fb:<payload type> <RTCP feedback type> [<RTCP feedback parameter>]
	 := strings.SplitN(, " ", 2)
	if len() != 2 {
		return
	}

	 := strings.Split([0], ":")
	if len() != 2 {
		return
	}

	 = [1] == "*"
	if ! {
		,  = strconv.ParseUint([1], 10, 8)
		if  != nil {
			return
		}

		.PayloadType = uint8()
	}

	.RTCPFeedback = append(.RTCPFeedback, [1])

	return , , nil
}

func mergeCodecs( Codec,  map[uint8]Codec) {
	 := [.PayloadType]

	if .PayloadType == 0 {
		.PayloadType = .PayloadType
	}
	if .Name == "" {
		.Name = .Name
	}
	if .ClockRate == 0 {
		.ClockRate = .ClockRate
	}
	if .EncodingParameters == "" {
		.EncodingParameters = .EncodingParameters
	}
	if .Fmtp == "" {
		.Fmtp = .Fmtp
	}
	.RTCPFeedback = append(.RTCPFeedback, .RTCPFeedback...)

	[.PayloadType] = 
}

func ( *SessionDescription) () map[uint8]Codec { //nolint:cyclop
	 := map[uint8]Codec{
		// static codecs that do not require a rtpmap
		0: {
			PayloadType: 0,
			Name:        "PCMU",
			ClockRate:   8000,
		},
		8: {
			PayloadType: 8,
			Name:        "PCMA",
			ClockRate:   8000,
		},
	}

	 := []string{}
	for ,  := range .MediaDescriptions {
		for ,  := range .Attributes {
			 := .String()
			switch {
			case strings.HasPrefix(, "rtpmap:"):
				,  := parseRtpmap()
				if  == nil {
					mergeCodecs(, )
				}
			case strings.HasPrefix(, "fmtp:"):
				,  := parseFmtp()
				if  == nil {
					mergeCodecs(, )
				}
			case strings.HasPrefix(, "rtcp-fb:"):
				, ,  := parseRtcpFb()
				switch {
				case  != nil:
				case :
					 = append(, .RTCPFeedback...)
				default:
					mergeCodecs(, )
				}
			}
		}
	}

	for ,  := range  {
		for ,  := range  {
			.appendRTCPFeedback()
		}

		[] = 
	}

	return 
}

func equivalentFmtp(,  string) bool {
	 := strings.Split(, ";")
	 := strings.Split(, ";")

	if len() != len() {
		return false
	}

	sort.Strings()
	sort.Strings()

	for ,  := range  {
		 = strings.TrimSpace()
		 := strings.TrimSpace([])
		if  !=  {
			return false
		}
	}

	return true
}

func codecsMatch(,  Codec) bool {
	if .Name != "" && !strings.EqualFold(.Name, .Name) {
		return false
	}
	if .ClockRate != 0 && .ClockRate != .ClockRate {
		return false
	}
	if .EncodingParameters != "" && .EncodingParameters != .EncodingParameters {
		return false
	}
	if .Fmtp != "" && !equivalentFmtp(.Fmtp, .Fmtp) {
		return false
	}

	return true
}

// GetCodecForPayloadType scans the SessionDescription for the given payload type and returns the codec.
func ( *SessionDescription) ( uint8) (Codec, error) {
	 := .buildCodecMap()

	,  := []
	if  {
		return , nil
	}

	return , errPayloadTypeNotFound
}

// GetPayloadTypeForCodec scans the SessionDescription for a codec that matches the provided codec
// as closely as possible and returns its payload type.
func ( *SessionDescription) ( Codec) (uint8, error) {
	 := .buildCodecMap()

	for ,  := range  {
		if codecsMatch(, ) {
			return , nil
		}
	}

	return 0, errCodecNotFound
}

type stateFn func(*lexer) (stateFn, error)

type lexer struct {
	desc  *SessionDescription
	cache *unmarshalCache
	baseLexer
}

type keyToState func(key byte) stateFn

func ( *lexer) ( keyToState) (stateFn, error) {
	,  := .readType()
	if errors.Is(, io.EOF) &&  == 0 {
		return nil, nil //nolint:nilnil
	} else if  != nil {
		return nil, 
	}

	if  := ();  != nil {
		return , nil
	}

	return nil, .syntaxError()
}