using System;

namespace MultiWheelC.TrajectoryPlanning.EMPlanner;

internal sealed class LateralPathInterpolator
{
    private const double BoundaryTolerance = 1e-10d;
    private readonly LateralPath path;

    public LateralPathInterpolator(LateralPath path)
    {
        if (path == null || !path.IsIndependentlyValidated || path.Points.Count < 2)
            throw new ArgumentException("Trajectory assembly requires an independently validated lateral path.", nameof(path));

        double previousPathS = double.NegativeInfinity;
        for (int index = 0; index < path.Points.Count; index++)
        {
            LateralPathPoint point = path.Points[index];
            if (point == null || point.PathS <= previousPathS)
                throw new ArgumentException("Lateral path points must have strictly increasing PathS.", nameof(path));
            previousPathS = point.PathS;
        }

        this.path = path;
    }

    public InterpolatedLateralPathPoint Interpolate(double pathS)
    {
        if (!IsFinite(pathS))
            throw new ArgumentOutOfRangeException(nameof(pathS));

        LateralPathPoint first = path.Points[0];
        LateralPathPoint last = path.Points[path.Points.Count - 1];
        if (pathS < first.PathS - BoundaryTolerance || pathS > last.PathS + BoundaryTolerance)
            throw new ArgumentOutOfRangeException(nameof(pathS));
        if (pathS <= first.PathS + BoundaryTolerance)
            return From(first);
        if (pathS >= last.PathS - BoundaryTolerance)
            return From(last);

        for (int index = 1; index < path.Points.Count; index++)
        {
            LateralPathPoint right = path.Points[index];
            if (pathS <= right.PathS)
            {
                LateralPathPoint left = path.Points[index - 1];
                double ratio = (pathS - left.PathS) / (right.PathS - left.PathS);
                return new InterpolatedLateralPathPoint(
                    Linear(left.X, right.X, ratio),
                    Linear(left.Y, right.Y, ratio),
                    NormalizeYaw(left.VehicleYaw + ratio * NormalizeYaw(right.VehicleYaw - left.VehicleYaw)),
                    Linear(left.VehicleCurvature, right.VehicleCurvature, ratio));
            }
        }

        throw new InvalidOperationException("A PathS value inside the lateral path was not bracketed.");
    }

    private static InterpolatedLateralPathPoint From(LateralPathPoint point)
    {
        return new InterpolatedLateralPathPoint(point.X, point.Y, NormalizeYaw(point.VehicleYaw), point.VehicleCurvature);
    }

    private static double Linear(double left, double right, double ratio)
    {
        return left + ratio * (right - left);
    }

    internal static double NormalizeYaw(double yaw)
    {
        double normalized = yaw % (2d * Math.PI);
        if (normalized >= Math.PI)
            normalized -= 2d * Math.PI;
        if (normalized < -Math.PI)
            normalized += 2d * Math.PI;
        return normalized;
    }

    private static bool IsFinite(double value)
    {
        return !double.IsNaN(value) && !double.IsInfinity(value);
    }
}

internal sealed class InterpolatedLateralPathPoint
{
    public InterpolatedLateralPathPoint(double x, double y, double yaw, double vehicleCurvature)
    {
        X = x;
        Y = y;
        Yaw = yaw;
        VehicleCurvature = vehicleCurvature;
    }

    public double X { get; }
    public double Y { get; }
    public double Yaw { get; }
    public double VehicleCurvature { get; }
}
