package protoparse

import (
	"bytes"
	"fmt"
	"math"

	"github.com/golang/protobuf/proto"
	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"

	"github.com/jhump/protoreflect/desc"
	"github.com/jhump/protoreflect/desc/internal"
	"github.com/jhump/protoreflect/desc/protoparse/ast"
	"github.com/jhump/protoreflect/dynamic"
)

// NB: To process options, we need descriptors, but we may not have rich
// descriptors when trying to interpret options for unlinked parsed files.
// So we define minimal interfaces that can be backed by both rich descriptors
// as well as their poorer cousins, plain ol' descriptor protos.

type descriptorish interface {
	GetFile() fileDescriptorish
	GetFullyQualifiedName() string
	AsProto() proto.Message
}

type fileDescriptorish interface {
	descriptorish
	GetFileOptions() *dpb.FileOptions
	GetPackage() string
	FindSymbol(name string) desc.Descriptor
	GetPublicDependencies() []fileDescriptorish
	GetDependencies() []fileDescriptorish
	GetMessageTypes() []msgDescriptorish
	GetExtensions() []fldDescriptorish
	GetEnumTypes() []enumDescriptorish
	GetServices() []svcDescriptorish
}

type msgDescriptorish interface {
	descriptorish
	GetMessageOptions() *dpb.MessageOptions
	GetFields() []fldDescriptorish
	GetOneOfs() []oneofDescriptorish
	GetExtensionRanges() []extRangeDescriptorish
	GetNestedMessageTypes() []msgDescriptorish
	GetNestedExtensions() []fldDescriptorish
	GetNestedEnumTypes() []enumDescriptorish
}

type fldDescriptorish interface {
	descriptorish
	GetFieldOptions() *dpb.FieldOptions
	GetMessageType() *desc.MessageDescriptor
	GetEnumType() *desc.EnumDescriptor
	AsFieldDescriptorProto() *dpb.FieldDescriptorProto
}

type oneofDescriptorish interface {
	descriptorish
	GetOneOfOptions() *dpb.OneofOptions
}

type enumDescriptorish interface {
	descriptorish
	GetEnumOptions() *dpb.EnumOptions
	GetValues() []enumValDescriptorish
}

type enumValDescriptorish interface {
	descriptorish
	GetEnumValueOptions() *dpb.EnumValueOptions
}

type svcDescriptorish interface {
	descriptorish
	GetServiceOptions() *dpb.ServiceOptions
	GetMethods() []methodDescriptorish
}

type methodDescriptorish interface {
	descriptorish
	GetMethodOptions() *dpb.MethodOptions
}

// The hierarchy of descriptorish implementations backed by
// rich descriptors:

type richFileDescriptorish struct {
	*desc.FileDescriptor
}

func (d richFileDescriptorish) GetFile() fileDescriptorish {
	return d
}

func (d richFileDescriptorish) GetPublicDependencies() []fileDescriptorish {
	deps := d.FileDescriptor.GetPublicDependencies()
	ret := make([]fileDescriptorish, len(deps))
	for i, d := range deps {
		ret[i] = richFileDescriptorish{FileDescriptor: d}
	}
	return ret
}

func (d richFileDescriptorish) GetDependencies() []fileDescriptorish {
	deps := d.FileDescriptor.GetDependencies()
	ret := make([]fileDescriptorish, len(deps))
	for i, d := range deps {
		ret[i] = richFileDescriptorish{FileDescriptor: d}
	}
	return ret
}

func (d richFileDescriptorish) GetMessageTypes() []msgDescriptorish {
	msgs := d.FileDescriptor.GetMessageTypes()
	ret := make([]msgDescriptorish, len(msgs))
	for i, m := range msgs {
		ret[i] = richMsgDescriptorish{MessageDescriptor: m}
	}
	return ret
}

func (d richFileDescriptorish) GetExtensions() []fldDescriptorish {
	flds := d.FileDescriptor.GetExtensions()
	ret := make([]fldDescriptorish, len(flds))
	for i, f := range flds {
		ret[i] = richFldDescriptorish{FieldDescriptor: f}
	}
	return ret
}

func (d richFileDescriptorish) GetEnumTypes() []enumDescriptorish {
	ens := d.FileDescriptor.GetEnumTypes()
	ret := make([]enumDescriptorish, len(ens))
	for i, en := range ens {
		ret[i] = richEnumDescriptorish{EnumDescriptor: en}
	}
	return ret
}

func (d richFileDescriptorish) GetServices() []svcDescriptorish {
	svcs := d.FileDescriptor.GetServices()
	ret := make([]svcDescriptorish, len(svcs))
	for i, s := range svcs {
		ret[i] = richSvcDescriptorish{ServiceDescriptor: s}
	}
	return ret
}

type richMsgDescriptorish struct {
	*desc.MessageDescriptor
}

func (d richMsgDescriptorish) GetFile() fileDescriptorish {
	return richFileDescriptorish{FileDescriptor: d.MessageDescriptor.GetFile()}
}

func (d richMsgDescriptorish) GetFields() []fldDescriptorish {
	flds := d.MessageDescriptor.GetFields()
	ret := make([]fldDescriptorish, len(flds))
	for i, f := range flds {
		ret[i] = richFldDescriptorish{FieldDescriptor: f}
	}
	return ret
}

func (d richMsgDescriptorish) GetOneOfs() []oneofDescriptorish {
	oos := d.MessageDescriptor.GetOneOfs()
	ret := make([]oneofDescriptorish, len(oos))
	for i, oo := range oos {
		ret[i] = richOneOfDescriptorish{OneOfDescriptor: oo}
	}
	return ret
}

func (d richMsgDescriptorish) GetExtensionRanges() []extRangeDescriptorish {
	md := d.MessageDescriptor
	mdFqn := md.GetFullyQualifiedName()
	extrs := md.AsDescriptorProto().GetExtensionRange()
	ret := make([]extRangeDescriptorish, len(extrs))
	for i, extr := range extrs {
		ret[i] = extRangeDescriptorish{
			er:   extr,
			qual: mdFqn,
			file: richFileDescriptorish{FileDescriptor: md.GetFile()},
		}
	}
	return ret
}

