/*
Copyright 2023 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package ini

// Copy from https://github.com/aws/aws-sdk-go
// May have been modified by Beijing Volcanoengine Technology Ltd.

import (
	"fmt"
	"strconv"
	"strings"
)

var (
	runesTrue  = []rune("true")
	runesFalse = []rune("false")
)

var literalValues = [][]rune{
	runesTrue,
	runesFalse,
}

func isBoolValue(b []rune) bool {
	for _, lv := range literalValues {
		if isLitValue(lv, b) {
			return true
		}
	}
	return false
}

func isLitValue(want, have []rune) bool {
	if len(have) < len(want) {
		return false
	}

	for i := 0; i < len(want); i++ {
		if want[i] != have[i] {
			return false
		}
	}

	return true
}

// isNumberValue will return whether not the leading characters in
// a byte slice is a number. A number is delimited by whitespace or
// the newline token.
//
// A number is defined to be in a binary, octal, decimal (int | float), hex format,
// or in scientific notation.
func isNumberValue(b []rune) bool {
	negativeIndex := 0
	helper := numberHelper{}
	needDigit := false

	for i := 0; i < len(b); i++ {
		negativeIndex++

		switch b[i] {
		case '-':
			if helper.IsNegative() || negativeIndex != 1 {
				return false
			}
			helper.Determine(b[i])
			needDigit = true
			continue
		case 'e', 'E':
			if err := helper.Determine(b[i]); err != nil {
				return false
			}
			negativeIndex = 0
			needDigit = true
			continue
		case 'b':
			if helper.numberFormat == hex {
				break
			}
			fallthrough
		case 'o', 'x':
			needDigit = true
			if i == 0 {
				return false
			}

			fallthrough
		case '.':
			if err := helper.Determine(b[i]); err != nil {
				return false
			}
			needDigit = true
			continue
		}

		if i > 0 && (isNewline(b[i:]) || isWhitespace(b[i])) {
			return !needDigit
		}

		if !helper.CorrectByte(b[i]) {
			return false
		}
		needDigit = false
	}

	return !needDigit
}

func isValid(b []rune) (bool, int, error) {
	if len(b) == 0 {
		// TODO: should probably return an error
		return false, 0, nil
	}

	return isValidRune(b[0]), 1, nil
}

func isValidRune(r rune) bool {
	return r != ':' && r != '=' && r != '[' && r != ']' && r != ' ' && r != '\n'
}

// ValueType is an enum that will signify what type
// the Value is
type ValueType int

func (v ValueType) String() string {
	switch v {
	case NoneType:
		return "NONE"
	case DecimalType:
		return "FLOAT"
	case IntegerType:
		return "INT"
	case StringType:
		return "STRING"
	case BoolType:
		return "BOOL"
	}

	return ""
}

// ValueType enums
const (
	NoneType = ValueType(iota)
	DecimalType
	IntegerType
	StringType
	QuotedStringType
	BoolType
)

// Value is a union container
type Value struct {
	Type ValueType
	raw  []rune

	integer int64
	decimal float64
	boolean bool
	str     string
}

func newValue(t ValueType, base int, raw []rune) (Value, error) {
	v := Value{
		Type: t,
		raw:  raw,
	}
	var err error

	switch t {
	case DecimalType:
		v.decimal, err = strconv.ParseFloat(string(raw), 64)
	case IntegerType:
		if base != 10 {
			raw = raw[2:]
		}

		v.integer, err = strconv.ParseInt(string(raw), base, 64)
	case StringType:
		v.str = string(raw)
	case QuotedStringType:
		v.str = string(raw[1 : len(raw)-1])
	case BoolType:
		v.boolean = runeCompare(v.raw, runesTrue)
	}

	// issue 2253
	//
	// if the value trying to be parsed is too large, then we will use
	// the 'StringType' and raw value instead.
	if nerr, ok := err.(*strconv.NumError); ok && nerr.Err == strconv.ErrRange {
		v.Type = StringType
		v.str = string(raw)
		err = nil
	}

	return v, err
}

// Append will append values and change the type to a string
// type.
func (v *Value) Append(tok Token) {
	r := tok.Raw()
	if v.Type != QuotedStringType {
		v.Type = StringType
		r = tok.raw[1 : len(tok.raw)-1]
	}
	if tok.Type() != TokenLit {
		v.raw = append(v.raw, tok.Raw()...)
	} else {
		v.raw = append(v.raw, r...)
	}
}

func (v Value) String() string {
	switch v.Type {
	case DecimalType:
		return fmt.Sprintf("decimal: %f", v.decimal)
	case IntegerType:
		return fmt.Sprintf("integer: %d", v.integer)
	case StringType:
		return fmt.Sprintf("string: %s", string(v.raw))
	case QuotedStringType:
		return fmt.Sprintf("quoted string: %s", string(v.raw))
	case BoolType:
		return fmt.Sprintf("bool: %t", v.boolean)
	default:
		return "union not set"
	}
}

func newLitToken(b []rune) (Token, int, error) {
	n := 0
	var err error

	token := Token{}
	if b[0] == '"' {
		n, err = getStringValue(b)
		if err != nil {
			return token, n, err
		}

		token = newToken(TokenLit, b[:n], QuotedStringType)
	} else if isNumberValue(b) {
		var base int
		base, n, err = getNumericalValue(b)
		if err != nil {
			return token, 0, err
		}

		value := b[:n]
		vType := IntegerType
		if contains(value, '.') || hasExponent(value) {
			vType = DecimalType
		}
		token = newToken(TokenLit, value, vType)
		token.base = base
	} else if isBoolValue(b) {
		n, err = getBoolValue(b)

		token = newToken(TokenLit, b[:n], BoolType)
	} else {
		n, err = getValue(b)
		token = newToken(TokenLit, b[:n], StringType)
	}

	return token, n, err
}

// IntValue returns an integer value
func (v Value) IntValue() int64 {
	return v.integer
}

// FloatValue returns a float value
func (v Value) FloatValue() float64 {
	return v.decimal
}

// BoolValue returns a bool value
func (v Value) BoolValue() bool {
	return v.boolean
}

func isTrimmable(r rune) bool {
	switch r {
	case '\n', ' ':
		return true
	}
	return false
}

// StringValue returns the string value
func (v Value) StringValue() string {
	switch v.Type {
	case StringType:
		return strings.TrimFunc(string(v.raw), isTrimmable)
	case QuotedStringType:
		// preserve all characters in the quotes
		return string(removeEscapedCharacters(v.raw[1 : len(v.raw)-1]))
	default:
		return strings.TrimFunc(string(v.raw), isTrimmable)
	}
}

func contains(runes []rune, c rune) bool {
	for i := 0; i < len(runes); i++ {
		if runes[i] == c {
			return true
		}
	}

	return false
}

func runeCompare(v1 []rune, v2 []rune) bool {
	if len(v1) != len(v2) {
		return false
	}

	for i := 0; i < len(v1); i++ {
		if v1[i] != v2[i] {
			return false
		}
	}

	return true
}
