// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package text implements the text format for protocol buffers.
// This package has no semantic understanding for protocol buffers and is only
// a parser and composer for the format.
//
// There is no formal specification for the protobuf text format, as such the
// C++ implementation (see google::protobuf::TextFormat) is the reference
// implementation of the text format.
//
// This package is neither a superset nor a subset of the C++ implementation.
// This implementation permits a more liberal grammar in some cases to be
// backwards compatible with the historical Go implementation.
// Future parsings unique to Go should not be added.
// Some grammars allowed by the C++ implementation are deliberately
// not implemented here because they are considered a bug by the protobuf team
// and should not be replicated.
//
// The Go implementation should implement a sufficient amount of the C++
// grammar such that the default text serialization by C++ can be parsed by Go.
// However, just because the C++ parser accepts some input does not mean that
// the Go implementation should as well.
//
// The text format is almost a superset of JSON except:
//	* message keys are not quoted strings, but identifiers
//	* the top-level value must be a message without the delimiters
package text

import (
	"fmt"
	"math"
	"strings"

	"google.golang.org/protobuf/internal/flags"
	"google.golang.org/protobuf/reflect/protoreflect"
)

// Type represents a type expressible in the text format.
type Type uint8

const (
	_ Type = iota

	// Bool is a boolean (e.g., "true" or "false").
	Bool
	// Int is a signed integer (e.g., "-1423").
	Int
	// Uint is an unsigned integer (e.g., "0xdeadbeef").
	Uint
	// Float32 is a 32-bit floating-point number (e.g., "1.234" or "1e38").
	// This allows encoding to differentiate the bitsize used for formatting.
	Float32
	// Float64 is a 64-bit floating-point number.
	Float64
	// String is a quoted string (e.g., `"the quick brown fox"`).
	String
	// Name is a protocol buffer identifier (e.g., `field_name`).
	Name
	// List is an ordered list of values (e.g., `[0, "one", true]`).
	List
	// Message is an ordered map of values (e.g., `{"key": null}`).
	Message
)

func (t Type) String() string {
	switch t {
	case Bool:
		return "bool"
	case Int:
		return "int"
	case Uint:
		return "uint"
	case Float32:
		return "float32"
	case Float64:
		return "float64"
	case String:
		return "string"
	case Name:
		return "name"
	case List:
		return "list"
	case Message:
		return "message"
	default:
		return "<invalid>"
	}
}

// Value contains a value of a given Type.
type Value struct {
	typ Type
	raw []byte     // raw bytes of the serialized data
	str string     // only for String or Name
	num uint64     // only for Bool, Int, Uint, Float32, or Float64
	arr []Value    // only for List
	obj [][2]Value // only for Message
}

// ValueOf returns a Value for a given Go value:
//	bool               =>  Bool
//	int32, int64       =>  Int
//	uint32, uint64     =>  Uint
//	float32            =>  Float32
//	float64            =>  Float64
//	string, []byte     =>  String
//	protoreflect.Name  =>  Name
//	[]Value            =>  List
//	[][2]Value         =>  Message
//
// ValueOf panics if the Go type is not one of the above.
func ValueOf(v interface{}) Value {
	switch v := v.(type) {
	case bool:
		if v {
			return Value{typ: Bool, num: 1}
		} else {
			return Value{typ: Bool, num: 0}
		}
	case int32:
		return Value{typ: Int, num: uint64(v)}
	case int64:
		return Value{typ: Int, num: uint64(v)}
	case uint32:
		return Value{typ: Uint, num: uint64(v)}
	case uint64:
		return Value{typ: Uint, num: uint64(v)}
	case float32:
		// Store as float64 bits.
		return Value{typ: Float32, num: math.Float64bits(float64(v))}
	case float64:
		return Value{typ: Float64, num: math.Float64bits(float64(v))}
	case string:
		return Value{typ: String, str: string(v)}
	case []byte:
		return Value{typ: String, str: string(v)}
	case protoreflect.Name:
		return Value{typ: Name, str: string(v)}
	case []Value:
		return Value{typ: List, arr: v}
	case [][2]Value:
		return Value{typ: Message, obj: v}
	default:
		panic(fmt.Sprintf("invalid type %T", v))
	}
}
func rawValueOf(v interface{}, raw []byte) Value {
	v2 := ValueOf(v)
	v2.raw = raw
	return v2
}

// Type is the type of the value. When parsing, this is a best-effort guess
// at the resulting type. However, there are ambiguities as to the exact type
// of the value (e.g., "false" is either a bool or a name).
// Thus, some of the types are convertible with each other.
// The Bool, Int, Uint, Float32, Float64, and Name methods return a boolean to
// report whether the conversion was successful.
func (v Value) Type() Type {
	return v.typ
}

// Bool returns v as a bool and reports whether the conversion succeeded.
func (v Value) Bool() (x bool, ok bool) {
	switch v.typ {
	case Bool:
		return v.num > 0, true
	case Uint, Int:
		// C++ allows a 1-bit unsigned integer (e.g., "0", "1", or "0x1").
		if len(v.raw) > 0 && v.raw[0] != '-' && v.num < 2 {
			return v.num > 0, true
		}
	}
	return false, false
}

