// 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 impl

import (
	
	
	
	

	
	
	
)

// legacyEnumName returns the name of enums used in legacy code.
// It is neither the protobuf full name nor the qualified Go name,
// but rather an odd hybrid of both.
func ( protoreflect.EnumDescriptor) string {
	var  string
	 := string(.FullName())
	if  := .ParentFile();  != nil {
		 = string(.Package())
		 = strings.TrimPrefix(, +".")
	}
	if  == "" {
		return strs.GoCamelCase()
	}
	return  + "." + strs.GoCamelCase()
}

// legacyWrapEnum wraps v as a protoreflect.Enum,
// where v must be a int32 kind and not implement the v2 API already.
func ( reflect.Value) protoreflect.Enum {
	 := legacyLoadEnumType(.Type())
	return .New(protoreflect.EnumNumber(.Int()))
}

var legacyEnumTypeCache sync.Map // map[reflect.Type]protoreflect.EnumType

// legacyLoadEnumType dynamically loads a protoreflect.EnumType for t,
// where t must be an int32 kind and not implement the v2 API already.
func ( reflect.Type) protoreflect.EnumType {
	// Fast-path: check if a EnumType is cached for this concrete type.
	if ,  := legacyEnumTypeCache.Load();  {
		return .(protoreflect.EnumType)
	}

	// Slow-path: derive enum descriptor and initialize EnumType.
	var  protoreflect.EnumType
	 := LegacyLoadEnumDesc()
	 = &legacyEnumType{
		desc:   ,
		goType: ,
	}
	if ,  := legacyEnumTypeCache.LoadOrStore(, );  {
		return .(protoreflect.EnumType)
	}
	return 
}

type legacyEnumType struct {
	desc   protoreflect.EnumDescriptor
	goType reflect.Type
	m      sync.Map // map[protoreflect.EnumNumber]proto.Enum
}

func ( *legacyEnumType) ( protoreflect.EnumNumber) protoreflect.Enum {
	if ,  := .m.Load();  {
		return .(protoreflect.Enum)
	}
	 := &legacyEnumWrapper{num: , pbTyp: , goTyp: .goType}
	.m.Store(, )
	return 
}
func ( *legacyEnumType) () protoreflect.EnumDescriptor {
	return .desc
}

type legacyEnumWrapper struct {
	num   protoreflect.EnumNumber
	pbTyp protoreflect.EnumType
	goTyp reflect.Type
}

func ( *legacyEnumWrapper) () protoreflect.EnumDescriptor {
	return .pbTyp.Descriptor()
}
func ( *legacyEnumWrapper) () protoreflect.EnumType {
	return .pbTyp
}
func ( *legacyEnumWrapper) () protoreflect.EnumNumber {
	return .num
}
func ( *legacyEnumWrapper) () protoreflect.Enum {
	return 
}
func ( *legacyEnumWrapper) () interface{} {
	 := reflect.New(.goTyp).Elem()
	.SetInt(int64(.num))
	return .Interface()
}

var (
	_ protoreflect.Enum = (*legacyEnumWrapper)(nil)
	_ unwrapper         = (*legacyEnumWrapper)(nil)
)

var legacyEnumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor

// LegacyLoadEnumDesc returns an EnumDescriptor derived from the Go type,
// which must be an int32 kind and not implement the v2 API already.
//
// This is exported for testing purposes.
func ( reflect.Type) protoreflect.EnumDescriptor {
	// Fast-path: check if an EnumDescriptor is cached for this concrete type.
	if ,  := legacyEnumDescCache.Load();  {
		return .(protoreflect.EnumDescriptor)
	}

	// Slow-path: initialize EnumDescriptor from the raw descriptor.
	 := reflect.Zero().Interface()
	if ,  := .(protoreflect.Enum);  {
		panic(fmt.Sprintf("%v already implements proto.Enum", ))
	}
	,  := .(enumV1)
	if ! {
		return aberrantLoadEnumDesc()
	}
	,  := .EnumDescriptor()

	var  protoreflect.EnumDescriptor
	if len() == 1 {
		 = legacyLoadFileDesc().Enums().Get([0])
	} else {
		 := legacyLoadFileDesc().Messages().Get([0])
		for ,  := range [1 : len()-1] {
			 = .Messages().Get()
		}
		 = .Enums().Get([len()-1])
	}
	if ,  := legacyEnumDescCache.LoadOrStore(, );  {
		return .(protoreflect.EnumDescriptor)
	}
	return 
}

var aberrantEnumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor

// aberrantLoadEnumDesc returns an EnumDescriptor derived from the Go type,
// which must not implement protoreflect.Enum or enumV1.
//
// If the type does not implement enumV1, then there is no reliable
// way to derive the original protobuf type information.
// We are unable to use the global enum registry since it is
// unfortunately keyed by the protobuf full name, which we also do not know.
// Thus, this produces some bogus enum descriptor based on the Go type name.
func ( reflect.Type) protoreflect.EnumDescriptor {
	// Fast-path: check if an EnumDescriptor is cached for this concrete type.
	if ,  := aberrantEnumDescCache.Load();  {
		return .(protoreflect.EnumDescriptor)
	}

	// Slow-path: construct a bogus, but unique EnumDescriptor.
	 := &filedesc.Enum{L2: new(filedesc.EnumL2)}
	.L0.FullName = AberrantDeriveFullName() // e.g., github_com.user.repo.MyEnum
	.L0.ParentFile = filedesc.SurrogateProto3
	.L2.Values.List = append(.L2.Values.List, filedesc.EnumValue{})

	// TODO: Use the presence of a UnmarshalJSON method to determine proto2?

	 := &.L2.Values.List[0]
	.L0.FullName = .L0.FullName + "_UNKNOWN" // e.g., github_com.user.repo.MyEnum_UNKNOWN
	.L0.ParentFile = .L0.ParentFile
	.L0.Parent = 

	// TODO: We could use the String method to obtain some enum value names by
	// starting at 0 and print the enum until it produces invalid identifiers.
	// An exhaustive query is clearly impractical, but can be best-effort.

	if ,  := aberrantEnumDescCache.LoadOrStore(, );  {
		return .(protoreflect.EnumDescriptor)
	}
	return 
}

// AberrantDeriveFullName derives a fully qualified protobuf name for the given Go type
// The provided name is not guaranteed to be stable nor universally unique.
// It should be sufficiently unique within a program.
//
// This is exported for testing purposes.
func ( reflect.Type) protoreflect.FullName {
	 := func( rune) rune {
		switch {
		case  == '/':
			return '.'
		case 'a' <=  &&  <= 'z', 'A' <=  &&  <= 'Z', '0' <=  &&  <= '9':
			return 
		default:
			return '_'
		}
	}
	 := strings.Map(, .PkgPath())
	 := strings.Map(, .Name())
	if  == "" {
		 = fmt.Sprintf("UnknownX%X", reflect.ValueOf().Pointer())
	}

	 := append(strings.Split(, "."), )
	for ,  := range  {
		if  == "" || ('0' <= [0] && [0] <= '9') {
			[] = "x" + 
		}
	}
	return protoreflect.FullName(strings.Join(, "."))
}