// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package generator

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strconv"
	"strings"

	"github.com/go-openapi/analysis"
	"github.com/go-openapi/loads"
	"github.com/go-openapi/spec"
	"github.com/go-openapi/swag"
)

const asMethod = "()"

/*
Rewrite specification document first:

* anonymous objects
* tuples
* extensible objects (properties + additionalProperties)
* AllOfs when they match the rewrite criteria (not a nullable allOf)

Find string enums and generate specialized idiomatic enum with them

Every action that happens tracks the path which is a linked list of refs


*/

// GenerateDefinition generates a model file for a schema definition.
func GenerateDefinition(modelNames []string, opts *GenOpts) error {
	if opts == nil {
		return errors.New("gen opts are required")
	}

	if opts.TemplateDir != "" {
		if err := templates.LoadDir(opts.TemplateDir); err != nil {
			return err
		}
	}

	if err := opts.CheckOpts(); err != nil {
		return err
	}

	// Load the spec
	specPath, specDoc, err := loadSpec(opts.Spec)
	if err != nil {
		return err
	}

	if len(modelNames) == 0 {
		for k := range specDoc.Spec().Definitions {
			modelNames = append(modelNames, k)
		}
	}

	for _, modelName := range modelNames {
		// lookup schema
		model, ok := specDoc.Spec().Definitions[modelName]
		if !ok {
			return fmt.Errorf("model %q not found in definitions given by %q", modelName, specPath)
		}

		// generate files
		generator := definitionGenerator{
			Name:    modelName,
			Model:   model,
			SpecDoc: specDoc,
			Target: filepath.Join(
				opts.Target,
				filepath.FromSlash(opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, ""))),
			opts: opts,
		}

		if err := generator.Generate(); err != nil {
			return err
		}
	}

	return nil
}

type definitionGenerator struct {
	Name    string
	Model   spec.Schema
	SpecDoc *loads.Document
	Target  string
	opts    *GenOpts
}

func (m *definitionGenerator) Generate() error {

	mod, err := makeGenDefinition(m.Name, m.Target, m.Model, m.SpecDoc, m.opts)
	if err != nil {
		return fmt.Errorf("could not generate definitions for model %s on target %s: %v", m.Name, m.Target, err)
	}

	if m.opts.DumpData {
		bb, _ := json.MarshalIndent(swag.ToDynamicJSON(mod), "", " ")
		fmt.Fprintln(os.Stdout, string(bb))
		return nil
	}

	if m.opts.IncludeModel {
		log.Println("including additional model")
		if err := m.generateModel(mod); err != nil {
			return fmt.Errorf("could not generate model: %v", err)
		}
	}
	log.Println("generated model", m.Name)

	return nil
}

func (m *definitionGenerator) generateModel(g *GenDefinition) error {
	debugLog("rendering definitions for %+v", *g)
	return m.opts.renderDefinition(g)
}

func makeGenDefinition(name, pkg string, schema spec.Schema, specDoc *loads.Document, opts *GenOpts) (*GenDefinition, error) {
	gd, err := makeGenDefinitionHierarchy(name, pkg, "", schema, specDoc, opts)

	if err == nil && gd != nil {
		// before yielding the schema to the renderer, we check if the top-level Validate method gets some content
		// this means that the immediate content of the top level definitions has at least one validation.
		//
		// If none is found at this level and that no special case where no Validate() method is exposed at all
		// (e.g. io.ReadCloser and interface{} types and their aliases), then there is an empty Validate() method which
		// just return nil (the object abides by the runtime.Validatable interface, but knows it has nothing to validate).
		//
		// We do this at the top level because of the possibility of aliased types which always bubble up validation to types which
		// are referring to them. This results in correct but inelegant code with empty validations.
		gd.GenSchema.HasValidations = shallowValidationLookup(gd.GenSchema)
	}
	return gd, err
}

func shallowValidationLookup(sch GenSchema) bool {
	// scan top level need for validations
	//
	// NOTE: this supersedes the previous NeedsValidation flag
	// With the introduction of this shallow lookup, it is no more necessary
	// to establish a distinction between HasValidations (e.g. carries on validations)
	// and NeedsValidation (e.g. should have a Validate method with something in it).
	// The latter was almost not used anyhow.

	if sch.IsArray && sch.HasValidations {
		return true
	}
	if sch.IsStream || sch.IsInterface { // these types have no validation - aliased types on those do not implement the Validatable interface
		return false
	}
	if sch.Required || sch.IsCustomFormatter && !sch.IsStream {
		return true
	}
	if sch.MaxLength != nil || sch.MinLength != nil || sch.Pattern != "" || sch.MultipleOf != nil || sch.Minimum != nil || sch.Maximum != nil || len(sch.Enum) > 0 || len(sch.ItemsEnum) > 0 {
		return true
	}
	for _, a := range sch.AllOf {
		if a.HasValidations {
			return true
		}
	}
	for _, p := range sch.Properties {
		// Using a base type within another structure triggers validation of the base type.
		// The discriminator property in the base type definition itself does not.
		if (p.HasValidations || p.Required) && !(sch.IsBaseType && p.Name == sch.DiscriminatorField) || (p.IsAliased || p.IsComplexObject) && !(p.IsInterface || p.IsStream) {
			return true
		}
	}
	if sch.IsTuple && (sch.AdditionalItems != nil && (sch.AdditionalItems.HasValidations || sch.AdditionalItems.Required)) {
		return true
	}
	if sch.HasAdditionalProperties && (sch.AdditionalProperties.IsInterface || sch.AdditionalProperties.IsStream) {
		return false
	}

	if sch.HasAdditionalProperties && (sch.AdditionalProperties.HasValidations || sch.AdditionalProperties.Required || sch.AdditionalProperties.IsAliased && !(sch.AdditionalProperties.IsInterface || sch.AdditionalProperties.IsStream)) {
		return true
	}

	if sch.IsAliased && (sch.IsPrimitive && sch.HasValidations) { // non primitive aliased have either other attributes with validation (above) or shall not validate
		return true
	}
	if sch.HasBaseType || sch.IsSubType {
		return true
	}
	return false
}

func makeGenDefinitionHierarchy(name, pkg, container string, schema spec.Schema, specDoc *loads.Document, opts *GenOpts) (*GenDefinition, error) {
	// Check if model is imported from external package using x-go-type
	_, external := schema.Extensions[xGoType]

	receiver := "m"
	// models are resolved in the current package
	resolver := newTypeResolver("", specDoc)
	resolver.ModelName = name
	analyzed := analysis.New(specDoc.Spec())

	di := discriminatorInfo(analyzed)

	pg := schemaGenContext{
		Path:                       "",
		Name:                       name,
		Receiver:                   receiver,
		IndexVar:                   "i",
		ValueExpr:                  receiver,
		Schema:                     schema,
		Required:                   false,
		TypeResolver:               resolver,
		Named:                      true,
		ExtraSchemas:               make(map[string]GenSchema),
		Discrimination:             di,
		Container:                  container,
		IncludeValidator:           opts.IncludeValidator,
		IncludeModel:               opts.IncludeModel,
		StrictAdditionalProperties: opts.StrictAdditionalProperties,
	}
	if err := pg.makeGenSchema(); err != nil {
		return nil, fmt.Errorf("could not generate schema for %s: %v", name, err)
	}
	dsi, ok := di.Discriminators["#/definitions/"+name]
	if ok {
		// when these 2 are true then the schema will render as an interface
		pg.GenSchema.IsBaseType = true
		pg.GenSchema.IsExported = true
		pg.GenSchema.DiscriminatorField = dsi.FieldName

		if pg.GenSchema.Discriminates == nil {
			pg.GenSchema.Discriminates = make(map[string]string)
		}
		pg.GenSchema.Discriminates[name] = dsi.GoType
		pg.GenSchema.DiscriminatorValue = name

		for _, v := range dsi.Children {
			pg.GenSchema.Discriminates[v.FieldValue] = v.GoType
		}

		for j := range pg.GenSchema.Properties {
			if !strings.HasSuffix(pg.GenSchema.Properties[j].ValueExpression, asMethod) {
				pg.GenSchema.Properties[j].ValueExpression += asMethod
			}
		}
	}

	dse, ok := di.Discriminated["#/definitions/"+name]
	if ok {
		pg.GenSchema.DiscriminatorField = dse.FieldName
		pg.GenSchema.DiscriminatorValue = dse.FieldValue
		pg.GenSchema.IsSubType = true
		knownProperties := make(map[string]struct{})

		// find the referenced definitions
		// check if it has a discriminator defined
		// when it has a discriminator get the schema and run makeGenSchema for it.
		// replace the ref with this new genschema
		swsp := specDoc.Spec()
		for i, ss := range schema.AllOf {
			ref := ss.Ref
			for ref.String() != "" {
				var rsch *spec.Schema
				var err error
				rsch, err = spec.ResolveRef(swsp, &ref)
				if err != nil {
					return nil, err
				}
				ref = rsch.Ref
				if rsch != nil && rsch.Ref.String() != "" {
					ref = rsch.Ref
					continue
				}
				ref = spec.Ref{}
				if rsch != nil && rsch.Discriminator != "" {
					gs, err := makeGenDefinitionHierarchy(strings.TrimPrefix(ss.Ref.String(), "#/definitions/"), pkg, pg.GenSchema.Name, *rsch, specDoc, opts)
					if err != nil {
						return nil, err
					}
					gs.GenSchema.IsBaseType = true
					gs.GenSchema.IsExported = true
					pg.GenSchema.AllOf[i] = gs.GenSchema
					schPtr := &(pg.GenSchema.AllOf[i])
					if schPtr.AdditionalItems != nil {
						schPtr.AdditionalItems.IsBaseType = true
					}
					if schPtr.AdditionalProperties != nil {
						schPtr.AdditionalProperties.IsBaseType = true
					}
					for j := range schPtr.Properties {
						schPtr.Properties[j].IsBaseType = true
						knownProperties[schPtr.Properties[j].Name] = struct{}{}
					}
				}
			}
		}

		// dedupe the fields
		alreadySeen := make(map[string]struct{})
		for i, ss := range pg.GenSchema.AllOf {
			var remainingProperties GenSchemaList
			for _, p := range ss.Properties {
				if _, ok := knownProperties[p.Name]; !ok || ss.IsBaseType {
					if _, seen := alreadySeen[p.Name]; !seen {
						remainingProperties = append(remainingProperties, p)
						alreadySeen[p.Name] = struct{}{}
					}
				}
			}
			pg.GenSchema.AllOf[i].Properties = remainingProperties
		}

	}

	defaultImports := []string{
		"github.com/go-openapi/errors",
		"github.com/go-openapi/runtime",
		"github.com/go-openapi/swag",
		"github.com/go-openapi/validate",
	}

	return &GenDefinition{
		GenCommon: GenCommon{
			Copyright:        opts.Copyright,
			TargetImportPath: filepath.ToSlash(opts.LanguageOpts.baseImport(opts.Target)),
		},
		Package:        opts.LanguageOpts.ManglePackageName(path.Base(filepath.ToSlash(pkg)), "definitions"),
		GenSchema:      pg.GenSchema,
		DependsOn:      pg.Dependencies,
		DefaultImports: defaultImports,
		ExtraSchemas:   gatherExtraSchemas(pg.ExtraSchemas),
		Imports:        findImports(&pg.GenSchema),
		External:       external,
	}, nil
}

