using Avalonia;

namespace AtomUI.Utils;

public static class MathUtils
{
    // smallest such that 1.0+DoubleEpsilon != 1.0
    internal const double DoubleEpsilon = 2.2204460492503131e-016;

    private const float FloatEpsilon = 1.192092896e-07F;

    public static double RoundToFixedPoint(double value, int fixedCount)
    {
        var factor = Math.Pow(10, fixedCount);
        return Math.Round(value * factor) / factor;
    }

    /// <summary>
    /// AreClose - Returns whether or not two doubles are "close".  That is, whether or
    /// not they are within epsilon of each other.
    /// </summary>
    /// <param name="value1"> The first double to compare. </param>
    /// <param name="value2"> The second double to compare. </param>
    public static bool AreClose(double value1, double value2)
    {
        //in case they are Infinities (then epsilon check does not work)
        if (value1 == value2)
        {
            return true;
        }

        var eps   = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DoubleEpsilon;
        var delta = value1 - value2;
        return -eps < delta && eps > delta;
    }

    /// <summary>
    /// AreClose - Returns whether or not two doubles are "close".  That is, whether or
    /// not they are within epsilon of each other.
    /// </summary>
    /// <param name="value1"> The first double to compare. </param>
    /// <param name="value2"> The second double to compare. </param>
    /// <param name="eps"> The fixed epsilon value used to compare.</param>
    public static bool AreClose(double value1, double value2, double eps)
    {
        //in case they are Infinities (then epsilon check does not work)
        if (value1 == value2)
        {
            return true;
        }

        var delta = value1 - value2;
        return -eps < delta && eps > delta;
    }

    /// <summary>
    /// AreClose - Returns whether or not two floats are "close".  That is, whether or
    /// not they are within epsilon of each other.
    /// </summary>
    /// <param name="value1"> The first float to compare. </param>
    /// <param name="value2"> The second float to compare. </param>
    public static bool AreClose(float value1, float value2)
    {
        //in case they are Infinities (then epsilon check does not work)
        if (value1 == value2)
        {
            return true;
        }

        var eps   = (Math.Abs(value1) + Math.Abs(value2) + 10.0f) * FloatEpsilon;
        var delta = value1 - value2;
        return -eps < delta && eps > delta;
    }

    /// <summary>
    /// LessThan - Returns whether or not the first double is less than the second double.
    /// That is, whether or not the first is strictly less than *and* not within epsilon of
    /// the other number.
    /// </summary>
    /// <param name="value1"> The first double to compare. </param>
    /// <param name="value2"> The second double to compare. </param>
    public static bool LessThan(double value1, double value2)
    {
        return value1 < value2 && !AreClose(value1, value2);
    }

    /// <summary>
    /// LessThan - Returns whether or not the first float is less than the second float.
    /// That is, whether or not the first is strictly less than *and* not within epsilon of
    /// the other number.
    /// </summary>
    /// <param name="value1"> The first single float to compare. </param>
    /// <param name="value2"> The second single float to compare. </param>
    public static bool LessThan(float value1, float value2)
    {
        return value1 < value2 && !AreClose(value1, value2);
    }

    /// <summary>
    /// GreaterThan - Returns whether or not the first double is greater than the second double.
    /// That is, whether or not the first is strictly greater than *and* not within epsilon of
    /// the other number.
    /// </summary>
    /// <param name="value1"> The first double to compare. </param>
    /// <param name="value2"> The second double to compare. </param>
    public static bool GreaterThan(double value1, double value2)
    {
        return value1 > value2 && !AreClose(value1, value2);
    }

    /// <summary>
    /// GreaterThan - Returns whether or not the first float is greater than the second float.
    /// That is, whether or not the first is strictly greater than *and* not within epsilon of
    /// the other number.
    /// </summary>
    /// <param name="value1"> The first float to compare. </param>
    /// <param name="value2"> The second float to compare. </param>
    public static bool GreaterThan(float value1, float value2)
    {
        return value1 > value2 && !AreClose(value1, value2);
    }

    /// <summary>
    /// LessThanOrClose - Returns whether or not the first double is less than or close to
    /// the second double.  That is, whether or not the first is strictly less than or within
    /// epsilon of the other number.
    /// </summary>
    /// <param name="value1"> The first double to compare. </param>
    /// <param name="value2"> The second double to compare. </param>
    public static bool LessThanOrClose(double value1, double value2)
    {
        return value1 < value2 || AreClose(value1, value2);
    }

