// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package s2

import (
	"math"
	"reflect"
	"testing"

	"github.com/golang/geo/r3"
	"github.com/golang/geo/s1"
)

func TestPolylineBasics(t *testing.T) {
	empty := Polyline{}
	if empty.RectBound() != EmptyRect() {
		t.Errorf("empty.RectBound() = %v, want %v", empty.RectBound(), EmptyRect())
	}
	if len(empty) != 0 {
		t.Errorf("empty Polyline should have no vertices")
	}
	empty.Reverse()
	if len(empty) != 0 {
		t.Errorf("reversed empty Polyline should have no vertices")
	}

	latlngs := []LatLng{
		LatLngFromDegrees(0, 0),
		LatLngFromDegrees(0, 90),
		LatLngFromDegrees(0, 180),
	}

	semiEquator := PolylineFromLatLngs(latlngs)
	want := PointFromCoords(0, 1, 0)
	if got, _ := semiEquator.Interpolate(0.5); !got.ApproxEqual(want) {
		t.Errorf("semiEquator.Interpolate(0.5) = %v, want %v", got, want)
	}
	semiEquator.Reverse()
	if got, want := (*semiEquator)[2], (Point{r3.Vector{X: 1, Y: 0, Z: 0}}); !got.ApproxEqual(want) {
		t.Errorf("semiEquator[2] = %v, want %v", got, want)
	}
}

func TestPolylineShape(t *testing.T) {
	var shape Shape = makePolyline("0:0, 1:0, 1:1, 2:1")
	if got, want := shape.NumEdges(), 3; got != want {
		t.Errorf("%v.NumEdges() = %v, want %d", shape, got, want)
	}
	if got, want := shape.NumChains(), 1; got != want {
		t.Errorf("%v.NumChains() = %d, want %d", shape, got, want)
	}
	if got, want := shape.Chain(0).Start, 0; got != want {
		t.Errorf("%v.Chain(0).Start = %d, want %d", shape, got, want)
	}
	if got, want := shape.Chain(0).Length, 3; got != want {
		t.Errorf("%v.Chain(0).Length = %d, want %d", shape, got, want)
	}

	e := shape.Edge(2)
	if want := PointFromLatLng(LatLngFromDegrees(1, 1)); !e.V0.ApproxEqual(want) {
		t.Errorf("%v.Edge(%d) point A = %v  want %v", shape, 2, e.V0, want)
	}
	if want := PointFromLatLng(LatLngFromDegrees(2, 1)); !e.V1.ApproxEqual(want) {
		t.Errorf("%v.Edge(%d) point B = %v  want %v", shape, 2, e.V1, want)
	}
	if shape.ReferencePoint().Contained {
		t.Errorf("polylines should not contain their reference points")
	}
	if got, want := shape.Dimension(), 1; got != want {
		t.Errorf("shape.Dimension() = %v, want %v", got, want)
	}
}

func TestPolylineEmpty(t *testing.T) {
	shape := &Polyline{}
	if got, want := shape.NumEdges(), 0; got != want {
		t.Errorf("%v.NumEdges() = %d, want %d", shape, got, want)
	}
	if got, want := shape.NumChains(), 0; got != want {
		t.Errorf("%v.NumChains() = %d, want %d", shape, got, want)
	}
	if !shape.IsEmpty() {
		t.Errorf("shape.IsEmpty() = false, want true")
	}
	if shape.IsFull() {
		t.Errorf("shape.IsFull() = true, want false")
	}
	if shape.ReferencePoint().Contained {
		t.Errorf("polylines should not contain their reference points")
	}
}

