using System;
using System.Collections.Generic;
using System.Globalization;
using MultiWheelC.TrajectoryPlanning.CoarsePath;
using MultiWheelC.TrajectoryPlanning.EMPlanner;
using TrajectoryPlanningVisualization;

namespace MultiWheelC.TrajectoryPlanning.TrajectoryObservation;

public sealed class TrajectoryObservationKinematicChartBuilder
{
    public IReadOnlyList<VisualizationChart> Build(EmTrajectory trajectory, DirectionSegmentView segment,
        VehicleParameters vehicle, EmPlannerConfiguration configuration)
    {
        if (trajectory == null) throw new ArgumentNullException(nameof(trajectory));
        if (segment == null) throw new ArgumentNullException(nameof(segment));
        if (vehicle == null) throw new ArgumentNullException(nameof(vehicle));
        if (configuration == null) throw new ArgumentNullException(nameof(configuration));

        LsBuildResult ls = BuildLs(trajectory, segment, configuration.Frenet.MaximumProjectionDistanceMeters);
        double curvatureLimit = CurvatureLimit(vehicle);
        double terminalTime = trajectory.Points.Count == 0 ? 0d : trajectory.Points[trajectory.Points.Count - 1].TimeFromStart;
        return new[]
        {
            Chart("ls", "横向偏移", "ReferenceS (m)", "l (m)", ls.Points,
                "完整活动方向段投影；投影失败=" + ls.ProjectionFailureCount.ToString(CultureInfo.InvariantCulture),
                new[] { new VisualizationChartAnnotation("ls-s-end", "s-end", "s_end",
                    segment.SourceStartArcLength + segment.LengthMeters) }),
            Chart("st", "时空轨迹", "t (s)", "PathS (m)", Points(trajectory, p => p.PathS), "",
                new[] { new VisualizationChartAnnotation("st-s-end", "s-end", "s_end", terminalTime) }),
            WithSymmetricLimit("curvature-s", "曲率-距离", "s (m)", "κ (m⁻¹)",
                PointsBySegmentS(trajectory), curvatureLimit),
            WithSymmetricLimit("curvature-t", "曲率-时间", "t (s)", "κ (m⁻¹)",
                Points(trajectory, p => p.VehicleCurvature), curvatureLimit),
            Velocity(trajectory, configuration),
            Acceleration(trajectory, configuration),
            Jerk(trajectory, configuration),
            Chart("yaw-rate-t", "横摆角速度", "t (s)", "ω (rad/s)",
                Points(trajectory, p => p.YawRate), "生效配置没有独立横摆角速度上限"),
        };
    }

    public int CountProjectionFailures(EmTrajectory trajectory, DirectionSegmentView segment,
        EmPlannerConfiguration configuration)
    {
        if (trajectory == null) throw new ArgumentNullException(nameof(trajectory));
        if (segment == null) throw new ArgumentNullException(nameof(segment));
        if (configuration == null) throw new ArgumentNullException(nameof(configuration));
        return BuildLs(trajectory, segment, configuration.Frenet.MaximumProjectionDistanceMeters).ProjectionFailureCount;
    }

    private static VisualizationChart Velocity(EmTrajectory trajectory, EmPlannerConfiguration configuration)
    {
        IReadOnlyList<VisualizationPoint> points = Points(trajectory, p => p.SignedLongitudinalVelocity);
        return new VisualizationChart("velocity-t", "速度", "t (s)", "v (m/s)", new[]
        {
            new VisualizationSeries("velocity-t-current", "当前轨迹", VisualizationLineStyle.Solid, points),
            Limit("velocity-t-forward-limit", "前进生效限制", points, configuration.Longitudinal.MaximumForwardSpeedMetersPerSecond),
            Limit("velocity-t-reverse-limit", "倒车生效限制", points, -configuration.Longitudinal.MaximumReverseSpeedMetersPerSecond),
        });
    }

    private static VisualizationChart Acceleration(EmTrajectory trajectory, EmPlannerConfiguration configuration)
    {
        IReadOnlyList<VisualizationPoint> points = Points(trajectory, p => p.LongitudinalAcceleration);
        return new VisualizationChart("acceleration-t", "加速度", "t (s)", "a (m/s²)", new[]
        {
            new VisualizationSeries("acceleration-t-current", "当前轨迹", VisualizationLineStyle.Solid, points),
            Limit("acceleration-t-positive-limit", "加速生效限制", points, configuration.Longitudinal.MaximumAccelerationMetersPerSecondSquared),
            Limit("acceleration-t-negative-limit", "减速生效限制", points, -configuration.Longitudinal.MaximumDecelerationMetersPerSecondSquared),
        });
    }

