// Copyright 2020 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 protocmp

import (
	"reflect"
	"sort"
	"strconv"
	"strings"

	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/runtime/protoiface"
)

func reflectValueOf(v interface{}) protoreflect.Value {
	switch v := v.(type) {
	case Enum:
		return protoreflect.ValueOfEnum(v.Number())
	case Message:
		return protoreflect.ValueOfMessage(v.ProtoReflect())
	case []byte:
		return protoreflect.ValueOfBytes(v) // avoid overlap with reflect.Slice check below
	default:
		switch rv := reflect.ValueOf(v); {
		case rv.Kind() == reflect.Slice:
			return protoreflect.ValueOfList(reflectList{rv})
		case rv.Kind() == reflect.Map:
			return protoreflect.ValueOfMap(reflectMap{rv})
		default:
			return protoreflect.ValueOf(v)
		}
	}
}

type reflectMessage Message

func (m reflectMessage) stringKey(fd protoreflect.FieldDescriptor) string {
	if m.Descriptor() != fd.ContainingMessage() {
		panic("mismatching containing message")
	}
	if fd.IsExtension() {
		return string("[" + fd.FullName() + "]")
	}
	return string(fd.Name())
}

func (m reflectMessage) Descriptor() protoreflect.MessageDescriptor {
	return (Message)(m).Descriptor()
}
func (m reflectMessage) Type() protoreflect.MessageType {
	return reflectMessageType{m.Descriptor()}
}
func (m reflectMessage) New() protoreflect.Message {
	return m.Type().New()
}
func (m reflectMessage) Interface() protoreflect.ProtoMessage {
	return Message(m)
}
func (m reflectMessage) Range(f func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool) {
	// Range over populated known fields.
	fds := m.Descriptor().Fields()
	for i := 0; i < fds.Len(); i++ {
		fd := fds.Get(i)
		if m.Has(fd) && !f(fd, m.Get(fd)) {
			return
		}
	}

	// Range over populated extension fields.
	for _, xd := range m[messageTypeKey].(messageType).xds {
		if m.Has(xd) && !f(xd, m.Get(xd)) {
			return
		}
	}
}
func (m reflectMessage) Has(fd protoreflect.FieldDescriptor) bool {
	_, ok := m[m.stringKey(fd)]
	return ok
}
func (m reflectMessage) Clear(protoreflect.FieldDescriptor) {
	panic("invalid mutation of read-only message")
}
func (m reflectMessage) Get(fd protoreflect.FieldDescriptor) protoreflect.Value {
	v, ok := m[m.stringKey(fd)]
	if !ok {
		switch {
		case fd.IsList():
			return protoreflect.ValueOfList(reflectList{})
		case fd.IsMap():
			return protoreflect.ValueOfMap(reflectMap{})
		case fd.Message() != nil:
			return protoreflect.ValueOfMessage(reflectMessage{
				messageTypeKey: messageType{md: m.Descriptor()},
			})
		default:
			return fd.Default()
		}
	}

	// The transformation may leave Any messages in structured form.
	// If so, convert them back to a raw-encoded form.
	if fd.FullName() == "google.protobuf.Any.value" {
		if m, ok := v.(Message); ok {
			b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
			if err != nil {
				panic("BUG: " + err.Error())
			}
			return protoreflect.ValueOfBytes(b)
		}
	}

	return reflectValueOf(v)
}
func (m reflectMessage) Set(protoreflect.FieldDescriptor, protoreflect.Value) {
	panic("invalid mutation of read-only message")
}
func (m reflectMessage) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value {
	panic("invalid mutation of read-only message")
}
func (m reflectMessage) NewField(protoreflect.FieldDescriptor) protoreflect.Value {
	panic("not implemented")
}
func (m reflectMessage) WhichOneof(od protoreflect.OneofDescriptor) protoreflect.FieldDescriptor {
	if m.Descriptor().Oneofs().ByName(od.Name()) != od {
		panic("oneof descriptor does not belong to this message")
	}
	fds := od.Fields()
	for i := 0; i < fds.Len(); i++ {
		fd := fds.Get(i)
		if _, ok := m[m.stringKey(fd)]; ok {
			return fd
		}
	}
	return nil
}
func (m reflectMessage) GetUnknown() protoreflect.RawFields {
	var nums []protoreflect.FieldNumber
	for k := range m {
		if len(strings.Trim(k, "0123456789")) == 0 {
			n, _ := strconv.ParseUint(k, 10, 32)
			nums = append(nums, protoreflect.FieldNumber(n))
		}
	}
	sort.Slice(nums, func(i, j int) bool { return nums[i] < nums[j] })

	var raw protoreflect.RawFields
	for _, num := range nums {
		b, _ := m[strconv.FormatUint(uint64(num), 10)].(protoreflect.RawFields)
		raw = append(raw, b...)
	}
	return raw
}
func (m reflectMessage) SetUnknown(protoreflect.RawFields) {
	panic("invalid mutation of read-only message")
}
func (m reflectMessage) IsValid() bool {
	invalid, _ := m[messageInvalidKey].(bool)
	return !invalid
}
func (m reflectMessage) ProtoMethods() *protoiface.Methods {
	return nil
}