func (d richMsgDescriptorish) GetNestedMessageTypes() []msgDescriptorish {
	msgs := d.MessageDescriptor.GetNestedMessageTypes()
	ret := make([]msgDescriptorish, len(msgs))
	for i, m := range msgs {
		ret[i] = richMsgDescriptorish{MessageDescriptor: m}
	}
	return ret
}

func (d richMsgDescriptorish) GetNestedExtensions() []fldDescriptorish {
	flds := d.MessageDescriptor.GetNestedExtensions()
	ret := make([]fldDescriptorish, len(flds))
	for i, f := range flds {
		ret[i] = richFldDescriptorish{FieldDescriptor: f}
	}
	return ret
}

func (d richMsgDescriptorish) GetNestedEnumTypes() []enumDescriptorish {
	ens := d.MessageDescriptor.GetNestedEnumTypes()
	ret := make([]enumDescriptorish, len(ens))
	for i, en := range ens {
		ret[i] = richEnumDescriptorish{EnumDescriptor: en}
	}
	return ret
}

type richFldDescriptorish struct {
	*desc.FieldDescriptor
}

func (d richFldDescriptorish) GetFile() fileDescriptorish {
	return richFileDescriptorish{FileDescriptor: d.FieldDescriptor.GetFile()}
}

func (d richFldDescriptorish) AsFieldDescriptorProto() *dpb.FieldDescriptorProto {
	return d.FieldDescriptor.AsFieldDescriptorProto()
}

type richOneOfDescriptorish struct {
	*desc.OneOfDescriptor
}

func (d richOneOfDescriptorish) GetFile() fileDescriptorish {
	return richFileDescriptorish{FileDescriptor: d.OneOfDescriptor.GetFile()}
}

type richEnumDescriptorish struct {
	*desc.EnumDescriptor
}

func (d richEnumDescriptorish) GetFile() fileDescriptorish {
	return richFileDescriptorish{FileDescriptor: d.EnumDescriptor.GetFile()}
}

func (d richEnumDescriptorish) GetValues() []enumValDescriptorish {
	vals := d.EnumDescriptor.GetValues()
	ret := make([]enumValDescriptorish, len(vals))
	for i, val := range vals {
		ret[i] = richEnumValDescriptorish{EnumValueDescriptor: val}
	}
	return ret
}

type richEnumValDescriptorish struct {
	*desc.EnumValueDescriptor
}

func (d richEnumValDescriptorish) GetFile() fileDescriptorish {
	return richFileDescriptorish{FileDescriptor: d.EnumValueDescriptor.GetFile()}
}

type richSvcDescriptorish struct {
	*desc.ServiceDescriptor
}

func (d richSvcDescriptorish) GetFile() fileDescriptorish {
	return richFileDescriptorish{FileDescriptor: d.ServiceDescriptor.GetFile()}
}

func (d richSvcDescriptorish) GetMethods() []methodDescriptorish {
	mtds := d.ServiceDescriptor.GetMethods()
	ret := make([]methodDescriptorish, len(mtds))
	for i, mtd := range mtds {
		ret[i] = richMethodDescriptorish{MethodDescriptor: mtd}
	}
	return ret
}

type richMethodDescriptorish struct {
	*desc.MethodDescriptor
}

func (d richMethodDescriptorish) GetFile() fileDescriptorish {
	return richFileDescriptorish{FileDescriptor: d.MethodDescriptor.GetFile()}
}

// The hierarchy of descriptorish implementations backed by
// plain descriptor protos:

type poorFileDescriptorish struct {
	*dpb.FileDescriptorProto
}

func (d poorFileDescriptorish) GetFile() fileDescriptorish {
	return d
}

func (d poorFileDescriptorish) GetFullyQualifiedName() string {
	return d.FileDescriptorProto.GetName()
}

func (d poorFileDescriptorish) AsProto() proto.Message {
	return d.FileDescriptorProto
}

func (d poorFileDescriptorish) GetFileOptions() *dpb.FileOptions {
	return d.FileDescriptorProto.GetOptions()
}

func (d poorFileDescriptorish) FindSymbol(name string) desc.Descriptor {
	return nil
}

func (d poorFileDescriptorish) GetPublicDependencies() []fileDescriptorish {
	return nil
}

func (d poorFileDescriptorish) GetDependencies() []fileDescriptorish {
	return nil
}

func (d poorFileDescriptorish) GetMessageTypes() []msgDescriptorish {
	msgs := d.FileDescriptorProto.GetMessageType()
	pkg := d.FileDescriptorProto.GetPackage()
	ret := make([]msgDescriptorish, len(msgs))
	for i, m := range msgs {
		ret[i] = poorMsgDescriptorish{
			DescriptorProto: m,
			qual:            pkg,
			file:            d,
		}
	}
	return ret
}

func (d poorFileDescriptorish) GetExtensions() []fldDescriptorish {
	exts := d.FileDescriptorProto.GetExtension()
	pkg := d.FileDescriptorProto.GetPackage()
	ret := make([]fldDescriptorish, len(exts))
	for i, e := range exts {
		ret[i] = poorFldDescriptorish{
			FieldDescriptorProto: e,
			qual:                 pkg,
			file:                 d,
		}
	}
	return ret
}

func (d poorFileDescriptorish) GetEnumTypes() []enumDescriptorish {
	ens := d.FileDescriptorProto.GetEnumType()
	pkg := d.FileDescriptorProto.GetPackage()
	ret := make([]enumDescriptorish, len(ens))
	for i, e := range ens {
		ret[i] = poorEnumDescriptorish{
			EnumDescriptorProto: e,
			qual:                pkg,
			file:                d,
		}
	}
	return ret
}

func (d poorFileDescriptorish) GetServices() []svcDescriptorish {
	svcs := d.FileDescriptorProto.GetService()
	pkg := d.FileDescriptorProto.GetPackage()
	ret := make([]svcDescriptorish, len(svcs))
	for i, s := range svcs {
		ret[i] = poorSvcDescriptorish{
			ServiceDescriptorProto: s,
			qual:                   pkg,
			file:                   d,
		}
	}
	return ret
}

