package jsonparser

import (
	"bytes"
	"errors"
	"fmt"
	"strconv"
)

// Errors
var (
	KeyPathNotFoundError       = errors.New("Key path not found")
	UnknownValueTypeError      = errors.New("Unknown value type")
	MalformedJsonError         = errors.New("Malformed JSON error")
	MalformedStringError       = errors.New("Value is string, but can't find closing '\"' symbol")
	MalformedArrayError        = errors.New("Value is array, but can't find closing ']' symbol")
	MalformedObjectError       = errors.New("Value looks like object, but can't find closing '}' symbol")
	MalformedValueError        = errors.New("Value looks like Number/Boolean/None, but can't find its end: ',' or '}' symbol")
	OverflowIntegerError       = errors.New("Value is number, but overflowed while parsing")
	MalformedStringEscapeError = errors.New("Encountered an invalid escape sequence in a string")
	NullValueError             = errors.New("Value is null")
)

// How much stack space to allocate for unescaping JSON strings; if a string longer
// than this needs to be escaped, it will result in a heap allocation
const unescapeStackBufSize = 64

// SYS-REQ-044
func tokenEnd(data []byte) int {
	for i, c := range data {
		switch c {
		case ' ', '\n', '\r', '\t', ',', '}', ']':
			return i
		}
	}

	return len(data)
}

// SYS-REQ-001
func findTokenStart(data []byte, token byte) int {
	for i := len(data) - 1; i >= 0; i-- {
		switch data[i] {
		case token:
			return i
		case '[', '{':
			return 0
		}
	}

	return 0
}

// SYS-REQ-001, SYS-REQ-020, SYS-REQ-024
func findKeyStart(data []byte, key string) (int, error) {
	i := nextToken(data)
	if i == -1 {
		return i, KeyPathNotFoundError
	}
	ln := len(data)
	// Note: nextToken returning non-negative (checked above) guarantees ln > 0,
	// so the former ln > 0 guard was tautological and has been removed.
	if data[i] == '{' || data[i] == '[' {
		i += 1
	}
	var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings

	if ku, err := Unescape(StringToBytes(key), stackbuf[:]); err == nil {
		key = bytesToString(&ku)
	}

	for i < ln {
		switch data[i] {
		case '"':
			i++
			keyBegin := i

			strEnd, keyEscaped := stringEnd(data[i:])
			if strEnd == -1 {
				break
			}
			i += strEnd
			keyEnd := i - 1

			valueOffset := nextToken(data[i:])
			if valueOffset == -1 {
				break
			}

			i += valueOffset

			// if string is a key, and key level match
			k := data[keyBegin:keyEnd]
			// for unescape: if there are no escape sequences, this is cheap; if there are, it is a
			// bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize
			if keyEscaped {
				if ku, err := Unescape(k, stackbuf[:]); err != nil {
					break
				} else {
					k = ku
				}
			}

			if data[i] == ':' && len(key) == len(k) && bytesToString(&k) == key {
				return keyBegin - 1, nil
			}

		case '[':
			end := blockEnd(data[i:], data[i], ']')
			if end != -1 {
				i = i + end
			}
		case '{':
			end := blockEnd(data[i:], data[i], '}')
			if end != -1 {
				i = i + end
			}
		}
		i++
	}

	return -1, KeyPathNotFoundError
}

// SYS-REQ-001
func tokenStart(data []byte) int {
	for i := len(data) - 1; i >= 0; i-- {
		switch data[i] {
		case '\n', '\r', '\t', ',', '{', '[':
			return i
		}
	}

	return 0
}

// SYS-REQ-001
// Find position of next character which is not whitespace
func nextToken(data []byte) int {
	for i, c := range data {
		switch c {
		case ' ', '\n', '\r', '\t':
			continue
		default:
			return i
		}
	}

	return -1
}

// SYS-REQ-001
// Find position of last character which is not whitespace
func lastToken(data []byte) int {
	for i := len(data) - 1; i >= 0; i-- {
		switch data[i] {
		case ' ', '\n', '\r', '\t':
			continue
		default:
			return i
		}
	}

	return -1
}

// SYS-REQ-045
// Tries to find the end of string
// Support if string contains escaped quote symbols.
func stringEnd(data []byte) (int, bool) {
	escaped := false
	for i, c := range data {
		if c == '"' {
			if !escaped {
				return i + 1, false
			} else {
				j := i - 1
				for {
					if j < 0 || data[j] != '\\' {
						return i + 1, true // even number of backslashes
					}
					j--
					if j < 0 || data[j] != '\\' {
						break // odd number of backslashes
					}
					j--

				}
			}
		} else if c == '\\' {
			escaped = true
		}
	}

	return -1, escaped
}