func findImports(sch *GenSchema) map[string]string {
	imp := map[string]string{}
	t := sch.resolvedType
	if t.Pkg != "" && t.PkgAlias != "" {
		imp[t.PkgAlias] = t.Pkg
	}
	if sch.Items != nil {
		sub := findImports(sch.Items)
		for k, v := range sub {
			imp[k] = v
		}
	}
	if sch.AdditionalItems != nil {
		sub := findImports(sch.AdditionalItems)
		for k, v := range sub {
			imp[k] = v
		}
	}
	if sch.Object != nil {
		sub := findImports(sch.Object)
		for k, v := range sub {
			imp[k] = v
		}
	}
	if sch.Properties != nil {
		for _, p := range sch.Properties {
			sub := findImports(&p)
			for k, v := range sub {
				imp[k] = v
			}
		}
	}
	if sch.AdditionalProperties != nil {
		sub := findImports(sch.AdditionalProperties)
		for k, v := range sub {
			imp[k] = v
		}
	}
	if sch.AllOf != nil {
		for _, p := range sch.AllOf {
			sub := findImports(&p)
			for k, v := range sub {
				imp[k] = v
			}
		}
	}
	return imp
}

type schemaGenContext struct {
	Required                   bool
	AdditionalProperty         bool
	Untyped                    bool
	Named                      bool
	RefHandled                 bool
	IsVirtual                  bool
	IsTuple                    bool
	IncludeValidator           bool
	IncludeModel               bool
	StrictAdditionalProperties bool
	Index                      int

	Path         string
	Name         string
	ParamName    string
	Accessor     string
	Receiver     string
	IndexVar     string
	KeyVar       string
	ValueExpr    string
	Container    string
	Schema       spec.Schema
	TypeResolver *typeResolver

	GenSchema      GenSchema
	Dependencies   []string // NOTE: Dependencies is actually set nowhere
	ExtraSchemas   map[string]GenSchema
	Discriminator  *discor
	Discriminated  *discee
	Discrimination *discInfo
}

func (sg *schemaGenContext) NewSliceBranch(schema *spec.Schema) *schemaGenContext {
	debugLog("new slice branch %s (model: %s)", sg.Name, sg.TypeResolver.ModelName)
	pg := sg.shallowClone()
	indexVar := pg.IndexVar
	if pg.Path == "" {
		pg.Path = "strconv.Itoa(" + indexVar + ")"
	} else {
		pg.Path = pg.Path + "+ \".\" + strconv.Itoa(" + indexVar + ")"
	}
	// check who is parent, if it's a base type then rewrite the value expression
	if sg.Discrimination != nil && sg.Discrimination.Discriminators != nil {
		_, rewriteValueExpr := sg.Discrimination.Discriminators["#/definitions/"+sg.TypeResolver.ModelName]
		if (pg.IndexVar == "i" && rewriteValueExpr) || sg.GenSchema.ElemType.IsBaseType {
			if !sg.GenSchema.IsAliased {
				pg.ValueExpr = sg.Receiver + "." + swag.ToJSONName(sg.GenSchema.Name) + "Field"
			} else {
				pg.ValueExpr = sg.Receiver
			}
		}
	}
	sg.GenSchema.IsBaseType = sg.GenSchema.ElemType.HasDiscriminator
	pg.IndexVar = indexVar + "i"
	pg.ValueExpr = pg.ValueExpr + "[" + indexVar + "]"
	pg.Schema = *schema
	pg.Required = false
	if sg.IsVirtual {
		pg.TypeResolver = sg.TypeResolver.NewWithModelName(sg.TypeResolver.ModelName)
	}

	// when this is an anonymous complex object, this needs to become a ref
	return pg
}

func (sg *schemaGenContext) NewAdditionalItems(schema *spec.Schema) *schemaGenContext {
	debugLog("new additional items\n")

	pg := sg.shallowClone()
	indexVar := pg.IndexVar
	pg.Name = sg.Name + " items"
	itemsLen := 0
	if sg.Schema.Items != nil {
		itemsLen = sg.Schema.Items.Len()
	}
	var mod string
	if itemsLen > 0 {
		mod = "+" + strconv.Itoa(itemsLen)
	}
	if pg.Path == "" {
		pg.Path = "strconv.Itoa(" + indexVar + mod + ")"
	} else {
		pg.Path = pg.Path + "+ \".\" + strconv.Itoa(" + indexVar + mod + ")"
	}
	pg.IndexVar = indexVar
	pg.ValueExpr = sg.ValueExpr + "." + pascalize(sg.GoName()) + "Items[" + indexVar + "]"
	pg.Schema = spec.Schema{}
	if schema != nil {
		pg.Schema = *schema
	}
	pg.Required = false
	return pg
}

func (sg *schemaGenContext) NewTupleElement(schema *spec.Schema, index int) *schemaGenContext {
	debugLog("New tuple element\n")

	pg := sg.shallowClone()
	if pg.Path == "" {
		pg.Path = "\"" + strconv.Itoa(index) + "\""
	} else {
		pg.Path = pg.Path + "+ \".\"+\"" + strconv.Itoa(index) + "\""
	}
	pg.ValueExpr = pg.ValueExpr + ".P" + strconv.Itoa(index)

	pg.Required = true
	pg.IsTuple = true
	pg.Schema = *schema

	return pg
}

func (sg *schemaGenContext) NewStructBranch(name string, schema spec.Schema) *schemaGenContext {
	debugLog("new struct branch %s (parent %s)", sg.Name, sg.Container)
	pg := sg.shallowClone()
	if sg.Path == "" {
		pg.Path = fmt.Sprintf("%q", name)
	} else {
		pg.Path = pg.Path + "+\".\"+" + fmt.Sprintf("%q", name)
	}
	pg.Name = name
	pg.ValueExpr = pg.ValueExpr + "." + pascalize(goName(&schema, name))
	pg.Schema = schema
	for _, fn := range sg.Schema.Required {
		if name == fn {
			pg.Required = true
			break
		}
	}
	debugLog("made new struct branch %s (parent %s)", pg.Name, pg.Container)
	return pg
}

func (sg *schemaGenContext) shallowClone() *schemaGenContext {
	debugLog("cloning context %s\n", sg.Name)
	pg := new(schemaGenContext)
	*pg = *sg
	if pg.Container == "" {
		pg.Container = sg.Name
	}
	pg.GenSchema = GenSchema{}
	pg.Dependencies = nil
	pg.Named = false
	pg.Index = 0
	pg.IsTuple = false
	pg.IncludeValidator = sg.IncludeValidator
	pg.IncludeModel = sg.IncludeModel
	pg.StrictAdditionalProperties = sg.StrictAdditionalProperties
	return pg
}

func (sg *schemaGenContext) NewCompositionBranch(schema spec.Schema, index int) *schemaGenContext {
	debugLog("new composition branch %s (parent: %s, index: %d)", sg.Name, sg.Container, index)
	pg := sg.shallowClone()
	pg.Schema = schema
	pg.Name = "AO" + strconv.Itoa(index)
	if sg.Name != sg.TypeResolver.ModelName {
		pg.Name = sg.Name + pg.Name
	}
	pg.Index = index
	debugLog("made new composition branch %s (parent: %s)", pg.Name, pg.Container)
	return pg
}

