func (items Items) String() string {
	result := ""
	for i, j := range items.Items {
		result += fmt.Sprintf("Item '%v' =\n", i) + text.Indent(j.String(), "  ")
	}
	return result
}
func (p Properties) String() string {
	result := ""
	for _, i := range p.SortedPropertyNames {
		result += "Property '" + i + "' =\n" + text.Indent(p.Properties[i].String(), "  ")
	}
	return result
}
func describeList(name string, value interface{}) string {
	if reflect.ValueOf(value).IsValid() {
		if !reflect.ValueOf(value).IsNil() {
			return fmt.Sprintf("%v\n", name) + text.Indent(fmt.Sprintf("%v", reflect.Indirect(reflect.ValueOf(value)).Interface()), "  ")
		}
	}
	return ""
}
// This is where we generate nested and compoound types in go to represent json payloads
// which are used as inputs and outputs for the REST API endpoints, and also for Pulse
// message bodies for the Exchange APIs.
// Returns the generated code content, and a map of keys of extra packages to import, e.g.
// a generated type might use time.Time, so if not imported, this would have to be added.
// using a map of strings -> bool to simulate a set - true => include
func generateGoTypes(schemaSet *SchemaSet) (string, StringSet, StringSet) {
	extraPackages := make(StringSet)
	rawMessageTypes := make(StringSet)
	content := "type (" // intentionally no \n here since each type starts with one already
	// Loop through all json schemas that were found referenced inside the API json schemas...
	typeDefinitions := make(map[string]string)
	typeNames := make([]string, 0, len(schemaSet.used))
	for _, i := range schemaSet.used {
		var newComment, newType string
		newComment, newType = i.typeDefinition(true, extraPackages, rawMessageTypes)
		typeDefinitions[i.TypeName] = text.Indent(newComment+i.TypeName+" "+newType, "\t")
		typeNames = append(typeNames, i.TypeName)
	}
	sort.Strings(typeNames)
	for _, t := range typeNames {
		content += typeDefinitions[t] + "\n"
	}
	return content + ")\n\n", extraPackages, rawMessageTypes
}
func (job *Job) Execute() (*Result, error) {
	// Generate normalised names for schemas. Keep a record of generated type
	// names, so that we don't reuse old names. Set acts like a set
	// of strings.
	job.result = new(Result)
	job.result.SchemaSet = &SchemaSet{
		all:       make(map[string]*JsonSubSchema),
		used:      make(map[string]*JsonSubSchema),
		TypeNames: make(StringSet),
	}
	if job.TypeNameBlacklist == nil {
		job.TypeNameBlacklist = make(StringSet)
	}
	if job.TypeNameGenerator == nil {
		job.TypeNameGenerator = text.GoIdentifierFrom
	}
	if job.MemberNameGenerator == nil {
		job.MemberNameGenerator = text.GoIdentifierFrom
	}
	for _, URL := range job.URLs {
		j, err := job.cacheJsonSchema(URL)
		if err != nil {
			return nil, err
		}
		// note we don't add inside cacheJsonSchema/loadJsonSchema
		// since we don't want to add e.g. top level items if only
		// definitions inside the schema are referenced
		job.add(j)
	}

	// link schemas (update cross references between schemas)
	for _, j := range job.result.SchemaSet.all {
		// if there is a referenced schema...
		if j.Ref != nil && *j.Ref != "" {
			// see if it is relative to current doc, or absolute reference
			var fullyQualifiedRef string
			if (*j.Ref)[0] == '#' {
				fullyQualifiedRef = j.SourceURL[:strings.Index(j.SourceURL, "#")] + *j.Ref
			} else {
				fullyQualifiedRef = sanitizeURL(*j.Ref)
			}
			j.RefSubSchema = job.result.SchemaSet.all[fullyQualifiedRef]
			job.add(j.RefSubSchema)
		}
	}

	var err error
	if !job.SkipCodeGen {
		types, extraPackages, rawMessageTypes := generateGoTypes(job.result.SchemaSet)
		content := `// This source code file is AUTO-GENERATED by github.com/taskcluster/jsonschema2go

package ` + job.Package + `

`
		extraPackagesContent := ""
		for j, k := range extraPackages {
			if k {
				extraPackagesContent += text.Indent(""+j+"\n", "\t")
			}
		}

		if extraPackagesContent != "" {
			content += `import (
` + extraPackagesContent + `)

`
		}
		content += types
		content += jsonRawMessageImplementors(rawMessageTypes)
		// format it
		job.result.SourceCode, err = format.Source([]byte(content))
		// imports should be good, so no need to run
		// https://godoc.org/golang.org/x/tools/imports#Process
	}
	return job.result, err
}
func (jsonSubSchema *JsonSubSchema) typeDefinition(topLevel bool, extraPackages StringSet, rawMessageTypes StringSet) (comment, typ string) {
	comment = "\n"
	if d := jsonSubSchema.Description; d != nil {
		comment += text.Indent(*d, "// ")
	}
	if comment[len(comment)-1:] != "\n" {
		comment += "\n"
	}
	if enum := jsonSubSchema.Enum; enum != nil {
		comment += "//\n// Possible values:\n"
		for _, i := range enum {
			switch i.(type) {
			case float64:
				comment += fmt.Sprintf("//   * %v\n", i)
			default:
				comment += fmt.Sprintf("//   * %q\n", i)
			}
		}
	}

	// Create comments for metadata in a single paragraph. Only start new
	// paragraph if we discover after inspecting all possible metadata, that
	// something has been specified. If there is no metadata, no need to create
	// a new paragraph.
	var metadata string
	if def := jsonSubSchema.Default; def != nil {
		var value string
		switch (*def).(type) {
		case bool:
			value = strconv.FormatBool((*def).(bool))
		case float64:
			value = strconv.FormatFloat((*def).(float64), 'g', -1, 64)
		default:
			value = fmt.Sprintf("%q", *def)
		}
		metadata += "// Default:    " + value + "\n"
	}
	if regex := jsonSubSchema.Pattern; regex != nil {
		metadata += "// Syntax:     " + *regex + "\n"
	}
	if minItems := jsonSubSchema.MinLength; minItems != nil {
		metadata += "// Min length: " + strconv.Itoa(*minItems) + "\n"
	}
	if maxItems := jsonSubSchema.MaxLength; maxItems != nil {
		metadata += "// Max length: " + strconv.Itoa(*maxItems) + "\n"
	}
	if minimum := jsonSubSchema.Minimum; minimum != nil {
		metadata += "// Mininum:    " + strconv.Itoa(*minimum) + "\n"
	}
	if maximum := jsonSubSchema.Maximum; maximum != nil {
		metadata += "// Maximum:    " + strconv.Itoa(*maximum) + "\n"
	}
	// Here we check if metadata was specified, and only create new
	// paragraph (`//\n`) if something was.
	if len(metadata) > 0 {
		comment += "//\n" + metadata
	}

	if URL := jsonSubSchema.SourceURL; URL != "" {
		u, err := url.Parse(URL)
		if err == nil && u.Scheme != "file" {
			comment += "//\n// See " + URL + "\n"
		}
	}
	for strings.Index(comment, "\n//\n") == 0 {
		comment = "\n" + comment[4:]
	}

	typ = "json.RawMessage"
	if p := jsonSubSchema.Type; p != nil {
		typ = *p
	}
	if p := jsonSubSchema.RefSubSchema; p != nil {
		typ = p.TypeName
	}
	switch typ {
	case "array":
		if jsonSubSchema.Items != nil {
			if jsonSubSchema.Items.Type != nil {
				var arrayType string
				_, arrayType = jsonSubSchema.Items.typeDefinition(false, extraPackages, rawMessageTypes)
				typ = "[]" + arrayType
			} else {
				if refSubSchema := jsonSubSchema.Items.RefSubSchema; refSubSchema != nil {
					typ = "[]" + refSubSchema.TypeName
				}
			}
		} else {
			typ = "[]interface{}"
		}
	case "object":
		if s := jsonSubSchema.Properties; s != nil {
			typ = fmt.Sprintf("struct {\n")
			for _, j := range s.SortedPropertyNames {
				// recursive call to build structs inside structs
				var subComment, subType string
				subMember := s.MemberNames[j]
				subComment, subType = s.Properties[j].typeDefinition(false, extraPackages, rawMessageTypes)
				jsonStructTagOptions := ""
				if !s.Properties[j].IsRequired {
					jsonStructTagOptions = ",omitempty"
				}
				// struct member name and type, as part of struct definition
				typ += fmt.Sprintf("\t%v%v %v `json:\"%v%v\"`\n", subComment, subMember, subType, j, jsonStructTagOptions)
			}
			typ += "}"
		} else {
			typ = "json.RawMessage"
		}
	case "number":
		typ = "float64"
	case "integer":
		typ = "int"
	case "boolean":
		typ = "bool"
	// json type string maps to go type string, so only need to test case of when
	// string is a json date-time, so we can convert to go type Time...
	case "string":
		if f := jsonSubSchema.Format; f != nil {
			if *f == "date-time" {
				typ = "tcclient.Time"
				extraPackages["tcclient \"github.com/taskcluster/taskcluster-client-go\""] = true
			}
		}
	}
	switch typ {
	case "json.RawMessage":
		extraPackages["\"encoding/json\""] = true
		if topLevel {
			// Special case: we have here a top level RawMessage such as
			// queue.PostArtifactRequest - therefore need to implement
			// Marhsal and Unmarshal methods. See:
			// http://play.golang.org/p/FKHSUmWVFD vs
			// http://play.golang.org/p/erjM6ptIYI
			extraPackages["\"errors\""] = true
			rawMessageTypes[jsonSubSchema.TypeName] = true
		}
	}
	return comment, typ
}