// SYS-REQ-046
// Find end of the data structure, array or object.
// For array openSym and closeSym will be '[' and ']', for object '{' and '}'
func blockEnd(data []byte, openSym byte, closeSym byte) int {
	level := 0
	i := 0
	ln := len(data)

	for i < ln {
		switch data[i] {
		case '"': // If inside string, skip it
			se, _ := stringEnd(data[i+1:])
			if se == -1 {
				return -1
			}
			i += se
		case openSym: // If open symbol, increase level
			level++
		case closeSym: // If close symbol, increase level
			level--

			// If we have returned to the original level, we're done
			if level == 0 {
				return i + 1
			}
		}
		i++
	}

	return -1
}

// SYS-REQ-001, SYS-REQ-020, SYS-REQ-021, SYS-REQ-022, SYS-REQ-023, SYS-REQ-047
func searchKeys(data []byte, keys ...string) int {
	keyLevel := 0
	level := 0
	i := 0
	ln := len(data)
	lk := len(keys)
	lastMatched := true

	if lk == 0 {
		return 0
	}

	var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings

	for i < ln {
		switch data[i] {
		case '"':
			i++
			keyBegin := i

			strEnd, keyEscaped := stringEnd(data[i:])
			if strEnd == -1 {
				return -1
			}
			i += strEnd
			keyEnd := i - 1

			valueOffset := nextToken(data[i:])
			if valueOffset == -1 {
				return -1
			}

			i += valueOffset

			// if string is a key
			if data[i] == ':' {
				if level < 1 {
					return -1
				}

				key := data[keyBegin:keyEnd]

				// for unescape: if there are no escape sequences, this is cheap; if there are, it is a
				// bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize
				var keyUnesc []byte
				if !keyEscaped {
					keyUnesc = key
				} else if ku, err := Unescape(key, stackbuf[:]); err != nil {
					return -1
				} else {
					keyUnesc = ku
				}

				if level <= len(keys) {
					if equalStr(&keyUnesc, keys[level-1]) {
						lastMatched = true

						// if key level match
						if keyLevel == level-1 {
							keyLevel++
							// If we found all keys in path
							if keyLevel == lk {
								return i + 1
							}
						}
					} else {
						lastMatched = false
					}
				} else {
					return -1
				}
			} else {
				i--
			}
		case '{':

			// in case parent key is matched then only we will increase the level otherwise can directly
			// can move to the end of this block
			if !lastMatched {
				end := blockEnd(data[i:], '{', '}')
				if end == -1 {
					return -1
				}
				i += end - 1
			} else {
				level++
			}
		case '}':
			level--
			if level == keyLevel {
				keyLevel--
			}
		case '[':
			// If we want to get array element by index
			if keyLevel == level && keys[level][0] == '[' {
				keyLen := len(keys[level])
				// Note: keys[level][0] == '[' is guaranteed by the outer if-guard,
				// so the former middle term `keys[level][0] != '['` was always false
				// (dead code) and has been removed.
				if keyLen < 3 || keys[level][keyLen-1] != ']' {
					return -1
				}
				aIdx, err := strconv.Atoi(keys[level][1 : keyLen-1])
				if err != nil {
					return -1
				}
				var curIdx int
				var valueFound []byte
				var valueOffset int
				curI := i
				ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) {
					if curIdx == aIdx {
						valueFound = value
						valueOffset = offset
						if dataType == String {
							valueOffset = valueOffset - 2
							valueFound = data[curI+valueOffset : curI+valueOffset+len(value)+2]
						}
					}
					curIdx += 1
				})

				if valueFound == nil {
					return -1
				} else {
					subIndex := searchKeys(valueFound, keys[level+1:]...)
					if subIndex < 0 {
						return -1
					}
					return i + valueOffset + subIndex
				}
			} else {
				// Do not search for keys inside arrays
				if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 {
					return -1
				} else {
					i += arraySkip - 1
				}
			}
		case ':': // If encountered, JSON data is malformed
			return -1
		}

		i++
	}

	return -1
}

// SYS-REQ-008
func sameTree(p1, p2 []string) bool {
	minLen := len(p1)
	if len(p2) < minLen {
		minLen = len(p2)
	}

	for pi_1, p_1 := range p1[:minLen] {
		if p2[pi_1] != p_1 {
			return false
		}
	}

	return true
}

const stackArraySize = 128

