// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2022 mochi-co
// SPDX-FileContributor: mochi-co

package packets

import (
	"bytes"
)

// FixedHeader contains the values of the fixed header portion of the MQTT packet.
type FixedHeader struct {
	Remaining int  `json:"remaining"` // the number of remaining bytes in the payload.
	Type      byte `json:"type"`      // the type of the packet (PUBLISH, SUBSCRIBE, etc) from bits 7 - 4 (byte 1).
	Qos       byte `json:"qos"`       // indicates the quality of service expected.
	Dup       bool `json:"dup"`       // indicates if the packet was already sent at an earlier time.
	Retain    bool `json:"retain"`    // whether the message should be retained.
}

// Encode encodes the FixedHeader and returns a bytes buffer.
func (fh *FixedHeader) Encode(buf *bytes.Buffer) {
	buf.WriteByte(fh.Type<<4 | encodeBool(fh.Dup)<<3 | fh.Qos<<1 | encodeBool(fh.Retain))
	encodeLength(buf, int64(fh.Remaining))
}

// Decode extracts the specification bits from the header byte.
func (fh *FixedHeader) Decode(hb byte) error {
	fh.Type = hb >> 4 // Get the message type from the first 4 bytes.

	switch fh.Type {
	case Publish:
		if (hb>>1)&0x01 > 0 && (hb>>1)&0x02 > 0 {
			return ErrProtocolViolationQosOutOfRange // [MQTT-3.3.1-4]
		}

		fh.Dup = (hb>>3)&0x01 > 0 // is duplicate
		fh.Qos = (hb >> 1) & 0x03 // qos flag
		fh.Retain = hb&0x01 > 0   // is retain flag
	case Pubrel:
		fallthrough
	case Subscribe:
		fallthrough
	case Unsubscribe:
		if (hb>>0)&0x01 != 0 || (hb>>1)&0x01 != 1 || (hb>>2)&0x01 != 0 || (hb>>3)&0x01 != 0 { // [MQTT-3.8.1-1] [MQTT-3.10.1-1]
			return ErrMalformedFlags
		}

		fh.Qos = (hb >> 1) & 0x03
	default:
		if (hb>>0)&0x01 != 0 ||
			(hb>>1)&0x01 != 0 ||
			(hb>>2)&0x01 != 0 ||
			(hb>>3)&0x01 != 0 { // [MQTT-3.8.3-5] [MQTT-3.14.1-1] [MQTT-3.15.1-1]
			return ErrMalformedFlags
		}
	}

	if fh.Qos == 0 && fh.Dup {
		return ErrProtocolViolationDupNoQos // [MQTT-3.3.1-2]
	}

	return nil
}
