package hjson

import (
	"bytes"
	"encoding"
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"strings"
)

const maxPointerDepth = 512

type commentInfo struct {
	hasComment bool
	cmStart    int
	cmEnd      int
}

// If a destination type implements ElemTyper, Unmarshal() will call ElemType()
// on the destination when unmarshalling an array or an object, to see if any
// array element or leaf node should be of type string even if it can be treated
// as a number, boolean or null. This is most useful if the destination also
// implements the json.Unmarshaler interface, because then there is no other way
// for Unmarshal() to know the type of the elements on the destination. If a
// destination implements ElemTyper all of its elements must be of the same
// type.
type ElemTyper interface {
	// Returns the desired type of any elements. If ElemType() is implemented
	// using a pointer receiver it must be possible to call with nil as receiver.
	ElemType() reflect.Type
}

// DecoderOptions defines options for decoding Hjson.
type DecoderOptions struct {
	// UseJSONNumber causes the Decoder to unmarshal a number into an interface{} as a
	// json.Number instead of as a float64.
	UseJSONNumber bool
	// DisallowUnknownFields causes an error to be returned when the destination
	// is a struct and the input contains object keys which do not match any
	// non-ignored, exported fields in the destination.
	DisallowUnknownFields bool
	// DisallowDuplicateKeys causes an error to be returned if an object (map) in
	// the Hjson input contains duplicate keys. If DisallowDuplicateKeys is set
	// to false, later values will silently overwrite previous values for the
	// same key.
	DisallowDuplicateKeys bool
	// WhitespaceAsComments only has any effect when an hjson.Node struct (or
	// an *hjson.Node pointer) is used as target for Unmarshal. If
	// WhitespaceAsComments is set to true, all whitespace and comments are stored
	// in the Node structs so that linefeeds and custom indentation is kept. If
	// WhitespaceAsComments instead is set to false, only actual comments are
	// stored as comments in Node structs.
	WhitespaceAsComments bool
}

// DefaultDecoderOptions returns the default decoding options.
func DefaultDecoderOptions() DecoderOptions {
	return DecoderOptions{
		UseJSONNumber:         false,
		DisallowUnknownFields: false,
		DisallowDuplicateKeys: false,
		WhitespaceAsComments:  true,
	}
}

type hjsonParser struct {
	DecoderOptions
	data              []byte
	at                int  // The index of the current character
	ch                byte // The current character
	structTypeCache   map[reflect.Type]structFieldMap
	willMarshalToJSON bool
	nodeDestination   bool
}

var unmarshalerText = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
var elemTyper = reflect.TypeOf((*ElemTyper)(nil)).Elem()

func (p *hjsonParser) setComment1(pCm *string, ci commentInfo) {
	if ci.hasComment {
		*pCm = string(p.data[ci.cmStart:ci.cmEnd])
	}
}

func (p *hjsonParser) setComment2(pCm *string, ciA, ciB commentInfo) {
	if ciA.hasComment && ciB.hasComment {
		*pCm = string(p.data[ciA.cmStart:ciA.cmEnd]) + string(p.data[ciB.cmStart:ciB.cmEnd])
	} else {
		p.setComment1(pCm, ciA)
		p.setComment1(pCm, ciB)
	}
}

func (p *hjsonParser) resetAt() {
	p.at = 0
	p.next()
}

func isPunctuatorChar(c byte) bool {
	return c == '{' || c == '}' || c == '[' || c == ']' || c == ',' || c == ':'
}

func (p *hjsonParser) errAt(message string) error {
	if p.at <= len(p.data) {
		var i int
		col := 0
		line := 1
		for i = p.at - 1; i > 0 && p.data[i] != '\n'; i-- {
			col++
		}
		for ; i > 0; i-- {
			if p.data[i] == '\n' {
				line++
			}
		}
		samEnd := p.at - col + 20
		if samEnd > len(p.data) {
			samEnd = len(p.data)
		}
		return fmt.Errorf("%s at line %d,%d >>> %s", message, line, col, string(p.data[p.at-col:samEnd]))
	}
	return errors.New(message)
}

func (p *hjsonParser) next() bool {
	// get the next character.
	if p.at < len(p.data) {
		p.ch = p.data[p.at]
		p.at++
		return true
	}
	p.at++
	p.ch = 0
	return false
}

func (p *hjsonParser) prev() bool {
	// get the previous character.
	if p.at > 1 {
		p.ch = p.data[p.at-2]
		p.at--
		return true
	}

	return false
}

