// 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 (
	

	
	
	
)

//go:generate go run golang.org/x/tools/cmd/stringer -type=DatumKind -linecomment

// DatumKind is an enum used for denoting which kind of type a datum is encapsulating
type DatumKind int

const (
	KindNone    DatumKind = iota // none
	KindScalar                   // scalar
	KindArray                    // array
	KindChunked                  // chunked_array
	KindRecord                   // record_batch
	KindTable                    // table
)

const UnknownLength int64 = -1

// DatumIsValue returns true if the datum passed is a Scalar, Array
// or ChunkedArray type (e.g. it contains a specific value not a
// group of values)
func ( Datum) bool {
	switch .Kind() {
	case KindScalar, KindArray, KindChunked:
		return true
	}
	return false
}

// Datum is a variant interface for wrapping the various Arrow data structures
// for now the various Datum types just hold a Value which is the type they
// are wrapping, but it might make sense in the future for those types
// to actually be aliases or embed their types instead. Not sure yet.
type Datum interface {
	fmt.Stringer
	Kind() DatumKind
	Len() int64
	Equals(Datum) bool
	Release()

	data() any
}

// ArrayLikeDatum is an interface for treating a Datum similarly to an Array,
// so that it is easy to differentiate between Record/Table/Collection and Scalar,
// Array/ChunkedArray for ease of use. Chunks will return an empty slice for Scalar,
// a slice with 1 element for Array, and the slice of chunks for a chunked array.
type ArrayLikeDatum interface {
	Datum
	NullN() int64
	Type() arrow.DataType
	Chunks() []arrow.Array
}

// TableLikeDatum is an interface type for specifying either a RecordBatch or a
// Table as both contain a schema as opposed to just a single data type.
type TableLikeDatum interface {
	Datum
	Schema() *arrow.Schema
}

// EmptyDatum is the null case, a Datum with nothing in it.
type EmptyDatum struct{}

func (EmptyDatum) () string  { return "nullptr" }
func (EmptyDatum) () DatumKind { return KindNone }
func (EmptyDatum) () int64      { return UnknownLength }
func (EmptyDatum) ()        {}
func (EmptyDatum) ( Datum) bool {
	,  := .(EmptyDatum)
	return 
}
func (EmptyDatum) () any { return nil }

// ScalarDatum contains a scalar value
type ScalarDatum struct {
	Value scalar.Scalar
}

func (ScalarDatum) () DatumKind         { return KindScalar }
func (ScalarDatum) () int64              { return 1 }
func (ScalarDatum) () []arrow.Array   { return nil }
func ( *ScalarDatum) () arrow.DataType { return .Value.DataType() }
func ( *ScalarDatum) () string       { return .Value.String() }
func ( *ScalarDatum) () (scalar.Scalar, error) {
	return .Value, nil
}
func ( *ScalarDatum) () any { return .Value }
func ( *ScalarDatum) () int64 {
	if .Value.IsValid() {
		return 0
	}
	return 1
}

type releasable interface {
	Release()
}

func ( *ScalarDatum) () {
	if ,  := .Value.(releasable);  {
		.Release()
	}
}

func ( *ScalarDatum) ( Datum) bool {
	if ,  := .(*ScalarDatum);  {
		return scalar.Equals(.Value, .Value)
	}
	return false
}

// ArrayDatum references an array.Data object which can be used to create
// array instances from if needed.
type ArrayDatum struct {
	Value arrow.ArrayData
}

func (ArrayDatum) () DatumKind           { return KindArray }
func ( *ArrayDatum) () arrow.DataType   { return .Value.DataType() }
func ( *ArrayDatum) () int64             { return int64(.Value.Len()) }
func ( *ArrayDatum) () int64           { return int64(.Value.NullN()) }
func ( *ArrayDatum) () string         { return fmt.Sprintf("Array:{%s}", .Value.DataType()) }
func ( *ArrayDatum) () arrow.Array { return array.MakeFromData(.Value) }
func ( *ArrayDatum) () []arrow.Array  { return []arrow.Array{.MakeArray()} }
func ( *ArrayDatum) () (scalar.Scalar, error) {
	return scalar.NewListScalarData(.Value), nil
}
func ( *ArrayDatum) () {
	.Value.Release()
	.Value = nil
}
func ( *ArrayDatum) () any { return .Value }
func ( *ArrayDatum) ( Datum) bool {
	,  := .(*ArrayDatum)
	if ! {
		return false
	}

	 := .MakeArray()
	defer .Release()
	 := .MakeArray()
	defer .Release()

	return array.Equal(, )
}

