package internal

import "time"

type BackoffFlags uint8

const (
	BackoffHasPriority BackoffFlags = 1 << iota
	BackoffCriticalPath
	BackoffTCPConn
)

func NewBackoff(priority BackoffFlags) Backoff {
	if priority&BackoffCriticalPath != 0 {
		return Backoff{
			maxWait: uint32(1 * time.Millisecond),
		}
	} else if priority&BackoffTCPConn != 0 {
		return Backoff{
			maxWait: uint32(5 * time.Microsecond),
		}
	}
	return Backoff{
		maxWait: uint32(time.Second) >> (priority & BackoffHasPriority),
	}
}

// A Backoff with a non-zero MaxWait is ready for use.
type Backoff struct {
	// wait defines the amount of time that Miss will wait on next call.
	wait uint32
	// Maximum allowable value for Wait.
	maxWait uint32
	// startWait is the value that Wait takes after a call to Hit.
	startWait uint32
	// expMinusOne is the shift performed on Wait minus one, so the zero value performs a shift of 1.
	expMinusOne uint32
}

// Hit sets eb.Wait to the StartWait value.
func (eb *Backoff) Hit() {
	if eb.maxWait == 0 {
		panic("MaxWait cannot be zero")
	}
	eb.wait = eb.startWait
}

// Miss sleeps for eb.Wait and increases eb.Wait exponentially.
func (eb *Backoff) Miss() {
	const k = 1
	wait := eb.wait
	maxWait := eb.maxWait
	exp := eb.expMinusOne + 1
	if maxWait == 0 {
		panic("MaxWait cannot be zero")
	}
	time.Sleep(time.Duration(wait))
	wait |= k
	wait <<= exp
	if wait > maxWait {
		wait = maxWait
	}
	eb.wait = wait
}
