package metricsql

import (
	"fmt"
	"math"
	"strconv"
	"strings"
	"unicode"
	"unicode/utf8"
)

type lexer struct {
	// Token contains the currently parsed token.
	// An empty token means EOF.
	Token string

	prevTokens []string
	nextTokens []string

	sOrig string
	sTail string

	err error
}

func (lex *lexer) Context() string {
	return fmt.Sprintf("%s%s", lex.Token, lex.sTail)
}

func (lex *lexer) Init(s string) {
	lex.Token = ""
	lex.prevTokens = nil
	lex.nextTokens = nil
	lex.err = nil

	lex.sOrig = s
	lex.sTail = s
}

func (lex *lexer) PushBack(currToken, sHead string) {
	lex.Token = currToken
	lex.sTail = sHead + lex.sTail
}

func (lex *lexer) Next() error {
	if lex.err != nil {
		return lex.err
	}
	lex.prevTokens = append(lex.prevTokens, lex.Token)
	if len(lex.nextTokens) > 0 {
		lex.Token = lex.nextTokens[len(lex.nextTokens)-1]
		lex.nextTokens = lex.nextTokens[:len(lex.nextTokens)-1]
		return nil
	}
	token, err := lex.next()
	if err != nil {
		lex.err = err
		return err
	}
	lex.Token = token
	return nil
}

func (lex *lexer) next() (string, error) {
again:
	// Skip whitespace
	s := lex.sTail
	i := 0
	for i < len(s) && isSpaceChar(s[i]) {
		i++
	}
	s = s[i:]
	lex.sTail = s

	if len(s) == 0 {
		return "", nil
	}

	var token string
	var err error
	switch s[0] {
	case '#':
		// Skip comment till the end of string
		s = s[1:]
		n := strings.IndexByte(s, '\n')
		if n < 0 {
			return "", nil
		}
		lex.sTail = s[n+1:]
		goto again
	case '{', '}', '[', ']', '(', ')', ',', '@':
		token = s[:1]
		goto tokenFoundLabel
	}
	if isIdentPrefix(s) {
		token = scanIdent(s)
		goto tokenFoundLabel
	}
	if isStringPrefix(s) {
		token, err = scanString(s)
		if err != nil {
			return "", err
		}
		goto tokenFoundLabel
	}
	if n := scanBinaryOpPrefix(s); n > 0 {
		token = s[:n]
		goto tokenFoundLabel
	}
	if n := scanTagFilterOpPrefix(s); n > 0 {
		token = s[:n]
		goto tokenFoundLabel
	}
	if n := scanDuration(s); n > 0 {
		token = s[:n]
		goto tokenFoundLabel
	}
	if isPositiveNumberPrefix(s) {
		token, err = scanPositiveNumber(s)
		if err != nil {
			return "", err
		}
		goto tokenFoundLabel
	}
	if strings.HasPrefix(s, "$__interval") {
		lex.sTail = s[len("$__interval"):]
		return "$__interval", nil
	}
	if strings.HasPrefix(s, "$__rate_interval") {
		lex.sTail = s[len("$__rate_interval"):]
		return "$__interval", nil
	}
	return "", fmt.Errorf("cannot recognize %q", s)

tokenFoundLabel:
	lex.sTail = s[len(token):]
	return token, nil
}

func scanString(s string) (string, error) {
	if len(s) < 2 {
		return "", fmt.Errorf("cannot find end of string in %q", s)
	}

	quote := s[0]
	i := 1
	for {
		n := strings.IndexByte(s[i:], quote)
		if n < 0 {
			return "", fmt.Errorf("cannot find closing quote %c for the string %q", quote, s)
		}
		i += n
		bs := 0
		for bs < i && s[i-bs-1] == '\\' {
			bs++
		}
		if bs%2 == 0 {
			token := s[:i+1]
			return token, nil
		}
		i++
	}
}

