package sqleng

import (
	"fmt"
	"time"

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

// getRowFillValues populates a slice of values corresponding to the provided data.Frame fields.
// Uses data.FillMissing settings to fill in values that are missing. Values are normally missing
// due to that the selected query interval doesn't match the intervals of the data returned from
// the query and therefore needs to be resampled.
func getRowFillValues(f *data.Frame, tsSchema data.TimeSeriesSchema, currentTime time.Time,
	fillMissing *data.FillMissing, intermediateRows []int, lastSeenRowIdx int) []interface{} {
	vals := make([]interface{}, 0, len(f.Fields))
	for i, field := range f.Fields {
		// if the current field is the time index of the series
		// set the new value to be added to the new timestamp
		if i == tsSchema.TimeIndex {
			switch f.Fields[tsSchema.TimeIndex].Type() {
			case data.FieldTypeTime:
				vals = append(vals, currentTime)
			default:
				vals = append(vals, &currentTime)
			}
			continue
		}

		isValueField := false
		for _, idx := range tsSchema.ValueIndices {
			if i == idx {
				isValueField = true
				break
			}
		}

		// if the current field is value Field
		// set the new value to the last seen field value (if such exists)
		// otherwise set the appropriate value according to the fillMissing mode
		// if the current field is string field)
		// set the new value to be added to the last seen value (if such exists)
		// if the Frame is wide then there should not be any string fields
		var newVal interface{}
		if isValueField {
			if len(intermediateRows) > 0 {
				// instead of setting the last seen
				// we could set avg, sum, min or max
				// of the intermediate values for each field
				newVal = f.At(i, intermediateRows[len(intermediateRows)-1])
			} else {
				val, err := data.GetMissing(fillMissing, field, lastSeenRowIdx)
				if err == nil {
					newVal = val
				}
			}
		} else if lastSeenRowIdx >= 0 {
			newVal = f.At(i, lastSeenRowIdx)
		}
		vals = append(vals, newVal)
	}
	return vals
}

// resample resample provided time-series data.Frame.
// This is needed in the case of the selected query interval doesn't
// match the intervals of the time-series field in the data.Frame and
// therefore needs to be resampled.
func resample(f *data.Frame, qm dataQueryModel) (*data.Frame, error) {
	tsSchema := f.TimeSeriesSchema()
	if tsSchema.Type == data.TimeSeriesTypeNot {
		return f, fmt.Errorf("can not fill missing, not timeseries frame")
	}

	if qm.Interval == 0 {
		return f, nil
	}

	newFields := make([]*data.Field, 0, len(f.Fields))
	for _, field := range f.Fields {
		newField := data.NewFieldFromFieldType(field.Type(), 0)
		newField.Name = field.Name
		newField.Labels = field.Labels
		newFields = append(newFields, newField)
	}
	resampledFrame := data.NewFrame(f.Name, newFields...)
	resampledFrame.Meta = f.Meta

	resampledRowidx := 0
	lastSeenRowIdx := -1
	timeField := f.Fields[tsSchema.TimeIndex]

	for currentTime := qm.TimeRange.From; !currentTime.After(qm.TimeRange.To); currentTime = currentTime.Add(qm.Interval) {
		initialRowIdx := 0
		if lastSeenRowIdx > 0 {
			initialRowIdx = lastSeenRowIdx + 1
		}
		intermediateRows := make([]int, 0)
		for {
			rowLen, err := f.RowLen()
			if err != nil {
				return f, err
			}
			if initialRowIdx == rowLen {
				break
			}

			t, ok := timeField.ConcreteAt(initialRowIdx)
			if !ok {
				return f, fmt.Errorf("time point is nil")
			}

			if t.(time.Time).After(currentTime) {
				nextTime := currentTime.Add(qm.Interval)
				if t.(time.Time).Before(nextTime) {
					intermediateRows = append(intermediateRows, initialRowIdx)
					lastSeenRowIdx = initialRowIdx
					initialRowIdx++
				}
				break
			}

			intermediateRows = append(intermediateRows, initialRowIdx)
			lastSeenRowIdx = initialRowIdx
			initialRowIdx++
		}

		// no intermediate points; set values following fill missing mode
		fieldVals := getRowFillValues(f, tsSchema, currentTime, qm.FillMissing, intermediateRows, lastSeenRowIdx)

		resampledFrame.InsertRow(resampledRowidx, fieldVals...)
		resampledRowidx++
	}

	return resampledFrame, nil
}
