// Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License.

using System.Collections.Concurrent;
using System.Collections.Immutable;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Order;

namespace Benchmarks;

[Config(typeof(GenericConfig))]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
public class DataCacheBenchmark
{
    private ConcurrentDictionary<int, int> _concurrentDictionary;
    private Dictionary<int, int> _dictionary;
    private HashSet<int> _hashSet;
    private ImmutableHashSet<int> _immutableHashSet;
    [Params(1000, 100000, 1000000)] public int N;

    [GlobalSetup]
    public void Setup()
    {
        _hashSet = new HashSet<int>();
        _dictionary = new Dictionary<int, int>();
        _concurrentDictionary = new ConcurrentDictionary<int, int>();
        _immutableHashSet = ImmutableHashSet<int>.Empty;

        _hashSet.Clear();
        _dictionary.Clear();
        _concurrentDictionary.Clear();
        _immutableHashSet = _immutableHashSet.Clear();
        _hashSet.EnsureCapacity(N);
        _dictionary.EnsureCapacity(N);

        for (var i = 0; i < N; i++)
        {
            _immutableHashSet = _immutableHashSet.Add(i);
            _hashSet.Add(i);
            _dictionary.Add(i, i);
            _concurrentDictionary.TryAdd(i, i);
        }
    }

    [Benchmark]
    public void HashSetAdd()
    {
        ParallelEnumerable.Range(0, N).AsParallel().ForAll(i => _hashSet.Add(i));
    }

    [Benchmark]
    public void DictionaryAdd()
    {
        ParallelEnumerable.Range(0, N).AsParallel().ForAll(i => _dictionary.Add(N + i, i));
    }

    [Benchmark]
    public void ConcurrentDictionaryAddOrUpdate()
    {
        ParallelEnumerable.Range(0, N).AsParallel().ForAll(i =>
            _concurrentDictionary.AddOrUpdate(N + i, i, (key, oldValue) => oldValue + i));
    }

    [Benchmark]
    public void ImmutableHashSetLookup()
    {
        ParallelEnumerable.Range(0, N).AsParallel().ForAll(i => _immutableHashSet.Contains(i));
    }

    [Benchmark]
    public void HashSetLookup()
    {
        ParallelEnumerable.Range(0, N).AsParallel().ForAll(i => _hashSet.Contains(i));
    }

    [Benchmark]
    public void DictionaryLookup()
    {
        ParallelEnumerable.Range(0, N).AsParallel().ForAll(i => _dictionary.ContainsKey(i));
    }

    [Benchmark]
    public void ConcurrentDictionaryLookup()
    {
        ParallelEnumerable.Range(0, N).AsParallel().ForAll(i => _concurrentDictionary.ContainsKey(i));
    }
}