// Package utils provides decoding helpers shared across flow decoders.
package utils

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"reflect"
)

// BytesBuffer is a minimal buffer interface for decoding helpers.
type BytesBuffer interface {
	io.Reader
	Next(int) []byte
}

// BinaryDecoder decodes multiple values from the buffer using big-endian encoding.
func BinaryDecoder(payload *bytes.Buffer, dests ...interface{}) error {
	for _, dest := range dests {
		err := BinaryRead(payload, binary.BigEndian, dest)
		if err != nil {
			return fmt.Errorf("BinaryDecoder: %w", err)
		}
	}
	return nil
}

// BinaryRead decodes a single value from the buffer using the supplied byte order.
func BinaryRead(payload BytesBuffer, order binary.ByteOrder, data any) error {
	// Fast path for basic types and slices.
	if n := intDataSize(data); n != 0 {
		bs := payload.Next(n)
		if len(bs) < n {
			return io.ErrUnexpectedEOF
		}
		switch data := data.(type) {
		case *bool:
			*data = bs[0] != 0
		case *int8:
			*data = int8(bs[0])
		case *uint8:
			*data = bs[0]
		case *int16:
			*data = int16(order.Uint16(bs))
		case *uint16:
			*data = order.Uint16(bs)
		case *int32:
			*data = int32(order.Uint32(bs))
		case *uint32:
			*data = order.Uint32(bs)
		case *int64:
			*data = int64(order.Uint64(bs))
		case *uint64:
			*data = order.Uint64(bs)
		case *string:
			strlen := int(order.Uint32(bs))
			buf := payload.Next(strlen)
			if len(buf) < strlen {
				return io.ErrUnexpectedEOF
			}
			*data = string(buf)
		case []bool:
			for i, x := range bs { // Easier to loop over the input for 8-bit values.
				data[i] = x != 0
			}
		case []int8:
			for i, x := range bs {
				data[i] = int8(x)
			}
		case []uint8:
			copy(data, bs)
		case IPAddress:
			copy(data, bs)
		case MacAddress:
			copy(data, bs)
		case []int16:
			for i := range data {
				data[i] = int16(order.Uint16(bs[2*i:]))
			}
		case []uint16:
			for i := range data {
				data[i] = order.Uint16(bs[2*i:])
			}
		case []int32:
			for i := range data {
				data[i] = int32(order.Uint32(bs[4*i:]))
			}
		case []uint32:
			for i := range data {
				data[i] = order.Uint32(bs[4*i:])
			}
		case []int64:
			for i := range data {
				data[i] = int64(order.Uint64(bs[8*i:]))
			}
		case []uint64:
			for i := range data {
				data[i] = order.Uint64(bs[8*i:])
			}
		default:
			n = 0 // fast path doesn't apply
		}
		if n != 0 {
			return nil
		}
	}

	return errors.New("binary.Read: invalid type " + reflect.TypeOf(data).String())
}

// intDataSize returns the size of the data required to represent the data when encoded.
// It returns zero if the type cannot be implemented by the fast path in Read or Write.
func intDataSize(data any) int {
	switch data := data.(type) {
	case bool, int8, uint8, *bool, *int8, *uint8:
		return 1
	case []bool:
		return len(data)
	case []int8:
		return len(data)
	case []uint8:
		return len(data)
	case IPAddress:
		return len(data)
	case MacAddress:
		return len(data)
	case int16, uint16, *int16, *uint16:
		return 2
	case []int16:
		return 2 * len(data)
	case []uint16:
		return 2 * len(data)
	case int32, uint32, *int32, *uint32:
		return 4
	case *string: // return the length field
		return 4
	case []int32:
		return 4 * len(data)
	case []uint32:
		return 4 * len(data)
	case int64, uint64, *int64, *uint64:
		return 8
	case []int64:
		return 8 * len(data)
	case []uint64:
		return 8 * len(data)
	}
	return 0
}
