package video

import (
	"image"
	"reflect"
	"testing"
)

var imageSizes = map[string][2]int{
	"480p":  [2]int{720, 480},
	"1080p": [2]int{1920, 1080},
}

func TestToI420(t *testing.T) {
	cases := map[string]struct {
		src      image.Image
		expected image.Image
	}{
		"I444": {
			src: &image.YCbCr{
				SubsampleRatio: image.YCbCrSubsampleRatio444,
				Y: []uint8{
					0xF0, 0x10, 0x00, 0x00,
					0x00, 0x00, 0x40, 0x00,
					0x00, 0x00, 0x00, 0x00,
					0x00, 0x80, 0x30, 0x00,
				},
				Cb: []uint8{
					0xF0, 0xF0, 0x80, 0x80,
					0xF0, 0xF0, 0x80, 0x80,
					0x80, 0x80, 0x30, 0x30,
					0x80, 0x80, 0x30, 0x30,
				},
				Cr: []uint8{
					0x10, 0x10, 0x40, 0x40,
					0x10, 0x10, 0x40, 0x40,
					0x80, 0x80, 0x80, 0x80,
					0x80, 0x80, 0x80, 0x80,
				},
				YStride: 4,
				CStride: 4,
				Rect:    image.Rect(0, 0, 4, 4),
			},
			expected: &image.YCbCr{
				SubsampleRatio: image.YCbCrSubsampleRatio420,
				Y: []uint8{
					0xF0, 0x10, 0x00, 0x00,
					0x00, 0x00, 0x40, 0x00,
					0x00, 0x00, 0x00, 0x00,
					0x00, 0x80, 0x30, 0x00,
				},
				Cb: []uint8{
					0xF0, 0x80,
					0x80, 0x30,
				},
				Cr: []uint8{
					0x10, 0x40,
					0x80, 0x80,
				},
				YStride: 4,
				CStride: 2,
				Rect:    image.Rect(0, 0, 4, 4),
			},
		},
		"I422": {
			src: &image.YCbCr{
				SubsampleRatio: image.YCbCrSubsampleRatio422,
				Y: []uint8{
					0xF0, 0x10, 0x00, 0x00,
					0x00, 0x00, 0x40, 0x00,
					0x00, 0x00, 0x00, 0x00,
					0x00, 0x80, 0x30, 0x00,
				},
				Cb: []uint8{
					0xF0, 0x80,
					0xF0, 0x80,
					0x80, 0x30,
					0x80, 0x30,
				},
				Cr: []uint8{
					0x10, 0x40,
					0x10, 0x40,
					0x80, 0x80,
					0x80, 0x80,
				},
				YStride: 4,
				CStride: 2,
				Rect:    image.Rect(0, 0, 4, 4),
			},
			expected: &image.YCbCr{
				SubsampleRatio: image.YCbCrSubsampleRatio420,
				Y: []uint8{
					0xF0, 0x10, 0x00, 0x00,
					0x00, 0x00, 0x40, 0x00,
					0x00, 0x00, 0x00, 0x00,
					0x00, 0x80, 0x30, 0x00,
				},
				Cb: []uint8{
					0xF0, 0x80,
					0x80, 0x30,
				},
				Cr: []uint8{
					0x10, 0x40,
					0x80, 0x80,
				},
				YStride: 4,
				CStride: 2,
				Rect:    image.Rect(0, 0, 4, 4),
			},
		},
		"RGBA": {
			src: &image.RGBA{
				Pix: []uint8{
					0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
					0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
				},
				Stride: 16,
				Rect:   image.Rect(0, 0, 4, 4),
			},
			expected: &image.YCbCr{
				SubsampleRatio: image.YCbCrSubsampleRatio420,
				Y: []uint8{
					0x00, 0x00, 0x00, 0x00,
					0x00, 0x00, 0x00, 0x00,
					0xFF, 0xFF, 0x00, 0x00,
					0xFF, 0xFF, 0x00, 0x00,
				},
				Cb: []uint8{
					0x80, 0x80,
					0x80, 0x80,
				},
				Cr: []uint8{
					0x80, 0x80,
					0x80, 0x80,
				},
				YStride: 4,
				CStride: 2,
				Rect:    image.Rect(0, 0, 4, 4),
			},
		},
	}
	for name, c := range cases {
		c := c
		t.Run(name, func(t *testing.T) {
			r := ToI420(ReaderFunc(func() (image.Image, error) {
				return c.src, nil
			}))
			out, err := r.Read()
			if err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}
			if !reflect.DeepEqual(c.expected, out) {
				t.Errorf("Expected output image:\n%v\ngot:\n%v", c.expected, out)
			}
		})
	}
}

