// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build go1.18

package compute

import (
	
	
	

	
	
)

type Function interface {
	Name() string
	Kind() FuncKind
	Arity() Arity
	Doc() FunctionDoc
	NumKernels() int
	Execute(context.Context, FunctionOptions, ...Datum) (Datum, error)
	DispatchExact(...arrow.DataType) (exec.Kernel, error)
	DispatchBest(...arrow.DataType) (exec.Kernel, error)
	DefaultOptions() FunctionOptions
	Validate() error
}

// Arity defines the number of required arguments for a function.
//
// Naming conventions are taken from https://en.wikipedia.org/wiki/Arity
type Arity struct {
	NArgs     int
	IsVarArgs bool
}

// Convenience functions to generating Arities

func () Arity            { return Arity{0, false} }
func () Arity              { return Arity{1, false} }
func () Arity             { return Arity{2, false} }
func () Arity            { return Arity{3, false} }
func ( int) Arity { return Arity{, true} }

type FunctionDoc struct {
	// A one-line summary of the function, using a verb.
	//
	// For example, "Add two numeric arrays or scalars"
	Summary string
	// A detailed description of the function, meant to follow the summary.
	Description string
	// Symbolic names (identifiers) for the function arguments.
	//
	// Can be used to generate nicer function signatures.
	ArgNames []string
	// Name of the options struct type, if any
	OptionsType string
	// Whether or not options are required for function execution.
	//
	// If false, then either there are no options for this function,
	// or there is a usable default options value.
	OptionsRequired bool
}

// EmptyFuncDoc is a reusable empty function doc definition for convenience.
var EmptyFuncDoc FunctionDoc

// FuncKind is an enum representing the type of a function
type FuncKind int8

const (
	// A function that performs scalar data operations on whole arrays
	// of data. Can generally process Array or Scalar values. The size
	// of the output will be the same as the size (or broadcasted size,
	// in the case of mixing Array and Scalar inputs) of the input.
	FuncScalar FuncKind = iota // Scalar
	// A function with array input and output whose behavior depends on
	// the values of the entire arrays passed, rather than the value of
	// each scalar value.
	FuncVector // Vector
	// A function that computes a scalar summary statistic from array input.
	FuncScalarAgg // ScalarAggregate
	// A function that computes grouped summary statistics from array
	// input and an array of group identifiers.
	FuncHashAgg // HashAggregate
	// A function that dispatches to other functions and does not contain
	// its own kernels.
	FuncMeta // Meta
)

func validateFunctionSummary( string) error {
	if strings.Contains(, "\n") {
		return fmt.Errorf("%w: summary contains a newline", arrow.ErrInvalid)
	}
	if [len()-1] == '.' {
		return fmt.Errorf("%w: summary ends with a point", arrow.ErrInvalid)
	}
	return nil
}

func validateFunctionDescription( string) error {
	if len() != 0 && [len()-1] == '\n' {
		return fmt.Errorf("%w: description ends with a newline", arrow.ErrInvalid)
	}

	const  = 78
	for ,  := range strings.Split(, "\n") {
		if len() >  {
			return fmt.Errorf("%w: description line length exceeds %d characters", arrow.ErrInvalid, )
		}
	}
	return nil
}

// baseFunction is the base class for compute functions. Function
// implementations should embed this baseFunction and will contain
// a collection of "kernels" which are implementations of the function
// for specific argument types. Selecting a viable kernel for
// executing the function is referred to as "dispatching".
type baseFunction struct {
	name        string
	kind        FuncKind
	arity       Arity
	doc         FunctionDoc
	defaultOpts FunctionOptions
}

func ( *baseFunction) () string                    { return .name }
func ( *baseFunction) () FuncKind                  { return .kind }
func ( *baseFunction) () Arity                    { return .arity }
func ( *baseFunction) () FunctionDoc                { return .doc }
func ( *baseFunction) () FunctionOptions { return .defaultOpts }
func ( *baseFunction) () error {
	if .doc.Summary == "" {
		return nil
	}

	 := len(.doc.ArgNames)
	if  != .arity.NArgs && (!.arity.IsVarArgs ||  != .arity.NArgs+1) {
		return fmt.Errorf("in function '%s': number of argument names for function doc != function arity", .name)
	}

	if  := validateFunctionSummary(.doc.Summary);  != nil {
		return 
	}
	return validateFunctionDescription(.doc.Description)
}

