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

//go:build !js
// +build !js

package webrtc

import (
	
	
	

	
	
	
)

// ICEGatherer gathers local host, server reflexive and relay
// candidates, as well as enabling the retrieval of local Interactive
// Connectivity Establishment (ICE) parameters which can be
// exchanged in signaling.
type ICEGatherer struct {
	lock  sync.RWMutex
	log   logging.LeveledLogger
	state ICEGathererState

	validatedServers []*stun.URI
	gatherPolicy     ICETransportPolicy

	agent *ice.Agent

	onLocalCandidateHandler atomic.Value // func(candidate *ICECandidate)
	onStateChangeHandler    atomic.Value // func(state ICEGathererState)

	// Used for GatheringCompletePromise
	onGatheringCompleteHandler atomic.Value // func()

	api *API

	// Used to set the corresponding media stream identification tag and media description index
	// for ICE candidates generated by this gatherer.
	sdpMid        atomic.Value  // string
	sdpMLineIndex atomic.Uint32 // uint16
}

// NewICEGatherer creates a new NewICEGatherer.
// This constructor is part of the ORTC API. It is not
// meant to be used together with the basic WebRTC API.
func ( *API) ( ICEGatherOptions) (*ICEGatherer, error) {
	var  []*stun.URI
	if len(.ICEServers) > 0 {
		for ,  := range .ICEServers {
			,  := .urls()
			if  != nil {
				return nil, 
			}
			 = append(, ...)
		}
	}

	return &ICEGatherer{
		state:            ICEGathererStateNew,
		gatherPolicy:     .ICEGatherPolicy,
		validatedServers: ,
		api:              ,
		log:              .settingEngine.LoggerFactory.NewLogger("ice"),
		sdpMid:           atomic.Value{},
		sdpMLineIndex:    atomic.Uint32{},
	}, nil
}

func ( *ICEGatherer) () error { //nolint:cyclop
	.lock.Lock()
	defer .lock.Unlock()

	if .agent != nil || .State() != ICEGathererStateNew {
		return nil
	}

	 := []ice.CandidateType{}
	if .api.settingEngine.candidates.ICELite {
		 = append(, ice.CandidateTypeHost)
	} else if .gatherPolicy == ICETransportPolicyRelay {
		 = append(, ice.CandidateTypeRelay)
	}

	var  ice.CandidateType
	switch .api.settingEngine.candidates.NAT1To1IPCandidateType {
	case ICECandidateTypeHost:
		 = ice.CandidateTypeHost
	case ICECandidateTypeSrflx:
		 = ice.CandidateTypeServerReflexive
	default:
		 = ice.CandidateTypeUnspecified
	}

	 := .api.settingEngine.candidates.MulticastDNSMode
	if  != ice.MulticastDNSModeDisabled &&  != ice.MulticastDNSModeQueryAndGather {
		// If enum is in state we don't recognized default to MulticastDNSModeQueryOnly
		 = ice.MulticastDNSModeQueryOnly
	}

	 := &ice.AgentConfig{
		Lite:                   .api.settingEngine.candidates.ICELite,
		Urls:                   .validatedServers,
		PortMin:                .api.settingEngine.ephemeralUDP.PortMin,
		PortMax:                .api.settingEngine.ephemeralUDP.PortMax,
		DisconnectedTimeout:    .api.settingEngine.timeout.ICEDisconnectedTimeout,
		FailedTimeout:          .api.settingEngine.timeout.ICEFailedTimeout,
		KeepaliveInterval:      .api.settingEngine.timeout.ICEKeepaliveInterval,
		LoggerFactory:          .api.settingEngine.LoggerFactory,
		CandidateTypes:         ,
		HostAcceptanceMinWait:  .api.settingEngine.timeout.ICEHostAcceptanceMinWait,
		SrflxAcceptanceMinWait: .api.settingEngine.timeout.ICESrflxAcceptanceMinWait,
		PrflxAcceptanceMinWait: .api.settingEngine.timeout.ICEPrflxAcceptanceMinWait,
		RelayAcceptanceMinWait: .api.settingEngine.timeout.ICERelayAcceptanceMinWait,
		STUNGatherTimeout:      .api.settingEngine.timeout.ICESTUNGatherTimeout,
		InterfaceFilter:        .api.settingEngine.candidates.InterfaceFilter,
		IPFilter:               .api.settingEngine.candidates.IPFilter,
		NAT1To1IPs:             .api.settingEngine.candidates.NAT1To1IPs,
		NAT1To1IPCandidateType: ,
		IncludeLoopback:        .api.settingEngine.candidates.IncludeLoopbackCandidate,
		Net:                    .api.settingEngine.net,
		MulticastDNSMode:       ,
		MulticastDNSHostName:   .api.settingEngine.candidates.MulticastDNSHostName,
		LocalUfrag:             .api.settingEngine.candidates.UsernameFragment,
		LocalPwd:               .api.settingEngine.candidates.Password,
		TCPMux:                 .api.settingEngine.iceTCPMux,
		UDPMux:                 .api.settingEngine.iceUDPMux,
		ProxyDialer:            .api.settingEngine.iceProxyDialer,
		DisableActiveTCP:       .api.settingEngine.iceDisableActiveTCP,
		MaxBindingRequests:     .api.settingEngine.iceMaxBindingRequests,
		BindingRequestHandler:  .api.settingEngine.iceBindingRequestHandler,
	}

	 := .api.settingEngine.candidates.ICENetworkTypes
	if len() == 0 {
		 = supportedNetworkTypes()
	}

	for ,  := range  {
		.NetworkTypes = append(.NetworkTypes, ice.NetworkType())
	}

	,  := ice.NewAgent()
	if  != nil {
		return 
	}

	.agent = 

	return nil
}

