using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace TrajectoryPlanningVisualization;

public sealed class VisualizationPoint
{
    public VisualizationPoint(double x, double y)
    {
        VisualizationContract.RequireFinite(x, nameof(x));
        VisualizationContract.RequireFinite(y, nameof(y));
        X = x;
        Y = y;
    }

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

public sealed class VisualizationPose
{
    public VisualizationPose(double x, double y, double headingRadians)
    {
        VisualizationContract.RequireFinite(x, nameof(x));
        VisualizationContract.RequireFinite(y, nameof(y));
        VisualizationContract.RequireFinite(headingRadians, nameof(headingRadians));
        X = x;
        Y = y;
        HeadingRadians = headingRadians;
    }

    public double X { get; }
    public double Y { get; }
    public double HeadingRadians { get; }
}

public sealed class VisualizationBounds
{
    public VisualizationBounds(double xMin, double xMax, double yMin, double yMax)
    {
        VisualizationContract.RequireFinite(xMin, nameof(xMin));
        VisualizationContract.RequireFinite(xMax, nameof(xMax));
        VisualizationContract.RequireFinite(yMin, nameof(yMin));
        VisualizationContract.RequireFinite(yMax, nameof(yMax));
        if (xMax < xMin || yMax < yMin)
        {
            throw new ArgumentOutOfRangeException(nameof(xMax), "Bounds maxima must not precede minima.");
        }

        XMin = xMin;
        XMax = xMax;
        YMin = yMin;
        YMax = yMax;
    }

    public double XMin { get; }
    public double XMax { get; }
    public double YMin { get; }
    public double YMax { get; }
}

public sealed class VisualizationValue
{
    public VisualizationValue(string chineseName, string rawName, string value, string unit, string severity)
    {
        ChineseName = VisualizationContract.RequireText(chineseName, nameof(chineseName));
        RawName = VisualizationContract.RequireText(rawName, nameof(rawName));
        Value = VisualizationContract.RequireText(value, nameof(value));
        Unit = VisualizationContract.RequireText(unit, nameof(unit));
        if (severity != "normal" && severity != "notice" && severity != "failure")
        {
            throw new ArgumentOutOfRangeException(nameof(severity), "Severity must be normal, notice, or failure.");
        }

        Severity = severity;
    }

    public string ChineseName { get; }
    public string RawName { get; }
    public string Value { get; }
    public string Unit { get; }
    public string Severity { get; }
}

public sealed class VisualizationConfigurationGroup
{
    public VisualizationConfigurationGroup(string chineseTitle, IReadOnlyList<VisualizationValue> entries)
    {
        ChineseTitle = VisualizationContract.RequireText(chineseTitle, nameof(chineseTitle));
        Entries = VisualizationContract.Copy(entries, nameof(entries));
    }

    public string ChineseTitle { get; }
    public IReadOnlyList<VisualizationValue> Entries { get; }
}

internal static class VisualizationContract
{
    public static string RequireText(string value, string parameterName)
    {
        if (value == null)
        {
            throw new ArgumentNullException(parameterName);
        }

        return value;
    }

    public static void RequireFinite(double value, string parameterName)
    {
        if (double.IsNaN(value) || double.IsInfinity(value))
        {
            throw new ArgumentOutOfRangeException(parameterName, "Value must be finite.");
        }
    }

    public static void RequireFinite(double? value, string parameterName)
    {
        if (value.HasValue)
        {
            RequireFinite(value.Value, parameterName);
        }
    }

    public static IReadOnlyList<T> Copy<T>(IReadOnlyList<T> values, string parameterName)
    {
        if (values == null)
        {
            throw new ArgumentNullException(parameterName);
        }

        var copy = new List<T>(values.Count);
        for (var index = 0; index < values.Count; index++)
        {
            T value = values[index];
            if (value is null)
            {
                throw new ArgumentException("Collections cannot contain null elements.", parameterName);
            }

            copy.Add(value);
        }

        return new ReadOnlyCollection<T>(copy);
    }
}
