package clients

import (
	"errors"
	"io"
	"io/ioutil"
	"net"
	"sync/atomic"
	"testing"
	"time"

	"github.com/mochi-co/mqtt/server/events"
	"github.com/mochi-co/mqtt/server/internal/circ"
	"github.com/mochi-co/mqtt/server/internal/packets"
	"github.com/mochi-co/mqtt/server/listeners/auth"
	"github.com/mochi-co/mqtt/server/system"
	"github.com/stretchr/testify/require"
)

var testClientStop = errors.New("test stop")

func genClient() *Client {
	c, _ := net.Pipe()
	return NewClient(c, circ.NewReader(128, 8), circ.NewWriter(128, 8), new(system.Info))
}

func TestNewClients(t *testing.T) {
	cl := New()
	require.NotNil(t, cl.internal)
}

func BenchmarkNewClients(b *testing.B) {
	for n := 0; n < b.N; n++ {
		New()
	}
}

func TestClientsAdd(t *testing.T) {
	cl := New()
	cl.Add(&Client{ID: "t1"})
	require.Contains(t, cl.internal, "t1")
}

func BenchmarkClientsAdd(b *testing.B) {
	cl := New()
	client := &Client{ID: "t1"}
	for n := 0; n < b.N; n++ {
		cl.Add(client)
	}
}

func TestClientsGet(t *testing.T) {
	cl := New()
	cl.Add(&Client{ID: "t1"})
	cl.Add(&Client{ID: "t2"})
	require.Contains(t, cl.internal, "t1")
	require.Contains(t, cl.internal, "t2")

	client, ok := cl.Get("t1")
	require.Equal(t, true, ok)
	require.Equal(t, "t1", client.ID)
}

func BenchmarkClientsGet(b *testing.B) {
	cl := New()
	cl.Add(&Client{ID: "t1"})
	for n := 0; n < b.N; n++ {
		cl.Get("t1")
	}
}

func TestClientsLen(t *testing.T) {
	cl := New()
	cl.Add(&Client{ID: "t1"})
	cl.Add(&Client{ID: "t2"})
	require.Contains(t, cl.internal, "t1")
	require.Contains(t, cl.internal, "t2")
	require.Equal(t, 2, cl.Len())
}

func BenchmarkClientsLen(b *testing.B) {
	cl := New()
	cl.Add(&Client{ID: "t1"})
	for n := 0; n < b.N; n++ {
		cl.Len()
	}
}

func TestClientsDelete(t *testing.T) {
	cl := New()
	cl.Add(&Client{ID: "t1"})
	require.Contains(t, cl.internal, "t1")

	cl.Delete("t1")
	_, ok := cl.Get("t1")
	require.Equal(t, false, ok)
	require.Nil(t, cl.internal["t1"])
}

func BenchmarkClientsDelete(b *testing.B) {
	cl := New()
	cl.Add(&Client{ID: "t1"})
	for n := 0; n < b.N; n++ {
		cl.Delete("t1")
	}
}

func TestClientsGetByListener(t *testing.T) {
	cl := New()
	cl.Add(&Client{ID: "t1", Listener: "tcp1"})
	cl.Add(&Client{ID: "t2", Listener: "ws1"})
	require.Contains(t, cl.internal, "t1")
	require.Contains(t, cl.internal, "t2")

	clients := cl.GetByListener("tcp1")
	require.NotEmpty(t, clients)
	require.Equal(t, 1, len(clients))
	require.Equal(t, "tcp1", clients[0].Listener)
}

func BenchmarkClientsGetByListener(b *testing.B) {
	cl := New()
	cl.Add(&Client{ID: "t1", Listener: "tcp1"})
	cl.Add(&Client{ID: "t2", Listener: "ws1"})
	for n := 0; n < b.N; n++ {
		cl.GetByListener("tcp1")
	}
}

func TestNewClient(t *testing.T) {
	cl := genClient()

	require.NotNil(t, cl)
	require.NotNil(t, cl.Inflight.internal)
	require.NotNil(t, cl.Subscriptions)
	require.NotNil(t, cl.R)
	require.NotNil(t, cl.W)
	require.Nil(t, cl.StopCause())
}

