package quic

import (
	
	
	
	
	
	
	

	
	
	
)

var (
	// ErrPathClosed is returned when trying to switch to a path that has been closed.
	ErrPathClosed = errors.New("path closed")
	// ErrPathNotValidated is returned when trying to use a path before path probing has completed.
	ErrPathNotValidated = errors.New("path not yet validated")
)

var errPathDoesNotExist = errors.New("path does not exist")

// Path is a network path.
type Path struct {
	id          pathID
	pathManager *pathManagerOutgoing
	tr          *Transport
	initialRTT  time.Duration

	enablePath func()
	validated  atomic.Bool
	abandon    chan struct{}
}

func ( *Path) ( context.Context) error {
	 := .pathManager.addPath(, .enablePath)

	.pathManager.enqueueProbe()
	 := .initialRTT
	var  *time.Timer
	var  <-chan time.Time
	for {
		select {
		case <-.Done():
			return context.Cause()
		case <-.Validated():
			.validated.Store(true)
			return nil
		case <-:
			 *= 2 // exponential backoff
			.pathManager.enqueueProbe()
		case <-.ProbeSent():
		case <-.abandon:
			return ErrPathClosed
		}

		if  != nil {
			.Stop()
		}
		 = time.NewTimer()
		 = .C
	}
}

// Switch switches the QUIC connection to this path.
// It immediately stops sending on the old path, and sends on this new path.
func ( *Path) () error {
	if  := .pathManager.switchToPath(.id);  != nil {
		switch {
		case errors.Is(, ErrPathNotValidated):
			return 
		case errors.Is(, errPathDoesNotExist) && !.validated.Load():
			select {
			case <-.abandon:
				return ErrPathClosed
			default:
				return ErrPathNotValidated
			}
		default:
			return ErrPathClosed
		}
	}
	return nil
}

// Close abandons a path.
// It is not possible to close the path that’s currently active.
// After closing, it is not possible to probe this path again.
func ( *Path) () error {
	select {
	case <-.abandon:
		return nil
	default:
	}

	if  := .pathManager.removePath(.id);  != nil {
		return 
	}
	close(.abandon)
	return nil
}

type pathOutgoing struct {
	pathChallenges [][8]byte // length is implicitly limited by exponential backoff
	tr             *Transport
	isValidated    bool
	probeSent      chan struct{} // receives when a PATH_CHALLENGE is sent
	validated      chan struct{} // closed when the path the corresponding PATH_RESPONSE is received
	enablePath     func()
}

func ( *pathOutgoing) () <-chan struct{} { return .probeSent }
func ( *pathOutgoing) () <-chan struct{} { return .validated }

type pathManagerOutgoing struct {
	getConnID       func(pathID) (_ protocol.ConnectionID, ok bool)
	retireConnID    func(pathID)
	scheduleSending func()

	mx             sync.Mutex
	activePath     pathID
	pathsToProbe   []pathID
	paths          map[pathID]*pathOutgoing
	nextPathID     pathID
	pathToSwitchTo *pathOutgoing
}

// newPathManagerOutgoing creates a new pathManagerOutgoing object. This
// function must be side-effect free as it may be called multiple times for a
// single connection.
func newPathManagerOutgoing(
	 func(pathID) ( protocol.ConnectionID,  bool),
	 func(pathID),
	 func(),
) *pathManagerOutgoing {
	return &pathManagerOutgoing{
		activePath:      0, // at initialization time, we're guaranteed to be using the handshake path
		nextPathID:      1,
		getConnID:       ,
		retireConnID:    ,
		scheduleSending: ,
		paths:           make(map[pathID]*pathOutgoing, 4),
	}
}

func ( *pathManagerOutgoing) ( *Path,  func()) *pathOutgoing {
	.mx.Lock()
	defer .mx.Unlock()

	// path might already exist, and just being re-probed
	if ,  := .paths[.id];  {
		.validated = make(chan struct{})
		return 
	}

	 := &pathOutgoing{
		tr:         .tr,
		probeSent:  make(chan struct{}, 1),
		validated:  make(chan struct{}),
		enablePath: ,
	}
	.paths[.id] = 
	return 
}