func (sg *schemaGenContext) NewAdditionalProperty(schema spec.Schema) *schemaGenContext {
	debugLog("new additional property %s (expr: %s)", sg.Name, sg.ValueExpr)
	pg := sg.shallowClone()
	pg.Schema = schema
	if pg.KeyVar == "" {
		pg.ValueExpr = sg.ValueExpr
	}
	pg.KeyVar += "k"
	pg.ValueExpr += "[" + pg.KeyVar + "]"
	pg.Path = pg.KeyVar
	pg.GenSchema.Suffix = "Value"
	if sg.Path != "" {
		pg.Path = sg.Path + "+\".\"+" + pg.KeyVar
	}
	// propagates the special IsNullable override for maps of slices and
	// maps of aliased types.
	pg.GenSchema.IsMapNullOverride = sg.GenSchema.IsMapNullOverride
	return pg
}

func hasSliceValidations(model *spec.Schema) (hasSliceValidations bool) {
	hasSliceValidations = model.MaxItems != nil || model.MinItems != nil || model.UniqueItems || len(model.Enum) > 0
	return
}

func hasValidations(model *spec.Schema, isRequired bool) (hasValidation bool) {
	// NOTE: needsValidation has gone deprecated and is replaced by top-level's shallowValidationLookup()
	hasNumberValidation := model.Maximum != nil || model.Minimum != nil || model.MultipleOf != nil
	hasStringValidation := model.MaxLength != nil || model.MinLength != nil || model.Pattern != ""
	hasEnum := len(model.Enum) > 0

	// since this was added to deal with discriminator, we'll fix this when testing discriminated types
	simpleObject := len(model.Properties) > 0 && model.Discriminator == ""

	// lift validations from allOf branches
	hasAllOfValidation := false
	for _, s := range model.AllOf {
		hasAllOfValidation = hasValidations(&s, false)
		hasAllOfValidation = s.Ref.String() != "" || hasAllOfValidation
		if hasAllOfValidation {
			break
		}
	}

	hasValidation = hasNumberValidation || hasStringValidation || hasSliceValidations(model) || hasEnum || simpleObject || hasAllOfValidation || isRequired

	return
}

// handleFormatConflicts handles all conflicting model properties when a format is set
func handleFormatConflicts(model *spec.Schema) {
	switch model.Format {
	case "date", "datetime", "uuid", "bsonobjectid", "base64", "duration":
		model.MinLength = nil
		model.MaxLength = nil
		model.Pattern = ""
		// more cases should be inserted here if they arise
	}
}

func (sg *schemaGenContext) schemaValidations() sharedValidations {
	model := sg.Schema
	// resolve any conflicting properties if the model has a format
	handleFormatConflicts(&model)

	isRequired := sg.Required
	if model.Default != nil || model.ReadOnly {
		// when readOnly or default is specified, this disables Required validation (Swagger-specific)
		isRequired = false
	}
	hasSliceValidations := model.MaxItems != nil || model.MinItems != nil || model.UniqueItems || len(model.Enum) > 0
	hasValidations := hasValidations(&model, isRequired)

	s := sharedValidationsFromSchema(model, sg.Required)
	s.HasValidations = hasValidations
	s.HasSliceValidations = hasSliceValidations
	return s
}

func mergeValidation(other *schemaGenContext) bool {
	// NOTE: NeesRequired and NeedsValidation are deprecated
	if other.GenSchema.AdditionalProperties != nil && other.GenSchema.AdditionalProperties.HasValidations {
		return true
	}
	if other.GenSchema.AdditionalItems != nil && other.GenSchema.AdditionalItems.HasValidations {
		return true
	}
	for _, sch := range other.GenSchema.AllOf {
		if sch.HasValidations {
			return true
		}
	}
	return other.GenSchema.HasValidations
}

func (sg *schemaGenContext) MergeResult(other *schemaGenContext, liftsRequired bool) {
	sg.GenSchema.HasValidations = sg.GenSchema.HasValidations || mergeValidation(other)

	if liftsRequired && other.GenSchema.AdditionalProperties != nil && other.GenSchema.AdditionalProperties.Required {
		sg.GenSchema.Required = true
	}
	if liftsRequired && other.GenSchema.Required {
		sg.GenSchema.Required = other.GenSchema.Required
	}

	if other.GenSchema.HasBaseType {
		sg.GenSchema.HasBaseType = other.GenSchema.HasBaseType
	}

	sg.Dependencies = append(sg.Dependencies, other.Dependencies...)

	// lift extra schemas
	for k, v := range other.ExtraSchemas {
		sg.ExtraSchemas[k] = v
	}
	if other.GenSchema.IsMapNullOverride {
		sg.GenSchema.IsMapNullOverride = true
	}
}

func (sg *schemaGenContext) buildProperties() error {
	debugLog("building properties %s (parent: %s)", sg.Name, sg.Container)

	for k, v := range sg.Schema.Properties {
		debugLogAsJSON("building property %s[%q] (tup: %t) (BaseType: %t)",
			sg.Name, k, sg.IsTuple, sg.GenSchema.IsBaseType, sg.Schema)
		debugLog("property %s[%q] (tup: %t) HasValidations: %t)",
			sg.Name, k, sg.IsTuple, sg.GenSchema.HasValidations)

		// check if this requires de-anonymizing, if so lift this as a new struct and extra schema
		tpe, err := sg.TypeResolver.ResolveSchema(&v, true, sg.IsTuple || containsString(sg.Schema.Required, k))
		if sg.Schema.Discriminator == k {
			tpe.IsNullable = false
		}
		if err != nil {
			return err
		}

		vv := v
		var hasValidation bool
		if tpe.IsComplexObject && tpe.IsAnonymous && len(v.Properties) > 0 {
			// this is an anonymous complex construct: build a new new type for it
			pg := sg.makeNewStruct(sg.Name+swag.ToGoName(k), v)
			pg.IsTuple = sg.IsTuple
			if sg.Path != "" {
				pg.Path = sg.Path + "+ \".\"+" + fmt.Sprintf("%q", k)
			} else {
				pg.Path = fmt.Sprintf("%q", k)
			}
			if err := pg.makeGenSchema(); err != nil {
				return err
			}
			if v.Discriminator != "" {
				pg.GenSchema.IsBaseType = true
				pg.GenSchema.IsExported = true
				pg.GenSchema.HasBaseType = true
			}

			vv = *spec.RefProperty("#/definitions/" + pg.Name)
			hasValidation = pg.GenSchema.HasValidations
			sg.ExtraSchemas[pg.Name] = pg.GenSchema
			// NOTE: MergeResult lifts validation status and extra schemas
			sg.MergeResult(pg, false)
		}

		emprop := sg.NewStructBranch(k, vv)
		emprop.IsTuple = sg.IsTuple

		if err := emprop.makeGenSchema(); err != nil {
			return err
		}

		// whatever the validations says, if we have an interface{}, do not validate
		// NOTE: this may be the case when the type is left empty and we get a Enum validation.
		if emprop.GenSchema.IsInterface || emprop.GenSchema.IsStream {
			emprop.GenSchema.HasValidations = false
		} else if hasValidation || emprop.GenSchema.HasValidations || emprop.GenSchema.Required || emprop.GenSchema.IsAliased || len(emprop.GenSchema.AllOf) > 0 {
			emprop.GenSchema.HasValidations = true
			sg.GenSchema.HasValidations = true
		}

		// generates format validation on property
		emprop.GenSchema.HasValidations = emprop.GenSchema.HasValidations || (tpe.IsCustomFormatter && !tpe.IsStream) || (tpe.IsArray && tpe.ElemType.IsCustomFormatter && !tpe.ElemType.IsStream)

		if emprop.Schema.Ref.String() != "" {
			// expand the schema of this property, so we take informed decisions about its type
			ref := emprop.Schema.Ref
			var sch *spec.Schema
			for ref.String() != "" {
				var rsch *spec.Schema
				var err error
				specDoc := sg.TypeResolver.Doc
				rsch, err = spec.ResolveRef(specDoc.Spec(), &ref)
				if err != nil {
					return err
				}
				ref = rsch.Ref
				if rsch != nil && rsch.Ref.String() != "" {
					ref = rsch.Ref
					continue
				}
				ref = spec.Ref{}
				sch = rsch
			}

			if emprop.Discrimination != nil {
				if _, ok := emprop.Discrimination.Discriminators[emprop.Schema.Ref.String()]; ok {
					emprop.GenSchema.IsBaseType = true
					emprop.GenSchema.IsNullable = false
					emprop.GenSchema.HasBaseType = true
				}
				if _, ok := emprop.Discrimination.Discriminated[emprop.Schema.Ref.String()]; ok {
					emprop.GenSchema.IsSubType = true
				}
			}

			// set property name
			var nm = filepath.Base(emprop.Schema.Ref.GetURL().Fragment)

			tr := sg.TypeResolver.NewWithModelName(goName(&emprop.Schema, swag.ToGoName(nm)))
			ttpe, err := tr.ResolveSchema(sch, false, true)
			if err != nil {
				return err
			}
			if ttpe.IsAliased {
				emprop.GenSchema.IsAliased = true
			}

			// lift validations
			hv := hasValidations(sch, false)

			// include format validation, excluding binary
			hv = hv || (ttpe.IsCustomFormatter && !ttpe.IsStream) || (ttpe.IsArray && ttpe.ElemType.IsCustomFormatter && !ttpe.ElemType.IsStream)

			// a base type property is always validated against the base type
			// exception: for the base type definition itself (see shallowValidationLookup())
			if (hv || emprop.GenSchema.IsBaseType) && !(emprop.GenSchema.IsInterface || emprop.GenSchema.IsStream) {
				emprop.GenSchema.HasValidations = true
			}
			if ttpe.HasAdditionalItems && sch.AdditionalItems.Schema != nil {
				// when AdditionalItems specifies a Schema, there is a validation
				// check if we stepped upon an exception
				child, err := tr.ResolveSchema(sch.AdditionalItems.Schema, false, true)
				if err != nil {
					return err
				}
				if !child.IsInterface && !child.IsStream {
					emprop.GenSchema.HasValidations = true
				}
			}
			if ttpe.IsMap && sch.AdditionalProperties != nil && sch.AdditionalProperties.Schema != nil {
				// when AdditionalProperties specifies a Schema, there is a validation
				// check if we stepped upon an exception
				child, err := tr.ResolveSchema(sch.AdditionalProperties.Schema, false, true)
				if err != nil {
					return err
				}
				if !child.IsInterface && !child.IsStream {
					emprop.GenSchema.HasValidations = true
				}
			}
		}

		if sg.Schema.Discriminator == k {
			// this is the discriminator property:
			// it is required, but forced as non-nullable,
			// since we never fill it with a zero-value
			// TODO: when no other property than discriminator, there is no validation
			emprop.GenSchema.IsNullable = false
		}
		if emprop.GenSchema.IsBaseType {
			sg.GenSchema.HasBaseType = true
		}
		sg.MergeResult(emprop, false)

		// when discriminated, data is accessed via a getter func
		if emprop.GenSchema.HasDiscriminator {
			emprop.GenSchema.ValueExpression += asMethod
		}

		emprop.GenSchema.Extensions = emprop.Schema.Extensions

		// set custom serializer tag
		if customTag, found := emprop.Schema.Extensions[xGoCustomTag]; found {
			emprop.GenSchema.CustomTag = customTag.(string)
		}
		sg.GenSchema.Properties = append(sg.GenSchema.Properties, emprop.GenSchema)
	}
	sort.Sort(sg.GenSchema.Properties)

	return nil
}

