// Copyright 2015 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"
)

func TestChordAngleBasics(t *testing.T) {
	var zeroChord ChordAngle
	tests := []struct {
		a, b     ChordAngle
		lessThan bool
		equal    bool
	}{
		{NegativeChordAngle, NegativeChordAngle, false, true},
		{NegativeChordAngle, zeroChord, true, false},
		{NegativeChordAngle, StraightChordAngle, true, false},
		{NegativeChordAngle, InfChordAngle(), true, false},

		{zeroChord, zeroChord, false, true},
		{zeroChord, StraightChordAngle, true, false},
		{zeroChord, InfChordAngle(), true, false},

		{StraightChordAngle, StraightChordAngle, false, true},
		{StraightChordAngle, InfChordAngle(), true, false},

		{InfChordAngle(), InfChordAngle(), false, true},
		{InfChordAngle(), StraightChordAngle, false, false},
	}

	for _, test := range tests {
		if got := test.a < test.b; got != test.lessThan {
			t.Errorf("%v should be less than %v", test.a, test.b)
		}
		if got := test.a == test.b; got != test.equal {
			t.Errorf("%v should be equal to %v", test.a, test.b)
		}
	}
}

func TestChordAngleAngleEquality(t *testing.T) {
	var zeroAngle Angle
	var zeroChord ChordAngle

	if InfAngle() != InfChordAngle().Angle() {
		t.Errorf("Infinite ChordAngle to Angle = %v, want %v", InfChordAngle().Angle(), InfAngle())
	}

	oneEighty := 180 * Degree
	if oneEighty != StraightChordAngle.Angle() {
		t.Errorf("Right ChordAngle to degrees = %v, want %v", StraightChordAngle.Angle(), oneEighty)
	}

	if zeroAngle != zeroChord.Angle() {
		t.Errorf("Zero ChordAngle to Angle = %v, want %v", zeroChord.Angle(), zeroAngle)
	}

	d := RightChordAngle.Angle().Degrees()
	if !float64Near(90, d, 1e-13) {
		t.Errorf("Right ChordAngle to degrees = %v, want %v", d, 90)
	}

}

func TestChordAngleIsFunctions(t *testing.T) {
	var zeroChord ChordAngle
	tests := []struct {
		have       ChordAngle
		isNegative bool
		isZero     bool
		isInf      bool
		isSpecial  bool
	}{
		{zeroChord, false, true, false, false},
		{NegativeChordAngle, true, false, false, true},
		{StraightChordAngle, false, false, false, false},
		{InfChordAngle(), false, false, true, true},
	}

	for _, test := range tests {
		if got := test.have < 0; got != test.isNegative {
			t.Errorf("%v.isNegative() = %t, want %t", test.have, got, test.isNegative)
		}
		if got := test.have == 0; got != test.isZero {
			t.Errorf("%v.isZero() = %t, want %t", test.have, got, test.isZero)
		}
		if got := test.have.IsInfinity(); got != test.isInf {
			t.Errorf("%v.IsInfinity() = %t, want %t", test.have, got, test.isInf)
		}
		if got := test.have.isSpecial(); got != test.isSpecial {
			t.Errorf("%v.isSpecial() = %t, want %t", test.have, got, test.isSpecial)
		}
	}
}

func TestChordAngleSuccessor(t *testing.T) {
	if got, want := NegativeChordAngle.Successor(), ChordAngle(0.0); got != want {
		t.Errorf("NegativeChordAngle.Successor() = %v, want %v", got, want)
	}
	if got, want := StraightChordAngle.Successor(), InfChordAngle(); got != want {
		t.Errorf("StraightChordAngle.Successor() = %v, want %v", got, want)
	}
	if got, want := InfChordAngle().Successor(), InfChordAngle(); got != want {
		t.Errorf("InfChordAngle.Successor() = %v, want %v", got, want)
	}
	x := NegativeChordAngle
	for i := 0; i < 10; i++ {
		if x >= x.Successor() {
			t.Errorf("%d. %0.24f >= %0.24f.Successor() = %0.24f, want <", i, x, x, x.Successor())
		}
		x = x.Successor()
	}
}

func TestChordAnglePredecessor(t *testing.T) {
	if got, want := InfChordAngle().Predecessor(), StraightChordAngle; got != want {
		t.Errorf("InfChordAngle.Predecessor() = %v, want %v", got, want)
	}
	if got, want := (ChordAngle(0)).Predecessor(), NegativeChordAngle; got != want {
		t.Errorf("Zero ChordAngle.Predecessor() = %v, want %v", got, want)
	}
	if got, want := NegativeChordAngle.Predecessor(), NegativeChordAngle; got != want {
		t.Errorf("NegativeChordAngle.Predecessor() = %v, want %v", got, want)
	}
	x := InfChordAngle()
	for i := 0; i < 10; i++ {
		if x <= x.Predecessor() {
			t.Errorf("%v <= %v.Predecessor() = %v, want <", x, x, x.Predecessor())
		}
		x = x.Predecessor()
	}
}

func TestChordAngleFromAngle(t *testing.T) {
	for _, angle := range []float64{0, 1, -1, math.Pi} {
		if got := ChordAngleFromAngle(Angle(angle)).Angle().Radians(); got != angle {
			t.Errorf("ChordAngleFromAngle(Angle(%v)) = %v, want %v", angle, got, angle)
		}
	}

	if got := ChordAngleFromAngle(Angle(math.Pi)); got != StraightChordAngle {
		t.Errorf("a ChordAngle from an Angle of π = %v, want %v", got, StraightChordAngle)
	}

	if InfAngle() != ChordAngleFromAngle(InfAngle()).Angle() {
		t.Errorf("converting infinite Angle to ChordAngle should yield infinite Angle")
	}
}