func parsePositiveNumber(s string) (float64, error) {
	if isSpecialIntegerPrefix(s) {
		n, err := strconv.ParseInt(s, 0, 64)
		if err != nil {
			return 0, err
		}
		return float64(n), nil
	}
	s = strings.ToLower(s)
	m := float64(1)
	switch true {
	case strings.HasSuffix(s, "kib"):
		s = s[:len(s)-3]
		m = 1024
	case strings.HasSuffix(s, "ki"):
		s = s[:len(s)-2]
		m = 1024
	case strings.HasSuffix(s, "kb"):
		s = s[:len(s)-2]
		m = 1000
	case strings.HasSuffix(s, "k"):
		s = s[:len(s)-1]
		m = 1000
	case strings.HasSuffix(s, "mib"):
		s = s[:len(s)-3]
		m = 1024 * 1024
	case strings.HasSuffix(s, "mi"):
		s = s[:len(s)-2]
		m = 1024 * 1024
	case strings.HasSuffix(s, "mb"):
		s = s[:len(s)-2]
		m = 1000 * 1000
	case strings.HasSuffix(s, "m"):
		s = s[:len(s)-1]
		m = 1000 * 1000
	case strings.HasSuffix(s, "gib"):
		s = s[:len(s)-3]
		m = 1024 * 1024 * 1024
	case strings.HasSuffix(s, "gi"):
		s = s[:len(s)-2]
		m = 1024 * 1024 * 1024
	case strings.HasSuffix(s, "gb"):
		s = s[:len(s)-2]
		m = 1000 * 1000 * 1000
	case strings.HasSuffix(s, "g"):
		s = s[:len(s)-1]
		m = 1000 * 1000 * 1000
	case strings.HasSuffix(s, "tib"):
		s = s[:len(s)-3]
		m = 1024 * 1024 * 1024 * 1024
	case strings.HasSuffix(s, "ti"):
		s = s[:len(s)-2]
		m = 1024 * 1024 * 1024 * 1024
	case strings.HasSuffix(s, "tb"):
		s = s[:len(s)-2]
		m = 1000 * 1000 * 1000 * 1000
	case strings.HasSuffix(s, "t"):
		s = s[:len(s)-1]
		m = 1000 * 1000 * 1000 * 1000
	}
	v, err := strconv.ParseFloat(s, 64)
	if err != nil {
		return 0, err
	}
	return v * m, nil
}

func scanPositiveNumber(s string) (string, error) {
	// Scan integer part. It may be empty if fractional part exists.
	i := 0
	skipChars, isHex := scanSpecialIntegerPrefix(s)
	i += skipChars
	if isHex {
		// Scan integer hex number
		for i < len(s) && isHexChar(s[i]) {
			i++
		}
		return s[:i], nil
	}
	for i < len(s) && isDecimalCharOrUnderscore(s[i]) {
		i++
	}

	if i == len(s) {
		if i == 0 {
			return "", fmt.Errorf("number cannot be empty")
		}
		return s, nil
	}
	if sLen := scanNumMultiplier(s[i:]); sLen > 0 {
		i += sLen
		return s[:i], nil
	}
	if s[i] != '.' && s[i] != 'e' && s[i] != 'E' {
		if i == 0 {
			return "", fmt.Errorf("missing positive number")
		}
		return s[:i], nil
	}

	if s[i] == '.' {
		// Scan fractional part. It cannot be empty.
		i++
		j := i
		for j < len(s) && isDecimalCharOrUnderscore(s[j]) {
			j++
		}
		i = j
		if i == len(s) {
			return s, nil
		}
	}
	if sLen := scanNumMultiplier(s[i:]); sLen > 0 {
		i += sLen
		return s[:i], nil
	}

	if s[i] != 'e' && s[i] != 'E' {
		return s[:i], nil
	}
	i++

	// Scan exponent part.
	if i == len(s) {
		return "", fmt.Errorf("missing exponent part in %q", s)
	}
	if s[i] == '-' || s[i] == '+' {
		i++
	}
	j := i
	for j < len(s) && isDecimalChar(s[j]) {
		j++
	}
	if j == i {
		return "", fmt.Errorf("missing exponent part in %q", s)
	}
	return s[:j], nil
}

func scanNumMultiplier(s string) int {
	if len(s) > 3 {
		s = s[:3]
	}
	s = strings.ToLower(s)
	switch true {
	case strings.HasPrefix(s, "kib"):
		return 3
	case strings.HasPrefix(s, "ki"):
		return 2
	case strings.HasPrefix(s, "kb"):
		return 2
	case strings.HasPrefix(s, "k"):
		return 1
	case strings.HasPrefix(s, "mib"):
		return 3
	case strings.HasPrefix(s, "mi"):
		return 2
	case strings.HasPrefix(s, "mb"):
		return 2
	case strings.HasPrefix(s, "m"):
		return 1
	case strings.HasPrefix(s, "gib"):
		return 3
	case strings.HasPrefix(s, "gi"):
		return 2
	case strings.HasPrefix(s, "gb"):
		return 2
	case strings.HasPrefix(s, "g"):
		return 1
	case strings.HasPrefix(s, "tib"):
		return 3
	case strings.HasPrefix(s, "ti"):
		return 2
	case strings.HasPrefix(s, "tb"):
		return 2
	case strings.HasPrefix(s, "t"):
		return 1
	default:
		return 0
	}
}

