package attributevalue

import (
	"encoding"
	"fmt"
	"reflect"
	"strconv"
	"time"

	"github.com/aws/aws-sdk-go-v2/service/dynamodbstreams/types"
)

// An UnixTime provides aliasing of time.Time into a type that when marshaled
// and unmarshaled with AttributeValues it will be done so as number
// instead of string in seconds since January 1, 1970 UTC.
//
// This type is useful as an alternative to the struct tag `unixtime` when you
// want to have your time value marshaled as Unix time in seconds into a number
// attribute type instead of the default time.RFC3339Nano.
//
// Important to note that zero value time as unixtime is not 0 seconds
// from January 1, 1970 UTC, but -62135596800. Which is seconds between
// January 1, 0001 UTC, and January 1, 0001 UTC.
//
// Also, important to note: the default UnixTime implementation of the Marshaler
// interface will marshal into an attribute of type of number; therefore,
// it may not be used as a sort key if the attribute value is of type string. Further,
// the time.RFC3339Nano format removes trailing zeros from the seconds field
// and thus may not sort correctly once formatted.
type UnixTime time.Time

// MarshalDynamoDBStreamsAttributeValue implements the Marshaler interface so that
// the UnixTime can be marshaled from to a AttributeValue number
// value encoded in the number of seconds since January 1, 1970 UTC.
func (e UnixTime) MarshalDynamoDBStreamsAttributeValue() (types.AttributeValue, error) {
	return &types.AttributeValueMemberN{
		Value: strconv.FormatInt(time.Time(e).Unix(), 10),
	}, nil
}

// UnmarshalDynamoDBStreamsAttributeValue implements the Unmarshaler interface so that
// the UnixTime can be unmarshaled from a AttributeValue number representing
// the number of seconds since January 1, 1970 UTC.
//
// If an error parsing the AttributeValue number occurs UnmarshalError will be
// returned.
func (e *UnixTime) UnmarshalDynamoDBStreamsAttributeValue(av types.AttributeValue) error {
	tv, ok := av.(*types.AttributeValueMemberN)
	if !ok {
		return &UnmarshalTypeError{
			Value: fmt.Sprintf("%T", av),
			Type:  reflect.TypeOf((*UnixTime)(nil)),
		}
	}

	t, err := decodeUnixTime(tv.Value)
	if err != nil {
		return err
	}

	*e = UnixTime(t)
	return nil
}

// A Marshaler is an interface to provide custom marshaling of Go value types
// to AttributeValues. Use this to provide custom logic determining how a
// Go Value type should be marshaled.
//
//		type CustomIntType struct {
//			Value Int
//		}
//		func (m *CustomIntType) MarshalDynamoDBStreamsAttributeValue() (types.AttributeValue, error) {
//			return &types.AttributeValueMemberN{
//				Value: strconv.Itoa(m.Value),
//			}, nil
//		}
//
type Marshaler interface {
	MarshalDynamoDBStreamsAttributeValue() (types.AttributeValue, error)
}