func (p *hjsonParser) peek(offs int) byte {
	pos := p.at + offs
	if pos >= 0 && pos < len(p.data) {
		return p.data[p.at+offs]
	}
	return 0
}

var escapee = map[byte]byte{
	'"':  '"',
	'\'': '\'',
	'\\': '\\',
	'/':  '/',
	'b':  '\b',
	'f':  '\f',
	'n':  '\n',
	'r':  '\r',
	't':  '\t',
}

func unravelDestination(dest reflect.Value, t reflect.Type) (reflect.Value, reflect.Type) {
	if dest.IsValid() {
		for a := 0; a < maxPointerDepth && (dest.Kind() == reflect.Ptr ||
			dest.Kind() == reflect.Interface) && !dest.IsNil(); a++ {

			dest = dest.Elem()
		}

		if dest.IsValid() {
			t = dest.Type()
		}
	}

	for a := 0; a < maxPointerDepth && t != nil && t.Kind() == reflect.Ptr; a++ {
		t = t.Elem()
	}

	return dest, t
}

func (p *hjsonParser) readString(allowML bool) (string, error) {

	// Parse a string value.
	res := new(bytes.Buffer)

	// callers make sure that (ch === '"' || ch === "'")
	// When parsing for string values, we must look for " and \ characters.
	exitCh := p.ch
	for p.next() {
		if p.ch == exitCh {
			p.next()
			if allowML && exitCh == '\'' && p.ch == '\'' && res.Len() == 0 {
				// ''' indicates a multiline string
				p.next()
				return p.readMLString()
			} else {
				return res.String(), nil
			}
		}
		if p.ch == '\\' {
			p.next()
			if p.ch == 'u' {
				uffff := 0
				for i := 0; i < 4; i++ {
					p.next()
					var hex int
					if p.ch >= '0' && p.ch <= '9' {
						hex = int(p.ch - '0')
					} else if p.ch >= 'a' && p.ch <= 'f' {
						hex = int(p.ch - 'a' + 0xa)
					} else if p.ch >= 'A' && p.ch <= 'F' {
						hex = int(p.ch - 'A' + 0xa)
					} else {
						return "", p.errAt("Bad \\u char " + string(p.ch))
					}
					uffff = uffff*16 + hex
				}
				res.WriteRune(rune(uffff))
			} else if ech, ok := escapee[p.ch]; ok {
				res.WriteByte(ech)
			} else {
				return "", p.errAt("Bad escape \\" + string(p.ch))
			}
		} else if p.ch == '\n' || p.ch == '\r' {
			return "", p.errAt("Bad string containing newline")
		} else {
			res.WriteByte(p.ch)
		}
	}
	return "", p.errAt("Bad string")
}

func (p *hjsonParser) readMLString() (value string, err error) {

	// Parse a multiline string value.
	res := new(bytes.Buffer)
	triple := 0

	// we are at ''' +1 - get indent
	indent := 0
	for {
		c := p.peek(-indent - 5)
		if c == 0 || c == '\n' {
			break
		}
		indent++
	}

	skipIndent := func() {
		skip := indent
		for p.ch > 0 && p.ch <= ' ' && p.ch != '\n' && skip > 0 {
			skip--
			p.next()
		}
	}

	// skip white/to (newline)
	for p.ch > 0 && p.ch <= ' ' && p.ch != '\n' {
		p.next()
	}
	if p.ch == '\n' {
		p.next()
		skipIndent()
	}

	// When parsing multiline string values, we must look for ' characters.
	lastLf := false
	for {
		if p.ch == 0 {
			return "", p.errAt("Bad multiline string")
		} else if p.ch == '\'' {
			triple++
			p.next()
			if triple == 3 {
				sres := res.Bytes()
				if lastLf {
					return string(sres[0 : len(sres)-1]), nil // remove last EOL
				}
				return string(sres), nil
			}
			continue
		} else {
			for triple > 0 {
				res.WriteByte('\'')
				triple--
				lastLf = false
			}
		}
		if p.ch == '\n' {
			res.WriteByte('\n')
			lastLf = true
			p.next()
			skipIndent()
		} else {
			if p.ch != '\r' {
				res.WriteByte(p.ch)
				lastLf = false
			}
			p.next()
		}
	}
}