func TestClientInfoUnknown(t *testing.T) {
	cl := genClient()
	cl.ID = "testid"
	cl.Listener = "testlistener"
	cl.conn = nil

	require.Equal(t, events.Client{
		ID:       "testid",
		Remote:   "unknown",
		Listener: "testlistener",
	}, cl.Info())
}

func TestClientInfoKnown(t *testing.T) {
	c1, c2 := net.Pipe()
	defer c1.Close()
	defer c2.Close()

	cl := genClient()
	cl.ID = "ID"
	cl.Listener = "L"
	cl.conn = c1

	require.Equal(t, events.Client{
		ID:       "ID",
		Remote:   c1.RemoteAddr().String(),
		Listener: "L",
	}, cl.Info())
}

func BenchmarkNewClient(b *testing.B) {
	c, _ := net.Pipe()
	for n := 0; n < b.N; n++ {
		NewClient(c, circ.NewReader(16, 4), circ.NewWriter(16, 4), nil)
	}
}

func TestNewClientStub(t *testing.T) {
	cl := NewClientStub(nil)

	require.NotNil(t, cl)
	require.NotNil(t, cl.Inflight.internal)
	require.NotNil(t, cl.Subscriptions)
}

func BenchmarkNewClientStub(b *testing.B) {
	for n := 0; n < b.N; n++ {
		NewClientStub(nil)
	}
}

func TestClientIdentify(t *testing.T) {
	cl := genClient()

	pk := packets.Packet{
		FixedHeader: packets.FixedHeader{
			Type:      packets.Connect,
			Remaining: 16,
		},
		ProtocolName:     []byte{'M', 'Q', 'T', 'T'},
		ProtocolVersion:  4,
		CleanSession:     true,
		Keepalive:        60,
		ClientIdentifier: "mochi",
	}

	cl.Identify("tcp1", pk, new(auth.Allow))
	require.Equal(t, pk.Keepalive, cl.keepalive)
	require.Equal(t, pk.CleanSession, cl.CleanSession)
	require.Equal(t, pk.ClientIdentifier, cl.ID)
}

func BenchmarkClientIdentify(b *testing.B) {
	cl := genClient()

	pk := packets.Packet{
		FixedHeader: packets.FixedHeader{
			Type:      packets.Connect,
			Remaining: 16,
		},
		ProtocolName:     []byte{'M', 'Q', 'T', 'T'},
		ProtocolVersion:  4,
		CleanSession:     true,
		Keepalive:        60,
		ClientIdentifier: "mochi",
	}

	for n := 0; n < b.N; n++ {
		cl.Identify("tcp1", pk, new(auth.Allow))
	}
}

func TestClientIdentifyNoID(t *testing.T) {
	cl := genClient()

	pk := packets.Packet{
		FixedHeader: packets.FixedHeader{
			Type:      packets.Connect,
			Remaining: 16,
		},
		ProtocolName:    []byte{'M', 'Q', 'T', 'T'},
		ProtocolVersion: 4,
		CleanSession:    true,
		Keepalive:       60,
	}

	cl.Identify("tcp1", pk, new(auth.Allow))
	require.NotEmpty(t, cl.ID)
}

func TestClientIdentifyLWT(t *testing.T) {
	cl := genClient()

	pk := packets.Packet{
		FixedHeader: packets.FixedHeader{
			Type:      packets.Connect,
			Remaining: 16,
		},
		ProtocolName:     []byte{'M', 'Q', 'T', 'T'},
		ProtocolVersion:  4,
		CleanSession:     true,
		Keepalive:        60,
		ClientIdentifier: "mochi",
		WillFlag:         true,
		WillTopic:        "lwt",
		WillMessage:      []byte("lol gg"),
		WillQos:          1,
		WillRetain:       false,
	}

	cl.Identify("tcp1", pk, new(auth.Allow))
	require.Equal(t, pk.WillTopic, cl.LWT.Topic)
	require.Equal(t, pk.WillMessage, cl.LWT.Message)
	require.Equal(t, pk.WillQos, cl.LWT.Qos)
	require.Equal(t, pk.WillRetain, cl.LWT.Retain)
}

