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

namespace NavisworksTransport.UnitTests.CoordinateSystem
{
    [TestClass]
    public class RealObjectProjectedExtentResolverTests
    {
        [TestMethod]
        public void Ground_DualAxisCorrection_ShouldProjectAgainstFinalPose()
        {
            var convention = new ModelAxisConvention(LocalAxisDirection.PositiveX, LocalAxisDirection.PositiveY);
            Vector3 targetForward = Vector3.Normalize(new Vector3(-0.8987f, 0.0f, 0.4386f));
            Vector3 targetUp = Vector3.UnitY;

            Quaternion baseline = Quaternion.Normalize(Quaternion.CreateFromRotationMatrix(new Matrix4x4(
                -0.8987f, 0.0000f, 0.4386f, 0f,
                 0.0000f, 1.0000f, 0.0000f, 0f,
                -0.4386f, 0.0000f,-0.8987f, 0f,
                 0f,      0f,      0f,      1f)));

            var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
            Quaternion final = adapter.ComposeHostQuaternion(
                baseline,
                new LocalEulerRotationCorrection(90.0, 0.0, 90.0));

            var extents = RealObjectProjectedExtentResolver.CalculateProjectedSemanticExtents(
                convention,
                forwardSize: 6.0,
                sideSize: 2.0,
                upSize: 4.0,
                baseline,
                final,
                targetForward,
                targetUp);

            Vector3 localSize = convention.CreateScaleVector3(6.0, 2.0, 4.0);
            Vector3 rotatedLocalX = Vector3.Normalize(Vector3.Transform(Vector3.UnitX, final));
            Vector3 rotatedLocalY = Vector3.Normalize(Vector3.Transform(Vector3.UnitY, final));
            Vector3 rotatedLocalZ = Vector3.Normalize(Vector3.Transform(Vector3.UnitZ, final));
            Vector3 targetSide = Vector3.Normalize(Vector3.Cross(targetForward, targetUp));

            double expectedForward = ProjectExtent(localSize, rotatedLocalX, rotatedLocalY, rotatedLocalZ, targetForward);
            double expectedSide = ProjectExtent(localSize, rotatedLocalX, rotatedLocalY, rotatedLocalZ, targetSide);
            double expectedUp = ProjectExtent(localSize, rotatedLocalX, rotatedLocalY, rotatedLocalZ, targetUp);

            Assert.AreEqual(expectedForward, extents.forwardExtent, 1e-5);
            Assert.AreEqual(expectedSide, extents.sideExtent, 1e-5);
            Assert.AreEqual(expectedUp, extents.upExtent, 1e-5);
            Assert.AreNotEqual(4.0, extents.upExtent, 1e-3, "双轴旋转后，Ground 的法线尺寸不应停留在旧高度。");
        }

        [TestMethod]
        public void Ground_DisplayedHostAxesProjection_ShouldKeepXAxisLengthAndSwapXZAsAxesRotate()
        {
            Vector3 targetForward = Vector3.UnitX;
            Vector3 targetUp = Vector3.UnitY;

            var noCorrection = RealObjectProjectedExtentResolver.CalculateProjectedDisplayedExtents(
                displayedAxisXSize: 6.0,
                displayedAxisYSize: 2.0,
                displayedAxisZSize: 4.0,
                displayedHostAxisX: Vector3.UnitX,
                displayedHostAxisY: Vector3.UnitZ,
                displayedHostAxisZ: -Vector3.UnitY,
                targetForward,
                targetUp);

            Assert.AreEqual(6.0, noCorrection.forwardExtent, 1e-5);
            Assert.AreEqual(2.0, noCorrection.sideExtent, 1e-5);
            Assert.AreEqual(4.0, noCorrection.upExtent, 1e-5);

            var xRotated = RealObjectProjectedExtentResolver.CalculateProjectedDisplayedExtents(
                displayedAxisXSize: 6.0,
                displayedAxisYSize: 2.0,
                displayedAxisZSize: 4.0,
                displayedHostAxisX: Vector3.UnitX,
                displayedHostAxisY: Vector3.UnitY,
                displayedHostAxisZ: Vector3.UnitZ,
                targetForward,
                targetUp);

            Assert.AreEqual(6.0, xRotated.forwardExtent, 1e-5);
            Assert.AreEqual(4.0, xRotated.sideExtent, 1e-5);
            Assert.AreEqual(2.0, xRotated.upExtent, 1e-5);

            var zRotated = RealObjectProjectedExtentResolver.CalculateProjectedDisplayedExtents(
                displayedAxisXSize: 6.0,
                displayedAxisYSize: 2.0,
                displayedAxisZSize: 4.0,
                displayedHostAxisX: -Vector3.UnitZ,
                displayedHostAxisY: Vector3.UnitX,
                displayedHostAxisZ: -Vector3.UnitY,
                targetForward,
                targetUp);

            Assert.AreEqual(2.0, zRotated.forwardExtent, 1e-5);
            Assert.AreEqual(6.0, zRotated.sideExtent, 1e-5);
            Assert.AreEqual(4.0, zRotated.upExtent, 1e-5);
        }

        [TestMethod]
        public void Ground_DisplayedHostAxesProjection_ShouldIgnoreVerticalComponentInForward()
        {
            Vector3 targetUp = Vector3.UnitY;
            Vector3 planarForward = Vector3.Normalize(new Vector3(1f, 0f, 1f));
            Vector3 slopedForward = Vector3.Normalize(new Vector3(1f, 2f, 1f));

            var planar = RealObjectProjectedExtentResolver.CalculateProjectedDisplayedExtents(
                displayedAxisXSize: 6.0,
                displayedAxisYSize: 2.0,
                displayedAxisZSize: 4.0,
                displayedHostAxisX: Vector3.UnitX,
                displayedHostAxisY: Vector3.UnitZ,
                displayedHostAxisZ: -Vector3.UnitY,
                planarForward,
                targetUp);

            var sloped = RealObjectProjectedExtentResolver.CalculateProjectedDisplayedExtents(
                displayedAxisXSize: 6.0,
                displayedAxisYSize: 2.0,
                displayedAxisZSize: 4.0,
                displayedHostAxisX: Vector3.UnitX,
                displayedHostAxisY: Vector3.UnitZ,
                displayedHostAxisZ: -Vector3.UnitY,
                slopedForward,
                targetUp);

            Assert.AreNotEqual(planar.forwardExtent, sloped.forwardExtent, 1e-3, "原始带竖直分量的路径方向会污染 Ground 尺寸投影。");
        }

        private static double ProjectExtent(
            Vector3 localSize,
            Vector3 rotatedLocalX,
            Vector3 rotatedLocalY,
            Vector3 rotatedLocalZ,
            Vector3 targetAxis)
        {
            return System.Math.Abs(Vector3.Dot(rotatedLocalX, targetAxis)) * localSize.X +
                   System.Math.Abs(Vector3.Dot(rotatedLocalY, targetAxis)) * localSize.Y +
                   System.Math.Abs(Vector3.Dot(rotatedLocalZ, targetAxis)) * localSize.Z;
        }
    }
}