// Gather ICE candidates.
func ( *ICEGatherer) () error { //nolint:cyclop
	if  := .createAgent();  != nil {
		return 
	}

	 := .getAgent()
	// it is possible agent had just been closed
	if  == nil {
		return fmt.Errorf("%w: unable to gather", errICEAgentNotExist)
	}

	.setState(ICEGathererStateGathering)
	if  := .OnCandidate(func( ice.Candidate) {
		 := func(*ICECandidate) {}
		if ,  := .onLocalCandidateHandler.Load().(func( *ICECandidate));  &&  != nil {
			 = 
		}

		 := func() {}
		if ,  := .onGatheringCompleteHandler.Load().(func());  &&  != nil {
			 = 
		}

		 := ""

		if ,  := .sdpMid.Load().(string);  {
			 = 
		}

		 := uint16(.sdpMLineIndex.Load()) //nolint:gosec // G115

		if  != nil {
			,  := newICECandidateFromICE(, , )
			if  != nil {
				.log.Warnf("Failed to convert ice.Candidate: %s", )

				return
			}
			(&)
		} else {
			.setState(ICEGathererStateComplete)

			()
			(nil)
		}
	});  != nil {
		return 
	}

	return .GatherCandidates()
}

// set media stream identification tag and media description index for this gatherer.
func ( *ICEGatherer) ( string,  uint16) {
	.sdpMid.Store()
	.sdpMLineIndex.Store(uint32())
}

// Close prunes all local candidates, and closes the ports.
func ( *ICEGatherer) () error {
	return .close(false /* shouldGracefullyClose */)
}

// GracefulClose prunes all local candidates, and closes the ports. It also waits
// for any goroutines it started to complete. This is only safe to call outside of
// ICEGatherer callbacks or if in a callback, in its own goroutine.
func ( *ICEGatherer) () error {
	return .close(true /* shouldGracefullyClose */)
}

func ( *ICEGatherer) ( bool) error {
	.lock.Lock()
	defer .lock.Unlock()

	if .agent == nil {
		return nil
	}
	if  {
		if  := .agent.GracefulClose();  != nil {
			return 
		}
	} else {
		if  := .agent.Close();  != nil {
			return 
		}
	}

	.agent = nil
	.setState(ICEGathererStateClosed)

	return nil
}

// GetLocalParameters returns the ICE parameters of the ICEGatherer.
func ( *ICEGatherer) () (ICEParameters, error) {
	if  := .createAgent();  != nil {
		return ICEParameters{}, 
	}

	 := .getAgent()
	// it is possible agent had just been closed
	if  == nil {
		return ICEParameters{}, fmt.Errorf("%w: unable to get local parameters", errICEAgentNotExist)
	}

	, ,  := .GetLocalUserCredentials()
	if  != nil {
		return ICEParameters{}, 
	}

	return ICEParameters{
		UsernameFragment: ,
		Password:         ,
		ICELite:          false,
	}, nil
}