func TestClientNextPacketID(t *testing.T) {
	cl := genClient()

	require.Equal(t, uint32(1), cl.NextPacketID())
	require.Equal(t, uint32(2), cl.NextPacketID())

	cl.packetID = uint32(65534)
	require.Equal(t, uint32(65535), cl.NextPacketID())
	require.Equal(t, uint32(1), cl.NextPacketID())
}

func BenchmarkClientNextPacketID(b *testing.B) {
	cl := genClient()

	for n := 0; n < b.N; n++ {
		cl.NextPacketID()
	}
}

func TestClientNoteSubscription(t *testing.T) {
	cl := genClient()

	cl.NoteSubscription("a/b/c", 0)
	require.Contains(t, cl.Subscriptions, "a/b/c")
	require.Equal(t, byte(0), cl.Subscriptions["a/b/c"])
}

func BenchmarkClientNoteSubscription(b *testing.B) {
	cl := genClient()
	for n := 0; n < b.N; n++ {
		cl.NoteSubscription("a/b/c", 0)
	}
}

func TestClientForgetSubscription(t *testing.T) {
	cl := genClient()
	require.NotNil(t, cl)
	cl.Subscriptions = map[string]byte{
		"a/b/c/": 1,
	}
	cl.ForgetSubscription("a/b/c/")
	require.Empty(t, cl.Subscriptions["a/b/c"])
}

func BenchmarkClientForgetSubscription(b *testing.B) {
	cl := genClient()
	for n := 0; n < b.N; n++ {
		cl.NoteSubscription("a/b/c", 0)
		cl.ForgetSubscription("a/b/c/")
	}
}

func TestClientRefreshDeadline(t *testing.T) {
	cl := genClient()
	cl.refreshDeadline(10)

	// How do we check net.Conn deadline?
	require.NotNil(t, cl.conn)
}

func BenchmarkClientRefreshDeadline(b *testing.B) {
	cl := genClient()
	for n := 0; n < b.N; n++ {
		cl.refreshDeadline(10)
	}
}

func TestClientStart(t *testing.T) {
	cl := genClient()
	cl.Start()
	defer cl.Stop(testClientStop)
	time.Sleep(time.Millisecond)
	require.Equal(t, uint32(1), atomic.LoadUint32(&cl.R.State))
	require.Equal(t, uint32(2), atomic.LoadUint32(&cl.W.State))
}

func BenchmarkClientStart(b *testing.B) {
	cl := genClient()
	defer cl.Stop(testClientStop)

	for n := 0; n < b.N; n++ {
		cl.Start()
	}
}

func TestClientReadFixedHeader(t *testing.T) {
	cl := genClient()
	cl.Start()
	defer cl.Stop(testClientStop)

	cl.R.Set([]byte{packets.Connect << 4, 0x00}, 0, 2)
	cl.R.SetPos(0, 2)

	fh := new(packets.FixedHeader)
	err := cl.ReadFixedHeader(fh)
	require.NoError(t, err)
	require.Equal(t, int64(2), atomic.LoadInt64(&cl.systemInfo.BytesRecv))

	tail, head := cl.R.GetPos()
	require.Equal(t, int64(2), tail)
	require.Equal(t, int64(2), head)

}

func TestClientReadFixedHeaderDecodeError(t *testing.T) {
	cl := genClient()
	cl.Start()
	defer cl.Stop(testClientStop)

	o := make(chan error)
	go func() {
		fh := new(packets.FixedHeader)
		cl.R.Set([]byte{packets.Connect<<4 | 1<<1, 0x00, 0x00}, 0, 2)
		cl.R.SetPos(0, 2)
		o <- cl.ReadFixedHeader(fh)
	}()
	time.Sleep(time.Millisecond)
	require.Error(t, <-o)
}

func TestClientReadFixedHeaderReadEOF(t *testing.T) {
	cl := genClient()
	cl.Start()
	defer cl.Stop(testClientStop)

	o := make(chan error)
	go func() {
		fh := new(packets.FixedHeader)
		cl.R.Set([]byte{packets.Connect << 4, 0x00}, 0, 2)
		cl.R.SetPos(0, 1)
		o <- cl.ReadFixedHeader(fh)
	}()
	time.Sleep(time.Millisecond)
	cl.R.Stop()
	err := <-o
	require.Error(t, err)
	require.Equal(t, io.EOF, err)
}