type poorMsgDescriptorish struct {
	*dpb.DescriptorProto
	qual string
	file fileDescriptorish
}

func (d poorMsgDescriptorish) GetFile() fileDescriptorish {
	return d.file
}

func (d poorMsgDescriptorish) GetFullyQualifiedName() string {
	return qualify(d.qual, d.DescriptorProto.GetName())
}

func qualify(qual, name string) string {
	if qual == "" {
		return name
	} else {
		return fmt.Sprintf("%s.%s", qual, name)
	}
}

func (d poorMsgDescriptorish) AsProto() proto.Message {
	return d.DescriptorProto
}

func (d poorMsgDescriptorish) GetMessageOptions() *dpb.MessageOptions {
	return d.DescriptorProto.GetOptions()
}

func (d poorMsgDescriptorish) GetFields() []fldDescriptorish {
	flds := d.DescriptorProto.GetField()
	ret := make([]fldDescriptorish, len(flds))
	for i, f := range flds {
		ret[i] = poorFldDescriptorish{
			FieldDescriptorProto: f,
			qual:                 d.GetFullyQualifiedName(),
			file:                 d.file,
		}
	}
	return ret
}

func (d poorMsgDescriptorish) GetOneOfs() []oneofDescriptorish {
	oos := d.DescriptorProto.GetOneofDecl()
	ret := make([]oneofDescriptorish, len(oos))
	for i, oo := range oos {
		ret[i] = poorOneOfDescriptorish{
			OneofDescriptorProto: oo,
			qual:                 d.GetFullyQualifiedName(),
			file:                 d.file,
		}
	}
	return ret
}

func (d poorMsgDescriptorish) GetExtensionRanges() []extRangeDescriptorish {
	mdFqn := d.GetFullyQualifiedName()
	extrs := d.DescriptorProto.GetExtensionRange()
	ret := make([]extRangeDescriptorish, len(extrs))
	for i, extr := range extrs {
		ret[i] = extRangeDescriptorish{
			er:   extr,
			qual: mdFqn,
			file: d.file,
		}
	}
	return ret
}

func (d poorMsgDescriptorish) GetNestedMessageTypes() []msgDescriptorish {
	msgs := d.DescriptorProto.GetNestedType()
	ret := make([]msgDescriptorish, len(msgs))
	for i, m := range msgs {
		ret[i] = poorMsgDescriptorish{
			DescriptorProto: m,
			qual:            d.GetFullyQualifiedName(),
			file:            d.file,
		}
	}
	return ret
}

func (d poorMsgDescriptorish) GetNestedExtensions() []fldDescriptorish {
	flds := d.DescriptorProto.GetExtension()
	ret := make([]fldDescriptorish, len(flds))
	for i, f := range flds {
		ret[i] = poorFldDescriptorish{
			FieldDescriptorProto: f,
			qual:                 d.GetFullyQualifiedName(),
			file:                 d.file,
		}
	}
	return ret
}

func (d poorMsgDescriptorish) GetNestedEnumTypes() []enumDescriptorish {
	ens := d.DescriptorProto.GetEnumType()
	ret := make([]enumDescriptorish, len(ens))
	for i, en := range ens {
		ret[i] = poorEnumDescriptorish{
			EnumDescriptorProto: en,
			qual:                d.GetFullyQualifiedName(),
			file:                d.file,
		}
	}
	return ret
}

type poorFldDescriptorish struct {
	*dpb.FieldDescriptorProto
	qual string
	file fileDescriptorish
}

func (d poorFldDescriptorish) GetFile() fileDescriptorish {
	return d.file
}

func (d poorFldDescriptorish) GetFullyQualifiedName() string {
	return qualify(d.qual, d.FieldDescriptorProto.GetName())
}

func (d poorFldDescriptorish) AsProto() proto.Message {
	return d.FieldDescriptorProto
}

func (d poorFldDescriptorish) GetFieldOptions() *dpb.FieldOptions {
	return d.FieldDescriptorProto.GetOptions()
}

func (d poorFldDescriptorish) GetMessageType() *desc.MessageDescriptor {
	return nil
}

func (d poorFldDescriptorish) GetEnumType() *desc.EnumDescriptor {
	return nil
}

type poorOneOfDescriptorish struct {
	*dpb.OneofDescriptorProto
	qual string
	file fileDescriptorish
}

func (d poorOneOfDescriptorish) GetFile() fileDescriptorish {
	return d.file
}

func (d poorOneOfDescriptorish) GetFullyQualifiedName() string {
	return qualify(d.qual, d.OneofDescriptorProto.GetName())
}

func (d poorOneOfDescriptorish) AsProto() proto.Message {
	return d.OneofDescriptorProto
}

func (d poorOneOfDescriptorish) GetOneOfOptions() *dpb.OneofOptions {
	return d.OneofDescriptorProto.GetOptions()
}

func (d poorFldDescriptorish) AsFieldDescriptorProto() *dpb.FieldDescriptorProto {
	return d.FieldDescriptorProto
}

type poorEnumDescriptorish struct {
	*dpb.EnumDescriptorProto
	qual string
	file fileDescriptorish
}

func (d poorEnumDescriptorish) GetFile() fileDescriptorish {
	return d.file
}

func (d poorEnumDescriptorish) GetFullyQualifiedName() string {
	return qualify(d.qual, d.EnumDescriptorProto.GetName())
}

func (d poorEnumDescriptorish) AsProto() proto.Message {
	return d.EnumDescriptorProto
}

func (d poorEnumDescriptorish) GetEnumOptions() *dpb.EnumOptions {
	return d.EnumDescriptorProto.GetOptions()
}

func (d poorEnumDescriptorish) GetValues() []enumValDescriptorish {
	vals := d.EnumDescriptorProto.GetValue()
	ret := make([]enumValDescriptorish, len(vals))
	for i, v := range vals {
		ret[i] = poorEnumValDescriptorish{
			EnumValueDescriptorProto: v,
			qual:                     d.GetFullyQualifiedName(),
			file:                     d.file,
		}
	}
	return ret
}