    /// <summary>
    /// LessThanOrClose - Returns whether or not the first float is less than or close to
    /// the second float.  That is, whether or not the first is strictly less than or within
    /// epsilon of the other number.
    /// </summary>
    /// <param name="value1"> The first float to compare. </param>
    /// <param name="value2"> The second float to compare. </param>
    public static bool LessThanOrClose(float value1, float value2)
    {
        return value1 < value2 || AreClose(value1, value2);
    }

    /// <summary>
    /// GreaterThanOrClose - Returns whether or not the first double is greater than or close to
    /// the second double.  That is, whether or not the first is strictly greater than or within
    /// epsilon of the other number.
    /// </summary>
    /// <param name="value1"> The first double to compare. </param>
    /// <param name="value2"> The second double to compare. </param>
    public static bool GreaterThanOrClose(double value1, double value2)
    {
        return value1 > value2 || AreClose(value1, value2);
    }

    /// <summary>
    /// GreaterThanOrClose - Returns whether or not the first float is greater than or close to
    /// the second float.  That is, whether or not the first is strictly greater than or within
    /// epsilon of the other number.
    /// </summary>
    /// <param name="value1"> The first float to compare. </param>
    /// <param name="value2"> The second float to compare. </param>
    public static bool GreaterThanOrClose(float value1, float value2)
    {
        return value1 > value2 || AreClose(value1, value2);
    }

    /// <summary>
    /// IsOne - Returns whether or not the double is "close" to 1.  Same as AreClose(double, 1),
    /// but this is faster.
    /// </summary>
    /// <param name="value"> The double to compare to 1. </param>
    public static bool IsOne(double value)
    {
        return Math.Abs(value - 1.0) < 10.0 * DoubleEpsilon;
    }

    /// <summary>
    /// IsOne - Returns whether or not the float is "close" to 1.  Same as AreClose(float, 1),
    /// but this is faster.
    /// </summary>
    /// <param name="value"> The float to compare to 1. </param>
    public static bool IsOne(float value)
    {
        return Math.Abs(value - 1.0f) < 10.0f * FloatEpsilon;
    }

    /// <summary>
    /// IsZero - Returns whether or not the double is "close" to 0.  Same as AreClose(double, 0),
    /// but this is faster.
    /// </summary>
    /// <param name="value"> The double to compare to 0. </param>
    public static bool IsZero(double value)
    {
        return Math.Abs(value) < 10.0 * DoubleEpsilon;
    }

    /// <summary>
    /// IsZero - Returns whether or not the float is "close" to 0.  Same as AreClose(float, 0),
    /// but this is faster.
    /// </summary>
    /// <param name="value"> The float to compare to 0. </param>
    public static bool IsZero(float value)
    {
        return Math.Abs(value) < 10.0f * FloatEpsilon;
    }

    /// <summary>
    /// Converts an angle in degrees to radians.
    /// </summary>
    /// <param name="angle">The angle in degrees.</param>
    /// <returns>The angle in radians.</returns>
    public static double Deg2Rad(double angle)
    {
        return angle * (Math.PI / 180d);
    }

    /// <summary>
    /// Converts an angle in gradians to radians.
    /// </summary>
    /// <param name="angle">The angle in gradians.</param>
    /// <returns>The angle in radians.</returns>
    public static double Grad2Rad(double angle)
    {
        return angle * (Math.PI / 200d);
    }

    /// <summary>
    /// Converts an angle in turns to radians.
    /// </summary>
    /// <param name="angle">The angle in turns.</param>
    /// <returns>The angle in radians.</returns>
    public static double Turn2Rad(double angle)
    {
        return angle * 2 * Math.PI;
    }

    /// <summary>
    /// Calculates the point of an angle on an ellipse.
    /// </summary>
    /// <param name="centre">The centre point of the ellipse.</param>
    /// <param name="radiusX">The x radius of the ellipse.</param>
    /// <param name="radiusY">The y radius of the ellipse.</param>
    /// <param name="angle">The angle in radians.</param>
    /// <returns>A point on the ellipse.</returns>
    public static Point GetEllipsePoint(Point centre, double radiusX, double radiusY, double angle)
    {
        return new Point(radiusX * Math.Cos(angle) + centre.X, radiusY * Math.Sin(angle) + centre.Y);
    }
}