namespace DesktopSprites.Collections
{
    using System;
    using System.Collections.Generic;
    using DesktopSprites.Core;

    /// <summary>
    /// Creates a wrapper around a <see cref="T:System.Func`1"/> that generates instances of
    /// <see cref="T:System.Collections.Generic.IEnumerator`1"/> to expose it through the
    /// <see cref="T:System.Collections.Generic.IEnumerable`1"/> interface.
    /// </summary>
    public static class Enumerable
    {
        /// <summary>
        /// Creates an <see cref="T:System.Collections.Generic.IEnumerable`1"/> by returning the
        /// <see cref="T:System.Collections.Generic.IEnumerator`1"/> generated by the specified function.
        /// </summary>
        /// <typeparam name="T">The type of elements to enumerate.</typeparam>
        /// <param name="enumeratorFactory">A <see cref="T:System.Func`1"/> that generates a new enumerator that enumerates the elements of
        /// a generic collection.</param>
        /// <returns>An <see cref="T:System.Collections.Generic.IEnumerable`1"/> which returns an enumerator generated by the specified
        /// function when its <see cref="M:System.Collections.Generic.IEnumerable`1.GetEnumerator()"/> method is called.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="enumeratorFactory"/> is null.</exception>
        public static IEnumerable<T> Create<T>(Func<IEnumerator<T>> enumeratorFactory)
        {
            return new EnumerableFromFactory<T>(enumeratorFactory);
        }

        /// <summary>
        /// Exposes an enumerator for elements of a generic collection.
        /// </summary>
        /// <typeparam name="T">The type of elements to enumerate.</typeparam>
        private struct EnumerableFromFactory<T> : IEnumerable<T>
        {
            /// <summary>
            /// The enumerator generation function.
            /// </summary>
            private Func<IEnumerator<T>> enumeratorFactory;
            /// <summary>
            /// Initializes a new instance of the <see cref="T:DesktopSprites.Collections.Enumerable.EnumerableFromFactory`1"/> struct from
            /// the specified enumerator generation function.
            /// </summary>
            /// <param name="enumeratorFactory">A <see cref="T:System.Func`1"/> that generates a new enumerator that enumerates the
            /// elements of a generic collection.</param>
            /// <exception cref="T:System.ArgumentNullException"><paramref name="enumeratorFactory"/> is null.</exception>
            public EnumerableFromFactory(Func<IEnumerator<T>> enumeratorFactory)
            {
                this.enumeratorFactory = Argument.EnsureNotNull(enumeratorFactory, "enumeratorFactory");
            }
            /// <summary>
            /// Returns an enumerator that iterates through the generic collection.
            /// </summary>
            /// <returns>An <see cref="T:System.Collections.Generic.IEnumerator`1"/> for the generic collection.</returns>
            public IEnumerator<T> GetEnumerator()
            {
                return enumeratorFactory();
            }
            /// <summary>
            /// Returns an enumerator that iterates through the generic collection.
            /// </summary>
            /// <returns>An <see cref="T:System.Collections.Generic.IEnumerator`1"/> for the generic collection.</returns>
            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
            /// <summary>
            /// Tests whether two specified <see cref="T:DesktopSprites.Collections.Enumerable.EnumeratorWrapper`1"/> structures are
            /// equivalent.
            /// </summary>
            /// <param name="left">The <see cref="T:DesktopSprites.Collections.Enumerable.EnumeratorWrapper`1"/> that is to the left of
            /// the equality operator.
            /// </param>
            /// <param name="right">The <see cref="T:DesktopSprites.Collections.Enumerable.EnumeratorWrapper`1"/> that is to the right of
            /// the equality operator.
            /// </param>
            /// <returns>Returns true if the two <see cref="T:DesktopSprites.Collections.Enumerable.EnumeratorWrapper`1"/> structures are
            /// equal; otherwise, false.</returns>
            public static bool operator ==(EnumerableFromFactory<T> left, EnumerableFromFactory<T> right)
            {
                return left.enumeratorFactory == right.enumeratorFactory;
            }
            /// <summary>
            /// Tests whether two specified <see cref="T:DesktopSprites.Collections.Enumerable.EnumeratorWrapper`1"/> structures are
            /// different.
            /// </summary>
            /// <param name="left">The <see cref="T:DesktopSprites.Collections.Enumerable.EnumeratorWrapper`1"/> that is to the left of
            /// the inequality operator.
            /// </param>
            /// <param name="right">The <see cref="T:DesktopSprites.Collections.Enumerable.EnumeratorWrapper`1"/> that is to the right of
            /// the inequality operator.</param>
            /// <returns>Returns true if the two <see cref="T:DesktopSprites.Collections.Enumerable.EnumeratorWrapper`1"/> structures are
            /// different; otherwise, false.</returns>
            public static bool operator !=(EnumerableFromFactory<T> left, EnumerableFromFactory<T> right)
            {
                return !(left == right);
            }
            /// <summary>
            /// Tests whether the specified object is a <see cref="T:DesktopSprites.Collections.Enumerable.EnumeratorWrapper`1"/>
            /// structure and is equivalent to this <see cref="T:DesktopSprites.Collections.Enumerable`1"/> structure.
            /// </summary>
            /// <param name="obj">The object to test.</param>
            /// <returns>Returns true if <paramref name="obj"/> is a
            /// <see cref="T:DesktopSprites.Collections.Enumerable.EnumeratorWrapper`1"/> structure equivalent to this
            /// <see cref="T:DesktopSprites.Collections.Enumerable.EnumeratorWrapper`1"/> structure; otherwise, false.
            /// </returns>
            public override bool Equals(object obj)
            {
                if (obj == null || !(obj is EnumerableFromFactory<T>))
                    return false;

                return this == (EnumerableFromFactory<T>)obj;
            }
            /// <summary>
            /// Returns a hash code for this <see cref="T:DesktopSprites.Collections.Enumerable.EnumeratorWrapper`1"/> structure.
            /// </summary>
            /// <returns>An integer value that specifies the hash code for this
            /// <see cref="T:DesktopSprites.Collections.Enumerable.EnumeratorWrapper`1"/>.
            /// </returns>
            public override int GetHashCode()
            {
                return int.MaxValue ^ enumeratorFactory.GetHashCode();
            }
        }
    }
}