type poorEnumValDescriptorish struct {
	*dpb.EnumValueDescriptorProto
	qual string
	file fileDescriptorish
}

func (d poorEnumValDescriptorish) GetFile() fileDescriptorish {
	return d.file
}

func (d poorEnumValDescriptorish) GetFullyQualifiedName() string {
	return qualify(d.qual, d.EnumValueDescriptorProto.GetName())
}

func (d poorEnumValDescriptorish) AsProto() proto.Message {
	return d.EnumValueDescriptorProto
}

func (d poorEnumValDescriptorish) GetEnumValueOptions() *dpb.EnumValueOptions {
	return d.EnumValueDescriptorProto.GetOptions()
}

type poorSvcDescriptorish struct {
	*dpb.ServiceDescriptorProto
	qual string
	file fileDescriptorish
}

func (d poorSvcDescriptorish) GetFile() fileDescriptorish {
	return d.file
}

func (d poorSvcDescriptorish) GetFullyQualifiedName() string {
	return qualify(d.qual, d.ServiceDescriptorProto.GetName())
}

func (d poorSvcDescriptorish) AsProto() proto.Message {
	return d.ServiceDescriptorProto
}

func (d poorSvcDescriptorish) GetServiceOptions() *dpb.ServiceOptions {
	return d.ServiceDescriptorProto.GetOptions()
}

func (d poorSvcDescriptorish) GetMethods() []methodDescriptorish {
	mtds := d.ServiceDescriptorProto.GetMethod()
	ret := make([]methodDescriptorish, len(mtds))
	for i, m := range mtds {
		ret[i] = poorMethodDescriptorish{
			MethodDescriptorProto: m,
			qual:                  d.GetFullyQualifiedName(),
			file:                  d.file,
		}
	}
	return ret
}

type poorMethodDescriptorish struct {
	*dpb.MethodDescriptorProto
	qual string
	file fileDescriptorish
}

func (d poorMethodDescriptorish) GetFile() fileDescriptorish {
	return d.file
}

func (d poorMethodDescriptorish) GetFullyQualifiedName() string {
	return qualify(d.qual, d.MethodDescriptorProto.GetName())
}

func (d poorMethodDescriptorish) AsProto() proto.Message {
	return d.MethodDescriptorProto
}

func (d poorMethodDescriptorish) GetMethodOptions() *dpb.MethodOptions {
	return d.MethodDescriptorProto.GetOptions()
}

type extRangeDescriptorish struct {
	er   *dpb.DescriptorProto_ExtensionRange
	qual string
	file fileDescriptorish
}

func (er extRangeDescriptorish) GetFile() fileDescriptorish {
	return er.file
}

func (er extRangeDescriptorish) GetFullyQualifiedName() string {
	return qualify(er.qual, fmt.Sprintf("%d-%d", er.er.GetStart(), er.er.GetEnd()-1))
}

func (er extRangeDescriptorish) AsProto() proto.Message {
	return er.er
}

func (er extRangeDescriptorish) GetExtensionRangeOptions() *dpb.ExtensionRangeOptions {
	return er.er.GetOptions()
}

func interpretFileOptions(r *parseResult, fd fileDescriptorish) error {
	opts := fd.GetFileOptions()
	if opts != nil {
		if len(opts.UninterpretedOption) > 0 {
			if remain, err := interpretOptions(r, fd, opts, opts.UninterpretedOption); err != nil {
				return err
			} else {
				opts.UninterpretedOption = remain
			}
		}
	}
	for _, md := range fd.GetMessageTypes() {
		if err := interpretMessageOptions(r, md); err != nil {
			return err
		}
	}
	for _, fld := range fd.GetExtensions() {
		if err := interpretFieldOptions(r, fld); err != nil {
			return err
		}
	}
	for _, ed := range fd.GetEnumTypes() {
		if err := interpretEnumOptions(r, ed); err != nil {
			return err
		}
	}
	for _, sd := range fd.GetServices() {
		opts := sd.GetServiceOptions()
		if len(opts.GetUninterpretedOption()) > 0 {
			if remain, err := interpretOptions(r, sd, opts, opts.UninterpretedOption); err != nil {
				return err
			} else {
				opts.UninterpretedOption = remain
			}
		}
		for _, mtd := range sd.GetMethods() {
			opts := mtd.GetMethodOptions()
			if len(opts.GetUninterpretedOption()) > 0 {
				if remain, err := interpretOptions(r, mtd, opts, opts.UninterpretedOption); err != nil {
					return err
				} else {
					opts.UninterpretedOption = remain
				}
			}
		}
	}
	return nil
}

func interpretMessageOptions(r *parseResult, md msgDescriptorish) error {
	opts := md.GetMessageOptions()
	if opts != nil {
		if len(opts.UninterpretedOption) > 0 {
			if remain, err := interpretOptions(r, md, opts, opts.UninterpretedOption); err != nil {
				return err
			} else {
				opts.UninterpretedOption = remain
			}
		}
	}
	for _, fld := range md.GetFields() {
		if err := interpretFieldOptions(r, fld); err != nil {
			return err
		}
	}
	for _, ood := range md.GetOneOfs() {
		opts := ood.GetOneOfOptions()
		if len(opts.GetUninterpretedOption()) > 0 {
			if remain, err := interpretOptions(r, ood, opts, opts.UninterpretedOption); err != nil {
				return err
			} else {
				opts.UninterpretedOption = remain
			}
		}
	}
	for _, fld := range md.GetNestedExtensions() {
		if err := interpretFieldOptions(r, fld); err != nil {
			return err
		}
	}
	for _, er := range md.GetExtensionRanges() {
		opts := er.GetExtensionRangeOptions()
		if len(opts.GetUninterpretedOption()) > 0 {
			if remain, err := interpretOptions(r, er, opts, opts.UninterpretedOption); err != nil {
				return err
			} else {
				opts.UninterpretedOption = remain
			}
		}
	}
	for _, nmd := range md.GetNestedMessageTypes() {
		if err := interpretMessageOptions(r, nmd); err != nil {
			return err
		}
	}
	for _, ed := range md.GetNestedEnumTypes() {
		if err := interpretEnumOptions(r, ed); err != nil {
			return err
		}
	}
	return nil
}