func (p *hjsonParser) readKeyname() (string, error) {

	// quotes for keys are optional in Hjson
	// unless they include {}[],: or whitespace.

	if p.ch == '"' || p.ch == '\'' {
		return p.readString(false)
	}

	name := new(bytes.Buffer)
	start := p.at
	space := -1
	for {
		if p.ch == ':' {
			if name.Len() == 0 {
				return "", p.errAt("Found ':' but no key name (for an empty key name use quotes)")
			} else if space >= 0 && space != name.Len() {
				p.at = start + space
				return "", p.errAt("Found whitespace in your key name (use quotes to include)")
			}
			return name.String(), nil
		} else if p.ch <= ' ' {
			if p.ch == 0 {
				return "", p.errAt("Found EOF while looking for a key name (check your syntax)")
			}
			if space < 0 {
				space = name.Len()
			}
		} else {
			if isPunctuatorChar(p.ch) {
				return "", p.errAt("Found '" + string(p.ch) + "' where a key name was expected (check your syntax or use quotes if the key name includes {}[],: or whitespace)")
			}
			name.WriteByte(p.ch)
		}
		p.next()
	}
}

func (p *hjsonParser) commonWhite(onlyAfter bool) (commentInfo, bool) {
	ci := commentInfo{
		false,
		p.at - 1,
		0,
	}
	var hasLineFeed bool

	for p.ch > 0 {
		// Skip whitespace.
		for p.ch > 0 && p.ch <= ' ' {
			if p.ch == '\n' {
				hasLineFeed = true
				if onlyAfter {
					ci.cmEnd = p.at - 1
					// Skip EOL.
					p.next()
					return ci, hasLineFeed
				}
			}
			p.next()
		}
		// Hjson allows comments
		if p.ch == '#' || p.ch == '/' && p.peek(0) == '/' {
			ci.hasComment = p.nodeDestination
			for p.ch > 0 && p.ch != '\n' {
				p.next()
			}
		} else if p.ch == '/' && p.peek(0) == '*' {
			ci.hasComment = p.nodeDestination
			p.next()
			p.next()
			for p.ch > 0 && !(p.ch == '*' && p.peek(0) == '/') {
				p.next()
			}
			if p.ch > 0 {
				p.next()
				p.next()
			}
		} else {
			break
		}
	}

	// cmEnd is the first char after the comment (i.e. not included in the comment).
	ci.cmEnd = p.at - 1

	return ci, hasLineFeed
}

func (p *hjsonParser) white() commentInfo {
	ci, _ := p.commonWhite(false)

	ci.hasComment = (ci.hasComment || (p.WhitespaceAsComments && (ci.cmEnd > ci.cmStart)))

	return ci
}

func (p *hjsonParser) whiteAfterComma() commentInfo {
	ci, hasLineFeed := p.commonWhite(true)

	ci.hasComment = (ci.hasComment || (p.WhitespaceAsComments &&
		hasLineFeed && (ci.cmEnd > ci.cmStart)))

	return ci
}

func (p *hjsonParser) getCommentAfter() commentInfo {
	ci, _ := p.commonWhite(true)

	ci.hasComment = (ci.hasComment || (p.WhitespaceAsComments && (ci.cmEnd > ci.cmStart)))

	return ci
}

func (p *hjsonParser) maybeWrapNode(n *Node, v interface{}) (interface{}, error) {
	if p.nodeDestination {
		n.Value = v
		return n, nil
	}
	return v, nil
}

