package adjust

import (
	"image"
	"testing"

	"github.com/anthonynsimon/bild/util"
)

func TestBrightness(t *testing.T) {
	cases := []struct {
		desc     string
		percent  float64
		value    image.Image
		expected *image.RGBA
	}{
		{
			desc:    "+100%",
			percent: 1.0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0xFF, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
		{
			desc:    "+0%",
			percent: 0.0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
		{
			desc:    "+50%",
			percent: 0.50,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0xC0, 0xC0, 0xC0, 0x80, 0xC0, 0xC0, 0xC0, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
		{
			desc:    "-100%",
			percent: -1.0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0xFF,
					0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
	}

	for _, c := range cases {
		actual := Brightness(c.value, c.percent)
		if !util.RGBAImageEqual(actual, c.expected) {
			t.Errorf("%s: expected: %#v, actual: %#v", "Brightness "+c.desc, c.expected, actual)
		}
	}
}

func TestGamma(t *testing.T) {
	cases := []struct {
		desc     string
		gamma    float64
		value    image.Image
		expected *image.RGBA
	}{
		{
			desc:  "1.0",
			gamma: 1.0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
		{
			desc:  "0",
			gamma: 0.0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xff,
					0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff,
				},
			},
		},
		{
			desc:  "2.2",
			gamma: 2.2,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0xba, 0xba, 0xba, 0x80, 0xba, 0xba, 0xba, 0xff,
					0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0xff,
				},
			},
		},
		{
			desc:  "0.5",
			gamma: 0.5,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x40, 0x40, 0x40, 0x80, 0x40, 0x40, 0x40, 0xff,
					0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0xff,
				},
			},
		},
	}

	for _, c := range cases {
		actual := Gamma(c.value, c.gamma)
		if !util.RGBAImageEqual(actual, c.expected) {
			t.Errorf("%s: expected: %#v, actual: %#v", "Gamma "+c.desc, c.expected, actual)
		}
	}
}

func TestContrast(t *testing.T) {
	cases := []struct {
		desc     string
		change   float64
		value    image.Image
		expected *image.RGBA
	}{
		{
			desc:   "1.0",
			change: 1.0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
		{
			desc:   "1.0",
			change: 1.0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x40, 0x40, 0x40, 0xFF, 0x40, 0x40, 0x40, 0xFF,
					0xF0, 0xF0, 0xF0, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
		{
			desc:   "0.0",
			change: 0.0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0xBA, 0xBA, 0xBA, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0xBA, 0xBA, 0xBA, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
		{
			desc:   "-0.5",
			change: -0.5,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x7F, 0x7F, 0x7F, 0x80, 0x7F, 0x7F, 0x7F, 0xff,
					0xBF, 0xBF, 0xBF, 0xff, 0x3F, 0x3F, 0x3F, 0xff,
				},
			},
		},
		{
			desc:   "0.5",
			change: 0.5,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xff,
					0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0xff,
				},
			},
		},
	}

	for _, c := range cases {
		actual := Contrast(c.value, c.change)
		if !util.RGBAImageEqual(actual, c.expected) {
			t.Errorf("%s: expected: %#v, actual: %#v", "Contrast "+c.desc, c.expected, actual)
		}
	}
}

func TestSaturation(t *testing.T) {
	cases := []struct {
		change   float64
		value    image.Image
		expected *image.RGBA
	}{
		{
			change: 0.0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
		{
			change: 1.0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
		{
			change: -1.0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
		{
			change: 0.0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x00, 0x00, 0xFF, 0x00, 0x80, 0x00, 0xFF,
					0x00, 0x00, 0x80, 0xFF, 0x00, 0x00, 0x00, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x00, 0x00, 0xFF, 0x00, 0x80, 0x00, 0xFF,
					0x00, 0x00, 0x80, 0xFF, 0x00, 0x00, 0x00, 0xFF,
				},
			},
		},
		{
			change: 1.0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x7A, 0x44, 0x44, 0xFF, 0x44, 0x7A, 0x44, 0xFF,
					0x44, 0x44, 0x7A, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x95, 0x29, 0x29, 0xFF, 0x29, 0x95, 0x29, 0xFF,
					0x29, 0x29, 0x95, 0xFF, 0x00, 0x00, 0x00, 0xFF,
				},
			},
		},
		{
			change: -1.0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x00, 0x00, 0xFF, 0x00, 0x80, 0x00, 0xFF,
					0x00, 0x00, 0x80, 0xFF, 0x00, 0x00, 0x00, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x40, 0x40, 0x40, 0xFF, 0x40, 0x40, 0x40, 0xFF,
					0x40, 0x40, 0x40, 0xFF, 0x00, 0x00, 0x00, 0xFF,
				},
			},
		},
	}

	for _, c := range cases {
		actual := Saturation(c.value, c.change)
		if !util.RGBAImageEqual(actual, c.expected) {
			t.Errorf("%s:\nexpected: %v\nactual: %v", "Saturation", util.RGBAToString(c.expected), util.RGBAToString(actual))
		}
	}
}

func TestHue(t *testing.T) {
	cases := []struct {
		change   int
		value    image.Image
		expected *image.RGBA
	}{
		{
			change: 0,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x00, 0x00, 0x80, 0xFF, 0x00, 0x00, 0xFF,
					0x00, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x00, 0x00, 0x80, 0xFF, 0x00, 0x00, 0xFF,
					0x00, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
		{
			change: 360,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x00, 0x00, 0x80, 0xFF, 0x00, 0x00, 0xFF,
					0x00, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x00, 0x00, 0x80, 0xFF, 0x00, 0x00, 0xFF,
					0x00, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
		{
			change: 40,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x00, 0x00, 0x80, 0xFF, 0x00, 0x00, 0xFF,
					0x00, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x55, 0x0, 0x80, 0xFF, 0xAA, 0x0, 0xFF,
					0x0, 0x55, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
		{
			change: -67,
			value: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x80, 0x00, 0x00, 0x80, 0xFF, 0x00, 0x00, 0xFF,
					0x00, 0xFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
			expected: &image.RGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 8,
				Pix: []uint8{
					0x71, 0x0, 0x80, 0x80, 0xE1, 0x0, 0xFF, 0xFF,
					0x1E, 0xFF, 0x0, 0xFF, 0x0, 0x0, 0x0, 0xFF,
				},
			},
		},
	}

	for _, c := range cases {
		actual := Hue(c.value, c.change)
		if !util.RGBAImageEqual(actual, c.expected) {
			t.Errorf("%s:\nexpected: %v\nactual: %v", "Hue", util.RGBAToString(c.expected), util.RGBAToString(actual))
		}
	}
}
