//
// Copyright (c) 2011-2019 Canonical Ltd
//
// 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 yaml

import (
	
	
	
	
	
	
	
	
	
	
)

type encoder struct {
	emitter  yaml_emitter_t
	event    yaml_event_t
	out      []byte
	flow     bool
	indent   int
	doneInit bool
}

func newEncoder() *encoder {
	 := &encoder{}
	yaml_emitter_initialize(&.emitter)
	yaml_emitter_set_output_string(&.emitter, &.out)
	yaml_emitter_set_unicode(&.emitter, true)
	return 
}

func newEncoderWithWriter( io.Writer) *encoder {
	 := &encoder{}
	yaml_emitter_initialize(&.emitter)
	yaml_emitter_set_output_writer(&.emitter, )
	yaml_emitter_set_unicode(&.emitter, true)
	return 
}

func ( *encoder) () {
	if .doneInit {
		return
	}
	if .indent == 0 {
		.indent = 4
	}
	.emitter.best_indent = .indent
	yaml_stream_start_event_initialize(&.event, yaml_UTF8_ENCODING)
	.emit()
	.doneInit = true
}

func ( *encoder) () {
	.emitter.open_ended = false
	yaml_stream_end_event_initialize(&.event)
	.emit()
}

func ( *encoder) () {
	yaml_emitter_delete(&.emitter)
}

func ( *encoder) () {
	// This will internally delete the e.event value.
	.must(yaml_emitter_emit(&.emitter, &.event))
}

func ( *encoder) ( bool) {
	if ! {
		 := .emitter.problem
		if  == "" {
			 = "unknown problem generating YAML content"
		}
		failf("%s", )
	}
}

func ( *encoder) ( string,  reflect.Value) {
	.init()
	var  *Node
	if .IsValid() {
		, _ = .Interface().(*Node)
	}
	if  != nil && .Kind == DocumentNode {
		.nodev()
	} else {
		yaml_document_start_event_initialize(&.event, nil, nil, true)
		.emit()
		.marshal(, )
		yaml_document_end_event_initialize(&.event, true)
		.emit()
	}
}

func ( *encoder) ( string,  reflect.Value) {
	 = shortTag()
	if !.IsValid() || .Kind() == reflect.Ptr && .IsNil() {
		.nilv()
		return
	}
	 := .Interface()
	switch value := .(type) {
	case *Node:
		.nodev()
		return
	case Node:
		if !.CanAddr() {
			var  = reflect.New(.Type()).Elem()
			.Set()
			 = 
		}
		.nodev(.Addr())
		return
	case time.Time:
		.timev(, )
		return
	case *time.Time:
		.timev(, .Elem())
		return
	case time.Duration:
		.stringv(, reflect.ValueOf(.String()))
		return
	case Marshaler:
		,  := .MarshalYAML()
		if  != nil {
			fail()
		}
		if  == nil {
			.nilv()
			return
		}
		.(, reflect.ValueOf())
		return
	case encoding.TextMarshaler:
		,  := .MarshalText()
		if  != nil {
			fail()
		}
		 = reflect.ValueOf(string())
	case nil:
		.nilv()
		return
	}
	switch .Kind() {
	case reflect.Interface:
		.(, .Elem())
	case reflect.Map:
		.mapv(, )
	case reflect.Ptr:
		.(, .Elem())
	case reflect.Struct:
		.structv(, )
	case reflect.Slice, reflect.Array:
		.slicev(, )
	case reflect.String:
		.stringv(, )
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		.intv(, )
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		.uintv(, )
	case reflect.Float32, reflect.Float64:
		.floatv(, )
	case reflect.Bool:
		.boolv(, )
	default:
		panic("cannot marshal type: " + .Type().String())
	}
}

func ( *encoder) ( string,  reflect.Value) {
	.mappingv(, func() {
		 := keyList(.MapKeys())
		sort.Sort()
		for ,  := range  {
			.marshal("", )
			.marshal("", .MapIndex())
		}
	})
}

func ( *encoder) ( reflect.Value,  []int) ( reflect.Value) {
	for ,  := range  {
		for {
			if .Kind() == reflect.Ptr {
				if .IsNil() {
					return reflect.Value{}
				}
				 = .Elem()
				continue
			}
			break
		}
		 = .Field()
	}
	return 
}

func ( *encoder) ( string,  reflect.Value) {
	,  := getStructInfo(.Type())
	if  != nil {
		panic()
	}
	.mappingv(, func() {
		for ,  := range .FieldsList {
			var  reflect.Value
			if .Inline == nil {
				 = .Field(.Num)
			} else {
				 = .fieldByIndex(, .Inline)
				if !.IsValid() {
					continue
				}
			}
			if .OmitEmpty && isZero() {
				continue
			}
			.marshal("", reflect.ValueOf(.Key))
			.flow = .Flow
			.marshal("", )
		}
		if .InlineMap >= 0 {
			 := .Field(.InlineMap)
			if .Len() > 0 {
				.flow = false
				 := keyList(.MapKeys())
				sort.Sort()
				for ,  := range  {
					if ,  := .FieldsMap[.String()];  {
						panic(fmt.Sprintf("cannot have key %q in inlined map: conflicts with struct field", .String()))
					}
					.marshal("", )
					.flow = false
					.marshal("", .MapIndex())
				}
			}
		}
	})
}

