// 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;
using System.Threading;
using System.Threading.Tasks;
namespace System.Linq
{
    public static partial class AsyncEnumerable
    {
        /// 
        /// Bypasses elements in an async-enumerable sequence as long as a specified condition is true and then returns the remaining elements.
        /// 
        /// The type of the elements in the source sequence.
        /// An async-enumerable sequence to return elements from.
        /// A function to test each element for a condition.
        /// An async-enumerable sequence that contains the elements from the input sequence starting at the first element in the linear series that does not pass the test specified by predicate.
        ///  or  is null.
        public static IAsyncEnumerable SkipWhile(this IAsyncEnumerable source, Func predicate)
        {
            if (source == null)
                throw Error.ArgumentNull(nameof(source));
            if (predicate == null)
                throw Error.ArgumentNull(nameof(predicate));
            return Create(Core);
            async IAsyncEnumerator Core(CancellationToken cancellationToken)
            {
                await using var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false);
                while (await e.MoveNextAsync())
                {
                    var element = e.Current;
                    if (!predicate(element))
                    {
                        yield return element;
                        while (await e.MoveNextAsync())
                        {
                            yield return e.Current;
                        }
                        yield break;
                    }
                }
            }
        }
        /// 
        /// Bypasses elements in an async-enumerable sequence as long as a specified condition is true and then returns the remaining elements.
        /// The element's index is used in the logic of the predicate function.
        /// 
        /// The type of the elements in the source sequence.
        /// An async-enumerable sequence to return elements from.
        /// A function to test each element for a condition; the second parameter of the function represents the index of the source element.
        /// An async-enumerable sequence that contains the elements from the input sequence starting at the first element in the linear series that does not pass the test specified by predicate.
        ///  or  is null.
        public static IAsyncEnumerable SkipWhile(this IAsyncEnumerable source, Func predicate)
        {
            if (source == null)
                throw Error.ArgumentNull(nameof(source));
            if (predicate == null)
                throw Error.ArgumentNull(nameof(predicate));
            return Create(Core);
            async IAsyncEnumerator Core(CancellationToken cancellationToken)
            {
                await using var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false);
                var index = -1;
                while (await e.MoveNextAsync())
                {
                    checked
                    {
                        index++;
                    }
                    var element = e.Current;
                    if (!predicate(element, index))
                    {
                        yield return element;
                        while (await e.MoveNextAsync())
                        {
                            yield return e.Current;
                        }
                        yield break;
                    }
                }
            }
        }
        internal static IAsyncEnumerable SkipWhileAwaitCore(this IAsyncEnumerable source, Func> predicate)
        {
            if (source == null)
                throw Error.ArgumentNull(nameof(source));
            if (predicate == null)
                throw Error.ArgumentNull(nameof(predicate));
            return Create(Core);
            async IAsyncEnumerator Core(CancellationToken cancellationToken)
            {
                await using var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false);
                while (await e.MoveNextAsync())
                {
                    var element = e.Current;
                    if (!await predicate(element).ConfigureAwait(false))
                    {
                        yield return element;
                        while (await e.MoveNextAsync())
                        {
                            yield return e.Current;
                        }
                        yield break;
                    }
                }
            }
        }
#if !NO_DEEP_CANCELLATION
        internal static IAsyncEnumerable SkipWhileAwaitWithCancellationCore(this IAsyncEnumerable source, Func> predicate)
        {
            if (source == null)
                throw Error.ArgumentNull(nameof(source));
            if (predicate == null)
                throw Error.ArgumentNull(nameof(predicate));
            return Create(Core);
            async IAsyncEnumerator Core(CancellationToken cancellationToken)
            {
                await using var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false);
                while (await e.MoveNextAsync())
                {
                    var element = e.Current;
                    if (!await predicate(element, cancellationToken).ConfigureAwait(false))
                    {
                        yield return element;
                        while (await e.MoveNextAsync())
                        {
                            yield return e.Current;
                        }
                        yield break;
                    }
                }
            }
        }
#endif
        internal static IAsyncEnumerable SkipWhileAwaitCore(this IAsyncEnumerable source, Func> predicate)
        {
            if (source == null)
                throw Error.ArgumentNull(nameof(source));
            if (predicate == null)
                throw Error.ArgumentNull(nameof(predicate));
            return Create(Core);
            async IAsyncEnumerator Core(CancellationToken cancellationToken)
            {
                await using var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false);
                var index = -1;
                while (await e.MoveNextAsync())
                {
                    checked
                    {
                        index++;
                    }
                    var element = e.Current;
                    if (!await predicate(element, index).ConfigureAwait(false))
                    {
                        yield return element;
                        while (await e.MoveNextAsync())
                        {
                            yield return e.Current;
                        }
                        yield break;
                    }
                }
            }
        }
#if !NO_DEEP_CANCELLATION
        internal static IAsyncEnumerable SkipWhileAwaitWithCancellationCore(this IAsyncEnumerable source, Func> predicate)
        {
            if (source == null)
                throw Error.ArgumentNull(nameof(source));
            if (predicate == null)
                throw Error.ArgumentNull(nameof(predicate));
            return Create(Core);
            async IAsyncEnumerator Core(CancellationToken cancellationToken)
            {
                await using var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false);
                var index = -1;
                while (await e.MoveNextAsync())
                {
                    checked
                    {
                        index++;
                    }
                    var element = e.Current;
                    if (!await predicate(element, index, cancellationToken).ConfigureAwait(false))
                    {
                        yield return element;
                        while (await e.MoveNextAsync())
                        {
                            yield return e.Current;
                        }
                        yield break;
                    }
                }
            }
        }
#endif
    }
}