// Marshal will serialize the passed in Go value type into a AttributeValue
// type. This value can be used in API operations to simplify marshaling
// your Go value types into AttributeValues.
//
// Marshal will recursively transverse the passed in value marshaling its
// contents into a AttributeValue. Marshal supports basic scalars
// (int,uint,float,bool,string), maps, slices, and structs. Anonymous
// nested types are flattened based on Go anonymous type visibility.
//
// Marshaling slices to AttributeValue will default to a List for all
// types except for []byte and [][]byte. []byte will be marshaled as
// Binary data (B), and [][]byte will be marshaled as binary data set
// (BS).
//
// The `time.Time` type is marshaled as `time.RFC3339Nano` format.
//
// `dynamodbav` struct tag can be used to control how the value will be
// marshaled into a AttributeValue.
//
//		// Field is ignored
//		Field int `dynamodbav:"-"`
//
//		// Field AttributeValue map key "myName"
//		Field int `dynamodbav:"myName"`
//
//		// Field AttributeValue map key "myName", and
//		// Field is omitted if the field is a zero value for the type.
//		Field int `dynamodbav:"myName,omitempty"`
//
//		// Field AttributeValue map key "Field", and
//		// Field is omitted if the field is a zero value for the type.
//		Field int `dynamodbav:",omitempty"`
//
//		// Field's elems will be omitted if the elem's value is empty.
//		// only valid for slices, and maps.
//		Field []string `dynamodbav:",omitemptyelem"`
//
//		// Field AttributeValue map key "Field", and
//		// Field is sent as NULL if the field is a zero value for the type.
//		Field int `dynamodbav:",nullempty"`
//
//		// Field's elems will be sent as NULL if the elem's value a zero value
//		// for the type. Only valid for slices, and maps.
//		Field []string `dynamodbav:",nullemptyelem"`
//
//		// Field will be marshaled as a AttributeValue string
//		// only value for number types, (int,uint,float)
//		Field int `dynamodbav:",string"`
//
//		// Field will be marshaled as a binary set
//		Field [][]byte `dynamodbav:",binaryset"`
//
//		// Field will be marshaled as a number set
//		Field []int `dynamodbav:",numberset"`
//
//		// Field will be marshaled as a string set
//		Field []string `dynamodbav:",stringset"`
//
//		// Field will be marshaled as Unix time number in seconds.
//		// This tag is only valid with time.Time typed struct fields.
//		// Important to note that zero value time as unixtime is not 0 seconds
//		// from January 1, 1970 UTC, but -62135596800. Which is seconds between
//		// January 1, 0001 UTC, and January 1, 0001 UTC.
//		Field time.Time `dynamodbav:",unixtime"`
//
// The omitempty tag is only used during Marshaling and is ignored for
// Unmarshal. omitempty will skip any member if the Go value of the member is
// zero. The omitemptyelem tag works the same as omitempty except it applies to
// the elements of maps and slices instead of struct fields, and will not be
// included in the marshaled AttributeValue Map, List, or Set.
//
// The nullempty tag is only used during Marshaling and is ignored for
// Unmarshal. nullempty will serialize a AttributeValueMemberNULL for the
// member if the Go value of the member is zero. nullemptyelem tag works the
// same as nullempty except it applies to the elements of maps and slices
// instead of struct fields, and will not be included in the marshaled
// AttributeValue Map, List, or Set.
//
// All struct fields and with anonymous fields, are marshaled unless the
// any of the following conditions are meet.
//
//		- the field is not exported
//		- json or dynamodbav field tag is "-"
//		- json or dynamodbav field tag specifies "omitempty", and is a zero value.
//
// Pointer and interfaces values are encoded as the value pointed to or
// contained in the interface. A nil value encodes as the AttributeValue NULL
// value unless `omitempty` struct tag is provided.
//
// Channel, complex, and function values are not encoded and will be skipped
// when walking the value to be marshaled.
//
// Error that occurs when marshaling will stop the marshal, and return
// the error.
//
// Marshal cannot represent cyclic data structures and will not handle them.
// Passing cyclic structures to Marshal will result in an infinite recursion.
func Marshal(in interface{}) (types.AttributeValue, error) {
	return NewEncoder().Encode(in)
}