func (sg *schemaGenContext) buildAllOf() error {
	if len(sg.Schema.AllOf) == 0 {
		return nil
	}

	var hasArray, hasNonArray int

	sort.Sort(sg.GenSchema.AllOf)
	if sg.Container == "" {
		sg.Container = sg.Name
	}
	debugLogAsJSON("building all of for %d entries", len(sg.Schema.AllOf), sg.Schema)
	for i, sch := range sg.Schema.AllOf {
		tpe, ert := sg.TypeResolver.ResolveSchema(&sch, sch.Ref.String() == "", false)
		if ert != nil {
			return ert
		}

		// check for multiple arrays in allOf branches.
		// Although a valid JSON-Schema construct, it is not suited for serialization.
		// This is the same if we attempt to serialize an array with another object.
		// We issue a generation warning on this.
		if tpe.IsArray {
			hasArray++
		} else {
			hasNonArray++
		}
		debugLogAsJSON("trying", sch)
		if (tpe.IsAnonymous && len(sch.AllOf) > 0) || (sch.Ref.String() == "" && !tpe.IsComplexObject && (tpe.IsArray || tpe.IsInterface || tpe.IsPrimitive)) {
			// cases where anonymous structures cause the creation of a new type:
			// - nested allOf: this one is itself a AllOf: build a new type for it
			// - anonymous simple types for edge cases: array, primitive, interface{}
			// NOTE: when branches are aliased or anonymous, the nullable property in the branch type is lost.
			name := swag.ToVarName(goName(&sch, sg.Name+"AllOf"+strconv.Itoa(i)))
			debugLog("building anonymous nested allOf in %s: %s", sg.Name, name)
			ng := sg.makeNewStruct(name, sch)
			if err := ng.makeGenSchema(); err != nil {
				return err
			}

			newsch := spec.RefProperty("#/definitions/" + ng.Name)
			sg.Schema.AllOf[i] = *newsch

			pg := sg.NewCompositionBranch(*newsch, i)
			if err := pg.makeGenSchema(); err != nil {
				return err
			}

			// lift extra schemas & validations from new type
			pg.MergeResult(ng, true)

			// lift validations when complex or ref'ed:
			// - parent always calls its Validatable child
			// - child may or may not have validations
			//
			// Exception: child is not Validatable when interface or stream
			if !pg.GenSchema.IsInterface && !pg.GenSchema.IsStream {
				sg.GenSchema.HasValidations = true
			}

			// add the newly created type to the list of schemas to be rendered inline
			pg.ExtraSchemas[ng.Name] = ng.GenSchema

			sg.MergeResult(pg, true)

			sg.GenSchema.AllOf = append(sg.GenSchema.AllOf, pg.GenSchema)

			continue
		}

		comprop := sg.NewCompositionBranch(sch, i)
		if err := comprop.makeGenSchema(); err != nil {
			return err
		}
		if comprop.GenSchema.IsMap && comprop.GenSchema.HasAdditionalProperties && comprop.GenSchema.AdditionalProperties != nil && !comprop.GenSchema.IsInterface {
			// the anonymous branch is a map for AdditionalProperties: rewrite value expression
			comprop.GenSchema.ValueExpression = comprop.GenSchema.ValueExpression + "." + comprop.Name
			comprop.GenSchema.AdditionalProperties.ValueExpression = comprop.GenSchema.ValueExpression + "[" + comprop.GenSchema.AdditionalProperties.KeyVar + "]"
		}

		// lift validations when complex or ref'ed
		if (comprop.GenSchema.IsComplexObject || comprop.Schema.Ref.String() != "") && !(comprop.GenSchema.IsInterface || comprop.GenSchema.IsStream) {
			comprop.GenSchema.HasValidations = true
		}
		sg.MergeResult(comprop, true)
		sg.GenSchema.AllOf = append(sg.GenSchema.AllOf, comprop.GenSchema)
	}

	if hasArray > 1 || (hasArray > 0 && hasNonArray > 0) {
		log.Printf("warning: cannot generate serializable allOf with conflicting array definitions in %s", sg.Container)
	}

	sg.GenSchema.IsNullable = true

	// prevent IsAliased to bubble up (e.g. when a single branch is itself aliased)
	sg.GenSchema.IsAliased = sg.GenSchema.IsAliased && len(sg.GenSchema.AllOf) < 2

	return nil
}

type mapStack struct {
	Type     *spec.Schema
	Next     *mapStack
	Previous *mapStack
	ValueRef *schemaGenContext
	Context  *schemaGenContext
	NewObj   *schemaGenContext
}

func newMapStack(context *schemaGenContext) (first, last *mapStack, err error) {
	ms := &mapStack{
		Type:    &context.Schema,
		Context: context,
	}

	l := ms
	for l.HasMore() {
		tpe, err := l.Context.TypeResolver.ResolveSchema(l.Type.AdditionalProperties.Schema, true, true)
		if err != nil {
			return nil, nil, err
		}

		if !tpe.IsMap {
			//reached the end of the rabbit hole
			if tpe.IsComplexObject && tpe.IsAnonymous {
				// found an anonymous object: create the struct from a newly created definition
				nw := l.Context.makeNewStruct(l.Context.Name+" Anon", *l.Type.AdditionalProperties.Schema)
				sch := spec.RefProperty("#/definitions/" + nw.Name)
				l.NewObj = nw

				l.Type.AdditionalProperties.Schema = sch
				l.ValueRef = l.Context.NewAdditionalProperty(*sch)
			}
			// other cases where to stop are: a $ref or a simple object
			break
		}

		// continue digging for maps
		l.Next = &mapStack{
			Previous: l,
			Type:     l.Type.AdditionalProperties.Schema,
			Context:  l.Context.NewAdditionalProperty(*l.Type.AdditionalProperties.Schema),
		}
		l = l.Next
	}

	//return top and bottom entries of this stack of AdditionalProperties
	return ms, l, nil
}

