package nbt

import "strconv"

const (
	scanContinue        = iota // uninteresting byte
	scanBeginLiteral           // end implied by next result != scanContinue
	scanBeginCompound          // begin TAG_Compound (after left-brace )
	scanBeginList              // begin TAG_List (after left-brack)
	scanListValue              // just finished read list value (after comma)
	scanListType               // just finished read list type (after "B;" or "L;")
	scanCompoundTagName        // just finished read tag name (before colon)
	scanCompoundValue          // just finished read value (after comma)
	scanSkipSpace              // space byte; can skip; known to be last "continue" result
	scanEndValue

	scanEnd
	scanError
)

// These values are stored in the parseState stack.
// They give the current state of a composite value
// being scanned. If the parser is inside a nested value
// the parseState describes the nested state, outermost at entry 0.
const (
	parseCompoundName  = iota // parsing tag name (before colon)
	parseCompoundValue        // parsing value (after colon)
	parseListValue            // parsing list
)

const maxNestingDepth = 10000

type scanner struct {
	step       func(s *scanner, c byte) int
	parseState []int
	errContext string
	endTop     bool
}

// reset prepares the scanner for use.
// It must be called before calling s.step.
func (s *scanner) reset() {
	s.step = stateBeginValue
	s.parseState = s.parseState[0:0]
	s.errContext = ""
	s.endTop = false
}

// pushParseState pushes a new parse state p onto the parse stack.
// an error state is returned if maxNestingDepth was exceeded, otherwise successState is returned.
func (s *scanner) pushParseState(newParseState int, successState int) int {
	s.parseState = append(s.parseState, newParseState)
	if len(s.parseState) <= maxNestingDepth {
		return successState
	}
	return scanError
}

// popParseState pops a parse state (already obtained) off the stack
// and updates s.step accordingly.
func (s *scanner) popParseState() {
	n := len(s.parseState) - 1
	s.parseState = s.parseState[:n]
	if n == 0 {
		s.step = stateEndTop
		s.endTop = true
	} else {
		s.step = stateEndValue
	}
}

// eof tells the scanner that the end of input has been reached.
// It returns a scan status just as s.step does.
func (s *scanner) eof() int {
	if s.errContext != "" {
		return scanError
	}
	if s.endTop {
		return scanEnd
	}
	s.step(s, ' ')
	if s.endTop {
		return scanEnd
	}
	if s.errContext == "" {
		s.errContext = "unexpected end of JSON input"
	}
	return scanError
}

// stateEndTop is the state after finishing the top-level value,
// such as after reading `{}` or `[1,2,3]`.
// Only space characters should be seen now.
func stateEndTop(s *scanner, c byte) int {
	if !isSpace(c) {
		// Complain about non-space byte on next call.
		s.error(c, "after top-level value")
	}
	return scanEnd
}

func stateBeginValue(s *scanner, c byte) int {
	if isSpace(c) {
		s.step = stateBeginValue
		return scanSkipSpace
	}
	switch c {
	case '{': // beginning of TAG_Compound
		s.step = stateCompoundOrEmpty
		return s.pushParseState(parseCompoundName, scanBeginCompound)
	case '[': // beginning of TAG_List
		s.step = stateListOrArray
		return s.pushParseState(parseListValue, scanBeginList)
	case '"', '\'': // beginning of TAG_String
		return stateBeginString(s, c)
	default:
		if isNumber(c) {
			stateNum0(s, c)
			return scanBeginLiteral
		}
		if isAllowedInUnquotedString(c) {
			return stateBeginString(s, c)
		}
	}
	return s.error(c, "looking for beginning of value")
}

func stateCompoundOrEmpty(s *scanner, c byte) int {
	if isSpace(c) {
		return scanSkipSpace
	}
	if c == '}' {
		n := len(s.parseState)
		s.parseState[n-1] = parseCompoundValue
		return stateEndValue(s, c)
	}
	return stateBeginString(s, c)
}

func stateBeginString(s *scanner, c byte) int {
	if isSpace(c) {
		return scanSkipSpace
	}
	switch c {
	case '\'':
		s.step = stateInSingleQuotedString
		return scanBeginLiteral
	case '"':
		s.step = stateInDoubleQuotedString
		return scanBeginLiteral
	default:
		if isAllowedInUnquotedString(c) {
			s.step = stateInUnquotedString
			return scanBeginLiteral
		}
	}
	return s.error(c, "looking for beginning of string")
}

func stateInSingleQuotedString(s *scanner, c byte) int {
	if c == '\\' {
		s.step = stateInSingleQuotedStringEsc
		return scanContinue
	}
	if c == '\'' {
		s.step = stateEndValue
		return scanContinue
	}
	return scanContinue
}

func stateInSingleQuotedStringEsc(s *scanner, c byte) int {
	switch c {
	case '\\', '\'':
		s.step = stateInSingleQuotedString
		return scanContinue
	}
	return s.error(c, "in string escape code")
}

func stateInDoubleQuotedString(s *scanner, c byte) int {
	if c == '\\' {
		s.step = stateInDqStringEsc
		return scanContinue
	}
	if c == '"' {
		s.step = stateEndValue
		return scanContinue
	}
	return scanContinue
}

func stateInDqStringEsc(s *scanner, c byte) int {
	switch c {
	case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
		s.step = stateInDoubleQuotedString
		return scanContinue
	}
	return s.error(c, "in string escape code")
}