// MarshalWithOptions will serialize the passed in Go value type into a AttributeValue
// type, by using . This value can be used in API operations to simplify marshaling
// your Go value types into AttributeValues.
//
// Use the `optsFns` functional options to override the default configuration.
//
// MarshalWithOptions will recursively transverse the passed in value marshaling its
// contents into a AttributeValue. Marshal supports basic scalars
// (int,uint,float,bool,string), maps, slices, and structs. Anonymous
// nested types are flattened based on Go anonymous type visibility.
//
// Marshaling slices to AttributeValue will default to a List for all
// types except for []byte and [][]byte. []byte will be marshaled as
// Binary data (B), and [][]byte will be marshaled as binary data set
// (BS).
//
// The `time.Time` type is marshaled as `time.RFC3339Nano` format.
//
// `dynamodbav` struct tag can be used to control how the value will be
// marshaled into a AttributeValue.
//
//		// Field is ignored
//		Field int `dynamodbav:"-"`
//
//		// Field AttributeValue map key "myName"
//		Field int `dynamodbav:"myName"`
//
//		// Field AttributeValue map key "myName", and
//		// Field is omitted if the field is a zero value for the type.
//		Field int `dynamodbav:"myName,omitempty"`
//
//		// Field AttributeValue map key "Field", and
//		// Field is omitted if the field is a zero value for the type.
//		Field int `dynamodbav:",omitempty"`
//
//		// Field's elems will be omitted if the elem's value is empty.
//		// only valid for slices, and maps.
//		Field []string `dynamodbav:",omitemptyelem"`
//
//		// Field AttributeValue map key "Field", and
//		// Field is sent as NULL if the field is a zero value for the type.
//		Field int `dynamodbav:",nullempty"`
//
//		// Field's elems will be sent as NULL if the elem's value a zero value
//		// for the type. Only valid for slices, and maps.
//		Field []string `dynamodbav:",nullemptyelem"`
//
//		// Field will be marshaled as a AttributeValue string
//		// only value for number types, (int,uint,float)
//		Field int `dynamodbav:",string"`
//
//		// Field will be marshaled as a binary set
//		Field [][]byte `dynamodbav:",binaryset"`
//
//		// Field will be marshaled as a number set
//		Field []int `dynamodbav:",numberset"`
//
//		// Field will be marshaled as a string set
//		Field []string `dynamodbav:",stringset"`
//
//		// Field will be marshaled as Unix time number in seconds.
//		// This tag is only valid with time.Time typed struct fields.
//		// Important to note that zero value time as unixtime is not 0 seconds
//		// from January 1, 1970 UTC, but -62135596800. Which is seconds between
//		// January 1, 0001 UTC, and January 1, 0001 UTC.
//		Field time.Time `dynamodbav:",unixtime"`
//
// The omitempty tag is only used during Marshaling and is ignored for
// Unmarshal. omitempty will skip any member if the Go value of the member is
// zero. The omitemptyelem tag works the same as omitempty except it applies to
// the elements of maps and slices instead of struct fields, and will not be
// included in the marshaled AttributeValue Map, List, or Set.
//
// The nullempty tag is only used during Marshaling and is ignored for
// Unmarshal. nullempty will serialize a AttributeValueMemberNULL for the
// member if the Go value of the member is zero. nullemptyelem tag works the
// same as nullempty except it applies to the elements of maps and slices
// instead of struct fields, and will not be included in the marshaled
// AttributeValue Map, List, or Set.
//
// All struct fields and with anonymous fields, are marshaled unless the
// any of the following conditions are meet.
//
//		- the field is not exported
//		- json or dynamodbav field tag is "-"
//		- json or dynamodbav field tag specifies "omitempty", and is a zero value.
//
// Pointer and interfaces values are encoded as the value pointed to or
// contained in the interface. A nil value encodes as the AttributeValue NULL
// value unless `omitempty` struct tag is provided.
//
// Channel, complex, and function values are not encoded and will be skipped
// when walking the value to be marshaled.
//
// Error that occurs when marshaling will stop the marshal, and return
// the error.
//
// MarshalWithOptions cannot represent cyclic data structures and will not handle them.
// Passing cyclic structures to Marshal will result in an infinite recursion.
func MarshalWithOptions(in interface{}, optFns ...func(*EncoderOptions)) (types.AttributeValue, error) {
	return NewEncoder(optFns...).Encode(in)
}

// MarshalMap is an alias for Marshal func which marshals Go value type to a
// map of AttributeValues. If the in parameter does not serialize to a map, an
// empty AttributeValue map will be returned.
//
// Use the `optsFns` functional options to override the default configuration.
//
// This is useful for APIs such as PutItem.
func MarshalMap(in interface{}) (map[string]types.AttributeValue, error) {
	av, err := NewEncoder().Encode(in)

	asMap, ok := av.(*types.AttributeValueMemberM)
	if err != nil || av == nil || !ok {
		return map[string]types.AttributeValue{}, err
	}

	return asMap.Value, nil
}

