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

namespace NavisworksTransport.UnitTests.CoordinateSystem
{
    [TestClass]
    public class HoistingRealObjectPoseHelperTests
    {
        [TestMethod]
        public void ShouldKeepActualUpAxis_WhenAligningHoistingForward()
        {
            Quaternion actualRotation = Quaternion.Normalize(new Quaternion(0.70710677f, 0.0f, 0.0f, 0.70710677f));
            Vector3 hostUp = Vector3.UnitY;
            Vector3 targetForward = -Vector3.UnitX;

            bool ok = HoistingRealObjectPoseHelper.TryCreateRotationFromActualPose(
                actualRotation,
                targetForward,
                hostUp,
                out Quaternion resultRotation,
                out LocalAxisDirection selectedDirection,
                out Vector3 selectedAxisLocal,
                out Vector3 projectedForward);

            Assert.IsTrue(ok);
            Assert.AreEqual(LocalAxisDirection.PositiveX, selectedDirection);
            AssertVector(selectedAxisLocal, 1.0, 0.0, 0.0);
            AssertVector(projectedForward, 1.0, 0.0, 0.0);

            Vector3 transformedUp = Vector3.Normalize(Vector3.Transform(-Vector3.UnitZ, resultRotation));
            AssertVector(transformedUp, 0.0, 1.0, 0.0, 1e-4);

            Vector3 transformedForward = Vector3.Normalize(Vector3.Transform(Vector3.UnitX, resultRotation));
            AssertVector(transformedForward, 1.0, 0.0, 0.0, 1e-4);
        }

        [TestMethod]
        public void ShouldChooseHorizontalAxisFromActualPose_ForHoisting()
        {
            Quaternion actualRotation = Quaternion.Normalize(new Quaternion(0.70710677f, 0.0f, 0.0f, 0.70710677f));
            Vector3 hostUp = Vector3.UnitY;
            Vector3 targetForward = Vector3.UnitX;

            bool ok = HoistingRealObjectPoseHelper.TryCreateRotationFromActualPose(
                actualRotation,
                targetForward,
                hostUp,
                out Quaternion resultRotation,
                out LocalAxisDirection selectedDirection,
                out Vector3 selectedAxisLocal,
                out Vector3 projectedForward);

            Assert.IsTrue(ok);
            Assert.AreEqual(LocalAxisDirection.PositiveX, selectedDirection);
            AssertVector(selectedAxisLocal, 1.0, 0.0, 0.0);
            AssertVector(projectedForward, 1.0, 0.0, 0.0);

            Vector3 transformedUp = Vector3.Normalize(Vector3.Transform(-Vector3.UnitZ, resultRotation));
            AssertVector(transformedUp, 0.0, 1.0, 0.0, 1e-4);

            Vector3 transformedForward = Vector3.Normalize(Vector3.Transform(Vector3.UnitX, resultRotation));
            AssertVector(transformedForward, 1.0, 0.0, 0.0, 1e-4);
        }

        [TestMethod]
        public void CreateRotationFromPlanarBasePose_ShouldReturnBaseRotation_WhenYawUnchanged()
        {
            Quaternion baseRotation = Quaternion.Normalize(
                Quaternion.CreateFromAxisAngle(Vector3.UnitZ, 0.31f) *
                Quaternion.CreateFromAxisAngle(Vector3.UnitX, -0.42f));

            Quaternion resultRotation = HoistingRealObjectPoseHelper.CreateRotationFromPlanarBasePose(
                baseRotation,
                baseYawRadians: 1.25,
                targetYawRadians: 1.25,
                hostUp: Vector3.UnitY);

            AssertQuaternionEquivalent(resultRotation, baseRotation);
        }

        [TestMethod]
        public void CreateRotationFromPlanarBasePose_ShouldPreserveTiltAndApplyDeltaYaw()
        {
            Quaternion baseRotation = Quaternion.Normalize(
                Quaternion.CreateFromAxisAngle(Vector3.UnitY, 0.20f) *
                Quaternion.CreateFromAxisAngle(Vector3.UnitZ, 0.35f) *
                Quaternion.CreateFromAxisAngle(Vector3.UnitX, -0.40f));

            Quaternion resultRotation = HoistingRealObjectPoseHelper.CreateRotationFromPlanarBasePose(
                baseRotation,
                baseYawRadians: 0.0,
                targetYawRadians: System.Math.PI / 2.0,
                hostUp: Vector3.UnitY);

            Quaternion expectedRotation = Quaternion.Normalize(
                Quaternion.CreateFromAxisAngle(Vector3.UnitY, (float)(System.Math.PI / 2.0)) *
                baseRotation);

            AssertQuaternionEquivalent(resultRotation, expectedRotation);
        }

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

        private static void AssertQuaternionEquivalent(Quaternion actual, Quaternion expected, double tolerance = 1e-6)
        {
            float dot = Quaternion.Dot(Quaternion.Normalize(actual), Quaternion.Normalize(expected));
            Assert.AreEqual(1.0, System.Math.Abs(dot), tolerance);
        }
    }
}
