// Copyright 2021 CUE Authors
//
// 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.

package pbinternal

import (
	"strings"
	"unicode"
	"unicode/utf8"

	"cuelang.org/go/cue"
)

type CompositeType int

const (
	Normal CompositeType = iota
	List
	Map
)

type ValueType int

const (
	Unknown ValueType = iota
	Message
	Int
	Float
	String
	Bytes
	Bool
)

type Info struct {
	Name    string
	CUEName string
	Attr    cue.Attribute
	Value   cue.Value

	CompositeType CompositeType
	ValueType     ValueType
	Type          string

	IsEnum bool

	// For maps only
	KeyType       ValueType // only for maps
	KeyTypeString string
}

func FromIter(i *cue.Iterator) (info Info, err error) {
	return FromValue(i.Selector().Unquoted(), i.Value())
}

func FromValue(name string, v cue.Value) (info Info, err error) {
	a := v.Attribute("protobuf")

	info.Name = name
	info.CUEName = name

	if a.Err() == nil {
		info.Attr = a

		s, ok, err := a.Lookup(1, "name")
		if err != nil {
			return info, err
		}
		if ok {
			info.Name = strings.TrimSpace(s)
		}

		info.Type, err = a.String(1)
		if err != nil {
			return info, err
		}
	}

	switch v.IncompleteKind() {
	case cue.ListKind:
		info.CompositeType = List
		e, _ := v.Elem()
		if e.Exists() {
			v = e
		} else {
			for i, _ := v.List(); i.Next(); {
				v = i.Value()
				break
			}
		}

	case cue.StructKind:
		if strings.HasPrefix(info.Type, "map[") {
			a := strings.SplitN(info.Type[len("map["):], "]", 2)
			info.KeyTypeString = strings.TrimSpace(a[0])
			switch info.KeyTypeString {
			case "string":
				info.KeyType = String
			case "bytes":
				info.KeyType = Bytes
			case "double", "float":
				info.KeyType = Float
			case "bool":
				info.KeyType = Bool
			default:
				info.KeyType = Int // Assuming
			}
			info.CompositeType = Map
			v, _ = v.Elem()
		}
	}

	info.Value = v

	switch v.IncompleteKind() {
	case cue.StructKind:
		info.ValueType = Message

	case cue.StringKind:
		info.ValueType = String

	case cue.BytesKind:
		info.ValueType = Bytes

	case cue.BoolKind:
		info.ValueType = Bool

	case cue.IntKind:
		info.ValueType = Int
		r, _ := utf8.DecodeRuneInString(info.Type)
		info.IsEnum = unicode.In(r, unicode.Upper)

	case cue.FloatKind, cue.NumberKind:
		info.ValueType = Float
	}

	return info, nil
}