func interpretFieldOptions(r *parseResult, fld fldDescriptorish) error {
	opts := fld.GetFieldOptions()
	if len(opts.GetUninterpretedOption()) > 0 {
		uo := opts.UninterpretedOption
		scope := fmt.Sprintf("field %s", fld.GetFullyQualifiedName())

		// process json_name pseudo-option
		if index, err := findOption(r, scope, uo, "json_name"); err != nil && !r.lenient {
			return err
		} else if index >= 0 {
			opt := uo[index]
			optNode := r.getOptionNode(opt)

			// attribute source code info
			if on, ok := optNode.(*ast.OptionNode); ok {
				r.interpretedOptions[on] = []int32{-1, internal.Field_jsonNameTag}
			}
			uo = removeOption(uo, index)
			if opt.StringValue == nil {
				if err := r.errs.handleErrorWithPos(optNode.GetValue().Start(), "%s: expecting string value for json_name option", scope); err != nil {
					return err
				}
			} else {
				fld.AsFieldDescriptorProto().JsonName = proto.String(string(opt.StringValue))
			}
		}

		// and process default pseudo-option
		if index, err := processDefaultOption(r, scope, fld, uo); err != nil && !r.lenient {
			return err
		} else if index >= 0 {
			// attribute source code info
			optNode := r.getOptionNode(uo[index])
			if on, ok := optNode.(*ast.OptionNode); ok {
				r.interpretedOptions[on] = []int32{-1, internal.Field_defaultTag}
			}
			uo = removeOption(uo, index)
		}

		if len(uo) == 0 {
			// no real options, only pseudo-options above? clear out options
			fld.AsFieldDescriptorProto().Options = nil
		} else if remain, err := interpretOptions(r, fld, opts, uo); err != nil {
			return err
		} else {
			opts.UninterpretedOption = remain
		}
	}
	return nil
}

func processDefaultOption(res *parseResult, scope string, fld fldDescriptorish, uos []*dpb.UninterpretedOption) (defaultIndex int, err error) {
	found, err := findOption(res, scope, uos, "default")
	if err != nil || found == -1 {
		return -1, err
	}
	opt := uos[found]
	optNode := res.getOptionNode(opt)
	fdp := fld.AsFieldDescriptorProto()
	if fdp.GetLabel() == dpb.FieldDescriptorProto_LABEL_REPEATED {
		return -1, res.errs.handleErrorWithPos(optNode.GetName().Start(), "%s: default value cannot be set because field is repeated", scope)
	}
	if fdp.GetType() == dpb.FieldDescriptorProto_TYPE_GROUP || fdp.GetType() == dpb.FieldDescriptorProto_TYPE_MESSAGE {
		return -1, res.errs.handleErrorWithPos(optNode.GetName().Start(), "%s: default value cannot be set because field is a message", scope)
	}
	val := optNode.GetValue()
	if _, ok := val.(*ast.MessageLiteralNode); ok {
		return -1, res.errs.handleErrorWithPos(val.Start(), "%s: default value cannot be a message", scope)
	}
	mc := &messageContext{
		res:         res,
		file:        fld.GetFile(),
		elementName: fld.GetFullyQualifiedName(),
		elementType: descriptorType(fld.AsProto()),
		option:      opt,
	}
	v, err := fieldValue(res, mc, fld, val, true)
	if err != nil {
		return -1, res.errs.handleError(err)
	}
	if str, ok := v.(string); ok {
		fld.AsFieldDescriptorProto().DefaultValue = proto.String(str)
	} else if b, ok := v.([]byte); ok {
		fld.AsFieldDescriptorProto().DefaultValue = proto.String(encodeDefaultBytes(b))
	} else {
		var flt float64
		var ok bool
		if flt, ok = v.(float64); !ok {
			var flt32 float32
			if flt32, ok = v.(float32); ok {
				flt = float64(flt32)
			}
		}
		if ok {
			if math.IsInf(flt, 1) {
				fld.AsFieldDescriptorProto().DefaultValue = proto.String("inf")
			} else if ok && math.IsInf(flt, -1) {
				fld.AsFieldDescriptorProto().DefaultValue = proto.String("-inf")
			} else if ok && math.IsNaN(flt) {
				fld.AsFieldDescriptorProto().DefaultValue = proto.String("nan")
			} else {
				fld.AsFieldDescriptorProto().DefaultValue = proto.String(fmt.Sprintf("%v", v))
			}
		} else {
			fld.AsFieldDescriptorProto().DefaultValue = proto.String(fmt.Sprintf("%v", v))
		}
	}
	return found, nil
}

func encodeDefaultBytes(b []byte) string {
	var buf bytes.Buffer
	writeEscapedBytes(&buf, b)
	return buf.String()
}

func interpretEnumOptions(r *parseResult, ed enumDescriptorish) error {
	opts := ed.GetEnumOptions()
	if opts != nil {
		if len(opts.UninterpretedOption) > 0 {
			if remain, err := interpretOptions(r, ed, opts, opts.UninterpretedOption); err != nil {
				return err
			} else {
				opts.UninterpretedOption = remain
			}
		}
	}
	for _, evd := range ed.GetValues() {
		opts := evd.GetEnumValueOptions()
		if len(opts.GetUninterpretedOption()) > 0 {
			if remain, err := interpretOptions(r, evd, opts, opts.UninterpretedOption); err != nil {
				return err
			} else {
				opts.UninterpretedOption = remain
			}
		}
	}
	return nil
}