func stateInUnquotedString(s *scanner, c byte) int {
	if isAllowedInUnquotedString(c) {
		return scanContinue
	}
	return stateEndValue(s, c)
}

func stateListOrArray(s *scanner, c byte) int {
	if isSpace(c) {
		return scanSkipSpace
	}
	switch c {
	case 'B', 'I', 'L':
		s.step = stateListOrArrayT
		return scanBeginLiteral
	case ']':
		return stateEndValue(s, c)
	default:
		return stateBeginValue(s, c)
	}
}

func stateListOrArrayT(s *scanner, c byte) int {
	if c == ';' {
		s.step = stateArrayT
		return scanListType
	}
	return stateInUnquotedString(s, c)
}

func stateArrayT(s *scanner, c byte) int {
	if isSpace(c) {
		return scanSkipSpace
	}
	if c == ']' { // empty array
		return scanEndValue
	}
	return stateBeginValue(s, c)
}

func stateNum0(s *scanner, c byte) int {
	if isNumber(c) {
		s.step = stateNum1
		return scanContinue
	}
	return stateEndNumValue(s, c)
}

func stateNum1(s *scanner, c byte) int {
	if isNumber(c) {
		s.step = stateNum1
		return scanContinue
	}
	if c == '.' {
		s.step = stateNumDot
		return scanContinue
	}
	return stateEndNumValue(s, c)
}

// stateDot is the state after reading the integer and decimal point in a number,
// such as after reading `1.`.
func stateNumDot(s *scanner, c byte) int {
	if isNumber(c) {
		s.step = stateNumDot0
		return scanContinue
	}
	if isAllowedInUnquotedString(c) {
		s.step = stateInUnquotedString
		return scanContinue
	}
	return s.error(c, "after decimal point in numeric literal")
}

// stateNumDot0 is the state after reading the integer, decimal point, and subsequent
// digits of a number, such as after reading `3.14`.
func stateNumDot0(s *scanner, c byte) int {
	if isNumber(c) {
		s.step = stateNumDot0
		return scanContinue
	}
	return stateEndNumDotValue(s, c)
}

func stateEndNumValue(s *scanner, c byte) int {
	switch c {
	case 'b', 'B': // TAG_Byte
		s.step = stateEndValue
		return scanContinue
	case 's', 'S': // TAG_Short
		s.step = stateEndValue
		return scanContinue
	case 'l', 'L': // TAG_Long
		s.step = stateEndValue
		return scanContinue
	case 'f', 'F', 'd', 'D':
		return stateEndNumDotValue(s, c)
	}
	if isAllowedInUnquotedString(c) {
		s.step = stateInUnquotedString
		return scanContinue
	}
	return stateEndValue(s, c)
}

func stateEndNumDotValue(s *scanner, c byte) int {
	switch c {
	case 'f', 'F': // TAG_Float
		s.step = stateEndValue
		return scanContinue
	case 'd', 'D': // TAG_Double
		s.step = stateEndValue
		return scanContinue
	}
	return stateEndValue(s, c)
}

func stateEndValue(s *scanner, c byte) int {
	n := len(s.parseState)
	if n == 0 {
		// Completed top-level before the current byte.
		s.step = stateEndTop
		s.endTop = true
		return stateEndTop(s, c)
	}
	if isSpace(c) {
		return scanSkipSpace
	}

	ps := s.parseState[n-1]
	switch ps {
	case parseCompoundName:
		if c == ':' {
			s.parseState[n-1] = parseCompoundValue
			s.step = stateBeginValue
			return scanCompoundTagName
		}
		return s.error(c, "after compound tag name")
	case parseCompoundValue:
		switch c {
		case ',':
			s.parseState[n-1] = parseCompoundName
			s.step = stateBeginString
			return scanCompoundValue
		case '}':
			s.popParseState()
			return scanEndValue
		}
		return s.error(c, "after compound value")
	case parseListValue:
		switch c {
		case ',':
			s.step = stateBeginValue
			return scanListValue
		case ']':
			s.popParseState()
			return scanEndValue
		}
		return s.error(c, "after list element")
	}
	return s.error(c, "")
}

func (s *scanner) error(c byte, context string) int {
	s.step = stateError
	s.errContext = "invalid character " + quoteChar(c) + " " + context
	return scanError
}

// stateError is the state after reaching a syntax error,
// such as after reading `[1}` or `5.1.2`.
func stateError(*scanner, byte) int {
	return scanError
}

func isSpace(c byte) bool {
	return c <= ' ' && (c == ' ' || c == '\t' || c == '\r' || c == '\n')
}

func isNumber(c byte) bool {
	return c >= '0' && c <= '9'
}

func isAllowedInUnquotedString(c byte) bool {
	return c == '_' || c == '-' ||
		c == '.' || c == '+' ||
		c >= '0' && c <= '9' ||
		c >= 'A' && c <= 'Z' ||
		c >= 'a' && c <= 'z'
}

// quoteChar formats c as a quoted character literal
func quoteChar(c byte) string {
	// special cases - different from quoted strings
	if c == '\'' {
		return `'\''`
	}
	if c == '"' {
		return `'"'`
	}

	// use quoted string with different quotation marks
	s := strconv.Quote(string(c))
	return "'" + s[1:len(s)-1] + "'"
}
