// run -gcflags=-G=3

// Copyright 2021 The Go 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 metrics provides tracking arbitrary metrics composed of
// values of comparable types.
package main

import (
	"fmt"
	"sort"
	"sync"
)

// _Metric1 tracks metrics of values of some type.
type _Metric1[T comparable] struct {
	mu sync.Mutex
	m  map[T]int
}

// Add adds another instance of some value.
func (m *_Metric1[T]) Add(v T) {
	m.mu.Lock()
	defer m.mu.Unlock()
	if m.m == nil {
		m.m = make(map[T]int)
	}
	m.m[v]++
}

// Count returns the number of instances we've seen of v.
func (m *_Metric1[T]) Count(v T) int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.m[v]
}

// Metrics returns all the values we've seen, in an indeterminate order.
func (m *_Metric1[T]) Metrics() []T {
	return _Keys(m.m)
}

type key2[T1, T2 comparable] struct {
	f1 T1
	f2 T2
}

// _Metric2 tracks metrics of pairs of values.
type _Metric2[T1, T2 comparable] struct {
	mu sync.Mutex
	m  map[key2[T1, T2]]int
}

// Add adds another instance of some pair of values.
func (m *_Metric2[T1, T2]) Add(v1 T1, v2 T2) {
	m.mu.Lock()
	defer m.mu.Unlock()
	if m.m == nil {
		m.m = make(map[key2[T1, T2]]int)
	}
	m.m[key2[T1, T2]{v1, v2}]++
}

// Count returns the number of instances we've seen of v1/v2.
func (m *_Metric2[T1, T2]) Count(v1 T1, v2 T2) int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.m[key2[T1, T2]{v1, v2}]
}

// Metrics returns all the values we've seen, in an indeterminate order.
func (m *_Metric2[T1, T2]) Metrics() (r1 []T1, r2 []T2) {
	for _, k := range _Keys(m.m) {
		r1 = append(r1, k.f1)
		r2 = append(r2, k.f2)
	}
	return r1, r2
}

type key3[T1, T2, T3 comparable] struct {
	f1 T1
	f2 T2
	f3 T3
}

// _Metric3 tracks metrics of triplets of values.
type _Metric3[T1, T2, T3 comparable] struct {
	mu sync.Mutex
	m  map[key3[T1, T2, T3]]int
}

// Add adds another instance of some triplet of values.
func (m *_Metric3[T1, T2, T3]) Add(v1 T1, v2 T2, v3 T3) {
	m.mu.Lock()
	defer m.mu.Unlock()
	if m.m == nil {
		m.m = make(map[key3[T1, T2, T3]]int)
	}
	m.m[key3[T1, T2, T3]{v1, v2, v3}]++
}

// Count returns the number of instances we've seen of v1/v2/v3.
func (m *_Metric3[T1, T2, T3]) Count(v1 T1, v2 T2, v3 T3) int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.m[key3[T1, T2, T3]{v1, v2, v3}]
}

// Metrics returns all the values we've seen, in an indeterminate order.
func (m *_Metric3[T1, T2, T3]) Metrics() (r1 []T1, r2 []T2, r3 []T3) {
	for k := range m.m {
		r1 = append(r1, k.f1)
		r2 = append(r2, k.f2)
		r3 = append(r3, k.f3)
	}
	return r1, r2, r3
}

type S struct{ a, b, c string }

func TestMetrics() {
	m1 := _Metric1[string]{}
	if got := m1.Count("a"); got != 0 {
		panic(fmt.Sprintf("Count(%q) = %d, want 0", "a", got))
	}
	m1.Add("a")
	m1.Add("a")
	if got := m1.Count("a"); got != 2 {
		panic(fmt.Sprintf("Count(%q) = %d, want 2", "a", got))
	}
	if got, want := m1.Metrics(), []string{"a"}; !_SlicesEqual(got, want) {
		panic(fmt.Sprintf("Metrics = %v, want %v", got, want))
	}

	m2 := _Metric2[int, float64]{}
	m2.Add(1, 1)
	m2.Add(2, 2)
	m2.Add(3, 3)
	m2.Add(3, 3)
	k1, k2 := m2.Metrics()

	sort.Ints(k1)
	w1 := []int{1, 2, 3}
	if !_SlicesEqual(k1, w1) {
		panic(fmt.Sprintf("_Metric2.Metrics first slice = %v, want %v", k1, w1))
	}

	sort.Float64s(k2)
	w2 := []float64{1, 2, 3}
	if !_SlicesEqual(k2, w2) {
		panic(fmt.Sprintf("_Metric2.Metrics first slice = %v, want %v", k2, w2))
	}

	m3 := _Metric3[string, S, S]{}
	m3.Add("a", S{"d", "e", "f"}, S{"g", "h", "i"})
	m3.Add("a", S{"d", "e", "f"}, S{"g", "h", "i"})
	m3.Add("a", S{"d", "e", "f"}, S{"g", "h", "i"})
	m3.Add("b", S{"d", "e", "f"}, S{"g", "h", "i"})
	if got := m3.Count("a", S{"d", "e", "f"}, S{"g", "h", "i"}); got != 3 {
		panic(fmt.Sprintf("Count(%v, %v, %v) = %d, want 3", "a", S{"d", "e", "f"}, S{"g", "h", "i"}, got))
	}
}

func main() {
	TestMetrics()
}

// _Equal reports whether two slices are equal: the same length and all
// elements equal. All floating point NaNs are considered equal.
func _SlicesEqual[Elem comparable](s1, s2 []Elem) bool {
	if len(s1) != len(s2) {
		return false
	}
	for i, v1 := range s1 {
		v2 := s2[i]
		if v1 != v2 {
			isNaN := func(f Elem) bool { return f != f }
			if !isNaN(v1) || !isNaN(v2) {
				return false
			}
		}
	}
	return true
}

// _Keys returns the keys of the map m.
// The keys will be an indeterminate order.
func _Keys[K comparable, V any](m map[K]V) []K {
	r := make([]K, 0, len(m))
	for k := range m {
		r = append(r, k)
	}
	return r
}
