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

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

package webrtc

import (
	
	
	
	
	

	
	
	
	
)

// ICETransport allows an application access to information about the ICE
// transport over which packets are sent and received.
type ICETransport struct {
	lock sync.RWMutex

	role ICERole

	onConnectionStateChangeHandler         atomic.Value // func(ICETransportState)
	internalOnConnectionStateChangeHandler atomic.Value // func(ICETransportState)
	onSelectedCandidatePairChangeHandler   atomic.Value // func(*ICECandidatePair)

	state atomic.Value // ICETransportState

	gatherer *ICEGatherer
	conn     *ice.Conn
	mux      *mux.Mux

	ctxCancel func()

	loggerFactory logging.LoggerFactory

	log logging.LeveledLogger
}

// GetSelectedCandidatePair returns the selected candidate pair on which packets are sent
// if there is no selected pair nil is returned.
func ( *ICETransport) () (*ICECandidatePair, error) {
	 := .gatherer.getAgent()
	if  == nil {
		return nil, nil //nolint:nilnil
	}

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

	,  := newICECandidateFromICE(.Local, "", 0)
	if  != nil {
		return nil, 
	}

	,  := newICECandidateFromICE(.Remote, "", 0)
	if  != nil {
		return nil, 
	}

	return NewICECandidatePair(&, &), nil
}

// GetSelectedCandidatePairStats returns the selected candidate pair stats on which packets are sent
// if there is no selected pair empty stats, false is returned to indicate stats not available.
func ( *ICETransport) () (ICECandidatePairStats, bool) {
	return .gatherer.getSelectedCandidatePairStats()
}

// NewICETransport creates a new NewICETransport.
func ( *ICEGatherer,  logging.LoggerFactory) *ICETransport {
	 := &ICETransport{
		gatherer:      ,
		loggerFactory: ,
		log:           .NewLogger("ortc"),
	}
	.setState(ICETransportStateNew)

	return 
}

// Start incoming connectivity checks based on its configured role.
func ( *ICETransport) ( *ICEGatherer,  ICEParameters,  *ICERole) error { //nolint:cyclop
	.lock.Lock()
	defer .lock.Unlock()

	if .State() != ICETransportStateNew {
		return errICETransportNotInNew
	}

	if  != nil {
		.gatherer = 
	}

	if  := .ensureGatherer();  != nil {
		return 
	}

	 := .gatherer.getAgent()
	if  == nil {
		return fmt.Errorf("%w: unable to start ICETransport", errICEAgentNotExist)
	}

	if  := .OnConnectionStateChange(func( ice.ConnectionState) {
		 := newICETransportStateFromICE()

		.setState()
		.onConnectionStateChange()
	});  != nil {
		return 
	}
	if  := .OnSelectedCandidatePairChange(func(,  ice.Candidate) {
		,  := newICECandidatesFromICE([]ice.Candidate{, }, "", 0)
		if  != nil {
			.log.Warnf("%w: %s", errICECandiatesCoversionFailed, )

			return
		}
		.onSelectedCandidatePairChange(NewICECandidatePair(&[0], &[1]))
	});  != nil {
		return 
	}

	if  == nil {
		 := ICERoleControlled
		 = &
	}
	.role = *

	,  := context.WithCancel(context.Background())
	.ctxCancel = 

	// Drop the lock here to allow ICE candidates to be
	// added so that the agent can complete a connection
	.lock.Unlock()

	var  *ice.Conn
	var  error
	switch * {
	case ICERoleControlling:
		,  = .Dial(,
			.UsernameFragment,
			.Password)

	case ICERoleControlled:
		,  = .Accept(,
			.UsernameFragment,
			.Password)

	default:
		 = errICERoleUnknown
	}

	// Reacquire the lock to set the connection/mux
	.lock.Lock()
	if  != nil {
		return 
	}

	if .State() == ICETransportStateClosed {
		return errICETransportClosed
	}

	.conn = 

	 := mux.Config{
		Conn:          .conn,
		BufferSize:    int(.gatherer.api.settingEngine.getReceiveMTU()), //nolint:gosec // G115
		LoggerFactory: .loggerFactory,
	}
	.mux = mux.NewMux()

	return nil
}

// restart is not exposed currently because ORTC has users create a whole new ICETransport
// so for now lets keep it private so we don't cause ORTC users to depend on non-standard APIs.
func ( *ICETransport) () error {
	.lock.Lock()
	defer .lock.Unlock()

	 := .gatherer.getAgent()
	if  == nil {
		return fmt.Errorf("%w: unable to restart ICETransport", errICEAgentNotExist)
	}

	if  := .Restart(
		.gatherer.api.settingEngine.candidates.UsernameFragment,
		.gatherer.api.settingEngine.candidates.Password,
	);  != nil {
		return 
	}

	return .gatherer.Gather()
}

// Stop irreversibly stops the ICETransport.
func ( *ICETransport) () error {
	return .stop(false /* shouldGracefullyClose */)
}

// GracefulStop irreversibly stops the ICETransport. It also waits
// for any goroutines it started to complete. This is only safe to call outside of
// ICETransport callbacks or if in a callback, in its own goroutine.
func ( *ICETransport) () error {
	return .stop(true /* shouldGracefullyClose */)
}

