package packets

import (
	"bytes"
	"fmt"
	"io"

	"github.com/winc-link/hummingbird/internal/pkg/codes"
)

// Publish represents the MQTT Publish  packet
type Publish struct {
	Version    Version
	FixHeader  *FixHeader
	Dup        bool   //是否重发 [MQTT-3.3.1.-1]
	Qos        uint8  //qos等级
	Retain     bool   //是否保留消息
	TopicName  []byte //主题名
	PacketID          //报文标识符
	Payload    []byte
	Properties *Properties
}

func (p *Publish) String() string {
	return fmt.Sprintf("Publish, Version: %v, Pid: %v, Dup: %v, Qos: %v, Retain: %v, TopicName: %s, Payload: %s, Properties: %s",
		p.Version, p.PacketID, p.Dup, p.Qos, p.Retain, p.TopicName, p.Payload, p.Properties)
}

// NewPublishPacket returns a Publish instance by the given FixHeader and io.Reader.
func NewPublishPacket(fh *FixHeader, version Version, r io.Reader) (*Publish, error) {
	p := &Publish{FixHeader: fh, Version: version}
	p.Dup = (1 & (fh.Flags >> 3)) > 0
	p.Qos = (fh.Flags >> 1) & 3
	if p.Qos == 0 && p.Dup { //[MQTT-3.3.1-2]、 [MQTT-4.3.1-1]
		return nil, codes.ErrMalformed
	}
	if p.Qos > Qos2 {
		return nil, codes.ErrMalformed
	}
	if fh.Flags&1 == 1 { //保留标志
		p.Retain = true
	}
	err := p.Unpack(r)
	if err != nil {
		return nil, err
	}
	return p, nil
}

// Pack encodes the packet struct into bytes and writes it into io.Writer.
func (p *Publish) Pack(w io.Writer) error {
	p.FixHeader = &FixHeader{PacketType: PUBLISH}
	bufw := &bytes.Buffer{}
	var dup, retain byte
	dup = 0
	retain = 0
	if p.Dup {
		dup = 8
	}
	if p.Retain {
		retain = 1
	}
	p.FixHeader.Flags = dup | retain | (p.Qos << 1)
	writeBinary(bufw, p.TopicName)
	if p.Qos == Qos1 || p.Qos == Qos2 {
		writeUint16(bufw, p.PacketID)
	}
	if p.Version == Version5 {
		p.Properties.Pack(bufw, PUBLISH)
	}
	bufw.Write(p.Payload)
	p.FixHeader.RemainLength = bufw.Len()
	err := p.FixHeader.Pack(w)
	if err != nil {
		return err
	}
	_, err = bufw.WriteTo(w)

	return err

}

// Unpack read the packet bytes from io.Reader and decodes it into the packet struct.
func (p *Publish) Unpack(r io.Reader) error {
	var err error
	restBuffer := make([]byte, p.FixHeader.RemainLength)
	_, err = io.ReadFull(r, restBuffer)
	if err != nil {
		return codes.ErrMalformed
	}
	bufr := bytes.NewBuffer(restBuffer)
	p.TopicName, err = readUTF8String(true, bufr)
	if err != nil {
		return err
	}
	if !ValidTopicName(true, p.TopicName) {
		return codes.ErrMalformed
	}
	if p.Qos > Qos0 {
		p.PacketID, err = readUint16(bufr)
		if err != nil {
			return err
		}
	}
	if p.Version == Version5 {
		p.Properties = &Properties{}
		if err := p.Properties.Unpack(bufr, PUBLISH); err != nil {
			return err
		}
	}
	p.Payload = bufr.Next(bufr.Len())
	return nil
}

// NewPuback returns the puback struct related to the publish struct in QoS 1
func (p *Publish) NewPuback(code codes.Code, ppt *Properties) *Puback {
	pub := &Puback{
		Version:    p.Version,
		Code:       code,
		PacketID:   p.PacketID,
		Properties: ppt,
	}
	return pub
}

// NewPubrec returns the pubrec struct related to the publish struct in QoS 2
func (p *Publish) NewPubrec(code codes.Code, ppt *Properties) *Pubrec {
	pub := &Pubrec{
		Version:    p.Version,
		Code:       code,
		PacketID:   p.PacketID,
		Properties: ppt,
	}
	return pub
}
