using System.Numerics;
using System.Runtime.Intrinsics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace MinMax;

class Program
{
    static void Main(string[] args)
    {
        BenchmarkRunner.Run<Demo>();
    }
}

public class Demo
{
    int[] _data;

    [GlobalSetup]
    public void GlobalSetup()
    {
        _data = new int[32 * 1024];
        for (var i = 0; i < _data.Length; i++)
        {
            _data[i] = Random.Shared.Next();
        }
    }

    [Benchmark(Baseline = true)]
    public (int, int) MinMax()
    {
        int min = 0, max = 0;
        for (var i = 0; i < _data.Length; ++i)
        {
            min = Math.Min(min, _data[i]);
            max = Math.Max(max, _data[i]);
        }
        return (min, max);
    }

    [Benchmark]
    public (int, int) MinMaxILP()
    {
        int min1 = 0, min2 = 0, max1 = 0, max2 = 0;
        for (int i = 0; i < _data.Length; i += 2)
        {
            min1 = Math.Min(min1, _data[i]); min2 = Math.Min(min2, _data[i + 1]);
            max1 = Math.Max(max1, _data[i]); max2 = Math.Max(max2, _data[i + 1]);
        }
        return (Math.Min(min1, min2), Math.Max(max1, max2));
    }

    [Benchmark]
    public (int, int) MinMaxParallel()
    {
        return (_data.AsParallel().Min(), _data.AsParallel().Max());
    }

    [Benchmark]
    public (int, int) MinMaxVector256()
    {
        //Vector256.IsHardwareAccelerated
        var vmin = Vector256.Create(int.MaxValue);
        var vmax = Vector256.Create(int.MinValue);
        for (var i = 0; i < _data.Length; i += Vector256<int>.Count)
        {
            var v = Vector256.Create(_data, i);
            var mask1 = Vector256.LessThan(v, vmin);
            var mask2 = Vector256.GreaterThan(v, vmax);
            vmin = Vector256.ConditionalSelect(mask1, v, vmin);
            vmax = Vector256.ConditionalSelect(mask2, v, vmax);
        }
        int min = int.MaxValue, max = int.MinValue;
        for (var i = 0; i < Vector256<int>.Count; ++i)
        {
            min = Math.Min(min, vmin[i]);
            max = Math.Max(max, vmax[i]);
        }
        return (min, max);
        //System.Runtime.Intrinsics

        // https://source.dot.net/#System.Linq/System/Linq/MaxMin.cs,22
        // https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs,160
    }
}