// SYS-REQ-008, SYS-REQ-085
func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]string) int {
	var x struct{}
	var level, pathsMatched, i int
	ln := len(data)

	pathFlags := make([]bool, stackArraySize)[:]
	if len(paths) > cap(pathFlags) {
		pathFlags = make([]bool, len(paths))[:]
	}
	pathFlags = pathFlags[0:len(paths)]

	var maxPath int
	for _, p := range paths {
		if len(p) > maxPath {
			maxPath = len(p)
		}
	}

	pathsBuf := make([]string, stackArraySize)[:]
	if maxPath > cap(pathsBuf) {
		pathsBuf = make([]string, maxPath)[:]
	}
	pathsBuf = pathsBuf[0:maxPath]

	for i < ln {
		switch data[i] {
		case '"':
			i++
			keyBegin := i

			strEnd, keyEscaped := stringEnd(data[i:])
			if strEnd == -1 {
				return -1
			}
			i += strEnd

			keyEnd := i - 1

			valueOffset := nextToken(data[i:])
			if valueOffset == -1 {
				return -1
			}

			i += valueOffset

			// if string is a key, and key level match
			if data[i] == ':' {
				match := -1
				key := data[keyBegin:keyEnd]

				// for unescape: if there are no escape sequences, this is cheap; if there are, it is a
				// bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize
				var keyUnesc []byte
				if !keyEscaped {
					keyUnesc = key
				} else {
					var stackbuf [unescapeStackBufSize]byte
					if ku, err := Unescape(key, stackbuf[:]); err != nil {
						return -1
					} else {
						keyUnesc = ku
					}
				}

				if maxPath >= level {
					if level < 1 {
						cb(-1, nil, Unknown, MalformedJsonError)
						return -1
					}

					pathsBuf[level-1] = bytesToString(&keyUnesc)
					for pi, p := range paths {
						if len(p) != level || pathFlags[pi] || !equalStr(&keyUnesc, p[level-1]) || !sameTree(p, pathsBuf[:level]) {
							continue
						}

						match = pi

						pathsMatched++
						pathFlags[pi] = true

						v, dt, _, e := Get(data[i+1:])
						cb(pi, v, dt, e)

						if pathsMatched == len(paths) {
							break
						}
					}
					if pathsMatched == len(paths) {
						return i
					}
				}

				if match == -1 {
					tokenOffset := nextToken(data[i+1:])
					i += tokenOffset
					// Note: i is now at the character BEFORE the value (the colon
					// when tokenOffset==0, or the last whitespace character otherwise).
					// The former `if data[i] == '{'` block-skip was structurally dead
					// code because i never reaches the opening brace — the outer loop's
					// i++ advances to it on the next iteration.  Likewise, the former
					// `if i < ln` guard was tautological since i remains within bounds.
				}

				switch data[i] {
				case '{', '}', '[', '"':
					i--
				}
			} else {
				i--
			}
		case '{':
			level++
		case '}':
			level--
		case '[':
			var ok bool
			arrIdxFlags := make(map[int]struct{})

			pIdxFlags := make([]bool, stackArraySize)[:]
			if len(paths) > cap(pIdxFlags) {
				pIdxFlags = make([]bool, len(paths))[:]
			}
			pIdxFlags = pIdxFlags[0:len(paths)]

			if level < 0 {
				cb(-1, nil, Unknown, MalformedJsonError)
				return -1
			}

			for pi, p := range paths {
				if len(p) < level+1 || pathFlags[pi] || p[level][0] != '[' || !sameTree(p, pathsBuf[:level]) {
					continue
				}
				if len(p[level]) >= 2 {
					aIdx, _ := strconv.Atoi(p[level][1 : len(p[level])-1])
					arrIdxFlags[aIdx] = x
					pIdxFlags[pi] = true
				}
			}

			if len(arrIdxFlags) > 0 {
				level++

				var curIdx int
				arrOff, _ := ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) {
					if _, ok = arrIdxFlags[curIdx]; ok {
						for pi, p := range paths {
							if pIdxFlags[pi] {
								aIdx, _ := strconv.Atoi(p[level-1][1 : len(p[level-1])-1])

								if curIdx == aIdx {
									of := searchKeys(value, p[level:]...)

									pathsMatched++
									pathFlags[pi] = true

									if of != -1 {
										v, dt, _, e := Get(value[of:])
										cb(pi, v, dt, e)
									}
								}
							}
						}
					}

					curIdx += 1
				})

				if pathsMatched == len(paths) {
					return i
				}

				i += arrOff - 1
			} else {
				// Do not search for keys inside arrays
				if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 {
					return -1
				} else {
					i += arraySkip - 1
				}
			}
		case ']':
			level--
		}

		i++
	}

	return -1
}