// Build rewinds the stack of additional properties, building schemas from bottom to top
func (mt *mapStack) Build() error {
	if mt.NewObj == nil && mt.ValueRef == nil && mt.Next == nil && mt.Previous == nil {
		csch := mt.Type.AdditionalProperties.Schema
		cp := mt.Context.NewAdditionalProperty(*csch)
		d := mt.Context.TypeResolver.Doc

		asch, err := analysis.Schema(analysis.SchemaOpts{
			Root:     d.Spec(),
			BasePath: d.SpecFilePath(),
			Schema:   csch,
		})
		if err != nil {
			return err
		}
		cp.Required = !asch.IsSimpleSchema && !asch.IsMap

		// when the schema is an array or an alias, this may result in inconsistent
		// nullable status between the map element and the array element (resp. the aliased type).
		//
		// Example: when an object has no property and only additionalProperties,
		// which turn out to be arrays of some other object.

		// save the initial override
		hadOverride := cp.GenSchema.IsMapNullOverride
		if err := cp.makeGenSchema(); err != nil {
			return err
		}

		// if we have an override at the top of stack, propagates it down nested arrays
		if hadOverride && cp.GenSchema.IsArray {
			// do it for nested arrays: override is also about map[string][][]... constructs
			it := &cp.GenSchema
			for it.Items != nil && it.IsArray {
				it.Items.IsMapNullOverride = hadOverride
				it = it.Items
			}
		}
		// cover other cases than arrays (aliased types)
		cp.GenSchema.IsMapNullOverride = hadOverride

		mt.Context.MergeResult(cp, false)
		mt.Context.GenSchema.AdditionalProperties = &cp.GenSchema

		// lift validations
		if (csch.Ref.String() != "" || cp.GenSchema.IsAliased) && !(cp.GenSchema.IsInterface || cp.GenSchema.IsStream) {
			// - we stopped on a ref, or anything else that require we call its Validate() method
			// - if the alias / ref is on an interface (or stream) type: no validation
			mt.Context.GenSchema.HasValidations = true
			mt.Context.GenSchema.AdditionalProperties.HasValidations = true
		}

		debugLog("early mapstack exit, nullable: %t for %s", cp.GenSchema.IsNullable, cp.GenSchema.Name)
		return nil
	}
	cur := mt
	for cur != nil {
		if cur.NewObj != nil {
			// a new model has been created during the stack construction (new ref on anonymous object)
			if err := cur.NewObj.makeGenSchema(); err != nil {
				return err
			}
		}

		if cur.ValueRef != nil {
			if err := cur.ValueRef.makeGenSchema(); err != nil {
				return nil
			}
		}

		if cur.NewObj != nil {
			// newly created model from anonymous object is declared as extra schema
			cur.Context.MergeResult(cur.NewObj, false)

			// propagates extra schemas
			cur.Context.ExtraSchemas[cur.NewObj.Name] = cur.NewObj.GenSchema
		}

		if cur.ValueRef != nil {
			// this is the genSchema for this new anonymous AdditionalProperty
			if err := cur.Context.makeGenSchema(); err != nil {
				return err
			}

			// if there is a ValueRef, we must have a NewObj (from newMapStack() construction)
			cur.ValueRef.GenSchema.HasValidations = cur.NewObj.GenSchema.HasValidations
			cur.Context.MergeResult(cur.ValueRef, false)
			cur.Context.GenSchema.AdditionalProperties = &cur.ValueRef.GenSchema
		}

		if cur.Previous != nil {
			// we have a parent schema: build a schema for current AdditionalProperties
			if err := cur.Context.makeGenSchema(); err != nil {
				return err
			}
		}
		if cur.Next != nil {
			// we previously made a child schema: lifts things from that one
			// - Required is not lifted (in a cascade of maps, only the last element is actually checked for Required)
			cur.Context.MergeResult(cur.Next.Context, false)
			cur.Context.GenSchema.AdditionalProperties = &cur.Next.Context.GenSchema

			// lift validations
			c := &cur.Next.Context.GenSchema
			if (cur.Next.Context.Schema.Ref.String() != "" || c.IsAliased) && !(c.IsInterface || c.IsStream) {
				// - we stopped on a ref, or anything else that require we call its Validate()
				// - if the alias / ref is on an interface (or stream) type: no validation
				cur.Context.GenSchema.HasValidations = true
				cur.Context.GenSchema.AdditionalProperties.HasValidations = true
			}
		}
		if cur.ValueRef != nil {
			cur.Context.MergeResult(cur.ValueRef, false)
			cur.Context.GenSchema.AdditionalProperties = &cur.ValueRef.GenSchema
		}

		if cur.Context.GenSchema.AdditionalProperties != nil {
			// propagate overrides up the resolved schemas, but leaves any ExtraSchema untouched
			cur.Context.GenSchema.AdditionalProperties.IsMapNullOverride = cur.Context.GenSchema.IsMapNullOverride
		}
		cur = cur.Previous
	}

	return nil
}

func (mt *mapStack) HasMore() bool {
	return mt.Type.AdditionalProperties != nil && (mt.Type.AdditionalProperties.Schema != nil || mt.Type.AdditionalProperties.Allows)
}

/* currently unused:
func (mt *mapStack) Dict() map[string]interface{} {
	res := make(map[string]interface{})
	res["context"] = mt.Context.Schema
	if mt.Next != nil {
		res["next"] = mt.Next.Dict()
	}
	if mt.NewObj != nil {
		res["obj"] = mt.NewObj.Schema
	}
	if mt.ValueRef != nil {
		res["value"] = mt.ValueRef.Schema
	}
	return res
}
*/

func (sg *schemaGenContext) buildAdditionalProperties() error {
	if sg.Schema.AdditionalProperties == nil {
		return nil
	}
	addp := *sg.Schema.AdditionalProperties

	wantsAdditional := addp.Schema != nil || addp.Allows
	sg.GenSchema.HasAdditionalProperties = wantsAdditional
	if !wantsAdditional {
		return nil
	}

	// flag swap
	if sg.GenSchema.IsComplexObject {
		sg.GenSchema.IsAdditionalProperties = true
		sg.GenSchema.IsComplexObject = false
		sg.GenSchema.IsMap = false
	}

	if addp.Schema == nil {
		// this is for AdditionalProperties:true|false
		if addp.Allows {
			// additionalProperties: true is rendered as: map[string]interface{}
			addp.Schema = &spec.Schema{}

			addp.Schema.Typed("object", "")
			sg.GenSchema.HasAdditionalProperties = true
			sg.GenSchema.IsComplexObject = false
			sg.GenSchema.IsMap = true

			sg.GenSchema.ValueExpression += "." + swag.ToGoName(sg.Name+" additionalProperties")
			cp := sg.NewAdditionalProperty(*addp.Schema)
			cp.Name += "AdditionalProperties"
			cp.Required = false
			if err := cp.makeGenSchema(); err != nil {
				return err
			}
			sg.MergeResult(cp, false)
			sg.GenSchema.AdditionalProperties = &cp.GenSchema
			debugLog("added interface{} schema for additionalProperties[allows == true], IsInterface=%t", cp.GenSchema.IsInterface)
		}
		return nil
	}

	if !sg.GenSchema.IsMap && (sg.GenSchema.IsAdditionalProperties && sg.Named) {
		// we have a complex object with an AdditionalProperties schema

		tpe, ert := sg.TypeResolver.ResolveSchema(addp.Schema, addp.Schema.Ref.String() == "", false)
		if ert != nil {
			return ert
		}

		if tpe.IsComplexObject && tpe.IsAnonymous {
			// if the AdditionalProperties is an anonymous complex object, generate a new type for it
			pg := sg.makeNewStruct(sg.Name+" Anon", *addp.Schema)
			if err := pg.makeGenSchema(); err != nil {
				return err
			}
			sg.MergeResult(pg, false)
			sg.ExtraSchemas[pg.Name] = pg.GenSchema

			sg.Schema.AdditionalProperties.Schema = spec.RefProperty("#/definitions/" + pg.Name)
			sg.IsVirtual = true

			comprop := sg.NewAdditionalProperty(*sg.Schema.AdditionalProperties.Schema)
			if err := comprop.makeGenSchema(); err != nil {
				return err
			}

			comprop.GenSchema.Required = true
			comprop.GenSchema.HasValidations = true

			comprop.GenSchema.ValueExpression = sg.GenSchema.ValueExpression + "." + swag.ToGoName(sg.GenSchema.Name) + "[" + comprop.KeyVar + "]"

			sg.GenSchema.AdditionalProperties = &comprop.GenSchema
			sg.GenSchema.HasAdditionalProperties = true
			sg.GenSchema.ValueExpression += "." + swag.ToGoName(sg.GenSchema.Name)

			sg.MergeResult(comprop, false)

			return nil
		}

		// this is a regular named schema for AdditionalProperties
		sg.GenSchema.ValueExpression += "." + swag.ToGoName(sg.GenSchema.Name)
		comprop := sg.NewAdditionalProperty(*addp.Schema)
		d := sg.TypeResolver.Doc
		asch, err := analysis.Schema(analysis.SchemaOpts{
			Root:     d.Spec(),
			BasePath: d.SpecFilePath(),
			Schema:   addp.Schema,
		})
		if err != nil {
			return err
		}
		comprop.Required = !asch.IsSimpleSchema && !asch.IsMap
		if err := comprop.makeGenSchema(); err != nil {
			return err
		}

		sg.MergeResult(comprop, false)
		sg.GenSchema.AdditionalProperties = &comprop.GenSchema
		sg.GenSchema.AdditionalProperties.ValueExpression = sg.GenSchema.ValueExpression + "[" + comprop.KeyVar + "]"

		// rewrite value expression for arrays and arrays of arrays in maps (rendered as map[string][][]...)
		if sg.GenSchema.AdditionalProperties.IsArray {
			// maps of slices are where an override may take effect
			sg.GenSchema.AdditionalProperties.Items.IsMapNullOverride = sg.GenSchema.AdditionalProperties.IsMapNullOverride
			sg.GenSchema.AdditionalProperties.Items.ValueExpression = sg.GenSchema.ValueExpression + "[" + comprop.KeyVar + "]" + "[" + sg.GenSchema.AdditionalProperties.IndexVar + "]"
			ap := sg.GenSchema.AdditionalProperties.Items
			for ap != nil && ap.IsArray {
				ap.Items.IsMapNullOverride = ap.IsMapNullOverride
				ap.Items.ValueExpression = ap.ValueExpression + "[" + ap.IndexVar + "]"
				ap = ap.Items
			}
		}

		// lift validation
		if (sg.GenSchema.AdditionalProperties.IsComplexObject || sg.GenSchema.AdditionalProperties.IsAliased || sg.GenSchema.AdditionalProperties.Required) && !(sg.GenSchema.AdditionalProperties.IsInterface || sg.GenSchema.IsStream) {
			sg.GenSchema.HasValidations = true
		}
		return nil
	}

	if sg.GenSchema.IsMap && wantsAdditional {
		// this is itself an AdditionalProperties schema with some AdditionalProperties.
		// this also runs for aliased map types (with zero properties save additionalProperties)
		//
		// find out how deep this rabbit hole goes
		// descend, unwind and rewrite
		// This needs to be depth first, so it first goes as deep as it can and then
		// builds the result in reverse order.
		_, ls, err := newMapStack(sg)
		if err != nil {
			return err
		}
		return ls.Build()
	}

	if sg.GenSchema.IsAdditionalProperties && !sg.Named {
		// for an anonymous object, first build the new object
		// and then replace the current one with a $ref to the
		// new object
		newObj := sg.makeNewStruct(sg.GenSchema.Name+" P"+strconv.Itoa(sg.Index), sg.Schema)
		if err := newObj.makeGenSchema(); err != nil {
			return err
		}

		hasMapNullOverride := sg.GenSchema.IsMapNullOverride
		sg.GenSchema = GenSchema{}
		sg.Schema = *spec.RefProperty("#/definitions/" + newObj.Name)
		if err := sg.makeGenSchema(); err != nil {
			return err
		}
		sg.MergeResult(newObj, false)

		sg.GenSchema.IsMapNullOverride = hasMapNullOverride
		if sg.GenSchema.IsArray {
			sg.GenSchema.Items.IsMapNullOverride = hasMapNullOverride
		}

		sg.GenSchema.HasValidations = newObj.GenSchema.HasValidations
		sg.ExtraSchemas[newObj.Name] = newObj.GenSchema
		return nil
	}
	return nil
}