// MarshalMapWithOptions is an alias for MarshalWithOptions func which marshals Go value type to a
// map of AttributeValues. If the in parameter does not serialize to a map, an
// empty AttributeValue map will be returned.
//
// Use the `optsFns` functional options to override the default configuration.
//
// This is useful for APIs such as PutItem.
func MarshalMapWithOptions(in interface{}, optFns ...func(*EncoderOptions)) (map[string]types.AttributeValue, error) {
	av, err := NewEncoder(optFns...).Encode(in)

	asMap, ok := av.(*types.AttributeValueMemberM)
	if err != nil || av == nil || !ok {
		return map[string]types.AttributeValue{}, err
	}

	return asMap.Value, nil
}

// MarshalList is an alias for Marshal func which marshals Go value
// type to a slice of AttributeValues. If the in parameter does not serialize
// to a slice, an empty AttributeValue slice will be returned.
func MarshalList(in interface{}) ([]types.AttributeValue, error) {
	av, err := NewEncoder().Encode(in)

	asList, ok := av.(*types.AttributeValueMemberL)
	if err != nil || av == nil || !ok {
		return []types.AttributeValue{}, err
	}

	return asList.Value, nil
}

// MarshalListWithOptions is an alias for MarshalWithOptions func which marshals Go value
// type to a slice of AttributeValues. If the in parameter does not serialize
// to a slice, an empty AttributeValue slice will be returned.
//
// Use the `optsFns` functional options to override the default configuration.
func MarshalListWithOptions(in interface{}, optFns ...func(*EncoderOptions)) ([]types.AttributeValue, error) {
	av, err := NewEncoder(optFns...).Encode(in)

	asList, ok := av.(*types.AttributeValueMemberL)
	if err != nil || av == nil || !ok {
		return []types.AttributeValue{}, err
	}

	return asList.Value, nil
}

// EncoderOptions is a collection of options shared between marshaling
// and unmarshaling
type EncoderOptions struct {
	// Support other custom struct tag keys, such as `yaml`, `json`, or `toml`.
	// Note that values provided with a custom TagKey must also be supported
	// by the (un)marshalers in this package.
	//
	// Tag key `dynamodbav` will always be read, but if custom tag key
	// conflicts with `dynamodbav` the custom tag key value will be used.
	TagKey string

	// Will encode any slice being encoded as a set (SS, NS, and BS) as a NULL
	// AttributeValue if the slice is not nil, but is empty but contains no
	// elements.
	//
	// If a type implements the Marshal interface, and returns empty set
	// slices, this option will not modify the returned value.
	//
	// Defaults to enabled, because AttributeValue sets cannot currently be
	// empty lists.
	NullEmptySets bool

	// Will encode time.Time fields
	//
	// Default encoding is time.RFC3339Nano in a DynamoDBStreams String (S) data type.
	EncodeTime func(time.Time) (types.AttributeValue, error)
}

// An Encoder provides marshaling Go value types to AttributeValues.
type Encoder struct {
	options EncoderOptions
}

// NewEncoder creates a new Encoder with default configuration. Use
// the `opts` functional options to override the default configuration.
func NewEncoder(optFns ...func(*EncoderOptions)) *Encoder {
	options := EncoderOptions{
		TagKey:        defaultTagKey,
		NullEmptySets: true,
		EncodeTime:    defaultEncodeTime,
	}
	for _, fn := range optFns {
		fn(&options)
	}

	if options.EncodeTime == nil {
		options.EncodeTime = defaultEncodeTime
	}

	return &Encoder{
		options: options,
	}
}

// Encode will marshal a Go value type to an AttributeValue. Returning
// the AttributeValue constructed or error.
func (e *Encoder) Encode(in interface{}) (types.AttributeValue, error) {
	return e.encode(reflect.ValueOf(in), tag{})
}

