// Copyright 2020 The Libc Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package libc // import "modernc.org/libc"

import (
	"io"
	"strconv"
	"unsafe"
)

// The format string consists of a sequence of directives which describe how to
// process the sequence of input characters.  If processing of a directive
// fails, no further input  is  read,  and scanf()  returns.   A "failure" can
// be either of the following: input failure, meaning that input characters
// were unavailable, or matching failure, meaning that the input was
// inappropriate.
func scanf(r io.ByteScanner, format, args uintptr) (nvalues int32) {
	// var src []byte //TODO-
	var ok bool
out:
	for {
		c := *(*byte)(unsafe.Pointer(format))
		// src = append(src, c) //TODO-
		switch c {
		case '%':
			var n int
			var match bool
			format, n, match = scanfConversion(r, format, &args)
			if !match {
				break out
			}

			nvalues += int32(n)
			ok = true
		case 0:
			break out
		case ' ', '\t', '\n', '\r', '\v', '\f':
			format = skipWhiteSpace(format)
			ok = true
		next:
			for {
				c, err := r.ReadByte()
				if err != nil {
					break out
				}

				switch c {
				case ' ', '\t', '\n', '\r', '\v', '\f':
					// nop
				default:
					r.UnreadByte()
					break next
				}
			}
		default:
			c2, err := r.ReadByte()
			if err != nil {
				break out
			}

			if c2 != c {
				r.UnreadByte()
				break out
			}

			format++
			ok = true
		}
	}
	if ok {
		return nvalues
	}

	return -1 // stdio.EOF but not defined for windows
}

