package gen

import (
	"math"
	"time"

	"github.com/influxdata/influxdb/v2/models"
)

type SeriesGenerator interface {
	// Next advances the series generator to the next series key.
	Next() bool

	// Key returns the series key.
	// The returned value may be cached.
	Key() []byte

	// Name returns the name of the measurement.
	// The returned value may be modified by a subsequent call to Next.
	Name() []byte

	// Tags returns the tag set.
	// The returned value may be modified by a subsequent call to Next.
	Tags() models.Tags

	// Field returns the name of the field.
	// The returned value may be modified by a subsequent call to Next.
	Field() []byte

	// FieldType returns the data type for the field.
	FieldType() models.FieldType

	// TimeValuesGenerator returns a values sequence for the current series.
	TimeValuesGenerator() TimeValuesSequence
}

type TimeSequenceSpec struct {
	// Count specifies the maximum number of values to generate.
	Count int

	// Start specifies the starting time for the values.
	Start time.Time

	// Delta specifies the interval between time stamps.
	Delta time.Duration

	// Precision specifies the precision of timestamp intervals
	Precision time.Duration
}

func (ts TimeSequenceSpec) ForTimeRange(tr TimeRange) TimeSequenceSpec {
	// Truncate time range
	if ts.Delta > 0 {
		tr = tr.Truncate(ts.Delta)
	} else {
		tr = tr.Truncate(ts.Precision)
	}

	ts.Start = tr.Start

	if ts.Delta > 0 {
		intervals := int(tr.End.Sub(tr.Start) / ts.Delta)
		if intervals > ts.Count {
			// if the number of intervals in the specified time range exceeds
			// the maximum count, move the start forward to limit the number of values
			ts.Start = tr.End.Add(-time.Duration(ts.Count) * ts.Delta)
		} else {
			ts.Count = intervals
		}
	} else {
		ts.Delta = tr.End.Sub(tr.Start) / time.Duration(ts.Count)
		if ts.Delta < ts.Precision {
			// count is too high for the range of time and precision
			ts.Count = int(tr.End.Sub(tr.Start) / ts.Precision)
			ts.Delta = ts.Precision
		} else {
			ts.Delta = ts.Delta.Round(ts.Precision)
		}
		ts.Precision = 0
	}

	return ts
}

type TimeRange struct {
	Start time.Time
	End   time.Time
}

func (t TimeRange) Truncate(d time.Duration) TimeRange {
	return TimeRange{
		Start: t.Start.Truncate(d),
		End:   t.End.Truncate(d),
	}
}

type TimeValuesSequence interface {
	Reset()
	Next() bool
	Values() Values
	ValueType() models.FieldType
}

type Values interface {
	MinTime() int64
	MaxTime() int64
	Encode([]byte) ([]byte, error)
}

type cache struct {
	key  []byte
	tags models.Tags
}

type seriesGenerator struct {
	name  []byte
	tags  TagsSequence
	field []byte
	vg    TimeValuesSequence
	n     int64

	c cache
}

func NewSeriesGenerator(name []byte, field []byte, vg TimeValuesSequence, tags TagsSequence) SeriesGenerator {
	return NewSeriesGeneratorLimit(name, field, vg, tags, math.MaxInt64)
}

func NewSeriesGeneratorLimit(name []byte, field []byte, vg TimeValuesSequence, tags TagsSequence, n int64) SeriesGenerator {
	return &seriesGenerator{
		name:  name,
		field: field,
		tags:  tags,
		vg:    vg,
		n:     n,
	}
}

func (g *seriesGenerator) Next() bool {
	if g.n > 0 {
		g.n--
		if g.tags.Next() {
			g.c = cache{}
			g.vg.Reset()
			return true
		}
		g.n = 0
	}

	return false
}

func (g *seriesGenerator) Key() []byte {
	if len(g.c.key) == 0 {
		g.c.key = models.MakeKey(g.name, g.tags.Value())
	}
	return g.c.key
}

func (g *seriesGenerator) Name() []byte {
	return g.name
}

func (g *seriesGenerator) Tags() models.Tags {
	if len(g.c.tags) == 0 {
		g.c.tags = g.tags.Value().Clone()
	}
	return g.c.tags
}

func (g *seriesGenerator) Field() []byte {
	return g.field
}

func (g *seriesGenerator) FieldType() models.FieldType {
	return g.vg.ValueType()
}

func (g *seriesGenerator) TimeValuesGenerator() TimeValuesSequence {
	return g.vg
}