func checkOptions( Function,  FunctionOptions) error {
	if  == nil && .Doc().OptionsRequired {
		return fmt.Errorf("%w: function '%s' cannot be called without options", arrow.ErrInvalid, .Name())
	}
	return nil
}

func ( *baseFunction) ( int) error {
	switch {
	case .arity.IsVarArgs &&  < .arity.NArgs:
		return fmt.Errorf("%w: varargs function '%s' needs at least %d arguments, but only %d passed",
			arrow.ErrInvalid, .name, .arity.NArgs, )
	case !.arity.IsVarArgs &&  != .arity.NArgs:
		return fmt.Errorf("%w: function '%s' accepts %d arguments but %d passed",
			arrow.ErrInvalid, .name, .arity.NArgs, )
	}
	return nil
}

// kernelType is a type constraint interface that is used for funcImpl
// generic definitions. It will be extended as other kernel types
// are defined.
//
// Currently only ScalarKernels are allowed to be used.
type kernelType interface {
	exec.ScalarKernel | exec.VectorKernel

	// specifying the Kernel interface here allows us to utilize
	// the methods of the Kernel interface on the generic
	// constrained type
	exec.Kernel
}

// funcImpl is the basic implementation for any functions that use kernels
// i.e. all except for Meta functions.
type funcImpl[ kernelType] struct {
	baseFunction

	kernels []
}

func ( *funcImpl[]) ( ...arrow.DataType) (*, error) {
	if  := .checkArity(len());  != nil {
		return nil, 
	}

	for  := range .kernels {
		if .kernels[].GetSig().MatchesInputs() {
			return &.kernels[], nil
		}
	}

	return nil, fmt.Errorf("%w: function '%s' has no kernel matching input types %s",
		arrow.ErrNotImplemented, .name, arrow.TypesToString())
}

func ( *funcImpl[]) () int { return len(.kernels) }
func ( *funcImpl[]) () []* {
	 := make([]*, len(.kernels))
	for  := range .kernels {
		[] = &.kernels[]
	}
	return 
}

// A ScalarFunction is a function that executes element-wise operations
// on arrays or scalars, and therefore whose results generally do not
// depend on the order of the values in the arguments. Accepts and returns
// arrays that are all of the same size. These functions roughly correspond
// to the functions used in most SQL expressions.
type ScalarFunction struct {
	funcImpl[exec.ScalarKernel]
}

// NewScalarFunction constructs a new ScalarFunction object with the passed in
// name, arity and function doc.
func ( string,  Arity,  FunctionDoc) *ScalarFunction {
	return &ScalarFunction{
		funcImpl: funcImpl[exec.ScalarKernel]{
			baseFunction: baseFunction{
				name:  ,
				arity: ,
				doc:   ,
				kind:  FuncScalar,
			},
		},
	}
}

func ( *ScalarFunction) ( FunctionOptions) {
	.defaultOpts = 
}

func ( *ScalarFunction) ( ...arrow.DataType) (exec.Kernel, error) {
	return .funcImpl.DispatchExact(...)
}

func ( *ScalarFunction) ( ...arrow.DataType) (exec.Kernel, error) {
	return .DispatchExact(...)
}

// AddNewKernel constructs a new kernel with the provided signature
// and execution/init functions and then adds it to the function's list of
// kernels. This assumes default null handling (intersection of validity bitmaps)
func ( *ScalarFunction) ( []exec.InputType,  exec.OutputType,  exec.ArrayKernelExec,  exec.KernelInitFn) error {
	if  := .checkArity(len());  != nil {
		return 
	}

	if .arity.IsVarArgs && len() != 1 {
		return fmt.Errorf("%w: varargs signatures must have exactly one input type", arrow.ErrInvalid)
	}

	 := &exec.KernelSignature{
		InputTypes: ,
		OutType:    ,
		IsVarArgs:  .arity.IsVarArgs,
	}

	.kernels = append(.kernels, exec.NewScalarKernelWithSig(, , ))
	return nil
}

// AddKernel adds the provided kernel to the list of kernels
// this function has. A copy of the kernel is added to the slice of kernels,
// which means that a given kernel object can be created, added and then
// reused to add other kernels.
func ( *ScalarFunction) ( exec.ScalarKernel) error {
	if  := .checkArity(len(.Signature.InputTypes));  != nil {
		return 
	}

	if .arity.IsVarArgs && !.Signature.IsVarArgs {
		return fmt.Errorf("%w: function accepts varargs but kernel signature does not", arrow.ErrInvalid)
	}

	.kernels = append(.kernels, )
	return nil
}