func (sg *schemaGenContext) makeNewStruct(name string, schema spec.Schema) *schemaGenContext {
	debugLog("making new struct: name: %s, container: %s", name, sg.Container)
	sp := sg.TypeResolver.Doc.Spec()
	name = swag.ToGoName(name)
	if sg.TypeResolver.ModelName != sg.Name {
		name = swag.ToGoName(sg.TypeResolver.ModelName + " " + name)
	}
	if sp.Definitions == nil {
		sp.Definitions = make(spec.Definitions)
	}
	sp.Definitions[name] = schema
	pg := schemaGenContext{
		Path:                       "",
		Name:                       name,
		Receiver:                   sg.Receiver,
		IndexVar:                   "i",
		ValueExpr:                  sg.Receiver,
		Schema:                     schema,
		Required:                   false,
		Named:                      true,
		ExtraSchemas:               make(map[string]GenSchema),
		Discrimination:             sg.Discrimination,
		Container:                  sg.Container,
		IncludeValidator:           sg.IncludeValidator,
		IncludeModel:               sg.IncludeModel,
		StrictAdditionalProperties: sg.StrictAdditionalProperties,
	}
	if schema.Ref.String() == "" {
		pg.TypeResolver = sg.TypeResolver.NewWithModelName(name)
	}
	pg.GenSchema.IsVirtual = true

	sg.ExtraSchemas[name] = pg.GenSchema
	return &pg
}

func (sg *schemaGenContext) buildArray() error {
	tpe, err := sg.TypeResolver.ResolveSchema(sg.Schema.Items.Schema, true, false)
	if err != nil {
		return err
	}

	// check if the element is a complex object, if so generate a new type for it
	if tpe.IsComplexObject && tpe.IsAnonymous {
		pg := sg.makeNewStruct(sg.Name+" items"+strconv.Itoa(sg.Index), *sg.Schema.Items.Schema)
		if err := pg.makeGenSchema(); err != nil {
			return err
		}
		sg.MergeResult(pg, false)
		sg.ExtraSchemas[pg.Name] = pg.GenSchema
		sg.Schema.Items.Schema = spec.RefProperty("#/definitions/" + pg.Name)
		sg.IsVirtual = true
		return sg.makeGenSchema()
	}

	// create the generation schema for items
	elProp := sg.NewSliceBranch(sg.Schema.Items.Schema)

	// when building a slice of maps, the map item is not required
	// items from maps of aliased or nullable type remain required

	// NOTE(fredbi): since this is reset below, this Required = true serves the obscure purpose
	// of indirectly lifting validations from the slice. This is carried on differently now.
	// elProp.Required = true

	if err := elProp.makeGenSchema(); err != nil {
		return err
	}

	sg.MergeResult(elProp, false)

	sg.GenSchema.IsBaseType = elProp.GenSchema.IsBaseType
	sg.GenSchema.ItemsEnum = elProp.GenSchema.Enum
	elProp.GenSchema.Suffix = "Items"

	elProp.GenSchema.IsNullable = tpe.IsNullable && !tpe.HasDiscriminator
	if elProp.GenSchema.IsNullable {
		sg.GenSchema.GoType = "[]*" + elProp.GenSchema.GoType
	} else {
		sg.GenSchema.GoType = "[]" + elProp.GenSchema.GoType
	}

	sg.GenSchema.IsArray = true

	schemaCopy := elProp.GenSchema

	schemaCopy.Required = false

	// validations of items
	hv := hasValidations(sg.Schema.Items.Schema, false)

	// include format validation, excluding binary
	hv = hv || (schemaCopy.IsCustomFormatter && !schemaCopy.IsStream) || (schemaCopy.IsArray && schemaCopy.ElemType.IsCustomFormatter && !schemaCopy.ElemType.IsStream)

	// base types of polymorphic types must be validated
	// NOTE: IsNullable is not useful to figure out a validation: we use Refed and IsAliased below instead
	if hv || elProp.GenSchema.IsBaseType {
		schemaCopy.HasValidations = true
	}

	if (elProp.Schema.Ref.String() != "" || elProp.GenSchema.IsAliased) && !(elProp.GenSchema.IsInterface || elProp.GenSchema.IsStream) {
		schemaCopy.HasValidations = true
	}

	// lift validations
	sg.GenSchema.HasValidations = sg.GenSchema.HasValidations || schemaCopy.HasValidations
	sg.GenSchema.HasSliceValidations = hasSliceValidations(&sg.Schema)

	// prevents bubbling custom formatter flag
	sg.GenSchema.IsCustomFormatter = false

	sg.GenSchema.Items = &schemaCopy
	if sg.Named {
		sg.GenSchema.AliasedType = sg.GenSchema.GoType
	}

	return nil
}

func (sg *schemaGenContext) buildItems() error {
	if sg.Schema.Items == nil {
		// in swagger, arrays MUST have an items schema
		return nil
	}

	// in Items spec, we have either Schema (array) or Schemas (tuple)
	presentsAsSingle := sg.Schema.Items.Schema != nil
	if presentsAsSingle && sg.Schema.AdditionalItems != nil { // unsure if this a valid of invalid schema
		return fmt.Errorf("single schema (%s) can't have additional items", sg.Name)
	}
	if presentsAsSingle {
		return sg.buildArray()
	}

	// This is a tuple, build a new model that represents this
	if sg.Named {
		sg.GenSchema.Name = sg.Name
		sg.GenSchema.GoType = sg.TypeResolver.goTypeName(sg.Name)
		for i, s := range sg.Schema.Items.Schemas {
			elProp := sg.NewTupleElement(&s, i)

			if s.Ref.String() == "" {
				tpe, err := sg.TypeResolver.ResolveSchema(&s, s.Ref.String() == "", true)
				if err != nil {
					return err
				}
				if tpe.IsComplexObject && tpe.IsAnonymous {
					// if the tuple element is an anonymous complex object, build a new type for it
					pg := sg.makeNewStruct(sg.Name+" Items"+strconv.Itoa(i), s)
					if err := pg.makeGenSchema(); err != nil {
						return err
					}
					elProp.Schema = *spec.RefProperty("#/definitions/" + pg.Name)
					elProp.MergeResult(pg, false)
					elProp.ExtraSchemas[pg.Name] = pg.GenSchema
				}
			}

			if err := elProp.makeGenSchema(); err != nil {
				return err
			}
			if elProp.GenSchema.IsInterface || elProp.GenSchema.IsStream {
				elProp.GenSchema.HasValidations = false
			}
			sg.MergeResult(elProp, false)

			elProp.GenSchema.Name = "p" + strconv.Itoa(i)
			sg.GenSchema.Properties = append(sg.GenSchema.Properties, elProp.GenSchema)
			sg.GenSchema.IsTuple = true
		}
		return nil
	}

	// for an anonymous object, first build the new object
	// and then replace the current one with a $ref to the
	// new tuple object
	var sch spec.Schema
	sch.Typed("object", "")
	sch.Properties = make(map[string]spec.Schema, len(sg.Schema.Items.Schemas))
	for i, v := range sg.Schema.Items.Schemas {
		sch.Required = append(sch.Required, "P"+strconv.Itoa(i))
		sch.Properties["P"+strconv.Itoa(i)] = v
	}
	sch.AdditionalItems = sg.Schema.AdditionalItems
	tup := sg.makeNewStruct(sg.GenSchema.Name+"Tuple"+strconv.Itoa(sg.Index), sch)
	tup.IsTuple = true
	if err := tup.makeGenSchema(); err != nil {
		return err
	}
	tup.GenSchema.IsTuple = true
	tup.GenSchema.IsComplexObject = false
	tup.GenSchema.Title = tup.GenSchema.Name + " a representation of an anonymous Tuple type"
	tup.GenSchema.Description = ""
	sg.ExtraSchemas[tup.Name] = tup.GenSchema

	sg.Schema = *spec.RefProperty("#/definitions/" + tup.Name)
	if err := sg.makeGenSchema(); err != nil {
		return err
	}
	sg.MergeResult(tup, false)
	return nil
}