func ( *encoder) ( string,  func()) {
	 :=  == ""
	 := yaml_BLOCK_MAPPING_STYLE
	if .flow {
		.flow = false
		 = yaml_FLOW_MAPPING_STYLE
	}
	yaml_mapping_start_event_initialize(&.event, nil, []byte(), , )
	.emit()
	()
	yaml_mapping_end_event_initialize(&.event)
	.emit()
}

func ( *encoder) ( string,  reflect.Value) {
	 :=  == ""
	 := yaml_BLOCK_SEQUENCE_STYLE
	if .flow {
		.flow = false
		 = yaml_FLOW_SEQUENCE_STYLE
	}
	.must(yaml_sequence_start_event_initialize(&.event, nil, []byte(), , ))
	.emit()
	 := .Len()
	for  := 0;  < ; ++ {
		.marshal("", .Index())
	}
	.must(yaml_sequence_end_event_initialize(&.event))
	.emit()
}

// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
//
// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
// in YAML 1.2 and by this package, but these should be marshalled quoted for
// the time being for compatibility with other parsers.
func isBase60Float( string) ( bool) {
	// Fast path.
	if  == "" {
		return false
	}
	 := [0]
	if !( == '+' ||  == '-' ||  >= '0' &&  <= '9') || strings.IndexByte(, ':') < 0 {
		return false
	}
	// Do the full match.
	return base60float.MatchString()
}

// From http://yaml.org/type/float.html, except the regular expression there
// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)

// isOldBool returns whether s is bool notation as defined in YAML 1.1.
//
// We continue to force strings that YAML 1.1 would interpret as booleans to be
// rendered as quotes strings so that the marshalled output valid for YAML 1.1
// parsing.
func isOldBool( string) ( bool) {
	switch  {
	case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON",
		"n", "N", "no", "No", "NO", "off", "Off", "OFF":
		return true
	default:
		return false
	}
}

func ( *encoder) ( string,  reflect.Value) {
	var  yaml_scalar_style_t
	 := .String()
	 := true
	switch {
	case !utf8.ValidString():
		if  == binaryTag {
			failf("explicitly tagged !!binary data must be base64-encoded")
		}
		if  != "" {
			failf("cannot marshal invalid UTF-8 data as %s", shortTag())
		}
		// It can't be encoded directly as YAML so use a binary tag
		// and encode it as base64.
		 = binaryTag
		 = encodeBase64()
	case  == "":
		// Check to see if it would resolve to a specific
		// tag when encoded unquoted. If it doesn't,
		// there's no need to quote it.
		,  := resolve("", )
		 =  == strTag && !(isBase60Float() || isOldBool())
	}
	// Note: it's possible for user code to emit invalid YAML
	// if they explicitly specify a tag and a string containing
	// text that's incompatible with that tag.
	switch {
	case strings.Contains(, "\n"):
		if .flow {
			 = yaml_DOUBLE_QUOTED_SCALAR_STYLE
		} else {
			 = yaml_LITERAL_SCALAR_STYLE
		}
	case :
		 = yaml_PLAIN_SCALAR_STYLE
	default:
		 = yaml_DOUBLE_QUOTED_SCALAR_STYLE
	}
	.emitScalar(, "", , , nil, nil, nil, nil)
}