func (e *Encoder) encode(v reflect.Value, fieldTag tag) (types.AttributeValue, error) {
	// Ignore fields explicitly marked to be skipped.
	if fieldTag.Ignore {
		return nil, nil
	}

	// Zero values are serialized as null, or skipped if omitEmpty.
	if isZeroValue(v) {
		if fieldTag.OmitEmpty && fieldTag.NullEmpty {
			return nil, &InvalidMarshalError{
				msg: "unable to encode AttributeValue for zero value field with incompatible struct tags, omitempty and nullempty"}
		}

		if fieldTag.OmitEmpty {
			return nil, nil
		} else if isNullableZeroValue(v) || fieldTag.NullEmpty {
			return encodeNull(), nil
		}
	}

	// Handle both pointers and interface conversion into types
	v = valueElem(v)

	if v.Kind() != reflect.Invalid {
		if av, err := tryMarshaler(v); err != nil {
			return nil, err
		} else if av != nil {
			return av, nil
		}
	}

	switch v.Kind() {
	case reflect.Invalid:
		if fieldTag.OmitEmpty {
			return nil, nil
		}
		// Handle case where member type needed to be dereferenced and resulted
		// in a kind that is invalid.
		return encodeNull(), nil

	case reflect.Struct:
		return e.encodeStruct(v, fieldTag)

	case reflect.Map:
		return e.encodeMap(v, fieldTag)

	case reflect.Slice, reflect.Array:
		return e.encodeSlice(v, fieldTag)

	case reflect.Chan, reflect.Func, reflect.UnsafePointer:
		// skip unsupported types
		return nil, nil

	default:
		return e.encodeScalar(v, fieldTag)
	}
}

func (e *Encoder) encodeStruct(v reflect.Value, fieldTag tag) (types.AttributeValue, error) {
	// Time structs have no public members, and instead are converted to
	// RFC3339Nano formatted string, unix time seconds number if struct tag is set.
	if v.Type().ConvertibleTo(timeType) {
		var t time.Time
		t = v.Convert(timeType).Interface().(time.Time)
		if fieldTag.AsUnixTime {
			return UnixTime(t).MarshalDynamoDBStreamsAttributeValue()
		}
		return e.options.EncodeTime(t)
	}

	m := &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{}}
	fields := unionStructFields(v.Type(), structFieldOptions{
		TagKey: e.options.TagKey,
	})
	for _, f := range fields.All() {
		if f.Name == "" {
			return nil, &InvalidMarshalError{msg: "map key cannot be empty"}
		}

		fv, found := encoderFieldByIndex(v, f.Index)
		if !found {
			continue
		}

		elem, err := e.encode(fv, f.tag)
		if err != nil {
			return nil, err
		} else if elem == nil {
			continue
		}

		m.Value[f.Name] = elem
	}

	return m, nil
}

func (e *Encoder) encodeMap(v reflect.Value, fieldTag tag) (types.AttributeValue, error) {
	m := &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{}}
	for _, key := range v.MapKeys() {
		keyName, err := mapKeyAsString(key, fieldTag)
		if err != nil {
			return nil, err
		}

		elemVal := v.MapIndex(key)
		elem, err := e.encode(elemVal, tag{
			OmitEmpty: fieldTag.OmitEmptyElem,
			NullEmpty: fieldTag.NullEmptyElem,
		})
		if err != nil {
			return nil, err
		} else if elem == nil {
			continue
		}

		m.Value[keyName] = elem
	}

	return m, nil
}

func mapKeyAsString(keyVal reflect.Value, fieldTag tag) (keyStr string, err error) {
	defer func() {
		if err != nil {
			return
		}
		if keyStr == "" {
			err = &InvalidMarshalError{msg: "map key cannot be empty"}
		}
	}()

	if k, ok := keyVal.Interface().(encoding.TextMarshaler); ok {
		b, err := k.MarshalText()
		if err != nil {
			return "", fmt.Errorf("failed to marshal text, %w", err)
		}
		return string(b), err
	}

	switch keyVal.Kind() {
	case reflect.Bool,
		reflect.String,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64:

		return fmt.Sprint(keyVal.Interface()), nil

	default:
		return "", &InvalidMarshalError{
			msg: "map key type not supported, must be string, number, bool, or TextMarshaler",
		}
	}
}

