// Copyright 2019 Dolthub, Inc.
//
// Licensed 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.
//
// This file incorporates work covered by the following copyright and
// permission notice:
//
// Copyright 2016 Attic Labs, Inc. All rights reserved.
// Licensed under the Apache License, version 2.0:
// http://www.apache.org/licenses/LICENSE-2.0

package types

import (
	"github.com/dolthub/dolt/go/gen/fb/serial"
)

// NomsKind allows a TypeDesc to indicate what kind of type is described.
type NomsKind uint8

// All supported kinds of Noms types are enumerated here.
// The ordering of these (especially Bool, Float and String) is important for ordering of values.
const (
	BoolKind NomsKind = iota
	FloatKind
	StringKind
	BlobKind
	ValueKind
	ListKind
	MapKind
	RefKind
	SetKind

	// Keep StructKind and CycleKind together.
	StructKind
	CycleKind

	TypeKind
	UnionKind

	// Internal to decoder
	hashKind

	UUIDKind
	IntKind
	UintKind
	NullKind
	TupleKind
	InlineBlobKind
	TimestampKind
	DecimalKind
	JSONKind
	GeometryKind
	PointKind
	LineStringKind
	PolygonKind

	SerialMessageKind

	MultiPointKind
	MultiLineStringKind
	MultiPolygonKind
	GeometryCollectionKind

	UnknownKind NomsKind = 255
)

var KindToType = make([]Value, 255)
var SupportedKinds = make([]bool, 255)

func init() {
	KindToType[BlobKind] = Blob{}
	KindToType[BoolKind] = Bool(false)
	KindToType[ListKind] = List{}
	KindToType[MapKind] = Map{}
	KindToType[FloatKind] = Float(0)
	KindToType[RefKind] = Ref{}
	KindToType[SetKind] = Set{}
	KindToType[StructKind] = Struct{}
	KindToType[StringKind] = String("")
	KindToType[TypeKind] = &Type{}
	KindToType[UUIDKind] = UUID{}
	KindToType[IntKind] = Int(0)
	KindToType[UintKind] = Uint(0)
	KindToType[NullKind] = NullValue
	KindToType[TupleKind] = Tuple{}
	KindToType[InlineBlobKind] = InlineBlob{}
	KindToType[TimestampKind] = Timestamp{}
	KindToType[DecimalKind] = Decimal{}
	KindToType[JSONKind] = JSON{}
	KindToType[GeometryKind] = Geometry{}
	KindToType[PointKind] = Point{}
	KindToType[LineStringKind] = LineString{}
	KindToType[PolygonKind] = Polygon{}
	KindToType[SerialMessageKind] = SerialMessage{}
	KindToType[MultiPointKind] = MultiPoint{}
	KindToType[MultiLineStringKind] = MultiLineString{}
	KindToType[MultiPolygonKind] = MultiPolygon{}
	KindToType[GeometryCollectionKind] = GeomColl{}

	SupportedKinds[BlobKind] = true
	SupportedKinds[BoolKind] = true
	SupportedKinds[CycleKind] = true
	SupportedKinds[ListKind] = true
	SupportedKinds[MapKind] = true
	SupportedKinds[FloatKind] = true
	SupportedKinds[RefKind] = true
	SupportedKinds[SetKind] = true
	SupportedKinds[StructKind] = true
	SupportedKinds[StringKind] = true
	SupportedKinds[TypeKind] = true
	SupportedKinds[UnionKind] = true
	SupportedKinds[ValueKind] = true
	SupportedKinds[UUIDKind] = true
	SupportedKinds[IntKind] = true
	SupportedKinds[UintKind] = true
	SupportedKinds[NullKind] = true
	SupportedKinds[TupleKind] = true
	SupportedKinds[InlineBlobKind] = true
	SupportedKinds[TimestampKind] = true
	SupportedKinds[DecimalKind] = true
	SupportedKinds[JSONKind] = true
	SupportedKinds[GeometryKind] = true
	SupportedKinds[PointKind] = true
	SupportedKinds[LineStringKind] = true
	SupportedKinds[PolygonKind] = true
	SupportedKinds[SerialMessageKind] = true
	SupportedKinds[MultiPointKind] = true
	SupportedKinds[MultiLineStringKind] = true
	SupportedKinds[MultiPolygonKind] = true
	SupportedKinds[GeometryCollectionKind] = true

	if serial.MessageTypesKind != int(SerialMessageKind) {
		panic("internal error: serial.MessageTypesKind != SerialMessageKind")
	}
}

var KindToTypeSlice []Value

var KindToString = map[NomsKind]string{
	UnknownKind:            "unknown",
	BlobKind:               "Blob",
	BoolKind:               "Bool",
	CycleKind:              "Cycle",
	ListKind:               "List",
	MapKind:                "Map",
	FloatKind:              "Float",
	RefKind:                "Ref",
	SetKind:                "Set",
	StructKind:             "Struct",
	StringKind:             "String",
	TypeKind:               "Type",
	UnionKind:              "Union",
	ValueKind:              "Value",
	UUIDKind:               "UUID",
	IntKind:                "Int",
	UintKind:               "Uint",
	NullKind:               "Null",
	TupleKind:              "Tuple",
	InlineBlobKind:         "InlineBlob",
	TimestampKind:          "Timestamp",
	DecimalKind:            "Decimal",
	JSONKind:               "JSON",
	GeometryKind:           "Geometry",
	PointKind:              "Point",
	LineStringKind:         "LineString",
	PolygonKind:            "Polygon",
	SerialMessageKind:      "SerialMessage",
	MultiPointKind:         "MultiPoint",
	MultiLineStringKind:    "MultiLineString",
	MultiPolygonKind:       "MultiPolygon",
	GeometryCollectionKind: "GeometryCollection",
}

// String returns the name of the kind.
func (k NomsKind) String() string {
	return KindToString[k]
}

// IsPrimitiveKind returns true if k represents a Noms primitive type, which excludes collections (List, Map, Set), Refs, Structs, Symbolic and Unresolved types.
func IsPrimitiveKind(k NomsKind) bool {
	i := int(k)
	return i < len(PrimitiveKindMask) && PrimitiveKindMask[i]
}

// isKindOrderedByValue determines if a value is ordered by its value instead of its hash.
func isKindOrderedByValue(k NomsKind) bool {
	return k <= StringKind || k >= UUIDKind
}

func IsGeometryKind(k NomsKind) bool {
	switch k {
	case PointKind,
		LineStringKind,
		PolygonKind,
		MultiPointKind,
		MultiLineStringKind,
		MultiPolygonKind,
		GeometryKind,
		GeometryCollectionKind:
		return true
	default:
		return false
	}
}

func (k NomsKind) writeTo(w nomsWriter, nbf *NomsBinFormat) error {
	if k == UnknownKind {
		return ErrUnknownType
	}

	w.writeUint8(uint8(k))
	return nil
}