func TestPolylineLengthAndCentroid(t *testing.T) {
	// Construct random great circles and divide them randomly into segments.
	// Then make sure that the length and centroid are correct.  Note that
	// because of the way the centroid is computed, it does not matter how
	// we split the great circle into segments.

	for i := 0; i < 100; i++ {
		// Choose a coordinate frame for the great circle.
		f := randomFrame()

		var line Polyline
		for theta := 0.0; theta < 2*math.Pi; theta += math.Pow(randomFloat64(), 10) {
			p := Point{f.row(0).Mul(math.Cos(theta)).Add(f.row(1).Mul(math.Sin(theta)))}
			if len(line) == 0 || !p.ApproxEqual(line[len(line)-1]) {
				line = append(line, p)
			}
		}

		// Close the circle.
		line = append(line, line[0])

		length := line.Length()
		if got, want := math.Abs(length.Radians()-2*math.Pi), 2e-14; got > want {
			t.Errorf("%v.Length() = %v, want < %v", line, got, want)
		}

		centroid := line.Centroid()
		if got, want := centroid.Norm(), 2e-14; got > want {
			t.Errorf("%v.Norm() = %v, want < %v", centroid, got, want)
		}
	}
}

func TestPolylineIntersectsCell(t *testing.T) {
	pline := Polyline{
		Point{r3.Vector{X: 1, Y: -1.1, Z: 0.8}.Normalize()},
		Point{r3.Vector{X: 1, Y: -0.8, Z: 1.1}.Normalize()},
	}

	for face := 0; face < 6; face++ {
		cell := CellFromCellID(CellIDFromFace(face))
		if got, want := pline.IntersectsCell(cell), face&1 == 0; got != want {
			t.Errorf("%v.IntersectsCell(%v) = %v, want %v", pline, cell, got, want)
		}
	}
}

func TestPolylineSubsample(t *testing.T) {
	const polyStr = "0:0, 0:1, -1:2, 0:3, 0:4, 1:4, 2:4.5, 3:4, 3.5:4, 4:4"

	tests := []struct {
		have      string
		tolerance float64
		want      []int
	}{
		{
			// No vertices.
			tolerance: 1.0,
		},
		{
			// One vertex.
			have:      "0:1",
			tolerance: 1.0,
			want:      []int{0},
		},
		{
			// Two vertices.
			have:      "10:10, 11:11",
			tolerance: 5.0,
			want:      []int{0, 1},
		},
		{
			// Three points on a straight line. In theory, zero tolerance
			// should work, but in practice there are floating point errors.
			have:      "-1:0, 0:0, 1:0",
			tolerance: 1e-15,
			want:      []int{0, 2},
		},
		{
			// Zero tolerance on a non-straight line.
			have:      "-1:0, 0:0, 1:1",
			tolerance: 0.0,
			want:      []int{0, 1, 2},
		},
		{
			// Negative tolerance should return all vertices.
			have:      "-1:0, 0:0, 1:1",
			tolerance: -1.0,
			want:      []int{0, 1, 2},
		},
		{
			// Non-zero tolerance with a straight line.
			have:      "0:1, 0:2, 0:3, 0:4, 0:5",
			tolerance: 1.0,
			want:      []int{0, 4},
		},
		{
			// And finally, verify that we still do something
			// reasonable if the client passes in an invalid polyline
			// with two or more adjacent vertices.
			have:      "0:1, 0:1, 0:1, 0:2",
			tolerance: 0.0,
			want:      []int{0, 3},
		},

		// Simple examples
		{
			have:      polyStr,
			tolerance: 3.0,
			want:      []int{0, 9},
		},
		{
			have:      polyStr,
			tolerance: 2.0,
			want:      []int{0, 6, 9},
		},
		{
			have:      polyStr,
			tolerance: 0.9,
			want:      []int{0, 2, 6, 9},
		},
		{
			have:      polyStr,
			tolerance: 0.4,
			want:      []int{0, 1, 2, 3, 4, 6, 9},
		},
		{
			have:      polyStr,
			tolerance: 0,
			want:      []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
		},

		// Check that duplicate vertices are never generated.
		{
			have:      "10:10, 12:12, 10:10",
			tolerance: 5.0,
			want:      []int{0},
		},
		{
			have:      "0:0, 1:1, 0:0, 0:120, 0:130",
			tolerance: 5.0,
			want:      []int{0, 3, 4},
		},

		// Check that points are not collapsed if they would create a line segment
		// longer than 90 degrees, and also that the code handles original polyline
		// segments longer than 90 degrees.
		{
			have:      "90:0, 50:180, 20:180, -20:180, -50:180, -90:0, 30:0, 90:0",
			tolerance: 5.0,
			want:      []int{0, 2, 4, 5, 6, 7},
		},

		// Check that the output polyline is parametrically equivalent and not just
		// geometrically equivalent, i.e. that backtracking is preserved.  The
		// algorithm achieves this by requiring that the points must be encountered
		// in increasing order of distance along each output segment, except for
		// points that are within "tolerance" of the first vertex of each segment.
		{
			have:      "10:10, 10:20, 10:30, 10:15, 10:40",
			tolerance: 5.0,
			want:      []int{0, 2, 3, 4},
		},
		{
			have:      "10:10, 10:20, 10:30, 10:10, 10:30, 10:40",
			tolerance: 5.0,
			want:      []int{0, 2, 3, 5},
		},
		{
			have:      "10:10, 12:12, 9:9, 10:20, 10:30",
			tolerance: 5.0,
			want:      []int{0, 4},
		},
	}

	for _, test := range tests {
		p := makePolyline(test.have)
		got := p.SubsampleVertices(s1.Angle(test.tolerance) * s1.Degree)
		if !reflect.DeepEqual(got, test.want) {
			t.Errorf("%q.SubsampleVertices(%v°) = %v, want %v", test.have, test.tolerance, got, test.want)
		}
	}
}

