package timing

import (
	"context"
	"fmt"
	"strings"
	"sync"
	"time"
)

func Start(ctx context.Context, name string, desc ...string) *TimingEnd {
	h, _ := ctx.Value(ctxKey).(*Header)
	if h == nil {
		return &TimingEnd{
			StartedAt: time.Now(),
			metric:    &Metric{},
		}
	}

	// group metrics by name and desc
	key := name
	if len(desc) > 0 {
		key = fmt.Sprintf("%s;desc=%s", name, desc[0])
	}

	h.Lock()
	defer h.Unlock()

	metric := &Metric{}
	if _, ok := h.metrics[key]; ok {
		h.metrics[key] = append(h.metrics[key], metric)
	} else {
		h.metrics[key] = []*Metric{metric}
	}
	return &TimingEnd{
		StartedAt: time.Now(),
		metric:    metric,
	}
}

func NewContext(ctx context.Context, h *Header) context.Context {
	c := context.WithValue(ctx, ctxKey, h)
	return c
}

func NewHeader() *Header {
	return &Header{
		metrics: map[string][]*Metric{},
	}
}

type Header struct {
	metrics map[string][]*Metric
	sync.Mutex
}

func (h *Header) Empty() bool {
	return len(h.metrics) == 0
}

func (h *Header) String() string {
	parts := make([]string, 0, len(h.metrics))
	for key, ms := range h.metrics {
		// count the duration of metrics with the same key
		var count = len(ms)
		var totalDuration time.Duration
		var maxDuration time.Duration
		for _, m := range ms {
			totalDuration += m.duration
			if m.duration > maxDuration {
				maxDuration = m.duration
			}
		}

		// format
		var part string
		totalDur := float64(totalDuration) / float64(time.Millisecond)
		maxDur := float64(maxDuration) / float64(time.Millisecond)
		if count > 1 {
			part = fmt.Sprintf("%s x%d;dur=%.3f;max=%.3f", key, count, totalDur, maxDur)
		} else {
			part = fmt.Sprintf("%s;dur=%.3f", key, totalDur)
		}

		parts = append(parts, part)
	}

	return strings.Join(parts, ",")
}

type Metric struct {
	duration time.Duration
}

type TimingEnd struct {
	StartedAt time.Time
	metric    *Metric
}

func (t *TimingEnd) End() time.Duration {
	duration := time.Since(t.StartedAt)
	t.metric.duration = duration
	return duration
}

type ctxKeyType struct{}

var ctxKey = ctxKeyType(struct{}{})
