package limiter

import (
	"sync"
	"time"

	"github.com/VictoriaMetrics/metrics"

	"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
)

// NewLimiter creates a Limiter object
// for the given perSecondLimit
func NewLimiter(perSecondLimit int64) *Limiter {
	return &Limiter{perSecondLimit: perSecondLimit}
}

// Limiter controls the amount of budget
// that can be spent according to configured perSecondLimit
type Limiter struct {
	perSecondLimit int64

	// mu protects budget and deadline from concurrent access.
	mu sync.Mutex

	// The current budget. It is increased by perSecondLimit every second.
	budget int64

	// The next deadline for increasing the budget by perSecondLimit
	deadline time.Time
}

// Register blocks for amount of time
// needed to process the given dataLen according
// to the configured perSecondLimit.
func (l *Limiter) Register(dataLen int) {
	limit := l.perSecondLimit
	if limit <= 0 {
		return
	}

	l.mu.Lock()
	defer l.mu.Unlock()

	for l.budget <= 0 {
		if d := time.Until(l.deadline); d > 0 {
			t := timerpool.Get(d)
			<-t.C
			timerpool.Put(t)
			limiterThrottleEventsTotal.Inc()
		}
		l.budget += limit
		l.deadline = time.Now().Add(time.Second)
	}
	l.budget -= int64(dataLen)
	limiterBytesProcessed.Add(dataLen)
}

var (
	limiterBytesProcessed      = metrics.NewCounter(`vmctl_limiter_bytes_processed_total`)
	limiterThrottleEventsTotal = metrics.NewCounter(`vmctl_limiter_throttle_events_total`)
)