func TestProject(t *testing.T) {
	latlngs := []LatLng{
		LatLngFromDegrees(0, 0),
		LatLngFromDegrees(0, 1),
		LatLngFromDegrees(0, 2),
		LatLngFromDegrees(1, 2),
	}
	line := PolylineFromLatLngs(latlngs)
	tests := []struct {
		haveLatLng     LatLng
		wantProjection LatLng
		wantNext       int
	}{
		{LatLngFromDegrees(0.5, -0.5), LatLngFromDegrees(0, 0), 1},
		{LatLngFromDegrees(0.5, 0.5), LatLngFromDegrees(0, 0.5), 1},
		{LatLngFromDegrees(0.5, 1), LatLngFromDegrees(0, 1), 2},
		{LatLngFromDegrees(-0.5, 2.5), LatLngFromDegrees(0, 2), 3},
		{LatLngFromDegrees(2, 2), LatLngFromDegrees(1, 2), 4},
		{LatLngFromDegrees(-50, 0.5), LatLngFromDegrees(0, 0.5), 1},
	}
	for _, test := range tests {
		projection, next := line.Project(PointFromLatLng(test.haveLatLng))
		if !PointFromLatLng(test.wantProjection).ApproxEqual(projection) {
			t.Errorf("line.Project(%v) = %v, want %v", test.haveLatLng, projection, test.wantProjection)
		}
		if next != test.wantNext {
			t.Errorf("line.Project(%v) = %v, want %v", test.haveLatLng, next, test.wantNext)
		}
	}
}

func TestIsOnRight(t *testing.T) {
	latlngs := []LatLng{
		LatLngFromDegrees(0, 0),
		LatLngFromDegrees(0, 1),
		LatLngFromDegrees(0, 2),
		LatLngFromDegrees(1, 2),
	}
	line1 := PolylineFromLatLngs(latlngs)
	latlngs = []LatLng{
		LatLngFromDegrees(0, 0),
		LatLngFromDegrees(0, 1),
		LatLngFromDegrees(-1, 0),
	}
	line2 := PolylineFromLatLngs(latlngs)
	tests := []struct {
		line        *Polyline
		haveLatLng  LatLng
		wantOnRight bool
	}{
		{line1, LatLngFromDegrees(-0.5, 0.5), true},
		{line1, LatLngFromDegrees(0.5, -0.5), false},
		{line1, LatLngFromDegrees(0.5, 0.5), false},
		{line1, LatLngFromDegrees(0.5, 1.0), false},
		{line1, LatLngFromDegrees(-0.5, 2.5), true},
		{line1, LatLngFromDegrees(1.5, 2.5), true},
		// Explicitly test the case where the closest point is an interior vertex.
		// The points are chosen such that they are on different sides of the two
		// edges that the interior vertex is on.
		{line2, LatLngFromDegrees(-0.5, 5.0), false},
		{line2, LatLngFromDegrees(5.5, 5.0), false},
	}
	for _, test := range tests {
		onRight := test.line.IsOnRight(PointFromLatLng(test.haveLatLng))
		if onRight != test.wantOnRight {
			t.Errorf("line.IsOnRight(%v) = %v, want %v", test.haveLatLng, onRight, test.wantOnRight)
		}
	}
}

