package frame

import (
	"encoding/binary"
	"errors"
	"io"
)

const (
	// MaxFrameSize is the maximum size of a frame. It is set to 65534 bytes,
	// which is one byte less than the maximum value of a uint16 (65535).
	MaxFrameSize uint16 = 65535
)

// Frame is a Frame that uses a length prefix to frame the data.
type Frame struct {
	contents []byte
}

// New creates a new Frame with a length prefix.
func New(contents []byte) *Frame {
	return &Frame{
		contents: contents,
	}
}

// Contents implements Frame.
func (l *Frame) Contents() []byte {
	return l.contents
}

// Len implements Frame.
func (l *Frame) Len() uint16 {
	return uint16(len(l.contents))
}

// ReadFrom implements Frame.
func (lpf *Frame) ReadFrom(r io.Reader) (int64, error) {
	// LPF expects a 2-byte length prefix followed by the contents.
	header := make([]byte, 2)
	// Read the header (2 bytes) from the reader. io.ReadFull will return an
	// error if it doesn't read exactly 2 bytes.
	if _, err := io.ReadFull(r, header); err != nil {
		return 0, err
	}

	// Calculate the length of the payload.
	payloadLen := binary.BigEndian.Uint16(header)

	// Check if the payload length exceeds the maximum frame size.
	if payloadLen >= MaxFrameSize {
		return 0, errors.New("payload length exceeds maximum frame size")
	}

	// Read the payload from the reader.
	payload := make([]byte, payloadLen)
	if _, err := io.ReadFull(r, payload); err != nil {
		return 0, err
	}

	// Set the contents of the frame.
	lpf.contents = payload

	// Calculate the total length of the frame.
	return int64(2 + len(payload)), nil
}

// WriteTo implements Frame.
func (l *Frame) WriteTo(w io.Writer) (int64, error) {
	// Check if the payload length exceeds the maximum frame size.
	if uint16(len(l.contents)) >= MaxFrameSize {
		return 0, errors.New("payload length exceeds maximum frame size")
	}

	// Create a buffer to hold the length prefix and the payload.
	lenBuf := make([]byte, 2)
	binary.BigEndian.PutUint16(lenBuf, uint16(len(l.contents)))

	// Write the header to the buffer.
	if _, err := w.Write(lenBuf); err != nil {
		return 0, errors.New("failed to write frame header")
	}

	// Write the payload to the buffer.
	if _, err := w.Write(l.contents); err != nil {
		return 0, errors.New("failed to write frame payload")
	}

	// Calculate the total length of the frame.
	return int64(2 + len(l.contents)), nil
}