using System;

namespace MemoryPools.Collections.Linq
{
    public static partial class PoolingEnumerable
    {
        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static double Average(this IPoolingEnumerable<int> source)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            using (var enumerator = source.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                {
                    // throw new InvalidOperationException("Sequence contains no elements");
                }

                long sum = enumerator.Current;
                long count = 1;
                checked
                {
                    while (enumerator.MoveNext())
                    {
                        sum += enumerator.Current;
                        ++count;
                    }
                }

                return (double)sum / count;
            }
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static double? Average(this IPoolingEnumerable<int?> source)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            using (var e = source.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    int? v = e.Current;
                    if (v.HasValue)
                    {
                        long sum = v.GetValueOrDefault();
                        long count = 1;
                        checked
                        {
                            while (e.MoveNext())
                            {
                                v = e.Current;
                                if (v.HasValue)
                                {
                                    sum += v.GetValueOrDefault();
                                    ++count;
                                }
                            }
                        }

                        return (double)sum / count;
                    }
                }
            }

            return null!;
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static double Average(this IPoolingEnumerable<long> source)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            using (var e = source.GetEnumerator())
            {
                if (!e.MoveNext())
                {
                    throw new InvalidOperationException("Sequence contains no elements");
                }

                long sum = e.Current;
                long count = 1;
                checked
                {
                    while (e.MoveNext())
                    {
                        sum += e.Current;
                        ++count;
                    }
                }

                return (double)sum / count;
            }
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static double? Average(this IPoolingEnumerable<long?> source)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            using (IPoolingEnumerator<long?> e = source.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    long? v = e.Current;
                    if (v.HasValue)
                    {
                        long sum = v.GetValueOrDefault();
                        long count = 1;
                        checked
                        {
                            while (e.MoveNext())
                            {
                                v = e.Current;
                                if (v.HasValue)
                                {
                                    sum += v.GetValueOrDefault();
                                    ++count;
                                }
                            }
                        }

                        return (double)sum / count;
                    }
                }
            }

            return null!;
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static float Average(this IPoolingEnumerable<float> source)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            using (IPoolingEnumerator<float> e = source.GetEnumerator())
            {
                if (!e.MoveNext())
                {
                    throw new InvalidOperationException("Sequence contains no elements");
                }

                double sum = e.Current;
                long count = 1;
                while (e.MoveNext())
                {
                    sum += e.Current;
                    ++count;
                }

                return (float)(sum / count);
            }
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static float? Average(this IPoolingEnumerable<float?> source)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            using (IPoolingEnumerator<float?> e = source.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    float? v = e.Current;
                    if (v.HasValue)
                    {
                        double sum = v.GetValueOrDefault();
                        long count = 1;
                        checked
                        {
                            while (e.MoveNext())
                            {
                                v = e.Current;
                                if (v.HasValue)
                                {
                                    sum += v.GetValueOrDefault();
                                    ++count;
                                }
                            }
                        }

                        return (float)(sum / count);
                    }
                }
            }

            return null!;
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static double Average(this IPoolingEnumerable<double> source)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            using (IPoolingEnumerator<double> e = source.GetEnumerator())
            {
                if (!e.MoveNext())
                {
                    throw new InvalidOperationException("Sequence contains no elements");
                }

                double sum = e.Current;
                long count = 1;
                while (e.MoveNext())
                {
                    // There is an opportunity to short-circuit here, in that if e.Current is
                    // ever NaN then the result will always be NaN. Assuming that this case is
                    // rare enough that not checking is the better approach generally.
                    sum += e.Current;
                    ++count;
                }

                return sum / count;
            }
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static double? Average(this IPoolingEnumerable<double?> source)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            using (IPoolingEnumerator<double?> e = source.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    double? v = e.Current;
                    if (v.HasValue)
                    {
                        double sum = v.GetValueOrDefault();
                        long count = 1;
                        checked
                        {
                            while (e.MoveNext())
                            {
                                v = e.Current;
                                if (v.HasValue)
                                {
                                    sum += v.GetValueOrDefault();
                                    ++count;
                                }
                            }
                        }

                        return sum / count;
                    }
                }
            }

            return null!;
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static decimal Average(this IPoolingEnumerable<decimal> source)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            using (IPoolingEnumerator<decimal> e = source.GetEnumerator())
            {
                if (!e.MoveNext())
                {
                    throw new InvalidOperationException("Sequence contains no elements");
                }

                decimal sum = e.Current;
                long count = 1;
                while (e.MoveNext())
                {
                    sum += e.Current;
                    ++count;
                }

                return sum / count;
            }
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static decimal? Average(this IPoolingEnumerable<decimal?> source)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            using (IPoolingEnumerator<decimal?> e = source.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    decimal? v = e.Current;
                    if (v.HasValue)
                    {
                        decimal sum = v.GetValueOrDefault();
                        long count = 1;
                        while (e.MoveNext())
                        {
                            v = e.Current;
                            if (v.HasValue)
                            {
                                sum += v.GetValueOrDefault();
                                ++count;
                            }
                        }

                        return sum / count;
                    }
                }
            }

            return null!;
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static double Average<TSource>(this IPoolingEnumerable<TSource> source, Func<TSource, int> selector)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (selector == null!)
            {
                throw new ArgumentNullException(nameof(selector));
            }

            using (var e = source.GetEnumerator())
            {
                if (!e.MoveNext())
                {
                    throw new InvalidOperationException("Sequence contains no elements");
                }

                long sum = selector(e.Current);
                long count = 1;
                checked
                {
                    while (e.MoveNext())
                    {
                        sum += selector(e.Current);
                        ++count;
                    }
                }

                return (double)sum / count;
            }
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static double? Average<TSource>(this IPoolingEnumerable<TSource> source, Func<TSource, int?> selector)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (selector == null!)
            {
                throw new ArgumentNullException(nameof(selector));
            }

            using (var e = source.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    int? v = selector(e.Current);
                    if (v.HasValue)
                    {
                        long sum = v.GetValueOrDefault();
                        long count = 1;
                        checked
                        {
                            while (e.MoveNext())
                            {
                                v = selector(e.Current);
                                if (v.HasValue)
                                {
                                    sum += v.GetValueOrDefault();
                                    ++count;
                                }
                            }
                        }

                        return (double)sum / count;
                    }
                }
            }

            return null!;
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static double Average<TSource>(this IPoolingEnumerable<TSource> source, Func<TSource, long> selector)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (selector == null!)
            {
                throw new ArgumentNullException(nameof(selector));
            }

            using (var e = source.GetEnumerator())
            {
                if (!e.MoveNext())
                {
                    throw new InvalidOperationException("Sequence contains no elements");
                }

                long sum = selector(e.Current);
                long count = 1;
                checked
                {
                    while (e.MoveNext())
                    {
                        sum += selector(e.Current);
                        ++count;
                    }
                }

                return (double)sum / count;
            }
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static double? Average<TSource>(this IPoolingEnumerable<TSource> source, Func<TSource, long?> selector)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (selector == null!)
            {
                throw new ArgumentNullException(nameof(selector));
            }

            using (var e = source.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    long? v = selector(e.Current);
                    if (v.HasValue)
                    {
                        long sum = v.GetValueOrDefault();
                        long count = 1;
                        checked
                        {
                            while (e.MoveNext())
                            {
                                v = selector(e.Current);
                                if (v.HasValue)
                                {
                                    sum += v.GetValueOrDefault();
                                    ++count;
                                }
                            }
                        }

                        return (double)sum / count;
                    }
                }
            }

            return null!;
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static float Average<TSource>(this IPoolingEnumerable<TSource> source, Func<TSource, float> selector)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (selector == null!)
            {
                throw new ArgumentNullException(nameof(selector));
            }

            using (IPoolingEnumerator<TSource> e = source.GetEnumerator())
            {
                if (!e.MoveNext())
                {
                    throw new InvalidOperationException("Sequence contains no elements");
                }

                double sum = selector(e.Current);
                long count = 1;
                while (e.MoveNext())
                {
                    sum += selector(e.Current);
                    ++count;
                }

                return (float)(sum / count);
            }
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static float? Average<TSource>(this IPoolingEnumerable<TSource> source, Func<TSource, float?> selector)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (selector == null!)
            {
                throw new ArgumentNullException(nameof(selector));
            }

            using (IPoolingEnumerator<TSource> e = source.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    float? v = selector(e.Current);
                    if (v.HasValue)
                    {
                        double sum = v.GetValueOrDefault();
                        long count = 1;
                        checked
                        {
                            while (e.MoveNext())
                            {
                                v = selector(e.Current);
                                if (v.HasValue)
                                {
                                    sum += v.GetValueOrDefault();
                                    ++count;
                                }
                            }
                        }

                        return (float)(sum / count);
                    }
                }
            }

            return null!;
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static double Average<TSource>(this IPoolingEnumerable<TSource> source, Func<TSource, double> selector)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (selector == null!)
            {
                throw new ArgumentNullException(nameof(selector));
            }

            using (IPoolingEnumerator<TSource> e = source.GetEnumerator())
            {
                if (!e.MoveNext())
                {
                    throw new InvalidOperationException("Sequence contains no elements");
                }

                double sum = selector(e.Current);
                long count = 1;
                while (e.MoveNext())
                {
                    // There is an opportunity to short-circuit here, in that if e.Current is
                    // ever NaN then the result will always be NaN. Assuming that this case is
                    // rare enough that not checking is the better approach generally.
                    sum += selector(e.Current);
                    ++count;
                }

                return sum / count;
            }
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static double? Average<TSource>(this IPoolingEnumerable<TSource> source, Func<TSource, double?> selector)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (selector == null!)
            {
                throw new ArgumentNullException(nameof(selector));
            }

            using (IPoolingEnumerator<TSource> e = source.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    double? v = selector(e.Current);
                    if (v.HasValue)
                    {
                        double sum = v.GetValueOrDefault();
                        long count = 1;
                        checked
                        {
                            while (e.MoveNext())
                            {
                                v = selector(e.Current);
                                if (v.HasValue)
                                {
                                    sum += v.GetValueOrDefault();
                                    ++count;
                                }
                            }
                        }

                        return sum / count;
                    }
                }
            }

            return null!;
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static decimal Average<TSource>(this IPoolingEnumerable<TSource> source, Func<TSource, decimal> selector)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (selector == null!)
            {
                throw new ArgumentNullException(nameof(selector));
            }

            using (IPoolingEnumerator<TSource> e = source.GetEnumerator())
            {
                if (!e.MoveNext())
                {
                    throw new InvalidOperationException("Sequence contains no elements");
                }

                decimal sum = selector(e.Current);
                long count = 1;
                while (e.MoveNext())
                {
                    sum += selector(e.Current);
                    ++count;
                }

                return sum / count;
            }
        }

        /// <summary>
        /// Calculates avg of all given numbers. Complexity = O(N) 
        /// </summary>
        public static decimal? Average<TSource>(this IPoolingEnumerable<TSource> source, Func<TSource, decimal?> selector)
        {
            if (source == null!)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (selector == null!)
            {
                throw new ArgumentNullException(nameof(selector));
            }

            using (IPoolingEnumerator<TSource> e = source.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    decimal? v = selector(e.Current);
                    if (v.HasValue)
                    {
                        decimal sum = v.GetValueOrDefault();
                        long count = 1;
                        while (e.MoveNext())
                        {
                            v = selector(e.Current);
                            if (v.HasValue)
                            {
                                sum += v.GetValueOrDefault();
                                ++count;
                            }
                        }

                        return sum / count;
                    }
                }
            }

            return null!;
        }
    }
}