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

namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Processing;

/// <summary>校验粗路径契约、保护换向拓扑并产生统一间距的平滑输入。</summary>
public sealed class PathSmoothingPreprocessor
{
    private const double Tolerance = 1e-8d;
    private readonly ArcLengthResampler _resampler;

    /// <summary>创建使用默认确定性重采样器的预处理器。</summary>
    public PathSmoothingPreprocessor()
        : this(new ArcLengthResampler())
    {
    }

    /// <summary>创建使用指定重采样器的预处理器。</summary>
    public PathSmoothingPreprocessor(ArcLengthResampler resampler)
    {
        _resampler = resampler ?? throw new ArgumentNullException(nameof(resampler));
    }

    /// <summary>将一条粗路径请求校验、按方向拆分并按配置间距重采样。</summary>
    public bool TryPrepare(PathSmoothingRequest request, out PreparedPath preparedPath, out string reason)
    {
        preparedPath = null;
        reason = string.Empty;
        if (request == null || request.Map == null || request.Vehicle == null || request.Configuration == null ||
            request.CoarsePath == null || request.Segments == null || request.CoarsePath.Count == 0 || request.Segments.Count == 0)
        {
            reason = "平滑请求缺少粗路径、方向分段、地图、车辆或配置。";
            return false;
        }

        PathSmoothingConfiguration configuration = request.Configuration;
        if (!NumericGuard.IsPositiveFinite(configuration.OutputSpacingMeters))
        {
            reason = "平滑输出采样间距无效。";
            return false;
        }

        if (!ValidatePathPoints(request.CoarsePath, out reason) ||
            !ValidateSegments(request.CoarsePath, request.Segments, out reason))
        {
            return false;
        }

        var preparedSegments = new List<PreparedDirectionSegment>(request.Segments.Count);
        for (int segmentIndex = 0; segmentIndex < request.Segments.Count; segmentIndex++)
        {
            PathSegment sourceSegment = request.Segments[segmentIndex];
            double segmentStartArcLength = request.CoarsePath[sourceSegment.StartIndex].ArcLength;
            var segmentPoints = new List<SmoothingPoint2D>(sourceSegment.EndIndex - sourceSegment.StartIndex + 1);
            for (int pointIndex = sourceSegment.StartIndex; pointIndex <= sourceSegment.EndIndex; pointIndex++)
            {
                CoarsePathPoint point = request.CoarsePath[pointIndex];
                bool isGearSwitch = point.IsGearSwitchPoint;
                segmentPoints.Add(new SmoothingPoint2D(
                    point.X,
                    point.Y,
                    point.ArcLength - segmentStartArcLength,
                    point.Heading,
                    point.UnwrappedHeading,
                    point.BodyClearance,
                    isGearSwitch,
                    isGearSwitch ? SmoothedPathPointSource.GearSwitch : SmoothedPathPointSource.Anchor));
            }

            var unresampled = new PreparedDirectionSegment(
                sourceSegment.SegmentIndex,
                sourceSegment.Direction,
                segmentPoints,
                sourceSegment.StartsAtGearSwitch,
                sourceSegment.EndsAtGearSwitch,
                request.CoarsePath[sourceSegment.StartIndex].VehicleCurvature);
            if (!_resampler.TryResample(unresampled, configuration.OutputSpacingMeters, out PreparedDirectionSegment resampled, out reason))
                return false;
            preparedSegments.Add(new PreparedDirectionSegment(
                resampled.SegmentIndex,
                resampled.Direction,
                resampled.Points,
                resampled.StartsAtGearSwitch,
                resampled.EndsAtGearSwitch,
                unresampled.StartVehicleCurvaturePerMeter));
        }

        preparedPath = new PreparedPath(preparedSegments);
        return true;
    }

