// 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. 

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<#
int ShuffleCount = 4;

var types = new[] { typeof(int), typeof(long), typeof(float), typeof(double), typeof(decimal) };

Func<Type, string> toCSharp = t =>
{
    if (t == typeof(int))
        return "int";
    else if (t == typeof(long))
        return "long";
    else if (t == typeof(float))
        return "float";
    else if (t == typeof(double))
        return "double";
    else if (t == typeof(decimal))
        return "decimal";

    throw new Exception();
};

Func<int, int, int, int, int[]> getRandom = (seed, N, min, max) =>
{
    var rand = new Random(seed);
    return Enumerable.Range(0, N).Select(i => rand.Next(min, max)).ToArray();
};
#>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace Tests
{
    public class MinMax : AsyncEnumerableTests
    {
<#
foreach (var op in new[] { "Min", "Max" })
{
    foreach (var t in types)
    {
        var cs = toCSharp(t);
#>
        [Fact]
        public async Task <#=op#>_Empty_<#=t.Name#>()
        {
            await AssertThrowsAsync<InvalidOperationException>(new <#=cs#>[0].ToAsyncEnumerable().<#=op#>Async(CancellationToken.None));
        }

        [Fact]
        public async Task <#=op#>_Selector_Empty_<#=t.Name#>()
        {
            await AssertThrowsAsync<InvalidOperationException>(new object[0].ToAsyncEnumerable().<#=op#>Async(_ => default(<#=cs#>), CancellationToken.None));
        }

        [Fact]
        public async Task <#=op#>_AsyncSelector_Empty_<#=t.Name#>()
        {
            await AssertThrowsAsync<InvalidOperationException>(new object[0].ToAsyncEnumerable().<#=op#>Async(_ => default(ValueTask<<#=cs#>>), CancellationToken.None));
        }

#if !NO_DEEP_CANCELLATION
        [Fact]
        public async Task <#=op#>_AsyncSelectorWithCancellation_Empty_<#=t.Name#>()
        {
            await AssertThrowsAsync<InvalidOperationException>(new object[0].ToAsyncEnumerable().<#=op#>Async((x, ct) => default(ValueTask<<#=cs#>>), CancellationToken.None));
        }
#endif

        [Fact]
        public async Task <#=op#>_Empty_Nullable_<#=t.Name#>()
        {
            Assert.Null(await new <#=cs#>?[0].ToAsyncEnumerable().<#=op#>Async(CancellationToken.None));
        }

        [Fact]
        public async Task <#=op#>_Selector_Empty_Nullable_<#=t.Name#>()
        {
            Assert.Null(await new object[0].ToAsyncEnumerable().<#=op#>Async(_ => default(<#=cs#>?), CancellationToken.None));
        }

        [Fact]
        public async Task <#=op#>_AsyncSelector_Empty_Nullable_<#=t.Name#>()
        {
            Assert.Null(await new object[0].ToAsyncEnumerable().<#=op#>Async(_ => default(ValueTask<<#=cs#>?>), CancellationToken.None));
        }

#if !NO_DEEP_CANCELLATION
        [Fact]
        public async Task <#=op#>_AsyncSelectorWithCancellation_Empty_Nullable_<#=t.Name#>()
        {
            Assert.Null(await new object[0].ToAsyncEnumerable().<#=op#>Async((x, ct) => default(ValueTask<<#=cs#>?>), CancellationToken.None));
        }
#endif

<#
        foreach (var n in new[] { 1, 4, 8, 16 })
        {
            var values = string.Join(", ", getRandom(n * 42, n, -100, 100));
            var valuesWithNull = string.Join(", ", getRandom(n * 42, n, -100, 100).Select((x, i) => i % 2 == 0 ? x.ToString() : "null"));
#>
        [Fact]
        public async Task <#=op#>_<#=t.Name#>_<#=n#>()
        {
            var input = new <#=cs#>[] { <#=values#> }.AsEnumerable();

            for (var i = 0; i < <#=ShuffleCount#>; i++)
            {
                var expected = input.<#=op#>();
                var actual = await input.ToAsyncEnumerable().<#=op#>Async(CancellationToken.None);
                Assert.Equal(expected, actual);

                input = Shuffle(input);
            }
        }

        [Fact]
        public async Task <#=op#>_Selector_<#=t.Name#>_<#=n#>()
        {
            var input = new <#=cs#>[] { <#=values#> }.AsEnumerable();

            for (var i = 0; i < <#=ShuffleCount#>; i++)
            {
                var expected = input.<#=op#>();
                var actual = await input.ToAsyncEnumerable().<#=op#>Async(x => x, CancellationToken.None);
                Assert.Equal(expected, actual);

                input = Shuffle(input);
            }
        }

        [Fact]
        public async Task <#=op#>_AsyncSelector_<#=t.Name#>_<#=n#>()
        {
            var input = new <#=cs#>[] { <#=values#> }.AsEnumerable();

            for (var i = 0; i < <#=ShuffleCount#>; i++)
            {
                var expected = input.<#=op#>();
                var actual = await input.ToAsyncEnumerable().<#=op#>Async(x => new ValueTask<<#=cs#>>(x), CancellationToken.None);
                Assert.Equal(expected, actual);

                input = Shuffle(input);
            }
        }

#if !NO_DEEP_CANCELLATION
        [Fact]
        public async Task <#=op#>_AsyncSelectorWithCancellation_<#=t.Name#>_<#=n#>()
        {
            var input = new <#=cs#>[] { <#=values#> }.AsEnumerable();

            for (var i = 0; i < <#=ShuffleCount#>; i++)
            {
                var expected = input.<#=op#>();
                var actual = await input.ToAsyncEnumerable().<#=op#>Async((x, ct) => new ValueTask<<#=cs#>>(x), CancellationToken.None);
                Assert.Equal(expected, actual);

                input = Shuffle(input);
            }
        }
#endif

        [Fact]
        public async Task <#=op#>_<#=t.Name#>_Nullable_<#=n#>_NoNull()
        {
            var input = new <#=cs#>?[] { <#=values#> }.AsEnumerable();

            for (var i = 0; i < <#=ShuffleCount#>; i++)
            {
                var expected = input.<#=op#>();
                var actual = await input.ToAsyncEnumerable().<#=op#>Async(CancellationToken.None);
                Assert.Equal(expected, actual);

                input = Shuffle(input);
            }
        }

        [Fact]
        public async Task <#=op#>_Selector_<#=t.Name#>_Nullable_<#=n#>_NoNull()
        {
            var input = new <#=cs#>?[] { <#=values#> }.AsEnumerable();

            for (var i = 0; i < <#=ShuffleCount#>; i++)
            {
                var expected = input.<#=op#>();
                var actual = await input.ToAsyncEnumerable().<#=op#>Async(x => x, CancellationToken.None);
                Assert.Equal(expected, actual);

                input = Shuffle(input);
            }
        }

        [Fact]
        public async Task <#=op#>_AsyncSelector_<#=t.Name#>_Nullable_<#=n#>_NoNull()
        {
            var input = new <#=cs#>?[] { <#=values#> }.AsEnumerable();

            for (var i = 0; i < <#=ShuffleCount#>; i++)
            {
                var expected = input.<#=op#>();
                var actual = await input.ToAsyncEnumerable().<#=op#>Async(x => new ValueTask<<#=cs#>?>(x), CancellationToken.None);
                Assert.Equal(expected, actual);

                input = Shuffle(input);
            }
        }

#if !NO_DEEP_CANCELLATION
        [Fact]
        public async Task <#=op#>_AsyncSelectorWithCancellation_<#=t.Name#>_Nullable_<#=n#>_NoNull()
        {
            var input = new <#=cs#>?[] { <#=values#> }.AsEnumerable();

            for (var i = 0; i < <#=ShuffleCount#>; i++)
            {
                var expected = input.<#=op#>();
                var actual = await input.ToAsyncEnumerable().<#=op#>Async((x, ct) => new ValueTask<<#=cs#>?>(x), CancellationToken.None);
                Assert.Equal(expected, actual);

                input = Shuffle(input);
            }
        }
#endif

        [Fact]
        public async Task <#=op#>_<#=t.Name#>_Nullable_<#=n#>_AllNull()
        {
            var input = Enumerable.Repeat(default(<#=cs#>?), <#=n#>);

            var expected = input.<#=op#>();
            var actual = await input.ToAsyncEnumerable().<#=op#>Async(CancellationToken.None);
            Assert.Equal(expected, actual);
        }

        [Fact]
        public async Task <#=op#>_Selector_<#=t.Name#>_Nullable_<#=n#>_AllNull()
        {
            var input = Enumerable.Repeat(default(<#=cs#>?), <#=n#>);

            var expected = input.<#=op#>();
            var actual = await input.ToAsyncEnumerable().<#=op#>Async(x => x, CancellationToken.None);
            Assert.Equal(expected, actual);
        }

        [Fact]
        public async Task <#=op#>_AsyncSelector_<#=t.Name#>_Nullable_<#=n#>_AllNull()
        {
            var input = Enumerable.Repeat(default(<#=cs#>?), <#=n#>);

            var expected = input.<#=op#>();
            var actual = await input.ToAsyncEnumerable().<#=op#>Async(x => new ValueTask<<#=cs#>?>(x), CancellationToken.None);
            Assert.Equal(expected, actual);
        }

#if !NO_DEEP_CANCELLATION
        [Fact]
        public async Task <#=op#>_AsyncSelectorWithCancellation_<#=t.Name#>_Nullable_<#=n#>_AllNull()
        {
            var input = Enumerable.Repeat(default(<#=cs#>?), <#=n#>);

            var expected = input.<#=op#>();
            var actual = await input.ToAsyncEnumerable().<#=op#>Async((x, ct) => new ValueTask<<#=cs#>?>(x), CancellationToken.None);
            Assert.Equal(expected, actual);
        }
#endif

        [Fact]
        public async Task <#=op#>_<#=t.Name#>_Nullable_<#=n#>_SomeNull()
        {
            var input = new <#=cs#>?[] { <#=valuesWithNull#> }.AsEnumerable();

            for (var i = 0; i < <#=ShuffleCount#>; i++)
            {
                var expected = input.<#=op#>();
                var actual = await input.ToAsyncEnumerable().<#=op#>Async(CancellationToken.None);
                Assert.Equal(expected, actual);

                input = Shuffle(input);
            }
        }

        [Fact]
        public async Task <#=op#>_Selector_<#=t.Name#>_Nullable_<#=n#>_SomeNull()
        {
            var input = new <#=cs#>?[] { <#=valuesWithNull#> }.AsEnumerable();

            for (var i = 0; i < <#=ShuffleCount#>; i++)
            {
                var expected = input.<#=op#>();
                var actual = await input.ToAsyncEnumerable().<#=op#>Async(x => x, CancellationToken.None);
                Assert.Equal(expected, actual);

                input = Shuffle(input);
            }
        }

        [Fact]
        public async Task <#=op#>_AsyncSelector_<#=t.Name#>_Nullable_<#=n#>_SomeNull()
        {
            var input = new <#=cs#>?[] { <#=valuesWithNull#> }.AsEnumerable();

            for (var i = 0; i < <#=ShuffleCount#>; i++)
            {
                var expected = input.<#=op#>();
                var actual = await input.ToAsyncEnumerable().<#=op#>Async(x => new ValueTask<<#=cs#>?>(x), CancellationToken.None);
                Assert.Equal(expected, actual);

                input = Shuffle(input);
            }
        }

#if !NO_DEEP_CANCELLATION
        [Fact]
        public async Task <#=op#>_AsyncSelectorWithCancellation_<#=t.Name#>_Nullable_<#=n#>_SomeNull()
        {
            var input = new <#=cs#>?[] { <#=valuesWithNull#> }.AsEnumerable();

            for (var i = 0; i < <#=ShuffleCount#>; i++)
            {
                var expected = input.<#=op#>();
                var actual = await input.ToAsyncEnumerable().<#=op#>Async((x, ct) => new ValueTask<<#=cs#>?>(x), CancellationToken.None);
                Assert.Equal(expected, actual);

                input = Shuffle(input);
            }
        }
#endif

<#
        }

        foreach (var n in new[] { 1, 4, 8, 16 })
        {
            var values = string.Join(", ", getRandom(n * 42, n, -100, 100));
#>
<#
        }
    }
}
#>

        private static IEnumerable<T> Shuffle<T>(IEnumerable<T> source)
        {
            var rand = new Random(42);
            return source.OrderBy(x => rand.Next());
        }
    }
}