func TestClientReadFixedHeaderNoLengthTerminator(t *testing.T) {
	cl := genClient()
	cl.Start()
	defer cl.Stop(testClientStop)

	o := make(chan error)
	go func() {
		fh := new(packets.FixedHeader)
		err := cl.R.Set([]byte{packets.Connect << 4, 0xd5, 0x86, 0xf9, 0x9e, 0x01}, 0, 5)
		require.NoError(t, err)
		cl.R.SetPos(0, 5)
		o <- cl.ReadFixedHeader(fh)
	}()
	time.Sleep(time.Millisecond)
	require.Error(t, <-o)
}

func TestClientReadOK(t *testing.T) {
	cl := genClient()
	cl.Start()
	defer cl.Stop(testClientStop)

	// Two packets in a row...
	b := []byte{
		byte(packets.Publish << 4), 18, // Fixed header
		0, 5, // Topic Name - LSB+MSB
		'a', '/', 'b', '/', 'c', // Topic Name
		'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', 'i', // Payload,
		byte(packets.Publish << 4), 11, // Fixed header
		0, 5, // Topic Name - LSB+MSB
		'd', '/', 'e', '/', 'f', // Topic Name
		'y', 'e', 'a', 'h', // Payload
	}

	err := cl.R.Set(b, 0, len(b))
	require.NoError(t, err)
	cl.R.SetPos(0, int64(len(b)))

	o := make(chan error)
	var pks []packets.Packet
	go func() {
		o <- cl.Read(func(cl *Client, pk packets.Packet) error {
			pks = append(pks, pk)
			return nil
		})
	}()

	time.Sleep(time.Millisecond)
	cl.R.Stop()

	err = <-o
	require.Error(t, err)
	require.Equal(t, io.EOF, err)
	require.Equal(t, 2, len(pks))
	require.Equal(t, pks, []packets.Packet{
		{
			FixedHeader: packets.FixedHeader{
				Type:      packets.Publish,
				Remaining: 18,
			},
			TopicName: "a/b/c",
			Payload:   []byte("hello mochi"),
		},
		{
			FixedHeader: packets.FixedHeader{
				Type:      packets.Publish,
				Remaining: 11,
			},
			TopicName: "d/e/f",
			Payload:   []byte("yeah"),
		},
	})

	require.Equal(t, int64(len(b)), atomic.LoadInt64(&cl.systemInfo.BytesRecv))
	require.Equal(t, int64(2), atomic.LoadInt64(&cl.systemInfo.MessagesRecv))

}

func TestClientClearBuffers(t *testing.T) {
	cl := genClient()
	cl.Start()
	cl.Stop(testClientStop)
	cl.ClearBuffers()

	require.Nil(t, cl.W)
	require.Nil(t, cl.R)
}

func TestClientReadDone(t *testing.T) {
	cl := genClient()
	cl.Start()
	defer cl.Stop(testClientStop)
	cl.State.Done = 1

	err := cl.Read(func(cl *Client, pk packets.Packet) error {
		return nil
	})

	require.NoError(t, err)
}

func TestClientReadPacketError(t *testing.T) {
	cl := genClient()
	cl.Start()
	defer cl.Stop(testClientStop)

	b := []byte{
		0, 18,
		0, 5,
		'a', '/', 'b', '/', 'c',
		'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', 'i',
	}
	err := cl.R.Set(b, 0, len(b))
	require.NoError(t, err)
	cl.R.SetPos(0, int64(len(b)))

	o := make(chan error)
	go func() {
		o <- cl.Read(func(cl *Client, pk packets.Packet) error {
			return nil
		})
	}()

	require.Error(t, <-o)
}

func TestClientReadPacketEOF(t *testing.T) {
	cl := genClient()
	cl.Start()

	b := []byte{
		0, 18,
		0, 5,
		'a', '/', 'b', '/', 'c',
		'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', // missing 1 byte
	}
	err := cl.R.Set(b, 0, len(b))
	require.NoError(t, err)
	cl.R.SetPos(0, int64(len(b)))

	o := make(chan error)
	go func() {
		o <- cl.Read(func(cl *Client, pk packets.Packet) error {
			return nil
		})
	}()

	cl.R.Stop()
	cl.Stop(testClientStop)
	require.Error(t, <-o)
	require.True(t, errors.Is(cl.StopCause(), testClientStop))
}

