using System;
using System.Collections.Generic;
using MultiWheelC.Fleet;
using MyParking.Shared;

namespace MultiWheelC.Tests
{
    internal static class FleetMemberCommandCorrectorTests
    {
        private const double Tolerance = 1e-9;

        public static void Run()
        {
            VerifyZeroErrorsPreserveBaseCommands();
            VerifyRelativePositionErrorProducesOpposingCorrection();
            VerifyCommonTranslationIsRemoved();
            VerifyCommonRotationIsRemoved();
            VerifyDeadbandSuppressesSmallErrors();
            VerifyCorrectionLimits();

            Console.WriteLine(
                "FleetMemberCommandCorrector测试通过，共6个场景。");
        }

        private static void VerifyZeroErrorsPreserveBaseCommands()
        {
            var layout = CreateLayout();
            var baseCommands = FleetKinematics.Decompose(
                layout,
                new FleetMotionCommand(
                    Point2D.Zero,
                    new Twist2D(0.4, 0.1, 0.05)));
            var corrected = CreateCorrector().Correct(
                layout,
                baseCommands,
                CreateErrors(Pose2D.Identity, Pose2D.Identity));

            AssertCommandsEqual(
                corrected,
                baseCommands,
                "零布局误差");
        }

        private static void
            VerifyRelativePositionErrorProducesOpposingCorrection()
        {
            var layout = CreateLayout();
            var corrected = CreateCorrector().Correct(
                layout,
                CreateStopCommands(layout),
                CreateErrors(
                    new Pose2D(0.02, 0.0, 0.0),
                    new Pose2D(0.02, 0.0, 0.0)));

            AssertTwist(
                FindCommand(corrected, 1).TwistInVehicleBody,
                -0.02,
                0.0,
                0.0,
                "车辆1相对位置纠偏");
            AssertTwist(
                FindCommand(corrected, 2).TwistInVehicleBody,
                -0.02,
                0.0,
                0.0,
                "车辆2相对位置纠偏");
        }

        private static void VerifyCommonTranslationIsRemoved()
        {
            var layout = CreateLayout();
            var corrected = CreateCorrector().Correct(
                layout,
                CreateStopCommands(layout),
                CreateErrors(
                    new Pose2D(0.02, 0.0, 0.0),
                    new Pose2D(-0.02, 0.0, 0.0)));

            AssertAllStopped(
                corrected,
                "共同平移不应成为成员相对纠偏");
        }

        private static void VerifyCommonRotationIsRemoved()
        {
            const double fleetYawErrorRadians = 0.02;
            var layout = CreateLayout();
            var commonRotationError = new Pose2D(
                0.0,
                -fleetYawErrorRadians,
                fleetYawErrorRadians);
            var corrected = CreateCorrector().Correct(
                layout,
                CreateStopCommands(layout),
                CreateErrors(
                    commonRotationError,
                    commonRotationError));

            AssertAllStopped(
                corrected,
                "共同旋转不应成为成员相对纠偏");
        }

        private static void VerifyDeadbandSuppressesSmallErrors()
        {
            var layout = CreateLayout();
            var corrector = new FleetMemberCommandCorrector(
                longitudinalPositionGainPerSecond: 1.0,
                lateralPositionGainPerSecond: 1.0,
                yawGainPerSecond: 1.0,
                positionErrorDeadbandMeters: 0.005,
                yawErrorDeadbandRadians: 0.02,
                maximumLinearCorrectionMetersPerSecond: 1.0,
                maximumAngularCorrectionRadiansPerSecond: 1.0);
            var corrected = corrector.Correct(
                layout,
                CreateStopCommands(layout),
                CreateErrors(
                    new Pose2D(0.004, 0.003, 0.01),
                    new Pose2D(0.004, -0.003, -0.01)));

            AssertAllStopped(corrected, "布局误差死区");
        }