func TestToRGBA(t *testing.T) {
	cases := map[string]struct {
		src      image.Image
		expected image.Image
	}{
		"I444": {
			src: &image.YCbCr{
				SubsampleRatio: image.YCbCrSubsampleRatio420,
				Y: []uint8{
					0x00, 0x00, 0x00, 0x00,
					0x00, 0x00, 0x00, 0x00,
					0xFF, 0xFF, 0x00, 0x00,
					0xFF, 0xFF, 0x00, 0x00,
				},
				Cb: []uint8{
					0x80, 0x80,
					0x80, 0x80,
				},
				Cr: []uint8{
					0x80, 0x80,
					0x80, 0x80,
				},
				YStride: 4,
				CStride: 2,
				Rect:    image.Rect(0, 0, 4, 4),
			},
			expected: &image.RGBA{
				Pix: []uint8{
					0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
					0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
				},
				Stride: 16,
				Rect:   image.Rect(0, 0, 4, 4),
			},
		},
	}
	for name, c := range cases {
		c := c
		t.Run(name, func(t *testing.T) {
			r := ToRGBA(ReaderFunc(func() (image.Image, error) {
				return c.src, nil
			}))
			out, err := r.Read()
			if err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}
			if !reflect.DeepEqual(c.expected, out) {
				t.Errorf("Expected output image:\n%v\ngot:\n%v", c.expected, out)
			}
		})
	}
}

func BenchmarkToI420(b *testing.B) {
	for name, sz := range imageSizes {
		cases := map[string]image.Image{
			"I444": image.NewYCbCr(image.Rect(0, 0, sz[0], sz[1]), image.YCbCrSubsampleRatio444),
			"I422": image.NewYCbCr(image.Rect(0, 0, sz[0], sz[1]), image.YCbCrSubsampleRatio422),
			"I420": image.NewYCbCr(image.Rect(0, 0, sz[0], sz[1]), image.YCbCrSubsampleRatio420),
			"RGBA": image.NewRGBA(image.Rect(0, 0, sz[0], sz[1])),
		}
		b.Run(name, func(b *testing.B) {
			for name, img := range cases {
				img := img
				b.Run(name, func(b *testing.B) {
					r := ToI420(ReaderFunc(func() (image.Image, error) {
						return img, nil
					}))

					for i := 0; i < b.N; i++ {
						_, err := r.Read()
						if err != nil {
							b.Fatalf("Unexpected error: %v", err)
						}
					}
				})
			}
		})
	}
}

func BenchmarkToRGBA(b *testing.B) {
	for name, sz := range imageSizes {
		cases := map[string]image.Image{
			"I444": image.NewYCbCr(image.Rect(0, 0, sz[0], sz[1]), image.YCbCrSubsampleRatio444),
			"I422": image.NewYCbCr(image.Rect(0, 0, sz[0], sz[1]), image.YCbCrSubsampleRatio422),
			"I420": image.NewYCbCr(image.Rect(0, 0, sz[0], sz[1]), image.YCbCrSubsampleRatio420),
			"RGBA": image.NewRGBA(image.Rect(0, 0, sz[0], sz[1])),
		}
		b.Run(name, func(b *testing.B) {
			for name, img := range cases {
				img := img
				b.Run(name, func(b *testing.B) {
					r := ToRGBA(ReaderFunc(func() (image.Image, error) {
						return img, nil
					}))

					for i := 0; i < b.N; i++ {
						_, err := r.Read()
						if err != nil {
							b.Fatalf("Unexpected error: %v", err)
						}
					}
				})
			}
		})
	}
}
