package xml

import (
	
	
	
	

	
)

// Value represents an XML Value type
// XML Value types: Object, Array, Map, String, Number, Boolean.
type Value struct {
	w       writer
	scratch *[]byte

	// xml start element is the associated start element for the Value
	startElement StartElement

	// indicates if the Value represents a flattened shape
	isFlattened bool
}

// newFlattenedValue returns a Value encoder. newFlattenedValue does NOT write the start element tag
func ( writer,  *[]byte,  StartElement) Value {
	return Value{
		w:            ,
		scratch:      ,
		startElement: ,
	}
}

// newValue writes the start element xml tag and returns a Value
func ( writer,  *[]byte,  StartElement) Value {
	writeStartElement(, )
	return Value{w: , scratch: , startElement: }
}

// writeStartElement takes in a start element and writes it.
// It handles namespace, attributes in start element.
func ( writer,  StartElement) error {
	if .isZero() {
		return fmt.Errorf("xml start element cannot be nil")
	}

	.WriteRune(leftAngleBracket)

	if len(.Name.Space) != 0 {
		escapeString(, .Name.Space)
		.WriteRune(colon)
	}
	escapeString(, .Name.Local)
	for ,  := range .Attr {
		.WriteRune(' ')
		writeAttribute(, &)
	}

	.WriteRune(rightAngleBracket)
	return nil
}

// writeAttribute writes an attribute from a provided Attribute
// For a namespace attribute, the attr.Name.Space must be defined as "xmlns".
// https://www.w3.org/TR/REC-xml-names/#NT-DefaultAttName
func ( writer,  *Attr) {
	// if local, space both are not empty
	if len(.Name.Space) != 0 && len(.Name.Local) != 0 {
		escapeString(, .Name.Space)
		.WriteRune(colon)
	}

	// if prefix is empty, the default `xmlns` space should be used as prefix.
	if len(.Name.Local) == 0 {
		.Name.Local = .Name.Space
	}

	escapeString(, .Name.Local)
	.WriteRune(equals)
	.WriteRune(quote)
	escapeString(, .Value)
	.WriteRune(quote)
}

// writeEndElement takes in a end element and writes it.
func ( writer,  EndElement) error {
	if .isZero() {
		return fmt.Errorf("xml end element cannot be nil")
	}

	.WriteRune(leftAngleBracket)
	.WriteRune(forwardSlash)

	if len(.Name.Space) != 0 {
		escapeString(, .Name.Space)
		.WriteRune(colon)
	}
	escapeString(, .Name.Local)
	.WriteRune(rightAngleBracket)

	return nil
}

// String encodes v as a XML string.
// It will auto close the parent xml element tag.
func ( Value) ( string) {
	escapeString(.w, )
	.Close()
}

// Byte encodes v as a XML number.
// It will auto close the parent xml element tag.
func ( Value) ( int8) {
	.Long(int64())
}

// Short encodes v as a XML number.
// It will auto close the parent xml element tag.
func ( Value) ( int16) {
	.Long(int64())
}

// Integer encodes v as a XML number.
// It will auto close the parent xml element tag.
func ( Value) ( int32) {
	.Long(int64())
}

// Long encodes v as a XML number.
// It will auto close the parent xml element tag.
func ( Value) ( int64) {
	*.scratch = strconv.AppendInt((*.scratch)[:0], , 10)
	.w.Write(*.scratch)

	.Close()
}

// Float encodes v as a XML number.
// It will auto close the parent xml element tag.
func ( Value) ( float32) {
	.float(float64(), 32)
	.Close()
}

// Double encodes v as a XML number.
// It will auto close the parent xml element tag.
func ( Value) ( float64) {
	.float(, 64)
	.Close()
}

func ( Value) ( float64,  int) {
	*.scratch = encoding.EncodeFloat((*.scratch)[:0], , )
	.w.Write(*.scratch)
}

// Boolean encodes v as a XML boolean.
// It will auto close the parent xml element tag.
func ( Value) ( bool) {
	*.scratch = strconv.AppendBool((*.scratch)[:0], )
	.w.Write(*.scratch)

	.Close()
}

