using System;
using MultiWheelC.TrajectoryPlanning.Utils;

namespace MultiWheelC.TrajectoryPlanning.Mapping;

/// <summary>
/// 有限的世界地图边界。
/// 单位：mm；范围采用左闭右开 [XMin, XMax) × [YMin, YMax)。
/// </summary>
public sealed class MapBoundsMm : IEquatable<MapBoundsMm>
{
    /// <summary>单张地图允许的最大栅格数，超过该值会拒绝创建地图。</summary>
    public const int MaximumCellCount = 4000000;

    /// <summary>
    /// 创建地图世界边界。
    ///
    /// 参数：
    /// - xMin、xMax：世界 X 轴下限和上限，单位 mm，且 xMax 必须大于 xMin。
    /// - yMin、yMax：世界 Y 轴下限和上限，单位 mm，且 yMax 必须大于 yMin。
    ///
    /// 注意：边界采用左闭右开规则，上限坐标不属于地图。
    /// </summary>
    public MapBoundsMm(float xMin, float xMax, float yMin, float yMax)
    {
        if (!NumericGuard.IsFinite(xMin) || !NumericGuard.IsFinite(xMax) ||
            !NumericGuard.IsFinite(yMin) || !NumericGuard.IsFinite(yMax) ||
            xMax <= xMin || yMax <= yMin)
            throw new ArgumentOutOfRangeException(nameof(xMax), "Map bounds must be finite and non-degenerate.");
        XMin = xMin; XMax = xMax; YMin = yMin; YMax = yMax;
    }

    /// <summary>世界 X 轴下限，单位 mm，包含在地图内。</summary>
    public float XMin { get; }
    /// <summary>世界 X 轴上限，单位 mm，不包含在地图内。</summary>
    public float XMax { get; }
    /// <summary>世界 Y 轴下限，单位 mm，包含在地图内。</summary>
    public float YMin { get; }
    /// <summary>世界 Y 轴上限，单位 mm，不包含在地图内。</summary>
    public float YMax { get; }

    /// <summary>
    /// 判断世界坐标是否属于地图边界。
    ///
    /// 参数：xMm、yMm 为世界坐标，单位 mm。
    /// 返回：坐标位于 [XMin, XMax) × [YMin, YMax) 时为 true，否则为 false。
    /// </summary>
    public bool Contains(float xMm, float yMm)
    {
        return xMm >= XMin && xMm < XMax && yMm >= YMin && yMm < YMax;
    }

    /// <summary>
    /// 根据栅格分辨率计算行列数。
    ///
    /// 参数：resolutionMm 为每个方格的边长，单位 mm，取值必须在 [20, 200]；rows、cols 为输出行数和列数。
    /// 返回：无；当分辨率无效或总格数超过 <see cref="MaximumCellCount"/> 时抛出异常。
    /// </summary>
    public void GetDimensions(float resolutionMm, out int rows, out int cols)
    {
        if (!NumericGuard.IsInRange(resolutionMm, 20f, 200f))
            throw new ArgumentOutOfRangeException(nameof(resolutionMm), "ResolutionMm must be within [20, 200].");
        double columnCount = Math.Ceiling(((double)XMax - XMin) / resolutionMm);
        double rowCount = Math.Ceiling(((double)YMax - YMin) / resolutionMm);
        if (columnCount > int.MaxValue || rowCount > int.MaxValue || columnCount <= 0d || rowCount <= 0d)
            throw new ArgumentOutOfRangeException(nameof(resolutionMm), "Map dimensions are invalid.");
        cols = (int)columnCount; rows = (int)rowCount;
        long cellCount = checked((long)rows * cols);
        if (cellCount > MaximumCellCount)
            throw new ArgumentOutOfRangeException(nameof(resolutionMm), "Map cell count exceeds 4,000,000.");
    }

    /// <summary>比较两个边界的四个 mm 坐标是否完全相同。</summary>
    public bool Equals(MapBoundsMm other)
    {
        return other != null && XMin.Equals(other.XMin) && XMax.Equals(other.XMax) &&
            YMin.Equals(other.YMin) && YMax.Equals(other.YMax);
    }
    /// <summary>比较当前边界与指定对象是否表示相同的世界范围。</summary>
    public override bool Equals(object obj) { return Equals(obj as MapBoundsMm); }
    /// <summary>返回由四个边界坐标组成的哈希值，用于缓存键比较。</summary>
    public override int GetHashCode()
    {
        unchecked { int hash = XMin.GetHashCode(); hash = hash * 31 + XMax.GetHashCode(); hash = hash * 31 + YMin.GetHashCode(); return hash * 31 + YMax.GetHashCode(); }
    }
}
