﻿using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace hactoolnet
{
    internal class MultiBenchmark
    {
        public int DefaultRunsNeeded { get; set; } = 500;

        private List<BenchmarkItem> Benchmarks { get; } = new List<BenchmarkItem>();

        public void Register(string name, Action setupAction, Action runAction, Func<double, string> resultPrinter, int runsNeeded = -1)
        {
            var benchmark = new BenchmarkItem
            {
                Name = name,
                Setup = setupAction,
                Run = runAction,
                PrintResult = resultPrinter,
                RunsNeeded = runsNeeded == -1 ? DefaultRunsNeeded : runsNeeded
            };

            Benchmarks.Add(benchmark);
        }

        public void Run()
        {
            foreach (BenchmarkItem item in Benchmarks)
            {
                RunBenchmark(item);

                Console.WriteLine($"{item.Name}: {item.Result}");
            }
        }

        private void RunBenchmark(BenchmarkItem item)
        {
            double fastestRun = double.MaxValue;
            var watch = new Stopwatch();

            int runsSinceLastBest = 0;

            while (runsSinceLastBest < item.RunsNeeded)
            {
                runsSinceLastBest++;
                item.Setup();

                watch.Restart();
                item.Run();
                watch.Stop();

                if (fastestRun > watch.Elapsed.TotalSeconds)
                {
                    fastestRun = watch.Elapsed.TotalSeconds;

                    runsSinceLastBest = 0;
                }
            }

            item.Time = fastestRun;
            item.Result = item.PrintResult(item.Time);
        }

        private class BenchmarkItem
        {
            public string Name { get; set; }
            public int RunsNeeded { get; set; }
            public double Time { get; set; }
            public string Result { get; set; }

            public Action Setup { get; set; }
            public Action Run { get; set; }
            public Func<double, string> PrintResult { get; set; }
        }
    }
}