// Int returns v as an int64 of the specified precision and reports whether
// the conversion succeeded.
func (v Value) Int(b64 bool) (x int64, ok bool) {
	switch v.typ {
	case Int:
		n := int64(v.num)
		if b64 || (math.MinInt32 <= n && n <= math.MaxInt32) {
			return int64(n), true
		}
	case Uint:
		n := uint64(v.num)
		if (!b64 && n <= math.MaxInt32) || (b64 && n <= math.MaxInt64) {
			return int64(n), true
		}
		// C++ accepts large positive hex numbers as negative values.
		// This feature is here for proto1 backwards compatibility purposes.
		if flags.Proto1Legacy && len(v.raw) > 1 && v.raw[0] == '0' && v.raw[1] == 'x' {
			if !b64 {
				return int64(int32(n)), n <= math.MaxUint32
			}
			// if !b64 && n <= math.MaxUint32 {
			// 	return int64(int32(n)), true
			// }
			return int64(n), true
		}
	}
	return 0, false
}

// Uint returns v as an uint64 of the specified precision and reports whether
// the conversion succeeded.
func (v Value) Uint(b64 bool) (x uint64, ok bool) {
	switch v.typ {
	case Int:
		n := int64(v.num)
		if len(v.raw) > 0 && v.raw[0] != '-' && (b64 || n <= math.MaxUint32) {
			return uint64(n), true
		}
	case Uint:
		n := uint64(v.num)
		if b64 || n <= math.MaxUint32 {
			return uint64(n), true
		}
	}
	return 0, false
}

// Float returns v as a float64 of the specified precision and reports whether
// the conversion succeeded.
func (v Value) Float(b64 bool) (x float64, ok bool) {
	switch v.typ {
	case Int:
		return float64(int64(v.num)), true // possibly lossy, but allowed
	case Uint:
		return float64(uint64(v.num)), true // possibly lossy, but allowed
	case Float32, Float64:
		n := math.Float64frombits(v.num)
		if math.IsNaN(n) || math.IsInf(n, 0) {
			return float64(n), true
		}
		if b64 || math.Abs(n) <= math.MaxFloat32 {
			return float64(n), true
		}
	}
	return 0, false
}

// String returns v as a string if the Type is String.
// Otherwise, this returns a formatted string of v for debugging purposes.
//
// Since String is used to represent both text and binary, it is not validated
// to contain valid UTF-8. When using this value with the string type in proto,
// it is the user's responsibility perform additional UTF-8 validation.
func (v Value) String() string {
	if v.typ != String {
		return v.stringValue()
	}
	return v.str
}
func (v Value) stringValue() string {
	switch v.typ {
	case Bool, Int, Uint, Float32, Float64, Name:
		return string(v.Raw())
	case List:
		var ss []string
		for _, v := range v.List() {
			ss = append(ss, v.String())
		}
		return "[" + strings.Join(ss, ",") + "]"
	case Message:
		var ss []string
		for _, v := range v.Message() {
			k := v[0].String()
			if v[0].Type() == String {
				k = "[" + k + "]"
			}
			ss = append(ss, k+":"+v[1].String())
		}
		return "{" + strings.Join(ss, ",") + "}"
	default:
		return "<invalid>"
	}
}

// Name returns the field name or enum value name and reports whether the value
// can be treated as an identifier.
func (v Value) Name() (protoreflect.Name, bool) {
	switch v.typ {
	case Bool, Float32, Float64:
		// Ambiguity arises in unmarshalValue since "nan" may interpreted as
		// either a Name type (for enum values) or a Float32/Float64 type.
		// Similarly, "true" may be interpreted as either a Name or Bool type.
		n := protoreflect.Name(v.raw)
		if n.IsValid() {
			return n, true
		}
	case Name:
		return protoreflect.Name(v.str), true
	}
	return "", false
}

// List returns the elements of v and panics if the Type is not List.
// Mutations on the return value may not be observable from the Raw method.
func (v Value) List() []Value {
	if v.typ != List {
		panic("value is not a list")
	}
	return v.arr
}

// Message returns the items of v and panics if the Type is not Message.
// The [2]Value represents a key and value pair, where the key is either
// a Name (representing a field name), a String (representing extension field
// names or the Any type URL), or an Uint for unknown fields.
//
// Mutations on the return value may not be observable from the Raw method.
func (v Value) Message() [][2]Value {
	if v.typ != Message {
		panic("value is not a message")
	}
	return v.obj
}

// Raw returns the raw representation of the value.
// The returned value may alias the input given to Unmarshal.
func (v Value) Raw() []byte {
	if len(v.raw) > 0 {
		return v.raw
	}
	p := encoder{}
	if err := p.marshalValue(v); err != nil {
		return []byte("<invalid>")
	}
	return p.out
}
