package netlink

import (
	"encoding/binary"
	"errors"
	"fmt"

	"github.com/mdlayher/netlink/nlenc"
)

var (
	// errInvalidAttribute specifies if an Attribute's length is incorrect.
	errInvalidAttribute = errors.New("invalid attribute; length too short or too large")
)

// An Attribute is a netlink attribute.  Attributes are packed and unpacked
// to and from the Data field of Message for some netlink families.
type Attribute struct {
	// Length of an Attribute, including this field and Type.
	Length uint16

	// The type of this Attribute, typically matched to a constant.
	Type uint16

	// An arbitrary payload which is specified by Type.
	Data []byte
}

// marshal marshals the contents of a into b and returns the number of bytes
// written to b, including attribute alignment padding.
func (a *Attribute) marshal(b []byte) (int, error) {
	if int(a.Length) < nlaHeaderLen {
		return 0, errInvalidAttribute
	}

	nlenc.PutUint16(b[0:2], a.Length)
	nlenc.PutUint16(b[2:4], a.Type)
	n := copy(b[nlaHeaderLen:], a.Data)

	return nlaHeaderLen + nlaAlign(n), nil
}

// unmarshal unmarshals the contents of a byte slice into an Attribute.
func (a *Attribute) unmarshal(b []byte) error {
	if len(b) < nlaHeaderLen {
		return errInvalidAttribute
	}

	a.Length = nlenc.Uint16(b[0:2])
	a.Type = nlenc.Uint16(b[2:4])

	if int(a.Length) > len(b) {
		return errInvalidAttribute
	}

	switch {
	// No length, no data
	case a.Length == 0:
		a.Data = make([]byte, 0)
	// Not enough length for any data
	case int(a.Length) < nlaHeaderLen:
		return errInvalidAttribute
	// Data present
	case int(a.Length) >= nlaHeaderLen:
		a.Data = make([]byte, len(b[nlaHeaderLen:a.Length]))
		copy(a.Data, b[nlaHeaderLen:a.Length])
	}

	return nil
}

// MarshalAttributes packs a slice of Attributes into a single byte slice.
// In most cases, the Length field of each Attribute should be set to 0, so it
// can be calculated and populated automatically for each Attribute.
func MarshalAttributes(attrs []Attribute) ([]byte, error) {
	// Count how many bytes we should allocate to store each attribute's contents.
	var c int
	for _, a := range attrs {
		c += nlaHeaderLen + nlaAlign(len(a.Data))
	}

	// Advance through b with idx to place attribute data at the correct offset.
	var idx int
	b := make([]byte, c)
	for _, a := range attrs {
		// Infer the length of attribute if zero.
		if a.Length == 0 {
			a.Length = uint16(nlaHeaderLen + len(a.Data))
		}

		// Marshal a into b and advance idx to show many bytes are occupied.
		n, err := a.marshal(b[idx:])
		if err != nil {
			return nil, err
		}
		idx += n
	}

	return b, nil
}

// UnmarshalAttributes unpacks a slice of Attributes from a single byte slice.
//
// It is recommend to use the AttributeDecoder type where possible instead of calling
// UnmarshalAttributes and using package nlenc functions directly.
func UnmarshalAttributes(b []byte) ([]Attribute, error) {
	var attrs []Attribute
	var i int
	for {
		if i > len(b) || len(b[i:]) == 0 {
			break
		}

		var a Attribute
		if err := (&a).unmarshal(b[i:]); err != nil {
			return nil, err
		}

		if a.Length == 0 {
			i += nlaHeaderLen
			continue
		}

		i += nlaAlign(int(a.Length))

		attrs = append(attrs, a)
	}

	return attrs, nil
}

// An AttributeDecoder provides a safe, iterator-like, API around attribute
// decoding.
//
// It is recommend to use an AttributeDecoder where possible instead of calling
// UnmarshalAttributes and using package nlenc functions directly.
//
// The Err method must be called after the Next method returns false to determine
// if any errors occurred during iteration.
type AttributeDecoder struct {
	// ByteOrder defines a specific byte order to use when processing integer
	// attributes.  ByteOrder should be set immediately after creating the
	// AttributeDecoder: before any attributes are parsed.
	//
	// If not set, the native byte order will be used.
	ByteOrder binary.ByteOrder

	// The attributes being worked on, and the iterator index into the slice of
	// attributes.
	attrs []Attribute
	i     int

	// Any error encountered while decoding attributes.
	err error
}