func interpretOptions(res *parseResult, element descriptorish, opts proto.Message, uninterpreted []*dpb.UninterpretedOption) ([]*dpb.UninterpretedOption, error) {
	optsd, err := desc.LoadMessageDescriptorForMessage(opts)
	if err != nil {
		if res.lenient {
			return uninterpreted, nil
		}
		return nil, res.errs.handleError(err)
	}
	dm := dynamic.NewMessage(optsd)
	err = dm.ConvertFrom(opts)
	if err != nil {
		if res.lenient {
			return uninterpreted, nil
		}
		node := res.nodes[element.AsProto()]
		return nil, res.errs.handleError(ErrorWithSourcePos{Pos: node.Start(), Underlying: err})
	}

	mc := &messageContext{res: res, file: element.GetFile(), elementName: element.GetFullyQualifiedName(), elementType: descriptorType(element.AsProto())}
	var remain []*dpb.UninterpretedOption
	for _, uo := range uninterpreted {
		node := res.getOptionNode(uo)
		if !uo.Name[0].GetIsExtension() && uo.Name[0].GetNamePart() == "uninterpreted_option" {
			if res.lenient {
				remain = append(remain, uo)
				continue
			}
			// uninterpreted_option might be found reflectively, but is not actually valid for use
			if err := res.errs.handleErrorWithPos(node.GetName().Start(), "%vinvalid option 'uninterpreted_option'", mc); err != nil {
				return nil, err
			}
		}
		mc.option = uo
		path, err := interpretField(res, mc, element, dm, uo, 0, nil)
		if err != nil {
			if res.lenient {
				remain = append(remain, uo)
				continue
			}
			return nil, err
		}
		if optn, ok := node.(*ast.OptionNode); ok {
			res.interpretedOptions[optn] = path
		}
	}

	if res.lenient {
		// If we're lenient, then we don't want to clobber the passed in message
		// and leave it partially populated. So we convert into a copy first
		optsClone := proto.Clone(opts)
		if err := dm.ConvertToDeterministic(optsClone); err != nil {
			// TODO: do this in a more granular way, so we can convert individual
			// fields and leave bad ones uninterpreted instead of skipping all of
			// the work we've done so far.
			return uninterpreted, nil
		}
		// conversion from dynamic message above worked, so now
		// it is safe to overwrite the passed in message
		opts.Reset()
		proto.Merge(opts, optsClone)

		return remain, nil
	}

	if err := dm.ValidateRecursive(); err != nil {
		node := res.nodes[element.AsProto()]
		if err := res.errs.handleErrorWithPos(node.Start(), "error in %s options: %v", descriptorType(element.AsProto()), err); err != nil {
			return nil, err
		}
	}

	// nw try to convert into the passed in message and fail if not successful
	if err := dm.ConvertToDeterministic(opts); err != nil {
		node := res.nodes[element.AsProto()]
		return nil, res.errs.handleError(ErrorWithSourcePos{Pos: node.Start(), Underlying: err})
	}

	return nil, nil
}

func interpretField(res *parseResult, mc *messageContext, element descriptorish, dm *dynamic.Message, opt *dpb.UninterpretedOption, nameIndex int, pathPrefix []int32) (path []int32, err error) {
	var fld *desc.FieldDescriptor
	nm := opt.GetName()[nameIndex]
	node := res.getOptionNamePartNode(nm)
	if nm.GetIsExtension() {
		extName := nm.GetNamePart()
		if extName[0] == '.' {
			extName = extName[1:] /* skip leading dot */
		}
		fld = findExtension(element.GetFile(), extName, false, map[fileDescriptorish]struct{}{})
		if fld == nil {
			return nil, res.errs.handleErrorWithPos(node.Start(),
				"%vunrecognized extension %s of %s",
				mc, extName, dm.GetMessageDescriptor().GetFullyQualifiedName())
		}
		if fld.GetOwner().GetFullyQualifiedName() != dm.GetMessageDescriptor().GetFullyQualifiedName() {
			return nil, res.errs.handleErrorWithPos(node.Start(),
				"%vextension %s should extend %s but instead extends %s",
				mc, extName, dm.GetMessageDescriptor().GetFullyQualifiedName(), fld.GetOwner().GetFullyQualifiedName())
		}
	} else {
		fld = dm.GetMessageDescriptor().FindFieldByName(nm.GetNamePart())
		if fld == nil {
			return nil, res.errs.handleErrorWithPos(node.Start(),
				"%vfield %s of %s does not exist",
				mc, nm.GetNamePart(), dm.GetMessageDescriptor().GetFullyQualifiedName())
		}
	}

	path = append(pathPrefix, fld.GetNumber())

	if len(opt.GetName()) > nameIndex+1 {
		nextnm := opt.GetName()[nameIndex+1]
		nextnode := res.getOptionNamePartNode(nextnm)
		if fld.GetType() != dpb.FieldDescriptorProto_TYPE_MESSAGE {
			return nil, res.errs.handleErrorWithPos(nextnode.Start(),
				"%vcannot set field %s because %s is not a message",
				mc, nextnm.GetNamePart(), nm.GetNamePart())
		}
		if fld.IsRepeated() {
			return nil, res.errs.handleErrorWithPos(nextnode.Start(),
				"%vcannot set field %s because %s is repeated (must use an aggregate)",
				mc, nextnm.GetNamePart(), nm.GetNamePart())
		}
		var fdm *dynamic.Message
		var err error
		if dm.HasField(fld) {
			var v interface{}
			v, err = dm.TryGetField(fld)
			fdm, _ = v.(*dynamic.Message)
		} else {
			fdm = dynamic.NewMessage(fld.GetMessageType())
			err = dm.TrySetField(fld, fdm)
		}
		if err != nil {
			return nil, res.errs.handleError(ErrorWithSourcePos{Pos: node.Start(), Underlying: err})
		}
		// recurse to set next part of name
		return interpretField(res, mc, element, fdm, opt, nameIndex+1, path)
	}

	optNode := res.getOptionNode(opt)
	if err := setOptionField(res, mc, dm, fld, node, optNode.GetValue()); err != nil {
		return nil, res.errs.handleError(err)
	}
	if fld.IsRepeated() {
		path = append(path, int32(dm.FieldLength(fld))-1)
	}
	return path, nil
}

