using NUnit.Framework;
using Unity.Mathematics.Geometry;
using Burst.Compiler.IL.Tests;

namespace Unity.Mathematics.Tests
{
    [TestFixture]
    public class TestPlane
    {
        // An arbitrary tolerance.
        const float Tolerance = 0.0000025f;

        [TestCompiler]
        public static void Empty()
        {
            var p = new Plane();

            TestUtils.AreEqual(float4.zero, p.NormalAndDistance);
            TestUtils.AreEqual(float3.zero, p.Normal);
            TestUtils.AreEqual(0.0f, p.Distance);
        }

        [TestCompiler]
        public static void GetNormal()
        {
            var p = new Plane { NormalAndDistance = new float4(1.0f, 2.0f, 3.0f, 4.0f) };

            TestUtils.AreEqual(new float3(1.0f, 2.0f, 3.0f), p.Normal);
        }

        [TestCompiler]
        public static void GetDistance()
        {
            var p = new Plane { NormalAndDistance = new float4(1.0f, 2.0f, 3.0f, 4.0f) };

            TestUtils.AreEqual(4.0f, p.Distance);
        }

        [TestCompiler]
        public static void SetNormal()
        {
            var p = new Plane { NormalAndDistance = new float4(1.0f, 2.0f, 3.0f, 4.0f) };
            p.Normal = new float3(-1.0f, -2.0f, -3.0f);

            TestUtils.AreEqual(new float3(-1.0f, -2.0f, -3.0f), p.Normal);
        }

        [TestCompiler]
        public static void SetDistance()
        {
            var p = new Plane { NormalAndDistance = new float4(1.0f, 2.0f, 3.0f, 4.0f) };
            p.Distance = -4.0f;

            TestUtils.AreEqual(-4.0f, p.Distance);
        }

        [TestCompiler]
        public static void ConstructWithNormalAndDistance()
        {
            var n = math.normalize(new float3(4.0f, -5.0f, 6.0f));
            var d = 123.0f;
            var p = new Plane(n, d);

            TestUtils.AreEqual(new float4(n, d), p.NormalAndDistance);
            TestUtils.AreEqual(n, p.Normal);
            TestUtils.AreEqual(d, p.Distance);
        }

        [TestCompiler]
        public static void ConstructWithCoefficients_NonUnitLengthNormal()
        {
            var abcd = new float4(4.0f, -5.0f, 6.0f, 123.0f);
            var p = new Plane(abcd.x, abcd.y, abcd.z, abcd.w);
            var expected = abcd * math.rsqrt(math.lengthsq(abcd.xyz));

            TestUtils.AreEqual(expected, p.NormalAndDistance, Tolerance);
            TestUtils.AreEqual(expected.xyz, p.Normal);
            TestUtils.AreEqual(expected.w, p.Distance);
        }

        [TestCompiler]
        public static void ConstructWithNormalAndPointInPlane_NegativeDistance()
        {
            var n = math.up();
            var pointInPlane = math.up();
            var p = new Plane(n, pointInPlane);

            TestUtils.AreEqual(new float4(math.up(), -1.0f), p.NormalAndDistance);
            TestUtils.AreEqual(n, p.Normal);
            TestUtils.AreEqual(-1.0f, p.Distance);
        }

        [TestCompiler]
        public static void ConstructWithNormalAndPointInPlane_PositiveDistance()
        {
            var n = math.down();
            var pointInPlane = math.up();
            var p = new Plane(n, pointInPlane);

            TestUtils.AreEqual(new float4(math.down(), 1.0f), p.NormalAndDistance);
            TestUtils.AreEqual(n, p.Normal);
            TestUtils.AreEqual(1.0f, p.Distance);
        }

