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

package rtcp

import (
	
	
)

/*
Converts an RTCP Packet into a human-readable format. The Packets
themselves can control the presentation as follows:

  - Fields of a type that have a String() method will be formatted
    with that String method (which should not emit '\n' characters)

  - Otherwise, fields with a tag containing a "fmt" string will use that
    format when serializing the value. For example, to format an SSRC
    value as base 16 insted of base 10:

    type ExamplePacket struct {
    LocalSSRC   uint32   `fmt:"0x%X"`
    RemotsSSRCs []uint32 `fmt:"%X"`
    }

- If no fmt string is present, "%+v" is used by default

The intention of this stringify() function is to simplify creation
of String() methods on new packet types, as it provides a simple
baseline implementation that works well in the majority of cases.
*/
func stringify( Packet) string {
	 := reflect.Indirect(reflect.ValueOf())
	return formatField(.Type().String(), "", , "")
}

func formatField( string,  string,  interface{},  string) string { //nolint:gocognit
	 := 
	 := reflect.ValueOf()

	if !.IsValid() {
		return fmt.Sprintf("%s%s: <nil>\n", , )
	}

	 := reflect.TypeOf().Implements(reflect.TypeOf((*Packet)(nil)).Elem())

	// Resolve pointers to their underlying values
	if .Type().Kind() == reflect.Ptr && !.IsNil() {
		 := reflect.Indirect()
		if .IsValid() {
			 = 
		}
	}

	// If the field type has a custom String method, use that
	// (unless we're a packet, since we want to avoid recursing
	// back into this function if the Packet's String() method
	// uses it)
	if  := .MethodByName("String"); ! && .IsValid() {
		 += fmt.Sprintf("%s: %s\n", , .Call([]reflect.Value{}))
		return 
	}

	switch .Kind() {
	case reflect.Struct:
		 += fmt.Sprintf("%s:\n", )
		for  := 0;  < .NumField(); ++ {
			if .Field().CanInterface() {
				 = .Type().Field().Tag.Get("fmt")
				if  == "" {
					 = "%+v"
				}
				 += (.Type().Field().Name, , .Field().Interface(), +"\t")
			}
		}
	case reflect.Slice:
		 := .Type().Elem().Kind()
		,  := .Type().Elem().MethodByName("String")
		if  ||  == reflect.Struct ||  == reflect.Ptr ||  == reflect.Interface ||  == reflect.Slice {
			 += fmt.Sprintf("%s:\n", )
			for  := 0;  < .Len(); ++ {
				 := fmt.Sprint()
				// Since interfaces can hold different types of things, we add the
				// most specific type name to the name to make it clear what the
				// subsequent fields represent.
				if .Index().Kind() == reflect.Interface {
					 += fmt.Sprintf(" (%s)", reflect.Indirect(reflect.ValueOf(.Index().Interface())).Type())
				}
				if .Index().CanInterface() {
					 += (, , .Index().Interface(), +"\t")
				}
			}
			return 
		}

		// If we didn't take care of stringing the value already, we fall through to the
		// generic case. This will print slices of basic types on a single line.
		fallthrough
	default:
		if .CanInterface() {
			 += fmt.Sprintf("%s: "++"\n", , .Interface())
		}
	}
	return 
}