func (e *Encoder) encodeSlice(v reflect.Value, fieldTag tag) (types.AttributeValue, error) {
	if v.Type().Elem().Kind() == reflect.Uint8 {
		slice := reflect.MakeSlice(byteSliceType, v.Len(), v.Len())
		reflect.Copy(slice, v)

		return &types.AttributeValueMemberB{
			Value: append([]byte{}, slice.Bytes()...),
		}, nil
	}

	var setElemFn func(types.AttributeValue) error
	var av types.AttributeValue

	if fieldTag.AsBinSet || v.Type() == byteSliceSliceType { // Binary Set
		if v.Len() == 0 && e.options.NullEmptySets {
			return encodeNull(), nil
		}

		bs := &types.AttributeValueMemberBS{Value: make([][]byte, 0, v.Len())}
		av = bs
		setElemFn = func(elem types.AttributeValue) error {
			b, ok := elem.(*types.AttributeValueMemberB)
			if !ok || b == nil || b.Value == nil {
				return &InvalidMarshalError{
					msg: "binary set must only contain non-nil byte slices"}
			}
			bs.Value = append(bs.Value, b.Value)
			return nil
		}

	} else if fieldTag.AsNumSet { // Number Set
		if v.Len() == 0 && e.options.NullEmptySets {
			return encodeNull(), nil
		}

		ns := &types.AttributeValueMemberNS{Value: make([]string, 0, v.Len())}
		av = ns
		setElemFn = func(elem types.AttributeValue) error {
			n, ok := elem.(*types.AttributeValueMemberN)
			if !ok || n == nil {
				return &InvalidMarshalError{
					msg: "number set must only contain non-nil string numbers"}
			}
			ns.Value = append(ns.Value, n.Value)
			return nil
		}

	} else if fieldTag.AsStrSet { // String Set
		if v.Len() == 0 && e.options.NullEmptySets {
			return encodeNull(), nil
		}

		ss := &types.AttributeValueMemberSS{Value: make([]string, 0, v.Len())}
		av = ss
		setElemFn = func(elem types.AttributeValue) error {
			s, ok := elem.(*types.AttributeValueMemberS)
			if !ok || s == nil {
				return &InvalidMarshalError{
					msg: "string set must only contain non-nil strings"}
			}
			ss.Value = append(ss.Value, s.Value)
			return nil
		}

	} else { // List
		l := &types.AttributeValueMemberL{Value: make([]types.AttributeValue, 0, v.Len())}
		av = l
		setElemFn = func(elem types.AttributeValue) error {
			l.Value = append(l.Value, elem)
			return nil
		}
	}

	if err := e.encodeListElems(v, fieldTag, setElemFn); err != nil {
		return nil, err
	}

	return av, nil
}

func (e *Encoder) encodeListElems(v reflect.Value, fieldTag tag, setElem func(types.AttributeValue) error) error {
	for i := 0; i < v.Len(); i++ {
		elem, err := e.encode(v.Index(i), tag{
			OmitEmpty: fieldTag.OmitEmptyElem,
			NullEmpty: fieldTag.NullEmptyElem,
		})
		if err != nil {
			return err
		} else if elem == nil {
			continue
		}

		if err := setElem(elem); err != nil {
			return err
		}
	}

	return nil
}

// Returns if the type of the value satisfies an interface for number like the
// encoding/json#Number and feature/dynamodb/attributevalue#Number
func isNumberValueType(v reflect.Value) bool {
	type numberer interface {
		Float64() (float64, error)
		Int64() (int64, error)
		String() string
	}

	_, ok := v.Interface().(numberer)
	return ok && v.Kind() == reflect.String
}