        [TestCompiler]
        public static void ConstructWithNormalAndPointInPlane()
        {
            var normal = new float3(2.0f, 2.0f, 2.0f);
            var pointInPlane = new float3(-2.0f, -2.0f, -2.0f);
            var p = new Plane(normal, pointInPlane);
            var expectedN = new float3(5.773502691896258e-01f);
            var expectedD = 3.464101615137755f;

            TestUtils.AreEqual(new float4(expectedN, expectedD), p.NormalAndDistance);
            TestUtils.AreEqual(expectedN, p.Normal);
            TestUtils.AreEqual(expectedD, p.Distance);
        }

        [TestCompiler]
        public static void ConstructWithTwoVectorsAndOrigin_Trivial()
        {
            var v1 = new float3(1.0f, 0.0f, 0.0f);
            var v2 = new float3(0.0f, 1.0f, 0.0f);
            var p = new Plane(v1, v2, float3.zero);

            TestUtils.AreEqual(new float4(0.0f, 0.0f, 1.0f, 0.0f), p);
        }

        [TestCompiler]
        public static void ConstructWithTwoVectorsAndOrigin_NonZeroOrigin()
        {
            var v1 = new float3(5.0f, 0.0f, 0.0f);
            var v2 = new float3(0.0f, 1.0f, 0.0f);
            var p = new Plane(v2, v1, new float3(0.0f, 0.0f, 1.0f));

            TestUtils.AreEqual(new float4(0.0f, 0.0f, -1.0f, 1.0f), p);
        }

        [TestCompiler]
        public static void SignedDistanceToPoint()
        {
            var vInPlane1 = new float3(2.0f, 1.0f, -0.18f);
            var vInPlane2 = new float3(-0.9928f, 10.0f, 3.125f);
            var n = math.normalize(math.cross(vInPlane1, vInPlane2));
            var pointInPlane = new float3(10.0f, -25.8918f, 1.0f);
            var p = new Plane(vInPlane1, vInPlane2, pointInPlane);

            TestUtils.AreEqual(0.0f, p.SignedDistanceToPoint(pointInPlane), Tolerance);
            TestUtils.AreEqual(0.0f, p.SignedDistanceToPoint(pointInPlane + vInPlane1), Tolerance);
            TestUtils.AreEqual(0.0f, p.SignedDistanceToPoint(pointInPlane + vInPlane2), Tolerance);
            TestUtils.AreEqual(1.0f, p.SignedDistanceToPoint(pointInPlane + n), Tolerance);
            TestUtils.AreEqual(-1.0f, p.SignedDistanceToPoint(pointInPlane - n), Tolerance);
        }

        [TestCompiler]
        public static void SignedDistanceToPointTrivial()
        {
            var p = new Plane(math.up(), 0.0f);

            TestUtils.AreEqual(0.0f, p.SignedDistanceToPoint(float3.zero));
            TestUtils.AreEqual(0.0f, p.SignedDistanceToPoint(math.left()));
            TestUtils.AreEqual(0.0f, p.SignedDistanceToPoint(math.right()));
            TestUtils.AreEqual(0.0f, p.SignedDistanceToPoint(math.forward()));
            TestUtils.AreEqual(0.0f, p.SignedDistanceToPoint(math.back()));
            TestUtils.AreEqual(1.0f, p.SignedDistanceToPoint(math.up()));
            TestUtils.AreEqual(1.0f, p.SignedDistanceToPoint(math.up() + math.left()));
            TestUtils.AreEqual(1.0f, p.SignedDistanceToPoint(math.up() + math.right()));
            TestUtils.AreEqual(1.0f, p.SignedDistanceToPoint(math.up() + math.back()));
            TestUtils.AreEqual(1.0f, p.SignedDistanceToPoint(math.up() + math.forward()));
            TestUtils.AreEqual(-1.0f, p.SignedDistanceToPoint(math.down()));
            TestUtils.AreEqual(-1.0f, p.SignedDistanceToPoint(math.down() + math.left()));
            TestUtils.AreEqual(-1.0f, p.SignedDistanceToPoint(math.down() + math.right()));
            TestUtils.AreEqual(-1.0f, p.SignedDistanceToPoint(math.down() + math.back()));
            TestUtils.AreEqual(-1.0f, p.SignedDistanceToPoint(math.down() + math.forward()));
        }