func TestPolylineValidate(t *testing.T) {
	p := makePolyline("0:0, 2:1, 0:2, 2:3, 0:4, 2:5, 0:6")
	if p.Validate() != nil {
		t.Errorf("valid polygon should Validate")
	}

	p1 := Polyline([]Point{
		PointFromCoords(0, 1, 0),
		{r3.Vector{X: 10, Y: 3, Z: 7}},
		PointFromCoords(0, 0, 1),
	})

	if err := p1.Validate(); err == nil {
		t.Errorf("polyline with non-normalized points shouldn't validate")
	}

	// adjacent equal vertices
	p2 := Polyline([]Point{
		PointFromCoords(0, 1, 0),
		PointFromCoords(0, 0, 1),
		PointFromCoords(0, 0, 1),
		PointFromCoords(1, 0, 0),
	})

	if err := p2.Validate(); err == nil {
		t.Errorf("polyline with repeated vertices shouldn't validate")
	}

	pt := PointFromCoords(1, 1, 0)
	antiPt := Point{pt.Mul(-1)}

	// non-adjacent antipodal points.
	p3 := Polyline([]Point{
		PointFromCoords(0, 1, 0),
		pt,
		PointFromCoords(0, 0, 1),
		antiPt,
	})

	if err := p3.Validate(); err != nil {
		t.Errorf("polyline with non-adjacent antipodal points should validate")
	}

	// non-adjacent antipodal points.
	p4 := Polyline([]Point{
		PointFromCoords(0, 1, 0),
		PointFromCoords(0, 0, 1),
		pt,
		antiPt,
	})

	if err := p4.Validate(); err == nil {
		t.Errorf("polyline with antipodal adjacent points shouldn't validate")
	}
}

func TestPolylineIntersects(t *testing.T) {
	// PolylineInterectsEmpty
	empty := Polyline{}
	line := makePolyline("1:1, 4:4")
	if empty.Intersects(line) {
		t.Errorf("%v.Intersects(%v) = true, want false", empty, line)
	}

	// PolylineIntersectsOnePointPolyline
	line1 := makePolyline("1:1, 4:4")
	line2 := makePolyline("1:1")
	if line1.Intersects(line2) {
		t.Errorf("%v.Intersects(%v) = true, want false", line1, line2)
	}

	// PolylineIntersects
	line3 := makePolyline("1:1, 4:4")
	smallCrossing := makePolyline("1:2, 2:1")
	smallNoncrossing := makePolyline("1:2, 2:3")
	bigCrossing := makePolyline("1:2, 2:3, 4:3")
	if !line3.Intersects(smallCrossing) {
		t.Errorf("%v.Intersects(%v) = false, want true", line3, smallCrossing)
	}
	if line3.Intersects(smallNoncrossing) {
		t.Errorf("%v.Intersects(%v) = true, want false", line3, smallNoncrossing)
	}
	if !line3.Intersects(bigCrossing) {
		t.Errorf("%v.Intersects(%v) = false, want true", line3, bigCrossing)
	}

	// PolylineIntersectsAtVertex
	line4 := makePolyline("1:1, 4:4, 4:6")
	line5 := makePolyline("1:1, 1:2")
	line6 := makePolyline("5:1, 4:4, 2:2")
	if !line4.Intersects(line5) {
		t.Errorf("%v.Intersects(%v) = false, want true", line4, line5)
	}
	if !line4.Intersects(line6) {
		t.Errorf("%v.Intersects(%v) = false, want true", line4, line6)
	}

	// TestPolylineIntersectsVertexOnEdge
	horizontalLeftToRight := makePolyline("0:1, 0:3")
	verticalBottomToTop := makePolyline("-1:2, 0:2, 1:2")
	horizontalRightToLeft := makePolyline("0:3, 0:1")
	verticalTopToBottom := makePolyline("1:2, 0:2, -1:2")
	if !horizontalLeftToRight.Intersects(verticalBottomToTop) {
		t.Errorf("%v.Intersects(%v) = false, want true", horizontalLeftToRight, verticalBottomToTop)
	}
	if !horizontalLeftToRight.Intersects(verticalTopToBottom) {
		t.Errorf("%v.Intersects(%v) = false, want true", horizontalLeftToRight, verticalTopToBottom)
	}
	if !horizontalRightToLeft.Intersects(verticalBottomToTop) {
		t.Errorf("%v.Intersects(%v) = false, want true", horizontalRightToLeft, verticalBottomToTop)
	}
	if !horizontalRightToLeft.Intersects(verticalTopToBottom) {
		t.Errorf("%v.Intersects(%v) = false, want true", horizontalRightToLeft, verticalTopToBottom)
	}
}

