package protocol

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Version", func() {
	isReservedVersion := func(v VersionNumber) bool {
		return v&0x0f0f0f0f == 0x0a0a0a0a
	}

	It("says if a version is valid", func() {
		Expect(IsValidVersion(VersionTLS)).To(BeTrue())
		Expect(IsValidVersion(VersionWhatever)).To(BeFalse())
		Expect(IsValidVersion(VersionUnknown)).To(BeFalse())
		Expect(IsValidVersion(VersionDraft29)).To(BeTrue())
		Expect(IsValidVersion(VersionDraft32)).To(BeTrue())
		Expect(IsValidVersion(1234)).To(BeFalse())
	})

	It("versions don't have reserved version numbers", func() {
		Expect(isReservedVersion(VersionTLS)).To(BeFalse())
	})

	It("has the right string representation", func() {
		Expect(VersionWhatever.String()).To(Equal("whatever"))
		Expect(VersionUnknown.String()).To(Equal("unknown"))
		Expect(VersionDraft29.String()).To(Equal("draft-29"))
		Expect(VersionDraft32.String()).To(Equal("draft-32"))
		// check with unsupported version numbers from the wiki
		Expect(VersionNumber(0x51303039).String()).To(Equal("gQUIC 9"))
		Expect(VersionNumber(0x51303133).String()).To(Equal("gQUIC 13"))
		Expect(VersionNumber(0x51303235).String()).To(Equal("gQUIC 25"))
		Expect(VersionNumber(0x51303438).String()).To(Equal("gQUIC 48"))
		Expect(VersionNumber(0x01234567).String()).To(Equal("0x1234567"))
	})

	It("recognizes supported versions", func() {
		Expect(IsSupportedVersion(SupportedVersions, 0)).To(BeFalse())
		Expect(IsSupportedVersion(SupportedVersions, SupportedVersions[0])).To(BeTrue())
		Expect(IsSupportedVersion(SupportedVersions, SupportedVersions[len(SupportedVersions)-1])).To(BeTrue())
	})

	It("has supported versions in sorted order", func() {
		for i := 0; i < len(SupportedVersions)-1; i++ {
			Expect(SupportedVersions[i]).To(BeNumerically(">", SupportedVersions[i+1]))
		}
	})

	It("says if backwards compatibility mode should be used", func() {
		Expect(UseRetireBugBackwardsCompatibilityMode(true, VersionDraft29)).To(BeTrue())
		Expect(UseRetireBugBackwardsCompatibilityMode(true, VersionDraft32)).To(BeFalse())
		Expect(UseRetireBugBackwardsCompatibilityMode(false, VersionDraft29)).To(BeFalse())
		Expect(UseRetireBugBackwardsCompatibilityMode(false, VersionDraft32)).To(BeFalse())
	})

	Context("highest supported version", func() {
		It("finds the supported version", func() {
			supportedVersions := []VersionNumber{1, 2, 3}
			other := []VersionNumber{6, 5, 4, 3}
			ver, ok := ChooseSupportedVersion(supportedVersions, other)
			Expect(ok).To(BeTrue())
			Expect(ver).To(Equal(VersionNumber(3)))
		})

		It("picks the preferred version", func() {
			supportedVersions := []VersionNumber{2, 1, 3}
			other := []VersionNumber{3, 6, 1, 8, 2, 10}
			ver, ok := ChooseSupportedVersion(supportedVersions, other)
			Expect(ok).To(BeTrue())
			Expect(ver).To(Equal(VersionNumber(2)))
		})

		It("says when no matching version was found", func() {
			_, ok := ChooseSupportedVersion([]VersionNumber{1}, []VersionNumber{2})
			Expect(ok).To(BeFalse())
		})

		It("handles empty inputs", func() {
			_, ok := ChooseSupportedVersion([]VersionNumber{102, 101}, []VersionNumber{})
			Expect(ok).To(BeFalse())
			_, ok = ChooseSupportedVersion([]VersionNumber{}, []VersionNumber{1, 2})
			Expect(ok).To(BeFalse())
			_, ok = ChooseSupportedVersion([]VersionNumber{}, []VersionNumber{})
			Expect(ok).To(BeFalse())
		})
	})

	Context("reserved versions", func() {
		It("adds a greased version if passed an empty slice", func() {
			greased := GetGreasedVersions([]VersionNumber{})
			Expect(greased).To(HaveLen(1))
			Expect(isReservedVersion(greased[0])).To(BeTrue())
		})

		It("strips greased versions", func() {
			v := SupportedVersions[0]
			greased := GetGreasedVersions([]VersionNumber{v})
			Expect(greased).To(HaveLen(2))
			stripped := StripGreasedVersions(greased)
			Expect(stripped).To(HaveLen(1))
			Expect(stripped[0]).To(Equal(v))
		})

		It("creates greased lists of version numbers", func() {
			supported := []VersionNumber{10, 18, 29}
			for _, v := range supported {
				Expect(isReservedVersion(v)).To(BeFalse())
			}
			var greasedVersionFirst, greasedVersionLast, greasedVersionMiddle int
			// check that
			// 1. the greased version sometimes appears first
			// 2. the greased version sometimes appears in the middle
			// 3. the greased version sometimes appears last
			// 4. the supported versions are kept in order
			for i := 0; i < 100; i++ {
				greased := GetGreasedVersions(supported)
				Expect(greased).To(HaveLen(4))
				var j int
				for i, v := range greased {
					if isReservedVersion(v) {
						if i == 0 {
							greasedVersionFirst++
						}
						if i == len(greased)-1 {
							greasedVersionLast++
						}
						greasedVersionMiddle++
						continue
					}
					Expect(supported[j]).To(Equal(v))
					j++
				}
			}
			Expect(greasedVersionFirst).ToNot(BeZero())
			Expect(greasedVersionLast).ToNot(BeZero())
			Expect(greasedVersionMiddle).ToNot(BeZero())
		})
	})
})
