package data

// FrameInputConverter is a type to support building a Frame while also
// doing conversion as data is added to the Frame.
type FrameInputConverter struct {
	Frame           *Frame
	fieldConverters []FieldConverter
}

// A FieldConverter is a type to support building Frame fields of a different
// type than one's input data.
type FieldConverter struct {
	// OutputFieldType is the type of Field that will be created.
	OutputFieldType FieldType

	// Converter is a conversion function that is called when setting Field values with a FrameInputConverter.
	// Care must be taken that the type returned by the conversion function matches the member type of the FieldType,
	// and that the input type matches the expected input type for the Converter function, or panics can occur.
	// If the Converter is nil, no conversion is performed when calling methods to set values.
	Converter Converter
}

// Converter is a function type for converting values in a Frame. It is the consumers responsibility
// to the check the underlying interface types of the input and return types to avoid panics.
type Converter func(v interface{}) (interface{}, error)

// NewFrameInputConverter returns a FrameInputConverter which is used to create a Frame from data
// that needs value conversions. The FrameInputConverter will create a new Frame with fields
// based on the FieldConverters' OutputFieldTypes of length rowLen.
func NewFrameInputConverter(fieldConvs []FieldConverter, rowLen int) (*FrameInputConverter, error) {
	fTypes := make([]FieldType, len(fieldConvs))
	for i, fc := range fieldConvs {
		fTypes[i] = fc.OutputFieldType
	}

	f := NewFrameOfFieldTypes("", rowLen, fTypes...)
	return &FrameInputConverter{
		Frame:           f,
		fieldConverters: fieldConvs,
	}, nil
}

// Set sets val a FieldIdx and rowIdx of the frame. If the corresponding FieldConverter's
// Converter is not nil, then the Converter function is called before setting the value (otherwise Frame.Set is called directly).
// If an error is returned from the Converter function this function returns that error.
// Like Frame.Set and Field.Set, it will panic if fieldIdx or rowIdx are out of range.
func (fic *FrameInputConverter) Set(fieldIdx, rowIdx int, val interface{}) error {
	if fic.fieldConverters[fieldIdx].Converter == nil {
		fic.Frame.Set(fieldIdx, rowIdx, val)
		return nil
	}
	convertedVal, err := fic.fieldConverters[fieldIdx].Converter(val)
	if err != nil {
		return err
	}
	fic.Frame.Set(fieldIdx, rowIdx, convertedVal)
	return nil
}
