// Copyright 2018 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"
	"testing"

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

func TestPlateCarreeProjectionInterpolate(t *testing.T) {
	a := r2.Point{X: 1.234, Y: -5.456e-20}
	b := r2.Point{X: 2.1234e-20, Y: 7.456}
	tests := []struct {
		dist float64
		a, b r2.Point
		want r2.Point
	}{
		{
			// Test that coordinates and/or arguments are not accidentally reversed.
			0.25,
			r2.Point{X: 1, Y: 5},
			r2.Point{X: 3, Y: 9},
			r2.Point{X: 1.5, Y: 6},
		},
		{
			// Test extrapolation.
			-2,
			r2.Point{X: 1, Y: 0},
			r2.Point{X: 3, Y: 0},
			r2.Point{X: -3, Y: 0},
		},
		// Check that interpolation is exact at both endpoints.
		{0, a, b, a},
		{1, a, b, b},
	}
	proj := NewPlateCarreeProjection(180)

	for _, test := range tests {
		if got := proj.Interpolate(test.dist, test.a, test.b); got != test.want {
			t.Errorf("proj.Interpolate(%v, %v, %v) = %v, want %v", test.dist, test.a, test.b, got, test.want)
		}
	}
}

func TestPlateCarreeProjectionProjectUnproject(t *testing.T) {
	tests := []struct {
		have Point
		want r2.Point
	}{
		{Point{r3.Vector{X: 1, Y: 0, Z: 0}}, r2.Point{X: 0, Y: 0}},
		{Point{r3.Vector{X: -1, Y: 0, Z: 0}}, r2.Point{X: 180, Y: 0}},
		{Point{r3.Vector{X: 0, Y: 1, Z: 0}}, r2.Point{X: 90, Y: 0}},
		{Point{r3.Vector{X: 0, Y: -1, Z: 0}}, r2.Point{X: -90, Y: 0}},
		{Point{r3.Vector{X: 0, Y: 0, Z: 1}}, r2.Point{X: 0, Y: 90}},
		{Point{r3.Vector{X: 0, Y: 0, Z: -1}}, r2.Point{X: 0, Y: -90}},
	}

	proj := NewPlateCarreeProjection(180)

	for _, test := range tests {
		if got := proj.Project(test.have); !r2PointsApproxEqual(test.want, got, epsilon) {
			t.Errorf("proj.Project(%v) = %v, want %v", test.have, got, test.want)
		}
		if got := proj.Unproject(test.want); !got.ApproxEqual(test.have) {
			t.Errorf("proj.Unproject(%v) = %v, want %v", test.want, got, test.have)
		}
	}
}

func TestMercatorProjectionProjectUnproject(t *testing.T) {
	tests := []struct {
		have Point
		want r2.Point
	}{
		{Point{r3.Vector{X: 1, Y: 0, Z: 0}}, r2.Point{X: 0, Y: 0}},
		{Point{r3.Vector{X: -1, Y: 0, Z: 0}}, r2.Point{X: 180, Y: 0}},
		{Point{r3.Vector{X: 0, Y: 1, Z: 0}}, r2.Point{X: 90, Y: 0}},
		{Point{r3.Vector{X: 0, Y: -1, Z: 0}}, r2.Point{X: -90, Y: 0}},
		// Test one arbitrary point as a sanity check.
		{PointFromLatLng(LatLng{1, 0}), r2.Point{X: 0, Y: 70.255578967830246}},
	}

	proj := NewMercatorProjection(180)

	for _, test := range tests {
		if got := proj.Project(test.have); !r2PointsApproxEqual(test.want, got, epsilon) {
			t.Errorf("proj.Project(%v) = %v, want %v", test.have, got, test.want)
		}
		if got := proj.Unproject(test.want); !got.ApproxEqual(test.have) {
			t.Errorf("proj.Unproject(%v) = %v, want %v", test.want, got, test.have)
		}
	}

	// two cases have values that should be infinities, so the equality tests
	// need to be inf ready.
	testsInf := []struct {
		have Point
		want r2.Point
	}{
		{Point{r3.Vector{X: 0, Y: 0, Z: 1}}, r2.Point{X: 0, Y: math.Inf(1)}},
		{Point{r3.Vector{X: 0, Y: 0, Z: -1}}, r2.Point{X: 0, Y: math.Inf(-1)}},
	}
	for _, test := range testsInf {
		got := proj.Project(test.have)
		if ((math.IsInf(test.want.X, 1) && !math.IsInf(got.X, 1)) ||
			(math.IsInf(test.want.X, -1) && !math.IsInf(got.X, -1))) ||
			((math.IsInf(test.want.Y, 1) && !math.IsInf(got.Y, 1)) ||
				(math.IsInf(test.want.Y, -1) && !math.IsInf(got.Y, -1))) {
			t.Errorf("proj.Project(%v) = %v, want %v", test.have, got, test.want)
		}
	}
}
