package proto

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

// Header is the decoded form of a 16-byte CA message header.
// All integer fields are stored in host byte order after decoding.
type Header struct {
	Command     uint16
	PayloadSize uint32 // actual payload size (resolved from extended form if needed)
	DataType    uint16
	DataCount   uint32 // actual element count (resolved from extended form if needed)
	Parameter1  uint32
	Parameter2  uint32
}

// HeaderSize is the wire size of a standard CA header.
const HeaderSize = 16

// Encode writes the header to buf (must be at least HeaderSize bytes).
// If PayloadSize > 0xFFFE or DataCount > 0xFFFF the extended encoding is used
// and the caller must prepend an extra 8 bytes; use EncodeExtended instead.
func (h Header) Encode(buf []byte) {
	binary.BigEndian.PutUint16(buf[0:], h.Command)
	binary.BigEndian.PutUint16(buf[2:], uint16(h.PayloadSize))
	binary.BigEndian.PutUint16(buf[4:], h.DataType)
	binary.BigEndian.PutUint16(buf[6:], uint16(h.DataCount))
	binary.BigEndian.PutUint32(buf[8:], h.Parameter1)
	binary.BigEndian.PutUint32(buf[12:], h.Parameter2)
}

// Bytes returns the 16-byte wire encoding of h.
func (h Header) Bytes() []byte {
	buf := make([]byte, HeaderSize)
	h.Encode(buf)
	return buf
}

// DecodeHeader reads exactly one CA header from r, handling the extended
// encoding (payload_size == 0xFFFF) transparently.
// Returns the decoded Header and the number of bytes read (16 or 24).
func DecodeHeader(r io.Reader) (Header, int, error) {
	var raw [HeaderSize]byte
	if _, err := io.ReadFull(r, raw[:]); err != nil {
		return Header{}, 0, fmt.Errorf("ca: read header: %w", err)
	}

	h := Header{
		Command:    binary.BigEndian.Uint16(raw[0:]),
		DataType:   binary.BigEndian.Uint16(raw[4:]),
		Parameter1: binary.BigEndian.Uint32(raw[8:]),
		Parameter2: binary.BigEndian.Uint32(raw[12:]),
	}

	rawPayload := binary.BigEndian.Uint16(raw[2:])
	rawCount := binary.BigEndian.Uint16(raw[6:])

	// Extended message: payload_size == 0xFFFF means real sizes are in the
	// next 8 bytes (parameter1 = real payload size, parameter2 = real count).
	if rawPayload == 0xFFFF && rawCount == 0x0000 {
		var ext [8]byte
		if _, err := io.ReadFull(r, ext[:]); err != nil {
			return Header{}, 16, fmt.Errorf("ca: read extended header: %w", err)
		}
		h.PayloadSize = binary.BigEndian.Uint32(ext[0:])
		h.DataCount = binary.BigEndian.Uint32(ext[4:])
		// Note: parameter1/2 in the base header are overwritten by the extended values.
		// In practice, the original parameter1/2 are still valid — the extended header
		// only carries sizes, not the original parameters.  We already decoded parameter1/2
		// above from the base header.
		return h, 24, nil
	}

	h.PayloadSize = uint32(rawPayload)
	h.DataCount = uint32(rawCount)
	return h, 16, nil
}

// PadTo8 returns n rounded up to the nearest multiple of 8.
// CA requires all message payloads to be padded to 8-byte boundaries.
func PadTo8(n int) int {
	return (n + 7) &^ 7
}

// PadBytes appends zero bytes to b until len(b) is a multiple of 8.
func PadBytes(b []byte) []byte {
	need := PadTo8(len(b)) - len(b)
	return append(b, make([]byte, need)...)
}

// BuildMessage assembles a complete CA message (header + payload).
// payload may be nil for zero-length messages.
func BuildMessage(h Header, payload []byte) []byte {
	h.PayloadSize = uint32(len(payload))
	msg := make([]byte, HeaderSize+len(payload))
	h.Encode(msg)
	copy(msg[HeaderSize:], payload)
	return msg
}

// BuildStringPayload encodes a string as a null-terminated, 8-byte padded payload.
func BuildStringPayload(s string) []byte {
	b := append([]byte(s), 0) // null terminator
	return PadBytes(b)
}