// ChunkedDatum contains a chunked array for use with expressions and compute.
type ChunkedDatum struct {
	Value *arrow.Chunked
}

func (ChunkedDatum) () DatumKind          { return KindChunked }
func ( *ChunkedDatum) () arrow.DataType  { return .Value.DataType() }
func ( *ChunkedDatum) () int64            { return int64(.Value.Len()) }
func ( *ChunkedDatum) () int64          { return int64(.Value.NullN()) }
func ( *ChunkedDatum) () string        { return fmt.Sprintf("Array:{%s}", .Value.DataType()) }
func ( *ChunkedDatum) () []arrow.Array { return .Value.Chunks() }
func ( *ChunkedDatum) () any             { return .Value }
func ( *ChunkedDatum) () {
	.Value.Release()
	.Value = nil
}

func ( *ChunkedDatum) ( Datum) bool {
	if ,  := .(*ChunkedDatum);  {
		return array.ChunkedEqual(.Value, .Value)
	}
	return false
}

// RecordDatum contains an array.Record for passing a full record to an expression
// or to compute.
type RecordDatum struct {
	Value arrow.RecordBatch
}

func (RecordDatum) () DatumKind          { return KindRecord }
func (RecordDatum) () string           { return "RecordBatch" }
func ( *RecordDatum) () int64            { return .Value.NumRows() }
func ( *RecordDatum) () *arrow.Schema { return .Value.Schema() }
func ( *RecordDatum) () any             { return .Value }
func ( *RecordDatum) () {
	.Value.Release()
	.Value = nil
}

func ( *RecordDatum) ( Datum) bool {
	if ,  := .(*RecordDatum);  {
		return array.RecordEqual(.Value, .Value)
	}
	return false
}

// TableDatum contains a table so that multiple record batches can be worked with
// together as a single table for being passed to compute and expression handling.
type TableDatum struct {
	Value arrow.Table
}

func (TableDatum) () DatumKind          { return KindTable }
func (TableDatum) () string           { return "Table" }
func ( *TableDatum) () int64            { return .Value.NumRows() }
func ( *TableDatum) () *arrow.Schema { return .Value.Schema() }
func ( *TableDatum) () any             { return .Value }
func ( *TableDatum) () {
	.Value.Release()
	.Value = nil
}

func ( *TableDatum) ( Datum) bool {
	if ,  := .(*TableDatum);  {
		return array.TableEqual(.Value, .Value)
	}
	return false
}

// NewDatum will construct the appropriate Datum type based on what is passed in
// as the argument.
//
// An arrow.Array gets an ArrayDatum
// An array.Chunked gets a ChunkedDatum
// An array.Record gets a RecordDatum
// An array.Table gets a TableDatum
// A scalar.Scalar gets a ScalarDatum
//
// Anything else is passed to scalar.MakeScalar and receives a scalar
// datum of that appropriate type.
func ( interface{}) Datum {
	switch v := .(type) {
	case Datum:
		return (.data())
	case arrow.Array:
		.Data().Retain()
		return &ArrayDatum{.Data()}
	case scalar.Releasable:
		.Retain()
		return NewDatumWithoutOwning()
	case scalar.Scalar:
		return &ScalarDatum{}
	default:
		return &ScalarDatum{scalar.MakeScalar()}
	}
}

// NewDatumWithoutOwning is like NewDatum only it does not call Retain on
// the passed in value (if applicable). This means that if the resulting
// Datum should not have Release called on it and the original value needs
// to outlive the Datum.
//
// Only use this if you know what you're doing. For the most part this is
// just a convenience function.+-

func ( interface{}) Datum {
	switch v := .(type) {
	case arrow.Array:
		return &ArrayDatum{.Data()}
	case arrow.ArrayData:
		return &ArrayDatum{}
	case *arrow.Chunked:
		return &ChunkedDatum{}
	case arrow.RecordBatch:
		return &RecordDatum{}
	case arrow.Table:
		return &TableDatum{}
	case scalar.Scalar:
		return &ScalarDatum{}
	default:
		return &ScalarDatum{scalar.MakeScalar()}
	}
}

var (
	_ ArrayLikeDatum = (*ScalarDatum)(nil)
	_ ArrayLikeDatum = (*ArrayDatum)(nil)
	_ ArrayLikeDatum = (*ChunkedDatum)(nil)
	_ TableLikeDatum = (*RecordDatum)(nil)
	_ TableLikeDatum = (*TableDatum)(nil)
)