using System;
using System.Globalization;
using System.Text;

namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Output.Visualization;

/// <summary>把单张共享图形定义渲染为 UTF-8 XML 可编辑 SVG；轨迹仅由离散点组成。</summary>
public sealed class SmoothingSvgRenderer
{
    public string Render(SmoothingFigureModel model)
    {
        if (model == null) throw new ArgumentNullException(nameof(model));
        return Render(new SmoothingFigureSetBuilder().Build(model).Figures[1]);
    }

    public string Render(SmoothingFigureDefinition figure)
    {
        if (figure == null) throw new ArgumentNullException(nameof(figure));
        var svg = new StringBuilder();
        svg.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"")
            .Append(Number(figure.FigureWidthPoints)).Append("pt\" height=\"").Append(Number(figure.FigureHeightPoints))
            .Append("pt\" viewBox=\"0 0 ").Append(Number(figure.FigureWidthPoints)).Append(' ').Append(Number(figure.FigureHeightPoints)).Append("\">\n")
            .Append("<rect width=\"100%\" height=\"100%\" fill=\"#FFFFFF\"/>\n")
            .Append("<clipPath id=\"plot-clip\"><rect x=\"").Append(Number(figure.PlotXPoints)).Append("\" y=\"").Append(Number(figure.PlotYPoints))
            .Append("\" width=\"").Append(Number(figure.PlotWidthPoints)).Append("\" height=\"").Append(Number(figure.PlotHeightPoints)).Append("\"/></clipPath>\n");
        AppendTitle(svg, figure);
        if (figure.IsCurvatureFigure) AppendCurvatureAxes(svg, figure); else AppendOverheadAxes(svg, figure);
        svg.Append("<g clip-path=\"url(#plot-clip)\">\n");
        if (!figure.IsCurvatureFigure && figure.ShowsMapContext) AppendObstacles(svg, figure);
        if (figure.IsCurvatureFigure) AppendCurvaturePoints(svg, figure); else AppendPathPoints(svg, figure);
        if (!figure.IsCurvatureFigure && figure.ShowsMapContext) AppendStartGoal(svg, figure);
        svg.Append("</g>\n");
        AppendLegend(svg, figure);
        svg.Append("</svg>");
        return svg.ToString();
    }

    private static void AppendTitle(StringBuilder svg, SmoothingFigureDefinition figure)
    {
        svg.Append("<text x=\"").Append(Number(figure.PlotXPoints)).Append("\" y=\"23\" font-size=\"12\"><tspan font-family=\"SimSun\">")
            .Append(Escape(figure.Title)).Append("</tspan><tspan font-family=\"Times New Roman\"> — ").Append(Escape(figure.Model.ScenarioLabel)).Append("</tspan></text>\n");
    }

    private static void AppendOverheadAxes(StringBuilder svg, SmoothingFigureDefinition figure)
    {
        AppendPlotFrame(svg, figure);
        for (int index = 0; index < figure.XTicks.Count; index++)
        {
            double x = WorldX(figure, figure.XTicks[index]);
            AppendGridLine(svg, x, figure.PlotYPoints, x, figure.PlotYPoints + figure.PlotHeightPoints);
            svg.Append("<text x=\"").Append(Number(x)).Append("\" y=\"").Append(Number(figure.PlotYPoints + figure.PlotHeightPoints + 15d))
                .Append("\" text-anchor=\"middle\" font-family=\"Times New Roman\" font-size=\"8\">").Append(Number(figure.XTicks[index])).Append("</text>\n");
        }
        for (int index = 0; index < figure.YTicks.Count; index++)
        {
            double y = WorldY(figure, figure.YTicks[index]);
            AppendGridLine(svg, figure.PlotXPoints, y, figure.PlotXPoints + figure.PlotWidthPoints, y);
            svg.Append("<text x=\"").Append(Number(figure.PlotXPoints - 8d)).Append("\" y=\"").Append(Number(y + 3d))
                .Append("\" text-anchor=\"end\" font-family=\"Times New Roman\" font-size=\"8\">").Append(Number(figure.YTicks[index])).Append("</text>\n");
        }
        svg.Append("<text x=\"").Append(Number(figure.PlotXPoints + figure.PlotWidthPoints / 2d)).Append("\" y=\"").Append(Number(figure.PlotYPoints + figure.PlotHeightPoints + 31d))
            .Append("\" text-anchor=\"middle\" font-family=\"Times New Roman\" font-size=\"10\">X (m)</text>\n")
            .Append("<text x=\"19\" y=\"").Append(Number(figure.PlotYPoints + figure.PlotHeightPoints / 2d)).Append("\" text-anchor=\"middle\" transform=\"rotate(-90 19 ")
            .Append(Number(figure.PlotYPoints + figure.PlotHeightPoints / 2d)).Append(")\" font-family=\"Times New Roman\" font-size=\"10\">Y (m)</text>\n");
    }

    private static void AppendCurvatureAxes(StringBuilder svg, SmoothingFigureDefinition figure)
    {
        AppendPlotFrame(svg, figure);
        for (int index = 0; index < figure.CurvatureArcLengthTicks.Count; index++)
        {
            double x = CurvatureX(figure, figure.CurvatureArcLengthTicks[index]);
            AppendGridLine(svg, x, figure.PlotYPoints, x, figure.PlotYPoints + figure.PlotHeightPoints);
            svg.Append("<text x=\"").Append(Number(x)).Append("\" y=\"").Append(Number(figure.PlotYPoints + figure.PlotHeightPoints + 15d))
                .Append("\" text-anchor=\"middle\" font-family=\"Times New Roman\" font-size=\"8\">").Append(Number(figure.CurvatureArcLengthTicks[index])).Append("</text>\n");
        }
        for (int index = 0; index < figure.CurvatureTicks.Count; index++)
        {
            double y = CurvatureY(figure, figure.CurvatureTicks[index]);
            AppendGridLine(svg, figure.PlotXPoints, y, figure.PlotXPoints + figure.PlotWidthPoints, y);
            svg.Append("<text x=\"").Append(Number(figure.PlotXPoints - 8d)).Append("\" y=\"").Append(Number(y + 3d))
                .Append("\" text-anchor=\"end\" font-family=\"Times New Roman\" font-size=\"8\">").Append(Number(figure.CurvatureTicks[index])).Append("</text>\n");
        }
        if (figure.CurvatureMinimumPerMeter < 0d && figure.CurvatureMaximumPerMeter > 0d)
        {
            double zero = CurvatureY(figure, 0d);
            svg.Append("<line x1=\"").Append(Number(figure.PlotXPoints)).Append("\" y1=\"").Append(Number(zero)).Append("\" x2=\"")
                .Append(Number(figure.PlotXPoints + figure.PlotWidthPoints)).Append("\" y2=\"").Append(Number(zero)).Append("\" stroke=\"#4D4D4D\" stroke-width=\"0.65\"/>\n");
        }
        svg.Append("<text x=\"").Append(Number(figure.PlotXPoints + figure.PlotWidthPoints / 2d)).Append("\" y=\"").Append(Number(figure.PlotYPoints + figure.PlotHeightPoints + 31d))
            .Append("\" text-anchor=\"middle\" font-family=\"Times New Roman\" font-size=\"10\">s (m)</text>\n")
            .Append("<text x=\"19\" y=\"").Append(Number(figure.PlotYPoints + figure.PlotHeightPoints / 2d)).Append("\" text-anchor=\"middle\" transform=\"rotate(-90 19 ")
            .Append(Number(figure.PlotYPoints + figure.PlotHeightPoints / 2d)).Append(")\" font-family=\"Times New Roman\" font-size=\"10\">κ (m⁻¹)</text>\n");
    }

    private static void AppendPlotFrame(StringBuilder svg, SmoothingFigureDefinition figure)
    {
        svg.Append("<rect x=\"").Append(Number(figure.PlotXPoints)).Append("\" y=\"").Append(Number(figure.PlotYPoints)).Append("\" width=\"")
            .Append(Number(figure.PlotWidthPoints)).Append("\" height=\"").Append(Number(figure.PlotHeightPoints)).Append("\" fill=\"#FFFFFF\" stroke=\"#000000\" stroke-width=\"0.75\"/>\n");
    }

    private static void AppendGridLine(StringBuilder svg, double x1, double y1, double x2, double y2)
    {
        svg.Append("<line x1=\"").Append(Number(x1)).Append("\" y1=\"").Append(Number(y1)).Append("\" x2=\"").Append(Number(x2)).Append("\" y2=\"")
            .Append(Number(y2)).Append("\" stroke=\"#E6E6E6\" stroke-width=\"0.5\"/>\n");
    }

    private static void AppendObstacles(StringBuilder svg, SmoothingFigureDefinition figure)
    {
        for (int index = 0; index < figure.Model.Obstacles.Count; index++)
        {
            SmoothingFigureObstacle obstacle = figure.Model.Obstacles[index];
            svg.Append("<rect class=\"obstacle\" x=\"").Append(Number(WorldX(figure, obstacle.X))).Append("\" y=\"")
                .Append(Number(WorldY(figure, obstacle.Y + obstacle.Height))).Append("\" width=\"").Append(Number(obstacle.Width * figure.WorldScalePointsPerMeter))
                .Append("\" height=\"").Append(Number(obstacle.Height * figure.WorldScalePointsPerMeter)).Append("\" fill=\"#D9D9D9\" stroke=\"#808080\" stroke-width=\"0.35\"/>\n");
        }
    }

    private static void AppendPathPoints(StringBuilder svg, SmoothingFigureDefinition figure)
    {
        for (int viewIndex = 0; viewIndex < figure.Series.Count; viewIndex++)
        {
            SmoothingFigureSeriesView view = figure.Series[viewIndex];
            AppendPoints(svg, view, point => WorldX(figure, point.X), point => WorldY(figure, point.Y));
            for (int markerIndex = 0; markerIndex < view.Series.ViolationMarkers.Count; markerIndex++) AppendCross(svg, figure, view.Series.ViolationMarkers[markerIndex], view.Series.Color);
        }
    }

    private static void AppendCurvaturePoints(StringBuilder svg, SmoothingFigureDefinition figure)
    {
        for (int viewIndex = 0; viewIndex < figure.Series.Count; viewIndex++)
        {
            SmoothingFigureSeriesView view = figure.Series[viewIndex];
            AppendPoints(svg, view, point => CurvatureX(figure, point.ArcLength), point => CurvatureY(figure, point.VehicleCurvature));
        }
    }

    private static void AppendPoints(StringBuilder svg, SmoothingFigureSeriesView view, Func<SmoothingFigurePoint, double> x, Func<SmoothingFigurePoint, double> y)
    {
        if (!view.Series.IsCurveVisible) return;
        svg.Append("<g class=\"trajectory-series\" data-series=\"").Append(Escape(view.Series.Key)).Append("\" fill=\"").Append(view.Series.Color).Append("\" opacity=\"").Append(Number(view.Opacity)).Append("\">\n");
        for (int index = 0; index < view.Series.Points.Count; index++)
        {
            SmoothingFigurePoint point = view.Series.Points[index];
            svg.Append("<circle class=\"trajectory-point\" cx=\"").Append(Number(x(point))).Append("\" cy=\"").Append(Number(y(point))).Append("\" r=\"").Append(Number(view.PointRadiusPoints)).Append("\"/>\n");
        }
        svg.Append("</g>\n");
    }

    private static void AppendCross(StringBuilder svg, SmoothingFigureDefinition figure, SmoothingFigurePoint point, string color)
    {
        double x = WorldX(figure, point.X), y = WorldY(figure, point.Y), radius = 3d;
        svg.Append("<g class=\"violation-cross\" stroke=\"").Append(color).Append("\" stroke-width=\"1.1\"><line x1=\"").Append(Number(x - radius)).Append("\" y1=\"")
            .Append(Number(y - radius)).Append("\" x2=\"").Append(Number(x + radius)).Append("\" y2=\"").Append(Number(y + radius)).Append("\"/><line x1=\"")
            .Append(Number(x - radius)).Append("\" y1=\"").Append(Number(y + radius)).Append("\" x2=\"").Append(Number(x + radius)).Append("\" y2=\"").Append(Number(y - radius)).Append("\"/></g>\n");
    }

    private static void AppendStartGoal(StringBuilder svg, SmoothingFigureDefinition figure)
    {
        svg.Append("<circle class=\"start-marker\" cx=\"").Append(Number(WorldX(figure, figure.Model.Start.X))).Append("\" cy=\"").Append(Number(WorldY(figure, figure.Model.Start.Y)))
            .Append("\" r=\"3.2\" fill=\"#F0E442\" stroke=\"#000000\" stroke-width=\"0.8\"/>\n");
        double x = WorldX(figure, figure.Model.Goal.X), y = WorldY(figure, figure.Model.Goal.Y), radius = 4d;
        svg.Append("<polygon class=\"goal-marker\" points=\"").Append(Number(x)).Append(',').Append(Number(y - radius)).Append(' ').Append(Number(x + radius)).Append(',').Append(Number(y)).Append(' ')
            .Append(Number(x)).Append(',').Append(Number(y + radius)).Append(' ').Append(Number(x - radius)).Append(',').Append(Number(y)).Append("\" fill=\"#CC79A7\" stroke=\"#000000\" stroke-width=\"0.8\"/>\n");
    }

    private static void AppendLegend(StringBuilder svg, SmoothingFigureDefinition figure)
    {
        double columnWidth = 198d;
        for (int index = 0; index < figure.LegendEntries.Count; index++)
        {
            SmoothingFigureLegendEntry entry = figure.LegendEntries[index];
            int column = index % 2;
            int row = index / 2;
            double x = figure.PlotXPoints + column * columnWidth;
            double y = figure.LegendYPoints + row * 15d;
            svg.Append("<circle class=\"legend-point\" cx=\"").Append(Number(x + 3d)).Append("\" cy=\"").Append(Number(y - 3d)).Append("\" r=\"2.2\" fill=\"")
                .Append(entry.Color).Append("\"/>\n<text x=\"").Append(Number(x + 10d)).Append("\" y=\"").Append(Number(y)).Append("\" font-size=\"8.5\"><tspan font-family=\"SimSun\">")
                .Append(Escape(entry.Label)).Append("</tspan></text>\n");
        }
    }

    private static double WorldX(SmoothingFigureDefinition figure, double x) { return figure.PlotXPoints + (x - figure.WorldXMinMeters) * figure.WorldScalePointsPerMeter; }
    private static double WorldY(SmoothingFigureDefinition figure, double y) { return figure.PlotYPoints + figure.PlotHeightPoints - (y - figure.WorldYMinMeters) * figure.WorldScalePointsPerMeter; }
    private static double CurvatureX(SmoothingFigureDefinition figure, double arcLength) { return figure.PlotXPoints + arcLength / figure.CurvatureArcLengthMaximumMeters * figure.PlotWidthPoints; }
    private static double CurvatureY(SmoothingFigureDefinition figure, double curvature) { return figure.PlotYPoints + figure.PlotHeightPoints - (curvature - figure.CurvatureMinimumPerMeter) / (figure.CurvatureMaximumPerMeter - figure.CurvatureMinimumPerMeter) * figure.PlotHeightPoints; }
    private static string Number(double value) { return value.ToString("0.###", CultureInfo.InvariantCulture); }
    private static string Escape(string value) { return (value ?? string.Empty).Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("\"", "&quot;").Replace("'", "&apos;"); }
}