    private static VisualizationChart Jerk(EmTrajectory trajectory, EmPlannerConfiguration configuration)
    {
        var intervals = new List<VisualizationPoint>();
        for (int index = 0; index + 1 < trajectory.Points.Count; index++)
            intervals.Add(new VisualizationPoint(trajectory.Points[index].TimeFromStart,
                trajectory.Points[index].LongitudinalJerk));
        return new VisualizationChart("jerk-t", "加加速度", "t (s)", "j (m/s³)", new[]
        {
            new VisualizationSeries("jerk-t-current", "当前轨迹", VisualizationLineStyle.Solid, intervals),
            Limit("jerk-t-positive-limit", "正向生效限制", intervals, configuration.Longitudinal.MaximumJerkMetersPerSecondCubed),
            Limit("jerk-t-negative-limit", "负向生效限制", intervals, -configuration.Longitudinal.MaximumJerkMetersPerSecondCubed),
        }, "j[i] 表示 [tᵢ, tᵢ₊₁)；末点后无时间区间");
    }

    private static VisualizationChart WithSymmetricLimit(string id, string title, string xAxis, string yAxis,
        IReadOnlyList<VisualizationPoint> points, double limit)
    {
        return new VisualizationChart(id, title, xAxis, yAxis, new[]
        {
            new VisualizationSeries(id + "-current", "当前轨迹", VisualizationLineStyle.Solid, points),
            Limit(id + "-positive-limit", "生效限制", points, limit),
            Limit(id + "-negative-limit", "生效限制", points, -limit),
        });
    }

    private static VisualizationSeries Limit(string id, string legend, IReadOnlyList<VisualizationPoint> points, double y)
    {
        double end = points.Count == 0 ? 0d : points[points.Count - 1].X;
        return new VisualizationSeries(id, legend, VisualizationLineStyle.Limit,
            new[] { new VisualizationPoint(0d, y), new VisualizationPoint(end, y) });
    }

    private static VisualizationChart Chart(string id, string title, string xAxis, string yAxis,
        IReadOnlyList<VisualizationPoint> points, string note,
        IReadOnlyList<VisualizationChartAnnotation> annotations = null)
    {
        return new VisualizationChart(id, title, xAxis, yAxis,
            new[] { new VisualizationSeries(id + "-current", "当前轨迹", VisualizationLineStyle.Solid, points) },
            note, annotations ?? Array.Empty<VisualizationChartAnnotation>());
    }

    private static IReadOnlyList<VisualizationPoint> Points(EmTrajectory trajectory, Func<EmTrajectoryPoint, double> y)
    {
        var points = new List<VisualizationPoint>();
        foreach (EmTrajectoryPoint point in trajectory.Points)
            points.Add(new VisualizationPoint(point.TimeFromStart, y(point)));
        return points;
    }

    private static IReadOnlyList<VisualizationPoint> PointsBySegmentS(EmTrajectory trajectory)
    {
        var points = new List<VisualizationPoint>();
        foreach (EmTrajectoryPoint point in trajectory.Points)
            points.Add(new VisualizationPoint(point.SegmentLocalS, point.VehicleCurvature));
        return points;
    }

    private static double CurvatureLimit(VehicleParameters vehicle)
    {
        if (vehicle.MaximumCurvaturePerMeter.HasValue)
        {
            return Math.Abs(vehicle.MaximumCurvaturePerMeter.Value);
        }

        if (vehicle.MinimumTurningRadiusMeters.HasValue && vehicle.MinimumTurningRadiusMeters.Value > 0d)
        {
            return 1d / vehicle.MinimumTurningRadiusMeters.Value;
        }

        throw new ArgumentException("Vehicle curvature limit is required.", nameof(vehicle));
    }

    private static LsBuildResult BuildLs(EmTrajectory trajectory, DirectionSegmentView segment,
        double maximumProjectionDistanceMeters)
    {
        var points = new List<VisualizationPoint>();
        var projector = new FrenetProjector();
        double seed = 0d;
        int failures = 0;
        foreach (EmTrajectoryPoint point in trajectory.Points)
        {
            if (!projector.TryProject(new Pose2D(point.X, point.Y, point.Yaw), segment, 0d, segment.LengthMeters,
                    maximumProjectionDistanceMeters, seed, out FrenetProjection projection))
            {
                failures++;
                continue;
            }
            points.Add(new VisualizationPoint(segment.SourceStartArcLength + projection.ReferenceS,
                projection.LateralOffset));
            seed = projection.ReferenceS;
        }
        return new LsBuildResult(points, failures);
    }

    private sealed class LsBuildResult
    {
        public LsBuildResult(IReadOnlyList<VisualizationPoint> points, int projectionFailureCount)
        {
            Points = points;
            ProjectionFailureCount = projectionFailureCount;
        }

        public IReadOnlyList<VisualizationPoint> Points { get; }
        public int ProjectionFailureCount { get; }
    }
}
