// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package json

import (
	
	
	
	
	

	
	
)

// kind represents an encoding type.
type kind uint8

const (
	_ kind = (1 << iota) / 2
	name
	scalar
	objectOpen
	objectClose
	arrayOpen
	arrayClose
)

// Encoder provides methods to write out JSON constructs and values. The user is
// responsible for producing valid sequences of JSON constructs and values.
type Encoder struct {
	indent   string
	lastKind kind
	indents  []byte
	out      []byte
}

// NewEncoder returns an Encoder.
//
// If indent is a non-empty string, it causes every entry for an Array or Object
// to be preceded by the indent and trailed by a newline.
func ( string) (*Encoder, error) {
	 := &Encoder{}
	if len() > 0 {
		if strings.Trim(, " \t") != "" {
			return nil, errors.New("indent may only be composed of space or tab characters")
		}
		.indent = 
	}
	return , nil
}

// Bytes returns the content of the written bytes.
func ( *Encoder) () []byte {
	return .out
}

// WriteNull writes out the null value.
func ( *Encoder) () {
	.prepareNext(scalar)
	.out = append(.out, "null"...)
}

// WriteBool writes out the given boolean value.
func ( *Encoder) ( bool) {
	.prepareNext(scalar)
	if  {
		.out = append(.out, "true"...)
	} else {
		.out = append(.out, "false"...)
	}
}

// WriteString writes out the given string in JSON string value. Returns error
// if input string contains invalid UTF-8.
func ( *Encoder) ( string) error {
	.prepareNext(scalar)
	var  error
	if .out,  = appendString(.out, );  != nil {
		return 
	}
	return nil
}

// Sentinel error used for indicating invalid UTF-8.
var errInvalidUTF8 = errors.New("invalid UTF-8")

func ( []byte,  string) ([]byte, error) {
	 = append(, '"')
	 := indexNeedEscapeInString()
	,  = [:], append(, [:]...)
	for len() > 0 {
		switch ,  := utf8.DecodeRuneInString(); {
		case  == utf8.RuneError &&  == 1:
			return , errInvalidUTF8
		case  < ' ' ||  == '"' ||  == '\\':
			 = append(, '\\')
			switch  {
			case '"', '\\':
				 = append(, byte())
			case '\b':
				 = append(, 'b')
			case '\f':
				 = append(, 'f')
			case '\n':
				 = append(, 'n')
			case '\r':
				 = append(, 'r')
			case '\t':
				 = append(, 't')
			default:
				 = append(, 'u')
				 = append(, "0000"[1+(bits.Len32(uint32())-1)/4:]...)
				 = strconv.AppendUint(, uint64(), 16)
			}
			 = [:]
		default:
			 := indexNeedEscapeInString([:])
			,  = [+:], append(, [:+]...)
		}
	}
	 = append(, '"')
	return , nil
}

// indexNeedEscapeInString returns the index of the character that needs
// escaping. If no characters need escaping, this returns the input length.
func ( string) int {
	for ,  := range  {
		if  < ' ' ||  == '\\' ||  == '"' ||  == utf8.RuneError {
			return 
		}
	}
	return len()
}

// WriteFloat writes out the given float and bitSize in JSON number value.
func ( *Encoder) ( float64,  int) {
	.prepareNext(scalar)
	.out = appendFloat(.out, , )
}

// appendFloat formats given float in bitSize, and appends to the given []byte.
func ( []byte,  float64,  int) []byte {
	switch {
	case math.IsNaN():
		return append(, `"NaN"`...)
	case math.IsInf(, +1):
		return append(, `"Infinity"`...)
	case math.IsInf(, -1):
		return append(, `"-Infinity"`...)
	}

	// JSON number formatting logic based on encoding/json.
	// See floatEncoder.encode for reference.
	 := byte('f')
	if  := math.Abs();  != 0 {
		if  == 64 && ( < 1e-6 ||  >= 1e21) ||
			 == 32 && (float32() < 1e-6 || float32() >= 1e21) {
			 = 'e'
		}
	}
	 = strconv.AppendFloat(, , , -1, )
	if  == 'e' {
		 := len()
		if  >= 4 && [-4] == 'e' && [-3] == '-' && [-2] == '0' {
			[-2] = [-1]
			 = [:-1]
		}
	}
	return 
}

// WriteInt writes out the given signed integer in JSON number value.
func ( *Encoder) ( int64) {
	.prepareNext(scalar)
	.out = append(.out, strconv.FormatInt(, 10)...)
}

// WriteUint writes out the given unsigned integer in JSON number value.
func ( *Encoder) ( uint64) {
	.prepareNext(scalar)
	.out = append(.out, strconv.FormatUint(, 10)...)
}

// StartObject writes out the '{' symbol.
func ( *Encoder) () {
	.prepareNext(objectOpen)
	.out = append(.out, '{')
}

// EndObject writes out the '}' symbol.
func ( *Encoder) () {
	.prepareNext(objectClose)
	.out = append(.out, '}')
}

// WriteName writes out the given string in JSON string value and the name
// separator ':'. Returns error if input string contains invalid UTF-8, which
// should not be likely as protobuf field names should be valid.
func ( *Encoder) ( string) error {
	.prepareNext(name)
	var  error
	// Append to output regardless of error.
	.out,  = appendString(.out, )
	.out = append(.out, ':')
	return 
}

// StartArray writes out the '[' symbol.
func ( *Encoder) () {
	.prepareNext(arrayOpen)
	.out = append(.out, '[')
}

// EndArray writes out the ']' symbol.
func ( *Encoder) () {
	.prepareNext(arrayClose)
	.out = append(.out, ']')
}

// prepareNext adds possible comma and indentation for the next value based
// on last type and indent option. It also updates lastKind to next.
func ( *Encoder) ( kind) {
	defer func() {
		// Set lastKind to next.
		.lastKind = 
	}()

	if len(.indent) == 0 {
		// Need to add comma on the following condition.
		if .lastKind&(scalar|objectClose|arrayClose) != 0 &&
			&(name|scalar|objectOpen|arrayOpen) != 0 {
			.out = append(.out, ',')
			// For single-line output, add a random extra space after each
			// comma to make output unstable.
			if detrand.Bool() {
				.out = append(.out, ' ')
			}
		}
		return
	}

	switch {
	case .lastKind&(objectOpen|arrayOpen) != 0:
		// If next type is NOT closing, add indent and newline.
		if &(objectClose|arrayClose) == 0 {
			.indents = append(.indents, .indent...)
			.out = append(.out, '\n')
			.out = append(.out, .indents...)
		}

	case .lastKind&(scalar|objectClose|arrayClose) != 0:
		switch {
		// If next type is either a value or name, add comma and newline.
		case &(name|scalar|objectOpen|arrayOpen) != 0:
			.out = append(.out, ',', '\n')

		// If next type is a closing object or array, adjust indentation.
		case &(objectClose|arrayClose) != 0:
			.indents = .indents[:len(.indents)-len(.indent)]
			.out = append(.out, '\n')
		}
		.out = append(.out, .indents...)

	case .lastKind&name != 0:
		.out = append(.out, ' ')
		// For multi-line output, add a random extra space after key: to make
		// output unstable.
		if detrand.Bool() {
			.out = append(.out, ' ')
		}
	}
}