// Data types available in valid JSON data.
type ValueType int

const (
	NotExist = ValueType(iota)
	String
	Number
	Object
	Array
	Boolean
	Null
	Unknown
)

// SYS-REQ-001
func (vt ValueType) String() string {
	switch vt {
	case NotExist:
		return "non-existent"
	case String:
		return "string"
	case Number:
		return "number"
	case Object:
		return "object"
	case Array:
		return "array"
	case Boolean:
		return "boolean"
	case Null:
		return "null"
	default:
		return "unknown"
	}
}

var (
	trueLiteral  = []byte("true")
	falseLiteral = []byte("false")
	nullLiteral  = []byte("null")
)

// SYS-REQ-009
func createInsertComponent(keys []string, setValue []byte, comma, object bool) []byte {
	isIndex := string(keys[0][0]) == "["
	offset := 0
	lk := calcAllocateSpace(keys, setValue, comma, object)
	buffer := make([]byte, lk, lk)
	if comma {
		offset += WriteToBuffer(buffer[offset:], ",")
	}
	if isIndex && !comma {
		offset += WriteToBuffer(buffer[offset:], "[")
	} else {
		if object {
			offset += WriteToBuffer(buffer[offset:], "{")
		}
		if !isIndex {
			offset += WriteToBuffer(buffer[offset:], "\"")
			offset += WriteToBuffer(buffer[offset:], keys[0])
			offset += WriteToBuffer(buffer[offset:], "\":")
		}
	}

	for i := 1; i < len(keys); i++ {
		if string(keys[i][0]) == "[" {
			offset += WriteToBuffer(buffer[offset:], "[")
		} else {
			offset += WriteToBuffer(buffer[offset:], "{\"")
			offset += WriteToBuffer(buffer[offset:], keys[i])
			offset += WriteToBuffer(buffer[offset:], "\":")
		}
	}
	offset += WriteToBuffer(buffer[offset:], string(setValue))
	for i := len(keys) - 1; i > 0; i-- {
		if string(keys[i][0]) == "[" {
			offset += WriteToBuffer(buffer[offset:], "]")
		} else {
			offset += WriteToBuffer(buffer[offset:], "}")
		}
	}
	if isIndex && !comma {
		offset += WriteToBuffer(buffer[offset:], "]")
	}
	if object && !isIndex {
		offset += WriteToBuffer(buffer[offset:], "}")
	}
	return buffer
}

// SYS-REQ-009
func calcAllocateSpace(keys []string, setValue []byte, comma, object bool) int {
	isIndex := string(keys[0][0]) == "["
	lk := 0
	if comma {
		// ,
		lk += 1
	}
	if isIndex && !comma {
		// []
		lk += 2
	} else {
		if object {
			// {
			lk += 1
		}
		if !isIndex {
			// "keys[0]"
			lk += len(keys[0]) + 3
		}
	}

	lk += len(setValue)
	for i := 1; i < len(keys); i++ {
		if string(keys[i][0]) == "[" {
			// []
			lk += 2
		} else {
			// {"keys[i]":setValue}
			lk += len(keys[i]) + 5
		}
	}

	if object && !isIndex {
		// }
		lk += 1
	}

	return lk
}

// SYS-REQ-009
func WriteToBuffer(buffer []byte, str string) int {
	copy(buffer, str)
	return len(str)
}