func ( *encoder) ( string,  reflect.Value) {
	var  string
	if .Bool() {
		 = "true"
	} else {
		 = "false"
	}
	.emitScalar(, "", , yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
}

func ( *encoder) ( string,  reflect.Value) {
	 := strconv.FormatInt(.Int(), 10)
	.emitScalar(, "", , yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
}

func ( *encoder) ( string,  reflect.Value) {
	 := strconv.FormatUint(.Uint(), 10)
	.emitScalar(, "", , yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
}

func ( *encoder) ( string,  reflect.Value) {
	 := .Interface().(time.Time)
	 := .Format(time.RFC3339Nano)
	.emitScalar(, "", , yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
}

func ( *encoder) ( string,  reflect.Value) {
	// Issue #352: When formatting, use the precision of the underlying value
	 := 64
	if .Kind() == reflect.Float32 {
		 = 32
	}

	 := strconv.FormatFloat(.Float(), 'g', -1, )
	switch  {
	case "+Inf":
		 = ".inf"
	case "-Inf":
		 = "-.inf"
	case "NaN":
		 = ".nan"
	}
	.emitScalar(, "", , yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
}

func ( *encoder) () {
	.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
}

func ( *encoder) (, ,  string,  yaml_scalar_style_t, , , ,  []byte) {
	// TODO Kill this function. Replace all initialize calls by their underlining Go literals.
	 :=  == ""
	if ! {
		 = longTag()
	}
	.must(yaml_scalar_event_initialize(&.event, []byte(), []byte(), []byte(), , , ))
	.event.head_comment = 
	.event.line_comment = 
	.event.foot_comment = 
	.event.tail_comment = 
	.emit()
}

func ( *encoder) ( reflect.Value) {
	.node(.Interface().(*Node), "")
}

func ( *encoder) ( *Node,  string) {
	// Zero nodes behave as nil.
	if .Kind == 0 && .IsZero() {
		.nilv()
		return
	}

	// If the tag was not explicitly requested, and dropping it won't change the
	// implicit tag of the value, don't include it in the presentation.
	var  = .Tag
	var  = shortTag()
	var  bool
	if  != "" && .Style&TaggedStyle == 0 {
		if .Kind == ScalarNode {
			if  == strTag && .Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 {
				 = ""
			} else {
				,  := resolve("", .Value)
				if  ==  {
					 = ""
				} else if  == strTag {
					 = ""
					 = true
				}
			}
		} else {
			var  string
			switch .Kind {
			case MappingNode:
				 = mapTag
			case SequenceNode:
				 = seqTag
			}
			if  ==  {
				 = ""
			}
		}
	}

	switch .Kind {
	case DocumentNode:
		yaml_document_start_event_initialize(&.event, nil, nil, true)
		.event.head_comment = []byte(.HeadComment)
		.emit()
		for ,  := range .Content {
			.(, "")
		}
		yaml_document_end_event_initialize(&.event, true)
		.event.foot_comment = []byte(.FootComment)
		.emit()

	case SequenceNode:
		 := yaml_BLOCK_SEQUENCE_STYLE
		if .Style&FlowStyle != 0 {
			 = yaml_FLOW_SEQUENCE_STYLE
		}
		.must(yaml_sequence_start_event_initialize(&.event, []byte(.Anchor), []byte(longTag()),  == "", ))
		.event.head_comment = []byte(.HeadComment)
		.emit()
		for ,  := range .Content {
			.(, "")
		}
		.must(yaml_sequence_end_event_initialize(&.event))
		.event.line_comment = []byte(.LineComment)
		.event.foot_comment = []byte(.FootComment)
		.emit()

	case MappingNode:
		 := yaml_BLOCK_MAPPING_STYLE
		if .Style&FlowStyle != 0 {
			 = yaml_FLOW_MAPPING_STYLE
		}
		yaml_mapping_start_event_initialize(&.event, []byte(.Anchor), []byte(longTag()),  == "", )
		.event.tail_comment = []byte()
		.event.head_comment = []byte(.HeadComment)
		.emit()

		// The tail logic below moves the foot comment of prior keys to the following key,
		// since the value for each key may be a nested structure and the foot needs to be
		// processed only the entirety of the value is streamed. The last tail is processed
		// with the mapping end event.
		var  string
		for  := 0; +1 < len(.Content);  += 2 {
			 := .Content[]
			 := .FootComment
			if  != "" {
				 := *
				.FootComment = ""
				 = &
			}
			.(, )
			 = 

			 := .Content[+1]
			.(, "")
		}

		yaml_mapping_end_event_initialize(&.event)
		.event.tail_comment = []byte()
		.event.line_comment = []byte(.LineComment)
		.event.foot_comment = []byte(.FootComment)
		.emit()

	case AliasNode:
		yaml_alias_event_initialize(&.event, []byte(.Value))
		.event.head_comment = []byte(.HeadComment)
		.event.line_comment = []byte(.LineComment)
		.event.foot_comment = []byte(.FootComment)
		.emit()

	case ScalarNode:
		 := .Value
		if !utf8.ValidString() {
			if  == binaryTag {
				failf("explicitly tagged !!binary data must be base64-encoded")
			}
			if  != "" {
				failf("cannot marshal invalid UTF-8 data as %s", )
			}
			// It can't be encoded directly as YAML so use a binary tag
			// and encode it as base64.
			 = binaryTag
			 = encodeBase64()
		}

		 := yaml_PLAIN_SCALAR_STYLE
		switch {
		case .Style&DoubleQuotedStyle != 0:
			 = yaml_DOUBLE_QUOTED_SCALAR_STYLE
		case .Style&SingleQuotedStyle != 0:
			 = yaml_SINGLE_QUOTED_SCALAR_STYLE
		case .Style&LiteralStyle != 0:
			 = yaml_LITERAL_SCALAR_STYLE
		case .Style&FoldedStyle != 0:
			 = yaml_FOLDED_SCALAR_STYLE
		case strings.Contains(, "\n"):
			 = yaml_LITERAL_SCALAR_STYLE
		case :
			 = yaml_DOUBLE_QUOTED_SCALAR_STYLE
		}

		.emitScalar(, .Anchor, , , []byte(.HeadComment), []byte(.LineComment), []byte(.FootComment), []byte())
	default:
		failf("cannot encode node with unknown kind %d", .Kind)
	}
}