func (s *Simulator) getNextEntry(index int, bc *batchConfig) (*data.Point, bool) {
	var result, entry *data.Point
	valid := true
	for result == nil {
		if bc.InsertPreviousEntry[index+s.offset] {
			if len(s.outOfOrderEntries) == 0 {
				panic("trying to insert an out of order entry when there are no out of order entries")
			}
			entry = s.outOfOrderEntries[0]
			s.outOfOrderEntries = s.outOfOrderEntries[1:]
		} else {
			entry = data.NewPoint()
			if valid = s.base.Next(entry); !valid {
				break
			}
		}
		if bc.MissingEntries[index+s.offset] {
			s.offset++
			continue
		}
		if bc.OutOfOrderEntries[index+s.offset] {
			s.outOfOrderEntries = append(s.outOfOrderEntries, entry)
			s.offset++
			continue
		}
		result = entry
	}
	return result, valid
}
getNextEntry returns the next entry which, depending on the batch configuration,
can be a previous out of order entry or the next entry from the base
common.Simulator. It also deals with missing or out of order entries. Its
setup so that it can declare an entry missing or out-of-order no matter if
its a previous out-of-order entry or a new one.
package iot
import (
	"github.com/timescale/tsbs/pkg/data"
	"github.com/timescale/tsbs/pkg/data/usecases/common"
	"time"
)
const (
	defaultBatchSize = 10
)
type SimulatorConfig common.BaseSimulatorConfig
func (sc *SimulatorConfig) NewSimulator(interval time.Duration, limit uint64) common.Simulator {
	s := (*common.BaseSimulatorConfig)(sc).NewSimulator(interval, limit)
	maxFieldCount := 0
	for _, fields := range s.Fields() {
		if len(fields) > maxFieldCount {
			maxFieldCount = len(fields)
		}
	}
	return &Simulator{
		base:            s,
		batchSize:       defaultBatchSize,
		configGenerator: newBatchConfig,
		maxFieldCount:   maxFieldCount,
	}
}
type Simulator struct {
	base            common.Simulator
	batchSize       uint
	configGenerator func(outOfOrderBatchCount, outOfOrderEntryCount, fieldCount, tagCount int) *batchConfig
	maxFieldCount int
	currBatch         []*data.Point
	outOfOrderBatches [][]*data.Point
	outOfOrderEntries []*data.Point
	offset int
}
func (s Simulator) Fields() map[string][]string {
	return s.base.Fields()
}
func (s Simulator) TagKeys() []string {
	return s.base.TagKeys()
}
func (s Simulator) TagTypes() []string {
	return s.base.TagTypes()
}
func (s Simulator) Finished() bool {
	return s.base.Finished() && len(s.currBatch) == 0 && !s.pendingOutOfOrderItems()
}
func (s *Simulator) Next(p *data.Point) bool {
	if s.batchSize == 0 {
		return s.base.Next(p)
	}
	if len(s.currBatch) > 0 || s.simulateNextBatch() {
		p.Copy(s.currBatch[0])
		s.currBatch = s.currBatch[1:]
		return true
	}
	return false
}
func (s *Simulator) Headers() *common.GeneratedDataHeaders {
	return &common.GeneratedDataHeaders{
		TagTypes:  s.TagTypes(),
		TagKeys:   s.TagKeys(),
		FieldKeys: s.Fields(),
	}
}
func (s *Simulator) pendingOutOfOrderItems() bool {
	return len(s.outOfOrderBatches) > 0 || len(s.outOfOrderEntries) > 0
}
func (s *Simulator) batchPending() []*data.Point {
	var batch []*data.Point
	if len(s.outOfOrderBatches) > 0 {
		batch = s.outOfOrderBatches[0]
		s.outOfOrderBatches = s.outOfOrderBatches[1:]
		return batch
	}
	pendingEntries := len(s.outOfOrderEntries)
	if pendingEntries > 0 {
		if pendingEntries > int(s.batchSize) {
			batch = s.outOfOrderEntries[:s.batchSize]
			s.outOfOrderEntries = s.outOfOrderEntries[s.batchSize:]
			return batch
		}
		batch = s.outOfOrderEntries
		s.outOfOrderEntries = s.outOfOrderEntries[:0]
		return batch
	}
	return batch
}
func (s *Simulator) simulateNextBatch() bool {
	if s.base.Finished() {
		if s.pendingOutOfOrderItems() {
			s.currBatch = s.batchPending()
			return true
		}
		return false
	}
	bc := s.configGenerator(len(s.outOfOrderBatches), len(s.outOfOrderEntries), s.maxFieldCount, len(s.TagKeys()))
	if bc.InsertPrevious {
		if len(s.outOfOrderBatches) == 0 {
			panic("trying to insert an out of order batch when there are no out of order batches")
		}
		s.currBatch = s.outOfOrderBatches[0]
		s.outOfOrderBatches = s.outOfOrderBatches[1:]
		return true
	}
	if bc.Missing {
		s.flushBatch()
		return s.simulateNextBatch()
	}
	if bc.OutOfOrder {
		s.generateOutOfOrderBatch(bc)
		return s.simulateNextBatch()
	}
	s.currBatch = s.generateBatch(bc)
	if len(s.currBatch) == 0 {
		return s.simulateNextBatch()
	}
	return len(s.currBatch) > 0
}
func (s *Simulator) generateBatch(bc *batchConfig) []*data.Point {
	batch := make([]*data.Point, s.batchSize)
	s.offset = 0
	for i := range batch {
		if s.base.Finished() {
			batch = batch[:i]
			break
		}
		entry, valid := s.getNextEntry(i, bc)
		if !valid {
			batch = batch[:i]
			break
		}
		if index, ok := bc.ZeroFields[i]; ok {
			keys := entry.FieldKeys()
			if index >= len(keys) {
				index = index % len(keys)
			}
			entry.ClearFieldValue(keys[index])
		}
		if index, ok := bc.ZeroTags[i]; ok {
			keys := entry.TagKeys()
			if len(keys) < index {
				panic("trying to zero a tag value with a non-existant index")
			}
			entry.ClearTagValue(keys[index])
		}
		batch[i] = entry
	}
	return batch
}