type reflectMessageType struct{ protoreflect.MessageDescriptor }

func (t reflectMessageType) New() protoreflect.Message {
	panic("not implemented")
}
func (t reflectMessageType) Zero() protoreflect.Message {
	panic("not implemented")
}
func (t reflectMessageType) Descriptor() protoreflect.MessageDescriptor {
	return t.MessageDescriptor
}

type reflectList struct{ v reflect.Value }

func (ls reflectList) Len() int {
	if !ls.IsValid() {
		return 0
	}
	return ls.v.Len()
}
func (ls reflectList) Get(i int) protoreflect.Value {
	return reflectValueOf(ls.v.Index(i).Interface())
}
func (ls reflectList) Set(int, protoreflect.Value) {
	panic("invalid mutation of read-only list")
}
func (ls reflectList) Append(protoreflect.Value) {
	panic("invalid mutation of read-only list")
}
func (ls reflectList) AppendMutable() protoreflect.Value {
	panic("invalid mutation of read-only list")
}
func (ls reflectList) Truncate(int) {
	panic("invalid mutation of read-only list")
}
func (ls reflectList) NewElement() protoreflect.Value {
	panic("not implemented")
}
func (ls reflectList) IsValid() bool {
	return ls.v.IsValid()
}

type reflectMap struct{ v reflect.Value }

func (ms reflectMap) Len() int {
	if !ms.IsValid() {
		return 0
	}
	return ms.v.Len()
}
func (ms reflectMap) Range(f func(protoreflect.MapKey, protoreflect.Value) bool) {
	if !ms.IsValid() {
		return
	}
	ks := ms.v.MapKeys()
	for _, k := range ks {
		pk := reflectValueOf(k.Interface()).MapKey()
		pv := reflectValueOf(ms.v.MapIndex(k).Interface())
		if !f(pk, pv) {
			return
		}
	}
}
func (ms reflectMap) Has(k protoreflect.MapKey) bool {
	if !ms.IsValid() {
		return false
	}
	return ms.v.MapIndex(reflect.ValueOf(k.Interface())).IsValid()
}
func (ms reflectMap) Clear(protoreflect.MapKey) {
	panic("invalid mutation of read-only list")
}
func (ms reflectMap) Get(k protoreflect.MapKey) protoreflect.Value {
	if !ms.IsValid() {
		return protoreflect.Value{}
	}
	v := ms.v.MapIndex(reflect.ValueOf(k.Interface()))
	if !v.IsValid() {
		return protoreflect.Value{}
	}
	return reflectValueOf(v.Interface())
}
func (ms reflectMap) Set(protoreflect.MapKey, protoreflect.Value) {
	panic("invalid mutation of read-only list")
}
func (ms reflectMap) Mutable(k protoreflect.MapKey) protoreflect.Value {
	panic("invalid mutation of read-only list")
}
func (ms reflectMap) NewValue() protoreflect.Value {
	panic("not implemented")
}
func (ms reflectMap) IsValid() bool {
	return ms.v.IsValid()
}
