package sims

import (
	"math"
	"time"

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

type flightSim struct {
	key simulationKey
	cfg flightConfig
}

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

type flightDataPoint struct {
	time     time.Time
	lat      float64 // gps
	lng      float64 // gps
	heading  float64 // degree
	altitude float64 // above ground
}

type flightConfig struct {
	CenterLat   float64 `json:"centerLat"`
	CenterLng   float64 `json:"centerLng"`
	Radius      float64 `json:"radius"`
	AltitudeMin float64 `json:"altitudeMin"`
	AltitudeMax float64 `json:"altitudeMax"`
	Period      float64 `json:"period"` // angular speed (seconds)
}

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

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

func (s *flightSim) NewFrame(size int) *data.Frame {
	frame := data.NewFrameOfFieldTypes("", size,
		data.FieldTypeTime,    // time
		data.FieldTypeFloat64, // lat
		data.FieldTypeFloat64, // lng
		data.FieldTypeFloat64, // heading
		data.FieldTypeFloat64, // altitude
	)
	frame.Fields[0].Name = "time"
	frame.Fields[1].Name = "lat"
	frame.Fields[2].Name = "lng"
	frame.Fields[3].Name = "heading"
	frame.Fields[4].Name = "altitude"
	return frame
}

func (s *flightSim) GetValues(t time.Time) map[string]any {
	p := s.cfg.getNextPoint(t)

	return map[string]any{
		"time":     p.time,
		"lat":      p.lat,
		"lng":      p.lng,
		"heading":  p.heading,
		"altitude": p.altitude,
	}
}

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

func newFlightSimInfo() simulationInfo {
	sf := flightConfig{
		CenterLat:   37.83, // San francisco
		CenterLng:   -122.42487,
		Radius:      0.01, // raw gps degrees
		AltitudeMin: 350,
		AltitudeMax: 400,
		Period:      10, //model.Get("period").MustFloat64(10),
	}

	df := data.NewFrame("")
	df.Fields = append(df.Fields, data.NewField("centerLat", nil, []float64{sf.CenterLat}))
	df.Fields = append(df.Fields, data.NewField("centerLng", nil, []float64{sf.CenterLng}))
	df.Fields = append(df.Fields, data.NewField("radius", nil, []float64{sf.Radius}))
	df.Fields = append(df.Fields, data.NewField("altitudeMin", nil, []float64{sf.AltitudeMin}))
	df.Fields = append(df.Fields, data.NewField("altitudeMax", nil, []float64{sf.AltitudeMax}))

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

	return simulationInfo{
		Type:         "flight",
		Name:         "Flight",
		Description:  "simple circling airplain",
		ConfigFields: df,
		OnlyForward:  false,
		create: func(cfg simulationState) (Simulation, error) {
			s := &flightSim{
				key: cfg.Key,
				cfg: sf, // default value
			}
			err := updateConfigObjectFromJSON(&s.cfg, cfg.Config) // override any fields
			return s, err
		},
	}
}

func (f *flightConfig) getNextPoint(t time.Time) flightDataPoint {
	periodNS := int64(f.Period * float64(time.Second))
	ms := t.UnixNano() % periodNS
	per := float64(ms) / float64(periodNS)
	rad := per * 2.0 * math.Pi // 0 >> 2Pi
	delta := f.AltitudeMax - f.AltitudeMin

	return flightDataPoint{
		time:     t,
		lat:      f.CenterLat + math.Sin(rad)*f.Radius,
		lng:      f.CenterLng + math.Cos(rad)*f.Radius,
		heading:  (rad * 180) / math.Pi,         // (math.Atanh(rad) * 180.0) / math.Pi, // in degrees
		altitude: f.AltitudeMin + (delta * per), // clif
	}
}
