using System;
using System.Collections.Generic;
using Unity.CodeEditor.Editing;
using UnityEngine;

namespace Unity.CodeEditor.Utils
{
    //PENDING TO COMPLETE
    internal static class ExtensionMethods
    {
        #region Rectangle

        internal static Rect WithY(this Rect rect, float y)
        {
            return new Rect(rect.x, y, rect.width, rect.height);
        }

        internal static Rect WithWidth(this Rect rect, float width)
        {
            return new Rect(rect.x, rect.y, width, rect.height);
        }

        #endregion

        #region Vector2
        internal static bool IsClose(this Vector2 v, Vector2 other)
        {
            return IsClose(v.x, other.x) && IsClose(v.y, other.y);
        }

        internal static Vector2 RelativeTo(this Vector2 v, Rect rect)
        {
            return new Vector2(v.x - rect.x, v.y - rect.y);
        }

        #endregion

        #region Epsilon / IsClose / CoerceValue
        /// <summary>
        /// Epsilon used for <c>IsClose()</c> implementations.
        /// We can use up quite a few digits in front of the decimal point (due to visual positions being relative to document origin),
        /// and there's no need to be too accurate (we're dealing with pixels here),
        /// so we will use the value 0.01.
        /// Previosly we used 1e-8 but that was causing issues:
        /// https://community.sharpdevelop.net/forums/t/16048.aspx
        /// </summary>
        internal const float Epsilon = 0.01f;

        /// <summary>
        /// Returns true if the doubles are close (difference smaller than 0.01).
        /// </summary>
        internal static bool IsClose(this float d1, float d2)
        {
            // ReSharper disable once CompareOfFloatsByEqualityOperator
            if (d1 == d2) // required for infinities
                return true;
            return Math.Abs(d1 - d2) < Epsilon;
        }

        /// <summary>
        /// Returns true if the doubles are close (difference smaller than 0.01).
        /// </summary>
        internal static bool IsClose(this double d1, double d2)
        {
            // ReSharper disable once CompareOfFloatsByEqualityOperator
            if (d1 == d2) // required for infinities
                return true;
            return Math.Abs(d1 - d2) < Epsilon;
        }

        /// <summary>
        /// Returns true if the doubles are close (difference smaller than 0.01).
        /// </summary>
        /*public static bool IsClose(this Size d1, Size d2)
        {
            return IsClose(d1.Width, d2.Width) && IsClose(d1.Height, d2.Height);
        }*/

        /// <summary>
        /// Forces the value to stay between mininum and maximum.
        /// </summary>
        /// <returns>minimum, if value is less than minimum.
        /// Maximum, if value is greater than maximum.
        /// Otherwise, value.</returns>
        internal static double CoerceValue(this double value, double minimum, double maximum)
        {
            return Math.Max(Math.Min(value, maximum), minimum);
        }

        /// <summary>
        /// Forces the value to stay between mininum and maximum.
        /// </summary>
        /// <returns>minimum, if value is less than minimum.
        /// Maximum, if value is greater than maximum.
        /// Otherwise, value.</returns>
        internal static int CoerceValue(this int value, int minimum, int maximum)
        {
            return Math.Max(Math.Min(value, maximum), minimum);
        }
        #endregion

        #region AddRange / Sequence
        internal static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> elements)
        {
            foreach (T e in elements)
                collection.Add(e);
        }

        /// <summary>
        /// Creates an IEnumerable with a single value.
        /// </summary>
        internal static IEnumerable<T> Sequence<T>(T value)
        {
            yield return value;
        }
        #endregion

        internal static T PeekOrDefault<T>(this ImmutableStack<T> stack)
        {
            return stack.IsEmpty ? default(T) : stack.Peek();
        }

        /// <summary>
        /// Gets the union of two rectangles.
        /// </summary>
        /// <param name="other">The other rectangle.</param>
        /// <returns>The union.</returns>
        internal static Rect Union(this Rect rect, Rect other)
        {
            if (rect.width == 0 && rect.height == 0)
            {
                return other;
            }
            else if (other.width == 0 && other.height == 0)
            {
                return rect;
            }
            else
            {
                var x1 = Math.Min(rect.x, other.x);
                var x2 = Math.Max(rect.xMax, other.xMax);
                var y1 = Math.Min(rect.y, other.y);
                var y2 = Math.Max(rect.yMax, other.yMax);

                return new Rect(new Vector2(x1, y1), new Vector2(x2, y2));
            }
        }
    }
}