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

package ice

import (
	
	

	
	
	
)

// MultiUDPMuxDefault implements both UDPMux and AllConnsGetter,
// allowing users to pass multiple UDPMux instances to the ICE agent
// configuration.
type MultiUDPMuxDefault struct {
	muxes          []UDPMux
	localAddrToMux map[string]UDPMux
}

// NewMultiUDPMuxDefault creates an instance of MultiUDPMuxDefault that
// uses the provided UDPMux instances.
func ( ...UDPMux) *MultiUDPMuxDefault {
	 := make(map[string]UDPMux)
	for ,  := range  {
		for ,  := range .GetListenAddresses() {
			[.String()] = 
		}
	}

	return &MultiUDPMuxDefault{
		muxes:          ,
		localAddrToMux: ,
	}
}

// GetConn returns a PacketConn given the connection's ufrag and network
// creates the connection if an existing one can't be found.
func ( *MultiUDPMuxDefault) ( string,  net.Addr) (net.PacketConn, error) {
	,  := .localAddrToMux[.String()]
	if ! {
		return nil, errNoUDPMuxAvailable
	}

	return .GetConn(, )
}

// RemoveConnByUfrag stops and removes the muxed packet connection
// from all underlying UDPMux instances.
func ( *MultiUDPMuxDefault) ( string) {
	for ,  := range .muxes {
		.RemoveConnByUfrag()
	}
}

// Close the multi mux, no further connections could be created.
func ( *MultiUDPMuxDefault) () error {
	var  error
	for ,  := range .muxes {
		if  := .Close();  != nil {
			 = 
		}
	}

	return 
}

// GetListenAddresses returns the list of addresses that this mux is listening on.
func ( *MultiUDPMuxDefault) () []net.Addr {
	 := make([]net.Addr, 0, len(.localAddrToMux))
	for ,  := range .muxes {
		 = append(, .GetListenAddresses()...)
	}

	return 
}

// NewMultiUDPMuxFromPort creates an instance of MultiUDPMuxDefault that
// listen all interfaces on the provided port.
func ( int,  ...UDPMuxFromPortOption) (*MultiUDPMuxDefault, error) { //nolint:cyclop
	 := multiUDPMuxFromPortParam{
		networks: []NetworkType{NetworkTypeUDP4, NetworkTypeUDP6},
	}
	for ,  := range  {
		.apply(&)
	}

	if .net == nil {
		var  error
		if .net,  = stdnet.NewNet();  != nil {
			return nil, fmt.Errorf("failed to get create network: %w", )
		}
	}

	, ,  := localInterfaces(.net, .ifFilter, .ipFilter, .networks, .includeLoopback)
	if  != nil {
		return nil, 
	}

	 := make([]net.PacketConn, 0, len())
	for ,  := range  {
		,  := .net.ListenUDP("udp", &net.UDPAddr{
			IP:   .AsSlice(),
			Port: ,
			Zone: .Zone(),
		})
		if  != nil {
			 = 

			break
		}
		if .readBufferSize > 0 {
			_ = .SetReadBuffer(.readBufferSize)
		}
		if .writeBufferSize > 0 {
			_ = .SetWriteBuffer(.writeBufferSize)
		}
		 = append(, )
	}

	if  != nil {
		for ,  := range  {
			_ = .Close()
		}

		return nil, 
	}

	 := make([]UDPMux, 0, len())
	for ,  := range  {
		 := NewUDPMuxDefault(UDPMuxParams{
			Logger:  .logger,
			UDPConn: ,
			Net:     .net,
		})
		 = append(, )
	}

	return NewMultiUDPMuxDefault(...), nil
}

// UDPMuxFromPortOption provide options for NewMultiUDPMuxFromPort.
type UDPMuxFromPortOption interface {
	apply(*multiUDPMuxFromPortParam)
}

type multiUDPMuxFromPortParam struct {
	ifFilter        func(string) (keep bool)
	ipFilter        func(ip net.IP) (keep bool)
	networks        []NetworkType
	readBufferSize  int
	writeBufferSize int
	logger          logging.LeveledLogger
	includeLoopback bool
	net             transport.Net
}

type udpMuxFromPortOption struct {
	f func(*multiUDPMuxFromPortParam)
}

func ( *udpMuxFromPortOption) ( *multiUDPMuxFromPortParam) {
	.f()
}

// UDPMuxFromPortWithInterfaceFilter set the filter to filter out interfaces that should not be used.
func ( func(string) ( bool)) UDPMuxFromPortOption {
	return &udpMuxFromPortOption{
		f: func( *multiUDPMuxFromPortParam) {
			.ifFilter = 
		},
	}
}

// UDPMuxFromPortWithIPFilter set the filter to filter out IP addresses that should not be used.
func ( func( net.IP) ( bool)) UDPMuxFromPortOption {
	return &udpMuxFromPortOption{
		f: func( *multiUDPMuxFromPortParam) {
			.ipFilter = 
		},
	}
}

// UDPMuxFromPortWithNetworks set the networks that should be used. default is both IPv4 and IPv6.
func ( ...NetworkType) UDPMuxFromPortOption {
	return &udpMuxFromPortOption{
		f: func( *multiUDPMuxFromPortParam) {
			.networks = 
		},
	}
}

// UDPMuxFromPortWithReadBufferSize set the UDP connection read buffer size.
func ( int) UDPMuxFromPortOption {
	return &udpMuxFromPortOption{
		f: func( *multiUDPMuxFromPortParam) {
			.readBufferSize = 
		},
	}
}

// UDPMuxFromPortWithWriteBufferSize set the UDP connection write buffer size.
func ( int) UDPMuxFromPortOption {
	return &udpMuxFromPortOption{
		f: func( *multiUDPMuxFromPortParam) {
			.writeBufferSize = 
		},
	}
}

// UDPMuxFromPortWithLogger set the logger for the created UDPMux.
func ( logging.LeveledLogger) UDPMuxFromPortOption {
	return &udpMuxFromPortOption{
		f: func( *multiUDPMuxFromPortParam) {
			.logger = 
		},
	}
}

// UDPMuxFromPortWithLoopback set loopback interface should be included.
func () UDPMuxFromPortOption {
	return &udpMuxFromPortOption{
		f: func( *multiUDPMuxFromPortParam) {
			.includeLoopback = true
		},
	}
}

// UDPMuxFromPortWithNet sets the network transport to use.
func ( transport.Net) UDPMuxFromPortOption {
	return &udpMuxFromPortOption{
		f: func( *multiUDPMuxFromPortParam) {
			.net = 
		},
	}
}