using System;
using System.Collections.Generic;
using System.Numerics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NavisworksTransport.Utils.GeometryAnalysis;

namespace NavisworksTransport.UnitTests.CoordinateSystem
{
    [TestClass]
    public class AssemblyEndFaceAnalyzerTests
    {
        [TestMethod]
        public void Analyze_RectangularFaceWithSideNoise_ShouldReturnFaceCenter()
        {
            var triangles = new List<AnalysisTriangle3>();
            triangles.AddRange(CreateRectangleFace(-2.0, 2.0, -1.0, 1.0, 10.0));
            triangles.AddRange(CreateRectangleFace(-2.0, 2.0, -1.0, 1.0, 8.0));
            triangles.Add(new AnalysisTriangle3(
                new Vector3(-2.0f, -1.0f, 10.0f),
                new Vector3(-2.0f, -1.0f, 9.0f),
                new Vector3(-2.0f, 1.0f, 9.0f)));

            EndFaceAnalysisResult result = AssemblyEndFaceAnalyzer.Analyze(
                triangles,
                new Vector3(-1.5f, -0.7f, 10.0f),
                new Vector3(1.4f, -0.5f, 10.0f),
                new Vector3(1.1f, 0.8f, 10.0f));

            Assert.IsTrue(result.IsReliable, result.DiagnosticMessage);
            AssertPoint(result.Center, 0.0, 0.0, 10.0);
            Assert.AreEqual(2, result.CandidateTriangleCount);
        }

        [TestMethod]
        public void Analyze_SquareRingFace_ShouldReturnRingCenter()
        {
            var triangles = new List<AnalysisTriangle3>();
            triangles.AddRange(CreateRingFace(4.0, 2.0, 5.0));

            EndFaceAnalysisResult result = AssemblyEndFaceAnalyzer.Analyze(
                triangles,
                new Vector3(-3.5f, -3.5f, 5.0f),
                new Vector3(3.5f, -3.5f, 5.0f),
                new Vector3(3.5f, 3.5f, 5.0f));

            Assert.IsTrue(result.IsReliable, result.DiagnosticMessage);
            AssertPoint(result.Center, 0.0, 0.0, 5.0);
            Assert.IsTrue(result.CandidateTriangleCount >= 8);
        }

        [TestMethod]
        public void Analyze_NearlyCollinearSeedPoints_ShouldFail()
        {
            var triangles = new List<AnalysisTriangle3>();
            triangles.AddRange(CreateRectangleFace(-1.0, 1.0, -1.0, 1.0, 0.0));

            EndFaceAnalysisResult result = AssemblyEndFaceAnalyzer.Analyze(
                triangles,
                new Vector3(0.0f, 0.0f, 0.0f),
                new Vector3(1.0f, 0.0f, 0.0f),
                new Vector3(2.0f, 0.0f, 0.0f));

            Assert.IsFalse(result.IsReliable);
        }

        [TestMethod]
        public void OrientNormalTowardTarget_ShouldFlip_WhenNormalOpposesTargetDirection()
        {
            Vector3 orientedNormal = AssemblyEndFaceAnalyzer.OrientNormalTowardTarget(
                new Vector3(-1f, 0f, 0f),
                Vector3.Zero,
                new Vector3(10f, 0f, 0f));

            AssertPoint(orientedNormal, 1.0, 0.0, 0.0);
        }

        [TestMethod]
        public void OrientNormalTowardTarget_ShouldKeepDirection_WhenNormalAlreadyFacesTarget()
        {
            Vector3 orientedNormal = AssemblyEndFaceAnalyzer.OrientNormalTowardTarget(
                new Vector3(0f, 1f, 0f),
                new Vector3(1f, 2f, 3f),
                new Vector3(1f, 5f, 3f));

            AssertPoint(orientedNormal, 0.0, 1.0, 0.0);
        }

        private static IEnumerable<AnalysisTriangle3> CreateRectangleFace(double minX, double maxX, double minY, double maxY, double z)
        {
            yield return new AnalysisTriangle3(
                new Vector3((float)minX, (float)minY, (float)z),
                new Vector3((float)maxX, (float)minY, (float)z),
                new Vector3((float)maxX, (float)maxY, (float)z));
            yield return new AnalysisTriangle3(
                new Vector3((float)minX, (float)minY, (float)z),
                new Vector3((float)maxX, (float)maxY, (float)z),
                new Vector3((float)minX, (float)maxY, (float)z));
        }

        private static IEnumerable<AnalysisTriangle3> CreateRingFace(double outerHalfSize, double innerHalfSize, double z)
        {
            if (innerHalfSize >= outerHalfSize)
            {
                throw new ArgumentOutOfRangeException(nameof(innerHalfSize));
            }

            foreach (AnalysisTriangle3 triangle in CreateRectangleFace(-outerHalfSize, outerHalfSize, innerHalfSize, outerHalfSize, z))
            {
                yield return triangle;
            }

            foreach (AnalysisTriangle3 triangle in CreateRectangleFace(-outerHalfSize, outerHalfSize, -outerHalfSize, -innerHalfSize, z))
            {
                yield return triangle;
            }

            foreach (AnalysisTriangle3 triangle in CreateRectangleFace(-outerHalfSize, -innerHalfSize, -innerHalfSize, innerHalfSize, z))
            {
                yield return triangle;
            }

            foreach (AnalysisTriangle3 triangle in CreateRectangleFace(innerHalfSize, outerHalfSize, -innerHalfSize, innerHalfSize, z))
            {
                yield return triangle;
            }
        }

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