// NewAttributeDecoder creates an AttributeDecoder that unpacks Attributes
// from b and prepares the decoder for iteration.
func NewAttributeDecoder(b []byte) (*AttributeDecoder, error) {
	attrs, err := UnmarshalAttributes(b)
	if err != nil {
		return nil, err
	}

	return &AttributeDecoder{
		// By default, use native byte order.
		ByteOrder: nlenc.NativeEndian(),

		attrs: attrs,
	}, nil
}

// Next advances the decoder to the next netlink attribute.  It returns false
// when no more attributes are present, or an error was encountered.
func (ad *AttributeDecoder) Next() bool {
	if ad.err != nil {
		// Hit an error, stop iteration.
		return false
	}

	ad.i++

	if len(ad.attrs) < ad.i {
		// No more attributes, stop iteration.
		return false
	}

	return true
}

// Type returns the Attribute.Type field of the current netlink attribute
// pointed to by the decoder.
func (ad *AttributeDecoder) Type() uint16 {
	return ad.attr().Type
}

// attr returns the current Attribute pointed to by the decoder.
func (ad *AttributeDecoder) attr() Attribute {
	return ad.attrs[ad.i-1]
}

// data returns the Data field of the current Attribute pointed to by the decoder.
func (ad *AttributeDecoder) data() []byte {
	return ad.attr().Data
}

// Err returns the first error encountered by the decoder.
func (ad *AttributeDecoder) Err() error {
	return ad.err
}

// Bytes returns the raw bytes of the current Attribute's data.
func (ad *AttributeDecoder) Bytes() []byte {
	src := ad.data()
	dest := make([]byte, len(src))
	copy(dest, src)
	return dest
}

// String returns the string representation of the current Attribute's data.
func (ad *AttributeDecoder) String() string {
	if ad.err != nil {
		return ""
	}

	return nlenc.String(ad.data())
}

// Uint8 returns the uint8 representation of the current Attribute's data.
func (ad *AttributeDecoder) Uint8() uint8 {
	if ad.err != nil {
		return 0
	}

	b := ad.data()
	if len(b) != 1 {
		ad.err = fmt.Errorf("netlink: attribute %d is not a uint8; length: %d", ad.Type(), len(b))
		return 0
	}

	return uint8(b[0])
}

// Uint16 returns the uint16 representation of the current Attribute's data.
func (ad *AttributeDecoder) Uint16() uint16 {
	if ad.err != nil {
		return 0
	}

	b := ad.data()
	if len(b) != 2 {
		ad.err = fmt.Errorf("netlink: attribute %d is not a uint16; length: %d", ad.Type(), len(b))
		return 0
	}

	return ad.ByteOrder.Uint16(b)
}

// Uint32 returns the uint32 representation of the current Attribute's data.
func (ad *AttributeDecoder) Uint32() uint32 {
	if ad.err != nil {
		return 0
	}

	b := ad.data()
	if len(b) != 4 {
		ad.err = fmt.Errorf("netlink: attribute %d is not a uint32; length: %d", ad.Type(), len(b))
		return 0
	}

	return ad.ByteOrder.Uint32(b)
}

// Uint64 returns the uint64 representation of the current Attribute's data.
func (ad *AttributeDecoder) Uint64() uint64 {
	if ad.err != nil {
		return 0
	}

	b := ad.data()
	if len(b) != 8 {
		ad.err = fmt.Errorf("netlink: attribute %d is not a uint64; length: %d", ad.Type(), len(b))
		return 0
	}

	return ad.ByteOrder.Uint64(b)
}

