package autorelay

import (
	
	
	

	
)

// AutoRelay will call this function when it needs new candidates because it is
// not connected to the desired number of relays or we get disconnected from one
// of the relays. Implementations must send *at most* numPeers, and close the
// channel when they don't intend to provide any more peers. AutoRelay will not
// call the callback again until the channel is closed. Implementations should
// send new peers, but may send peers they sent before. AutoRelay implements a
// per-peer backoff (see WithBackoff). See WithMinInterval for setting the
// minimum interval between calls to the callback. The context.Context passed
// may be canceled when AutoRelay feels satisfied, it will be canceled when the
// node is shutting down. If the context is canceled you MUST close the output
// channel at some point.
type PeerSource func(ctx context.Context, num int) <-chan peer.AddrInfo

type config struct {
	clock      ClockWithInstantTimer
	peerSource PeerSource
	// minimum interval used to call the peerSource callback
	minInterval time.Duration
	// see WithMinCandidates
	minCandidates int
	// see WithMaxCandidates
	maxCandidates int
	// Delay until we obtain reservations with relays, if we have less than minCandidates candidates.
	// See WithBootDelay.
	bootDelay time.Duration
	// backoff is the time we wait after failing to obtain a reservation with a candidate
	backoff time.Duration
	// Number of relays we strive to obtain a reservation with.
	desiredRelays int
	// see WithMaxCandidateAge
	maxCandidateAge  time.Duration
	setMinCandidates bool
	// see WithMetricsTracer
	metricsTracer MetricsTracer
}

var defaultConfig = config{
	clock:           RealClock{},
	minCandidates:   4,
	maxCandidates:   20,
	bootDelay:       3 * time.Minute,
	backoff:         time.Hour,
	desiredRelays:   2,
	maxCandidateAge: 30 * time.Minute,
	minInterval:     30 * time.Second,
}

var (
	errAlreadyHavePeerSource = errors.New("can only use a single WithPeerSource or WithStaticRelays")
)

type Option func(*config) error

func ( []peer.AddrInfo) Option {
	return func( *config) error {
		if .peerSource != nil {
			return errAlreadyHavePeerSource
		}

		WithPeerSource(func( context.Context,  int) <-chan peer.AddrInfo {
			if len() <  {
				 = len()
			}
			 := make(chan peer.AddrInfo, )
			defer close()

			for  := 0;  < ; ++ {
				 <- []
			}
			return 
		})()
		WithMinCandidates(len())()
		WithMaxCandidates(len())()
		WithNumRelays(len())()

		return nil
	}
}

// WithPeerSource defines a callback for AutoRelay to query for more relay candidates.
func ( PeerSource) Option {
	return func( *config) error {
		if .peerSource != nil {
			return errAlreadyHavePeerSource
		}
		.peerSource = 
		return nil
	}
}

// WithNumRelays sets the number of relays we strive to obtain reservations with.
func ( int) Option {
	return func( *config) error {
		.desiredRelays = 
		return nil
	}
}

// WithMaxCandidates sets the number of relay candidates that we buffer.
func ( int) Option {
	return func( *config) error {
		.maxCandidates = 
		if .minCandidates >  {
			.minCandidates = 
		}
		return nil
	}
}

// WithMinCandidates sets the minimum number of relay candidates we collect before to get a reservation
// with any of them (unless we've been running for longer than the boot delay).
// This is to make sure that we don't just randomly connect to the first candidate that we discover.
func ( int) Option {
	return func( *config) error {
		if  > .maxCandidates {
			 = .maxCandidates
		}
		.minCandidates = 
		.setMinCandidates = true
		return nil
	}
}

// WithBootDelay set the boot delay for finding relays.
// We won't attempt any reservation if we've have less than a minimum number of candidates.
// This prevents us to connect to the "first best" relay, and allows us to carefully select the relay.
// However, in case we haven't found enough relays after the boot delay, we use what we have.
func ( time.Duration) Option {
	return func( *config) error {
		.bootDelay = 
		return nil
	}
}

// WithBackoff sets the time we wait after failing to obtain a reservation with a candidate.
func ( time.Duration) Option {
	return func( *config) error {
		.backoff = 
		return nil
	}
}

// WithMaxCandidateAge sets the maximum age of a candidate.
// When we are connected to the desired number of relays, we don't ask the peer source for new candidates.
// This can lead to AutoRelay's candidate list becoming outdated, and means we won't be able
// to quickly establish a new relay connection if our existing connection breaks, if all the candidates
// have become stale.
func ( time.Duration) Option {
	return func( *config) error {
		.maxCandidateAge = 
		return nil
	}
}

// InstantTimer is a timer that triggers at some instant rather than some duration
type InstantTimer interface {
	Reset(d time.Time) bool
	Stop() bool
	Ch() <-chan time.Time
}

// ClockWithInstantTimer is a clock that can create timers that trigger at some
// instant rather than some duration
type ClockWithInstantTimer interface {
	Now() time.Time
	Since(t time.Time) time.Duration
	InstantTimer(when time.Time) InstantTimer
}

type RealTimer struct{ t *time.Timer }

var _ InstantTimer = (*RealTimer)(nil)

func ( RealTimer) () <-chan time.Time {
	return .t.C
}

func ( RealTimer) ( time.Time) bool {
	return .t.Reset(time.Until())
}

func ( RealTimer) () bool {
	return .t.Stop()
}

type RealClock struct{}

var _ ClockWithInstantTimer = RealClock{}

func (RealClock) () time.Time {
	return time.Now()
}
func (RealClock) ( time.Time) time.Duration {
	return time.Since()
}
func (RealClock) ( time.Time) InstantTimer {
	 := time.NewTimer(time.Until())
	return &RealTimer{}
}

func ( ClockWithInstantTimer) Option {
	return func( *config) error {
		.clock = 
		return nil
	}
}

// WithMinInterval sets the minimum interval after which peerSource callback will be called for more
// candidates even if AutoRelay needs new candidates.
func ( time.Duration) Option {
	return func( *config) error {
		.minInterval = 
		return nil
	}
}

// WithMetricsTracer configures autorelay to use mt to track metrics
func ( MetricsTracer) Option {
	return func( *config) error {
		.metricsTracer = 
		return nil
	}
}