// Execute uses the passed in context, function options and arguments to eagerly
// execute the function using kernel dispatch, batch iteration and memory
// allocation details as defined by the kernel.
//
// If opts is nil, then the DefaultOptions() will be used.
func ( *ScalarFunction) ( context.Context,  FunctionOptions,  ...Datum) (Datum, error) {
	return execInternal(, , , -1, ...)
}

type VectorFunction struct {
	funcImpl[exec.VectorKernel]
}

func ( string,  Arity,  FunctionDoc) *VectorFunction {
	return &VectorFunction{
		funcImpl: funcImpl[exec.VectorKernel]{
			baseFunction: baseFunction{
				name:  ,
				arity: ,
				doc:   ,
				kind:  FuncVector,
			},
		},
	}
}

func ( *VectorFunction) ( FunctionOptions) {
	.defaultOpts = 
}

func ( *VectorFunction) ( ...arrow.DataType) (exec.Kernel, error) {
	return .funcImpl.DispatchExact(...)
}

func ( *VectorFunction) ( ...arrow.DataType) (exec.Kernel, error) {
	return .DispatchExact(...)
}

func ( *VectorFunction) ( []exec.InputType,  exec.OutputType,  exec.ArrayKernelExec,  exec.KernelInitFn) error {
	if  := .checkArity(len());  != nil {
		return 
	}

	if .arity.IsVarArgs && len() != 1 {
		return fmt.Errorf("%w: varags signatures must have exactly one input type", arrow.ErrInvalid)
	}

	 := &exec.KernelSignature{
		InputTypes: ,
		OutType:    ,
		IsVarArgs:  .arity.IsVarArgs,
	}
	.kernels = append(.kernels, exec.NewVectorKernelWithSig(, , ))
	return nil
}

func ( *VectorFunction) ( exec.VectorKernel) error {
	if  := .checkArity(len(.Signature.InputTypes));  != nil {
		return 
	}

	if .arity.IsVarArgs && !.Signature.IsVarArgs {
		return fmt.Errorf("%w: function accepts varargs but kernel signature does not", arrow.ErrInvalid)
	}
	.kernels = append(.kernels, )
	return nil
}

func ( *VectorFunction) ( context.Context,  FunctionOptions,  ...Datum) (Datum, error) {
	return execInternal(, , , -1, ...)
}

// MetaFunctionImpl is the signature needed for implementing a MetaFunction
// which is a function that dispatches to another function instead.
type MetaFunctionImpl func(context.Context, FunctionOptions, ...Datum) (Datum, error)

// MetaFunction is a function which dispatches to other functions, the impl
// must not be nil.
//
// For Array, ChunkedArray and Scalar datums, this may rely on the execution
// of concrete function types, but this must handle other Datum kinds on its
// own.
type MetaFunction struct {
	baseFunction
	impl MetaFunctionImpl
}

// NewMetaFunction constructs a new MetaFunction which will call the provided
// impl for dispatching with the expected arity.
//
// Will panic if impl is nil.
func ( string,  Arity,  FunctionDoc,  MetaFunctionImpl) *MetaFunction {
	if  == nil {
		panic("arrow/compute: cannot construct MetaFunction with nil impl")
	}
	return &MetaFunction{
		baseFunction: baseFunction{
			name:  ,
			arity: ,
			doc:   ,
		},
		impl: ,
	}
}

func (MetaFunction) () int { return 0 }
func ( *MetaFunction) (...arrow.DataType) (exec.Kernel, error) {
	return nil, fmt.Errorf("%w: dispatch for metafunction", arrow.ErrNotImplemented)
}

func ( *MetaFunction) (...arrow.DataType) (exec.Kernel, error) {
	return nil, fmt.Errorf("%w: dispatch for metafunction", arrow.ErrNotImplemented)
}

func ( *MetaFunction) ( context.Context,  FunctionOptions,  ...Datum) (Datum, error) {
	if  := .checkArity(len());  != nil {
		return nil, 
	}
	if  := checkOptions(, );  != nil {
		return nil, 
	}

	if  == nil {
		 = .defaultOpts
	}

	return .impl(, , ...)
}