package canvas_test

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"testing"

	"fyne.io/fyne/v2/canvas"
	internalTest "fyne.io/fyne/v2/internal/test"
	"fyne.io/fyne/v2/test"

	"github.com/stretchr/testify/assert"
)

func TestNewHorizontalGradient(t *testing.T) {
	horizontal := canvas.NewHorizontalGradient(color.Black, color.Transparent)

	smallImg := horizontal.Generate(5, 5)
	expectedAlphaValues := [][]uint8{
		{0xe6, 0xb3, 0x7f, 0x4c, 0x19},
		{0xe6, 0xb3, 0x7f, 0x4c, 0x19},
		{0xe6, 0xb3, 0x7f, 0x4c, 0x19},
		{0xe6, 0xb3, 0x7f, 0x4c, 0x19},
		{0xe6, 0xb3, 0x7f, 0x4c, 0x19},
	}
	for y, xv := range expectedAlphaValues {
		for x, v := range xv {
			assert.Equal(t, color.NRGBA{0, 0, 0, v}, smallImg.At(x, y), fmt.Sprintf("alpha value at %d,%d", x, y))
		}
	}

	img := horizontal.Generate(51, 5)
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xfd}, img.At(0, 0))
	for i := 0; i < 5; i++ {
		assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, img.At(25, i))
	}
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x02}, img.At(50, 0))
}

func TestNewHorizontalGradient_Flipped(t *testing.T) {
	horizontal := canvas.NewHorizontalGradient(color.Black, color.Transparent)
	horizontal.Angle -= 180

	smallImg := horizontal.Generate(5, 5)
	expectedAlphaValues := [][]uint8{
		{0x19, 0x4c, 0x7f, 0xb3, 0xe6},
		{0x19, 0x4c, 0x7f, 0xb3, 0xe6},
		{0x19, 0x4c, 0x7f, 0xb3, 0xe6},
		{0x19, 0x4c, 0x7f, 0xb3, 0xe6},
		{0x19, 0x4c, 0x7f, 0xb3, 0xe6},
	}
	for y, xv := range expectedAlphaValues {
		for x, v := range xv {
			assert.Equal(t, color.NRGBA{0, 0, 0, v}, smallImg.At(x, y), fmt.Sprintf("alpha value at %d,%d", x, y))
		}
	}

	img := horizontal.Generate(51, 5)
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x02}, img.At(0, 0))
	for i := 0; i < 5; i++ {
		assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, img.At(25, i))
	}
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xfd}, img.At(50, 0))
}

func TestNewVerticalGradient(t *testing.T) {
	vertical := canvas.NewVerticalGradient(color.Black, color.Transparent)

	smallImg := vertical.Generate(5, 5)
	expectedAlphaValues := [][]uint8{
		{0xe6, 0xe6, 0xe6, 0xe6, 0xe6},
		{0xb3, 0xb3, 0xb3, 0xb3, 0xb3},
		{0x7f, 0x7f, 0x7f, 0x7f, 0x7f},
		{0x4c, 0x4c, 0x4c, 0x4c, 0x4c},
		{0x19, 0x19, 0x19, 0x19, 0x19},
	}
	for y, xv := range expectedAlphaValues {
		for x, v := range xv {
			assert.Equal(t, color.NRGBA{0, 0, 0, v}, smallImg.At(x, y), fmt.Sprintf("alpha value at %d,%d", x, y))
		}
	}

	imgVert := vertical.Generate(5, 51)
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xfd}, imgVert.At(0, 0))
	for i := 0; i < 5; i++ {
		assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, imgVert.At(i, 25))
	}
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x02}, imgVert.At(0, 50))
}

func TestNewVerticalGradient_Flipped(t *testing.T) {
	vertical := canvas.NewVerticalGradient(color.Black, color.Transparent)
	vertical.Angle += 180

	smallImg := vertical.Generate(5, 5)
	expectedAlphaValues := [][]uint8{
		{0x19, 0x19, 0x19, 0x19, 0x19},
		{0x4c, 0x4c, 0x4c, 0x4c, 0x4c},
		{0x7f, 0x7f, 0x7f, 0x7f, 0x7f},
		{0xb3, 0xb3, 0xb3, 0xb3, 0xb3},
		{0xe6, 0xe6, 0xe6, 0xe6, 0xe6},
	}
	for y, xv := range expectedAlphaValues {
		for x, v := range xv {
			assert.Equal(t, color.NRGBA{0, 0, 0, v}, smallImg.At(x, y), fmt.Sprintf("alpha value at %d,%d", x, y))
		}
	}

	imgVert := vertical.Generate(5, 51)
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x02}, imgVert.At(0, 0))
	for i := 0; i < 5; i++ {
		assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, imgVert.At(i, 25))
	}
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xfd}, imgVert.At(0, 50))
}

