package scan

// TODO: comment re context

//  o grammar for numbers & case-through
//    k len 0
//    - len 1
//    k has leading minus; strip & rest
//    - 0x, 0b, 0[0-9]
//    - decimal: leading minus; [0-9]+
//    - octal:   leading minus; 0[0-7]+
//    - hex:     leading minus; 0[xX][0-9a-fA-F]+
//    - float:   leadinug minus; [0-9] or '.'
//
//  o float literals:
//    123 123.  123.4 .234
//    1e2 1e-2 1.2e3 1.e3 1.2e-3 1.e-3
//    .2e3 .2e-3 1.e-3
//
//    ?- [0-9]+
//    ?- [0-9]+ '.' [0-9]*
//    ?- [0-9]* '.' [0-9]+
//    ?- [0-9]+            [eE] ?- [0-9]+
//    ?- [0-9]+ '.' [0-9]* [eE] ?- [0-9]+
//    ?- [0-9]* '.' [0-9]+ [eE] ?- [0-9]+

func FindScanType(sinput string) ScanType {
	input := []byte(sinput)

	if len(input) == 0 {
		return scanTypeString
	}

	i0 := input[0]
	if i0 == '-' {
		return findScanTypePositiveNumberOrString(input[1:])
	}
	if i0 == '+' {
		return findScanTypePositiveNumberOrString(input[1:])
	}
	if i0 >= '0' && i0 <= '9' {
		return findScanTypePositiveNumberOrString(input)
	}
	if i0 == '.' {
		if len(input) == 1 {
			return scanTypeString
		}
		return findScanTypePositiveDecimalOrFloatOrString(input)
	}

	return scanTypeString
}

func findScanTypePositiveNumberOrString(input []byte) ScanType {
	if len(input) == 0 {
		return scanTypeString
	}
	i0 := input[0]

	if i0 == '.' {
		return findScanTypePositiveFloatOrString(input)
	}

	if isDecimalDigit(i0) {
		if len(input) == 1 {
			return scanTypeDecimalInt
		}
		if i0 == '0' {
			i1 := input[1]
			if i1 == 'x' || i1 == 'X' {
				if len(input) == 2 {
					return scanTypeString
				}
				return findScanTypePositiveHexOrString(input[2:])
			}
			if i1 == 'o' || i1 == 'O' {
				if len(input) == 2 {
					return scanTypeString
				}
				return findScanTypePositiveOctalOrString(input[2:])
			}
			if i1 == 'b' || i1 == 'B' {
				if len(input) == 2 {
					return scanTypeString
				}
				return findScanTypePositiveBinaryOrString(input[2:])
			}

			allOctal := true
			allDecimal := true
			for _, c := range input[1:] {
				if !isOctalDigit(c) {
					allOctal = false
				}
				if !isDecimalDigit(c) {
					allDecimal = false
					break
				}
			}
			if allOctal {
				return scanTypeLeadingZeroOctalInt
			}
			if allDecimal {
				return scanTypeLeadingZeroDecimalInt
			}
			// else fall through
		}

		return findScanTypePositiveDecimalOrFloatOrString(input)
	}

	return scanTypeString
}

func findScanTypePositiveFloatOrString(input []byte) ScanType {
	for _, c := range []byte(input) {
		if !isFloatDigit(c) {
			return scanTypeString
		}
	}
	return scanTypeMaybeFloat
}

func findScanTypePositiveDecimalOrFloatOrString(input []byte) ScanType {
	maybeInt := true
	for _, c := range []byte(input) {
		// All float digits are decimal-int digits so if the current character
		// is not a float digit, this can't be either a float or a decimal int.
		// Example: "1x2"
		if !isFloatDigit(c) {
			return scanTypeString
		}

		// Examples: "1e2" or "1x2".
		if !isDecimalDigit(c) {
			maybeInt = false
		}
	}
	if maybeInt {
		return scanTypeDecimalInt
	}
	return scanTypeMaybeFloat
}

// Leading 0o has already been stripped
func findScanTypePositiveOctalOrString(input []byte) ScanType {
	for _, c := range []byte(input) {
		if !isOctalDigit(c) {
			return scanTypeString
		}
	}
	return scanTypeOctalInt
}

// Leading 0x has already been stripped
func findScanTypePositiveHexOrString(input []byte) ScanType {
	for _, c := range []byte(input) {
		if !isHexDigit(c) {
			return scanTypeString
		}
	}
	return scanTypeHexInt
}

// Leading 0b has already been stripped
func findScanTypePositiveBinaryOrString(input []byte) ScanType {
	for _, c := range []byte(input) {
		if c < '0' || c > '1' {
			return scanTypeString
		}
	}
	return scanTypeBinaryInt
}