        [TestCompiler]
        public static void ProjectionTrivial()
        {
            var p = new Plane(math.up(), 0.0f);
            var expected = math.left() + math.forward();

            TestUtils.AreEqual(expected, p.Projection(math.up() + expected));
        }

        [TestCompiler]
        public static void Projection()
        {
            var normal = new float3(1.0f);
            var n = math.normalize(normal);
            var d = -1.0f;
            var p = new Plane(n, d);
            var expected = new float3(math.sqrt(3.0f), 0.0f, 0.0f);

            TestUtils.AreEqual(expected, p.Projection(expected), math.EPSILON);
        }

        [TestCompiler]
        public static void Flipped()
        {
            var normal = new float3(1.0f);
            var d = 1.0f;
            var p = new Plane(normal, d);
            var expectedD = -5.773502691896258e-01f;
            var expectedN = new float3(-5.773502691896258e-01f);

            TestUtils.AreEqual(new float4(expectedN, expectedD), p.Flipped, Tolerance);
        }

        [TestCompiler]
        public static void ImplicitToFloat4()
        {
            var normal = new float3(1.1f, -20.0f, 15.182f);
            var d = 18.9281f;
            var p = new Plane(normal, d);
            var expectedN = new float3(4.376593115243121e-02f, -7.957442027714765e-01f, 6.040494243238278e-01f);
            var expectedD = 7.530962922239393e-01f;
            float4 p_as_float4 = p;

            TestUtils.AreEqual(new float4(expectedN, expectedD), p_as_float4, Tolerance);
        }

        [TestCompiler]
        public static void NormalizeFloat4()
        {
            var p = new float4(8.215876543024162e-01f, 2.786574280629829e-01f, 8.121669997361285e-01f, 9.352839276497152e-01f);
            var expected = new float4(6.913449765800294e-01f, 2.344830914500849e-01f, 6.834177369527146e-01f, 7.870174797181938e-01f);

            TestUtils.AreEqual(expected, Plane.Normalize(p), Tolerance);
        }

        [TestCompiler]
        public static void NormalizePlane()
        {
            var p = new Plane { NormalAndDistance = new float4(8.215876543024162e-01f, 2.786574280629829e-01f, 8.121669997361285e-01f, 9.352839276497152e-01f) };
            var normalized = Plane.Normalize(p);
            var expected = new float4(6.913449765800294e-01f, 2.344830914500849e-01f, 6.834177369527146e-01f, 7.870174797181938e-01f);

            TestUtils.AreEqual(expected, normalized.NormalAndDistance, Tolerance);
            TestUtils.AreEqual(expected.xyz, normalized.Normal, Tolerance);
            TestUtils.AreEqual(expected.w, normalized.Distance, Tolerance);
        }

        [TestCompiler]
        public static void CreateFromUnitNormalAndDistance()
        {
            var n = new float3(9.108767140247756e-02f, 3.966831912609620e-01f, 9.134251375397404e-01f);
            var d = 9.120230523544353e-01f;
            var expected = new float4(n, d);
            var p = Plane.CreateFromUnitNormalAndDistance(n, d);

            TestUtils.AreEqual(expected, p.NormalAndDistance);
        }

        [TestCompiler]
        public static void CreateFromUnitNormalAndPointInPlane()
        {
            var n = new float3(4.724920516359185e-01f, 1.444614284194820e-01f, 8.694148358751899e-01f);
            var pointInPlane = new float3(9.395666432987706f, 3.818578457438728f, 4.283295215216710f);
            var expected = new float4(n, -8.714975414445176f);
            var p = Plane.CreateFromUnitNormalAndPointInPlane(n, pointInPlane);

            TestUtils.AreEqual(expected, p.NormalAndDistance, Tolerance);
        }
    }
}