func findExtension(fd fileDescriptorish, name string, public bool, checked map[fileDescriptorish]struct{}) *desc.FieldDescriptor {
	if _, ok := checked[fd]; ok {
		return nil
	}
	checked[fd] = struct{}{}
	d := fd.FindSymbol(name)
	if d != nil {
		if fld, ok := d.(*desc.FieldDescriptor); ok {
			return fld
		}
		return nil
	}

	// When public = false, we are searching only directly imported symbols. But we
	// also need to search transitive public imports due to semantics of public imports.
	if public {
		for _, dep := range fd.GetPublicDependencies() {
			d := findExtension(dep, name, true, checked)
			if d != nil {
				return d
			}
		}
	} else {
		for _, dep := range fd.GetDependencies() {
			d := findExtension(dep, name, true, checked)
			if d != nil {
				return d
			}
		}
	}
	return nil
}

func setOptionField(res *parseResult, mc *messageContext, dm *dynamic.Message, fld *desc.FieldDescriptor, name ast.Node, val ast.ValueNode) error {
	v := val.Value()
	if sl, ok := v.([]ast.ValueNode); ok {
		// handle slices a little differently than the others
		if !fld.IsRepeated() {
			return errorWithPos(val.Start(), "%vvalue is an array but field is not repeated", mc)
		}
		origPath := mc.optAggPath
		defer func() {
			mc.optAggPath = origPath
		}()
		for index, item := range sl {
			mc.optAggPath = fmt.Sprintf("%s[%d]", origPath, index)
			if v, err := fieldValue(res, mc, richFldDescriptorish{FieldDescriptor: fld}, item, false); err != nil {
				return err
			} else if err = dm.TryAddRepeatedField(fld, v); err != nil {
				return errorWithPos(val.Start(), "%verror setting value: %s", mc, err)
			}
		}
		return nil
	}

	v, err := fieldValue(res, mc, richFldDescriptorish{FieldDescriptor: fld}, val, false)
	if err != nil {
		return err
	}
	if fld.IsRepeated() {
		err = dm.TryAddRepeatedField(fld, v)
	} else {
		if dm.HasField(fld) {
			return errorWithPos(name.Start(), "%vnon-repeated option field %s already set", mc, fieldName(fld))
		}
		err = dm.TrySetField(fld, v)
	}
	if err != nil {
		return errorWithPos(val.Start(), "%verror setting value: %s", mc, err)
	}

	return nil
}

func findOption(res *parseResult, scope string, opts []*dpb.UninterpretedOption, name string) (int, error) {
	found := -1
	for i, opt := range opts {
		if len(opt.Name) != 1 {
			continue
		}
		if opt.Name[0].GetIsExtension() || opt.Name[0].GetNamePart() != name {
			continue
		}
		if found >= 0 {
			optNode := res.getOptionNode(opt)
			return -1, res.errs.handleErrorWithPos(optNode.GetName().Start(), "%s: option %s cannot be defined more than once", scope, name)
		}
		found = i
	}
	return found, nil
}

func removeOption(uo []*dpb.UninterpretedOption, indexToRemove int) []*dpb.UninterpretedOption {
	if indexToRemove == 0 {
		return uo[1:]
	} else if int(indexToRemove) == len(uo)-1 {
		return uo[:len(uo)-1]
	} else {
		return append(uo[:indexToRemove], uo[indexToRemove+1:]...)
	}
}

type messageContext struct {
	res         *parseResult
	file        fileDescriptorish
	elementType string
	elementName string
	option      *dpb.UninterpretedOption
	optAggPath  string
}

func (c *messageContext) String() string {
	var ctx bytes.Buffer
	if c.elementType != "file" {
		_, _ = fmt.Fprintf(&ctx, "%s %s: ", c.elementType, c.elementName)
	}
	if c.option != nil && c.option.Name != nil {
		ctx.WriteString("option ")
		writeOptionName(&ctx, c.option.Name)
		if c.res.nodes == nil {
			// if we have no source position info, try to provide as much context
			// as possible (if nodes != nil, we don't need this because any errors
			// will actually have file and line numbers)
			if c.optAggPath != "" {
				_, _ = fmt.Fprintf(&ctx, " at %s", c.optAggPath)
			}
		}
		ctx.WriteString(": ")
	}
	return ctx.String()
}

func writeOptionName(buf *bytes.Buffer, parts []*dpb.UninterpretedOption_NamePart) {
	first := true
	for _, p := range parts {
		if first {
			first = false
		} else {
			buf.WriteByte('.')
		}
		nm := p.GetNamePart()
		if nm[0] == '.' {
			// skip leading dot
			nm = nm[1:]
		}
		if p.GetIsExtension() {
			buf.WriteByte('(')
			buf.WriteString(nm)
			buf.WriteByte(')')
		} else {
			buf.WriteString(nm)
		}
	}
}

func fieldName(fld *desc.FieldDescriptor) string {
	if fld.IsExtension() {
		return fld.GetFullyQualifiedName()
	} else {
		return fld.GetName()
	}
}

func valueKind(val interface{}) string {
	switch val := val.(type) {
	case ast.Identifier:
		return "identifier"
	case bool:
		return "bool"
	case int64:
		if val < 0 {
			return "negative integer"
		}
		return "integer"
	case uint64:
		return "integer"
	case float64:
		return "double"
	case string, []byte:
		return "string"
	case []*ast.MessageFieldNode:
		return "message"
	case []ast.ValueNode:
		return "array"
	default:
		return fmt.Sprintf("%T", val)
	}
}