func (p *hjsonParser) readTfnns(dest reflect.Value, t reflect.Type) (interface{}, error) {

	// Hjson strings can be quoteless
	// returns string, (json.Number or float64), true, false, or null.
	// Or wraps the value in a Node.

	if isPunctuatorChar(p.ch) {
		return nil, p.errAt("Found a punctuator character '" + string(p.ch) + "' when expecting a quoteless string (check your syntax)")
	}
	chf := p.ch
	var node Node
	value := new(bytes.Buffer)
	value.WriteByte(p.ch)

	var newT reflect.Type
	if !p.nodeDestination {
		// Keep the original dest and t, because we need to check if it implements
		// encoding.TextUnmarshaler.
		_, newT = unravelDestination(dest, t)
	}

	for {
		p.next()
		isEol := p.ch == '\r' || p.ch == '\n' || p.ch == 0
		if isEol ||
			p.ch == ',' || p.ch == '}' || p.ch == ']' ||
			p.ch == '#' ||
			p.ch == '/' && (p.peek(0) == '/' || p.peek(0) == '*') {

			// Do not output anything else than a string if our destination is a string.
			// Pointer methods can be called if the destination is addressable,
			// therefore we also check if dest.Addr() implements encoding.TextUnmarshaler.
			// But "null" is a special case: unmarshal it as nil if the original
			// destination type is a pointer.
			if chf == 'n' && !p.nodeDestination && t != nil && t.Kind() == reflect.Ptr &&
				strings.TrimSpace(value.String()) == "null" {

				return p.maybeWrapNode(&node, nil)
			}
			if (newT == nil || newT.Kind() != reflect.String) &&
				(t == nil || !(t.Implements(unmarshalerText) ||
					dest.CanAddr() && dest.Addr().Type().Implements(unmarshalerText))) {

				switch chf {
				case 'f':
					if strings.TrimSpace(value.String()) == "false" {
						return p.maybeWrapNode(&node, false)
					}
				case 'n':
					if strings.TrimSpace(value.String()) == "null" {
						return p.maybeWrapNode(&node, nil)
					}
				case 't':
					if strings.TrimSpace(value.String()) == "true" {
						return p.maybeWrapNode(&node, true)
					}
				default:
					if chf == '-' || chf >= '0' && chf <= '9' {
						// Always use json.Number if we will marshal to JSON.
						if n, err := tryParseNumber(
							value.Bytes(),
							false,
							p.willMarshalToJSON || p.DecoderOptions.UseJSONNumber,
						); err == nil {
							return p.maybeWrapNode(&node, n)
						}
					}
				}
			}

			if isEol {
				// remove any whitespace at the end (ignored in quoteless strings)
				return p.maybeWrapNode(&node, strings.TrimSpace(value.String()))
			}
		}
		value.WriteByte(p.ch)
	}
}

// t must not have been unraveled
func getElemTyperType(rv reflect.Value, t reflect.Type) reflect.Type {
	var elemType reflect.Type
	isElemTyper := false

	if t != nil && t.Implements(elemTyper) {
		isElemTyper = true
		if t.Kind() == reflect.Ptr {
			// If ElemType() has a value receiver we would get a panic if we call it
			// on a nil pointer.
			if !rv.IsValid() || rv.IsNil() {
				rv = reflect.New(t.Elem())
			}
		} else if !rv.IsValid() {
			rv = reflect.Zero(t)
		}
	}
	if !isElemTyper && rv.CanAddr() {
		rv = rv.Addr()
		if rv.Type().Implements(elemTyper) {
			isElemTyper = true
		}
	}
	if !isElemTyper && t != nil {
		pt := reflect.PtrTo(t)
		if pt.Implements(elemTyper) {
			isElemTyper = true
			rv = reflect.Zero(pt)
		}
	}
	if isElemTyper {
		elemType = rv.Interface().(ElemTyper).ElemType()
	}

	return elemType
}

func (p *hjsonParser) readArray(dest reflect.Value, t reflect.Type) (value interface{}, err error) {
	var node Node
	array := make([]interface{}, 0, 1)

	// Skip '['.
	p.next()
	ciBefore := p.getCommentAfter()
	p.setComment1(&node.Cm.InsideFirst, ciBefore)
	ciBefore = p.white()

	if p.ch == ']' {
		p.setComment1(&node.Cm.InsideLast, ciBefore)
		p.next()
		return p.maybeWrapNode(&node, array) // empty array
	}

	var elemType reflect.Type
	if !p.nodeDestination {
		elemType = getElemTyperType(dest, t)

		dest, t = unravelDestination(dest, t)

		// All elements in any existing slice/array will be removed, so we only care
		// about the type of the new elements that will be created.
		if elemType == nil && t != nil && (t.Kind() == reflect.Slice || t.Kind() == reflect.Array) {
			elemType = t.Elem()
		}
	}

	for p.ch > 0 {
		var elemNode *Node
		var val interface{}
		if val, err = p.readValue(reflect.Value{}, elemType); err != nil {
			return nil, err
		}
		if p.nodeDestination {
			var ok bool
			if elemNode, ok = val.(*Node); ok {
				p.setComment1(&elemNode.Cm.Before, ciBefore)
			}
		}
		// Check white before comma because comma might be on other line.
		ciAfter := p.white()
		// in Hjson the comma is optional and trailing commas are allowed
		if p.ch == ',' {
			p.next()
			ciAfterComma := p.whiteAfterComma()
			if elemNode != nil {
				existingAfter := elemNode.Cm.After
				p.setComment2(&elemNode.Cm.After, ciAfter, ciAfterComma)
				elemNode.Cm.After = existingAfter + elemNode.Cm.After
			}
			// Any comments starting on the line after the comma.
			ciAfter = p.white()
		}
		if p.ch == ']' {
			p.setComment1(&node.Cm.InsideLast, ciAfter)
			array = append(array, val)
			p.next()
			return p.maybeWrapNode(&node, array)
		}
		array = append(array, val)
		ciBefore = ciAfter
	}

	return nil, p.errAt("End of input while parsing an array (did you forget a closing ']'?)")
}

