package packets

import (
	"bytes"
	"fmt"
	"io"
	"reflect"
	"strings"

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

const (
	PropPayloadFormat          byte = 0x01
	PropMessageExpiry          byte = 0x02
	PropContentType            byte = 0x03
	PropResponseTopic          byte = 0x08
	PropCorrelationData        byte = 0x09
	PropSubscriptionIdentifier byte = 0x0B
	PropSessionExpiryInterval  byte = 0x11
	PropAssignedClientID       byte = 0x12
	PropServerKeepAlive        byte = 0x13
	PropAuthMethod             byte = 0x15
	PropAuthData               byte = 0x16
	PropRequestProblemInfo     byte = 0x17
	PropWillDelayInterval      byte = 0x18
	PropRequestResponseInfo    byte = 0x19
	PropResponseInfo           byte = 0x1A
	PropServerReference        byte = 0x1C
	PropReasonString           byte = 0x1F
	PropReceiveMaximum         byte = 0x21
	PropTopicAliasMaximum      byte = 0x22
	PropTopicAlias             byte = 0x23
	PropMaximumQoS             byte = 0x24
	PropRetainAvailable        byte = 0x25
	PropUser                   byte = 0x26
	PropMaximumPacketSize      byte = 0x27
	PropWildcardSubAvailable   byte = 0x28
	PropSubIDAvailable         byte = 0x29
	PropSharedSubAvailable     byte = 0x2A
)

func errMorethanOnce(property byte) error {
	return fmt.Errorf("property %v presents more than once", property)
}

type UserProperty struct {
	K []byte
	V []byte
}

// Properties is a struct representing the all the described properties
// allowed by the MQTT protocol, determining the validity of a property
// relvative to the packettype it was received in is provided by the
// ValidateID function
type Properties struct {
	// PayloadFormat indicates the format of the payload of the message
	// 0 is unspecified bytes
	// 1 is UTF8 encoded character data
	PayloadFormat *byte
	// MessageExpiry is the lifetime of the message in seconds
	MessageExpiry *uint32
	// ContentType is a UTF8 string describing the content of the message
	// for example it could be a MIME type
	ContentType []byte
	// ResponseTopic is a UTF8 string indicating the topic name to which any
	// response to this message should be sent
	ResponseTopic []byte
	// CorrelationData is binary data used to associate future response
	// messages with the original request message
	CorrelationData []byte
	// SubscriptionIdentifier is an identifier of the subscription to which
	// the Publish matched
	SubscriptionIdentifier []uint32
	// SessionExpiryInterval is the time in seconds after a client disconnects
	// that the server should retain the session Info (subscriptions etc)
	SessionExpiryInterval *uint32
	// AssignedClientID is the server assigned client identifier in the case
	// that a client connected without specifying a clientID the server
	// generates one and returns it in the Connack
	AssignedClientID []byte
	// ServerKeepAlive allows the server to specify in the Connack packet
	// the time in seconds to be used as the keep alive value
	ServerKeepAlive *uint16
	// AuthMethod is a UTF8 string containing the name of the authentication
	// method to be used for extended authentication
	AuthMethod []byte
	// AuthData is binary data containing authentication data
	AuthData []byte
	// RequestProblemInfo is used by the Client to indicate to the server to
	// include the Reason String and/or User Properties in case of failures
	RequestProblemInfo *byte
	// WillDelayInterval is the number of seconds the server waits after the
	// point at which it would otherwise send the will message before sending
	// it. The client reconnecting before that time expires causes the server
	// to cancel sending the will
	WillDelayInterval *uint32
	// RequestResponseInfo is used by the Client to request the Server provide
	// Response Info in the Connack
	RequestResponseInfo *byte
	// ResponseInfo is a UTF8 encoded string that can be used as the basis for
	// createing a Response Topic. The way in which the Client creates a
	// Response Topic from the Response Info is not defined. A base
	// use of this is to pass a globally unique portion of the topic tree which
	// is reserved for this Client for at least the lifetime of its Session. This
	// often cannot just be a random name as both the requesting Client and the
	// responding Client need to be authorized to use it. It is normal to use this
	// as the root of a topic tree for a particular Client. For the Server to
	// return this Info, it normally needs to be correctly configured.
	// Using this mechanism allows this configuration to be done once in the
	// Server rather than in each Client
	ResponseInfo []byte
	// ServerReference is a UTF8 string indicating another server the client
	// can use
	ServerReference []byte
	// ReasonString is a UTF8 string representing the reason associated with
	// this response, intended to be human readable for diagnostic purposes
	ReasonString []byte
	// ReceiveMaximum is the maximum number of QOS1 & 2 messages allowed to be
	// 'inflight' (not having received a PUBACK/PUBCOMP response for)
	ReceiveMaximum *uint16
	// TopicAliasMaximum is the highest value permitted as a Topic Alias
	TopicAliasMaximum *uint16
	// TopicAlias is used in place of the topic string to reduce the size of
	// packets for repeated messages on a topic
	TopicAlias *uint16
	// MaximumQoS is the highest QOS level permitted for a Publish
	MaximumQoS *byte
	// RetainAvailable indicates whether the server supports messages with the
	// retain flag set
	RetainAvailable *byte
	// User is a map of user provided properties
	User []UserProperty

	// MaximumPacketSize allows the client or server to specify the maximum packet
	// size in bytes that they support
	MaximumPacketSize *uint32
	// WildcardSubAvailable indicates whether wildcard subscriptions are permitted
	WildcardSubAvailable *byte
	// SubIDAvailable indicates whether subscription identifiers are supported
	SubIDAvailable *byte
	// SharedSubAvailable indicates whether shared subscriptions are supported
	SharedSubAvailable *byte
}

func sprintf(name string, v interface{}) string {
	if v == nil {
		return fmt.Sprintf("%s: %v", name, v)
	}
	t := reflect.TypeOf(v)
	if t.Kind() == reflect.Ptr {
		rv := reflect.ValueOf(v)
		if rv.IsNil() {
			return fmt.Sprintf("%s: nil", name)
		}
		return fmt.Sprintf("%s: %v", name, reflect.ValueOf(v).Elem())
	}
	return fmt.Sprintf("%s: %v", name, v)

}

func (p *Properties) String() string {
	var str []string
	str = append(str, sprintf("PayloadFormat", p.PayloadFormat))
	str = append(str, sprintf("MessageExpiry", p.MessageExpiry))
	str = append(str, sprintf("ContentType", p.ContentType))
	str = append(str, sprintf("ResponseTopic", p.ResponseTopic))
	str = append(str, sprintf("CorrelationData", p.CorrelationData))
	str = append(str, sprintf("SubscriptionIdentifier", p.SubscriptionIdentifier))
	str = append(str, sprintf("SessionExpiryInterval", p.SessionExpiryInterval))
	str = append(str, sprintf("AssignedClientID", p.AssignedClientID))
	str = append(str, sprintf("ServerKeepAlive", p.ServerKeepAlive))
	str = append(str, sprintf("AuthMethod", p.AuthMethod))
	str = append(str, sprintf("AuthData", p.AuthData))
	str = append(str, sprintf("RequestProblemInfo", p.RequestProblemInfo))
	str = append(str, sprintf("WillDelayInterval", p.WillDelayInterval))
	str = append(str, sprintf("RequestResponseInfo", p.RequestResponseInfo))
	str = append(str, sprintf("ResponseInfo", p.ResponseInfo))
	str = append(str, sprintf("ServerReference", p.ServerReference))
	str = append(str, sprintf("ReasonString", p.ReasonString))
	str = append(str, sprintf("ReceiveMaximum", p.ReceiveMaximum))
	str = append(str, sprintf("TopicAliasMaximum", p.TopicAliasMaximum))
	str = append(str, sprintf("TopicAlias", p.TopicAlias))
	str = append(str, sprintf("MaximumQoS", p.MaximumQoS))
	str = append(str, sprintf("RetainAvailable", p.RetainAvailable))
	str = append(str, sprintf("User", p.User))
	str = append(str, sprintf("MaximumPacketSize", p.MaximumPacketSize))
	str = append(str, sprintf("WildcardSubAvailable", p.WildcardSubAvailable))
	str = append(str, sprintf("SubIDAvailable", p.SubIDAvailable))
	str = append(str, sprintf("SharedSubAvailable", p.SharedSubAvailable))
	return strings.Join(str, ", ")
}

func (p *Properties) PackWillProperties(bufw *bytes.Buffer) {
	newBufw := &bytes.Buffer{}
	defer func() {
		b, _ := DecodeRemainLength(newBufw.Len())
		bufw.Write(b)
		newBufw.WriteTo(bufw)
	}()
	if p == nil {
		return
	}
	propertyWriteByte(PropPayloadFormat, p.PayloadFormat, newBufw)
	propertyWriteUint32(PropMessageExpiry, p.MessageExpiry, newBufw)
	propertyWriteString(PropContentType, p.ContentType, newBufw)
	propertyWriteString(PropResponseTopic, p.ResponseTopic, newBufw)
	propertyWriteString(PropCorrelationData, p.CorrelationData, newBufw)
	propertyWriteUint32(PropWillDelayInterval, p.WillDelayInterval, newBufw)
	if len(p.User) != 0 {
		for _, v := range p.User {
			newBufw.WriteByte(PropUser)
			writeBinary(newBufw, v.K)
			writeBinary(newBufw, v.V)
		}
	}
}

// Pack takes all the defined properties for an Properties and produces
// a slice of bytes representing the wire format for the Info
func (p *Properties) Pack(bufw *bytes.Buffer, packetType byte) {
	newBufw := &bytes.Buffer{}
	defer func() {
		b, _ := DecodeRemainLength(newBufw.Len())
		bufw.Write(b)
		newBufw.WriteTo(bufw)
	}()
	if p == nil {
		return
	}
	propertyWriteByte(PropPayloadFormat, p.PayloadFormat, newBufw)
	propertyWriteUint32(PropMessageExpiry, p.MessageExpiry, newBufw)
	propertyWriteString(PropContentType, p.ContentType, newBufw)
	propertyWriteString(PropResponseTopic, p.ResponseTopic, newBufw)
	propertyWriteString(PropCorrelationData, p.CorrelationData, newBufw)

	if len(p.SubscriptionIdentifier) != 0 {
		for _, v := range p.SubscriptionIdentifier {
			newBufw.WriteByte(PropSubscriptionIdentifier)
			b, _ := DecodeRemainLength(int(v))
			newBufw.Write(b)
		}
	}
	propertyWriteUint32(PropSessionExpiryInterval, p.SessionExpiryInterval, newBufw)
	propertyWriteString(PropAssignedClientID, p.AssignedClientID, newBufw)
	propertyWriteUint16(PropServerKeepAlive, p.ServerKeepAlive, newBufw)
	propertyWriteString(PropAuthMethod, p.AuthMethod, newBufw)
	propertyWriteString(PropAuthData, p.AuthData, newBufw)
	propertyWriteByte(PropRequestProblemInfo, p.RequestProblemInfo, newBufw)
	propertyWriteUint32(PropWillDelayInterval, p.WillDelayInterval, newBufw)
	propertyWriteByte(PropRequestResponseInfo, p.RequestResponseInfo, newBufw)
	propertyWriteString(PropResponseInfo, p.ResponseInfo, newBufw)
	propertyWriteString(PropServerReference, p.ServerReference, newBufw)
	propertyWriteString(PropReasonString, p.ReasonString, newBufw)
	propertyWriteUint16(PropReceiveMaximum, p.ReceiveMaximum, newBufw)
	propertyWriteUint16(PropTopicAliasMaximum, p.TopicAliasMaximum, newBufw)
	propertyWriteUint16(PropTopicAlias, p.TopicAlias, newBufw)
	propertyWriteByte(PropMaximumQoS, p.MaximumQoS, newBufw)
	propertyWriteByte(PropRetainAvailable, p.RetainAvailable, newBufw)

	if len(p.User) != 0 {
		for _, v := range p.User {
			newBufw.WriteByte(PropUser)
			writeBinary(newBufw, v.K)
			writeBinary(newBufw, v.V)
		}
	}

	propertyWriteUint32(PropMaximumPacketSize, p.MaximumPacketSize, newBufw)
	propertyWriteByte(PropWildcardSubAvailable, p.WildcardSubAvailable, newBufw)
	propertyWriteByte(PropSubIDAvailable, p.SubIDAvailable, newBufw)
	propertyWriteByte(PropSharedSubAvailable, p.SharedSubAvailable, newBufw)
}

func (p *Properties) UnpackWillProperties(bufr *bytes.Buffer) error {
	var err error
	length, err := EncodeRemainLength(bufr)
	// 整个buffer最多只能读到length这么长
	if err != nil {
		return err
	}
	if length == 0 {
		return nil
	}
	newBufr := bytes.NewBuffer(bufr.Next(length))
	var propType byte
	for {
		if err != nil {
			return err
		}
		propType, err = newBufr.ReadByte()
		if err != nil && err != io.EOF {
			return err
		}
		if err == io.EOF {
			break
		}
		switch propType {
		case PropWillDelayInterval:
			p.WillDelayInterval, err = propertyReadUint32(p.WillDelayInterval, newBufr, propType, nil)
		case PropPayloadFormat:
			p.PayloadFormat, err = propertyReadBool(p.PayloadFormat, newBufr, propType)
		case PropMessageExpiry:
			p.MessageExpiry, err = propertyReadUint32(p.MessageExpiry, newBufr, propType, nil)
		case PropContentType:
			p.ContentType, err = propertyReadUTF8String(p.ContentType, newBufr, propType, nil)
		case PropResponseTopic:
			p.ResponseTopic, err = propertyReadUTF8String(p.ResponseTopic, newBufr, propType, func(u []byte) bool {
				return ValidTopicName(true, u) // [MQTT-3.3.2-14]
			})
		case PropCorrelationData:
			p.CorrelationData, err = propertyReadBinary(p.CorrelationData, newBufr, propType, nil)
		case PropUser:
			k, err := readUTF8String(true, newBufr)
			if err != nil {
				return codes.ErrMalformed
			}
			v, err := readUTF8String(true, newBufr)
			if err != nil {
				return codes.ErrMalformed
			}
			p.User = append(p.User, UserProperty{K: k, V: v})
		default:
			return codes.ErrMalformed
		}
	}
	return nil
}

// Unpack takes a buffer of bytes and reads out the defined properties
// filling in the appropriate entries in the struct, it returns the number
// of bytes used to store the Prop data and any error in decoding them
func (p *Properties) Unpack(bufr *bytes.Buffer, packetType byte) error {
	var err error
	length, err := EncodeRemainLength(bufr)
	// 整个buffer最多只能读到length这么长
	if err != nil {
		return err
	}
	if length == 0 {
		return nil
	}
	newBufr := bytes.NewBuffer(bufr.Next(length))
	var propType byte
	for {
		if err != nil {
			return err
		}
		propType, err = newBufr.ReadByte()
		if err != nil && err != io.EOF {
			return err
		}
		if err == io.EOF {
			break
		}

		if !ValidateID(packetType, propType) {
			return codes.ErrProtocol
		}

		switch propType {
		case PropPayloadFormat:
			p.PayloadFormat, err = propertyReadBool(p.PayloadFormat, newBufr, propType)
		case PropMessageExpiry:
			p.MessageExpiry, err = propertyReadUint32(p.MessageExpiry, newBufr, propType, nil)
		case PropContentType:
			p.ContentType, err = propertyReadUTF8String(p.ContentType, newBufr, propType, nil)
		case PropResponseTopic:
			p.ResponseTopic, err = propertyReadUTF8String(p.ResponseTopic, newBufr, propType, func(u []byte) bool {
				return ValidTopicName(true, u) // [MQTT-3.3.2-14]
			})
		case PropCorrelationData:
			p.CorrelationData, err = propertyReadBinary(p.CorrelationData, newBufr, propType, nil)
		case PropSubscriptionIdentifier:
			if len(p.SubscriptionIdentifier) != 0 {
				return codes.ErrProtocol
			}
			si, err := EncodeRemainLength(newBufr)
			if err != nil {
				return codes.ErrMalformed
			}
			if si == 0 {
				return codes.ErrProtocol
			}
			p.SubscriptionIdentifier = append(p.SubscriptionIdentifier, uint32(si))
		case PropSessionExpiryInterval:
			p.SessionExpiryInterval, err = propertyReadUint32(p.SessionExpiryInterval, newBufr, propType, nil)
		case PropAssignedClientID:
			p.AssignedClientID, err = propertyReadUTF8String(p.AssignedClientID, newBufr, propType, nil)
		case PropServerKeepAlive:
			p.ServerKeepAlive, err = propertyReadUint16(p.ServerKeepAlive, newBufr, propType, nil)
		case PropAuthMethod:
			p.AuthMethod, err = propertyReadUTF8String(p.AuthMethod, newBufr, propType, nil)
		case PropAuthData:
			p.AuthData, err = propertyReadUTF8String(p.AuthData, newBufr, propType, nil)
		case PropRequestProblemInfo:
			p.RequestProblemInfo, err = propertyReadBool(p.RequestProblemInfo, newBufr, propType)
		case PropWillDelayInterval:
			p.WillDelayInterval, err = propertyReadUint32(p.WillDelayInterval, newBufr, propType, nil)
		case PropRequestResponseInfo:
			p.RequestResponseInfo, err = propertyReadBool(p.RequestResponseInfo, newBufr, propType)
		case PropResponseInfo:
			p.ResponseInfo, err = propertyReadUTF8String(p.ResponseInfo, newBufr, propType, nil)
		case PropServerReference:
			p.ServerReference, err = propertyReadUTF8String(p.ServerReference, newBufr, propType, nil)
		case PropReasonString:
			p.ReasonString, err = propertyReadUTF8String(p.ReasonString, newBufr, propType, nil)
		case PropReceiveMaximum:
			p.ReceiveMaximum, err = propertyReadUint16(p.ReceiveMaximum, newBufr, propType, func(u uint16) bool {
				return u != 0
			})
		case PropTopicAliasMaximum:
			p.TopicAliasMaximum, err = propertyReadUint16(p.TopicAliasMaximum, newBufr, propType, nil)
		case PropTopicAlias:
			p.TopicAlias, err = propertyReadUint16(p.TopicAlias, newBufr, propType, func(u uint16) bool {
				return u != 0 // [MQTT-3.3.2-8]
			})
		case PropMaximumQoS:
			p.MaximumQoS, err = propertyReadBool(p.MaximumQoS, newBufr, propType)
		case PropRetainAvailable:
			p.RetainAvailable, err = propertyReadBool(p.RetainAvailable, newBufr, propType)
		case PropUser:
			k, err := readUTF8String(true, newBufr)
			if err != nil {
				return codes.ErrMalformed
			}
			v, err := readUTF8String(true, newBufr)
			if err != nil {
				return codes.ErrMalformed
			}
			p.User = append(p.User, UserProperty{K: k, V: v})
		case PropMaximumPacketSize:
			p.MaximumPacketSize, err = propertyReadUint32(p.MaximumPacketSize, newBufr, propType, func(u uint32) bool {
				return u != 0
			})
		case PropWildcardSubAvailable:
			p.WildcardSubAvailable, err = propertyReadBool(p.WildcardSubAvailable, newBufr, propType)
		case PropSubIDAvailable:
			p.SubIDAvailable, err = propertyReadBool(p.SubIDAvailable, newBufr, propType)
		case PropSharedSubAvailable:
			p.SharedSubAvailable, err = propertyReadBool(p.SharedSubAvailable, newBufr, propType)
		default:
			return codes.ErrMalformed
		}
	}
	if p.AuthData != nil && p.AuthMethod == nil {
		return codes.ErrMalformed
	}
	return nil
}

// ValidProperties is a map of the various properties and the
// PacketTypes that is valid for server to unpack.
var ValidProperties = map[byte]map[byte]struct{}{
	PropPayloadFormat:          {CONNECT: {}, PUBLISH: {}},
	PropMessageExpiry:          {CONNECT: {}, PUBLISH: {}},
	PropContentType:            {CONNECT: {}, PUBLISH: {}},
	PropResponseTopic:          {CONNECT: {}, PUBLISH: {}},
	PropCorrelationData:        {CONNECT: {}, PUBLISH: {}},
	PropSubscriptionIdentifier: {SUBSCRIBE: {}},
	PropSessionExpiryInterval:  {CONNECT: {}, CONNACK: {}, DISCONNECT: {}},
	PropAssignedClientID:       {CONNACK: {}},
	PropServerKeepAlive:        {CONNACK: {}},
	PropAuthMethod:             {CONNECT: {}, CONNACK: {}, AUTH: {}},
	PropAuthData:               {CONNECT: {}, CONNACK: {}, AUTH: {}},
	PropRequestProblemInfo:     {CONNECT: {}},
	PropWillDelayInterval:      {CONNECT: {}},
	PropRequestResponseInfo:    {CONNECT: {}},
	PropResponseInfo:           {CONNACK: {}},
	PropServerReference:        {CONNACK: {}, DISCONNECT: {}},
	PropReasonString:           {CONNACK: {}, PUBACK: {}, PUBREC: {}, PUBREL: {}, PUBCOMP: {}, SUBACK: {}, UNSUBACK: {}, DISCONNECT: {}, AUTH: {}},
	PropReceiveMaximum:         {CONNECT: {}, CONNACK: {}},
	PropTopicAliasMaximum:      {CONNECT: {}, CONNACK: {}},
	PropTopicAlias:             {PUBLISH: {}},
	PropMaximumQoS:             {CONNACK: {}},
	PropRetainAvailable:        {CONNACK: {}},
	PropUser:                   {CONNECT: {}, CONNACK: {}, PUBLISH: {}, PUBACK: {}, PUBREC: {}, PUBREL: {}, PUBCOMP: {}, SUBSCRIBE: {}, UNSUBSCRIBE: {}, SUBACK: {}, UNSUBACK: {}, DISCONNECT: {}, AUTH: {}},
	PropMaximumPacketSize:      {CONNECT: {}, CONNACK: {}},
	PropWildcardSubAvailable:   {CONNACK: {}},
	PropSubIDAvailable:         {CONNACK: {}},
	PropSharedSubAvailable:     {CONNACK: {}},
}

// ValidateID takes a PacketType and a property name and returns
// a boolean indicating if that property is valid for that
// PacketType
func ValidateID(packetType byte, i byte) bool {
	_, ok := ValidProperties[i][packetType]
	return ok
}

func ValidateCode(packType byte, code byte) bool {
	return true
}

func propertyReadBool(i *byte, r *bytes.Buffer, propType byte) (*byte, error) {
	if i != nil {
		return nil, codes.ErrProtocol
	}
	o, err := r.ReadByte()
	if err != nil {
		return nil, codes.ErrMalformed
	}
	if o != 0 && o != 1 {
		return nil, codes.ErrProtocol
	}
	return &o, nil
}

func propertyReadUint32(i *uint32, r *bytes.Buffer, propType byte,
	validate func(u uint32) bool) (*uint32, error) {
	if i != nil {
		return nil, errMorethanOnce(propType)
	}
	o, err := readUint32(r)
	if err != nil {
		return nil, codes.ErrMalformed
	}
	if validate != nil {
		if !validate(o) {
			return nil, codes.ErrProtocol
		}
	}
	return &o, nil
}

func propertyReadUint16(i *uint16, r *bytes.Buffer, propType byte, validate func(u uint16) bool) (*uint16, error) {
	if i != nil {
		return nil, codes.ErrProtocol
	}
	o, err := readUint16(r)
	if err != nil {
		return nil, codes.ErrMalformed
	}
	if validate != nil {
		if !validate(o) {
			return nil, codes.ErrProtocol
		}
	}
	return &o, nil
}
func propertyReadUTF8String(i []byte, r *bytes.Buffer, propType byte, validate func(u []byte) bool) (b []byte, err error) {
	if i != nil {
		return nil, errMorethanOnce(propType)
	}
	o, err := readUTF8String(true, r)
	if err != nil {
		return nil, err
	}
	if validate != nil {
		if !validate(o) {
			return nil, codes.ErrProtocol
		}
	}
	return o, nil
}
func propertyReadBinary(i []byte, r *bytes.Buffer, propType byte,
	validate func(u []byte) bool) (b []byte, err error) {
	if i != nil {
		return nil, errMorethanOnce(propType)
	}
	o, err := readUTF8String(false, r)
	if err != nil {
		return nil, codes.ErrMalformed
	}
	if validate != nil {
		if !validate(o) {
			return nil, codes.ErrProtocol
		}
	}
	return o, nil
}

func propertyWriteByte(t byte, i *byte, w *bytes.Buffer) {
	if i != nil {
		w.Write([]byte{t, *i})
	}
}

func propertyWriteUint16(t byte, i *uint16, w *bytes.Buffer) {
	if i != nil {
		w.WriteByte(t)
		writeUint16(w, *i)
	}
}
func propertyWriteUint32(t byte, i *uint32, w *bytes.Buffer) {
	if i != nil {
		w.WriteByte(t)
		writeUint32(w, *i)
	}
}

func propertyWriteString(t byte, i []byte, w *bytes.Buffer) {
	if i != nil {
		w.WriteByte(t)
		writeBinary(w, i)
	}
}