func scanIdent(s string) string {
	i := 0
	for i < len(s) {
		r, size := utf8.DecodeRuneInString(s[i:])
		if i == 0 && isFirstIdentChar(r) || i > 0 && isIdentChar(r) {
			i += size
			continue
		}
		if r != '\\' {
			break
		}
		i += size
		r, n := decodeEscapeSequence(s[i:])
		if r == utf8.RuneError {
			// Invalid escape sequence
			i -= size
			break
		}
		i += n
	}
	if i == 0 {
		panic("BUG: scanIdent couldn't find a single ident char; make sure isIdentPrefix called before scanIdent")
	}
	return s[:i]
}

func unescapeIdent(s string) string {
	n := strings.IndexByte(s, '\\')
	if n < 0 {
		return s
	}
	dst := make([]byte, 0, len(s))
	for {
		dst = append(dst, s[:n]...)
		s = s[n+1:]
		r, size := decodeEscapeSequence(s)
		if r == utf8.RuneError {
			// Cannot decode escape sequence. Put it in the output as is
			dst = append(dst, '\\')
		} else {
			dst = utf8.AppendRune(dst, r)
			s = s[size:]
		}
		n = strings.IndexByte(s, '\\')
		if n < 0 {
			dst = append(dst, s...)
			return string(dst)
		}
	}
}

func hasEscapedChars(s string) bool {
	i := 0
	for i < len(s) {
		r, size := utf8.DecodeRuneInString(s[i:])
		if i == 0 && !isFirstIdentChar(r) || i > 0 && !isIdentChar(r) {
			return true
		}
		i += size
	}
	return false
}

func appendQuotedIdent(dst []byte, s string) []byte {
	dst = utf8.AppendRune(dst, '"')
	for i := 0; i < len(s); {
		r, size := utf8.DecodeRuneInString(s[i:])
		dst = utf8.AppendRune(dst, r)
		i += size
	}
	dst = utf8.AppendRune(dst, '"')
	return dst
}

func appendEscapedIdent(dst []byte, s string) []byte {
	i := 0
	for i < len(s) {
		r, size := utf8.DecodeRuneInString(s[i:])
		if i == 0 && isFirstIdentChar(r) || i > 0 && isIdentChar(r) {
			dst = utf8.AppendRune(dst, r)
		} else {
			dst = appendEscapeSequence(dst, r)
		}
		i += size
	}
	return dst
}

func ifEscapedCharsAppendQuotedIdent(dst []byte, s string) []byte {
	if hasEscapedChars(s) {
		return appendQuotedIdent(dst, s)
	}
	return appendEscapedIdent(dst, s)
}

func (lex *lexer) Prev() {
	lex.nextTokens = append(lex.nextTokens, lex.Token)
	lex.Token = lex.prevTokens[len(lex.prevTokens)-1]
	lex.prevTokens = lex.prevTokens[:len(lex.prevTokens)-1]
}

func isEOF(s string) bool {
	return len(s) == 0
}

func scanTagFilterOpPrefix(s string) int {
	if len(s) >= 2 {
		switch s[:2] {
		case "=~", "!~", "!=":
			return 2
		}
	}
	if len(s) >= 1 {
		if s[0] == '=' {
			return 1
		}
	}
	return -1
}

func isInfOrNaN(s string) bool {
	if len(s) != 3 {
		return false
	}
	s = strings.ToLower(s)
	return s == "inf" || s == "nan"
}

func isOffset(s string) bool {
	s = strings.ToLower(s)
	return s == "offset"
}

func isStringPrefix(s string) bool {
	if len(s) == 0 {
		return false
	}
	switch s[0] {
	// See https://prometheus.io/docs/prometheus/latest/querying/basics/#string-literals
	case '"', '\'', '`':
		return true
	default:
		return false
	}
}

func isPositiveNumberPrefix(s string) bool {
	if len(s) == 0 {
		return false
	}
	if isDecimalChar(s[0]) {
		return true
	}

	// Check for .234 numbers
	if s[0] != '.' || len(s) < 2 {
		return false
	}
	return isDecimalChar(s[1])
}