func (p *hjsonParser) readObject(
	withoutBraces bool,
	dest reflect.Value,
	t reflect.Type,
	ciBefore commentInfo,
) (value interface{}, err error) {
	// Parse an object value.
	var node Node
	var elemNode *Node
	object := NewOrderedMap()

	// If withoutBraces == true we use the input argument ciBefore as
	// Before-comment on the first element of this obj, or as InnerLast-comment
	// on this obj if it doesn't contain any elements. If withoutBraces == false
	// we ignore the input ciBefore.

	if !withoutBraces {
		// assuming ch == '{'
		p.next()
		ciInsideFirst := p.getCommentAfter()
		p.setComment1(&node.Cm.InsideFirst, ciInsideFirst)
		ciBefore = p.white()
		if p.ch == '}' {
			p.setComment1(&node.Cm.InsideLast, ciBefore)
			p.next()
			return p.maybeWrapNode(&node, object) // empty object
		}
	}

	var stm structFieldMap

	var elemType reflect.Type
	if !p.nodeDestination {
		elemType = getElemTyperType(dest, t)

		dest, t = unravelDestination(dest, t)

		if elemType == nil && t != nil {
			switch t.Kind() {
			case reflect.Struct:
				var ok bool
				stm, ok = p.structTypeCache[t]
				if !ok {
					stm = getStructFieldInfoMap(t)
					p.structTypeCache[t] = stm
				}

			case reflect.Map:
				// For any key that we find in our loop here below, the new value fully
				// replaces any old value. So no need for us to dig down into a tree.
				// (This is because we are decoding into a map. If we were decoding into
				// a struct we would need to dig down into a tree, to match the behavior
				// of Golang's JSON decoder.)
				elemType = t.Elem()
			}
		}
	}

	for p.ch > 0 {
		var key string
		if key, err = p.readKeyname(); err != nil {
			return nil, err
		}
		ciKey := p.white()
		if p.ch != ':' {
			return nil, p.errAt("Expected ':' instead of '" + string(p.ch) + "'")
		}
		p.next()

		var newDest reflect.Value
		var newDestType reflect.Type
		if stm != nil {
			sfi, ok := stm.getField(key)
			if ok {
				// The field might be found on the root struct or in embedded structs.
				newDest, newDestType = dest, t
				for _, i := range sfi.indexPath {
					newDest, newDestType = unravelDestination(newDest, newDestType)

					if newDestType == nil {
						return nil, p.errAt("Internal error")
					}
					newDestType = newDestType.Field(i).Type
					elemType = newDestType

					if newDest.IsValid() {
						if newDest.Kind() != reflect.Struct {
							// We are only keeping track of newDest in case it contains a
							// tree that we will partially update. But here we have not found
							// any tree, so we can ignore newDest and just look at
							// newDestType instead.
							newDest = reflect.Value{}
						} else {
							newDest = newDest.Field(i)
						}
					}
				}
			}
		}

		// duplicate keys overwrite the previous value
		var val interface{}
		if val, err = p.readValue(newDest, elemType); err != nil {
			return nil, err
		}
		if p.nodeDestination {
			var ok bool
			if elemNode, ok = val.(*Node); ok {
				p.setComment1(&elemNode.Cm.Key, ciKey)
				elemNode.Cm.Key += elemNode.Cm.Before
				elemNode.Cm.Before = ""
				p.setComment1(&elemNode.Cm.Before, ciBefore)
			}
		}
		// Check white before comma because comma might be on other line.
		ciAfter := p.white()
		// in Hjson the comma is optional and trailing commas are allowed
		if p.ch == ',' {
			p.next()
			ciAfterComma := p.whiteAfterComma()
			if elemNode != nil {
				existingAfter := elemNode.Cm.After
				p.setComment2(&elemNode.Cm.After, ciAfter, ciAfterComma)
				elemNode.Cm.After = existingAfter + elemNode.Cm.After
			}
			ciAfter = p.white()
		}
		if p.ch == '}' && !withoutBraces {
			p.setComment1(&node.Cm.InsideLast, ciAfter)
			oldValue, isDuplicate := object.Set(key, val)
			if isDuplicate && p.DisallowDuplicateKeys {
				return nil, p.errAt(fmt.Sprintf("Found duplicate values ('%#v' and '%#v') for key '%v'",
					oldValue, val, key))
			}
			p.next()
			return p.maybeWrapNode(&node, object)
		}
		oldValue, isDuplicate := object.Set(key, val)
		if isDuplicate && p.DisallowDuplicateKeys {
			return nil, p.errAt(fmt.Sprintf("Found duplicate values ('%#v' and '%#v') for key '%v'",
				oldValue, val, key))
		}
		ciBefore = ciAfter
	}

	if withoutBraces {
		p.setComment1(&node.Cm.InsideLast, ciBefore)
		return p.maybeWrapNode(&node, object)
	}
	return nil, p.errAt("End of input while parsing an object (did you forget a closing '}'?)")
}

