package backoff

import (
	
	
	
	

	logging 
)

var log = logging.Logger("discovery-backoff")

type BackoffFactory func() BackoffStrategy

// BackoffStrategy describes how backoff will be implemented. BackoffStrategies are stateful.
type BackoffStrategy interface {
	// Delay calculates how long the next backoff duration should be, given the prior calls to Delay
	Delay() time.Duration
	// Reset clears the internal state of the BackoffStrategy
	Reset()
}

// Jitter implementations taken roughly from https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/

// Jitter must return a duration between min and max. Min must be lower than, or equal to, max.
type Jitter func(duration, min, max time.Duration, rng *rand.Rand) time.Duration

// FullJitter returns a random number, uniformly chosen from the range [min, boundedDur].
// boundedDur is the duration bounded between min and max.
func (, ,  time.Duration,  *rand.Rand) time.Duration {
	if  <=  {
		return 
	}

	 := boundedDuration(, , ) - 

	return boundedDuration(time.Duration(.Int63n(int64()))+, , )
}

// NoJitter returns the duration bounded between min and max
func (, ,  time.Duration,  *rand.Rand) time.Duration {
	return boundedDuration(, , )
}

type randomizedBackoff struct {
	min time.Duration
	max time.Duration
	rng *rand.Rand
}

func ( *randomizedBackoff) ( time.Duration) time.Duration {
	return boundedDuration(, .min, .max)
}

func boundedDuration(, ,  time.Duration) time.Duration {
	if  <  {
		return 
	}
	if  >  {
		return 
	}
	return 
}

type attemptBackoff struct {
	attempt int
	jitter  Jitter
	randomizedBackoff
}

func ( *attemptBackoff) () {
	.attempt = 0
}

// NewFixedBackoff creates a BackoffFactory with a constant backoff duration
func ( time.Duration) BackoffFactory {
	return func() BackoffStrategy {
		return &fixedBackoff{delay: }
	}
}

type fixedBackoff struct {
	delay time.Duration
}

func ( *fixedBackoff) () time.Duration {
	return .delay
}

func ( *fixedBackoff) () {}

// NewPolynomialBackoff creates a BackoffFactory with backoff of the form c0*x^0, c1*x^1, ...cn*x^n where x is the attempt number
// jitter is the function for adding randomness around the backoff
// timeUnits are the units of time the polynomial is evaluated in
// polyCoefs is the array of polynomial coefficients from [c0, c1, ... cn]
func (,  time.Duration,  Jitter,
	 time.Duration,  []float64,  rand.Source) BackoffFactory {
	 := rand.New(&lockedSource{src: })
	return func() BackoffStrategy {
		return &polynomialBackoff{
			attemptBackoff: attemptBackoff{
				randomizedBackoff: randomizedBackoff{
					min: ,
					max: ,
					rng: ,
				},
				jitter: ,
			},
			timeUnits: ,
			poly:      ,
		}
	}
}

type polynomialBackoff struct {
	attemptBackoff
	timeUnits time.Duration
	poly      []float64
}

func ( *polynomialBackoff) () time.Duration {
	var  float64
	switch len(.poly) {
	case 0:
		return 0
	case 1:
		 = .poly[0]
	default:
		 = .poly[0]
		 := 1
		 := .attempt
		.attempt++

		for ,  := range .poly[1:] {
			 *= 
			 += float64() * 
		}
	}
	return .jitter(time.Duration(float64(.timeUnits)*), .min, .max, .rng)
}

// NewExponentialBackoff creates a BackoffFactory with backoff of the form base^x + offset where x is the attempt number
// jitter is the function for adding randomness around the backoff
// timeUnits are the units of time the base^x is evaluated in
func (,  time.Duration,  Jitter,
	 time.Duration,  float64,  time.Duration,  rand.Source) BackoffFactory {
	 := rand.New(&lockedSource{src: })
	return func() BackoffStrategy {
		return &exponentialBackoff{
			attemptBackoff: attemptBackoff{
				randomizedBackoff: randomizedBackoff{
					min: ,
					max: ,
					rng: ,
				},
				jitter: ,
			},
			timeUnits: ,
			base:      ,
			offset:    ,
		}
	}
}

type exponentialBackoff struct {
	attemptBackoff
	timeUnits time.Duration
	base      float64
	offset    time.Duration
}

func ( *exponentialBackoff) () time.Duration {
	 := .attempt
	.attempt++
	return .jitter(
		time.Duration(math.Pow(.base, float64())*float64(.timeUnits))+.offset, .min, .max, .rng)
}

// NewExponentialDecorrelatedJitter creates a BackoffFactory with backoff of the roughly of the form base^x where x is the attempt number.
// Delays start at the minimum duration and after each attempt delay = rand(min, delay * base), bounded by the max
// See https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ for more information
func (,  time.Duration,  float64,  rand.Source) BackoffFactory {
	 := rand.New(&lockedSource{src: })
	return func() BackoffStrategy {
		return &exponentialDecorrelatedJitter{
			randomizedBackoff: randomizedBackoff{
				min: ,
				max: ,
				rng: ,
			},
			base: ,
		}
	}
}

type exponentialDecorrelatedJitter struct {
	randomizedBackoff
	base      float64
	lastDelay time.Duration
}

func ( *exponentialDecorrelatedJitter) () time.Duration {
	if .lastDelay < .min {
		.lastDelay = .min
		return .lastDelay
	}

	 := int64(float64(.lastDelay) * .base)
	.lastDelay = boundedDuration(time.Duration(.rng.Int63n(-int64(.min)))+.min, .min, .max)
	return .lastDelay
}

func ( *exponentialDecorrelatedJitter) () { .lastDelay = 0 }

type lockedSource struct {
	lk  sync.Mutex
	src rand.Source
}

func ( *lockedSource) () ( int64) {
	.lk.Lock()
	 = .src.Int63()
	.lk.Unlock()
	return
}

func ( *lockedSource) ( int64) {
	.lk.Lock()
	.src.Seed()
	.lk.Unlock()
}