// Copyright 2017 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package metrics provides simple metrics tracking features.
package metrics

import (
	"fmt"
	"io"
	"sort"
	"sync"
	"time"
)

// The Provider interface defines an interface for measuring metrics.
type Provider interface {
	// MeasureSince adds a measurement for key to the metric store.
	// Used with defer and time.Now().
	MeasureSince(key string, start time.Time)

	// WriteMetrics will write a summary of the metrics to w.
	WriteMetrics(w io.Writer)

	// Reset clears the metric store.
	Reset()
}

// Store provides storage for a set of metrics.
type Store struct {
	metrics map[string][]time.Duration
	mu      *sync.Mutex
}

// NewProvider returns a new instance of a metric store.
func NewProvider() Provider {
	return &Store{
		metrics: make(map[string][]time.Duration),
		mu:      &sync.Mutex{},
	}
}

// Reset clears the metrics store.
func (s *Store) Reset() {
	s.mu.Lock()
	s.metrics = make(map[string][]time.Duration)
	s.mu.Unlock()
}

// MeasureSince adds a measurement for key to the metric store.
func (s *Store) MeasureSince(key string, start time.Time) {
	s.mu.Lock()
	s.metrics[key] = append(s.metrics[key], time.Since(start))
	s.mu.Unlock()
}

// WriteMetrics writes a summary of the metrics to w.
func (s *Store) WriteMetrics(w io.Writer) {
	s.mu.Lock()

	results := make([]result, len(s.metrics))

	var i int
	for k, v := range s.metrics {
		var sum time.Duration
		var max time.Duration

		for _, d := range v {
			sum += d
			if d > max {
				max = d
			}
		}

		avg := time.Duration(int(sum) / len(v))

		results[i] = result{key: k, count: len(v), max: max, sum: sum, avg: avg}
		i++
	}

	s.mu.Unlock()

	// sort and print results
	fmt.Fprintf(w, "  %13s  %12s  %12s  %5s  %s\n", "cumulative", "average", "maximum", "", "")
	fmt.Fprintf(w, "  %13s  %12s  %12s  %5s  %s\n", "duration", "duration", "duration", "count", "template")
	fmt.Fprintf(w, "  %13s  %12s  %12s  %5s  %s\n", "----------", "--------", "--------", "-----", "--------")

	sort.Sort(bySum(results))
	for _, v := range results {
		fmt.Fprintf(w, "  %13s  %12s  %12s  %5d  %s\n", v.sum, v.avg, v.max, v.count, v.key)
	}

}

// A result represents the calculated results for a given metric.
type result struct {
	key   string
	count int
	sum   time.Duration
	max   time.Duration
	avg   time.Duration
}

type bySum []result

func (b bySum) Len() int           { return len(b) }
func (b bySum) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
func (b bySum) Less(i, j int) bool { return b[i].sum > b[j].sum }