// Do is a general purpose function which allows access to the current data
// pointed to by the AttributeDecoder.
//
// Do can be used to allow parsing arbitrary data within the context of the
// decoder.  Do is most useful when dealing with nested attributes, attribute
// arrays, or decoding arbitrary types (such as C structures) which don't fit
// cleanly into a typical unsigned integer value.
//
// The function fn should not retain any reference to the data b outside of the
// scope of the function.
func (ad *AttributeDecoder) Do(fn func(b []byte) error) {
	if ad.err != nil {
		return
	}

	b := ad.data()
	if err := fn(b); err != nil {
		ad.err = err
	}
}

// An AttributeEncoder provides a safe way to encode attributes.
//
// It is recommended to use an AttributeEncoder where possible instead of
// calling MarshalAttributes or using package nlenc directly.
//
// Errors from intermediate encoding steps are returned in the call to
// Encode.
type AttributeEncoder struct {
	// ByteOrder defines a specific byte order to use when processing integer
	// attributes.  ByteOrder should be set immediately after creating the
	// AttributeEncoder: before any attributes are encoded.
	//
	// If not set, the native byte order will be used.
	ByteOrder binary.ByteOrder

	attrs []Attribute
	err   error
}

// NewAttributeEncoder creates an AttributeEncoder that encodes Attributes.
func NewAttributeEncoder() *AttributeEncoder {
	return &AttributeEncoder{
		ByteOrder: nlenc.NativeEndian(),
	}
}

// Uint8 encodes uint8 data into an Attribute specified by typ.
func (ae *AttributeEncoder) Uint8(typ uint16, v uint8) {
	if ae.err != nil {
		return
	}

	ae.attrs = append(ae.attrs, Attribute{
		Type: typ,
		Data: []byte{v},
	})
}

// Uint16 encodes uint16 data into an Attribute specified by typ.
func (ae *AttributeEncoder) Uint16(typ uint16, v uint16) {
	if ae.err != nil {
		return
	}

	b := make([]byte, 2)
	ae.ByteOrder.PutUint16(b, v)

	ae.attrs = append(ae.attrs, Attribute{
		Type: typ,
		Data: b,
	})
}

// Uint32 encodes uint32 data into an Attribute specified by typ.
func (ae *AttributeEncoder) Uint32(typ uint16, v uint32) {
	if ae.err != nil {
		return
	}

	b := make([]byte, 4)
	ae.ByteOrder.PutUint32(b, v)

	ae.attrs = append(ae.attrs, Attribute{
		Type: typ,
		Data: b,
	})
}

// Uint64 encodes uint64 data into an Attribute specified by typ.
func (ae *AttributeEncoder) Uint64(typ uint16, v uint64) {
	if ae.err != nil {
		return
	}

	b := make([]byte, 8)
	ae.ByteOrder.PutUint64(b, v)

	ae.attrs = append(ae.attrs, Attribute{
		Type: typ,
		Data: b,
	})
}

// String encodes string s as a null-terminated string into an Attribute
// specified by typ.
func (ae *AttributeEncoder) String(typ uint16, s string) {
	if ae.err != nil {
		return
	}

	ae.attrs = append(ae.attrs, Attribute{
		Type: typ,
		Data: nlenc.Bytes(s),
	})
}

// Bytes embeds raw byte data into an Attribute specified by typ.
func (ae *AttributeEncoder) Bytes(typ uint16, b []byte) {
	if ae.err != nil {
		return
	}

	ae.attrs = append(ae.attrs, Attribute{
		Type: typ,
		Data: b,
	})
}

// Do is a general purpose function to encode arbitrary data into an attribute
// specified by typ.
//
// Do is especially helpful in encoding nested attributes, attribute arrays,
// or encoding arbitrary types (such as C structures) which don't fit cleanly
// into an unsigned integer value.
func (ae *AttributeEncoder) Do(typ uint16, fn func() ([]byte, error)) {
	if ae.err != nil {
		return
	}

	b, err := fn()
	if err != nil {
		ae.err = err
		return
	}

	ae.attrs = append(ae.attrs, Attribute{
		Type: typ,
		Data: b,
	})
}

// Encode returns the encoded bytes representing the attributes.
func (ae *AttributeEncoder) Encode() ([]byte, error) {
	if ae.err != nil {
		return nil, ae.err
	}

	return MarshalAttributes(ae.attrs)
}