func ( *pathManagerOutgoing) ( *Path) {
	.mx.Lock()
	.pathsToProbe = append(.pathsToProbe, .id)
	.mx.Unlock()
	.scheduleSending()
}

func ( *pathManagerOutgoing) ( pathID) error {
	if  := .removePathImpl();  != nil {
		return 
	}
	.scheduleSending()
	return nil
}

func ( *pathManagerOutgoing) ( pathID) error {
	.mx.Lock()
	defer .mx.Unlock()

	if  == .activePath {
		return errors.New("cannot close active path")
	}
	,  := .paths[]
	if ! {
		return nil
	}
	if len(.pathChallenges) > 0 {
		.retireConnID()
	}
	delete(.paths, )
	return nil
}

func ( *pathManagerOutgoing) ( pathID) error {
	.mx.Lock()
	defer .mx.Unlock()

	,  := .paths[]
	if ! {
		return errPathDoesNotExist
	}
	if !.isValidated {
		return ErrPathNotValidated
	}
	.pathToSwitchTo = 
	.activePath = 
	return nil
}

func ( *pathManagerOutgoing) ( *Transport,  time.Duration,  func()) *Path {
	.mx.Lock()
	defer .mx.Unlock()

	 := .nextPathID
	.nextPathID++
	return &Path{
		pathManager: ,
		id:          ,
		tr:          ,
		enablePath:  ,
		initialRTT:  ,
		abandon:     make(chan struct{}),
	}
}

func ( *pathManagerOutgoing) () ( protocol.ConnectionID,  ackhandler.Frame,  *Transport,  bool) {
	.mx.Lock()
	defer .mx.Unlock()

	var  *pathOutgoing
	 := invalidPathID
	for ,  := range .pathsToProbe {
		var  bool
		,  = .paths[]
		if  {
			 = 
			break
		}
		// if the path doesn't exist in the map, it might have been abandoned
		.pathsToProbe = .pathsToProbe[1:]
	}
	if  == invalidPathID {
		return protocol.ConnectionID{}, ackhandler.Frame{}, nil, false
	}

	,  := .getConnID()
	if ! {
		return protocol.ConnectionID{}, ackhandler.Frame{}, nil, false
	}

	var  [8]byte
	_, _ = rand.Read([:])
	.pathChallenges = append(.pathChallenges, )

	.pathsToProbe = .pathsToProbe[1:]
	.enablePath()
	select {
	case .probeSent <- struct{}{}:
	default:
	}
	 := ackhandler.Frame{
		Frame:   &wire.PathChallengeFrame{Data: },
		Handler: (*pathManagerOutgoingAckHandler)(),
	}
	return , , .tr, true
}

func ( *pathManagerOutgoing) ( *wire.PathResponseFrame) {
	.mx.Lock()
	defer .mx.Unlock()

	for ,  := range .paths {
		if slices.Contains(.pathChallenges, .Data) {
			// path validated
			if !.isValidated {
				// make sure that duplicate PATH_RESPONSE frames are ignored
				.isValidated = true
				.pathChallenges = nil
				close(.validated)
			}
			break
		}
	}
}

func ( *pathManagerOutgoing) () (*Transport, bool) {
	.mx.Lock()
	defer .mx.Unlock()

	if .pathToSwitchTo == nil {
		return nil, false
	}
	 := .pathToSwitchTo
	.pathToSwitchTo = nil
	return .tr, true
}

type pathManagerOutgoingAckHandler pathManagerOutgoing

var _ ackhandler.FrameHandler = &pathManagerOutgoingAckHandler{}

// OnAcked is called when the PATH_CHALLENGE is acked.
// This doesn't validate the path, only receiving the PATH_RESPONSE does.
func ( *pathManagerOutgoingAckHandler) (wire.Frame) {}

func ( *pathManagerOutgoingAckHandler) (wire.Frame) {}