// Base64EncodeBytes writes v as a base64 value in XML string.
// It will auto close the parent xml element tag.
func ( Value) ( []byte) {
	encodeByteSlice(.w, (*.scratch)[:0], )
	.Close()
}

// BigInteger encodes v big.Int as XML value.
// It will auto close the parent xml element tag.
func ( Value) ( *big.Int) {
	.w.Write([]byte(.Text(10)))
	.Close()
}

// BigDecimal encodes v big.Float as XML value.
// It will auto close the parent xml element tag.
func ( Value) ( *big.Float) {
	if ,  := .Int64();  == big.Exact {
		.Long()
		return
	}

	.w.Write([]byte(.Text('e', -1)))
	.Close()
}

// Write writes v directly to the xml document
// if escapeXMLText is set to true, write will escape text.
// It will auto close the parent xml element tag.
func ( Value) ( []byte,  bool) {
	// escape and write xml text
	if  {
		escapeText(.w, )
	} else {
		// write xml directly
		.w.Write()
	}

	.Close()
}

// MemberElement does member element encoding. It returns a Value.
// Member Element method should be used for all shapes except flattened shapes.
//
// A call to MemberElement will write nested element tags directly using the
// provided start element. The value returned by MemberElement should be closed.
func ( Value) ( StartElement) Value {
	return newValue(.w, .scratch, )
}

// FlattenedElement returns flattened element encoding. It returns a Value.
// This method should be used for flattened shapes.
//
// Unlike MemberElement, flattened element will NOT write element tags
// directly for the associated start element.
//
// The value returned by the FlattenedElement does not need to be closed.
func ( Value) ( StartElement) Value {
	 := newFlattenedValue(.w, .scratch, )
	.isFlattened = true
	return 
}

// Array returns an array encoder. By default, the members of array are
// wrapped with `<member>` element tag.
// If value is marked as flattened, the start element is used to wrap the members instead of
// the `<member>` element.
func ( Value) () *Array {
	return newArray(.w, .scratch, arrayMemberWrapper, .startElement, .isFlattened)
}

/*
ArrayWithCustomName returns an array encoder.

It takes named start element as an argument, the named start element will used to wrap xml array entries.
for eg, `<someList><customName>entry1</customName></someList>`
Here `customName` named start element will be wrapped on each array member.
*/
func ( Value) ( StartElement) *Array {
	return newArray(.w, .scratch, , .startElement, .isFlattened)
}

/*
Map returns a map encoder. By default, the map entries are
wrapped with `<entry>` element tag.

If value is marked as flattened, the start element is used to wrap the entry instead of
the `<member>` element.
*/
func ( Value) () *Map {
	// flattened map
	if .isFlattened {
		return newFlattenedMap(.w, .scratch, .startElement)
	}

	// un-flattened map
	return newMap(.w, .scratch)
}

// encodeByteSlice is modified copy of json encoder's encodeByteSlice.
// It is used to base64 encode a byte slice.
func ( writer,  []byte,  []byte) {
	if  == nil {
		return
	}

	 := base64.StdEncoding.EncodedLen(len())
	if  <= len() {
		// If the encoded bytes fit in e.scratch, avoid an extra
		// allocation and use the cheaper Encoding.Encode.
		 := [:]
		base64.StdEncoding.Encode(, )
		.Write()
	} else if  <= 1024 {
		// The encoded bytes are short enough to allocate for, and
		// Encoding.Encode is still cheaper.
		 := make([]byte, )
		base64.StdEncoding.Encode(, )
		.Write()
	} else {
		// The encoded bytes are too long to cheaply allocate, and
		// Encoding.Encode is no longer noticeably cheaper.
		 := base64.NewEncoder(base64.StdEncoding, )
		.Write()
		.Close()
	}
}

// IsFlattened returns true if value is for flattened shape.
func ( Value) () bool {
	return .isFlattened
}

// Close closes the value.
func ( Value) () {
	writeEndElement(.w, .startElement.End())
}