func ( *ICETransport) ( bool) error {
	.lock.Lock()
	.setState(ICETransportStateClosed)

	if .ctxCancel != nil {
		.ctxCancel()
	}

	// mux and gatherer can only be set when ICETransport.State != Closed.
	 := .mux
	 := .gatherer
	.lock.Unlock()

	if  != nil {
		var  []error
		if  &&  != nil {
			// we can't access icegatherer/icetransport.Close via
			// mux's net.Conn Close so we call it earlier here.
			 = append(, .GracefulClose())
		}
		 = append(, .Close())

		return util.FlattenErrs()
	} else if  != nil {
		if  {
			return .GracefulClose()
		}

		return .Close()
	}

	return nil
}

// OnSelectedCandidatePairChange sets a handler that is invoked when a new
// ICE candidate pair is selected.
func ( *ICETransport) ( func(*ICECandidatePair)) {
	.onSelectedCandidatePairChangeHandler.Store()
}

func ( *ICETransport) ( *ICECandidatePair) {
	if ,  := .onSelectedCandidatePairChangeHandler.Load().(func(*ICECandidatePair));  {
		()
	}
}

// OnConnectionStateChange sets a handler that is fired when the ICE
// connection state changes.
func ( *ICETransport) ( func(ICETransportState)) {
	.onConnectionStateChangeHandler.Store()
}

func ( *ICETransport) ( ICETransportState) {
	if ,  := .onConnectionStateChangeHandler.Load().(func(ICETransportState));  {
		()
	}
	if ,  := .internalOnConnectionStateChangeHandler.Load().(func(ICETransportState));  {
		()
	}
}

// Role indicates the current role of the ICE transport.
func ( *ICETransport) () ICERole {
	.lock.RLock()
	defer .lock.RUnlock()

	return .role
}

// SetRemoteCandidates sets the sequence of candidates associated with the remote ICETransport.
func ( *ICETransport) ( []ICECandidate) error {
	.lock.RLock()
	defer .lock.RUnlock()

	if  := .ensureGatherer();  != nil {
		return 
	}

	 := .gatherer.getAgent()
	if  == nil {
		return fmt.Errorf("%w: unable to set remote candidates", errICEAgentNotExist)
	}

	for ,  := range  {
		,  := .ToICE()
		if  != nil {
			return 
		}

		if  = .AddRemoteCandidate();  != nil {
			return 
		}
	}

	return nil
}

// AddRemoteCandidate adds a candidate associated with the remote ICETransport.
func ( *ICETransport) ( *ICECandidate) error {
	.lock.RLock()
	defer .lock.RUnlock()

	var (
		 ice.Candidate
		       error
	)

	if  = .ensureGatherer();  != nil {
		return 
	}

	if  != nil {
		if ,  = .ToICE();  != nil {
			return 
		}
	}

	 := .gatherer.getAgent()
	if  == nil {
		return fmt.Errorf("%w: unable to add remote candidates", errICEAgentNotExist)
	}

	return .AddRemoteCandidate()
}

// State returns the current ice transport state.
func ( *ICETransport) () ICETransportState {
	if ,  := .state.Load().(ICETransportState);  {
		return 
	}

	return ICETransportState(0)
}

// GetLocalParameters returns an IceParameters object which provides information
// uniquely identifying the local peer for the duration of the ICE session.
func ( *ICETransport) () (ICEParameters, error) {
	if  := .ensureGatherer();  != nil {
		return ICEParameters{}, 
	}

	return .gatherer.GetLocalParameters()
}

// GetRemoteParameters returns an IceParameters object which provides information
// uniquely identifying the remote peer for the duration of the ICE session.
func ( *ICETransport) () (ICEParameters, error) {
	.lock.Lock()
	defer .lock.Unlock()

	 := .gatherer.getAgent()
	if  == nil {
		return ICEParameters{}, fmt.Errorf("%w: unable to get remote parameters", errICEAgentNotExist)
	}

	, ,  := .GetRemoteUserCredentials()
	if  != nil {
		return ICEParameters{}, fmt.Errorf("%w: unable to get remote parameters", )
	}

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

func ( *ICETransport) ( ICETransportState) {
	.state.Store()
}

func ( *ICETransport) ( mux.MatchFunc) *mux.Endpoint {
	.lock.Lock()
	defer .lock.Unlock()

	return .mux.NewEndpoint()
}

func ( *ICETransport) () error {
	if .gatherer == nil {
		return errICEGathererNotStarted
	} else if .gatherer.getAgent() == nil {
		if  := .gatherer.createAgent();  != nil {
			return 
		}
	}

	return nil
}

func ( *ICETransport) ( *statsReportCollector) {
	.lock.Lock()
	 := .conn
	.lock.Unlock()

	.Collecting()

	 := TransportStats{
		Timestamp: statsTimestampFrom(time.Now()),
		Type:      StatsTypeTransport,
		ID:        "iceTransport",
	}

	if  != nil {
		.BytesSent = .BytesSent()
		.BytesReceived = .BytesReceived()
	}

	.Collect(.ID, )
}

func ( *ICETransport) (,  string) bool {
	.lock.Lock()
	defer .lock.Unlock()

	 := .gatherer.getAgent()
	if  == nil {
		return false
	}

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

	return  !=  ||  != 
}

func ( *ICETransport) (,  string) error {
	.lock.Lock()
	defer .lock.Unlock()

	 := .gatherer.getAgent()
	if  == nil {
		return fmt.Errorf("%w: unable to SetRemoteCredentials", errICEAgentNotExist)
	}

	return .SetRemoteCredentials(, )
}