package peer

import (
	"sync"
	"time"
)

// LatencyEWMASmooting governs the decay of the EWMA (the speed
// at which it changes). This must be a normalized (0-1) value.
// 1 is 100% change, 0 is no change.
var LatencyEWMASmoothing = 0.1

// Metrics is just an object that tracks metrics
// across a set of peers.
type Metrics interface {

	// RecordLatency records a new latency measurement
	RecordLatency(ID, time.Duration)

	// LatencyEWMA returns an exponentially-weighted moving avg.
	// of all measurements of a peer's latency.
	LatencyEWMA(ID) time.Duration
}

type metrics struct {
	latmap map[ID]time.Duration
	latmu  sync.RWMutex
}

func NewMetrics() Metrics {
	return &metrics{
		latmap: make(map[ID]time.Duration),
	}
}

// RecordLatency records a new latency measurement
func (m *metrics) RecordLatency(p ID, next time.Duration) {
	nextf := float64(next)
	s := LatencyEWMASmoothing
	if s > 1 || s < 0 {
		s = 0.1 // ignore the knob. it's broken. look, it jiggles.
	}

	m.latmu.Lock()
	ewma, found := m.latmap[p]
	ewmaf := float64(ewma)
	if !found {
		m.latmap[p] = next // when no data, just take it as the mean.
	} else {
		nextf = ((1.0 - s) * ewmaf) + (s * nextf)
		m.latmap[p] = time.Duration(nextf)
	}
	m.latmu.Unlock()
}

// LatencyEWMA returns an exponentially-weighted moving avg.
// of all measurements of a peer's latency.
func (m *metrics) LatencyEWMA(p ID) time.Duration {
	m.latmu.RLock()
	lat := m.latmap[p]
	m.latmu.RUnlock()
	return time.Duration(lat)
}