func TestClientReadHandlerErr(t *testing.T) {
	cl := genClient()
	cl.Start()
	defer cl.Stop(testClientStop)

	b := []byte{
		byte(packets.Publish << 4), 11, // Fixed header
		0, 5, // Topic Name - LSB+MSB
		'd', '/', 'e', '/', 'f', // Topic Name
		'y', 'e', 'a', 'h', // Payload
	}

	err := cl.R.Set(b, 0, len(b))
	require.NoError(t, err)
	cl.R.SetPos(0, int64(len(b)))

	err = cl.Read(func(cl *Client, pk packets.Packet) error {
		return errors.New("test")
	})

	require.Error(t, err)
}

func TestClientReadPacketOK(t *testing.T) {
	cl := genClient()
	cl.Start()
	defer cl.Stop(testClientStop)

	err := cl.R.Set([]byte{
		byte(packets.Publish << 4), 11, // Fixed header
		0, 5,
		'd', '/', 'e', '/', 'f',
		'y', 'e', 'a', 'h',
	}, 0, 13)
	require.NoError(t, err)
	cl.R.SetPos(0, 13)

	fh := new(packets.FixedHeader)
	err = cl.ReadFixedHeader(fh)
	require.NoError(t, err)

	pk, err := cl.ReadPacket(fh)
	require.NoError(t, err)
	require.NotNil(t, pk)

	require.Equal(t, packets.Packet{
		FixedHeader: packets.FixedHeader{
			Type:      packets.Publish,
			Remaining: 11,
		},
		TopicName: "d/e/f",
		Payload:   []byte("yeah"),
	}, pk)
}

func TestClientReadPacket(t *testing.T) {
	cl := genClient()
	cl.Start()
	defer cl.Stop(testClientStop)

	for i, tt := range pkTable {
		err := cl.R.Set(tt.bytes, 0, len(tt.bytes))
		require.NoError(t, err)
		cl.R.SetPos(0, int64(len(tt.bytes)))

		fh := new(packets.FixedHeader)
		err = cl.ReadFixedHeader(fh)
		require.NoError(t, err)

		pk, err := cl.ReadPacket(fh)
		require.NoError(t, err)
		require.NotNil(t, pk)

		require.Equal(t, tt.packet, pk, "Mismatched packet: [i:%d] %d", i, tt.bytes[0])
		if tt.packet.FixedHeader.Type == packets.Publish {
			require.Equal(t, int64(1), atomic.LoadInt64(&cl.systemInfo.PublishRecv))
		}
	}
}

func TestClientReadPacketReadingError(t *testing.T) {
	cl := genClient()
	cl.Start()
	defer cl.Stop(testClientStop)

	err := cl.R.Set([]byte{
		0, 11, // Fixed header
		0, 5,
		'd', '/', 'e', '/', 'f',
		'y', 'e', 'a', 'h',
	}, 0, 13)
	require.NoError(t, err)
	cl.R.SetPos(2, 13)

	_, err = cl.ReadPacket(&packets.FixedHeader{
		Type:      0,
		Remaining: 11,
	})
	require.Error(t, err)
}

func TestClientReadPacketReadError(t *testing.T) {
	cl := genClient()
	cl.Start()
	defer cl.Stop(testClientStop)
	cl.R.Stop()

	_, err := cl.ReadPacket(&packets.FixedHeader{
		Remaining: 1,
	})
	require.Error(t, err)
	require.Equal(t, io.EOF, err)
}

func TestClientReadPacketReadUnknown(t *testing.T) {
	cl := genClient()
	cl.Start()
	defer cl.Stop(testClientStop)
	cl.R.Stop()

	_, err := cl.ReadPacket(&packets.FixedHeader{
		Remaining: 1,
	})
	require.Error(t, err)
}