func TestNewLinearGradient_45(t *testing.T) {
	negative := canvas.NewLinearGradient(color.Black, color.Transparent, 45.0)

	smallImg := negative.Generate(5, 5)
	expectedAlphaValues := [][]uint8{
		{0x7f, 0x99, 0xb3, 0xcc, 0xe6},
		{0x66, 0x7f, 0x99, 0xb3, 0xcc},
		{0x4c, 0x66, 0x7f, 0x99, 0xb3},
		{0x33, 0x4c, 0x66, 0x7f, 0x99},
		{0x19, 0x33, 0x4c, 0x66, 0x7f},
	}
	for y, xv := range expectedAlphaValues {
		for x, v := range xv {
			assert.Equal(t, color.NRGBA{0, 0, 0, v}, smallImg.At(x, y), fmt.Sprintf("alpha value at %d,%d", x, y))
		}
	}

	img := negative.Generate(51, 51)
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xfd}, img.At(50, 0))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x02}, img.At(0, 50))
	for i := 0; i < 5; i++ {
		assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, img.At(i, i))
	}
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, img.At(0, 0))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, img.At(50, 50))
}

func TestNewLinearGradient_225(t *testing.T) {
	negative := canvas.NewLinearGradient(color.Black, color.Transparent, 225.0)

	smallImg := negative.Generate(5, 5)
	expectedAlphaValues := [][]uint8{
		{0x7f, 0x66, 0x4c, 0x33, 0x19},
		{0x99, 0x7f, 0x66, 0x4c, 0x33},
		{0xb3, 0x99, 0x7f, 0x66, 0x4c},
		{0xcc, 0xb3, 0x99, 0x7f, 0x66},
		{0xe6, 0xcc, 0xb3, 0x99, 0x7f},
	}
	for y, xv := range expectedAlphaValues {
		for x, v := range xv {
			assert.Equal(t, color.NRGBA{0, 0, 0, v}, smallImg.At(x, y), fmt.Sprintf("alpha value at %d,%d", x, y))
		}
	}

	img := negative.Generate(51, 51)
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x02}, img.At(50, 0))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xfd}, img.At(0, 50))
	for i := 0; i < 5; i++ {
		assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, img.At(i, i))
	}
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, img.At(0, 0))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, img.At(50, 50))
}

func TestNewLinearGradient_135(t *testing.T) {
	positive := canvas.NewLinearGradient(color.Black, color.Transparent, 135.0)

	smallImg := positive.Generate(5, 5)
	expectedAlphaValues := [][]uint8{
		{0x19, 0x33, 0x4c, 0x66, 0x7f},
		{0x33, 0x4c, 0x66, 0x7f, 0x99},
		{0x4c, 0x66, 0x7f, 0x99, 0xb3},
		{0x66, 0x7f, 0x99, 0xb3, 0xcc},
		{0x7f, 0x99, 0xb3, 0xcc, 0xe6},
	}
	for y, xv := range expectedAlphaValues {
		for x, v := range xv {
			assert.Equal(t, color.NRGBA{0, 0, 0, v}, smallImg.At(x, y), fmt.Sprintf("alpha value at %d,%d", x, y))
		}
	}

	img := positive.Generate(51, 51)
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x02}, img.At(0, 0))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xfd}, img.At(50, 50))
	for i := 0; i < 5; i++ {
		assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, img.At(50-i, i))
	}
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, img.At(50, 0))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, img.At(0, 50))
}