func TestPolylineApproxEqual(t *testing.T) {
	degree := s1.Angle(1) * s1.Degree

	tests := []struct {
		a, b     string
		maxError s1.Angle
		want     bool
	}{
		{
			// Close lines, differences within maxError.
			a:        "0:0, 0:10, 5:5",
			b:        "0:0.1, -0.1:9.9, 5:5.2",
			maxError: 0.5 * degree,
			want:     true,
		},
		{
			// Close lines, differences outside maxError.
			a:        "0:0, 0:10, 5:5",
			b:        "0:0.1, -0.1:9.9, 5:5.2",
			maxError: 0.01 * degree,
			want:     false,
		},
		{
			// Same line, but different number of vertices.
			a:        "0:0, 0:10, 0:20",
			b:        "0:0, 0:20",
			maxError: 0.1 * degree,
			want:     false,
		},
		{
			// Same vertices, in different order.
			a:        "0:0, 5:5, 0:10",
			b:        "5:5, 0:10, 0:0",
			maxError: 0.1 * degree,
			want:     false,
		},
	}
	for _, test := range tests {
		a := makePolyline(test.a)
		b := makePolyline(test.b)
		if got := a.approxEqual(b, test.maxError); got != test.want {
			t.Errorf("%v.approxEqual(%v, %v) = %v, want %v", a, b, test.maxError, got, test.want)
		}
	}
}

func TestPolylineInterpolate(t *testing.T) {
	vertices := []Point{PointFromCoords(1, 0, 0),
		PointFromCoords(0, 1, 0),
		PointFromCoords(0, 1, 1),
		PointFromCoords(0, 0, 1),
	}
	line := Polyline(vertices)

	point, next := line.Interpolate(-0.1)
	if point != vertices[0] {
		t.Errorf("%v.Interpolate(%v) = %v, want %v", line, -0.1, point, vertices[0])
	}
	if next != 1 {
		t.Errorf("%v.Interpolate(%v) = %v, want %v", line, -0.1, next, 1)
	}

	want := PointFromCoords(1, math.Tan(0.2*math.Pi/2.0), 0)
	if got, _ := line.Interpolate(0.1); !got.ApproxEqual(want) {
		t.Errorf("%v.Interpolate(%v) = %v, want %v", line, 0.1, got, want)
	}

	want = PointFromCoords(1, 1, 0)
	if got, _ := line.Interpolate(0.25); !got.ApproxEqual(want) {
		t.Errorf("%v.Interpolate(%v) = %v, want %v", line, 0.25, got, want)
	}

	want = vertices[1]
	if got, _ := line.Interpolate(0.5); got != want {
		t.Errorf("%v.Interpolate(%v) = %v, want %v", line, 0.5, got, want)
	}

	want = vertices[2]
	point, next = line.Interpolate(0.75)
	if !point.ApproxEqual(want) {
		t.Errorf("%v.Interpolate(%v) = %v, want %v", line, 0.75, point, want)
	}
	if next != 3 {
		t.Errorf("%v.Interpolate(%v) = %v, want %v", line, 0.75, next, 3)
	}

	point, next = line.Interpolate(1.1)
	if point != vertices[3] {
		t.Errorf("%v.Interpolate(%v) = %v, want %v", line, 1.1, point, vertices[3])
	}
	if next != 4 {
		t.Errorf("%v.Interpolate(%v) = %v, want %v", line, 1.1, next, 4)
	}

	// Check the case where the interpolation fraction is so close to 1 that
	// the interpolated point is identical to the last vertex.
	vertices2 := []Point{PointFromCoords(1, 1, 1),
		PointFromCoords(1, 1, 1+1e-15),
		PointFromCoords(1, 1, 1+2e-15),
	}
	shortLine := Polyline(vertices2)

	point, next = shortLine.Interpolate(1.0 - 2e-16)
	if point != vertices2[2] {
		t.Errorf("%v.Interpolate(%v) = %v, want %v", shortLine, 1.0-2e-16, point, vertices2[2])
	}
	if next != 3 {
		t.Errorf("%v.Interpolate(%v) = %v, want %v", shortLine, 1.0-2e-16, next, 3)
	}
}

