// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics.Metrics;
using BenchmarkDotNet.Attributes;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Tests;

/*
BenchmarkDotNet v0.13.10, Windows 11 (10.0.23424.1000)
Intel Core i7-9700 CPU 3.00GHz, 1 CPU, 8 logical and 8 physical cores
.NET SDK 8.0.100
  [Host]     : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2
  DefaultJob : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2


| Method  | UseWithRef | Mean     | Error    | StdDev   | Allocated |
|-------- |----------- |---------:|---------:|---------:|----------:|
| Collect | False      | 21.03 us | 0.148 us | 0.361 us |      96 B |
| Collect | True       | 20.37 us | 0.399 us | 0.559 us |      96 B |
*/

namespace Benchmarks.Metrics;

public class MetricCollectBenchmarks
{
    private readonly string[] dimensionValues = new string[] { "DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10" };

    // TODO: Confirm if this needs to be thread-safe
    private readonly Random random = new();
    private Counter<double>? counter;
    private MeterProvider? provider;
    private Meter? meter;
    private CancellationTokenSource? token;
    private BaseExportingMetricReader? reader;
    private Task? writeMetricTask;

    [Params(false, true)]
    public bool UseWithRef { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        var metricExporter = new TestExporter<Metric>(ProcessExport);
        void ProcessExport(Batch<Metric> batch)
        {
            double sum = 0;
            foreach (var metric in batch)
            {
                if (this.UseWithRef)
                {
                    // The performant way of iterating.
                    foreach (ref readonly var metricPoint in metric.GetMetricPoints())
                    {
                        sum += metricPoint.GetSumDouble();
                    }
                }
                else
                {
                    // The non-performant way of iterating.
                    // This is still "correct", but less performant.
                    foreach (var metricPoint in metric.GetMetricPoints())
                    {
                        sum += metricPoint.GetSumDouble();
                    }
                }
            }
        }

        this.reader = new BaseExportingMetricReader(metricExporter)
        {
            TemporalityPreference = MetricReaderTemporalityPreference.Cumulative,
        };

        this.meter = new Meter(Utils.GetCurrentMethodName());

        this.provider = Sdk.CreateMeterProviderBuilder()
            .AddMeter(this.meter.Name)
            .AddReader(this.reader)
            .Build();

        this.counter = this.meter.CreateCounter<double>("counter");
        this.token = new CancellationTokenSource();
        this.writeMetricTask = new Task(() =>
        {
            while (!this.token.IsCancellationRequested)
            {
                var tag1 = new KeyValuePair<string, object?>("DimName1", this.dimensionValues[this.random.Next(0, 10)]);
                var tag2 = new KeyValuePair<string, object?>("DimName2", this.dimensionValues[this.random.Next(0, 10)]);
                var tag3 = new KeyValuePair<string, object?>("DimName3", this.dimensionValues[this.random.Next(0, 10)]);
                this.counter.Add(100.00, tag1, tag2, tag3);
            }
        });
        this.writeMetricTask.Start();
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        this.token?.Cancel();
        this.token?.Dispose();
        this.writeMetricTask?.Wait();
        this.meter?.Dispose();
        this.provider?.Dispose();
    }

    [Benchmark]
    public void Collect()
    {
        this.reader!.Collect();
    }
}