func (e *Encoder) encodeScalar(v reflect.Value, fieldTag tag) (types.AttributeValue, error) {
	if isNumberValueType(v) {
		if fieldTag.AsString {
			return &types.AttributeValueMemberS{Value: v.String()}, nil
		}
		return &types.AttributeValueMemberN{Value: v.String()}, nil
	}

	switch v.Kind() {
	case reflect.Bool:
		return &types.AttributeValueMemberBOOL{Value: v.Bool()}, nil

	case reflect.String:
		return e.encodeString(v)

	default:
		// Fallback to encoding numbers, will return invalid type if not supported
		av, err := e.encodeNumber(v)
		if err != nil {
			return nil, err
		}

		n, isNumber := av.(*types.AttributeValueMemberN)
		if fieldTag.AsString && isNumber {
			return &types.AttributeValueMemberS{Value: n.Value}, nil
		}
		return av, nil
	}
}

func (e *Encoder) encodeNumber(v reflect.Value) (types.AttributeValue, error) {
	if av, err := tryMarshaler(v); err != nil {
		return nil, err
	} else if av != nil {
		return av, nil
	}

	var out string
	switch v.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		out = encodeInt(v.Int())

	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		out = encodeUint(v.Uint())

	case reflect.Float32:
		out = encodeFloat(v.Float(), 32)

	case reflect.Float64:
		out = encodeFloat(v.Float(), 64)

	default:
		return nil, nil
	}

	return &types.AttributeValueMemberN{Value: out}, nil
}

func (e *Encoder) encodeString(v reflect.Value) (types.AttributeValue, error) {
	if av, err := tryMarshaler(v); err != nil {
		return nil, err
	} else if av != nil {
		return av, nil
	}

	switch v.Kind() {
	case reflect.String:
		s := v.String()
		return &types.AttributeValueMemberS{Value: s}, nil

	default:
		return nil, nil
	}
}

func encodeInt(i int64) string {
	return strconv.FormatInt(i, 10)
}
func encodeUint(u uint64) string {
	return strconv.FormatUint(u, 10)
}
func encodeFloat(f float64, bitSize int) string {
	return strconv.FormatFloat(f, 'f', -1, bitSize)
}
func encodeNull() types.AttributeValue {
	return &types.AttributeValueMemberNULL{Value: true}
}

// encoderFieldByIndex finds the field with the provided nested index
func encoderFieldByIndex(v reflect.Value, index []int) (reflect.Value, bool) {
	for i, x := range index {
		if i > 0 && v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct {
			if v.IsNil() {
				return reflect.Value{}, false
			}
			v = v.Elem()
		}
		v = v.Field(x)
	}
	return v, true
}

func valueElem(v reflect.Value) reflect.Value {
	switch v.Kind() {
	case reflect.Interface, reflect.Ptr:
		for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
			v = v.Elem()
		}
	}

	return v
}

func isZeroValue(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Invalid:
		return true
	case reflect.Array:
		return v.Len() == 0
	case reflect.Map, reflect.Slice:
		return v.IsNil()
	case reflect.String:
		return v.Len() == 0
	case reflect.Bool:
		return !v.Bool()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return v.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return v.Float() == 0
	case reflect.Interface, reflect.Ptr:
		return v.IsNil()
	}
	return false
}

func isNullableZeroValue(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Invalid:
		return true
	case reflect.Map, reflect.Slice:
		return v.IsNil()
	case reflect.Interface, reflect.Ptr:
		return v.IsNil()
	}
	return false
}

func tryMarshaler(v reflect.Value) (types.AttributeValue, error) {
	if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
		v = v.Addr()
	}

	if v.Type().NumMethod() == 0 {
		return nil, nil
	}

	if m, ok := v.Interface().(Marshaler); ok {
		return m.MarshalDynamoDBStreamsAttributeValue()
	}

	return nil, nil
}

// An InvalidMarshalError is an error type representing an error
// occurring when marshaling a Go value type to an AttributeValue.
type InvalidMarshalError struct {
	msg string
}

// Error returns the string representation of the error.
// satisfying the error interface
func (e *InvalidMarshalError) Error() string {
	return fmt.Sprintf("marshal failed, %s", e.msg)
}

func defaultEncodeTime(t time.Time) (types.AttributeValue, error) {
	return &types.AttributeValueMemberS{
		Value: t.Format(time.RFC3339Nano),
	}, nil
}