/*

Del - Receives existing data structure, path to delete.

Returns:
`data` - return modified data

*/
// SYS-REQ-010, SYS-REQ-033, SYS-REQ-034, SYS-REQ-035, SYS-REQ-048, SYS-REQ-049, SYS-REQ-050, SYS-REQ-056
func Delete(data []byte, keys ...string) []byte {
	lk := len(keys)
	if lk == 0 {
		return data[:0]
	}

	array := false
	if len(keys[lk-1]) > 0 && string(keys[lk-1][0]) == "[" {
		array = true
	}

	var startOffset, keyOffset int
	endOffset := len(data)
	var err error
	if !array {
		if len(keys) > 1 {
			_, _, startOffset, endOffset, err = internalGet(data, keys[:lk-1]...)
			if err != nil {
				// problem parsing the data
				return data
			}
		}

		keyOffset, err = findKeyStart(data[startOffset:endOffset], keys[lk-1])
		if err == KeyPathNotFoundError {
			// problem parsing the data
			return data
		}
		keyOffset += startOffset
		var subEndOffset int
		_, _, _, subEndOffset, err = internalGet(data[startOffset:endOffset], keys[lk-1])
		if err != nil {
			return data
		}
		endOffset = startOffset + subEndOffset
		tokEnd := tokenEnd(data[endOffset:])
		tokStart := findTokenStart(data[:keyOffset], ","[0])

		if endOffset+tokEnd >= len(data) {
			// tokenEnd sentinel: no delimiter found, input is truncated
			return data
		}

		if data[endOffset+tokEnd] == ','  {
			endOffset += tokEnd + 1
		} else if data[endOffset+tokEnd] == ' ' && len(data) > endOffset+tokEnd+1 && data[endOffset+tokEnd+1] == ',' {
			endOffset += tokEnd + 2
		} else if data[endOffset+tokEnd] == '}' && data[tokStart] == ',' {
			keyOffset = tokStart
		}
	} else {
		_, _, keyOffset, endOffset, err = internalGet(data, keys...)
		if err != nil {
			// problem parsing the data
			return data
		}

		tokEnd := tokenEnd(data[endOffset:])
		tokStart := findTokenStart(data[:keyOffset], ","[0])

		if endOffset+tokEnd >= len(data) {
			// tokenEnd sentinel: no delimiter found, input is truncated
			return data
		}

		if data[endOffset+tokEnd] == ',' {
			endOffset += tokEnd + 1
		} else if data[endOffset+tokEnd] == ']' && data[tokStart] == ',' {
			keyOffset = tokStart
		}
	}

	// We need to remove remaining trailing comma if we delete last element in the object.
	// Extract nextToken once to avoid the redundant double call in the original code.
	prevTok := lastToken(data[:keyOffset])
	remainedValue := data[endOffset:]
	remainedTok := nextToken(remainedValue)

	var newOffset int
	if remainedTok > -1 && remainedValue[remainedTok] == '}' && data[prevTok] == ',' {
		newOffset = prevTok
	} else {
		newOffset = prevTok + 1
	}

	// We have to make a copy here if we don't want to mangle the original data, because byte slices are
	// accessed by reference and not by value
	dataCopy := make([]byte, len(data))
	copy(dataCopy, data)
	data = append(dataCopy[:newOffset], dataCopy[endOffset:]...)

	return data
}

/*

Set - Receives existing data structure, path to set, and data to set at that key.

Returns:
`value` - modified byte array
`err` - On any parsing error

*/
// SYS-REQ-009, SYS-REQ-051, SYS-REQ-068, SYS-REQ-069, SYS-REQ-070
func Set(data []byte, setValue []byte, keys ...string) (value []byte, err error) {
	// ensure keys are set
	if len(keys) == 0 {
		return nil, KeyPathNotFoundError
	}

	_, _, startOffset, endOffset, err := internalGet(data, keys...)
	if err != nil {
		if err != KeyPathNotFoundError {
			// problem parsing the data
			return nil, err
		}
		// full path doesnt exist
		// does any subpath exist?
		var depth int
		for i := range keys {
			_, _, start, end, sErr := internalGet(data, keys[:i+1]...)
			if sErr != nil {
				break
			} else {
				endOffset = end
				startOffset = start
				depth++
			}
		}
		comma := true
		object := false
		if endOffset == -1 {
			firstToken := nextToken(data)
			// We can't set a top-level key if data isn't an object
			if firstToken < 0 || data[firstToken] != '{' {
				return nil, KeyPathNotFoundError
			}
			// Don't need a comma if the input is an empty object
			secondToken := firstToken + 1 + nextToken(data[firstToken+1:])
			if data[secondToken] == '}' {
				comma = false
			}
			// Set the top level key at the end (accounting for any trailing whitespace)
			// This assumes last token is valid like '}', could check and return error
			endOffset = lastToken(data)
		}
		depthOffset := endOffset
		if depth != 0 {
			// if subpath is a non-empty object, add to it
			// or if subpath is a non-empty array, add to it
			if (data[startOffset] == '{' && data[startOffset+1+nextToken(data[startOffset+1:])] != '}') ||
				(data[startOffset] == '[' && data[startOffset+1+nextToken(data[startOffset+1:])] == '{') && keys[depth:][0][0] == 91 {
				depthOffset--
				startOffset = depthOffset
				// otherwise, over-write it with a new object
			} else {
				comma = false
				object = true
			}
		} else {
			startOffset = depthOffset
		}
		value = append(data[:startOffset], append(createInsertComponent(keys[depth:], setValue, comma, object), data[depthOffset:]...)...)
	} else {
		// path currently exists
		startComponent := data[:startOffset]
		endComponent := data[endOffset:]

		value = make([]byte, len(startComponent)+len(endComponent)+len(setValue))
		newEndOffset := startOffset + len(setValue)
		copy(value[0:startOffset], startComponent)
		copy(value[startOffset:newEndOffset], setValue)
		copy(value[newEndOffset:], endComponent)
	}
	return value, nil
}

