// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT 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 CatchCore(source, 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 CatchCore(sources);
        }
        /// 
        /// 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 CatchCore(sources);
        }
        /// 
        /// 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 CatchCore(new[] { first, second });
        }
        private static IEnumerable CatchCore(IEnumerable source, Func> handler)
            where TException : Exception
        {
            var err = default(IEnumerable);
            using (var e = source.GetEnumerator())
            {
                while (true)
                {
                    TSource c;
                    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 CatchCore(IEnumerable> sources)
        {
            var error = default(Exception);
            foreach (var source in sources)
            {
                using var e = source.GetEnumerator();
                error = null;
                while (true)
                {
                    TSource c;
                    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;
        }
    }
}