func fieldValue(res *parseResult, mc *messageContext, fld fldDescriptorish, val ast.ValueNode, enumAsString bool) (interface{}, error) {
	v := val.Value()
	t := fld.AsFieldDescriptorProto().GetType()
	switch t {
	case dpb.FieldDescriptorProto_TYPE_ENUM:
		if id, ok := v.(ast.Identifier); ok {
			ev := fld.GetEnumType().FindValueByName(string(id))
			if ev == nil {
				return nil, errorWithPos(val.Start(), "%venum %s has no value named %s", mc, fld.GetEnumType().GetFullyQualifiedName(), id)
			}
			if enumAsString {
				return ev.GetName(), nil
			} else {
				return ev.GetNumber(), nil
			}
		}
		return nil, errorWithPos(val.Start(), "%vexpecting enum, got %s", mc, valueKind(v))
	case dpb.FieldDescriptorProto_TYPE_MESSAGE, dpb.FieldDescriptorProto_TYPE_GROUP:
		if aggs, ok := v.([]*ast.MessageFieldNode); ok {
			fmd := fld.GetMessageType()
			fdm := dynamic.NewMessage(fmd)
			origPath := mc.optAggPath
			defer func() {
				mc.optAggPath = origPath
			}()
			for _, a := range aggs {
				if origPath == "" {
					mc.optAggPath = a.Name.Value()
				} else {
					mc.optAggPath = origPath + "." + a.Name.Value()
				}
				var ffld *desc.FieldDescriptor
				if a.Name.IsExtension() {
					n := string(a.Name.Name.AsIdentifier())
					ffld = findExtension(mc.file, n, false, map[fileDescriptorish]struct{}{})
					if ffld == nil {
						// may need to qualify with package name
						pkg := mc.file.GetPackage()
						if pkg != "" {
							ffld = findExtension(mc.file, pkg+"."+n, false, map[fileDescriptorish]struct{}{})
						}
					}
				} else {
					ffld = fmd.FindFieldByName(a.Name.Value())
				}
				if ffld == nil {
					return nil, errorWithPos(val.Start(), "%vfield %s not found", mc, string(a.Name.Name.AsIdentifier()))
				}
				if err := setOptionField(res, mc, fdm, ffld, a.Name, a.Val); err != nil {
					return nil, err
				}
			}
			return fdm, nil
		}
		return nil, errorWithPos(val.Start(), "%vexpecting message, got %s", mc, valueKind(v))
	case dpb.FieldDescriptorProto_TYPE_BOOL:
		if b, ok := v.(bool); ok {
			return b, nil
		}
		return nil, errorWithPos(val.Start(), "%vexpecting bool, got %s", mc, valueKind(v))
	case dpb.FieldDescriptorProto_TYPE_BYTES:
		if str, ok := v.(string); ok {
			return []byte(str), nil
		}
		return nil, errorWithPos(val.Start(), "%vexpecting bytes, got %s", mc, valueKind(v))
	case dpb.FieldDescriptorProto_TYPE_STRING:
		if str, ok := v.(string); ok {
			return str, nil
		}
		return nil, errorWithPos(val.Start(), "%vexpecting string, got %s", mc, valueKind(v))
	case dpb.FieldDescriptorProto_TYPE_INT32, dpb.FieldDescriptorProto_TYPE_SINT32, dpb.FieldDescriptorProto_TYPE_SFIXED32:
		if i, ok := v.(int64); ok {
			if i > math.MaxInt32 || i < math.MinInt32 {
				return nil, errorWithPos(val.Start(), "%vvalue %d is out of range for int32", mc, i)
			}
			return int32(i), nil
		}
		if ui, ok := v.(uint64); ok {
			if ui > math.MaxInt32 {
				return nil, errorWithPos(val.Start(), "%vvalue %d is out of range for int32", mc, ui)
			}
			return int32(ui), nil
		}
		return nil, errorWithPos(val.Start(), "%vexpecting int32, got %s", mc, valueKind(v))
	case dpb.FieldDescriptorProto_TYPE_UINT32, dpb.FieldDescriptorProto_TYPE_FIXED32:
		if i, ok := v.(int64); ok {
			if i > math.MaxUint32 || i < 0 {
				return nil, errorWithPos(val.Start(), "%vvalue %d is out of range for uint32", mc, i)
			}
			return uint32(i), nil
		}
		if ui, ok := v.(uint64); ok {
			if ui > math.MaxUint32 {
				return nil, errorWithPos(val.Start(), "%vvalue %d is out of range for uint32", mc, ui)
			}
			return uint32(ui), nil
		}
		return nil, errorWithPos(val.Start(), "%vexpecting uint32, got %s", mc, valueKind(v))
	case dpb.FieldDescriptorProto_TYPE_INT64, dpb.FieldDescriptorProto_TYPE_SINT64, dpb.FieldDescriptorProto_TYPE_SFIXED64:
		if i, ok := v.(int64); ok {
			return i, nil
		}
		if ui, ok := v.(uint64); ok {
			if ui > math.MaxInt64 {
				return nil, errorWithPos(val.Start(), "%vvalue %d is out of range for int64", mc, ui)
			}
			return int64(ui), nil
		}
		return nil, errorWithPos(val.Start(), "%vexpecting int64, got %s", mc, valueKind(v))
	case dpb.FieldDescriptorProto_TYPE_UINT64, dpb.FieldDescriptorProto_TYPE_FIXED64:
		if i, ok := v.(int64); ok {
			if i < 0 {
				return nil, errorWithPos(val.Start(), "%vvalue %d is out of range for uint64", mc, i)
			}
			return uint64(i), nil
		}
		if ui, ok := v.(uint64); ok {
			return ui, nil
		}
		return nil, errorWithPos(val.Start(), "%vexpecting uint64, got %s", mc, valueKind(v))
	case dpb.FieldDescriptorProto_TYPE_DOUBLE:
		if d, ok := v.(float64); ok {
			return d, nil
		}
		if i, ok := v.(int64); ok {
			return float64(i), nil
		}
		if u, ok := v.(uint64); ok {
			return float64(u), nil
		}
		return nil, errorWithPos(val.Start(), "%vexpecting double, got %s", mc, valueKind(v))
	case dpb.FieldDescriptorProto_TYPE_FLOAT:
		if d, ok := v.(float64); ok {
			if (d > math.MaxFloat32 || d < -math.MaxFloat32) && !math.IsInf(d, 1) && !math.IsInf(d, -1) && !math.IsNaN(d) {
				return nil, errorWithPos(val.Start(), "%vvalue %f is out of range for float", mc, d)
			}
			return float32(d), nil
		}
		if i, ok := v.(int64); ok {
			return float32(i), nil
		}
		if u, ok := v.(uint64); ok {
			return float32(u), nil
		}
		return nil, errorWithPos(val.Start(), "%vexpecting float, got %s", mc, valueKind(v))
	default:
		return nil, errorWithPos(val.Start(), "%vunrecognized field type: %s", mc, t)
	}
}
