package httpsfv

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
	"time"
)

// ErrInvalidBareItem is returned when a bare item is invalid.
var ErrInvalidBareItem = errors.New(
	"invalid bare item type (allowed types are bool, string, int64, float64, []byte, time.Time and Token)",
)

// assertBareItem asserts that v is a valid bare item
// according to https://httpwg.org/specs/rfc9651.html#item.
//
// v can be either:
//
// * an integer (Section 3.3.1.)
// * a decimal (Section 3.3.2.)
// * a string (Section 3.3.3.)
// * a token (Section 3.3.4.)
// * a byte sequence (Section 3.3.5.)
// * a boolean (Section 3.3.6.)
// * a date (Section 3.3.7.)
// * a display string (Section 3.3.8.)
func assertBareItem(v interface{}) {
	switch v.(type) {
	case bool,
		string,
		int,
		int8,
		int16,
		int32,
		int64,
		uint,
		uint8,
		uint16,
		uint32,
		uint64,
		float32,
		float64,
		[]byte,
		time.Time,
		Token,
		DisplayString:
		return
	default:
		panic(fmt.Errorf("%w: got %s", ErrInvalidBareItem, reflect.TypeOf(v)))
	}
}

// marshalBareItem serializes as defined in
// https://httpwg.org/specs/rfc9651.html#ser-bare-item.
func marshalBareItem(b *strings.Builder, v interface{}) error {
	switch v := v.(type) {
	case bool:
		return marshalBoolean(b, v)
	case string:
		return marshalString(b, v)
	case int64:
		return marshalInteger(b, v)
	case int, int8, int16, int32:
		return marshalInteger(b, reflect.ValueOf(v).Int())
	case uint, uint8, uint16, uint32, uint64:
		// Casting an uint64 to an int64 is possible because the maximum allowed value is 999,999,999,999,999
		return marshalInteger(b, int64(reflect.ValueOf(v).Uint()))
	case float32, float64:
		return marshalDecimal(b, v.(float64))
	case []byte:
		return marshalBinary(b, v)
	case time.Time:
		return marshalDate(b, v)
	case Token:
		return v.marshalSFV(b)
	case DisplayString:
		return v.marshalSFV(b)
	default:
		panic(ErrInvalidBareItem)
	}
}

// parseBareItem parses as defined in
// https://httpwg.org/specs/rfc9651.html#parse-bare-item.
func parseBareItem(s *scanner) (interface{}, error) {
	if s.eof() {
		return nil, &UnmarshalError{s.off, ErrUnexpectedEndOfString}
	}

	c := s.data[s.off]
	switch c {
	case '"':
		return parseString(s)
	case '?':
		return parseBoolean(s)
	case '*':
		return parseToken(s)
	case ':':
		return parseBinary(s)
	case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
		return parseNumber(s)
	case '@':
		return parseDate(s)
	case '%':
		return parseDisplayString(s)
	default:
		if isAlpha(c) {
			return parseToken(s)
		}

		return nil, &UnmarshalError{s.off, ErrUnrecognizedCharacter}
	}
}
