package retry

import (
	
	
	

	
	
)

const (
	// DefaultRequestCost is the cost of a single request from the adaptive
	// rate limited token bucket.
	DefaultRequestCost uint = 1
)

// DefaultThrottles provides the set of errors considered throttle errors that
// are checked by default.
var DefaultThrottles = []IsErrorThrottle{
	ThrottleErrorCode{
		Codes: DefaultThrottleErrorCodes,
	},
}

// AdaptiveModeOptions provides the functional options for configuring the
// adaptive retry mode, and delay behavior.
type AdaptiveModeOptions struct {
	// If the adaptive token bucket is empty, when an attempt will be made
	// AdaptiveMode will sleep until a token is available. This can occur when
	// attempts fail with throttle errors. Use this option to disable the sleep
	// until token is available, and return error immediately.
	FailOnNoAttemptTokens bool

	// The cost of an attempt from the AdaptiveMode's adaptive token bucket.
	RequestCost uint

	// Set of strategies to determine if the attempt failed due to a throttle
	// error.
	//
	// It is safe to append to this list in NewAdaptiveMode's functional options.
	Throttles []IsErrorThrottle

	// Set of options for standard retry mode that AdaptiveMode is built on top
	// of. AdaptiveMode may apply its own defaults to Standard retry mode that
	// are different than the defaults of NewStandard. Use these options to
	// override the default options.
	StandardOptions []func(*StandardOptions)
}

// AdaptiveMode provides an experimental retry strategy that expands on the
// Standard retry strategy, adding client attempt rate limits. The attempt rate
// limit is initially unrestricted, but becomes restricted when the attempt
// fails with for a throttle error. When restricted AdaptiveMode may need to
// sleep before an attempt is made, if too many throttles have been received.
// AdaptiveMode's sleep can be canceled with context cancel. Set
// AdaptiveModeOptions FailOnNoAttemptTokens to change the behavior from sleep,
// to fail fast.
//
// Eventually unrestricted attempt rate limit will be restored once attempts no
// longer are failing due to throttle errors.
type AdaptiveMode struct {
	options   AdaptiveModeOptions
	throttles IsErrorThrottles

	retryer   aws.RetryerV2
	rateLimit *adaptiveRateLimit
}

// NewAdaptiveMode returns an initialized AdaptiveMode retry strategy.
func ( ...func(*AdaptiveModeOptions)) *AdaptiveMode {
	 := AdaptiveModeOptions{
		RequestCost: DefaultRequestCost,
		Throttles:   append([]IsErrorThrottle{}, DefaultThrottles...),
	}
	for ,  := range  {
		(&)
	}

	return &AdaptiveMode{
		options:   ,
		throttles: IsErrorThrottles(.Throttles),
		retryer:   NewStandard(.StandardOptions...),
		rateLimit: newAdaptiveRateLimit(),
	}
}

// IsErrorRetryable returns if the failed attempt is retryable. This check
// should determine if the error can be retried, or if the error is
// terminal.
func ( *AdaptiveMode) ( error) bool {
	return .retryer.IsErrorRetryable()
}

// MaxAttempts returns the maximum number of attempts that can be made for
// an attempt before failing. A value of 0 implies that the attempt should
// be retried until it succeeds if the errors are retryable.
func ( *AdaptiveMode) () int {
	return .retryer.MaxAttempts()
}

// RetryDelay returns the delay that should be used before retrying the
// attempt. Will return error if the if the delay could not be determined.
func ( *AdaptiveMode) ( int,  error) (
	time.Duration, error,
) {
	return .retryer.RetryDelay(, )
}

// GetRetryToken attempts to deduct the retry cost from the retry token pool.
// Returning the token release function, or error.
func ( *AdaptiveMode) ( context.Context,  error) (
	 func(error) error,  error,
) {
	return .retryer.GetRetryToken(, )
}

// GetInitialToken returns the initial attempt token that can increment the
// retry token pool if the attempt is successful.
//
// Deprecated: This method does not provide a way to block using Context,
// nor can it return an error. Use RetryerV2, and GetAttemptToken instead. Only
// present to implement Retryer interface.
func ( *AdaptiveMode) () ( func(error) error) {
	return nopRelease
}

// GetAttemptToken returns the attempt token that can be used to rate limit
// attempt calls. Will be used by the SDK's retry package's Attempt
// middleware to get an attempt token prior to calling the temp and releasing
// the attempt token after the attempt has been made.
func ( *AdaptiveMode) ( context.Context) (func(error) error, error) {
	for {
		,  := .rateLimit.AcquireToken(.options.RequestCost)
		if  {
			break
		}
		if .options.FailOnNoAttemptTokens {
			return nil, fmt.Errorf(
				"unable to get attempt token, and FailOnNoAttemptTokens enables")
		}

		if  := sdk.SleepWithContext(, );  != nil {
			return nil, fmt.Errorf("failed to wait for token to be available, %w", )
		}
	}

	return .handleResponse, nil
}

func ( *AdaptiveMode) ( error) error {
	 := .throttles.IsErrorThrottle().Bool()

	.rateLimit.Update()
	return nil
}