package protocol

import (
	"fmt"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

// Tests taken and extended from chrome
var _ = Describe("packet number calculation", func() {
	It("InvalidPacketNumber is smaller than all valid packet numbers", func() {
		Expect(InvalidPacketNumber).To(BeNumerically("<", 0))
	})

	It("works with the example from the draft", func() {
		Expect(DecodePacketNumber(PacketNumberLen2, 0xa82f30ea, 0x9b32)).To(Equal(PacketNumber(0xa82f9b32)))
	})

	It("works with the examples from the draft", func() {
		Expect(GetPacketNumberLengthForHeader(0xac5c02, 0xabe8b3)).To(Equal(PacketNumberLen2))
		Expect(GetPacketNumberLengthForHeader(0xace8fe, 0xabe8b3)).To(Equal(PacketNumberLen3))
	})

	getEpoch := func(len PacketNumberLen) uint64 {
		if len > 4 {
			Fail("invalid packet number len")
		}
		return uint64(1) << (len * 8)
	}

	check := func(length PacketNumberLen, expected, last uint64) {
		epoch := getEpoch(length)
		epochMask := epoch - 1
		wirePacketNumber := expected & epochMask
		ExpectWithOffset(1, DecodePacketNumber(length, PacketNumber(last), PacketNumber(wirePacketNumber))).To(Equal(PacketNumber(expected)))
	}

	for _, l := range []PacketNumberLen{PacketNumberLen1, PacketNumberLen2, PacketNumberLen3, PacketNumberLen4} {
		length := l

		Context(fmt.Sprintf("with %d bytes", length), func() {
			epoch := getEpoch(length)
			epochMask := epoch - 1

			It("works near epoch start", func() {
				// A few quick manual sanity check
				check(length, 1, 0)
				check(length, epoch+1, epochMask)
				check(length, epoch, epochMask)

				// Cases where the last number was close to the start of the range.
				for last := uint64(0); last < 10; last++ {
					// Small numbers should not wrap (even if they're out of order).
					for j := uint64(0); j < 10; j++ {
						check(length, j, last)
					}

					// Large numbers should not wrap either (because we're near 0 already).
					for j := uint64(0); j < 10; j++ {
						check(length, epoch-1-j, last)
					}
				}
			})

			It("works near epoch end", func() {
				// Cases where the last number was close to the end of the range
				for i := uint64(0); i < 10; i++ {
					last := epoch - i

					// Small numbers should wrap.
					for j := uint64(0); j < 10; j++ {
						check(length, epoch+j, last)
					}

					// Large numbers should not (even if they're out of order).
					for j := uint64(0); j < 10; j++ {
						check(length, epoch-1-j, last)
					}
				}
			})

			// Next check where we're in a non-zero epoch to verify we handle
			// reverse wrapping, too.
			It("works near previous epoch", func() {
				prevEpoch := 1 * epoch
				curEpoch := 2 * epoch
				// Cases where the last number was close to the start of the range
				for i := uint64(0); i < 10; i++ {
					last := curEpoch + i
					// Small number should not wrap (even if they're out of order).
					for j := uint64(0); j < 10; j++ {
						check(length, curEpoch+j, last)
					}

					// But large numbers should reverse wrap.
					for j := uint64(0); j < 10; j++ {
						num := epoch - 1 - j
						check(length, prevEpoch+num, last)
					}
				}
			})

			It("works near next epoch", func() {
				curEpoch := 2 * epoch
				nextEpoch := 3 * epoch
				// Cases where the last number was close to the end of the range
				for i := uint64(0); i < 10; i++ {
					last := nextEpoch - 1 - i

					// Small numbers should wrap.
					for j := uint64(0); j < 10; j++ {
						check(length, nextEpoch+j, last)
					}

					// but large numbers should not (even if they're out of order).
					for j := uint64(0); j < 10; j++ {
						num := epoch - 1 - j
						check(length, curEpoch+num, last)
					}
				}
			})

			Context("shortening a packet number for the header", func() {
				Context("shortening", func() {
					It("sends out low packet numbers as 2 byte", func() {
						length := GetPacketNumberLengthForHeader(4, 2)
						Expect(length).To(Equal(PacketNumberLen2))
					})

					It("sends out high packet numbers as 2 byte, if all ACKs are received", func() {
						length := GetPacketNumberLengthForHeader(0xdeadbeef, 0xdeadbeef-1)
						Expect(length).To(Equal(PacketNumberLen2))
					})

					It("sends out higher packet numbers as 3 bytes, if a lot of ACKs are missing", func() {
						length := GetPacketNumberLengthForHeader(40000, 2)
						Expect(length).To(Equal(PacketNumberLen3))
					})

					It("sends out higher packet numbers as 4 bytes, if a lot of ACKs are missing", func() {
						length := GetPacketNumberLengthForHeader(40000000, 2)
						Expect(length).To(Equal(PacketNumberLen4))
					})
				})

				Context("self-consistency", func() {
					It("works for small packet numbers", func() {
						for i := uint64(1); i < 10000; i++ {
							packetNumber := PacketNumber(i)
							leastUnacked := PacketNumber(1)
							length := GetPacketNumberLengthForHeader(packetNumber, leastUnacked)
							wirePacketNumber := (uint64(packetNumber) << (64 - length*8)) >> (64 - length*8)

							decodedPacketNumber := DecodePacketNumber(length, leastUnacked, PacketNumber(wirePacketNumber))
							Expect(decodedPacketNumber).To(Equal(packetNumber))
						}
					})

					It("works for small packet numbers and increasing ACKed packets", func() {
						for i := uint64(1); i < 10000; i++ {
							packetNumber := PacketNumber(i)
							leastUnacked := PacketNumber(i / 2)
							length := GetPacketNumberLengthForHeader(packetNumber, leastUnacked)
							epochMask := getEpoch(length) - 1
							wirePacketNumber := uint64(packetNumber) & epochMask

							decodedPacketNumber := DecodePacketNumber(length, leastUnacked, PacketNumber(wirePacketNumber))
							Expect(decodedPacketNumber).To(Equal(packetNumber))
						}
					})

					It("also works for larger packet numbers", func() {
						var increment uint64
						for i := uint64(1); i < getEpoch(PacketNumberLen4); i += increment {
							packetNumber := PacketNumber(i)
							leastUnacked := PacketNumber(1)
							length := GetPacketNumberLengthForHeader(packetNumber, leastUnacked)
							epochMask := getEpoch(length) - 1
							wirePacketNumber := uint64(packetNumber) & epochMask

							decodedPacketNumber := DecodePacketNumber(length, leastUnacked, PacketNumber(wirePacketNumber))
							Expect(decodedPacketNumber).To(Equal(packetNumber))

							increment = getEpoch(length) / 8
						}
					})

					It("works for packet numbers larger than 2^48", func() {
						for i := (uint64(1) << 48); i < ((uint64(1) << 63) - 1); i += (uint64(1) << 48) {
							packetNumber := PacketNumber(i)
							leastUnacked := PacketNumber(i - 1000)
							length := GetPacketNumberLengthForHeader(packetNumber, leastUnacked)
							wirePacketNumber := (uint64(packetNumber) << (64 - length*8)) >> (64 - length*8)

							decodedPacketNumber := DecodePacketNumber(length, leastUnacked, PacketNumber(wirePacketNumber))
							Expect(decodedPacketNumber).To(Equal(packetNumber))
						}
					})
				})
			})
		})
	}
})
