using Microsoft.VisualStudio.TestTools.UnitTesting;
using NavisworksTransport.Utils.CoordinateSystem;
using System;
using System.Numerics;

namespace NavisworksTransport.UnitTests.CoordinateSystem
{
    [TestClass]
    public class RealObjectPlanarPoseSolverTests
    {
        [TestMethod]
        public void ShouldChooseClosestCandidateAxis_FromRotatedReferencePose()
        {
            Quaternion referenceRotation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, (float)(Math.PI / 6.0));
            Vector3 desiredForward = new Vector3(1.0f, 0.2f, 0.1f);
            Vector3 desiredUp = Vector3.UnitZ;

            bool ok = RealObjectPlanarPoseSolver.TryCreatePlanarPoseFromReferencePose(
                referenceRotation,
                desiredForward,
                desiredUp,
                out RealObjectPlanarPoseSolution solution);

            Assert.IsTrue(ok);
            AssertVector(solution.SelectedReferenceAxisLocal, 1.0, 0.0, 0.0);

            Vector3 transformedSelectedAxis = Vector3.Normalize(Vector3.Transform(solution.SelectedReferenceAxisLocal, solution.BaselineRotation));
            AssertVector(transformedSelectedAxis, desiredForward.X / desiredForward.Length(), desiredForward.Y / desiredForward.Length(), desiredForward.Z / desiredForward.Length(), 1e-4);
        }

        [TestMethod]
        public void ShouldPreferNegativeAxisWhenItMatchesForwardBest()
        {
            bool ok = RealObjectPlanarPoseSolver.TryCreatePlanarPoseFromReferencePose(
                Quaternion.Identity,
                new Vector3(-0.1f, -1.0f, 0.05f),
                Vector3.UnitZ,
                out RealObjectPlanarPoseSolution solution);

            Assert.IsTrue(ok);
            AssertVector(solution.SelectedReferenceAxisLocal, 0.0, -1.0, 0.0);
        }

        [TestMethod]
        public void ShouldRespectDesiredUpPlane_WhenDesiredForwardHasVerticalComponent()
        {
            Vector3 desiredForward = Vector3.Normalize(new Vector3(1.0f, 1.0f, 1.0f));

            bool ok = RealObjectPlanarPoseSolver.TryCreatePlanarPoseFromReferencePose(
                Quaternion.Identity,
                desiredForward,
                Vector3.UnitY,
                out RealObjectPlanarPoseSolution solution);

            Assert.IsTrue(ok);
            Vector3 transformedSelectedAxis = Vector3.Normalize(Vector3.Transform(solution.SelectedReferenceAxisLocal, solution.BaselineRotation));
            AssertVector(transformedSelectedAxis, desiredForward.X, desiredForward.Y, desiredForward.Z, 1e-4);
        }

        [TestMethod]
        public void ShouldPreserveReferenceOrthogonalityByApplyingSingleRotationDelta()
        {
            Quaternion referenceRotation = Quaternion.Normalize(
                Quaternion.CreateFromYawPitchRoll(
                    (float)(Math.PI / 7.0),
                    (float)(Math.PI / 9.0),
                    (float)(Math.PI / 11.0)));

            Vector3 desiredForward = Vector3.Normalize(new Vector3(-0.3f, 0.8f, 0.52f));

            bool ok = RealObjectPlanarPoseSolver.TryCreatePlanarPoseFromReferencePose(
                referenceRotation,
                desiredForward,
                Vector3.UnitZ,
                out RealObjectPlanarPoseSolution solution);

            Assert.IsTrue(ok);

            Vector3 transformedX = Vector3.Normalize(Vector3.Transform(Vector3.UnitX, solution.BaselineRotation));
            Vector3 transformedY = Vector3.Normalize(Vector3.Transform(Vector3.UnitY, solution.BaselineRotation));
            Vector3 transformedZ = Vector3.Normalize(Vector3.Transform(Vector3.UnitZ, solution.BaselineRotation));

            Assert.AreEqual(1.0, transformedX.Length(), 1e-4);
            Assert.AreEqual(1.0, transformedY.Length(), 1e-4);
            Assert.AreEqual(1.0, transformedZ.Length(), 1e-4);
            Assert.AreEqual(0.0, Vector3.Dot(transformedX, transformedY), 1e-4);
            Assert.AreEqual(0.0, Vector3.Dot(transformedY, transformedZ), 1e-4);
            Assert.AreEqual(0.0, Vector3.Dot(transformedZ, transformedX), 1e-4);
        }