func TestPolylineUninterpolate(t *testing.T) {
	vertices := []Point{PointFromCoords(1, 0, 0)}
	line := Polyline(vertices)
	if got, want := line.Uninterpolate(PointFromCoords(0, 1, 0), 1), 0.0; !float64Eq(got, want) {
		t.Errorf("Uninterpolate on a polyline with 2 or fewer vertices should return 0, got %v", got)
	}

	vertices = append(vertices,
		PointFromCoords(0, 1, 0),
		PointFromCoords(0, 1, 1),
		PointFromCoords(0, 0, 1),
	)
	line = Polyline(vertices)

	interpolated, nextVertex := line.Interpolate(-0.1)
	if got, want := line.Uninterpolate(interpolated, nextVertex), 0.0; !float64Eq(got, want) {
		t.Errorf("line.Uninterpolate(%v, %d) = %v, want %v", interpolated, nextVertex, got, want)
	}
	interpolated, nextVertex = line.Interpolate(0.0)
	if got, want := line.Uninterpolate(interpolated, nextVertex), 0.0; !float64Eq(got, want) {
		t.Errorf("line.Uninterpolate(%v, %d) = %v, want %v", interpolated, nextVertex, got, want)
	}
	interpolated, nextVertex = line.Interpolate(0.5)
	if got, want := line.Uninterpolate(interpolated, nextVertex), 0.5; !float64Eq(got, want) {
		t.Errorf("line.Uninterpolate(%v, %d) = %v, want %v", interpolated, nextVertex, got, want)
	}
	interpolated, nextVertex = line.Interpolate(0.75)
	if got, want := line.Uninterpolate(interpolated, nextVertex), 0.75; !float64Eq(got, want) {
		t.Errorf("line.Uninterpolate(%v, %d) = %v, want %v", interpolated, nextVertex, got, want)
	}
	interpolated, nextVertex = line.Interpolate(1.1)
	if got, want := line.Uninterpolate(interpolated, nextVertex), 1.0; !float64Eq(got, want) {
		t.Errorf("line.Uninterpolate(%v, %d) = %v, want %v", interpolated, nextVertex, got, want)
	}

	// Check that the return value is clamped to 1.0.
	if got, want := line.Uninterpolate(PointFromCoords(0, 1, 0), len(line)), 1.0; !float64Eq(got, want) {
		t.Errorf("line.Uninterpolate(%v, %d) = %v, want %v", PointFromCoords(0, 1, 0), len(line), got, want)
	}
}

// TODO(roberts): Test differences from C++:
// InitToSnapped
// InitToSimplified
//
// PolylineCoveringTest
//    PolylineOverlapsSelf
//    PolylineDoesNotOverlapReverse
//    PolylineOverlapsEquivalent
//    ShortCoveredByLong
//    PartialOverlapOnly
//    ShortBacktracking
//    LongBacktracking
//    IsResilientToDuplicatePoints
//    CanChooseBetweenTwoPotentialStartingPoints
//    StraightAndWigglyPolylinesCoverEachOther
//    MatchStartsAtLastVertex
//    MatchStartsAtDuplicatedLastVertex
//    EmptyPolylines