// SYS-REQ-001, SYS-REQ-027
func getType(data []byte, offset int) ([]byte, ValueType, int, error) {
	var dataType ValueType
	endOffset := offset

	// if string value
	if data[offset] == '"' {
		dataType = String
		if idx, _ := stringEnd(data[offset+1:]); idx != -1 {
			endOffset += idx + 1
		} else {
			return nil, dataType, offset, MalformedStringError
		}
	} else if data[offset] == '[' { // if array value
		dataType = Array
		// break label, for stopping nested loops
		endOffset = blockEnd(data[offset:], '[', ']')

		if endOffset == -1 {
			return nil, dataType, offset, MalformedArrayError
		}

		endOffset += offset
	} else if data[offset] == '{' { // if object value
		dataType = Object
		// break label, for stopping nested loops
		endOffset = blockEnd(data[offset:], '{', '}')

		if endOffset == -1 {
			return nil, dataType, offset, MalformedObjectError
		}

		endOffset += offset
	} else {
		// Number, Boolean or None
		// tokenEnd returns len(data) when no delimiter is found, never -1,
		// so the old end == -1 guard was dead code and has been removed.
		end := tokenEnd(data[endOffset:])

		value := data[offset : endOffset+end]

		switch data[offset] {
		case 't', 'f': // true or false
			if bytes.Equal(value, trueLiteral) || bytes.Equal(value, falseLiteral) {
				dataType = Boolean
			} else {
				return nil, Unknown, offset, UnknownValueTypeError
			}
		case 'u', 'n': // undefined or null
			if bytes.Equal(value, nullLiteral) {
				dataType = Null
			} else {
				return nil, Unknown, offset, UnknownValueTypeError
			}
		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-':
			dataType = Number
		default:
			return nil, Unknown, offset, UnknownValueTypeError
		}

		endOffset += end
	}
	return data[offset:endOffset], dataType, endOffset, nil
}

/*
Get - Receives data structure, and key path to extract value from.

Returns:
`value` - Pointer to original data structure containing key value, or just empty slice if nothing found or error
`dataType` -    Can be: `NotExist`, `String`, `Number`, `Object`, `Array`, `Boolean` or `Null`
`offset` - Offset from provided data structure where key value ends. Used mostly internally, for example for `ArrayEach` helper.
`err` - If key not found or any other parsing issue it should return error. If key not found it also sets `dataType` to `NotExist`

Accept multiple keys to specify path to JSON value (in case of quering nested structures).
If no keys provided it will try to extract closest JSON value (simple ones or object/array), useful for reading streams or arrays, see `ArrayEach` implementation.
*/
// SYS-REQ-001, SYS-REQ-016, SYS-REQ-017, SYS-REQ-018, SYS-REQ-019, SYS-REQ-025, SYS-REQ-026, SYS-REQ-041, SYS-REQ-042, SYS-REQ-043
func Get(data []byte, keys ...string) (value []byte, dataType ValueType, offset int, err error) {
	a, b, _, d, e := internalGet(data, keys...)
	return a, b, d, e
}

// SYS-REQ-001
func internalGet(data []byte, keys ...string) (value []byte, dataType ValueType, offset, endOffset int, err error) {
	if len(keys) > 0 {
		if offset = searchKeys(data, keys...); offset == -1 {
			return nil, NotExist, -1, -1, KeyPathNotFoundError
		}
	}

	// Go to closest value
	nO := nextToken(data[offset:])
	if nO == -1 {
		return nil, NotExist, offset, -1, MalformedJsonError
	}

	offset += nO
	value, dataType, endOffset, err = getType(data, offset)
	if err != nil {
		return value, dataType, offset, endOffset, err
	}

	// Strip quotes from string values
	if dataType == String {
		value = value[1 : len(value)-1]
	}

	return value[:len(value):len(value)], dataType, offset, endOffset, nil
}