func scanfConversion(r io.ByteScanner, format uintptr, args *uintptr) (_ uintptr, nvalues int, match bool) {
	format++ // '%'

	// Each conversion specification in format begins with either the character '%'
	// or the character sequence "%n$" (see below for the distinction) followed by:

	mod := 0
	width := -1
flags:
	for {
		switch c := *(*byte)(unsafe.Pointer(format)); c {
		case '*':
			// An  optional '*' assignment-suppression character: scanf() reads input as
			// directed by the conversion specification, but discards the input.  No
			// corresponding pointer argument is re‐ quired, and this specification is not
			// included in the count of successful assignments returned by scanf().
			format++
			panic(todo(""))
		case '\'':
			// For decimal conversions, an optional quote character (').  This specifies
			// that the input number may include thousands' separators as defined by the
			// LC_NUMERIC category of  the  current locale.  (See setlocale(3).)  The quote
			// character may precede or follow the '*' assignment-suppression character.
			format++
			panic(todo(""))
		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
			// An  optional  decimal  integer  which  specifies  the maximum field width.
			// Reading of characters stops either when this maximum is reached or when a
			// nonmatching character is found, whichever happens first.  Most conversions
			// discard initial white space characters (the exceptions are noted below), and
			// these discarded characters don't  count  toward  the  maximum field width.
			// String input conversions store a terminating null byte ('\0') to mark the
			// end of the input; the maximum field width does not include this terminator.
			width = 0
		num:
			for {
				var digit int
				switch c := *(*byte)(unsafe.Pointer(format)); {
				default:
					break num
				case c >= '0' && c <= '9':
					format++
					digit = int(c) - '0'
				}
				width0 := width
				width = 10*width + digit
				if width < width0 {
					panic(todo(""))
				}
			}
		case 'h', 'j', 'l', 'L', 'q', 't', 'z':
			format, mod = parseLengthModifier(format)
		default:
			break flags
		}
	}

	// A conversion specifier that specifies the type of input conversion to be
	// performed.
	switch c := *(*byte)(unsafe.Pointer(format)); c {
	case '%':
		// Matches a literal '%'.  That is, %% in the format string matches a single
		// input '%' character.  No conversion is done (but initial white space
		// characters are discarded), and assign‐ ment does not occur.
		format++
		panic(todo(""))
	case 'd':
		// Matches an optionally signed decimal integer; the next pointer must be a
		// pointer to int.
		format++
		skipReaderWhiteSpace(r)
		var digit, n uint64
		allowSign := true
		neg := false
	dec:
		for ; width != 0; width-- {
			c, err := r.ReadByte()
			if err != nil {
				if match {
					break dec
				}

				return 0, 0, false
			}

			if allowSign {
				switch c {
				case '-':
					allowSign = false
					neg = true
					continue
				case '+':
					allowSign = false
					continue
				}
			}

			switch {
			case c >= '0' && c <= '9':
				digit = uint64(c) - '0'
			default:
				r.UnreadByte()
				break dec
			}
			match = true
			n0 := n
			n = n*10 + digit
			if n < n0 {
				panic(todo(""))
			}
		}
		if !match {
			break
		}

		arg := VaUintptr(args)
		v := int64(n)
		if neg {
			v = -v
		}
		switch mod {
		case modNone:
			*(*int32)(unsafe.Pointer(arg)) = int32(v)
		case modH:
			*(*int16)(unsafe.Pointer(arg)) = int16(v)
		case modHH:
			*(*int8)(unsafe.Pointer(arg)) = int8(v)
		case modL:
			*(*long)(unsafe.Pointer(arg)) = long(n)
		default:
			panic(todo(""))
		}
		nvalues = 1
	case 'D':
		// Equivalent  to  ld;  this  exists  only for backward compatibility.  (Note:
		// thus only in libc4.  In libc5 and glibc the %D is silently ignored, causing
		// old programs to fail mysteriously.)
		format++
		panic(todo(""))
	case 'i':
		// Matches an optionally signed integer; the next pointer must be a pointer to
		// int.  The integer is read in base 16 if it begins with 0x or 0X, in base 8
		// if it begins with  0,  and  in base 10 otherwise.  Only characters that
		// correspond to the base are used.
		format++
		panic(todo(""))
	case 'o':
		// Matches an unsigned octal integer; the next pointer must be a pointer to
		// unsigned int.
		format++
		panic(todo(""))
	case 'u':
		// Matches an unsigned decimal integer; the next pointer must be a pointer to
		// unsigned int.
		format++
		panic(todo(""))
	case 'x', 'X':
		// Matches an unsigned hexadecimal integer; the next pointer must be a pointer
		// to unsigned int.
		format++
		skipReaderWhiteSpace(r)
		var digit, n uint64
		allowPrefix := true
		var b []byte
	hex:
		for ; width != 0; width-- {
			c, err := r.ReadByte()
			if err != nil {
				if match {
					break hex
				}

				panic(todo("", err))
			}

			if allowPrefix {
				if len(b) == 1 && b[0] == '0' && (c == 'x' || c == 'X') {
					allowPrefix = false
					match = false
					b = nil
					continue
				}

				b = append(b, c)
			}

			switch {
			case c >= '0' && c <= '9':
				digit = uint64(c) - '0'
			case c >= 'a' && c <= 'f':
				digit = uint64(c) - 'a' + 10
			case c >= 'A' && c <= 'F':
				digit = uint64(c) - 'A' + 10
			default:
				r.UnreadByte()
				break hex
			}
			match = true
			n0 := n
			n = n<<4 + digit
			if n < n0 {
				panic(todo(""))
			}
		}
		if !match {
			break
		}

		arg := VaUintptr(args)
		switch mod {
		case modNone:
			*(*uint32)(unsafe.Pointer(arg)) = uint32(n)
		case modH:
			*(*uint16)(unsafe.Pointer(arg)) = uint16(n)
		case modHH:
			*(*byte)(unsafe.Pointer(arg)) = byte(n)
		case modL:
			*(*ulong)(unsafe.Pointer(arg)) = ulong(n)
		default:
			panic(todo(""))
		}
		nvalues = 1
	case 'f', 'e', 'g', 'E', 'a':
		// Matches an optionally signed floating-point number; the next pointer must be
		// a pointer to float.
		format++
		skipReaderWhiteSpace(r)
		seq := fpLiteral(r)
		if len(seq) == 0 {
			return 0, 0, false
		}

		var neg bool
		switch seq[0] {
		case '+':
			seq = seq[1:]
		case '-':
			neg = true
			seq = seq[1:]
		}
		n, err := strconv.ParseFloat(string(seq), 64)
		if err != nil {
			panic(todo("", err))
		}

		arg := VaUintptr(args)
		if neg {
			n = -n
		}
		switch mod {
		case modNone:
			*(*float32)(unsafe.Pointer(arg)) = float32(n)
		case modL:
			*(*float64)(unsafe.Pointer(arg)) = n
		default:
			panic(todo("", mod, neg, n))
		}
		return format, 1, true
	case 's':
		// Matches  a  sequence of non-white-space characters; the next pointer must be
		// a pointer to the initial element of a character array that is long enough to
		// hold the input sequence and the terminating null byte ('\0'), which is added
		// automatically.  The input string stops at white space or at the maximum
		// field width, whichever occurs first.
		format++
		panic(todo(""))
	case 'c':
		// Matches a sequence of characters whose length is specified by the maximum
		// field width (default 1); the next pointer must be a pointer to char, and
		// there must be enough room for  all the characters (no terminating null byte
		// is added).  The usual skip of leading white space is suppressed.  To skip
		// white space first, use an explicit space in the format.
		format++
		panic(todo(""))
	case '[':
		// Matches  a nonempty sequence of characters from the specified set of
		// accepted characters; the next pointer must be a pointer to char, and there
		// must be enough room for all the char‐ acters in the string, plus a
		// terminating null byte.  The usual skip of leading white space is suppressed.
		// The string is to be made up of characters in (or not in) a particular set;
		// the  set  is defined by the characters between the open bracket [ character
		// and a close bracket ] character.  The set excludes those characters if the
		// first character after the open bracket is a circumflex (^).  To include a
		// close bracket in the set, make it the first character after the open bracket
		// or the circumflex; any other position will end the set.   The hyphen
		// character - is also special; when placed between two other characters, it
		// adds all intervening characters to the set.  To include a hyphen, make it
		// the last character before the final close bracket.  For instance, [^]0-9-]
		// means the set "everything except close bracket, zero through nine, and
		// hyphen".  The string ends with the appearance of a  character not in the
		// (or, with a circumflex, in) set or when the field width runs out.
		format++
		panic(todo(""))
	case 'p':
		// Matches a pointer value (as printed by %p in printf(3); the next pointer
		// must be a pointer to a pointer to void.
		format++
		skipReaderWhiteSpace(r)
		c, err := r.ReadByte()
		if err != nil {
			panic(todo(""))
		}

		if c != '0' {
			r.UnreadByte()
			panic(todo(""))
		}

		if c, err = r.ReadByte(); err != nil {
			panic(todo(""))
		}

		if c != 'x' && c != 'X' {
			r.UnreadByte()
			panic(todo(""))
		}

		var digit, n uint64
	ptr:
		for ; width != 0; width-- {
			c, err := r.ReadByte()
			if err != nil {
				if match {
					break ptr
				}

				panic(todo(""))
			}

			switch {
			case c >= '0' && c <= '9':
				digit = uint64(c) - '0'
			case c >= 'a' && c <= 'f':
				digit = uint64(c) - 'a' + 10
			case c >= 'A' && c <= 'F':
				digit = uint64(c) - 'A' + 10
			default:
				r.UnreadByte()
				break ptr
			}
			match = true
			n0 := n
			n = n<<4 + digit
			if n < n0 {
				panic(todo(""))
			}
		}
		if !match {
			break
		}

		arg := VaUintptr(args)
		*(*uintptr)(unsafe.Pointer(arg)) = uintptr(n)
		nvalues = 1
	case 'n':
		// Nothing is expected; instead, the number of characters consumed thus far
		// from the input is stored through the next pointer, which must be a pointer
		// to int.  This is not a conversion and does not increase the count returned
		// by the function.  The assignment can be suppressed with the *
		// assignment-suppression character, but the effect on the return value is
		// undefined.  Therefore %*n conversions should not be used.
		format++
		panic(todo(""))
	default:
		panic(todo("%#U", c))
	}

	return format, nvalues, match
}