// dest and t must not have been unraveled yet here. In readTfnns we need
// to check if the original type (or a pointer to it) implements
// encoding.TextUnmarshaler.
func (p *hjsonParser) readValue(dest reflect.Value, t reflect.Type) (ret interface{}, err error) {
	ciBefore := p.white()
	// Parse an Hjson value. It could be an object, an array, a string, a number or a word.
	switch p.ch {
	case '{':
		ret, err = p.readObject(false, dest, t, ciBefore)
	case '[':
		ret, err = p.readArray(dest, t)
	case '"', '\'':
		s, err := p.readString(true)
		if err != nil {
			return nil, err
		}
		ret, err = p.maybeWrapNode(&Node{}, s)
	default:
		ret, err = p.readTfnns(dest, t)
		// Make sure that any comment will include preceding whitespace.
		if p.ch == '#' || p.ch == '/' {
			for p.prev() && p.ch <= ' ' {
			}
			p.next()
		}
	}

	ciAfter := p.getCommentAfter()
	if p.nodeDestination {
		if node, ok := ret.(*Node); ok {
			p.setComment1(&node.Cm.Before, ciBefore)
			p.setComment1(&node.Cm.After, ciAfter)
		}
	}

	return
}

func (p *hjsonParser) rootValue(dest reflect.Value) (ret interface{}, err error) {
	// Braces for the root object are optional

	// We have checked that dest is a pointer before calling rootValue().
	// Dereference here because readObject() etc will pass on child destinations
	// without creating pointers.
	dest = dest.Elem()
	t := dest.Type()

	var errSyntax error
	var ciAfter commentInfo
	ciBefore := p.white()

	switch p.ch {
	case '{':
		ret, err = p.readObject(false, dest, t, ciBefore)
		if err != nil {
			return
		}
		ciAfter, err = p.checkTrailing()
		if err != nil {
			return
		}
		if p.nodeDestination {
			if node, ok := ret.(*Node); ok {
				p.setComment1(&node.Cm.Before, ciBefore)
				p.setComment1(&node.Cm.After, ciAfter)
			}
		}
		return
	case '[':
		ret, err = p.readArray(dest, t)
		if err != nil {
			return
		}
		ciAfter, err = p.checkTrailing()
		if err != nil {
			return
		}
		if p.nodeDestination {
			if node, ok := ret.(*Node); ok {
				p.setComment1(&node.Cm.Before, ciBefore)
				p.setComment1(&node.Cm.After, ciAfter)
			}
		}
		return
	}

	if ret == nil {
		// Assume we have a root object without braces.
		ret, errSyntax = p.readObject(true, dest, t, ciBefore)
		ciAfter, err = p.checkTrailing()
		if errSyntax != nil || err != nil {
			// Syntax error, or maybe a single JSON value.
			ret = nil
			err = nil
		} else {
			if p.nodeDestination {
				if node, ok := ret.(*Node); ok {
					p.setComment1(&node.Cm.After, ciAfter)
				}
			}
			return
		}
	}

	if ret == nil {
		// test if we are dealing with a single JSON value instead (true/false/null/num/"")
		p.resetAt()
		ret, err = p.readValue(dest, t)
		if err == nil {
			ciAfter, err = p.checkTrailing()
		}
		if err == nil {
			if p.nodeDestination {
				if node, ok := ret.(*Node); ok {
					// ciBefore has been read again and set on the node inside the
					// function p.readValue().
					existingAfter := node.Cm.After
					p.setComment1(&node.Cm.After, ciAfter)
					if node.Cm.After != "" {
						existingAfter += "\n"
					}
					node.Cm.After = existingAfter + node.Cm.After
				}
			}

			return
		}
	}

	if errSyntax != nil {
		return nil, errSyntax
	}

	return
}

