// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information. 
using System.Collections.Generic;
namespace System.Linq
{
    public static partial class EnumerableEx
    {
        /// 
        ///     Creates a sequence that corresponds to the source sequence, concatenating it with the sequence resulting from
        ///     calling an exception handler function in case of an error.
        /// 
        /// Source sequence element type.
        /// Exception type to catch.
        /// Source sequence.
        /// Handler to invoke when an exception of the specified type occurs.
        /// Source sequence, concatenated with an exception handler result sequence in case of an error.
        public static IEnumerable Catch(this IEnumerable source, Func> handler)
            where TException : Exception
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }
            if (handler == null)
            {
                throw new ArgumentNullException(nameof(handler));
            }
            return source.Catch_(handler);
        }
        /// 
        ///     Creates a sequence by concatenating source sequences until a source sequence completes successfully.
        /// 
        /// Source sequence element type.
        /// Source sequences.
        /// Sequence that continues to concatenate source sequences while errors occur.
        public static IEnumerable Catch(this IEnumerable> sources)
        {
            if (sources == null)
            {
                throw new ArgumentNullException(nameof(sources));
            }
            return sources.Catch_();
        }
        /// 
        ///     Creates a sequence by concatenating source sequences until a source sequence completes successfully.
        /// 
        /// Source sequence element type.
        /// Source sequences.
        /// Sequence that continues to concatenate source sequences while errors occur.
        public static IEnumerable Catch(params IEnumerable[] sources)
        {
            if (sources == null)
            {
                throw new ArgumentNullException(nameof(sources));
            }
            return sources.Catch_();
        }
        /// 
        ///     Creates a sequence that returns the elements of the first sequence, switching to the second in case of an error.
        /// 
        /// Source sequence element type.
        /// First sequence.
        /// Second sequence, concatenated to the result in case the first sequence completes exceptionally.
        /// The first sequence, followed by the second sequence in case an error is produced.
        public static IEnumerable Catch(this IEnumerable first, IEnumerable second)
        {
            if (first == null)
            {
                throw new ArgumentNullException(nameof(first));
            }
            if (second == null)
            {
                throw new ArgumentNullException(nameof(second));
            }
            return new[] { first, second }.Catch_();
        }
        private static IEnumerable Catch_(this IEnumerable source, Func> handler)
            where TException : Exception
        {
            var err = default(IEnumerable);
            using (var e = source.GetEnumerator())
            {
                while (true)
                {
                    var c = default(TSource);
                    try
                    {
                        if (!e.MoveNext())
                        {
                            break;
                        }
                        c = e.Current;
                    }
                    catch (TException ex)
                    {
                        err = handler(ex);
                        break;
                    }
                    yield return c;
                }
            }
            if (err != null)
            {
                foreach (var item in err)
                {
                    yield return item;
                }
            }
        }
        private static IEnumerable Catch_(this IEnumerable> sources)
        {
            var error = default(Exception);
            foreach (var source in sources)
            {
                using (var e = source.GetEnumerator())
                {
                    error = null;
                    while (true)
                    {
                        var c = default(TSource);
                        try
                        {
                            if (!e.MoveNext())
                            {
                                break;
                            }
                            c = e.Current;
                        }
                        catch (Exception ex)
                        {
                            error = ex;
                            break;
                        }
                        yield return c;
                    }
                    if (error == null)
                    {
                        break;
                    }
                }
            }
            if (error != null)
            {
                throw error;
            }
        }
    }
}