// Copyright 2014 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 s1

import (
	"math"
	"testing"
)

// float64Eq reports whether the two values are within the default epsilon.
func float64Eq(x, y float64) bool {
	return float64Near(x, y, epsilon)
}

// float64Near reports whether the two values are within the specified epsilon.
func float64Near(x, y, eps float64) bool {
	return math.Abs(x-y) <= eps
}

func TestEmptyValue(t *testing.T) {
	var a Angle
	if rad := a.Radians(); rad != 0 {
		t.Errorf("Empty value of Angle was %v, want 0", rad)
	}
}

func TestPiRadiansExactly180Degrees(t *testing.T) {
	if rad := (math.Pi * Radian).Radians(); rad != math.Pi {
		t.Errorf("(π * Radian).Radians() was %v, want π", rad)
	}
	if deg := (math.Pi * Radian).Degrees(); deg != 180 {
		t.Errorf("(π * Radian).Degrees() was %v, want 180", deg)
	}
	if rad := (180 * Degree).Radians(); rad != math.Pi {
		t.Errorf("(180 * Degree).Radians() was %v, want π", rad)
	}
	if deg := (180 * Degree).Degrees(); deg != 180 {
		t.Errorf("(180 * Degree).Degrees() was %v, want 180", deg)
	}

	if deg := (math.Pi / 2 * Radian).Degrees(); deg != 90 {
		t.Errorf("(π/2 * Radian).Degrees() was %v, want 90", deg)
	}

	// Check negative angles.
	if deg := (-math.Pi / 2 * Radian).Degrees(); deg != -90 {
		t.Errorf("(-π/2 * Radian).Degrees() was %v, want -90", deg)
	}
	if rad := (-45 * Degree).Radians(); rad != -math.Pi/4 {
		t.Errorf("(-45 * Degree).Radians() was %v, want -π/4", rad)
	}
}

func TestE5E6E7Representation(t *testing.T) {
	// NOTE(dsymonds): This first test gives a variance in the 16th decimal place. I should track that down.
	exp, act := (-45 * Degree).Radians(), (-4500000 * E5).Radians()
	if math.Abs(exp-act) > 1e-15 {
		t.Errorf("(-4500000 * E5).Radians() was %v, want %v", act, exp)
	}
	if exp, act := (-60 * Degree).Radians(), (-60000000 * E6).Radians(); exp != act {
		t.Errorf("(-60000000 * E6).Radians() was %v, want %v", act, exp)
	}
	if exp, act := (75 * Degree).Radians(), (750000000 * E7).Radians(); exp != act {
		t.Errorf("(-750000000 * E7).Radians() was %v, want %v", act, exp)
	}

	if exp, act := int32(-17256123), (-172.56123 * Degree).E5(); exp != act {
		t.Errorf("(-172.56123°).E5() was %v, want %v", act, exp)
	}
	if exp, act := int32(12345678), (12.345678 * Degree).E6(); exp != act {
		t.Errorf("(12.345678°).E6() was %v, want %v", act, exp)
	}
	if exp, act := int32(-123456789), (-12.3456789 * Degree).E7(); exp != act {
		t.Errorf("(-12.3456789°).E7() was %v, want %v", act, exp)
	}

	roundingTests := []struct {
		have Angle
		want int32
	}{
		{0.500000001, 1},
		{-0.500000001, -1},
		{0.499999999, 0},
		{-0.499999999, 0},
	}
	for _, test := range roundingTests {
		if act := (test.have * 1e-5 * Degree).E5(); test.want != act {
			t.Errorf("(%v°).E5() was %v, want %v", test.have, act, test.want)
		}
		if act := (test.have * 1e-6 * Degree).E6(); test.want != act {
			t.Errorf("(%v°).E6() was %v, want %v", test.have, act, test.want)
		}
		if act := (test.have * 1e-7 * Degree).E7(); test.want != act {
			t.Errorf("(%v°).E7() was %v, want %v", test.have, act, test.want)
		}
	}
}

func TestNormalizeCorrectlyCanonicalizesAngles(t *testing.T) {
	tests := []struct {
		in, want float64 // both in degrees
	}{
		{360, 0},
		{-90, -90},
		{-180, 180},
		{180, 180},
		{540, 180},
		{-270, 90},
	}
	for _, test := range tests {
		deg := (Angle(test.in) * Degree).Normalized().Degrees()
		if deg != test.want {
			t.Errorf("Normalized %.0f° = %v, want %v", test.in, deg, test.want)
		}
	}
}

func TestAngleString(t *testing.T) {
	if s, exp := (180 * Degree).String(), "180.0000000"; s != exp {
		t.Errorf("(180°).String() = %q, want %q", s, exp)
	}
}

func TestDegreesVsRadians(t *testing.T) {
	// This test tests the exactness of specific values between degrees and radians.
	for k := -8; k <= 8; k++ {
		if got, want := Angle(45*k)*Degree, Angle((float64(k)*math.Pi)/4)*Radian; got != want {
			t.Errorf("45°*%d != (%d*π)/4 radians (%f vs %f)", k, k, got, want)
		}

		if got, want := (Angle(45*k) * Degree).Degrees(), float64(45*k); got != want {
			t.Errorf("Angle(45°*%d).Degrees() != 45*%d, (%f vs %f)", k, k, got, want)
		}
	}

	for k := uint64(0); k < 30; k++ {
		m := 1 << k
		n := float64(m)
		for _, test := range []struct{ deg, rad float64 }{
			{180, 1},
			{60, 3},
			{36, 5},
			{20, 9},
			{4, 45},
		} {
			if got, want := Angle(test.deg/n)*Degree, Angle(math.Pi/(test.rad*n))*Radian; got != want {
				t.Errorf("%v°/%d != π/%v*%d rad (%f vs %f)", test.deg, m, test.rad, m, got, want)
			}
		}
	}

	// We also spot check a non-identity.
	if got := (60 * Degree).Degrees(); float64Eq(got, 60) {
		t.Errorf("Angle(60).Degrees() == 60, but should not (%f vs %f)", got, 60.0)
	}
}

// TODO(roberts): Differences from C++
//   Benchmarking code.