// SYS-REQ-006, SYS-REQ-028, SYS-REQ-029, SYS-REQ-052, SYS-REQ-053, SYS-REQ-055, SYS-REQ-083
// ArrayEach is used when iterating arrays, accepts a callback function with the same return arguments as `Get`.
func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int, err error), keys ...string) (offset int, err error) {
	if len(data) == 0 {
		return -1, MalformedObjectError
	}

	nT := nextToken(data)
	if nT == -1 {
		return -1, MalformedJsonError
	}

	offset = nT + 1

	if len(keys) > 0 {
		if offset = searchKeys(data, keys...); offset == -1 {
			return offset, KeyPathNotFoundError
		}

		// Go to closest value
		nO := nextToken(data[offset:])
		if nO == -1 {
			return offset, MalformedJsonError
		}

		offset += nO

		if data[offset] != '[' {
			return offset, MalformedArrayError
		}

		offset++
	}

	nO := nextToken(data[offset:])
	if nO == -1 {
		return offset, MalformedJsonError
	}

	offset += nO

	if data[offset] == ']' {
		return offset, nil
	}

	for {
		v, t, o, e := Get(data[offset:])

		if o == 0 {
			// When Get returns endOffset==0, it always means a parse error
			// (no valid value found at the current position). The former
			// e==nil/break branch was structurally unreachable because Get
			// never returns endOffset==0 without an error.
			return offset, e
		}

		// Pass the error to the callback — the callback signature declares
		// an err parameter, so callers who check it should see real errors.
		cb(v, t, offset+o-len(v), e)

		if e != nil {
			return offset, e
		}

		offset += o

		skipToToken := nextToken(data[offset:])
		if skipToToken == -1 {
			return offset, MalformedArrayError
		}
		offset += skipToToken

		if data[offset] == ']' {
			break
		}

		if data[offset] != ',' {
			return offset, MalformedArrayError
		}

		offset++
	}

	return offset, nil
}

// SYS-REQ-007, SYS-REQ-030, SYS-REQ-031, SYS-REQ-032, SYS-REQ-054, SYS-REQ-084
// ObjectEach iterates over the key-value pairs of a JSON object, invoking a given callback for each such entry
func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType ValueType, offset int) error, keys ...string) (err error) {
	offset := 0

	// Descend to the desired key, if requested
	if len(keys) > 0 {
		if off := searchKeys(data, keys...); off == -1 {
			return KeyPathNotFoundError
		} else {
			offset = off
		}
	}

	// Validate and skip past opening brace
	if off := nextToken(data[offset:]); off == -1 {
		return MalformedObjectError
	} else if offset += off; data[offset] != '{' {
		return MalformedObjectError
	} else {
		offset++
	}

	// Skip to the first token inside the object, or stop if we find the ending brace
	if off := nextToken(data[offset:]); off == -1 {
		return MalformedJsonError
	} else if offset += off; data[offset] == '}' {
		return nil
	}

	// Loop pre-condition: data[offset] points to what should be either the next entry's key,
	// or the closing brace (if it's anything else, the JSON is malformed).
	// Every iteration either returns or advances offset past a token, so the loop
	// always exits via return; the former `offset < len(data)` guard was structurally
	// always true because internal nextToken/stringEnd calls return errors before
	// offset can reach len(data).
	for {
		// Step 1: find the next key
		var key []byte

		// Check what the the next token is: start of string, end of object, or something else (error)
		switch data[offset] {
		case '"':
			offset++ // accept as string and skip opening quote
		case '}':
			return nil // we found the end of the object; stop and return success
		default:
			return MalformedObjectError
		}

		// Find the end of the key string
		var keyEscaped bool
		if off, esc := stringEnd(data[offset:]); off == -1 {
			return MalformedJsonError
		} else {
			key, keyEscaped = data[offset:offset+off-1], esc
			offset += off
		}

		// Unescape the string if needed
		if keyEscaped {
			var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
			if keyUnescaped, err := Unescape(key, stackbuf[:]); err != nil {
				return MalformedStringEscapeError
			} else {
				key = keyUnescaped
			}
		}

		// Step 2: skip the colon
		if off := nextToken(data[offset:]); off == -1 {
			return MalformedJsonError
		} else if offset += off; data[offset] != ':' {
			return MalformedJsonError
		} else {
			offset++
		}

		// Step 3: find the associated value, then invoke the callback
		if value, valueType, off, err := Get(data[offset:]); err != nil {
			return err
		} else if err := callback(key, value, valueType, offset+off); err != nil { // Invoke the callback here!
			return err
		} else {
			offset += off
		}

		// Step 4: skip over the next comma to the following token, or stop if we hit the ending brace
		if off := nextToken(data[offset:]); off == -1 {
			return MalformedArrayError
		} else {
			offset += off
			switch data[offset] {
			case '}':
				return nil // Stop if we hit the close brace
			case ',':
				offset++ // Ignore the comma
			default:
				return MalformedObjectError
			}
		}

		// Skip to the next token after the comma
		if off := nextToken(data[offset:]); off == -1 {
			return MalformedArrayError
		} else {
			offset += off
		}
	}

	return MalformedObjectError // we shouldn't get here; it's expected that we will return via finding the ending brace
}

