package stats

import "fmt"

// Type is the type of aggregation of apply
type Type int

const (
	AggregateAvg Type = iota
	AggregateSum
	AggregateHistogram
)

var (
	// HistogramPercentiles is used to determine which percentiles to return for
	// SimpleCounter.Aggregate
	HistogramPercentiles = map[string]float64{
		"p50": 0.5,
		"p95": 0.95,
		"p99": 0.99,
	}

	// MinSamplesForPercentiles is used by SimpleCounter.Aggregate to determine
	// what the minimum number of samples is required for percentile analysis
	MinSamplesForPercentiles = 10
)

// Aggregates can be used to merge counters together. This is not goroutine safe
type Aggregates map[string]Counter

// Add adds the counter for aggregation. This is not goroutine safe
func (a Aggregates) Add(c Counter) error {
	key := c.FullKey()
	if counter, ok := a[key]; ok {
		if counter.GetType() != c.GetType() {
			return fmt.Errorf("stats: mismatched aggregation type for: %s", key)
		}
		counter.AddValues(c.GetValues()...)
	} else {
		a[key] = c
	}
	return nil
}

// Counter is the interface used by Aggregates to merge counters together
type Counter interface {
	// FullKey is used to uniquely identify the counter
	FullKey() string

	// AddValues adds values for aggregation
	AddValues(...float64)

	// GetValues returns the values for aggregation
	GetValues() []float64

	// GetType returns the type of aggregation to apply
	GetType() Type
}

// SimpleCounter is a basic implementation of the Counter interface
type SimpleCounter struct {
	Key    string
	Values []float64
	Type   Type
}

// FullKey is part of the Counter interace
func (s *SimpleCounter) FullKey() string {
	return s.Key
}

// GetValues is part of the Counter interface
func (s *SimpleCounter) GetValues() []float64 {
	return s.Values
}

// AddValues is part of the Counter interface
func (s *SimpleCounter) AddValues(vs ...float64) {
	s.Values = append(s.Values, vs...)
}

// GetType is part of the Counter interface
func (s *SimpleCounter) GetType() Type {
	return s.Type
}

// Aggregate aggregates the provided values appropriately, returning a map
// from key to value. If AggregateHistogram is specified, the map will contain
// the relevant percentiles as specified by HistogramPercentiles
func (s *SimpleCounter) Aggregate() map[string]float64 {
	switch s.Type {
	case AggregateAvg:
		return map[string]float64{
			s.Key: Average(s.Values),
		}
	case AggregateSum:
		return map[string]float64{
			s.Key: Sum(s.Values),
		}
	case AggregateHistogram:
		histogram := map[string]float64{
			s.Key: Average(s.Values),
		}
		if len(s.Values) > MinSamplesForPercentiles {
			for k, v := range Percentiles(s.Values, HistogramPercentiles) {
				histogram[fmt.Sprintf("%s.%s", s.Key, k)] = v
			}
		}
		return histogram
	}
	panic("stats: unsupported aggregation type")
}