func TestClientWritePacket(t *testing.T) {
	for i, tt := range pkTable {
		r, w := net.Pipe()
		cl := NewClient(r, circ.NewReader(128, 8), circ.NewWriter(128, 8), new(system.Info))
		cl.Start()

		o := make(chan []byte)
		go func() {
			buf, err := ioutil.ReadAll(w)
			require.NoError(t, err)
			o <- buf
		}()

		n, err := cl.WritePacket(tt.packet)
		require.NoError(t, err, "Error [i:%d] %d", i, tt.packet)
		require.Equal(t, len(tt.bytes), n, "Mismatched written [i:%d] %d", i, tt.packet)

		time.Sleep(2 * time.Millisecond)
		r.Close()

		require.Equal(t, tt.bytes, <-o, "Mismatched packet: [i:%d] %d", i, tt.bytes[0])
		cl.Stop(testClientStop)

		// The stop cause is either the test error, EOF, or a
		// closed pipe, depending on which goroutine runs first.
		err = cl.StopCause()
		time.Sleep(time.Millisecond * 5)
		require.True(t,
			errors.Is(err, testClientStop) ||
				errors.Is(err, io.EOF) ||
				errors.Is(err, io.ErrClosedPipe))

		require.Equal(t, int64(n), atomic.LoadInt64(&cl.systemInfo.BytesSent))
		require.Equal(t, int64(1), atomic.LoadInt64(&cl.systemInfo.MessagesSent))
		if tt.packet.FixedHeader.Type == packets.Publish {
			require.Equal(t, int64(1), atomic.LoadInt64(&cl.systemInfo.PublishSent))
		}
	}
}

func TestClientWritePacketWriteNoConn(t *testing.T) {
	c, _ := net.Pipe()
	cl := NewClient(c, circ.NewReader(16, 4), circ.NewWriter(16, 4), new(system.Info))
	cl.W.SetPos(0, 16)
	cl.Stop(testClientStop)

	_, err := cl.WritePacket(pkTable[1].packet)
	require.Error(t, err)
	require.Equal(t, ErrConnectionClosed, err)
}

func TestClientWritePacketWriteError(t *testing.T) {
	c, _ := net.Pipe()
	cl := NewClient(c, circ.NewReader(16, 4), circ.NewWriter(16, 4), new(system.Info))
	cl.W.SetPos(0, 16)
	cl.W.Stop()

	_, err := cl.WritePacket(pkTable[1].packet)
	require.Error(t, err)
}

func TestClientWritePacketInvalidPacket(t *testing.T) {
	c, _ := net.Pipe()
	cl := NewClient(c, circ.NewReader(16, 4), circ.NewWriter(16, 4), new(system.Info))
	cl.Start()

	_, err := cl.WritePacket(packets.Packet{})
	require.Error(t, err)
}

/////

func TestInflightSet(t *testing.T) {
	cl := genClient()
	q := cl.Inflight.Set(1, InflightMessage{Packet: packets.Packet{}, Sent: 0})
	require.Equal(t, true, q)
	require.NotNil(t, cl.Inflight.internal[1])
	require.NotEqual(t, 0, cl.Inflight.internal[1].Sent)

	q = cl.Inflight.Set(1, InflightMessage{Packet: packets.Packet{}, Sent: 0})
	require.Equal(t, false, q)
}

func BenchmarkInflightSet(b *testing.B) {
	cl := genClient()
	in := InflightMessage{Packet: packets.Packet{}, Sent: 0}
	for n := 0; n < b.N; n++ {
		cl.Inflight.Set(1, in)
	}
}

func TestInflightGet(t *testing.T) {
	cl := genClient()
	cl.Inflight.Set(2, InflightMessage{Packet: packets.Packet{}, Sent: 0})

	msg, ok := cl.Inflight.Get(2)
	require.Equal(t, true, ok)
	require.NotEqual(t, 0, msg.Sent)
}

func BenchmarkInflightGet(b *testing.B) {
	cl := genClient()
	cl.Inflight.Set(2, InflightMessage{Packet: packets.Packet{}, Sent: 0})
	for n := 0; n < b.N; n++ {
		cl.Inflight.Get(2)
	}
}

func TestInflightGetAll(t *testing.T) {
	cl := genClient()
	cl.Inflight.Set(2, InflightMessage{})

	m := cl.Inflight.GetAll()
	o := map[uint16]InflightMessage{
		2: {},
	}
	require.Equal(t, o, m)
}

