package mock

import (
	"math"
	"math/rand"
	"time"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/plugins/inputs"
)

type Mock struct {
	counter int64

	MetricName string            `toml:"metric_name"`
	Tags       map[string]string `toml:"tags"`

	Random   []*random   `toml:"random"`
	Step     []*step     `toml:"step"`
	Stock    []*stock    `toml:"stock"`
	SineWave []*sineWave `toml:"sine_wave"`
}

type random struct {
	Name string  `toml:"name"`
	Min  float64 `toml:"min"`
	Max  float64 `toml:"max"`
}

type sineWave struct {
	Name      string  `toml:"name"`
	Amplitude float64 `toml:"amplitude"`
	Period    float64 `toml:"period"`
}

type step struct {
	latest float64

	Name  string  `toml:"name"`
	Start float64 `toml:"min"`
	Step  float64 `toml:"max"`
}

type stock struct {
	latest float64

	Name       string  `toml:"name"`
	Price      float64 `toml:"price"`
	Volatility float64 `toml:"volatility"`
}

const sampleConfig = `
  ## Set the metric name to use for reporting
  metric_name = "mock"

  ## Optional string key-value pairs of tags to add to all metrics
  # [inputs.mock.tags]
  # "key" = "value"

  ## One or more mock data fields *must* be defined.
  ##
  ## [[inputs.mock.random]]
  ##   name = "rand"
  ##   min = 1.0
  ##   max = 6.0
  ## [[inputs.mock.sine_wave]]
  ##   name = "wave"
  ##   amplitude = 1.0
  ##   period = 0.5
  ## [[inputs.mock.step]]
  ##   name = "plus_one"
  ##   start = 0.0
  ##   step = 1.0
  ## [[inputs.mock.stock]]
  ##   name = "abc"
  ##   price = 50.00
  ##   volatility = 0.2
`

func (m *Mock) SampleConfig() string {
	return sampleConfig
}

func (m *Mock) Description() string {
	return "Generate metrics for test and demonstration purposes"
}

func (m *Mock) Init() error {
	rand.Seed(time.Now().UnixNano())
	return nil
}

func (m *Mock) Gather(acc telegraf.Accumulator) error {
	fields := make(map[string]interface{})
	m.generateRandomFloat64(fields)
	m.generateStockPrice(fields)
	m.generateSineWave(fields)
	m.generateStep(fields)

	tags := make(map[string]string)
	for key, value := range m.Tags {
		tags[key] = value
	}

	acc.AddFields(m.MetricName, fields, tags)

	m.counter++

	return nil
}

// Generate random value between min and max, inclusivly
func (m *Mock) generateRandomFloat64(fields map[string]interface{}) {
	for _, random := range m.Random {
		fields[random.Name] = random.Min + rand.Float64()*(random.Max-random.Min)
	}
}

// Create sine waves
func (m *Mock) generateSineWave(fields map[string]interface{}) {
	for _, field := range m.SineWave {
		fields[field.Name] = math.Sin((float64(m.counter) * field.Period * math.Pi)) * field.Amplitude
	}
}

// Begin at start value and then add step value every tick
func (m *Mock) generateStep(fields map[string]interface{}) {
	for _, step := range m.Step {
		if m.counter == 0 {
			step.latest = step.Start
		} else {
			step.latest += step.Step
		}

		fields[step.Name] = step.latest
	}
}

// Begin at start price and then generate random value
func (m *Mock) generateStockPrice(fields map[string]interface{}) {
	for _, stock := range m.Stock {
		if stock.latest == 0.0 {
			stock.latest = stock.Price
		} else {
			noise := 2 * (rand.Float64() - 0.5)
			stock.latest = stock.latest + (stock.latest * stock.Volatility * noise)

			// avoid going below zero
			if stock.latest < 1.0 {
				stock.latest = 1.0
			}
		}

		fields[stock.Name] = stock.latest
	}
}

func init() {
	inputs.Add("mock", func() telegraf.Input {
		return &Mock{}
	})
}