func isSpecialIntegerPrefix(s string) bool {
	skipChars, _ := scanSpecialIntegerPrefix(s)
	return skipChars > 0
}

func scanSpecialIntegerPrefix(s string) (skipChars int, isHex bool) {
	if len(s) < 1 || s[0] != '0' {
		return 0, false
	}
	s = strings.ToLower(s[1:])
	if len(s) == 0 {
		return 0, false
	}
	if isDecimalChar(s[0]) {
		// octal number: 0123
		return 1, false
	}
	if s[0] == 'x' {
		// 0x
		return 2, true
	}
	if s[0] == 'o' || s[0] == 'b' {
		// 0x, 0o or 0b prefix
		return 2, false
	}
	return 0, false
}

func isPositiveDuration(s string) bool {
	if s == "$__interval" {
		return true
	}
	n := scanDuration(s)
	return n == len(s)
}

// PositiveDurationValue returns positive duration in milliseconds for the given s
// and the given step.
//
// Duration in s may be combined, i.e. 2h5m or 2h-5m.
//
// Error is returned if the duration in s is negative.
func PositiveDurationValue(s string, step int64) (int64, error) {
	d, err := DurationValue(s, step)
	if err != nil {
		return 0, err
	}
	if d < 0 {
		return 0, fmt.Errorf("duration cannot be negative; got %q", s)
	}
	return d, nil
}

// DurationValue returns the duration in milliseconds for the given s
// and the given step.
//
// Duration in s may be combined, i.e. 2h5m, -2h5m or 2h-5m.
//
// The returned duration value can be negative.
func DurationValue(s string, step int64) (int64, error) {
	if len(s) == 0 {
		return 0, fmt.Errorf("duration cannot be empty")
	}
	lastChar := s[len(s)-1]
	if lastChar >= '0' && lastChar <= '9' || lastChar == '.' {
		// Try parsing floating-point duration
		d, err := strconv.ParseFloat(s, 64)
		if err == nil {
			// Convert the duration to milliseconds.
			return int64(d * 1000), nil
		}
	}
	isMinus := false
	d := float64(0)
	for len(s) > 0 {
		n := scanSingleDuration(s, true)
		if n <= 0 {
			return 0, fmt.Errorf("cannot parse duration %q", s)
		}
		ds := s[:n]
		s = s[n:]
		dLocal, err := parseSingleDuration(ds, step)
		if err != nil {
			return 0, err
		}
		if isMinus && dLocal > 0 {
			dLocal = -dLocal
		}
		d += dLocal
		if dLocal < 0 {
			isMinus = true
		}
	}
	if math.Abs(d) > 1<<63-1 {
		return 0, fmt.Errorf("too big duration %.0fms", d)
	}
	return int64(d), nil
}

func parseSingleDuration(s string, step int64) (float64, error) {
	if s == "$__interval" {
		return float64(step), nil
	}

	s = strings.ToLower(s)
	numPart := s[:len(s)-1]
	// Strip trailing m if the duration is in ms
	numPart = strings.TrimSuffix(numPart, "m")
	f, err := strconv.ParseFloat(numPart, 64)
	if err != nil {
		return 0, fmt.Errorf("cannot parse duration %q: %s", s, err)
	}
	var mp float64
	switch s[len(numPart):] {
	case "ms":
		mp = 1
	case "s":
		mp = 1000
	case "m":
		mp = 60 * 1000
	case "h":
		mp = 60 * 60 * 1000
	case "d":
		mp = 24 * 60 * 60 * 1000
	case "w":
		mp = 7 * 24 * 60 * 60 * 1000
	case "y":
		mp = 365 * 24 * 60 * 60 * 1000
	case "i":
		mp = float64(step)
	default:
		return 0, fmt.Errorf("invalid duration suffix in %q", s)
	}
	return mp * f, nil
}

// scanDuration scans duration, which must start with positive num.
//
// I.e. 123h, 3h5m or 3.4d-35.66s
func scanDuration(s string) int {
	// The first part must be non-negative
	n := scanSingleDuration(s, false)
	if n <= 0 {
		return -1
	}
	s = s[n:]
	i := n
	for {
		// Other parts may be negative
		n := scanSingleDuration(s, true)
		if n <= 0 {
			return i
		}
		s = s[n:]
		i += n
	}
}