func BenchmarkInflightGetAll(b *testing.B) {
	cl := genClient()
	cl.Inflight.Set(2, InflightMessage{Packet: packets.Packet{}, Sent: 0})
	for n := 0; n < b.N; n++ {
		cl.Inflight.Get(2)
	}
}

func TestInflightLen(t *testing.T) {
	cl := genClient()
	cl.Inflight.Set(2, InflightMessage{Packet: packets.Packet{}, Sent: 0})
	require.Equal(t, 1, cl.Inflight.Len())
}

func BenchmarkInflightLen(b *testing.B) {
	cl := genClient()
	cl.Inflight.Set(2, InflightMessage{Packet: packets.Packet{}, Sent: 0})
	for n := 0; n < b.N; n++ {
		cl.Inflight.Len()
	}
}

func TestInflightDelete(t *testing.T) {
	cl := genClient()
	cl.Inflight.Set(3, InflightMessage{Packet: packets.Packet{}, Sent: 0})
	require.NotNil(t, cl.Inflight.internal[3])

	q := cl.Inflight.Delete(3)
	require.Equal(t, true, q)
	require.Equal(t, int64(0), cl.Inflight.internal[3].Sent)

	_, ok := cl.Inflight.Get(3)
	require.Equal(t, false, ok)

	q = cl.Inflight.Delete(3)
	require.Equal(t, false, q)
}

func BenchmarkInflightDelete(b *testing.B) {
	cl := genClient()
	for n := 0; n < b.N; n++ {
		cl.Inflight.Set(4, InflightMessage{Packet: packets.Packet{}, Sent: 0})
		cl.Inflight.Delete(4)
	}
}

