// ABOUT DISPOSITION MATRICES/VECTORS
//
// Mlrvals can be of type MT_STRING, MT_INT, MT_FLOAT, MT_BOOLEAN, as well as
// MT_ABSENT, MT_VOID, and ERROR.  Thus when we do pairwise operations on them
// (for binary operators) or singly (for unary operators), what we do depends
// on the type.
//
// Mlrval type enums are 0-up integers precisely so that instead of if-elsing
// or switching on the types, we can instead define tables of function pointers
// and jump immediately to the right thing to do for a given type pairing.  For
// example: adding two ints, or an int and a float, or int and boolean (the
// latter being an error).
//
// The next-past-highest mlrval type enum is called mlrval.MT_DIM and that is the
// dimension of the binary-operator disposition matrices and unary-operator
// disposition vectors.
//
// Note that not every operation uses disposition matrices. If something makes
// sense only for pairs of strings and nothing else, it makes sense for the
// implementing method to return an MT_STRING result if both arguments are
// MT_STRING, or MT_ERROR otherwise.
//
// Naming conventions: since these functions fit into disposition matrices, the
// names are kept quite short. Many are of the form 'plus_f_fi', 'eq_b_xs',
// etc. The conventions are:
//
// * The 'plus_', 'eq_', etc is for the name of the operator.
//
// * For binary operators, things like _f_fi indicate the type of the return
//   value (e.g. 'f') and the types of the two arguments (e.g. 'fi').
//
// * For unary operators, things like _i_i indicate the type of the return
//   value and the type of the argument.
//
// * Type names:
//   's' for string
//   'i' for int
//   'f' for float64
//   'n' for number return types -- e.g. the auto-overflow from
//       int to float plus_n_ii returns MT_INT if integer-addition overflow
//       didn't happen, or MT_FLOAT if it did.
//   'b' for boolean
//   'x' for don't-care slots, e.g. eq_b_sx for comparing MT_STRING
//       ('s') to anything else ('x').

package bifs

import (
	"fmt"

	"github.com/johnkerl/miller/v6/pkg/lib"
	"github.com/johnkerl/miller/v6/pkg/mlrval"
	"github.com/johnkerl/miller/v6/pkg/types"
)

// Function-pointer type for zary functions.
type ZaryFunc func() *mlrval.Mlrval

// Function-pointer type for unary-operator disposition vectors.
type UnaryFunc func(input1 *mlrval.Mlrval) *mlrval.Mlrval

// The asserting_{type} need access to the context to say things like 'Assertion ... failed
// at filename {FILENAME} record number {NR}'.
type UnaryFuncWithContext func(input1 *mlrval.Mlrval, context *types.Context) *mlrval.Mlrval

// Returns nil, or one-up captures array as array slots 1..9 of 10-element
// array for "\1".."\9".
type RegexCaptureBinaryFunc func(input *mlrval.Mlrval, sregex *mlrval.Mlrval) (*mlrval.Mlrval, []string)

// Helps keystroke-saving for wrapping Go math-library functions
// Examples: cos, sin, etc.
type mathLibUnaryFunc func(float64) float64
type mathLibUnaryFuncWrapper func(input1 *mlrval.Mlrval, f mathLibUnaryFunc, fname string) *mlrval.Mlrval

