using System;
using System.Collections.Generic;
using EMPlannerVerificationHost;
using MultiWheelC.TrajectoryPlanning.CoarsePath;
using MultiWheelC.TrajectoryPlanning.PathSmoothing;
using MultiWheelC.TrajectoryPlanning.Utils;

namespace MultiWheelC.TrajectoryPlanning.EMPlanner;

internal static class FrenetChecks
{
    public static void Run()
    {
        VerifiesForwardProjectionAndReconstruction();
        VerifiesReverseProjectionAcrossYawWrap();
        VerifiesBoundedProjectionOnALoop();
    }

    private static void VerifiesForwardProjectionAndReconstruction()
    {
        DirectionSegmentView segment = CreateStraightSegment(TravelDirection.Forward, 0d, 0d);
        var world = new Pose2D(1.5d, 0.2d, 0d);
        var projector = new FrenetProjector();

        Verification.True(projector.TryProject(world, segment, 0d, 4d, 0.5d, out FrenetProjection projection),
            "forward projection succeeds");
        Verification.NearlyEqual(1.5d, projection.ReferenceS, "forward reference s");
        Verification.NearlyEqual(0.2d, projection.LateralOffset, "forward positive l is travel-left");
        Verification.True(FrenetTransform.TryReconstruct(projection.ReferencePoint, projection.LateralOffset, 0d, 0.2d,
            out Pose2D reconstructed), "forward reconstruction succeeds");
        Verification.NearlyEqual(world.X, reconstructed.X, "forward reconstructed x");
        Verification.NearlyEqual(world.Y, reconstructed.Y, "forward reconstructed y");
        Verification.NearlyEqual(0d, reconstructed.Heading, "forward reconstructed vehicle yaw");
    }

    private static void VerifiesReverseProjectionAcrossYawWrap()
    {
        const double vehicleYaw = -Math.PI + 0.01d;
        const double travelYaw = 0.01d;
        DirectionSegmentView segment = CreateStraightSegment(TravelDirection.Reverse, vehicleYaw, travelYaw);
        double expectedS = 1.5d;
        double expectedL = 0.2d;
        var world = new Pose2D(
            expectedS * Math.Cos(travelYaw) - expectedL * Math.Sin(travelYaw),
            expectedS * Math.Sin(travelYaw) + expectedL * Math.Cos(travelYaw),
            vehicleYaw);
        var projector = new FrenetProjector();

        Verification.True(projector.TryProject(world, segment, 0d, 4d, 0.5d, out FrenetProjection projection),
            "reverse projection succeeds");
        Verification.NearlyEqual(expectedS, projection.ReferenceS, "reverse reference s");
        Verification.NearlyEqual(expectedL, projection.LateralOffset, "reverse positive l is travel-left and body-right");
        Verification.True(FrenetTransform.TryReconstruct(projection.ReferencePoint, projection.LateralOffset, 0d, 0.2d,
            out Pose2D reconstructed), "reverse reconstruction succeeds");
        Verification.NearlyEqual(world.X, reconstructed.X, "reverse reconstructed x");
        Verification.NearlyEqual(world.Y, reconstructed.Y, "reverse reconstructed y");
        Verification.NearlyEqual(AngleMath.NormalizeRadians(vehicleYaw), reconstructed.Heading,
            "reverse reconstructed vehicle yaw");

        DirectionSegmentView wrapped = CreateWrappedForwardSegment();
        FrenetReferencePoint wrappedPoint = ReferencePathInterpolator.Interpolate(wrapped, 1.5d);
        Verification.NearlyEqual(3.18d, wrappedPoint.UnwrappedVehicleYaw, "interpolated yaw remains unwrapped internally");
        Verification.NearlyEqual(AngleMath.NormalizeRadians(3.18d), wrappedPoint.VehicleYaw,
            "interpolated public yaw is normalized");
        Verification.True(!FrenetTransform.TryReconstruct(new FrenetReferencePoint(0d, 0d, 0d, 0d, 0d,
            TravelDirection.Forward, 1d, 0d, 0d, 0d), 1d, 0d, 0.2d, out _),
            "singular Frenet reconstruction is rejected");
    }

