package cesiumlanguagewritertests;


import agi.foundation.compatibility.*;
import agi.foundation.compatibility.Action;
import agi.foundation.compatibility.AssertHelper;
import agi.foundation.compatibility.CultureInfoHelper;
import agi.foundation.compatibility.DoubleHelper;
import agi.foundation.compatibility.IEquatable;
import agi.foundation.compatibility.NotFiniteNumberException;
import agi.foundation.compatibility.TestContextRule;
import agi.foundation.TypeLiteral;
import cesiumlanguagewriter.*;
import javax.annotation.Nonnull;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.runners.MethodSorters;
import org.junit.Test;

/**
 * Tests the {@link Cartesian} type.
 */
@SuppressWarnings({
    "unused",
    "deprecation",
    "serial"
})
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestCartesian {
    /**
    * Tests that initialization of and access to the type's values works correctly.
    */
    @Test
    public final void testHoldValue() {
        Cartesian test = new Cartesian(1.0, 2.0, 3.0);
        AssertHelper.assertEquals(1.0, test.getX());
        AssertHelper.assertEquals(2.0, test.getY());
        AssertHelper.assertEquals(3.0, test.getZ());
    }

    /**
    * Tests implicit conversion from {@link UnitCartesian} coordinates.
    */
    @Test
    public final void testConversionFromUnitCartesian() {
        UnitCartesian unit = new UnitCartesian(1.0, 1.0, 1.0);
        Cartesian test = Cartesian.toCartesian(unit);
        AssertHelper.assertEquals(unit.getX(), test.getX());
        AssertHelper.assertEquals(unit.getY(), test.getY());
        AssertHelper.assertEquals(unit.getZ(), test.getZ());
    }

    /**
    * Tests the equality and inequality methods and operators.
    */
    @Test
    public final void testEquality() {
        Cartesian first = new Cartesian(1.0, 2.0, 3.0);
        Cartesian second = new Cartesian(1.0, 2.0, 3.0);
        AssertHelper.assertEquals(first, second);
        AssertHelper.assertEquals(second, first);
        Assert.assertTrue(Cartesian.equals(first, second));
        Assert.assertTrue(Cartesian.equals(second, first));
        Assert.assertFalse(Cartesian.notEquals(first, second));
        Assert.assertFalse(Cartesian.notEquals(second, first));
        Assert.assertTrue(first.equalsType(second));
        Assert.assertTrue(second.equalsType(first));
        second = new Cartesian(0.0, 2.0, 3.0);
        AssertHelper.assertNotEqual(first, second);
        AssertHelper.assertNotEqual(second, first);
        Assert.assertFalse(Cartesian.equals(first, second));
        Assert.assertFalse(Cartesian.equals(second, first));
        Assert.assertTrue(Cartesian.notEquals(first, second));
        Assert.assertTrue(Cartesian.notEquals(second, first));
        Assert.assertFalse(first.equalsType(second));
        Assert.assertFalse(second.equalsType(first));
        second = new Cartesian(1.0, 0.0, 3.0);
        AssertHelper.assertNotEqual(first, second);
        AssertHelper.assertNotEqual(second, first);
        Assert.assertFalse(Cartesian.equals(first, second));
        Assert.assertFalse(Cartesian.equals(second, first));
        Assert.assertTrue(Cartesian.notEquals(first, second));
        Assert.assertTrue(Cartesian.notEquals(second, first));
        Assert.assertFalse(first.equalsType(second));
        Assert.assertFalse(second.equalsType(first));
        second = new Cartesian(1.0, 2.0, 0.0);
        AssertHelper.assertNotEqual(first, second);
        AssertHelper.assertNotEqual(second, first);
        Assert.assertFalse(Cartesian.equals(first, second));
        Assert.assertFalse(Cartesian.equals(second, first));
        Assert.assertTrue(Cartesian.notEquals(first, second));
        Assert.assertTrue(Cartesian.notEquals(second, first));
        Assert.assertFalse(first.equalsType(second));
        Assert.assertFalse(second.equalsType(first));
    }

    /**
    * Tests the {@link Cartesian#equalsEpsilon} method.
    */
    @Test
    public final void testEqualsEpsilon() {
        Cartesian first = new Cartesian(1e-1, 1e-2, 1e-3);
        Cartesian second = new Cartesian(1.1e-1, 1.1e-2, 1.1e-3);
        Assert.assertTrue(second.equalsEpsilon(first, 1e-1));
        Assert.assertTrue(second.equalsEpsilon(first, 1e-2));
        Assert.assertFalse(second.equalsEpsilon(first, 1e-3));
        Assert.assertFalse(second.equalsEpsilon(first, 1e-4));
        Assert.assertFalse(second.equalsEpsilon(first, 1e-5));
    }

    /**
    * Tests that the {@link Cartesian#equalsEpsilon} method returns true
    when the difference is exactly epsilon.
    */
    @Test
    public final void testEqualsEpsilonExact() {
        Cartesian first = new Cartesian(0.1, 0.1, 0.1);
        Cartesian second = new Cartesian(0.1, 0.1, 0.1);
        Assert.assertTrue(second.equalsEpsilon(first, 0.0));
    }

    /**
    * Tests to ensure the equality fails when comparing incorrect type.
    */
    @Test
    public final void testEqualityWithWrongType() {
        Cartesian first = new Cartesian(1.0, 2.0, 3.0);
        Cartographic second = new Cartographic(1.0, 2.0, 3.0);
        // ReSharper disable once SuspiciousTypeConversion.Global
        Assert.assertFalse(first.equals(second));
    }

    /**
    * Tests the {@code Magnitude} ({@link Cartesian#getMagnitude get}) property.
    */
    @Test
    public final void testMagnitude() {
        Cartesian test = new Cartesian(2.0, 3.0, 6.0);
        AssertHelper.assertEquals(7.0, test.getMagnitude());
    }

    /**
    * Tests the {@link Cartesian#normalize()} method.
    */
    @Test
    public final void testNormalize() {
        Cartesian test = new Cartesian(2.0, 3.0, 6.0);
        UnitCartesian unit = test.normalize();
        AssertHelper.assertEquals(2.0 / 7.0, unit.getX());
        AssertHelper.assertEquals(3.0 / 7.0, unit.getY());
        AssertHelper.assertEquals(6.0 / 7.0, unit.getZ());
    }

    /**
    * Tests that normalization of a {@link Cartesian} with zero magnitude
    produces an {@link UnsupportedCaseException}.
    */
    @Test
    public final void testNormalizeOfZeroMagnitude() {
        final Cartesian test = Cartesian.getZero();
        AssertHelper.<ArithmeticException> assertThrows(new TypeLiteral<ArithmeticException>() {}, new Action() {
            public void invoke() {
                UnitCartesian unused = test.normalize();
            }
        });
    }

    /**
    * Tests that normalization of a {@link Cartesian} with infinite magnitude
    produces an {@link NotFiniteNumberException}.
    */
    @Test
    public final void testNormalizeOfInfiniteMagnitude() {
        final Cartesian test = new Cartesian(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
        AssertHelper.<NotFiniteNumberException> assertThrows(new TypeLiteral<NotFiniteNumberException>() {}, new Action() {
            public void invoke() {
                UnitCartesian unused = test.normalize();
            }
        });
    }

    /**
    * Tests the {@code IsUndefined} ({@link Cartesian#getIsUndefined get}) method.
    */
    @Test
    public final void testIsUndefined() {
        Assert.assertFalse(new Cartesian(1.0, 1.0, 1.0).getIsUndefined());
        Assert.assertTrue(Cartesian.getUndefined().getIsUndefined());
        Assert.assertTrue(new Cartesian(Double.NaN, 1.0, 1.0).getIsUndefined());
        Assert.assertTrue(new Cartesian(1.0, Double.NaN, 1.0).getIsUndefined());
        Assert.assertTrue(new Cartesian(1.0, 1.0, Double.NaN).getIsUndefined());
    }

    /**
    * Tests the {@code MostOrthogonalAxis} ({@link Cartesian#getMostOrthogonalAxis get}) method.
    */
    @Test
    public final void testMostOrthogonalAxis() {
        Cartesian cartesian = new Cartesian(1.0, 2.0, 3.0);
        AssertHelper.assertEquals(UnitCartesian.getUnitX(), cartesian.getMostOrthogonalAxis());
        cartesian = new Cartesian(2.0, 3.0, 1.0);
        AssertHelper.assertEquals(UnitCartesian.getUnitZ(), cartesian.getMostOrthogonalAxis());
        cartesian = new Cartesian(3.0, 1.0, 2.0);
        AssertHelper.assertEquals(UnitCartesian.getUnitY(), cartesian.getMostOrthogonalAxis());
    }

    /**
    * Tests negation of a set of coordinates.
    */
    @Test
    public final void testNegation() {
        Cartesian c = Cartesian.negate(new Cartesian(1.0, 2.0, 3.0));
        AssertHelper.assertEquals(-1.0, c.getX());
        AssertHelper.assertEquals(-2.0, c.getY());
        AssertHelper.assertEquals(-3.0, c.getZ());
    }

    /**
    * Tests the addition methods and operators.
    */
    @Test
    public final void testAdd() {
        Cartesian original = new Cartesian(10.0, 20.0, 30.0);
        Cartesian toAdd = new Cartesian(1.0, 2.0, 3.0);
        Cartesian result = Cartesian.add(original, toAdd);
        AssertHelper.assertEquals(11.0, result.getX());
        AssertHelper.assertEquals(22.0, result.getY());
        AssertHelper.assertEquals(33.0, result.getZ());
        result = original.add(toAdd);
        AssertHelper.assertEquals(11.0, result.getX());
        AssertHelper.assertEquals(22.0, result.getY());
        AssertHelper.assertEquals(33.0, result.getZ());
    }

    /**
    * Tests the subtraction methods and operators.
    */
    @Test
    public final void testSubtract() {
        Cartesian original = new Cartesian(10.0, 20.0, 30.0);
        Cartesian toSubtract = new Cartesian(1.0, 2.0, 3.0);
        Cartesian result = Cartesian.subtract(original, toSubtract);
        AssertHelper.assertEquals(9.0, result.getX());
        AssertHelper.assertEquals(18.0, result.getY());
        AssertHelper.assertEquals(27.0, result.getZ());
        result = original.subtract(toSubtract);
        AssertHelper.assertEquals(9.0, result.getX());
        AssertHelper.assertEquals(18.0, result.getY());
        AssertHelper.assertEquals(27.0, result.getZ());
    }

    /**
    * Tests the multiplication methods and operators.
    */
    @Test
    public final void testMultiply() {
        Cartesian original = new Cartesian(1.0, 2.0, 3.0);
        Cartesian multiplied = Cartesian.multiply(original, 5.0);
        AssertHelper.assertEquals(5.0, multiplied.getX());
        AssertHelper.assertEquals(10.0, multiplied.getY());
        AssertHelper.assertEquals(15.0, multiplied.getZ());
        multiplied = Cartesian.multiply(5.0, original);
        AssertHelper.assertEquals(5.0, multiplied.getX());
        AssertHelper.assertEquals(10.0, multiplied.getY());
        AssertHelper.assertEquals(15.0, multiplied.getZ());
        multiplied = original.multiply(5.0);
        AssertHelper.assertEquals(5.0, multiplied.getX());
        AssertHelper.assertEquals(10.0, multiplied.getY());
        AssertHelper.assertEquals(15.0, multiplied.getZ());
    }

    /**
    * Tests the multiplication methods and operators.
    */
    @Test
    public final void testDivide() {
        Cartesian original = new Cartesian(2.0, 4.0, 6.0);
        Cartesian result = Cartesian.divide(original, 2.0);
        AssertHelper.assertEquals(1.0, result.getX());
        AssertHelper.assertEquals(2.0, result.getY());
        AssertHelper.assertEquals(3.0, result.getZ());
        result = original.divide(2.0);
        AssertHelper.assertEquals(1.0, result.getX());
        AssertHelper.assertEquals(2.0, result.getY());
        AssertHelper.assertEquals(3.0, result.getZ());
    }

    /**
    * Tests the {@link Cartesian#dot} method.
    */
    @Test
    public final void testDotProduct() {
        Cartesian first = new Cartesian(1.0, 3.0, -2.0);
        Cartesian second = new Cartesian(4.0, -2.0, -1.0);
        AssertHelper.assertEquals(0, first.dot(second));
        AssertHelper.assertEquals(0, second.dot(first));
    }

    /**
    * Tests the {@link Cartesian#cross} method.
    */
    @Test
    public final void testCrossProduct() {
        double angle = Math.PI / 4.0;
        double cos = Math.cos(angle / 2.0);
        double sin = Math.sin(angle / 2.0);
        double a = cos * cos - sin * sin / 3.0;
        double b = 2.0 * (sin * sin + sin * cos * Math.sqrt(3.0)) / 3.0;
        double c = 2.0 * (sin * sin - sin * cos * Math.sqrt(3.0)) / 3.0;
        // The three vectors here are the orthonormal set obtained by rotating
        // the x-axis, y-axis, and z-axis through an angle of 45 degrees about
        // the (1,1,1) vector.
        Cartesian first = new Cartesian(a, b, c);
        Cartesian second = new Cartesian(c, a, b);
        Cartesian third = new Cartesian(b, c, a);
        Cartesian result = first.cross(second);
        AssertHelper.assertEquals(third.getX(), result.getX(), Constants.Epsilon14);
        AssertHelper.assertEquals(third.getY(), result.getY(), Constants.Epsilon14);
        AssertHelper.assertEquals(third.getZ(), result.getZ(), Constants.Epsilon14);
    }

    /**
    * Tests rotation by a {@link UnitQuaternion}.
    */
    @Test
    public final void testRotateByUnitQuaternion() {
        double angle = Math.PI / 3.0;
        // half angle of 120 degree rotation
        double cos = Math.cos(angle);
        double sin = Math.sin(angle);
        Cartesian axis = Cartesian.toCartesian(new Cartesian(1.0, 1.0, 1.0).normalize());
        // unit vector along [1,1,1]
        double w = cos;
        double x = sin * axis.getX();
        double y = sin * axis.getY();
        double z = sin * axis.getZ();
        // The original vector is along the x-axis.
        Cartesian original = new Cartesian(1.0, 0.0, 0.0);
        // The rotated vector is along the z-axis.
        Cartesian rotated = original.rotate(new UnitQuaternion(w, x, y, z));
        AssertHelper.assertEquals(0.0, rotated.getX(), Constants.Epsilon15);
        AssertHelper.assertEquals(0.0, rotated.getY(), Constants.Epsilon15);
        AssertHelper.assertEquals(1.0, rotated.getZ(), Constants.Epsilon15);
    }

    /**
    * Tests rotation by an {@link Matrix3By3}.
    */
    @Test
    public final void testRotateByMatrix3By3() {
        double angle = Math.PI / 3.0;
        // half angle of 120 degree rotation
        double cos = Math.cos(angle);
        double sin = Math.sin(angle);
        Cartesian axis = Cartesian.toCartesian(new Cartesian(1.0, 1.0, 1.0).normalize());
        // unit vector along [1,1,1]
        double w = cos;
        double x = sin * axis.getX();
        double y = sin * axis.getY();
        double z = sin * axis.getZ();
        // The original vector is along the x-axis.
        Cartesian original = new Cartesian(1.0, 0.0, 0.0);
        // The rotated vector is along the z-axis.
        Cartesian rotated = original.rotate(new Matrix3By3(new UnitQuaternion(w, x, y, z)));
        AssertHelper.assertEquals(0.0, rotated.getX(), Constants.Epsilon15);
        AssertHelper.assertEquals(0.0, rotated.getY(), Constants.Epsilon15);
        AssertHelper.assertEquals(1.0, rotated.getZ(), Constants.Epsilon15);
    }

    /**
    * Tests that GetHashCode returns something at least reasonably random.
    */
    @Test
    public final void testGetHashCode() {
        Cartesian object1 = new Cartesian(1.0, 2.0, 3.0);
        Cartesian object2 = new Cartesian(1.0, 2.0, 3.0);
        Cartesian object3 = new Cartesian(1.0, 2.0, 3.1);
        AssertHelper.assertEquals(object1.hashCode(), object2.hashCode());
        AssertHelper.assertNotEqual(object1.hashCode(), object3.hashCode());
    }

    /**
    * Tests ToString method
    */
    @Test
    public final void testToString() {
        final double val1 = 1.1;
        final double val2 = 2.1;
        final double val3 = 3.1;
        final String sep = ", ";
        String expected = DoubleHelper.toString(val1, CultureInfoHelper.getCurrentCulture()) + sep + DoubleHelper.toString(val2, CultureInfoHelper.getCurrentCulture()) + sep
                + DoubleHelper.toString(val3, CultureInfoHelper.getCurrentCulture());
        Cartesian test = new Cartesian(val1, val2, val3);
        AssertHelper.assertEquals(expected, test.toString());
    }

    @Nonnull
    private static final TestContextRule rule$testContext = new TestContextRule();

    @Nonnull
    @Rule
    @ClassRule
    public static TestContextRule getRule$testContext() {
        return rule$testContext;
    }
}