func TestNewLinearGradient_315(t *testing.T) {
	positive := canvas.NewLinearGradient(color.Black, color.Transparent, 315.0)

	smallImg := positive.Generate(5, 5)
	expectedAlphaValues := [][]uint8{
		{0xe6, 0xcc, 0xb3, 0x99, 0x7f},
		{0xcc, 0xb3, 0x99, 0x7f, 0x66},
		{0xb3, 0x99, 0x7f, 0x66, 0x4c},
		{0x99, 0x7f, 0x66, 0x4c, 0x33},
		{0x7f, 0x66, 0x4c, 0x33, 0x19},
	}
	for y, xv := range expectedAlphaValues {
		for x, v := range xv {
			assert.Equal(t, color.NRGBA{0, 0, 0, v}, smallImg.At(x, y), fmt.Sprintf("alpha value at %d,%d", x, y))
		}
	}

	img := positive.Generate(51, 51)
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xfd}, img.At(0, 0))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x02}, img.At(50, 50))
	for i := 0; i < 5; i++ {
		assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, img.At(50-i, i))
	}
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, img.At(50, 0))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x7f}, img.At(0, 50))
}

func TestNewRadialGradient(t *testing.T) {
	circle := canvas.NewRadialGradient(color.Black, color.Transparent)

	{
		imgOddDiameter := circle.Generate(5, 5)
		expectedAlphaValues := [][]uint8{
			{0x00, 0x1b, 0x33, 0x1b, 0x00},
			{0x1b, 0x6f, 0x99, 0x6f, 0x1b},
			{0x33, 0x99, 0xff, 0x99, 0x33},
			{0x1b, 0x6f, 0x99, 0x6f, 0x1b},
			{0x00, 0x1b, 0x33, 0x1b, 0x00},
		}
		for y, xv := range expectedAlphaValues {
			for x, v := range xv {
				assert.Equal(t, color.NRGBA{0, 0, 0, v}, imgOddDiameter.At(x, y), fmt.Sprintf("alpha value at %d,%d", x, y))
			}
		}
	}

	{
		imgEvenDiameter := circle.Generate(6, 6)
		expectedAlphaValues := [][]uint8{
			{0x00, 0x07, 0x26, 0x26, 0x07, 0x00},
			{0x07, 0x4a, 0x79, 0x79, 0x4a, 0x07},
			{0x26, 0x79, 0xc3, 0xc3, 0x79, 0x26},
			{0x26, 0x79, 0xc3, 0xc3, 0x79, 0x26},
			{0x07, 0x4a, 0x79, 0x79, 0x4a, 0x07},
			{0x00, 0x07, 0x26, 0x26, 0x07, 0x00},
		}
		for y, xv := range expectedAlphaValues {
			for x, v := range xv {
				assert.Equal(t, color.NRGBA{0, 0, 0, v}, imgEvenDiameter.At(x, y), fmt.Sprintf("alpha value at %d,%d", x, y))
			}
		}
	}

	imgCircle := circle.Generate(10, 10)
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x18}, imgCircle.At(9, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x4a}, imgCircle.At(8, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x7d}, imgCircle.At(7, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xaf}, imgCircle.At(6, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xdb}, imgCircle.At(5, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xdb}, imgCircle.At(4, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xaf}, imgCircle.At(3, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x7d}, imgCircle.At(2, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x4a}, imgCircle.At(1, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x18}, imgCircle.At(0, 5))

	circle.CenterOffsetX = 0.1
	circle.CenterOffsetY = 0.1
	imgCircleOffset := circle.Generate(10, 10)
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xe1}, imgCircleOffset.At(5, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xbc}, imgCircleOffset.At(4, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x93}, imgCircleOffset.At(3, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x69}, imgCircleOffset.At(2, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x3e}, imgCircleOffset.At(1, 5))

	circle.CenterOffsetX = -0.1
	circle.CenterOffsetY = -0.1
	imgCircleOffset = circle.Generate(10, 10)
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xa5}, imgCircleOffset.At(5, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xbc}, imgCircleOffset.At(4, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xbc}, imgCircleOffset.At(3, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0xa5}, imgCircleOffset.At(2, 5))
	assert.Equal(t, color.NRGBA{0, 0, 0, 0x83}, imgCircleOffset.At(1, 5))
}

func TestGradient_colorComputation(t *testing.T) {
	bg := internalTest.NewCheckedImage(50, 50, 1, 2)
	bounds := image.Rect(0, 0, 49, 49)
	dst := image.NewNRGBA(bounds)
	draw.Draw(dst, bounds, bg, image.Pt(0, 0), draw.Src)
	g := canvas.NewHorizontalGradient(color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}, color.Transparent)
	ovl := g.Generate(50, 50)
	draw.Draw(dst, bounds, ovl, image.Pt(0, 0), draw.Over)
	test.AssertImageMatches(t, "gradient_colors.png", dst)
}