    private static void VerifiesBoundedProjectionOnALoop()
    {
        DirectionSegmentView segment = CreateLoopSegment();
        var projector = new FrenetProjector();
        var world = new Pose2D(1d, 0.2d, Math.PI);

        Verification.True(projector.TryProject(world, segment, 2.2d, 4.2d, 0.1d, out FrenetProjection projection),
            "bounded loop projection succeeds");
        Verification.NearlyEqual(3.2d, projection.ReferenceS, "bounded loop picks the local upper branch");
        Verification.True(projection.ReferenceS >= 2.2d && projection.ReferenceS <= 4.2d,
            "projection remains in supplied reference-s interval");
        Verification.True(FrenetTransform.TryReconstruct(projection.ReferencePoint, projection.LateralOffset, 0d, 0.2d,
            out Pose2D reconstructed), "loop reconstruction succeeds");
        Verification.NearlyEqual(world.X, reconstructed.X, "loop reconstructed x");
        Verification.NearlyEqual(world.Y, reconstructed.Y, "loop reconstructed y");
    }

    private static DirectionSegmentView CreateStraightSegment(TravelDirection direction, double vehicleYaw,
        double travelYaw)
    {
        var points = new List<SmoothedPathPoint>
        {
            Point(0d, 0d, AngleMath.NormalizeRadians(vehicleYaw), vehicleYaw, 0d, direction),
            Point(4d * Math.Cos(travelYaw), 4d * Math.Sin(travelYaw), AngleMath.NormalizeRadians(vehicleYaw),
                vehicleYaw, 4d, direction),
        };
        return CreateSegment(direction, points);
    }

    private static DirectionSegmentView CreateWrappedForwardSegment()
    {
        var points = new List<SmoothedPathPoint>
        {
            Point(0d, 0d, AngleMath.NormalizeRadians(3.12d), 3.12d, 0d, TravelDirection.Forward),
            Point(1d, 0d, AngleMath.NormalizeRadians(3.16d), 3.16d, 1d, TravelDirection.Forward),
            Point(2d, 0d, AngleMath.NormalizeRadians(3.20d), 3.20d, 2d, TravelDirection.Forward),
        };
        return CreateSegment(TravelDirection.Forward, points);
    }

    private static DirectionSegmentView CreateLoopSegment()
    {
        var points = new List<SmoothedPathPoint>
        {
            Point(0d, 0d, 0d, 0d, 0d, TravelDirection.Forward),
            Point(2d, 0d, 0d, 0d, 2d, TravelDirection.Forward),
            Point(2d, 0.2d, Math.PI / 2d, Math.PI / 2d, 2.2d, TravelDirection.Forward),
            Point(0d, 0.2d, Math.PI, Math.PI, 4.2d, TravelDirection.Forward),
        };
        return CreateSegment(TravelDirection.Forward, points);
    }

    private static DirectionSegmentView CreateSegment(TravelDirection direction, IReadOnlyList<SmoothedPathPoint> points)
    {
        double length = points[points.Count - 1].ArcLength;
        return new DirectionSegmentView(0, direction, points,
            new ReferenceBoundary(0, 0d, EmBoundaryType.None, 0d),
            new ReferenceBoundary(0, length, EmBoundaryType.Goal, length), 0d);
    }

    private static SmoothedPathPoint Point(double x, double y, double heading, double unwrappedHeading, double s,
        TravelDirection direction)
    {
        return new SmoothedPathPoint(x, y, heading, unwrappedHeading, s, direction, 0d, 0d, 0d, 1d,
            false, SmoothedPathPointSource.Anchor);
    }
}
