// Copyright (c) 2020-2022, R.I. Pienaar and the Choria Project contributors
//
// SPDX-License-Identifier: Apache-2.0

package aggregate

import (
	"encoding/json"
	"fmt"
	"strconv"
	"sync"
)

// AverageAggregator averages seen values
type AverageAggregator struct {
	sum    float64
	count  int
	args   []any
	format string

	sync.Mutex
}

// NewAverageAggregator creates a new AverageAggregator with the specific options supplied
func NewAverageAggregator(args []any) (*AverageAggregator, error) {
	agg := &AverageAggregator{
		args:   args,
		format: parseFormatFromArgs(args),
	}

	return agg, nil
}

// Type is the type of Aggregator
func (a *AverageAggregator) Type() string {
	return "average"
}

// ProcessValue processes and tracks the specific value
func (a *AverageAggregator) ProcessValue(v any) error {
	a.Lock()
	defer a.Unlock()

	if a.processInt(v) {
		return nil
	}

	if a.processInt64(v) {
		return nil
	}

	if a.processFloat(v) {
		return nil
	}

	if a.processString(v) {
		return nil
	}

	return fmt.Errorf("unsupported data type for average aggregator")
}

// ResultJSON return the results in JSON format preserving types
func (a *AverageAggregator) ResultJSON() ([]byte, error) {
	a.Lock()
	defer a.Unlock()

	avg := a.sum / float64(a.count)

	return json.Marshal(map[string]float64{
		"average": avg,
	})
}

// ResultStrings returns a map of results in string format
func (a *AverageAggregator) ResultStrings() (map[string]string, error) {
	a.Lock()
	defer a.Unlock()

	avg := a.sum / float64(a.count)

	return map[string]string{"Average": fmt.Sprintf("%f", avg)}, nil
}

// ResultFormattedStrings return the results in a formatted way, if no format is given a calculated value is used
func (a *AverageAggregator) ResultFormattedStrings(format string) ([]string, error) {
	a.Lock()
	defer a.Unlock()

	avg := a.sum / float64(a.count)

	if format == "" {
		if a.format != "" {
			format = a.format
		} else {
			format = "Average: %.3f"
		}
	}

	return []string{fmt.Sprintf(format, avg)}, nil
}

func (a *AverageAggregator) processString(v any) bool {
	s, ok := v.(string)
	if !ok {
		return false
	}

	f, err := strconv.ParseFloat(s, 64)
	if err != nil {
		return false
	}

	a.sum = a.sum + f
	a.count++

	return true
}

func (a *AverageAggregator) processFloat(v any) bool {
	f, ok := v.(float64)
	if !ok {
		return false
	}

	a.sum = a.sum + f
	a.count++

	return true
}

func (a *AverageAggregator) processInt64(v any) bool {
	i, ok := v.(int64)
	if !ok {
		return false
	}

	a.sum = a.sum + float64(i)
	a.count++

	return true
}

func (a *AverageAggregator) processInt(v any) bool {
	i, ok := v.(int)
	if !ok {
		return false
	}

	a.sum = a.sum + float64(i)
	a.count++

	return true
}
