package stat

import (
	"sync"
	"time"

	"code.justin.tv/systems/sandstorm/closer"
	"code.justin.tv/systems/sandstorm/logging"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	"github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface"
)

// Client is a batched client that writes to cloudwatch
type Client struct {
	// cloudwatch namespace to push metrics out to
	Namespace string
	// CloudWatch client to use
	CloudWatch cloudwatchiface.CloudWatchAPI
	// logger to write events to
	Logger logging.Logger
	// Closer to syncronoize shutdown with
	Closer *closer.Closer

	// (optional) rate to flush to cloudwatch. defaults to 1 minute.
	FlushRate time.Duration

	initSync sync.Once
	runner   *runner
}

func (c *Client) init() {
	c.initSync.Do(func() {
		if c.FlushRate == time.Duration(0) {
			c.FlushRate = time.Minute
		}

		c.runner = &runner{
			Closer:     c.Closer,
			CloudWatch: c.CloudWatch,
			Logger:     c.Logger,
			FlushRate:  c.FlushRate,
			Namespace:  c.Namespace,
		}
	})
}

// Increment a single metric value
func (c *Client) Increment(metric Metric) {
	c.init()

	c.runner.Increment(metric)
}

// Measure a metric at a value
func (c *Client) Measure(metric Metric, value float64) {
	c.init()

	c.runner.Measure(metric, value)
}

// WithMeasuredResult records the success or failure of a function and latency.
// latency is only recorded on success.
func (c *Client) WithMeasuredResult(
	metricName string,
	dimensions []*cloudwatch.Dimension,
	fn func() error,
) error {
	startTime := time.Now()

	err := fn()
	if err != nil {
		c.Increment(Metric{
			Dimensions: dimensions,
			MetricName: metricName + ".failure",
		})
		return err
	}

	c.Increment(Metric{
		MetricName: metricName + ".success",
	})

	c.Measure(Metric{
		Dimensions: dimensions,
		MetricName: metricName + ".latency",
	}, time.Now().Sub(startTime).Seconds())

	return nil
}
