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

package sdp

// Marshal takes a SDP struct to text
// https://tools.ietf.org/html/rfc4566#section-5
// Session description
//
//	v=  (protocol version)
//	o=  (originator and session identifier)
//	s=  (session name)
//	i=* (session information)
//	u=* (URI of description)
//	e=* (email address)
//	p=* (phone number)
//	c=* (connection information -- not required if included in
//	     all media)
//	b=* (zero or more bandwidth information lines)
//	One or more time descriptions ("t=" and "r=" lines; see below)
//	z=* (time zone adjustments)
//	k=* (encryption key)
//	a=* (zero or more session attribute lines)
//	Zero or more media descriptions
//
// Time description
//
//	t=  (time the session is active)
//	r=* (zero or more repeat times)
//
// Media description, if present
//
//	m=  (media name and transport address)
//	i=* (media title)
//	c=* (connection information -- optional if included at
//	     session level)
//	b=* (zero or more bandwidth information lines)
//	k=* (encryption key)
//	a=* (zero or more media attribute lines)
func ( *SessionDescription) () ([]byte, error) { //nolint:cyclop
	 := make(marshaller, 0, .MarshalSize())

	.addKeyValue("v=", .Version.marshalInto)
	.addKeyValue("o=", .Origin.marshalInto)
	.addKeyValue("s=", .SessionName.marshalInto)

	if .SessionInformation != nil {
		.addKeyValue("i=", .SessionInformation.marshalInto)
	}

	if .URI != nil {
		 = append(, "u="...)
		 = append(, .URI.String()...)
		 = append(, "\r\n"...)
	}

	if .EmailAddress != nil {
		.addKeyValue("e=", .EmailAddress.marshalInto)
	}

	if .PhoneNumber != nil {
		.addKeyValue("p=", .PhoneNumber.marshalInto)
	}

	if .ConnectionInformation != nil {
		.addKeyValue("c=", .ConnectionInformation.marshalInto)
	}

	for ,  := range .Bandwidth {
		.addKeyValue("b=", .marshalInto)
	}

	for ,  := range .TimeDescriptions {
		.addKeyValue("t=", .Timing.marshalInto)
		for ,  := range .RepeatTimes {
			.addKeyValue("r=", .marshalInto)
		}
	}

	if len(.TimeZones) > 0 {
		 = append(, "z="...)
		for ,  := range .TimeZones {
			if  > 0 {
				 = append(, ' ')
			}
			 = .marshalInto()
		}
		 = append(, "\r\n"...)
	}

	if .EncryptionKey != nil {
		.addKeyValue("k=", .EncryptionKey.marshalInto)
	}

	for ,  := range .Attributes {
		.addKeyValue("a=", .marshalInto)
	}

	for ,  := range .MediaDescriptions {
		.addKeyValue("m=", .MediaName.marshalInto)

		if .MediaTitle != nil {
			.addKeyValue("i=", .MediaTitle.marshalInto)
		}

		if .ConnectionInformation != nil {
			.addKeyValue("c=", .ConnectionInformation.marshalInto)
		}

		for ,  := range .Bandwidth {
			.addKeyValue("b=", .marshalInto)
		}

		if .EncryptionKey != nil {
			.addKeyValue("k=", .EncryptionKey.marshalInto)
		}

		for ,  := range .Attributes {
			.addKeyValue("a=", .marshalInto)
		}
	}

	return , nil
}

// `$type=` and CRLF size.
const lineBaseSize = 4

// MarshalSize returns the size of the SessionDescription once marshaled.
func ( *SessionDescription) () ( int) { //nolint:cyclop
	 += lineBaseSize + .Version.marshalSize()
	 += lineBaseSize + .Origin.marshalSize()
	 += lineBaseSize + .SessionName.marshalSize()

	if .SessionInformation != nil {
		 += lineBaseSize + .SessionInformation.marshalSize()
	}

	if .URI != nil {
		 += lineBaseSize + len(.URI.String())
	}

	if .EmailAddress != nil {
		 += lineBaseSize + .EmailAddress.marshalSize()
	}

	if .PhoneNumber != nil {
		 += lineBaseSize + .PhoneNumber.marshalSize()
	}

	if .ConnectionInformation != nil {
		 += lineBaseSize + .ConnectionInformation.marshalSize()
	}

	for ,  := range .Bandwidth {
		 += lineBaseSize + .marshalSize()
	}

	for ,  := range .TimeDescriptions {
		 += lineBaseSize + .Timing.marshalSize()
		for ,  := range .RepeatTimes {
			 += lineBaseSize + .marshalSize()
		}
	}

	if len(.TimeZones) > 0 {
		 += lineBaseSize

		for ,  := range .TimeZones {
			if  > 0 {
				++
			}
			 += .marshalSize()
		}
	}

	if .EncryptionKey != nil {
		 += lineBaseSize + .EncryptionKey.marshalSize()
	}

	for ,  := range .Attributes {
		 += lineBaseSize + .marshalSize()
	}

	for ,  := range .MediaDescriptions {
		 += lineBaseSize + .MediaName.marshalSize()
		if .MediaTitle != nil {
			 += lineBaseSize + .MediaTitle.marshalSize()
		}
		if .ConnectionInformation != nil {
			 += lineBaseSize + .ConnectionInformation.marshalSize()
		}

		for ,  := range .Bandwidth {
			 += lineBaseSize + .marshalSize()
		}

		if .EncryptionKey != nil {
			 += lineBaseSize + .EncryptionKey.marshalSize()
		}

		for ,  := range .Attributes {
			 += lineBaseSize + .marshalSize()
		}
	}

	return 
}

// marshaller contains state during marshaling.
type marshaller []byte

func ( *marshaller) ( string,  func([]byte) []byte) {
	* = append(*, ...)
	* = (*)
	* = append(*, "\r\n"...)
}

func lenUint( uint64) ( int) {
	if  == 0 {
		return 1
	}

	for  != 0 {
		 /= 10
		++
	}

	return
}

func lenInt( int64) ( int) {
	if  < 0 {
		return lenUint(uint64(-)) + 1
	}

	return lenUint(uint64())
}

func stringFromMarshal( func([]byte) []byte,  func() int) string {
	return string((make([]byte, 0, ())))
}