func scanSingleDuration(s string, canBeNegative bool) int {
	if len(s) == 0 {
		return -1
	}
	i := 0
	if s[0] == '-' && canBeNegative {
		i++
	}
	if s[i:] == "$__interval" {
		return i + len("$__interval")
	}
	for i < len(s) && isDecimalChar(s[i]) {
		i++
	}
	if i == 0 || i == len(s) {
		return -1
	}
	if s[i] == '.' {
		j := i
		i++
		for i < len(s) && isDecimalChar(s[i]) {
			i++
		}
		if i == j || i == len(s) {
			return -1
		}
	}
	switch unicode.ToLower(rune(s[i])) {
	case 'm':
		if i+1 < len(s) {
			switch unicode.ToLower(rune(s[i+1])) {
			case 's':
				// duration in ms
				return i + 2
			case 'i', 'b':
				// This is not a duration, but Mi or MB suffix.
				// See parsePositiveNumber() and https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3664
				return -1
			}
		}
		// Allow small m for durtion in minutes.
		// Big M means 1e6.
		// See parsePositiveNumber() and https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3664
		if s[i] == 'm' {
			return i + 1
		}
		return -1
	case 's', 'h', 'd', 'w', 'y', 'i':
		return i + 1
	default:
		return -1
	}
}

func isDecimalChar(ch byte) bool {
	return ch >= '0' && ch <= '9'
}

func isDecimalCharOrUnderscore(ch byte) bool {
	return isDecimalChar(ch) || ch == '_'
}

func isHexChar(ch byte) bool {
	return isDecimalChar(ch) || ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F'
}

func isIdentPrefix(s string) bool {
	if len(s) == 0 {
		return false
	}
	r, size := utf8.DecodeRuneInString(s)
	if r == '\\' {
		r, _ = decodeEscapeSequence(s[size:])
		return r != utf8.RuneError
	}
	return isFirstIdentChar(r)
}

func isFirstIdentChar(r rune) bool {
	if unicode.IsLetter(r) {
		return true
	}
	return r == '_' || r == ':'
}

func isIdentChar(r rune) bool {
	if isFirstIdentChar(r) {
		return true
	}
	return r < 256 && isDecimalChar(byte(r)) || r == '.'
}

func isSpaceChar(ch byte) bool {
	switch ch {
	case ' ', '\t', '\n', '\v', '\f', '\r':
		return true
	default:
		return false
	}
}

func appendEscapeSequence(dst []byte, r rune) []byte {
	dst = append(dst, '\\')
	if unicode.IsPrint(r) {
		return utf8.AppendRune(dst, r)
	}
	// hex-encode non-printable chars
	if r < 256 {
		return append(dst, 'x', toHex(byte(r>>4)), toHex(byte(r&0xf)))
	}
	return append(dst, 'u', toHex(byte(r>>12)), toHex(byte((r>>8)&0xf)), toHex(byte(r>>4)), toHex(byte(r&0xf)))
}

func decodeEscapeSequence(s string) (rune, int) {
	if strings.HasPrefix(s, "x") || strings.HasPrefix(s, "X") {
		if len(s) >= 3 {
			h1 := fromHex(s[1])
			h2 := fromHex(s[2])
			if h1 >= 0 && h2 >= 0 {
				r := rune((h1 << 4) | h2)
				return r, 3
			}
		}
		return utf8.RuneError, 0
	}
	if strings.HasPrefix(s, "u") || strings.HasPrefix(s, "U") {
		if len(s) >= 5 {
			h1 := fromHex(s[1])
			h2 := fromHex(s[2])
			h3 := fromHex(s[3])
			h4 := fromHex(s[4])
			if h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0 {
				return rune((h1 << 12) | (h2 << 8) | (h3 << 4) | h4), 5
			}
		}
		return utf8.RuneError, 0
	}
	r, size := utf8.DecodeRuneInString(s)
	if unicode.IsPrint(r) {
		return r, size
	}
	// Improperly escaped non-printable char
	return utf8.RuneError, 0
}

func fromHex(ch byte) int {
	if ch >= '0' && ch <= '9' {
		return int(ch - '0')
	}
	if ch >= 'a' && ch <= 'f' {
		return int((ch - 'a') + 10)
	}
	if ch >= 'A' && ch <= 'F' {
		return int((ch - 'A') + 10)
	}
	return -1
}

func toHex(n byte) byte {
	if n < 10 {
		return '0' + n
	}
	return 'a' + (n - 10)
}
