package helper

import (
	"cmp"
	"fmt"
	"image/color"
	"iter"
	"slices"
	"testing"
)

func cmpRGBA64(a, b color.RGBA64) int {
	return cmp.Or(
		cmp.Compare(a.R, b.R),
		cmp.Compare(a.G, b.G),
		cmp.Compare(a.B, b.B),
		cmp.Compare(a.A, b.A),
	)
}

func eqRGBA64(a, b color.RGBA64) bool {
	return cmpRGBA64(a, b) == 0
}

func TestEnum(t *testing.T) {
	tests := []struct {
		color, alpha, slow bool
		expectedCount      int
	}{
		{true, true, true, 87481},
		{true, false, true, 140608},
		{true, true, false, 649},
		{true, false, false, 216},
		{false, true, true, 65551},
		{false, false, true, 65536},
		{false, true, false, 271},
		{false, false, false, 258},
	}
	for _, tt := range tests {
		t.Run(fmt.Sprintf("color=%v, alpha=%v, slow=%v", tt.color, tt.alpha, tt.slow), func(t *testing.T) {
			t.Run("sequence meets expected criteria", func(t *testing.T) {
				list := slices.Collect(Enum(tt.color, tt.alpha, tt.slow))
				gotCount := len(list)
				if gotCount != tt.expectedCount {
					t.Errorf("Enum(%v, %v, %v) returned %d items, wanted %d", tt.color, tt.alpha, tt.slow, gotCount, tt.expectedCount)
				}

				slices.SortFunc(list, cmpRGBA64)
				list = slices.CompactFunc(list, eqRGBA64)

				if len(list) != gotCount {
					t.Errorf("Enum(%v, %v, %v) returned %d duplicate items", tt.color, tt.alpha, tt.slow, gotCount-len(list))
				}

				listHasAlpha := false
				for _, c := range list {
					if c.A != 0xffff {
						if !tt.alpha {
							t.Errorf("Enum(%v, %v, %v) returned non-opaque color: %v", tt.color, tt.alpha, tt.slow, c)
						}

						listHasAlpha = true
						break
					}
				}

				if !listHasAlpha && tt.alpha {
					t.Errorf("Enum(%v, %v, %v) didn't return non-opaque colors", tt.color, tt.alpha, tt.slow)
				}
			})

			t.Run("cancel", func(t *testing.T) {
				// make sure cancelling the iteration doesn't panic.
				// But mostly, we want that sweet, sweet test coverage.
				next, stop := iter.Pull(Enum(tt.color, tt.alpha, tt.slow))
				// need to invoke next to actually have the generated be started.
				if _, ok := next(); !ok {
					t.Error("iteration stopped before we could cancel it")
				}
				stop()
			})
		})
	}

}

func TestEnumColor(t *testing.T) {
	tests := []struct {
		color, alpha, slow bool
	}{
		{true, true, true},
		{true, false, true},
		{true, true, false},
		{true, false, false},
	}
	for _, tt := range tests {
		t.Run(fmt.Sprintf("color=%v, alpha=%v, slow=%v", tt.color, tt.alpha, tt.slow), func(t *testing.T) {
			t.Run("sequence equivalence", func(t *testing.T) {
				nextRGBA64, stop1 := iter.Pull(Enum(tt.color, tt.alpha, tt.slow))
				defer stop1()
				nextNRGBA, stop2 := iter.Pull(EnumColor[color.NRGBA](tt.color, tt.alpha, tt.slow, color.NRGBAModel))
				defer stop2()

				for i := 0; ; i++ {
					rgba64, gotRgba64 := nextRGBA64()
					nrgba, gotNrgba := nextNRGBA()

					if gotRgba64 != gotNrgba {
						t.Errorf("one sequence terminated at i=%d: gotRgba64=%v, gotNrgba=%v", i, gotRgba64, gotNrgba)
						return
					}

					if !gotRgba64 {
						break
					}

					if wantNrgba := color.NRGBAModel.Convert(rgba64).(color.NRGBA); nrgba != wantNrgba {
						t.Errorf("i=%d: got %#+v, expected %#+v", i, nrgba, wantNrgba)
					}
				}
			})

			t.Run("cancel", func(t *testing.T) {
				// make sure cancelling the iteration doesn't panic.
				// But mostly, we want that sweet, sweet test coverage.
				next, stop := iter.Pull(EnumColor[color.NRGBA](tt.color, tt.alpha, tt.slow, color.NRGBAModel))
				// need to invoke next to actually have the generated be started.
				if _, ok := next(); !ok {
					t.Error("iteration stopped before we could cancel it")
				}
				stop()
			})
		})
	}
}