func (sg *schemaGenContext) buildAdditionalItems() error {
	wantsAdditionalItems :=
		sg.Schema.AdditionalItems != nil &&
			(sg.Schema.AdditionalItems.Allows || sg.Schema.AdditionalItems.Schema != nil)

	sg.GenSchema.HasAdditionalItems = wantsAdditionalItems
	if wantsAdditionalItems {
		// check if the element is a complex object, if so generate a new type for it
		tpe, err := sg.TypeResolver.ResolveSchema(sg.Schema.AdditionalItems.Schema, true, true)
		if err != nil {
			return err
		}
		if tpe.IsComplexObject && tpe.IsAnonymous {
			pg := sg.makeNewStruct(sg.Name+" Items", *sg.Schema.AdditionalItems.Schema)
			if err := pg.makeGenSchema(); err != nil {
				return err
			}
			sg.Schema.AdditionalItems.Schema = spec.RefProperty("#/definitions/" + pg.Name)
			pg.GenSchema.HasValidations = true
			sg.MergeResult(pg, false)
			sg.ExtraSchemas[pg.Name] = pg.GenSchema
		}

		it := sg.NewAdditionalItems(sg.Schema.AdditionalItems.Schema)
		// if AdditionalItems are themselves arrays, bump the index var
		if tpe.IsArray {
			it.IndexVar += "i"
		}

		if tpe.IsInterface {
			it.Untyped = true
		}

		if err := it.makeGenSchema(); err != nil {
			return err
		}

		// lift validations when complex is not anonymous or ref'ed
		if (tpe.IsComplexObject || it.Schema.Ref.String() != "") && !(tpe.IsInterface || tpe.IsStream) {
			it.GenSchema.HasValidations = true
		}

		sg.MergeResult(it, true)
		sg.GenSchema.AdditionalItems = &it.GenSchema
	}
	return nil
}

func (sg *schemaGenContext) buildXMLName() error {
	if sg.Schema.XML == nil {
		return nil
	}
	sg.GenSchema.XMLName = sg.Name

	if sg.Schema.XML.Name != "" {
		sg.GenSchema.XMLName = sg.Schema.XML.Name
		if sg.Schema.XML.Attribute {
			sg.GenSchema.XMLName += ",attr"
		}
	}
	return nil
}

func (sg *schemaGenContext) shortCircuitNamedRef() (bool, error) {
	// This if block ensures that a struct gets
	// rendered with the ref as embedded ref.
	//
	// NOTE: this assumes that all $ref point to a definition,
	// i.e. the spec is canonical, as guaranteed by minimal flattening.
	//
	// TODO: RefHandled is actually set nowhere
	if sg.RefHandled || !sg.Named || sg.Schema.Ref.String() == "" {
		return false, nil
	}
	debugLogAsJSON("short circuit named ref: %q", sg.Schema.Ref.String(), sg.Schema)

	// Simple aliased types (arrays, maps and primitives)
	//
	// Before deciding to make a struct with a composition branch (below),
	// check if the $ref points to a simple type or polymorphic (base) type.
	//
	// If this is the case, just realias this simple type, without creating a struct.
	asch, era := analysis.Schema(analysis.SchemaOpts{
		Root:     sg.TypeResolver.Doc.Spec(),
		BasePath: sg.TypeResolver.Doc.SpecFilePath(),
		Schema:   &sg.Schema,
	})
	if era != nil {
		return false, era
	}

	if asch.IsArray || asch.IsMap || asch.IsKnownType || asch.IsBaseType {
		tpx, ers := sg.TypeResolver.ResolveSchema(&sg.Schema, false, true)
		if ers != nil {
			return false, ers
		}
		tpe := resolvedType{}
		tpe.IsMap = asch.IsMap
		tpe.IsArray = asch.IsArray
		tpe.IsPrimitive = asch.IsKnownType

		tpe.IsAliased = true
		tpe.AliasedType = ""
		tpe.IsComplexObject = false
		tpe.IsAnonymous = false
		tpe.IsCustomFormatter = false
		tpe.IsBaseType = tpx.IsBaseType

		tpe.GoType = sg.TypeResolver.goTypeName(path.Base(sg.Schema.Ref.String()))

		tpe.IsNullable = tpx.IsNullable // TODO
		tpe.IsInterface = tpx.IsInterface
		tpe.IsStream = tpx.IsStream

		tpe.SwaggerType = tpx.SwaggerType
		sch := spec.Schema{}
		pg := sg.makeNewStruct(sg.Name, sch)
		if err := pg.makeGenSchema(); err != nil {
			return true, err
		}
		sg.MergeResult(pg, true)
		sg.GenSchema = pg.GenSchema
		sg.GenSchema.resolvedType = tpe
		sg.GenSchema.resolvedType.IsSuperAlias = true
		sg.GenSchema.IsBaseType = tpe.IsBaseType

		return true, nil
	}

	// Aliased object: use golang struct composition.
	// This is rendered as a struct with type field, i.e. :
	// Alias struct {
	//		AliasedType
	// }
	nullableOverride := sg.GenSchema.IsNullable

	tpe := resolvedType{}
	tpe.GoType = sg.TypeResolver.goTypeName(sg.Name)
	tpe.SwaggerType = "object"
	tpe.IsComplexObject = true
	tpe.IsMap = false
	tpe.IsArray = false
	tpe.IsAnonymous = false
	tpe.IsNullable = sg.TypeResolver.IsNullable(&sg.Schema)

	item := sg.NewCompositionBranch(sg.Schema, 0)
	if err := item.makeGenSchema(); err != nil {
		return true, err
	}
	sg.GenSchema.resolvedType = tpe
	sg.GenSchema.IsNullable = sg.GenSchema.IsNullable || nullableOverride
	// prevent format from bubbling up in composed type
	item.GenSchema.IsCustomFormatter = false

	sg.MergeResult(item, true)
	sg.GenSchema.AllOf = append(sg.GenSchema.AllOf, item.GenSchema)
	return true, nil
}

// liftSpecialAllOf attempts to simplify the rendering of allOf constructs by lifting simple things into the current schema.
func (sg *schemaGenContext) liftSpecialAllOf() error {
	// if there is only a $ref or a primitive and an x-isnullable schema then this is a nullable pointer
	// so this should not compose several objects, just 1
	// if there is a ref with a discriminator then we look for x-class on the current definition to know
	// the value of the discriminator to instantiate the class
	if len(sg.Schema.AllOf) < 2 {
		return nil
	}
	var seenSchema int
	var seenNullable bool
	var schemaToLift spec.Schema

	for _, sch := range sg.Schema.AllOf {

		tpe, err := sg.TypeResolver.ResolveSchema(&sch, true, true)
		if err != nil {
			return err
		}
		if sg.TypeResolver.IsNullable(&sch) {
			seenNullable = true
		}
		if len(sch.Type) > 0 || len(sch.Properties) > 0 || sch.Ref.GetURL() != nil || len(sch.AllOf) > 0 {
			seenSchema++
			if seenSchema > 1 {
				// won't do anything if several candidates for a lift
				break
			}
			if (!tpe.IsAnonymous && tpe.IsComplexObject) || tpe.IsPrimitive {
				// lifting complex objects here results in inlined structs in the model
				schemaToLift = sch
			}
		}
	}

	if seenSchema == 1 {
		// when there only a single schema to lift in allOf, replace the schema by its allOf definition
		debugLog("lifted schema in allOf for %s", sg.Name)
		sg.Schema = schemaToLift
		sg.GenSchema.IsNullable = seenNullable
	}
	return nil
}

