package pixel_test

import (
	"math"
	"reflect"
	"testing"

	"github.com/faiface/pixel"
)

func TestLine_Bounds(t *testing.T) {
	type fields struct {
		A pixel.Vec
		B pixel.Vec
	}
	tests := []struct {
		name   string
		fields fields
		want   pixel.Rect
	}{
		{
			name:   "Positive slope",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			want:   pixel.R(0, 0, 10, 10),
		},
		{
			name:   "Negative slope",
			fields: fields{A: pixel.V(10, 10), B: pixel.V(0, 0)},
			want:   pixel.R(0, 0, 10, 10),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := pixel.Line{
				A: tt.fields.A,
				B: tt.fields.B,
			}
			if got := l.Bounds(); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Line.Bounds() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestLine_Center(t *testing.T) {
	type fields struct {
		A pixel.Vec
		B pixel.Vec
	}
	tests := []struct {
		name   string
		fields fields
		want   pixel.Vec
	}{
		{
			name:   "Positive slope",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			want:   pixel.V(5, 5),
		},
		{
			name:   "Negative slope",
			fields: fields{A: pixel.V(10, 10), B: pixel.V(0, 0)},
			want:   pixel.V(5, 5),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := pixel.Line{
				A: tt.fields.A,
				B: tt.fields.B,
			}
			if got := l.Center(); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Line.Center() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestLine_Closest(t *testing.T) {
	type fields struct {
		A pixel.Vec
		B pixel.Vec
	}
	type args struct {
		v pixel.Vec
	}
	tests := []struct {
		name   string
		fields fields
		args   args
		want   pixel.Vec
	}{
		{
			name:   "Point on line",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{v: pixel.V(5, 5)},
			want:   pixel.V(5, 5),
		},
		{
			name:   "Point on next to line",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{v: pixel.V(0, 10)},
			want:   pixel.V(5, 5),
		},
		{
			name:   "Point on next to vertical line",
			fields: fields{A: pixel.V(5, 0), B: pixel.V(5, 10)},
			args:   args{v: pixel.V(6, 5)},
			want:   pixel.V(5, 5),
		},
		{
			name:   "Point on next to horizontal line",
			fields: fields{A: pixel.V(0, 5), B: pixel.V(10, 5)},
			args:   args{v: pixel.V(5, 6)},
			want:   pixel.V(5, 5),
		},
		{
			name:   "Point far from line",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{v: pixel.V(80, -70)},
			want:   pixel.V(5, 5),
		},
		{
			name:   "Point on inline with line",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{v: pixel.V(20, 20)},
			want:   pixel.V(10, 10),
		},
		{
			name:   "Vertical line",
			fields: fields{A: pixel.V(0, -10), B: pixel.V(0, 10)},
			args:   args{v: pixel.V(-1, 0)},
			want:   pixel.V(0, 0),
		},
		{
			name:   "Horizontal line",
			fields: fields{A: pixel.V(-10, 0), B: pixel.V(10, 0)},
			args:   args{v: pixel.V(0, -1)},
			want:   pixel.V(0, 0),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := pixel.Line{
				A: tt.fields.A,
				B: tt.fields.B,
			}
			if got := l.Closest(tt.args.v); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Line.Closest() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestLine_Contains(t *testing.T) {
	type fields struct {
		A pixel.Vec
		B pixel.Vec
	}
	type args struct {
		v pixel.Vec
	}
	tests := []struct {
		name   string
		fields fields
		args   args
		want   bool
	}{
		{
			name:   "Point on line",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{v: pixel.V(5, 5)},
			want:   true,
		},
		{
			name:   "Point on negative sloped line",
			fields: fields{A: pixel.V(0, 10), B: pixel.V(10, 0)},
			args:   args{v: pixel.V(5, 5)},
			want:   true,
		},
		{
			name:   "Point not on line",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{v: pixel.V(0, 10)},
			want:   false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := pixel.Line{
				A: tt.fields.A,
				B: tt.fields.B,
			}
			if got := l.Contains(tt.args.v); got != tt.want {
				t.Errorf("Line.Contains() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestLine_Formula(t *testing.T) {
	type fields struct {
		A pixel.Vec
		B pixel.Vec
	}
	tests := []struct {
		name   string
		fields fields
		wantM  float64
		wantB  float64
	}{
		{
			name:   "Getting formula - 45 degs",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			wantM:  1,
			wantB:  0,
		},
		{
			name:   "Getting formula - 90 degs",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(0, 10)},
			wantM:  math.Inf(1),
			wantB:  math.NaN(),
		},
		{
			name:   "Getting formula - 0 degs",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 0)},
			wantM:  0,
			wantB:  0,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := pixel.Line{
				A: tt.fields.A,
				B: tt.fields.B,
			}
			gotM, gotB := l.Formula()
			if gotM != tt.wantM {
				t.Errorf("Line.Formula() gotM = %v, want %v", gotM, tt.wantM)
			}
			if gotB != tt.wantB {
				if math.IsNaN(tt.wantB) && !math.IsNaN(gotB) {
					t.Errorf("Line.Formula() gotB = %v, want %v", gotB, tt.wantB)
				}
			}
		})
	}
}

func TestLine_Intersect(t *testing.T) {
	type fields struct {
		A pixel.Vec
		B pixel.Vec
	}
	type args struct {
		k pixel.Line
	}
	tests := []struct {
		name   string
		fields fields
		args   args
		want   pixel.Vec
		want1  bool
	}{
		{
			name:   "Lines intersect",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{k: pixel.L(pixel.V(0, 10), pixel.V(10, 0))},
			want:   pixel.V(5, 5),
			want1:  true,
		},
		{
			name:   "Lines intersect 2",
			fields: fields{A: pixel.V(5, 1), B: pixel.V(1, 1)},
			args:   args{k: pixel.L(pixel.V(2, 0), pixel.V(2, 3))},
			want:   pixel.V(2, 1),
			want1:  true,
		},
		{
			name:   "Line intersect with vertical",
			fields: fields{A: pixel.V(5, 0), B: pixel.V(5, 10)},
			args:   args{k: pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
			want:   pixel.V(5, 5),
			want1:  true,
		},
		{
			name:   "Line intersect with horizontal",
			fields: fields{A: pixel.V(0, 5), B: pixel.V(10, 5)},
			args:   args{k: pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
			want:   pixel.V(5, 5),
			want1:  true,
		},
		{
			name:   "Lines don't intersect",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{k: pixel.L(pixel.V(0, 10), pixel.V(1, 20))},
			want:   pixel.ZV,
			want1:  false,
		},
		{
			name:   "Lines don't intersect 2",
			fields: fields{A: pixel.V(1, 1), B: pixel.V(1, 5)},
			args:   args{k: pixel.L(pixel.V(-5, 0), pixel.V(-2, 2))},
			want:   pixel.ZV,
			want1:  false,
		},
		{
			name:   "Lines don't intersect 3",
			fields: fields{A: pixel.V(2, 0), B: pixel.V(2, 3)},
			args:   args{k: pixel.L(pixel.V(1, 5), pixel.V(5, 5))},
			want:   pixel.ZV,
			want1:  false,
		},
		{
			name:   "Lines parallel",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{k: pixel.L(pixel.V(0, 1), pixel.V(10, 11))},
			want:   pixel.ZV,
			want1:  false,
		}, {
			name:   "Lines intersect",
			fields: fields{A: pixel.V(600, 600), B: pixel.V(925, 150)},
			args:   args{k: pixel.L(pixel.V(740, 255), pixel.V(925, 255))},
			want:   pixel.V(849.1666666666666, 255),
			want1:  true,
		},
		{
			name:   "Lines intersect",
			fields: fields{A: pixel.V(600, 600), B: pixel.V(925, 150)},
			args:   args{k: pixel.L(pixel.V(740, 255), pixel.V(925, 255.0001))},
			want:   pixel.V(849.1666240490657, 255.000059008986),
			want1:  true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := pixel.Line{
				A: tt.fields.A,
				B: tt.fields.B,
			}
			got, got1 := l.Intersect(tt.args.k)
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Line.Intersect() got = %v, want %v", got, tt.want)
			}
			if got1 != tt.want1 {
				t.Errorf("Line.Intersect() got1 = %v, want %v", got1, tt.want1)
			}
		})
	}
}

func TestLine_IntersectCircle(t *testing.T) {
	type fields struct {
		A pixel.Vec
		B pixel.Vec
	}
	type args struct {
		c pixel.Circle
	}
	tests := []struct {
		name   string
		fields fields
		args   args
		want   pixel.Vec
	}{
		{
			name:   "Cirle intersects",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{c: pixel.C(pixel.V(6, 4), 2)},
			want:   pixel.V(0.5857864376269049, -0.5857864376269049),
		},
		{
			name:   "Cirle doesn't intersects",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{c: pixel.C(pixel.V(0, 5), 1)},
			want:   pixel.ZV,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := pixel.Line{
				A: tt.fields.A,
				B: tt.fields.B,
			}
			if got := l.IntersectCircle(tt.args.c); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Line.IntersectCircle() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestLine_IntersectRect(t *testing.T) {
	type fields struct {
		A pixel.Vec
		B pixel.Vec
	}
	type args struct {
		r pixel.Rect
	}
	tests := []struct {
		name   string
		fields fields
		args   args
		want   pixel.Vec
	}{
		{
			name:   "Line through rect vertically",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(0, 10)},
			args:   args{r: pixel.R(-1, 1, 5, 5)},
			want:   pixel.V(-1, 0),
		},
		{
			name:   "Line through rect horizontally",
			fields: fields{A: pixel.V(0, 1), B: pixel.V(10, 1)},
			args:   args{r: pixel.R(1, 0, 5, 5)},
			want:   pixel.V(0, -1),
		},
		{
			name:   "Line through rect diagonally bottom and left edges",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{r: pixel.R(0, 2, 3, 3)},
			want:   pixel.V(-1, 1),
		},
		{
			name:   "Line through rect diagonally top and right edges",
			fields: fields{A: pixel.V(10, 0), B: pixel.V(0, 10)},
			args:   args{r: pixel.R(5, 0, 8, 3)},
			want:   pixel.V(-2.5, -2.5),
		},
		{
			name:   "Line with not rect intersect",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{r: pixel.R(20, 20, 21, 21)},
			want:   pixel.ZV,
		},
		{
			name:   "Line intersects at 0,0",
			fields: fields{A: pixel.V(0, -10), B: pixel.V(0, 10)},
			args:   args{r: pixel.R(-1, 0, 2, 2)},
			want:   pixel.V(-1, 0),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := pixel.Line{
				A: tt.fields.A,
				B: tt.fields.B,
			}
			if got := l.IntersectRect(tt.args.r); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Line.IntersectRect() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestLine_Len(t *testing.T) {
	type fields struct {
		A pixel.Vec
		B pixel.Vec
	}
	tests := []struct {
		name   string
		fields fields
		want   float64
	}{
		{
			name:   "End right-up of start",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(3, 4)},
			want:   5,
		},
		{
			name:   "End left-up of start",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(-3, 4)},
			want:   5,
		},
		{
			name:   "End right-down of start",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(3, -4)},
			want:   5,
		},
		{
			name:   "End left-down of start",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(-3, -4)},
			want:   5,
		},
		{
			name:   "End same as start",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(0, 0)},
			want:   0,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := pixel.Line{
				A: tt.fields.A,
				B: tt.fields.B,
			}
			if got := l.Len(); got != tt.want {
				t.Errorf("Line.Len() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestLine_Rotated(t *testing.T) {
	// round returns the nearest integer, rounding ties away from zero.
	// This is required because `math.Round` wasn't introduced until Go1.10
	round := func(x float64) float64 {
		t := math.Trunc(x)
		if math.Abs(x-t) >= 0.5 {
			return t + math.Copysign(1, x)
		}
		return t
	}
	type fields struct {
		A pixel.Vec
		B pixel.Vec
	}
	type args struct {
		around pixel.Vec
		angle  float64
	}
	tests := []struct {
		name   string
		fields fields
		args   args
		want   pixel.Line
	}{
		{
			name:   "Rotating around line center",
			fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)},
			args:   args{around: pixel.V(2, 2), angle: math.Pi},
			want:   pixel.L(pixel.V(3, 3), pixel.V(1, 1)),
		},
		{
			name:   "Rotating around x-y origin",
			fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)},
			args:   args{around: pixel.V(0, 0), angle: math.Pi},
			want:   pixel.L(pixel.V(-1, -1), pixel.V(-3, -3)),
		},
		{
			name:   "Rotating around line end",
			fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)},
			args:   args{around: pixel.V(1, 1), angle: math.Pi},
			want:   pixel.L(pixel.V(1, 1), pixel.V(-1, -1)),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := pixel.Line{
				A: tt.fields.A,
				B: tt.fields.B,
			}
			// Have to round the results, due to floating-point in accuracies.  Results are correct to approximately
			// 10 decimal places.
			got := l.Rotated(tt.args.around, tt.args.angle)
			if round(got.A.X) != tt.want.A.X ||
				round(got.B.X) != tt.want.B.X ||
				round(got.A.Y) != tt.want.A.Y ||
				round(got.B.Y) != tt.want.B.Y {
				t.Errorf("Line.Rotated() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestLine_Scaled(t *testing.T) {
	type fields struct {
		A pixel.Vec
		B pixel.Vec
	}
	type args struct {
		scale float64
	}
	tests := []struct {
		name   string
		fields fields
		args   args
		want   pixel.Line
	}{
		{
			name:   "Scaling by 1",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{scale: 1},
			want:   pixel.L(pixel.V(0, 0), pixel.V(10, 10)),
		},
		{
			name:   "Scaling by >1",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{scale: 2},
			want:   pixel.L(pixel.V(-5, -5), pixel.V(15, 15)),
		},
		{
			name:   "Scaling by <1",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{scale: 0.5},
			want:   pixel.L(pixel.V(2.5, 2.5), pixel.V(7.5, 7.5)),
		},
		{
			name:   "Scaling by -1",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{scale: -1},
			want:   pixel.L(pixel.V(10, 10), pixel.V(0, 0)),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := pixel.Line{
				A: tt.fields.A,
				B: tt.fields.B,
			}
			if got := l.Scaled(tt.args.scale); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Line.Scaled() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestLine_ScaledXY(t *testing.T) {
	type fields struct {
		A pixel.Vec
		B pixel.Vec
	}
	type args struct {
		around pixel.Vec
		scale  float64
	}
	tests := []struct {
		name   string
		fields fields
		args   args
		want   pixel.Line
	}{
		{
			name:   "Scaling by 1 around origin",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{around: pixel.ZV, scale: 1},
			want:   pixel.L(pixel.V(0, 0), pixel.V(10, 10)),
		},
		{
			name:   "Scaling by >1 around origin",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{around: pixel.ZV, scale: 2},
			want:   pixel.L(pixel.V(0, 0), pixel.V(20, 20)),
		},
		{
			name:   "Scaling by <1 around origin",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{around: pixel.ZV, scale: 0.5},
			want:   pixel.L(pixel.V(0, 0), pixel.V(5, 5)),
		},
		{
			name:   "Scaling by -1 around origin",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
			args:   args{around: pixel.ZV, scale: -1},
			want:   pixel.L(pixel.V(0, 0), pixel.V(-10, -10)),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := pixel.Line{
				A: tt.fields.A,
				B: tt.fields.B,
			}
			if got := l.ScaledXY(tt.args.around, tt.args.scale); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Line.ScaledXY() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestLine_String(t *testing.T) {
	type fields struct {
		A pixel.Vec
		B pixel.Vec
	}
	tests := []struct {
		name   string
		fields fields
		want   string
	}{
		{
			name:   "Getting string",
			fields: fields{A: pixel.V(0, 0), B: pixel.V(1, 1)},
			want:   "Line(Vec(0, 0), Vec(1, 1))",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := pixel.Line{
				A: tt.fields.A,
				B: tt.fields.B,
			}
			if got := l.String(); got != tt.want {
				t.Errorf("Line.String() = %v, want %v", got, tt.want)
			}
		})
	}
}