func toCamelCase(str string) string {
	var link = regexp.MustCompile("(^[A-Za-z])|_([A-Za-z])")
	return link.ReplaceAllStringFunc(str, func(s string) string {
		return strings.ToUpper(strings.Replace(s, "_", "", -1))
	})
}
toCamelCase converts str to CamelCase
package generator
import (
	"regexp"
	"strconv"
	"strings"
	surface_v1 "github.com/google/gnostic/surface"
)
type ProtoLanguageModel struct{}
func NewProtoLanguageModel() *ProtoLanguageModel {
	return &ProtoLanguageModel{}
}
func (language *ProtoLanguageModel) Prepare(model *surface_v1.Model, inputDocumentType string) {
	for _, t := range model.Types {
		t.TypeName = protoTypeName(strings.Replace(t.Name, "Parameters", "Request", 1))
		for _, f := range t.Fields {
			f.FieldName = protoFieldName(f.Name, f.Type)
			f.NativeType = findNativeType(f.Type, f.Format)
			if f.EnumValues != nil {
				f.NativeType = strings.Title(f.Name)
			}
		}
	}
	for _, m := range model.Methods {
		m.HandlerName = protoTypeName(m.Name)
		m.ProcessorName = m.Name
		m.ClientName = m.Name
		m.ParametersTypeName = protoTypeName(strings.Replace(m.ParametersTypeName, "Parameters", "Request", 1))
		m.ResponsesTypeName = protoTypeName(m.ResponsesTypeName)
	}
	AdjustSurfaceModel(model, inputDocumentType)
}
func findNativeType(fType string, fFormat string) string {
	switch fType {
	case "boolean":
		return "bool"
	case "number":
		switch fFormat {
		case "float":
			return "float"
		case "double":
			return "double"
		default:
			return "float"
		}
	case "integer":
		switch fFormat {
		case "int32":
			return "int32"
		case "int64":
			return "int64"
		default:
			return "int64"
		}
	case "object":
		return "message"
	case "string":
		return "string"
	case "date":
		return "string"
	case "date-time":
		return "string"
	case "password":
		return "string"
	case "binary":
		return "string"
	case "email":
		return "string"
	case "uuid":
		return "string"
	case "uri":
		return "string"
	case "hostname":
		return "string"
	case "ipv4":
		return "string"
	case "ipv6":
		return "string"
	case "byte":
		return "string"
	default:
		if strings.Contains(fType, "map") {
			mapType := fType[11:]
			formattedType := map[string]bool{
				"int32": true,
				"int64": true,
			}
			if !formattedType[mapType] {
				return "map[string]" + findNativeType(mapType, "")
			}
			return fType
		}
		return protoTypeName(fType)
	}
}
func AdjustSurfaceModel(model *surface_v1.Model, inputDocumentType string) {
	if inputDocumentType == "openapi.v2.Document" {
		adjustV2Model(model)
	} else if inputDocumentType == "openapi.v3.Document" {
		adjustV3Model(model)
	} else if inputDocumentType == "discovery.v1.Document" {
		adjustV3Model(model)
	}
}
func adjustV3Model(model *surface_v1.Model) {
	nameToType, typesToDelete := initHashTables(model)
	for _, m := range model.Methods {
		if len(m.ParametersTypeName) > 0 {
			if parameters, ok := nameToType[m.ParametersTypeName]; ok {
				for _, f := range parameters.Fields {
					if f.Name == "request_body" {
						reqBody := f
						if intermediateType, ok := nameToType[reqBody.NativeType]; ok {
							reqBody.FieldName = intermediateType.Fields[0].FieldName
							reqBody.NativeType = intermediateType.Fields[0].NativeType
							typesToDelete[intermediateType] = true
						}
					}
				}
			}
		}
		if len(m.ResponsesTypeName) > 0 {
			if responses, ok := nameToType[m.ResponsesTypeName]; ok {
				typesToDelete[nameToType[m.ResponsesTypeName]] = true
				for _, f := range responses.Fields {
					typesToDelete[nameToType[f.NativeType]] = true
				}
				lowestStatusCodeResponse := findLowestStatusCode(responses, nameToType)
				m.ResponsesTypeName = ""
				if lowestStatusCodeResponse != nil && lowestStatusCodeResponse.Fields[0].Kind != surface_v1.FieldKind_SCALAR {
					m.ResponsesTypeName = lowestStatusCodeResponse.Fields[0].NativeType
				} else {
					m.ResponsesTypeName = ""
					typesToDelete[responses] = true
				}
			}
		}
	}
	filteredTypes := make([]*surface_v1.Type, 0)
	for _, t := range model.Types {
		if shouldDelete, ok := typesToDelete[t]; ok && !shouldDelete {
			filteredTypes = append(filteredTypes, t)
		}
	}
	model.Types = filteredTypes
}
func adjustV2Model(model *surface_v1.Model) {
	nameToType, typesToDelete := initHashTables(model)
	for _, m := range model.Methods {
		if len(m.ResponsesTypeName) > 0 {
			if responses, ok := nameToType[m.ResponsesTypeName]; ok {
				typesToDelete[nameToType[m.ResponsesTypeName]] = true
				lowestStatusCodeResponse := findLowestStatusCode(responses, nameToType)
				m.ResponsesTypeName = ""
				if lowestStatusCodeResponse != nil {
					m.ResponsesTypeName = lowestStatusCodeResponse.TypeName
				} else {
					m.ResponsesTypeName = ""
					typesToDelete[responses] = true
				}
			}
		}
	}
	filteredTypes := make([]*surface_v1.Type, 0)
	for _, t := range model.Types {
		if shouldDelete, ok := typesToDelete[t]; ok && !shouldDelete {
			filteredTypes = append(filteredTypes, t)
		}
	}
	model.Types = filteredTypes
}
func findLowestStatusCode(responses *surface_v1.Type, nameToType map[string]*surface_v1.Type) *surface_v1.Type {
	if lowestStatusCodeResponse, ok := nameToType[responses.Fields[0].NativeType]; ok {
		lowestStatusCode, err := strconv.Atoi(responses.Fields[0].FieldName)
		if err == nil {
			for _, f := range responses.Fields {
				statusCode, err := strconv.Atoi(f.FieldName)
				if err == nil && statusCode < lowestStatusCode {
					lowestStatusCodeResponse = nameToType[f.NativeType]
					lowestStatusCode = statusCode
				}
			}
		}
		return lowestStatusCodeResponse
	}
	return nil
}
func initHashTables(model *surface_v1.Model) (map[string]*surface_v1.Type, map[*surface_v1.Type]bool) {
	nameToType := make(map[string]*surface_v1.Type)
	typesToDelete := make(map[*surface_v1.Type]bool)
	for _, t := range model.Types {
		nameToType[t.TypeName] = t
	}
	for _, t := range model.Types {
		typesToDelete[t] = false
	}
	return nameToType, typesToDelete
}
func protoFieldName(originalName string, t string) string {
	name := CleanName(originalName)
	if len(name) == 0 {
		name = CleanName(t)
	}
	name = toSnakeCase(name)
	return name
}
func protoTypeName(originalName string) (name string) {
	name = CleanName(originalName)
	name = toCamelCase(name)
	return name
}
func CleanName(name string) string {
	name = strings.Replace(name, "application/json", "", -1)
	name = strings.Replace(name, ".", "_", -1)
	name = strings.Replace(name, "-", "_", -1)
	name = strings.Replace(name, " ", "", -1)
	name = strings.Replace(name, "(", "", -1)
	name = strings.Replace(name, ")", "", -1)
	name = strings.Replace(name, "{", "", -1)
	name = strings.Replace(name, "}", "", -1)
	name = strings.Replace(name, "/", "_", -1)
	name = strings.Replace(name, "$", "", -1)
	return name
}