func skipReaderWhiteSpace(r io.ByteScanner) error {
	for {
		c, err := r.ReadByte()
		if err != nil {
			return err
		}

		switch c {
		case ' ', '\t', '\n', '\r', '\v', '\f':
			// ok
		default:
			r.UnreadByte()
			return nil
		}
	}
}

func skipWhiteSpace(s uintptr) uintptr {
	for {
		switch c := *(*byte)(unsafe.Pointer(s)); c {
		case ' ', '\t', '\n', '\r', '\v', '\f':
			s++
		default:
			return s
		}
	}
}

// [-+]?([0-9]*[.])?[0-9]+([eE][-+]?\d+)?
func fpLiteral(rd io.ByteScanner) (seq []byte) {
	const endOfText = 0x110000
	var pos, width, length int

	defer func() {
		if len(seq) > length {
			rd.UnreadByte()
			seq = seq[:len(seq)-1]
		}
	}()

	var r rune
	step := func(pos int) (rune, int) {
		b, err := rd.ReadByte()
		if err != nil {
			return endOfText, 0
		}

		seq = append(seq, b)
		return rune(b), 1
	}
	move := func() {
		pos += width
		if r != endOfText {
			r, width = step(pos + width)
		}
	}
	accept := func(x rune) bool {
		if r == x {
			move()
			return true
		}
		return false
	}
	accept2 := func(x rune) bool {
		if r <= x {
			move()
			return true
		}
		return false
	}
	r = endOfText
	width = 0
	r, width = step(pos)
	if accept('.') {
		goto l7
	}
	if accept('+') {
		goto l30
	}
	if accept('-') {
		goto l30
	}
	if r < '0' {
		goto l4out
	}
	if accept2('9') {
		goto l35
	}
l4out:
	return seq
l7:
	if r < '0' {
		goto l7out
	}
	if accept2('9') {
		goto l10
	}
l7out:
	return seq
l10:
	length = pos
	if accept('E') {
		goto l18
	}
	if accept('e') {
		goto l18
	}
	if r < '0' {
		goto l15out
	}
	if accept2('9') {
		goto l10
	}
l15out:
	return seq
l18:
	if accept('+') {
		goto l23
	}
	if accept('-') {
		goto l23
	}
	if r < '0' {
		goto l20out
	}
	if accept2('9') {
		goto l26
	}
l20out:
	return seq
l23:
	if r < '0' {
		goto l23out
	}
	if accept2('9') {
		goto l26
	}
l23out:
	return seq
l26:
	length = pos
	if r < '0' {
		goto l27out
	}
	if accept2('9') {
		goto l26
	}
l27out:
	return seq
l30:
	if accept('.') {
		goto l7
	}
	if r < '0' {
		goto l32out
	}
	if accept2('9') {
		goto l35
	}
l32out:
	return seq
l35:
	length = pos
	if accept('.') {
		goto l7
	}
	if accept('E') {
		goto l18
	}
	if accept('e') {
		goto l18
	}
	if r < '0' {
		goto l42out
	}
	if accept2('9') {
		goto l35
	}
l42out:
	return seq
}
