package sims

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

	"github.com/grafana/grafana-plugin-sdk-go/data"
)

type waveformSim struct {
	key        simulationKey
	cfg        waveformConfig
	calculator func(x float64, cfg *waveformConfig) float64
}

var (
	_ Simulation = (*waveformSim)(nil)
)

type waveformConfig struct {
	Period    float64 `json:"period"`           // seconds
	Offset    float64 `json:"offset,omitempty"` // Y shift
	Phase     float64 `json:"phase,omitempty"`  // X shift // +- 1 (will scale the )
	Amplitude float64 `json:"amplitude"`        // Y size
	Noise     float64 `json:"noise,omitempty"`  // random noise to add
}

func (s *waveformSim) GetState() simulationState {
	return simulationState{
		Key:    s.key,
		Config: s.cfg,
	}
}

func (s *waveformSim) SetConfig(vals map[string]any) error {
	return updateConfigObjectFromJSON(s.cfg, vals)
}

func (s *waveformSim) NewFrame(size int) *data.Frame {
	frame := data.NewFrameOfFieldTypes("", size,
		data.FieldTypeTime,    // time
		data.FieldTypeFloat64, // value
	)
	frame.Fields[0].Name = data.TimeSeriesTimeFieldName
	frame.Fields[1].Name = data.TimeSeriesValueFieldName
	return frame
}

func (s *waveformSim) GetValues(t time.Time) map[string]any {
	x := 0.0
	if s.cfg.Period > 0 {
		periodMS := s.cfg.Period * 1000
		ms := t.UnixMilli() % int64(periodMS)
		x = ((float64(ms) / periodMS) * 2 * math.Pi) // 0 >> 2Pi
	}

	v := s.calculator(x, &s.cfg)

	noise := s.cfg.Noise
	if noise > 0 {
		gen := rand.New(rand.NewSource(t.UnixMilli())) // consistent for the value
		v += (gen.Float64() * 2.0 * noise) - noise
	}

	return map[string]any{
		data.TimeSeriesTimeFieldName:  t,
		data.TimeSeriesValueFieldName: v,
	}
}

func (s *waveformSim) Close() error {
	return nil
}

func newSinewaveInfo() simulationInfo {
	sf := waveformConfig{
		Period:    10,
		Amplitude: 1,
		Offset:    0,
		Phase:     0,
	}

	df := data.NewFrame("")
	df.Fields = append(df.Fields, data.NewField("period", nil, []float64{sf.Period}))
	df.Fields = append(df.Fields, data.NewField("offset", nil, []float64{sf.Offset}))
	df.Fields = append(df.Fields, data.NewField("phase", nil, []float64{sf.Phase}))
	df.Fields = append(df.Fields, data.NewField("amplitude", nil, []float64{sf.Amplitude}))
	df.Fields = append(df.Fields, data.NewField("noise", nil, []float64{sf.Noise}))

	f := data.NewField("period", nil, []float64{sf.Period})
	f.Config = &data.FieldConfig{Unit: "s"} // seconds
	df.Fields = append(df.Fields, f)

	return simulationInfo{
		Type:         "sine",
		Name:         "Sine",
		Description:  "Sinewave generator",
		ConfigFields: df,
		OnlyForward:  false,
		create: func(cfg simulationState) (Simulation, error) {
			s := &waveformSim{
				key:        cfg.Key,
				cfg:        sf, // default value
				calculator: sinewaveCalculator,
			}
			err := updateConfigObjectFromJSON(&s.cfg, cfg.Config) // override any fields
			return s, err
		},
	}
}

func sinewaveCalculator(x float64, cfg *waveformConfig) float64 {
	return (math.Sin(x) * cfg.Amplitude) + cfg.Offset
}
