// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package backoff

import (
	"context"
	"log"
	"math/rand"
	"time"
)

const MAX_BACKOFF_MSEC = 30000

type Backoff struct {
	n int
	// Name is the name of this backoff timer, for logging purposes.
	Name string
	// NewTimer is the function that acts like time.NewTimer().
	// You can override this in unit tests.
	NewTimer func(d time.Duration) *time.Timer
	// LogLongerThan sets the minimum time of a single backoff interval
	// before we mention it in the log.
	LogLongerThan time.Duration
}

func (b *Backoff) BackOff(ctx context.Context, err error) {
	if ctx.Err() == nil && err != nil {
		b.n++
		// n^2 backoff timer is a little smoother than the
		// common choice of 2^n.
		msec := b.n * b.n * 10
		if msec > MAX_BACKOFF_MSEC {
			msec = MAX_BACKOFF_MSEC
		}
		// Randomize the delay between 0.5-1.5 x msec, in order
		// to prevent accidental "thundering herd" problems.
		msec = rand.Intn(msec) + msec/2
		dur := time.Duration(msec) * time.Millisecond
		if dur >= b.LogLongerThan {
			log.Printf("%s: backoff: %d msec\n", b.Name, msec)
		}
		newTimer := b.NewTimer
		if newTimer == nil {
			newTimer = time.NewTimer
		}
		t := newTimer(dur)
		select {
		case <-ctx.Done():
			t.Stop()
		case <-t.C:
		}
	} else {
		// not a regular error
		b.n = 0
	}
}