        [TestMethod]
        public void FixedReferenceAxis_ShouldKeepAxisFamilyAcrossTurn()
        {
            Quaternion referenceRotation = Quaternion.CreateFromAxisAngle(Vector3.UnitY, (float)Math.PI);
            Vector3 desiredForwardStart = Vector3.Normalize(new Vector3(-0.95f, 0.0f, 0.31f));
            Vector3 desiredForwardTurn = Vector3.Normalize(new Vector3(0.22f, 0.0f, 0.97f));
            Vector3 desiredUp = Vector3.UnitY;

            bool startOk = RealObjectPlanarPoseSolver.TryCreatePlanarPoseFromReferencePose(
                referenceRotation,
                desiredForwardStart,
                desiredUp,
                out RealObjectPlanarPoseSolution startSolution);

            Assert.IsTrue(startOk);

            bool turnOk = RealObjectPlanarPoseSolver.TryCreatePlanarPoseFromReferencePose(
                referenceRotation,
                desiredForwardTurn,
                desiredUp,
                startSolution.SelectedReferenceAxisDirection,
                out RealObjectPlanarPoseSolution turnSolution);

            Assert.IsTrue(turnOk);
            Assert.AreEqual(startSolution.SelectedReferenceAxisDirection, turnSolution.SelectedReferenceAxisDirection);

            Vector3 transformedSelectedAxis = Vector3.Normalize(
                Vector3.Transform(turnSolution.SelectedReferenceAxisLocal, turnSolution.BaselineRotation));
            AssertVector(
                transformedSelectedAxis,
                desiredForwardTurn.X,
                desiredForwardTurn.Y,
                desiredForwardTurn.Z,
                1e-4);
        }

        [TestMethod]
        public void FixedPositiveX_ShouldNotSwitchToZ_WhenPathLeansTowardZ()
        {
            Quaternion referenceRotation = Quaternion.Identity;
            Vector3 desiredForward = Vector3.Normalize(new Vector3(-0.690f, 0.0f, 0.724f));
            Vector3 desiredUp = Vector3.UnitY;

            bool ok = RealObjectPlanarPoseSolver.TryCreatePlanarPoseFromReferencePose(
                referenceRotation,
                desiredForward,
                desiredUp,
                LocalAxisDirection.PositiveX,
                out RealObjectPlanarPoseSolution solution);

            Assert.IsTrue(ok);
            Assert.AreEqual(LocalAxisDirection.PositiveX, solution.SelectedReferenceAxisDirection);
            AssertVector(solution.SelectedReferenceAxisLocal, 1.0, 0.0, 0.0);

            Vector3 transformedSelectedAxis = Vector3.Normalize(
                Vector3.Transform(solution.SelectedReferenceAxisLocal, solution.BaselineRotation));
            AssertVector(
                transformedSelectedAxis,
                desiredForward.X,
                desiredForward.Y,
                desiredForward.Z,
                1e-4);
        }

        private static void AssertVector(Vector3 vector, double x, double y, double z, double tolerance = 1e-6)
        {
            Assert.AreEqual(x, vector.X, tolerance);
            Assert.AreEqual(y, vector.Y, tolerance);
            Assert.AreEqual(z, vector.Z, tolerance);
        }
    }
}