    private static bool ValidatePathPoints(IReadOnlyList<CoarsePathPoint> path, out string reason)
    {
        reason = string.Empty;
        CoarsePathPoint first = path[0];
        if (!IsValidPoint(first) || first.IsGearSwitchPoint || Math.Abs(first.ArcLength) > Tolerance)
        {
            reason = "粗路径首点无效。";
            return false;
        }

        for (int index = 1; index < path.Count; index++)
        {
            CoarsePathPoint previous = path[index - 1];
            CoarsePathPoint current = path[index];
            if (!IsValidPoint(current) || current.ArcLength + Tolerance < previous.ArcLength)
            {
                reason = "粗路径包含非法数值或非递增弧长。";
                return false;
            }

            double expectedHeadingDelta = AngleMath.ShortestSignedDifference(previous.Heading, current.Heading);
            if (!NumericGuard.IsFinite(expectedHeadingDelta) ||
                Math.Abs((current.UnwrappedHeading - previous.UnwrappedHeading) - expectedHeadingDelta) > Tolerance)
            {
                reason = "粗路径展开航向不连续。";
                return false;
            }

            bool duplicatePoseAndArc = Math.Abs(current.X - previous.X) <= Tolerance &&
                Math.Abs(current.Y - previous.Y) <= Tolerance &&
                Math.Abs(current.ArcLength - previous.ArcLength) <= Tolerance &&
                Math.Abs(AngleMath.ShortestSignedDifference(previous.Heading, current.Heading)) <= Tolerance;
            if (duplicatePoseAndArc)
            {
                if (previous.Direction == current.Direction || !current.IsGearSwitchPoint)
                {
                    reason = "粗路径包含非法的重复点。";
                    return false;
                }
            }
            else if (current.IsGearSwitchPoint || current.ArcLength <= previous.ArcLength + Tolerance)
            {
                reason = "粗路径普通点必须有正弧长增量且不得标记为换向点。";
                return false;
            }
        }
        return true;
    }

    private static bool ValidateSegments(
        IReadOnlyList<CoarsePathPoint> path,
        IReadOnlyList<PathSegment> segments,
        out string reason)
    {
        reason = string.Empty;
        int expectedStartIndex = 0;
        for (int segmentIndex = 0; segmentIndex < segments.Count; segmentIndex++)
        {
            PathSegment segment = segments[segmentIndex];
            if (segment == null || segment.SegmentIndex != segmentIndex || segment.StartIndex != expectedStartIndex ||
                segment.StartIndex < 0 || segment.EndIndex < segment.StartIndex || segment.EndIndex >= path.Count ||
                segment.StartsAtGearSwitch != path[segment.StartIndex].IsGearSwitchPoint)
            {
                reason = "粗路径方向分段索引或起始换向标记无效。";
                return false;
            }

            for (int pointIndex = segment.StartIndex; pointIndex <= segment.EndIndex; pointIndex++)
            {
                if (path[pointIndex].Direction != segment.Direction)
                {
                    reason = "粗路径方向分段包含不同方向的点。";
                    return false;
                }
            }

            bool hasNextSegment = segmentIndex + 1 < segments.Count;
            bool expectedEndsAtGearSwitch = hasNextSegment && segment.EndIndex + 1 < path.Count &&
                path[segment.EndIndex + 1].IsGearSwitchPoint;
            if (segment.EndsAtGearSwitch != expectedEndsAtGearSwitch)
            {
                reason = "粗路径方向分段末尾换向标记无效。";
                return false;
            }
            expectedStartIndex = segment.EndIndex + 1;
        }

        if (expectedStartIndex != path.Count)
        {
            reason = "粗路径方向分段未完整覆盖全部点。";
            return false;
        }
        return true;
    }

    private static bool IsValidPoint(CoarsePathPoint point)
    {
        return point != null && NumericGuard.IsFinite(point.X) && NumericGuard.IsFinite(point.Y) &&
            NumericGuard.IsFinite(point.Heading) && NumericGuard.IsFinite(point.UnwrappedHeading) &&
            NumericGuard.IsFinite(point.ArcLength) && point.ArcLength >= 0d &&
            NumericGuard.IsFinite(point.VehicleCurvature) && NumericGuard.IsFinite(point.BodyClearance) &&
            point.BodyClearance >= 0d &&
            (point.Direction == TravelDirection.Forward || point.Direction == TravelDirection.Reverse) &&
            Enum.IsDefined(typeof(CoarsePathPointSource), point.Source) &&
            Math.Abs(AngleMath.ShortestSignedDifference(point.Heading, AngleMath.NormalizeRadians(point.Heading))) <= Tolerance;
    }
}