func TestChordAngleArithmetic(t *testing.T) {
	var (
		zero      ChordAngle
		degree30  = ChordAngleFromAngle(30 * Degree)
		degree60  = ChordAngleFromAngle(60 * Degree)
		degree90  = ChordAngleFromAngle(90 * Degree)
		degree120 = ChordAngleFromAngle(120 * Degree)
		degree180 = StraightChordAngle
	)

	addTests := []struct {
		a, b ChordAngle
		want ChordAngle
	}{
		{zero, zero, zero},
		{degree60, zero, degree60},
		{zero, degree60, degree60},
		{degree30, degree60, degree90},
		{degree60, degree30, degree90},
		{degree180, zero, degree180},
		{degree60, degree30, degree90},
		{degree90, degree90, degree180},
		{degree120, degree90, degree180},
		{degree120, degree120, degree180},
		{degree30, degree180, degree180},
		{degree180, degree180, degree180},
	}

	subTests := []struct {
		a, b ChordAngle
		want ChordAngle
	}{
		{zero, zero, zero},
		{degree60, degree60, zero},
		{degree180, degree180, zero},
		{zero, degree60, zero},
		{degree30, degree90, zero},
		{degree90, degree30, degree60},
		{degree90, degree60, degree30},
		{degree180, zero, degree180},
	}

	for _, test := range addTests {
		if got := float64(test.a.Add(test.b)); !float64Eq(got, float64(test.want)) {
			t.Errorf("%v.Add(%v) = %0.24f, want %0.24f", test.a.Angle().Degrees(), test.b.Angle().Degrees(), got, test.want)
		}
	}
	for _, test := range subTests {
		if got := float64(test.a.Sub(test.b)); !float64Eq(got, float64(test.want)) {
			t.Errorf("%v.Sub(%v) = %0.24f, want %0.24f", test.a.Angle().Degrees(), test.b.Angle().Degrees(), got, test.want)
		}
	}
}

func TestChordAngleTrigonometry(t *testing.T) {
	// Because of the way the math works out, the 9/10th's case has slightly more
	// difference than all the other computations, so this gets a more generous
	// epsilon to deal with that.
	const epsilon = 1e-14
	const iters = 40
	for iter := 0; iter <= iters; iter++ {
		radians := math.Pi * float64(iter) / float64(iters)
		angle := ChordAngleFromAngle(Angle(radians))
		if !float64Near(math.Sin(radians), angle.Sin(), epsilon) {
			t.Errorf("(%d/%d)*π. %v.Sin() = %v, want %v", iter, iters, angle, angle.Sin(), math.Sin(radians))
		}
		if !float64Near(math.Cos(radians), angle.Cos(), epsilon) {
			t.Errorf("(%d/%d)*π. %v.Cos() = %v, want %v", iter, iters, angle, angle.Cos(), math.Cos(radians))
		}
		// Since tan(x) is unbounded near pi/4, we map the result back to an
		// angle before comparing. The assertion is that the result is equal to
		// the tangent of a nearby angle.
		if !float64Near(math.Atan(math.Tan(radians)), math.Atan(angle.Tan()), epsilon) {
			t.Errorf("(%d/%d)*π. %v.Tan() = %v, want %v", iter, iters, angle, angle.Tan(), math.Tan(radians))
		}
	}

	// Unlike Angle, ChordAngle can represent 90 and 180 degrees exactly.
	angle90 := ChordAngleFromSquaredLength(2)
	angle180 := ChordAngleFromSquaredLength(4)
	if !float64Eq(1, angle90.Sin()) {
		t.Errorf("%v.Sin() = %v, want 1", angle90, angle90.Sin())
	}
	if !float64Eq(0, angle90.Cos()) {
		t.Errorf("%v.Cos() = %v, want 0", angle90, angle90.Cos())
	}
	if !math.IsInf(angle90.Tan(), 0) {
		t.Errorf("%v.Tan() should be infinite, but was not.", angle90)
	}
	if !float64Eq(0, angle180.Sin()) {
		t.Errorf("%v.Sin() = %v, want 0", angle180, angle180.Sin())
	}
	if !float64Eq(-1, angle180.Cos()) {
		t.Errorf("%v.Cos() = %v, want -1", angle180, angle180.Cos())
	}
	if !float64Eq(0, angle180.Tan()) {
		t.Errorf("%v.Tan() = %v, want 0", angle180, angle180.Tan())
	}
}

func TestChordAngleExpanded(t *testing.T) {
	var zero ChordAngle

	tests := []struct {
		have ChordAngle
		add  float64
		want ChordAngle
	}{
		{NegativeChordAngle, 5, NegativeChordAngle.Expanded(5)},
		{InfChordAngle(), -5, InfChordAngle()},
		{StraightChordAngle, 5, ChordAngleFromSquaredLength(5)},
		{zero, -5, zero},
		{ChordAngleFromSquaredLength(1.25), 0.25, ChordAngleFromSquaredLength(1.5)},
		{ChordAngleFromSquaredLength(0.75), 0.25, ChordAngleFromSquaredLength(1)},
	}

	for _, test := range tests {
		if got := test.have.Expanded(test.add); got != test.want {
			t.Errorf("%v.Expanded(%v) = %v, want %v", test.have, test.add, got, test.want)
		}
	}
}
