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

package arrow

import (
	
	
	

	
)

type Metadata struct {
	keys   []string
	values []string
}

func (,  []string) Metadata {
	if len() != len() {
		panic("arrow: len mismatch")
	}

	 := len()
	if  == 0 {
		return Metadata{}
	}

	 := Metadata{
		keys:   make([]string, ),
		values: make([]string, ),
	}
	copy(.keys, )
	copy(.values, )
	return 
}

func ( map[string]string) Metadata {
	 := Metadata{
		keys:   make([]string, 0, len()),
		values: make([]string, 0, len()),
	}
	for  := range  {
		.keys = append(.keys, )
	}
	sort.Strings(.keys)
	for ,  := range .keys {
		.values = append(.values, [])
	}
	return 
}

func ( Metadata) () int         { return len(.keys) }
func ( Metadata) () []string   { return .keys }
func ( Metadata) () []string { return .values }
func ( Metadata) () map[string]string {
	 := make(map[string]string, len(.keys))
	for  := range .keys {
		[.keys[]] = .values[]
	}
	return 
}

func ( Metadata) () string {
	 := new(strings.Builder)
	fmt.Fprintf(, "[")
	for  := range .keys {
		if  > 0 {
			fmt.Fprintf(, ", ")
		}
		fmt.Fprintf(, "%q: %q", .keys[], .values[])
	}
	fmt.Fprintf(, "]")
	return .String()
}

// FindKey returns the index of the key-value pair with the provided key name,
// or -1 if such a key does not exist.
func ( Metadata) ( string) int {
	for ,  := range .keys {
		if  ==  {
			return 
		}
	}
	return -1
}

// GetValue returns the value associated with the provided key name.
// If the key does not exist, the second return value is false.
func ( Metadata) ( string) (string, bool) {
	 := .FindKey()
	if  < 0 {
		return "", false
	}
	return .values[], true
}

func ( Metadata) () Metadata {
	if len(.keys) == 0 {
		return Metadata{}
	}

	 := Metadata{
		keys:   make([]string, len(.keys)),
		values: make([]string, len(.values)),
	}
	copy(.keys, .keys)
	copy(.values, .values)

	return 
}

func ( Metadata) () []int {
	 := make([]int, len(.keys))
	for  := range  {
		[] = 
	}

	sort.Slice(, func(,  int) bool {
		return .keys[[]] < .keys[[]]
	})
	return 
}

func ( Metadata) ( Metadata) bool {
	if .Len() != .Len() {
		return false
	}

	 := .sortedIndices()
	 := .sortedIndices()
	for  := range  {
		 := []
		 := []
		if .keys[] != .keys[] || .values[] != .values[] {
			return false
		}
	}
	return true
}

// Schema is a sequence of Field values, describing the columns of a table or
// a record batch.
type Schema struct {
	fields     []Field
	index      map[string][]int
	meta       Metadata
	endianness endian.Endianness
}

// NewSchema returns a new Schema value from the slice of fields and metadata.
//
// NewSchema panics if there is a field with an invalid DataType.
func ( []Field,  *Metadata) *Schema {
	return NewSchemaWithEndian(, , endian.NativeEndian)
}

func ( []Field,  *Metadata,  endian.Endianness) *Schema {
	 := &Schema{
		fields:     make([]Field, 0, len()),
		index:      make(map[string][]int, len()),
		endianness: ,
	}
	if  != nil {
		.meta = .clone()
	}
	for ,  := range  {
		if .Type == nil {
			panic("arrow: field with nil DataType")
		}
		.fields = append(.fields, )
		.index[.Name] = append(.index[.Name], )
	}
	return 
}

func ( *Schema) ( endian.Endianness) *Schema {
	return NewSchemaWithEndian(.fields, &.meta, )
}

func ( *Schema) () endian.Endianness { return .endianness }
func ( *Schema) () bool          { return .endianness == endian.NativeEndian }
func ( *Schema) () Metadata            { return .meta }
func ( *Schema) () []Field {
	 := make([]Field, len(.fields))
	copy(, .fields)
	return 
}
func ( *Schema) ( int) Field { return .fields[] }
func ( *Schema) () int    { return len(.fields) }

func ( *Schema) ( string) ([]Field, bool) {
	,  := .index[]
	if ! {
		return nil, 
	}
	 := make([]Field, 0, len())
	for ,  := range  {
		 = append(, .fields[])
	}
	return , 
}

// FieldIndices returns the indices of the named field or nil.
func ( *Schema) ( string) []int {
	return .index[]
}

func ( *Schema) ( string) bool { return len(.FieldIndices()) > 0 }
func ( *Schema) () bool      { return len(.meta.keys) > 0 }

// Equal returns whether two schema are equal.
// Equal does not compare the metadata.
func ( *Schema) ( *Schema) bool {
	switch {
	case  == :
		return true
	case  == nil ||  == nil:
		return false
	case len(.fields) != len(.fields):
		return false
	case .endianness != .endianness:
		return false
	}

	for  := range .fields {
		if !.fields[].Equal(.fields[]) {
			return false
		}
	}
	return true
}

// AddField adds a field at the given index and return a new schema.
func ( *Schema) ( int,  Field) (*Schema, error) {
	if  < 0 ||  > len(.fields) {
		return nil, fmt.Errorf("arrow: invalid field index %d", )
	}

	 := make([]Field, len(.fields)+1)
	copy([:], .fields[:])
	[] = 
	copy([+1:], .fields[:])
	return NewSchema(, &.meta), nil
}

func ( *Schema) () string {
	 := new(strings.Builder)
	fmt.Fprintf(, "schema:\n  fields: %d\n", .NumFields())
	for ,  := range .fields {
		if  > 0 {
			.WriteString("\n")
		}
		fmt.Fprintf(, "    - %v", )
	}
	if .endianness != endian.NativeEndian {
		fmt.Fprintf(, "\n  endianness: %v", .endianness)
	}
	if  := .Metadata(); .Len() > 0 {
		fmt.Fprintf(, "\n  metadata: %v", )
	}
	return .String()
}

func ( *Schema) () string {
	if  == nil {
		return ""
	}

	var  strings.Builder
	.WriteString("S{")
	for ,  := range .fields {
		 := .Fingerprint()
		if  == "" {
			return ""
		}

		.WriteString()
		.WriteByte(';')
	}
	if .endianness == endian.LittleEndian {
		.WriteByte('L')
	} else {
		.WriteByte('B')
	}
	.WriteByte('}')
	return .String()
}