// GetLocalCandidates returns the sequence of valid local candidates associated with the ICEGatherer.
func ( *ICEGatherer) () ([]ICECandidate, error) {
	if  := .createAgent();  != nil {
		return nil, 
	}

	 := .getAgent()
	// it is possible agent had just been closed
	if  == nil {
		return nil, fmt.Errorf("%w: unable to get local candidates", errICEAgentNotExist)
	}

	,  := .GetLocalCandidates()
	if  != nil {
		return nil, 
	}

	 := ""
	if ,  := .sdpMid.Load().(string);  {
		 = 
	}

	 := uint16(.sdpMLineIndex.Load()) //nolint:gosec // G115

	return newICECandidatesFromICE(, , )
}

// OnLocalCandidate sets an event handler which fires when a new local ICE candidate is available
// Take note that the handler will be called with a nil pointer when gathering is finished.
func ( *ICEGatherer) ( func(*ICECandidate)) {
	.onLocalCandidateHandler.Store()
}

// OnStateChange fires any time the ICEGatherer changes.
func ( *ICEGatherer) ( func(ICEGathererState)) {
	.onStateChangeHandler.Store()
}

// State indicates the current state of the ICE gatherer.
func ( *ICEGatherer) () ICEGathererState {
	return atomicLoadICEGathererState(&.state)
}

func ( *ICEGatherer) ( ICEGathererState) {
	atomicStoreICEGathererState(&.state, )

	if ,  := .onStateChangeHandler.Load().(func( ICEGathererState));  &&  != nil {
		()
	}
}

func ( *ICEGatherer) () *ice.Agent {
	.lock.RLock()
	defer .lock.RUnlock()

	return .agent
}

func ( *ICEGatherer) ( *statsReportCollector) {
	 := .getAgent()
	if  == nil {
		return
	}

	.Collecting()
	go func( *statsReportCollector,  *ice.Agent) {
		for ,  := range .GetCandidatePairsStats() {
			.Collecting()

			,  := toICECandidatePairStats()
			if  != nil {
				.log.Error(.Error())
				.Done()

				continue
			}

			.Collect(.ID, )
		}

		for ,  := range .GetLocalCandidatesStats() {
			.Collecting()

			,  := getNetworkType(.NetworkType)
			if  != nil {
				.log.Error(.Error())
			}

			,  := getCandidateType(.CandidateType)
			if  != nil {
				.log.Error(.Error())
			}

			 := ICECandidateStats{
				Timestamp:     statsTimestampFrom(.Timestamp),
				ID:            .ID,
				Type:          StatsTypeLocalCandidate,
				IP:            .IP,
				Port:          int32(.Port), //nolint:gosec // G115, no overflow, port
				Protocol:      .Protocol(),
				CandidateType: ,
				Priority:      int32(.Priority), //nolint:gosec
				URL:           .URL,
				RelayProtocol: .RelayProtocol,
				Deleted:       .Deleted,
			}
			.Collect(.ID, )
		}

		for ,  := range .GetRemoteCandidatesStats() {
			.Collecting()
			,  := getNetworkType(.NetworkType)
			if  != nil {
				.log.Error(.Error())
			}

			,  := getCandidateType(.CandidateType)
			if  != nil {
				.log.Error(.Error())
			}

			 := ICECandidateStats{
				Timestamp:     statsTimestampFrom(.Timestamp),
				ID:            .ID,
				Type:          StatsTypeRemoteCandidate,
				IP:            .IP,
				Port:          int32(.Port), //nolint:gosec // G115, no overflow, port
				Protocol:      .Protocol(),
				CandidateType: ,
				Priority:      int32(.Priority), //nolint:gosec // G115
				URL:           .URL,
				RelayProtocol: .RelayProtocol,
			}
			.Collect(.ID, )
		}
		.Done()
	}(, )
}

func ( *ICEGatherer) () (ICECandidatePairStats, bool) {
	 := .getAgent()
	if  == nil {
		return ICECandidatePairStats{}, false
	}

	,  := .GetSelectedCandidatePairStats()
	if ! {
		return ICECandidatePairStats{}, false
	}

	,  := toICECandidatePairStats()
	if  != nil {
		.log.Error(.Error())

		return ICECandidatePairStats{}, false
	}

	return , true
}