        private static void VerifyCorrectionLimits()
        {
            var layout = CreateLayout();
            var corrector = new FleetMemberCommandCorrector(
                longitudinalPositionGainPerSecond: 1.0,
                lateralPositionGainPerSecond: 1.0,
                yawGainPerSecond: 1.0,
                positionErrorDeadbandMeters: 0.0,
                yawErrorDeadbandRadians: 0.0,
                maximumLinearCorrectionMetersPerSecond: 0.03,
                maximumAngularCorrectionRadiansPerSecond: 0.05);
            var corrected = corrector.Correct(
                layout,
                CreateStopCommands(layout),
                CreateErrors(
                    new Pose2D(0.2, 0.0, 0.2),
                    new Pose2D(0.2, 0.0, -0.2)));

            for (var index = 0; index < corrected.Count; index++)
            {
                var twist = corrected[index].TwistInVehicleBody;
                var linearMagnitude = Math.Sqrt(
                    twist.VxMetersPerSecond *
                    twist.VxMetersPerSecond +
                    twist.VyMetersPerSecond *
                    twist.VyMetersPerSecond);

                AssertNear(
                    linearMagnitude,
                    0.03,
                    "线速度纠偏限幅");
                AssertNear(
                    Math.Abs(twist.OmegaRadiansPerSecond),
                    0.05,
                    "角速度纠偏限幅");
            }
        }

        private static FleetMemberCommandCorrector CreateCorrector()
        {
            return new FleetMemberCommandCorrector(
                longitudinalPositionGainPerSecond: 1.0,
                lateralPositionGainPerSecond: 1.0,
                yawGainPerSecond: 1.0,
                positionErrorDeadbandMeters: 0.0,
                yawErrorDeadbandRadians: 0.0,
                maximumLinearCorrectionMetersPerSecond: 1.0,
                maximumAngularCorrectionRadiansPerSecond: 1.0);
        }

        private static FleetLayout CreateLayout()
        {
            return new FleetLayout(
                new[]
                {
                    new VehicleLayout(
                        1,
                        new Pose2D(-1.0, 0.0, 0.0)),
                    new VehicleLayout(
                        2,
                        new Pose2D(1.0, 0.0, Math.PI))
                });
        }

        private static IReadOnlyList<FleetMemberCommand>
            CreateStopCommands(FleetLayout layout)
        {
            return FleetKinematics.Decompose(
                layout,
                FleetMotionCommand.Stop());
        }

        private static FleetMemberLayoutError[] CreateErrors(
            Pose2D vehicle1Error,
            Pose2D vehicle2Error)
        {
            return new[]
            {
                new FleetMemberLayoutError(1, vehicle1Error),
                new FleetMemberLayoutError(2, vehicle2Error)
            };
        }

        private static FleetMemberCommand FindCommand(
            IReadOnlyList<FleetMemberCommand> commands,
            int vehicleId)
        {
            for (var index = 0; index < commands.Count; index++)
            {
                if (commands[index].VehicleId == vehicleId)
                {
                    return commands[index];
                }
            }

            throw new InvalidOperationException(
                $"没有找到车辆{vehicleId}的成员命令。");
        }

        private static void AssertCommandsEqual(
            IReadOnlyList<FleetMemberCommand> actual,
            IReadOnlyList<FleetMemberCommand> expected,
            string scenario)
        {
            if (actual.Count != expected.Count)
            {
                throw new InvalidOperationException(
                    $"{scenario}的命令数量不一致。");
            }

            for (var index = 0; index < expected.Count; index++)
            {
                var expectedCommand = expected[index];
                var actualCommand = FindCommand(
                    actual,
                    expectedCommand.VehicleId);
                AssertTwist(
                    actualCommand.TwistInVehicleBody,
                    expectedCommand.TwistInVehicleBody
                        .VxMetersPerSecond,
                    expectedCommand.TwistInVehicleBody
                        .VyMetersPerSecond,
                    expectedCommand.TwistInVehicleBody
                        .OmegaRadiansPerSecond,
                    scenario);
            }
        }

        private static void AssertAllStopped(
            IReadOnlyList<FleetMemberCommand> commands,
            string scenario)
        {
            for (var index = 0; index < commands.Count; index++)
            {
                AssertTwist(
                    commands[index].TwistInVehicleBody,
                    0.0,
                    0.0,
                    0.0,
                    scenario);
            }
        }

        private static void AssertTwist(
            Twist2D actual,
            double expectedVx,
            double expectedVy,
            double expectedOmega,
            string scenario)
        {
            AssertNear(
                actual.VxMetersPerSecond,
                expectedVx,
                scenario + " Vx");
            AssertNear(
                actual.VyMetersPerSecond,
                expectedVy,
                scenario + " Vy");
            AssertNear(
                actual.OmegaRadiansPerSecond,
                expectedOmega,
                scenario + " Omega");
        }

        private static void AssertNear(
            double actual,
            double expected,
            string valueName)
        {
            if (Math.Abs(actual - expected) > Tolerance)
            {
                throw new InvalidOperationException(
                    $"{valueName}错误：" +
                    $"actual={actual:F9}, expected={expected:F9}。");
            }
        }
    }
}