func (p *hjsonParser) checkTrailing() (commentInfo, error) {
	ci := p.white()
	if p.ch > 0 {
		return ci, p.errAt("Syntax error, found trailing characters")
	}
	return ci, nil
}

// Unmarshal parses the Hjson-encoded data using default options and stores the
// result in the value pointed to by v.
//
// See UnmarshalWithOptions.
func Unmarshal(data []byte, v interface{}) error {
	return UnmarshalWithOptions(data, v, DefaultDecoderOptions())
}

func orderedUnmarshal(
	data []byte,
	v interface{},
	options DecoderOptions,
	willMarshalToJSON bool,
	nodeDestination bool,
) (
	interface{},
	error,
) {
	rv := reflect.ValueOf(v)
	if rv.Kind() != reflect.Ptr || rv.IsNil() {
		return nil, fmt.Errorf("Cannot unmarshal into non-pointer %v", reflect.TypeOf(v))
	}

	parser := &hjsonParser{
		DecoderOptions:    options,
		data:              data,
		at:                0,
		ch:                ' ',
		structTypeCache:   map[reflect.Type]structFieldMap{},
		willMarshalToJSON: willMarshalToJSON,
		nodeDestination:   nodeDestination,
	}
	parser.resetAt()
	value, err := parser.rootValue(rv)
	if err != nil {
		return nil, err
	}

	return value, nil
}

// UnmarshalWithOptions parses the Hjson-encoded data and stores the result
// in the value pointed to by v.
//
// The Hjson input is internally converted to JSON, which is then used as input
// to the function json.Unmarshal(). Unless the input argument v is of any of
// these types:
//
//	*hjson.OrderedMap
//	**hjson.OrderedMap
//	*hjson.Node
//	**hjson.Node
//
// Comments can be read from the Hjson-encoded data, but only if the input
// argument v is of type *hjson.Node or **hjson.Node.
//
// For more details about the output from this function, see the documentation
// for json.Unmarshal().
func UnmarshalWithOptions(data []byte, v interface{}, options DecoderOptions) error {
	inOM, destinationIsOrderedMap := v.(*OrderedMap)
	if !destinationIsOrderedMap {
		pInOM, ok := v.(**OrderedMap)
		if ok {
			destinationIsOrderedMap = true
			inOM = &OrderedMap{}
			*pInOM = inOM
		}
	}

	inNode, destinationIsNode := v.(*Node)
	if !destinationIsNode {
		pInNode, ok := v.(**Node)
		if ok {
			destinationIsNode = true
			inNode = &Node{}
			*pInNode = inNode
		}
	}

	value, err := orderedUnmarshal(data, v, options, !(destinationIsOrderedMap ||
		destinationIsNode), destinationIsNode)
	if err != nil {
		return err
	}

	if destinationIsOrderedMap {
		if outOM, ok := value.(*OrderedMap); ok {
			*inOM = *outOM
			return nil
		}
		return fmt.Errorf("Cannot unmarshal into hjson.OrderedMap: Try %v as destination instead",
			reflect.TypeOf(v))
	}

	if destinationIsNode {
		if outNode, ok := value.(*Node); ok {
			*inNode = *outNode
			return nil
		}
	}

	// Convert to JSON so we can let json.Unmarshal() handle all destination
	// types (including interfaces json.Unmarshaler and encoding.TextUnmarshaler)
	// and merging.
	buf, err := json.Marshal(value)
	if err != nil {
		return errors.New("Internal error")
	}

	dec := json.NewDecoder(bytes.NewBuffer(buf))
	if options.UseJSONNumber {
		dec.UseNumber()
	}
	if options.DisallowUnknownFields {
		dec.DisallowUnknownFields()
	}

	err = dec.Decode(v)
	if err != nil {
		return err
	}

	return err
}
