// Copyright (c) 2015-2023 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.

package resty

import (
	
	
	
	
	
	
)

const (
	defaultMaxRetries  = 3
	defaultWaitTime    = time.Duration(100) * time.Millisecond
	defaultMaxWaitTime = time.Duration(2000) * time.Millisecond
)

type (
	// Option is to create convenient retry options like wait time, max retries, etc.
	Option func(*Options)

	// RetryConditionFunc type is for retry condition function
	// input: non-nil Response OR request execution error
	RetryConditionFunc func(*Response, error) bool

	// OnRetryFunc is for side-effecting functions triggered on retry
	OnRetryFunc func(*Response, error)

	// RetryAfterFunc returns time to wait before retry
	// For example, it can parse HTTP Retry-After header
	// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
	// Non-nil error is returned if it is found that request is not retryable
	// (0, nil) is a special result means 'use default algorithm'
	RetryAfterFunc func(*Client, *Response) (time.Duration, error)

	// Options struct is used to hold retry settings.
	Options struct {
		maxRetries      int
		waitTime        time.Duration
		maxWaitTime     time.Duration
		retryConditions []RetryConditionFunc
		retryHooks      []OnRetryFunc
		resetReaders    bool
	}
)

// Retries sets the max number of retries
func ( int) Option {
	return func( *Options) {
		.maxRetries = 
	}
}

// WaitTime sets the default wait time to sleep between requests
func ( time.Duration) Option {
	return func( *Options) {
		.waitTime = 
	}
}

// MaxWaitTime sets the max wait time to sleep between requests
func ( time.Duration) Option {
	return func( *Options) {
		.maxWaitTime = 
	}
}

// RetryConditions sets the conditions that will be checked for retry.
func ( []RetryConditionFunc) Option {
	return func( *Options) {
		.retryConditions = 
	}
}

// RetryHooks sets the hooks that will be executed after each retry
func ( []OnRetryFunc) Option {
	return func( *Options) {
		.retryHooks = 
	}
}

// ResetMultipartReaders sets a boolean value which will lead the start being seeked out
// on all multipart file readers, if they implement io.ReadSeeker
func ( bool) Option {
	return func( *Options) {
		.resetReaders = 
	}
}

// Backoff retries with increasing timeout duration up until X amount of retries
// (Default is 3 attempts, Override with option Retries(n))
func ( func() (*Response, error),  ...Option) error {
	// Defaults
	 := Options{
		maxRetries:      defaultMaxRetries,
		waitTime:        defaultWaitTime,
		maxWaitTime:     defaultMaxWaitTime,
		retryConditions: []RetryConditionFunc{},
	}

	for ,  := range  {
		(&)
	}

	var (
		 *Response
		  error
	)

	for  := 0;  <= .maxRetries; ++ {
		,  = ()
		 := context.Background()
		if  != nil && .Request.ctx != nil {
			 = .Request.ctx
		}
		if .Err() != nil {
			return 
		}

		 := unwrapNoRetryErr()           // raw error, it used for return users callback.
		 :=  != nil &&  ==  // retry on a few operation errors by default

		for ,  := range .retryConditions {
			 = (, )
			if  {
				break
			}
		}

		if ! {
			return 
		}

		if .resetReaders {
			if  := resetFileReaders(.Request.multipartFiles);  != nil {
				return 
			}
		}

		for ,  := range .retryHooks {
			(, )
		}

		// Don't need to wait when no retries left.
		// Still run retry hooks even on last retry to keep compatibility.
		if  == .maxRetries {
			return 
		}

		,  := sleepDuration(, .waitTime, .maxWaitTime, )
		if  != nil {
			if  == nil {
				 = 
			}
			return 
		}

		select {
		case <-time.After():
		case <-.Done():
			return .Err()
		}
	}

	return 
}

func sleepDuration( *Response, ,  time.Duration,  int) (time.Duration, error) {
	const  = 1<<31 - 1 // max int for arch 386
	if  < 0 {
		 = 
	}
	if  == nil {
		return jitterBackoff(, , ), nil
	}

	 := .Request.client.RetryAfter

	// Check for custom callback
	if  == nil {
		return jitterBackoff(, , ), nil
	}

	,  := (.Request.client, )
	if  != nil {
		return 0,  // i.e. 'API quota exceeded'
	}
	if  == 0 {
		return jitterBackoff(, , ), nil
	}
	if  < 0 ||  <  {
		 = 
	}
	if  <  {
		 = 
	}
	return , nil
}

// Return capped exponential backoff with jitter
// http://www.awsarchitectureblog.com/2015/03/backoff.html
func jitterBackoff(,  time.Duration,  int) time.Duration {
	 := float64()
	 := float64()

	 := math.Min(, *math.Exp2(float64()))
	 := time.Duration( / 2)
	if  == 0 {
		 = time.Nanosecond
	}
	 := randDuration()

	if  <  {
		 = 
	}

	return 
}

var rnd = newRnd()
var rndMu sync.Mutex

func randDuration( time.Duration) time.Duration {
	rndMu.Lock()
	defer rndMu.Unlock()

	var  = int64()
	var  = rnd.Int63n()
	return time.Duration(math.Abs(float64( + )))
}

func newRnd() *rand.Rand {
	var  = time.Now().UnixNano()
	var  = rand.NewSource()
	return rand.New()
}

func resetFileReaders( []*File) error {
	for ,  := range  {
		if ,  := .Reader.(io.ReadSeeker);  {
			if ,  := .Seek(0, io.SeekStart);  != nil {
				return 
			}
		}
	}

	return nil
}