package payload

import (
	"github.com/iotaledger/hive.go/marshalutil"
)

func init() {
	// register the generic unmarshaler
	SetGenericUnmarshalerFactory(GenericPayloadUnmarshalerFactory)
	// register the generic data payload type
	RegisterType(DataType, GenericPayloadUnmarshalerFactory(DataType))
}

// Payload represents some kind of payload of data which only gains meaning by having
// corresponding node logic processing payloads of a given type.
type Payload interface {
	// Type returns the type of the payload.
	Type() Type
	// Bytes returns the payload bytes.
	Bytes() []byte
	// Unmarshal unmarshals the payload from the given bytes.
	Unmarshal(bytes []byte) error
	// String returns a human-friendly representation of the payload.
	String() string
}

// FromBytes unmarshals bytes into a payload.
func FromBytes(bytes []byte) (result Payload, consumedBytes int, err error) {
	// initialize helper
	marshalUtil := marshalutil.New(bytes)

	// calculate result
	payloadType, err := marshalUtil.ReadUint32()
	if err != nil {
		return
	}

	payloadSize, err := marshalUtil.ReadUint32()
	if err != nil {
		return
	}

	marshalUtil.ReadSeek(marshalUtil.ReadOffset() - marshalutil.UINT32_SIZE*2)
	payloadBytes, err := marshalUtil.ReadBytes(int(payloadSize) + 8)
	if err != nil {
		return
	}

	readOffset := marshalUtil.ReadOffset()
	result, err = GetUnmarshaler(payloadType)(payloadBytes)
	if err != nil {
		// fallback to the generic unmarshaler if registered type fails to unmarshal
		marshalUtil.ReadSeek(readOffset)
		result, err = GenericPayloadUnmarshalerFactory(payloadType)(payloadBytes)
		if err != nil {
			return
		}
	}

	// return the number of bytes we processed
	consumedBytes = marshalUtil.ReadOffset()
	return
}

// Parse parses a payload by using the given marshal util.
func Parse(marshalUtil *marshalutil.MarshalUtil) (Payload, error) {
	if payload, err := marshalUtil.Parse(func(data []byte) (interface{}, int, error) { return FromBytes(data) }); err != nil {
		return nil, err
	} else {
		return payload.(Payload), nil
	}
}