// Copyright 2022 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 "reflect"

// This files contains an optimized marshal and unmarshal implementation
// for the any type. This type is often used when the Go program has
// no knowledge of the JSON schema. This is a common enough occurrence
// to justify the complexity of adding logic for this.

func marshalValueAny(mo MarshalOptions, enc *Encoder, val any) error {
	switch val := val.(type) {
	case nil:
		return enc.WriteToken(Null)
	case bool:
		return enc.WriteToken(Bool(val))
	case string:
		return enc.WriteToken(String(val))
	case float64:
		return enc.WriteToken(Float(val))
	case map[string]any:
		return marshalObjectAny(mo, enc, val)
	case []any:
		return marshalArrayAny(mo, enc, val)
	default:
		v := newAddressableValue(reflect.TypeOf(val))
		v.Set(reflect.ValueOf(val))
		marshal := lookupArshaler(v.Type()).marshal
		if mo.Marshalers != nil {
			marshal, _ = mo.Marshalers.lookup(marshal, v.Type())
		}
		return marshal(mo, enc, v)
	}
}

func unmarshalValueAny(uo UnmarshalOptions, dec *Decoder) (any, error) {
	switch k := dec.PeekKind(); k {
	case '{':
		return unmarshalObjectAny(uo, dec)
	case '[':
		return unmarshalArrayAny(uo, dec)
	default:
		var flags valueFlags
		val, err := dec.readValue(&flags)
		if err != nil {
			return nil, err
		}
		switch val.Kind() {
		case 'n':
			return nil, nil
		case 'f':
			return false, nil
		case 't':
			return true, nil
		case '"':
			val = unescapeStringMayCopy(val, flags.isVerbatim())
			if dec.stringCache == nil {
				dec.stringCache = new(stringCache)
			}
			return dec.stringCache.make(val), nil
		case '0':
			fv, _ := parseFloat(val, 64) // ignore error since readValue gaurantees val is valid
			return fv, nil
		default:
			panic("BUG: invalid kind: " + k.String())
		}
	}
}

func marshalObjectAny(mo MarshalOptions, enc *Encoder, obj map[string]any) error {
	// Check for cycles.
	if enc.tokens.depth() > startDetectingCyclesAfter {
		v := reflect.ValueOf(obj)
		if err := enc.seenPointers.visit(v); err != nil {
			return err
		}
		defer enc.seenPointers.leave(v)
	}

	// Optimize for marshaling an empty map without any preceding whitespace.
	if len(obj) == 0 && !enc.options.multiline && !enc.tokens.last.needObjectName() {
		enc.buf = enc.tokens.mayAppendDelim(enc.buf, '{')
		enc.buf = append(enc.buf, "{}"...)
		enc.tokens.last.increment()
		if enc.needFlush() {
			return enc.flush()
		}
		return nil
	}

	if err := enc.WriteToken(ObjectStart); err != nil {
		return err
	}
	// A Go map guarantees that each entry has a unique key
	// The only possibility of duplicates is due to invalid UTF-8.
	if !enc.options.AllowInvalidUTF8 {
		enc.tokens.last.disableNamespace()
	}
	for name, val := range obj {
		if err := enc.WriteToken(String(name)); err != nil {
			return err
		}
		if err := marshalValueAny(mo, enc, val); err != nil {
			return err
		}
	}
	if err := enc.WriteToken(ObjectEnd); err != nil {
		return err
	}
	return nil
}

func unmarshalObjectAny(uo UnmarshalOptions, dec *Decoder) (map[string]any, error) {
	tok, err := dec.ReadToken()
	if err != nil {
		return nil, err
	}
	k := tok.Kind()
	switch k {
	case 'n':
		return nil, nil
	case '{':
		obj := make(map[string]any)
		// A Go map guarantees that each entry has a unique key
		// The only possibility of duplicates is due to invalid UTF-8.
		if !dec.options.AllowInvalidUTF8 {
			dec.tokens.last.disableNamespace()
		}
		for dec.PeekKind() != '}' {
			tok, err := dec.ReadToken()
			if err != nil {
				return obj, err
			}
			name := tok.String()

			// Manually check for duplicate names.
			if _, ok := obj[name]; ok {
				name := dec.previousBuffer()
				err := &SyntacticError{str: "duplicate name " + string(name) + " in object"}
				return obj, err.withOffset(dec.InputOffset() - int64(len(name)))
			}

			val, err := unmarshalValueAny(uo, dec)
			obj[name] = val
			if err != nil {
				return obj, err
			}
		}
		if _, err := dec.ReadToken(); err != nil {
			return obj, err
		}
		return obj, nil
	}
	return nil, &SemanticError{action: "unmarshal", JSONKind: k, GoType: mapStringAnyType}
}

func marshalArrayAny(mo MarshalOptions, enc *Encoder, arr []any) error {
	// Check for cycles.
	if enc.tokens.depth() > startDetectingCyclesAfter {
		v := reflect.ValueOf(arr)
		if err := enc.seenPointers.visit(v); err != nil {
			return err
		}
		defer enc.seenPointers.leave(v)
	}

	// Optimize for marshaling an empty slice without any preceding whitespace.
	if len(arr) == 0 && !enc.options.multiline && !enc.tokens.last.needObjectName() {
		enc.buf = enc.tokens.mayAppendDelim(enc.buf, '[')
		enc.buf = append(enc.buf, "[]"...)
		enc.tokens.last.increment()
		if enc.needFlush() {
			return enc.flush()
		}
		return nil
	}

	if err := enc.WriteToken(ArrayStart); err != nil {
		return err
	}
	for _, val := range arr {
		if err := marshalValueAny(mo, enc, val); err != nil {
			return err
		}
	}
	if err := enc.WriteToken(ArrayEnd); err != nil {
		return err
	}
	return nil
}

func unmarshalArrayAny(uo UnmarshalOptions, dec *Decoder) ([]any, error) {
	tok, err := dec.ReadToken()
	if err != nil {
		return nil, err
	}
	k := tok.Kind()
	switch k {
	case 'n':
		return nil, nil
	case '[':
		arr := []any{}
		for dec.PeekKind() != ']' {
			val, err := unmarshalValueAny(uo, dec)
			arr = append(arr, val)
			if err != nil {
				return arr, err
			}
		}
		if _, err := dec.ReadToken(); err != nil {
			return arr, err
		}
		return arr, nil
	}
	return nil, &SemanticError{action: "unmarshal", JSONKind: k, GoType: sliceAnyType}
}