// SYS-REQ-011, SYS-REQ-080, SYS-REQ-081, SYS-REQ-082
// GetUnsafeString returns the value retrieved by `Get`, use creates string without memory allocation by mapping string to slice memory. It does not handle escape symbols.
func GetUnsafeString(data []byte, keys ...string) (val string, err error) {
	v, _, _, e := Get(data, keys...)

	if e != nil {
		return "", e
	}

	return bytesToString(&v), nil
}

// SYS-REQ-002, SYS-REQ-071, SYS-REQ-072, SYS-REQ-073, SYS-REQ-074
// GetString returns the value retrieved by `Get`, cast to a string if possible, trying to properly handle escape and utf8 symbols
// If key data type do not match, it will return an error.
func GetString(data []byte, keys ...string) (val string, err error) {
	v, t, _, e := Get(data, keys...)

	if e != nil {
		return "", e
	}

	if t != String {
		if t == Null {
			return "", NullValueError
		}
		return "", fmt.Errorf("Value is not a string: %s", string(v))
	}

	// If no escapes return raw content
	if bytes.IndexByte(v, '\\') == -1 {
		return string(v), nil
	}

	return ParseString(v)
}

// GetFloat returns the value retrieved by `Get`, cast to a float64 if possible.
// The offset is the same as in `Get`.
// If key data type do not match, it will return an error.
// SYS-REQ-004
func GetFloat(data []byte, keys ...string) (val float64, err error) {
	v, t, _, e := Get(data, keys...)

	if e != nil {
		return 0, e
	}

	if t != Number {
		if t == Null {
			return 0, NullValueError
		}
		return 0, fmt.Errorf("Value is not a number: %s", string(v))
	}

	return ParseFloat(v)
}

// GetInt returns the value retrieved by `Get`, cast to a int64 if possible.
// If key data type do not match, it will return an error.
// SYS-REQ-003, SYS-REQ-075, SYS-REQ-076, SYS-REQ-077, SYS-REQ-078
func GetInt(data []byte, keys ...string) (val int64, err error) {
	v, t, _, e := Get(data, keys...)

	if e != nil {
		return 0, e
	}

	if t != Number {
		if t == Null {
			return 0, NullValueError
		}
		return 0, fmt.Errorf("Value is not a number: %s", string(v))
	}

	return ParseInt(v)
}

// GetBoolean returns the value retrieved by `Get`, cast to a bool if possible.
// The offset is the same as in `Get`.
// If key data type do not match, it will return error.
// SYS-REQ-005, SYS-REQ-079
func GetBoolean(data []byte, keys ...string) (val bool, err error) {
	v, t, _, e := Get(data, keys...)

	if e != nil {
		return false, e
	}

	if t != Boolean {
		if t == Null {
			return false, NullValueError
		}
		return false, fmt.Errorf("Value is not a boolean: %s", string(v))
	}

	return ParseBoolean(v)
}

// ParseBoolean parses a Boolean ValueType into a Go bool (not particularly useful, but here for completeness)
// SYS-REQ-012, SYS-REQ-036, SYS-REQ-057, SYS-REQ-066
func ParseBoolean(b []byte) (bool, error) {
	switch {
	case bytes.Equal(b, trueLiteral):
		return true, nil
	case bytes.Equal(b, falseLiteral):
		return false, nil
	default:
		return false, MalformedValueError
	}
}

// ParseString parses a String ValueType into a Go string (the main parsing work is unescaping the JSON string)
// SYS-REQ-014, SYS-REQ-038, SYS-REQ-060, SYS-REQ-063, SYS-REQ-067
func ParseString(b []byte) (string, error) {
	var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
	if bU, err := Unescape(b, stackbuf[:]); err != nil {
		return "", MalformedValueError
	} else {
		return string(bU), nil
	}
}

// ParseNumber parses a Number ValueType into a Go float64
// SYS-REQ-013, SYS-REQ-037, SYS-REQ-065
func ParseFloat(b []byte) (float64, error) {
	if v, err := parseFloat(&b); err != nil {
		return 0, MalformedValueError
	} else {
		return v, nil
	}
}

// ParseInt parses a Number ValueType into a Go int64
// SYS-REQ-015, SYS-REQ-039, SYS-REQ-040, SYS-REQ-058, SYS-REQ-059, SYS-REQ-064
func ParseInt(b []byte) (int64, error) {
	if v, ok, overflow := parseInt(b); !ok {
		if overflow {
			return 0, OverflowIntegerError
		}
		return 0, MalformedValueError
	} else {
		return v, nil
	}
}
