package swarm

import (
	
	
	

	
	
)

// dialWorkerFunc is used by dialSync to spawn a new dial worker
type dialWorkerFunc func(peer.ID, <-chan dialRequest)

// errConcurrentDialSuccessful is used to signal that a concurrent dial succeeded
var errConcurrentDialSuccessful = errors.New("concurrent dial successful")

// newDialSync constructs a new dialSync
func newDialSync( dialWorkerFunc) *dialSync {
	return &dialSync{
		dials:      make(map[peer.ID]*activeDial),
		dialWorker: ,
	}
}

// dialSync is a dial synchronization helper that ensures that at most one dial
// to any given peer is active at any given time.
type dialSync struct {
	mutex      sync.Mutex
	dials      map[peer.ID]*activeDial
	dialWorker dialWorkerFunc
}

type activeDial struct {
	refCnt int

	ctx         context.Context
	cancelCause func(error)

	reqch chan dialRequest
}

func ( *activeDial) ( context.Context) (*Conn, error) {
	 := .ctx

	if ,  := network.GetForceDirectDial();  {
		 = network.WithForceDirectDial(, )
	}
	if , ,  := network.GetSimultaneousConnect();  {
		 = network.WithSimultaneousConnect(, , )
	}

	 := make(chan dialResponse, 1)
	select {
	case .reqch <- dialRequest{ctx: , resch: }:
	case <-.Done():
		return nil, .Err()
	}

	select {
	case  := <-:
		return .conn, .err
	case <-.Done():
		return nil, .Err()
	}
}

func ( *dialSync) ( peer.ID) (*activeDial, error) {
	.mutex.Lock()
	defer .mutex.Unlock()

	,  := .dials[]
	if ! {
		// This code intentionally uses the background context. Otherwise, if the first call
		// to Dial is canceled, subsequent dial calls will also be canceled.
		,  := context.WithCancelCause(context.Background())
		 = &activeDial{
			ctx:         ,
			cancelCause: ,
			reqch:       make(chan dialRequest),
		}
		go .dialWorker(, .reqch)
		.dials[] = 
	}
	// increase ref count before dropping mutex
	.refCnt++
	return , nil
}

// Dial initiates a dial to the given peer if there are none in progress
// then waits for the dial to that peer to complete.
func ( *dialSync) ( context.Context,  peer.ID) (*Conn, error) {
	,  := .getActiveDial()
	if  != nil {
		return nil, 
	}

	,  := .dial()

	.mutex.Lock()
	defer .mutex.Unlock()

	.refCnt--
	if .refCnt == 0 {
		if  == nil {
			.cancelCause(errConcurrentDialSuccessful)
		} else {
			.cancelCause()
		}
		close(.reqch)
		delete(.dials, )
	}

	return , 
}