func (sg *schemaGenContext) buildAliased() error {
	if !sg.GenSchema.IsPrimitive && !sg.GenSchema.IsMap && !sg.GenSchema.IsArray && !sg.GenSchema.IsInterface {
		return nil
	}

	if sg.GenSchema.IsPrimitive {
		if sg.GenSchema.SwaggerType == "string" && sg.GenSchema.SwaggerFormat == "" {
			sg.GenSchema.IsAliased = sg.GenSchema.GoType != sg.GenSchema.SwaggerType
		}
		if sg.GenSchema.IsNullable && sg.Named {
			sg.GenSchema.IsNullable = false
		}
	}

	if sg.GenSchema.IsInterface {
		sg.GenSchema.IsAliased = sg.GenSchema.GoType != iface
	}

	if sg.GenSchema.IsMap {
		sg.GenSchema.IsAliased = !strings.HasPrefix(sg.GenSchema.GoType, "map[")
	}

	if sg.GenSchema.IsArray {
		sg.GenSchema.IsAliased = !strings.HasPrefix(sg.GenSchema.GoType, "[]")
	}
	return nil
}

func (sg *schemaGenContext) GoName() string {
	return goName(&sg.Schema, sg.Name)
}

func goName(sch *spec.Schema, orig string) string {
	name, _ := sch.Extensions.GetString(xGoName)
	if name != "" {
		return name
	}
	return orig
}

func (sg *schemaGenContext) checkNeedsPointer(outer *GenSchema, sch *GenSchema, elem *GenSchema) {
	derefType := strings.TrimPrefix(elem.GoType, "*")
	switch {
	case outer.IsAliased && !strings.HasSuffix(outer.AliasedType, "*"+derefType):
		// override nullability of map of primitive elements: render element of aliased or anonymous map as a pointer
		outer.AliasedType = strings.TrimSuffix(outer.AliasedType, derefType) + "*" + derefType
	case sch != nil:
		// nullable primitive
		if sch.IsAnonymous && !strings.HasSuffix(outer.GoType, "*"+derefType) {
			sch.GoType = strings.TrimSuffix(sch.GoType, derefType) + "*" + derefType
		}
	case outer.IsAnonymous && !strings.HasSuffix(outer.GoType, "*"+derefType):
		outer.GoType = strings.TrimSuffix(outer.GoType, derefType) + "*" + derefType
	}
}

// buildMapOfNullable equalizes the nullablity status for aliased and anonymous maps of simple things,
// with the nullability of its innermost element.
//
// NOTE: at the moment, we decide to align the type of the outer element (map) to the type of the inner element
// The opposite could be done and result in non nullable primitive elements. If we do so, the validation
// code needs to be adapted by removing IsZero() and Required() calls in codegen.
func (sg *schemaGenContext) buildMapOfNullable(sch *GenSchema) {
	outer := &sg.GenSchema
	if sch == nil {
		sch = outer
	}
	if sch.IsMap && (outer.IsAliased || outer.IsAnonymous) {
		elem := sch.AdditionalProperties
		for elem != nil {
			if elem.IsPrimitive && elem.IsNullable {
				sg.checkNeedsPointer(outer, nil, elem)
			} else if elem.IsArray {
				// override nullability of array of primitive elements:
				// render element of aliased or anonyous map as a pointer
				it := elem.Items
				for it != nil {
					if it.IsPrimitive && it.IsNullable {
						sg.checkNeedsPointer(outer, sch, it)
					} else if it.IsMap {
						sg.buildMapOfNullable(it)
					}
					it = it.Items
				}
			}
			elem = elem.AdditionalProperties
		}
	}
}

func (sg *schemaGenContext) makeGenSchema() error {
	debugLogAsJSON("making gen schema (anon: %t, req: %t, tuple: %t) %s\n",
		!sg.Named, sg.Required, sg.IsTuple, sg.Name, sg.Schema)

	ex := ""
	if sg.Schema.Example != nil {
		ex = fmt.Sprintf("%#v", sg.Schema.Example)
	}
	sg.GenSchema.IsExported = true
	sg.GenSchema.Example = ex
	sg.GenSchema.Path = sg.Path
	sg.GenSchema.IndexVar = sg.IndexVar
	sg.GenSchema.Location = body
	sg.GenSchema.ValueExpression = sg.ValueExpr
	sg.GenSchema.KeyVar = sg.KeyVar
	sg.GenSchema.OriginalName = sg.Name
	sg.GenSchema.Name = sg.GoName()
	sg.GenSchema.Title = sg.Schema.Title
	sg.GenSchema.Description = trimBOM(sg.Schema.Description)
	sg.GenSchema.ReceiverName = sg.Receiver
	sg.GenSchema.sharedValidations = sg.schemaValidations()
	sg.GenSchema.ReadOnly = sg.Schema.ReadOnly
	sg.GenSchema.IncludeValidator = sg.IncludeValidator
	sg.GenSchema.IncludeModel = sg.IncludeModel
	sg.GenSchema.StrictAdditionalProperties = sg.StrictAdditionalProperties
	sg.GenSchema.Default = sg.Schema.Default

	var err error
	returns, err := sg.shortCircuitNamedRef()
	if err != nil {
		return err
	}
	if returns {
		return nil
	}
	debugLogAsJSON("after short circuit named ref", sg.Schema)

	if e := sg.liftSpecialAllOf(); e != nil {
		return e
	}
	nullableOverride := sg.GenSchema.IsNullable
	debugLogAsJSON("after lifting special all of", sg.Schema)

	if sg.Container == "" {
		sg.Container = sg.GenSchema.Name
	}
	if e := sg.buildAllOf(); e != nil {
		return e
	}

	var tpe resolvedType
	if sg.Untyped {
		tpe, err = sg.TypeResolver.ResolveSchema(nil, !sg.Named, sg.IsTuple || sg.Required || sg.GenSchema.Required)
	} else {
		tpe, err = sg.TypeResolver.ResolveSchema(&sg.Schema, !sg.Named, sg.IsTuple || sg.Required || sg.GenSchema.Required)
	}
	if err != nil {
		return err
	}

	debugLog("gschema rrequired: %t, nullable: %t", sg.GenSchema.Required, sg.GenSchema.IsNullable)
	tpe.IsNullable = tpe.IsNullable || nullableOverride
	sg.GenSchema.resolvedType = tpe
	sg.GenSchema.IsBaseType = tpe.IsBaseType
	sg.GenSchema.HasDiscriminator = tpe.HasDiscriminator

	// include format validations, excluding binary
	sg.GenSchema.HasValidations = sg.GenSchema.HasValidations || (tpe.IsCustomFormatter && !tpe.IsStream) || (tpe.IsArray && tpe.ElemType != nil && tpe.ElemType.IsCustomFormatter && !tpe.ElemType.IsStream)

	// usage of a polymorphic base type is rendered with getter funcs on private properties.
	// In the case of aliased types, the value expression remains unchanged to the receiver.
	if tpe.IsArray && tpe.ElemType != nil && tpe.ElemType.IsBaseType && sg.GenSchema.ValueExpression != sg.GenSchema.ReceiverName {
		sg.GenSchema.ValueExpression += asMethod
	}

	debugLog("gschema nullable: %t", sg.GenSchema.IsNullable)
	if e := sg.buildAdditionalProperties(); e != nil {
		return e
	}

	// rewrite value expression from top-down
	cur := &sg.GenSchema
	for cur.AdditionalProperties != nil {
		cur.AdditionalProperties.ValueExpression = cur.ValueExpression + "[" + cur.AdditionalProperties.KeyVar + "]"
		cur = cur.AdditionalProperties
	}

	prev := sg.GenSchema
	if sg.Untyped {
		debugLogAsJSON("untyped resolve:%t", sg.Named || sg.IsTuple || sg.Required || sg.GenSchema.Required, sg.Schema)
		tpe, err = sg.TypeResolver.ResolveSchema(nil, !sg.Named, sg.Named || sg.IsTuple || sg.Required || sg.GenSchema.Required)
	} else {
		debugLogAsJSON("typed resolve, isAnonymous(%t), n: %t, t: %t, sgr: %t, sr: %t, isRequired(%t), BaseType(%t)",
			!sg.Named, sg.Named, sg.IsTuple, sg.Required, sg.GenSchema.Required,
			sg.Named || sg.IsTuple || sg.Required || sg.GenSchema.Required, sg.GenSchema.IsBaseType, sg.Schema)
		tpe, err = sg.TypeResolver.ResolveSchema(&sg.Schema, !sg.Named, sg.Named || sg.IsTuple || sg.Required || sg.GenSchema.Required)
	}
	if err != nil {
		return err
	}
	otn := tpe.IsNullable // for debug only
	tpe.IsNullable = tpe.IsNullable || nullableOverride
	sg.GenSchema.resolvedType = tpe
	sg.GenSchema.IsComplexObject = prev.IsComplexObject
	sg.GenSchema.IsMap = prev.IsMap
	sg.GenSchema.IsAdditionalProperties = prev.IsAdditionalProperties
	sg.GenSchema.IsBaseType = sg.GenSchema.HasDiscriminator

	debugLogAsJSON("gschema nnullable:IsNullable:%t,resolver.IsNullable:%t,nullableOverride:%t",
		sg.GenSchema.IsNullable, otn, nullableOverride, sg.Schema)
	if err := sg.buildProperties(); err != nil {
		return err
	}

	if err := sg.buildXMLName(); err != nil {
		return err
	}

	if err := sg.buildAdditionalItems(); err != nil {
		return err
	}

	if err := sg.buildItems(); err != nil {
		return err
	}

	if err := sg.buildAliased(); err != nil {
		return err
	}

	sg.buildMapOfNullable(nil)

	debugLog("finished gen schema for %q", sg.Name)
	return nil
}