var (
	pkTable = []struct {
		bytes  []byte
		packet packets.Packet
	}{
		{
			bytes: []byte{
				byte(packets.Connect << 4), 16, // Fixed header
				0, 4, // Protocol Name - MSB+LSB
				'M', 'Q', 'T', 'T', // Protocol Name
				4,     // Protocol Version
				0,     // Packet Flags
				0, 60, // Keepalive
				0, 4, // Client ID - MSB+LSB
				'z', 'e', 'n', '3',
			},
			packet: packets.Packet{
				FixedHeader: packets.FixedHeader{
					Type:      packets.Connect,
					Remaining: 16,
				},
				ProtocolName:     []byte("MQTT"),
				ProtocolVersion:  4,
				CleanSession:     false,
				Keepalive:        60,
				ClientIdentifier: "zen3",
			},
		},
		{
			bytes: []byte{
				byte(packets.Connack << 4), 2,
				0,
				packets.Accepted,
			},
			packet: packets.Packet{
				FixedHeader: packets.FixedHeader{
					Type:      packets.Connack,
					Remaining: 2,
				},
				SessionPresent: false,
				ReturnCode:     packets.Accepted,
			},
		},
		{
			bytes: []byte{
				byte(packets.Publish << 4), 18,
				0, 5,
				'a', '/', 'b', '/', 'c',
				'h', 'e', 'l', 'l', 'o', ' ', 'm', 'o', 'c', 'h', 'i',
			},
			packet: packets.Packet{
				FixedHeader: packets.FixedHeader{
					Type:      packets.Publish,
					Remaining: 18,
				},
				TopicName: "a/b/c",
				Payload:   []byte("hello mochi"),
			},
		},
		{
			bytes: []byte{
				byte(packets.Puback << 4), 2, // Fixed header
				0, 11, // Packet ID - LSB+MSB
			},
			packet: packets.Packet{
				FixedHeader: packets.FixedHeader{
					Type:      packets.Puback,
					Remaining: 2,
				},
				PacketID: 11,
			},
		},
		{
			bytes: []byte{
				byte(packets.Pubrec << 4), 2, // Fixed header
				0, 12, // Packet ID - LSB+MSB
			},
			packet: packets.Packet{
				FixedHeader: packets.FixedHeader{
					Type:      packets.Pubrec,
					Remaining: 2,
				},
				PacketID: 12,
			},
		},
		{
			bytes: []byte{
				byte(packets.Pubrel<<4) | 2, 2, // Fixed header
				0, 12, // Packet ID - LSB+MSB
			},
			packet: packets.Packet{
				FixedHeader: packets.FixedHeader{
					Type:      packets.Pubrel,
					Remaining: 2,
					Qos:       1,
				},
				PacketID: 12,
			},
		},
		{
			bytes: []byte{
				byte(packets.Pubcomp << 4), 2, // Fixed header
				0, 14, // Packet ID - LSB+MSB
			},
			packet: packets.Packet{
				FixedHeader: packets.FixedHeader{
					Type:      packets.Pubcomp,
					Remaining: 2,
				},
				PacketID: 14,
			},
		},
		{
			bytes: []byte{
				byte(packets.Subscribe << 4), 30, // Fixed header
				0, 15, // Packet ID - LSB+MSB

				0, 3, // Topic Name - LSB+MSB
				'a', '/', 'b', // Topic Name
				0, // QoS

				0, 11, // Topic Name - LSB+MSB
				'd', '/', 'e', '/', 'f', '/', 'g', '/', 'h', '/', 'i', // Topic Name
				1, // QoS

				0, 5, // Topic Name - LSB+MSB
				'x', '/', 'y', '/', 'z', // Topic Name
				2, // QoS
			},
			packet: packets.Packet{
				FixedHeader: packets.FixedHeader{
					Type:      packets.Subscribe,
					Remaining: 30,
				},
				PacketID: 15,
				Topics: []string{
					"a/b",
					"d/e/f/g/h/i",
					"x/y/z",
				},
				Qoss: []byte{0, 1, 2},
			},
		},
		{
			bytes: []byte{
				byte(packets.Suback << 4), 6, // Fixed header
				0, 17, // Packet ID - LSB+MSB
				0,    // Return Code QoS 0
				1,    // Return Code QoS 1
				2,    // Return Code QoS 2
				0x80, // Return Code fail
			},
			packet: packets.Packet{
				FixedHeader: packets.FixedHeader{
					Type:      packets.Suback,
					Remaining: 6,
				},
				PacketID:    17,
				ReturnCodes: []byte{0, 1, 2, 0x80},
			},
		},
		{
			bytes: []byte{
				byte(packets.Unsubscribe << 4), 27, // Fixed header
				0, 35, // Packet ID - LSB+MSB

				0, 3, // Topic Name - LSB+MSB
				'a', '/', 'b', // Topic Name

				0, 11, // Topic Name - LSB+MSB
				'd', '/', 'e', '/', 'f', '/', 'g', '/', 'h', '/', 'i', // Topic Name

				0, 5, // Topic Name - LSB+MSB
				'x', '/', 'y', '/', 'z', // Topic Name
			},
			packet: packets.Packet{
				FixedHeader: packets.FixedHeader{
					Type:      packets.Unsubscribe,
					Remaining: 27,
				},
				PacketID: 35,
				Topics: []string{
					"a/b",
					"d/e/f/g/h/i",
					"x/y/z",
				},
			},
		},
		{
			bytes: []byte{
				byte(packets.Unsuback << 4), 2, // Fixed header
				0, 37, // Packet ID - LSB+MSB

			},
			packet: packets.Packet{
				FixedHeader: packets.FixedHeader{
					Type:      packets.Unsuback,
					Remaining: 2,
				},
				PacketID: 37,
			},
		},
		{
			bytes: []byte{
				byte(packets.Pingreq << 4), 0, // fixed header
			},
			packet: packets.Packet{
				FixedHeader: packets.FixedHeader{
					Type:      packets.Pingreq,
					Remaining: 0,
				},
			},
		},
		{
			bytes: []byte{
				byte(packets.Pingresp << 4), 0, // fixed header
			},
			packet: packets.Packet{
				FixedHeader: packets.FixedHeader{
					Type:      packets.Pingresp,
					Remaining: 0,
				},
			},
		},
		{
			bytes: []byte{
				byte(packets.Disconnect << 4), 0, // fixed header
			},
			packet: packets.Packet{
				FixedHeader: packets.FixedHeader{
					Type:      packets.Disconnect,
					Remaining: 0,
				},
			},
		},
	}
)
