// 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;
using System.Threading;
using System.Threading.Tasks;
namespace System.Linq
{
    public static partial class AsyncEnumerable
    {
        /// 
        /// Returns the last element of an async-enumerable sequence, or a default value if no such element exists.
        /// 
        /// The type of the elements in the source sequence.
        /// Source async-enumerable sequence.
        /// The optional cancellation token to be used for cancelling the sequence at any time.
        /// ValueTask containing the last element in the async-enumerable sequence, or a default value if no such element exists.
        ///  is null.
        public static ValueTask LastOrDefaultAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default)
        {
            if (source == null)
                throw Error.ArgumentNull(nameof(source));
            return Core(source, cancellationToken);
            static async ValueTask Core(IAsyncEnumerable source, CancellationToken cancellationToken)
            {
                var last = await TryGetLast(source, cancellationToken).ConfigureAwait(false);
                return last.HasValue ? last.Value : default;
            }
        }
        /// 
        /// Returns the last element of an async-enumerable sequence that satisfies the condition in the predicate, or a default value if no such element exists.
        /// 
        /// The type of the elements in the source sequence.
        /// Source async-enumerable sequence.
        /// A predicate function to evaluate for elements in the source sequence.
        /// The optional cancellation token to be used for cancelling the sequence at any time.
        /// ValueTask containing the last element in the async-enumerable sequence that satisfies the condition in the predicate, or a default value if no such element exists.
        ///  or  is null.
        public static ValueTask LastOrDefaultAsync(this IAsyncEnumerable source, Func predicate, CancellationToken cancellationToken = default)
        {
            if (source == null)
                throw Error.ArgumentNull(nameof(source));
            if (predicate == null)
                throw Error.ArgumentNull(nameof(predicate));
            return Core(source, predicate, cancellationToken);
            static async ValueTask Core(IAsyncEnumerable source, Func predicate, CancellationToken cancellationToken)
            {
                var last = await TryGetLast(source, predicate, cancellationToken).ConfigureAwait(false);
                return last.HasValue ? last.Value : default;
            }
        }
        internal static ValueTask LastOrDefaultAwaitAsyncCore(this IAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken = default)
        {
            if (source == null)
                throw Error.ArgumentNull(nameof(source));
            if (predicate == null)
                throw Error.ArgumentNull(nameof(predicate));
            return Core(source, predicate, cancellationToken);
            static async ValueTask Core(IAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken)
            {
                var last = await TryGetLast(source, predicate, cancellationToken).ConfigureAwait(false);
                return last.HasValue ? last.Value : default;
            }
        }
#if !NO_DEEP_CANCELLATION
        internal static ValueTask LastOrDefaultAwaitWithCancellationAsyncCore(this IAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken = default)
        {
            if (source == null)
                throw Error.ArgumentNull(nameof(source));
            if (predicate == null)
                throw Error.ArgumentNull(nameof(predicate));
            return Core(source, predicate, cancellationToken);
            static async ValueTask Core(IAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken)
            {
                var last = await TryGetLast(source, predicate, cancellationToken).ConfigureAwait(false);
                return last.HasValue ? last.Value : default;
            }
        }
#endif
        private static ValueTask> TryGetLast(IAsyncEnumerable source, CancellationToken cancellationToken)
        {
            if (source is IList list)
            {
                var count = list.Count;
                if (count > 0)
                {
                    return new ValueTask>(new Maybe(list[count - 1]));
                }
            }
            else if (source is IAsyncPartition p)
            {
                return p.TryGetLastAsync(cancellationToken);
            }
            else
            {
                return Core(source, cancellationToken);
                static async ValueTask> Core(IAsyncEnumerable source, CancellationToken cancellationToken)
                {
                    var last = default(TSource)!; // NB: Only matters when hasLast is set to true.
                    var hasLast = false;
                    await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
                    {
                        hasLast = true;
                        last = item;
                    }
                    return hasLast ? new Maybe(last!) : new Maybe();
                }
            }
            return new ValueTask>(new Maybe());
        }
        private static async ValueTask> TryGetLast(IAsyncEnumerable source, Func predicate, CancellationToken cancellationToken)
        {
            var last = default(TSource)!; // NB: Only matters when hasLast is set to true.
            var hasLast = false;
            await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
            {
                if (predicate(item))
                {
                    hasLast = true;
                    last = item;
                }
            }
            return hasLast ? new Maybe(last!) : new Maybe();
        }
        private static async ValueTask> TryGetLast(IAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken)
        {
            var last = default(TSource)!; // NB: Only matters when hasLast is set to true.
            var hasLast = false;
            await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
            {
                if (await predicate(item).ConfigureAwait(false))
                {
                    hasLast = true;
                    last = item;
                }
            }
            return hasLast ? new Maybe(last!) : new Maybe();
        }
#if !NO_DEEP_CANCELLATION
        private static async ValueTask> TryGetLast(IAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken)
        {
            var last = default(TSource)!; // NB: Only matters when hasLast is set to true.
            var hasLast = false;
            await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
            {
                if (await predicate(item, cancellationToken).ConfigureAwait(false))
                {
                    hasLast = true;
                    last = item;
                }
            }
            return hasLast ? new Maybe(last!) : new Maybe();
        }
#endif
    }
}