// Function-pointer type for binary-operator disposition matrices.
type BinaryFunc func(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval

// Function-pointer type for ternary functions
type TernaryFunc func(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval

// Function-pointer type for variadic functions.
type VariadicFunc func(inputs []*mlrval.Mlrval) *mlrval.Mlrval

// Function-pointer type for sorting. Returns < 0 if a < b, 0 if a == b, > 0 if a > b.
type ComparatorFunc func(*mlrval.Mlrval, *mlrval.Mlrval) int

// The following are frequently used in disposition matrices for various
// operators and are defined here for re-use. The names are VERY short,
// and all the same length, so that the disposition matrices will look
// reasonable rectangular even after gofmt has been run.

// Return absent (unary)
func _absn1(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.ABSENT
}

// Return zero (unary)
func _zero1(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.FromInt(0)
}

// Return null (unary)
func _null1(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.NULL
}

// Return void (unary)
func _void1(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.VOID
}

// Return argument (unary)
func _1u___(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	return input1
}

// Return absent (binary)
func _absn(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.ABSENT
}

// Return null (binary)
func _null(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.NULL
}

// Return void (binary)
func _void(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.VOID
}

// Return first argument (binary)
func _1___(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return input1
}

// Return second argument (binary)
func _2___(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return input2
}

// Return negative second argument (binary)
func _n2__(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return BIF_minus_unary(input2)
}

// Return first argument, as string (binary)
func _s1__(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.FromString(input1.String())
}

// Return second argument, as string (binary)
func _s2__(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.FromString(input2.String())
}

// Return integer zero (binary)
func _i0__(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.FromInt(0)
}

// Return float zero (binary)
func _f0__(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.FromFloat(0)
}

// Return boolean true (binary)
func _true(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.TRUE
}

// Return boolean false (binary)
func _fals(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.FALSE
}

// For the disposition-matrix entries behind the spaceship operator
func _less(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.FromInt(-1)
}

// For the disposition-matrix entries behind the spaceship operator
func _same(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.FromInt(0)
}

// For the disposition-matrix entries behind the spaceship operator
func _more(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.FromInt(1)
}

// recurseUnaryFuncOnInput1 is for BIF_latin1_to_utf8 and other functions which apply themselves
// recursively on array/map inputs.
func recurseUnaryFuncOnInput1(unaryFunc UnaryFunc, input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsArray() {
		inputArray := input1.GetArray()
		lib.InternalCodingErrorIf(inputArray == nil)
		outputArray := make([]*mlrval.Mlrval, len(inputArray))
		for i := range inputArray {
			outputArray[i] = unaryFunc(inputArray[i])
		}
		return mlrval.FromArray(outputArray)
	} else if input1.IsMap() {
		inputMap := input1.GetMap()
		lib.InternalCodingErrorIf(inputMap == nil)
		outputMap := mlrval.NewMlrmap()
		for pe := inputMap.Head; pe != nil; pe = pe.Next {
			outputMap.PutReference(pe.Key, unaryFunc(pe.Value))
		}
		return mlrval.FromMap(outputMap)
	}
	return unaryFunc(input1)
}

// recurseBinaryFuncOnInput1 is for fmtifnum and other functions which apply themselves recursively
// on array/map inputs.
func recurseBinaryFuncOnInput1(binaryFunc BinaryFunc, input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsArray() {
		inputArray := input1.GetArray()
		lib.InternalCodingErrorIf(inputArray == nil)
		outputArray := make([]*mlrval.Mlrval, len(inputArray))
		for i := range inputArray {
			outputArray[i] = binaryFunc(inputArray[i], input2)
		}
		return mlrval.FromArray(outputArray)
	} else if input1.IsMap() {
		inputMap := input1.GetMap()
		lib.InternalCodingErrorIf(inputMap == nil)
		outputMap := mlrval.NewMlrmap()
		for pe := inputMap.Head; pe != nil; pe = pe.Next {
			outputMap.PutReference(pe.Key, binaryFunc(pe.Value, input2))
		}
		return mlrval.FromMap(outputMap)
	}
	return binaryFunc(input1, input2)
}

func type_error_named_argument(
	funcname string,
	expectedTypeName string,
	varname string,
	varval *mlrval.Mlrval,
) *mlrval.Mlrval {
	return mlrval.FromError(
		fmt.Errorf(
			"%s: %s should be a %s; got type %s with value %s",
			funcname,
			varname,
			expectedTypeName,
			varval.GetTypeName(),
			varval.StringMaybeQuoted(),
		),
	)
}
