using BenchmarkDotNet.Attributes;
using System.Runtime.CompilerServices;

namespace AyCode.Benchmark;

/// <summary>
/// Benchmarks comparing value-by-copy vs 'in' parameter passing for large value types.
/// Tests decimal (16 bytes), DateTimeOffset (16 bytes), and Guid (16 bytes).
/// </summary>
[MemoryDiagnoser]
public class ValueTypePassingBenchmark
{
    private decimal _decimal;
    private DateTimeOffset _dateTimeOffset;
    private Guid _guid;
    private byte[] _buffer = null !;
    private int _position;
    [GlobalSetup]
    public void Setup()
    {
        _decimal = 12345.6789m;
        _dateTimeOffset = DateTimeOffset.Now;
        _guid = Guid.NewGuid();
        _buffer = new byte[1024];
    }

    // ============ DECIMAL (16 bytes) ============
    [Benchmark]
    public void WriteDecimal_ByValue()
    {
        _position = 0;
        for (int i = 0; i < 1000; i++)
        {
            WriteDecimalByValue(_decimal);
        }
    }

    [Benchmark]
    public void WriteDecimal_ByIn()
    {
        _position = 0;
        for (int i = 0; i < 1000; i++)
        {
            WriteDecimalByIn(in _decimal);
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void WriteDecimalByValue(decimal value)
    {
        Span<int> bits = stackalloc int[4];
        decimal.TryGetBits(value, bits, out _);
        System.Runtime.InteropServices.MemoryMarshal.AsBytes(bits).CopyTo(_buffer.AsSpan(_position, 16));
        _position += 16;
        if (_position > 900)
            _position = 0;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void WriteDecimalByIn(in decimal value)
    {
        Span<int> bits = stackalloc int[4];
        decimal.TryGetBits(value, bits, out _);
        System.Runtime.InteropServices.MemoryMarshal.AsBytes(bits).CopyTo(_buffer.AsSpan(_position, 16));
        _position += 16;
        if (_position > 900)
            _position = 0;
    }

    // ============ DATETIMEOFFSET (16 bytes) ============
    [Benchmark]
    public void WriteDateTimeOffset_ByValue()
    {
        _position = 0;
        for (int i = 0; i < 1000; i++)
        {
            WriteDateTimeOffsetByValue(_dateTimeOffset);
        }
    }

    [Benchmark]
    public void WriteDateTimeOffset_ByIn()
    {
        _position = 0;
        for (int i = 0; i < 1000; i++)
        {
            WriteDateTimeOffsetByIn(in _dateTimeOffset);
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void WriteDateTimeOffsetByValue(DateTimeOffset value)
    {
        Unsafe.WriteUnaligned(ref _buffer[_position], value.UtcTicks);
        Unsafe.WriteUnaligned(ref _buffer[_position + 8], (short)value.Offset.TotalMinutes);
        _position += 10;
        if (_position > 900)
            _position = 0;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void WriteDateTimeOffsetByIn(in DateTimeOffset value)
    {
        Unsafe.WriteUnaligned(ref _buffer[_position], value.UtcTicks);
        Unsafe.WriteUnaligned(ref _buffer[_position + 8], (short)value.Offset.TotalMinutes);
        _position += 10;
        if (_position > 900)
            _position = 0;
    }

    // ============ GUID (16 bytes) ============
    [Benchmark]
    public void WriteGuid_ByValue()
    {
        _position = 0;
        for (int i = 0; i < 1000; i++)
        {
            WriteGuidByValue(_guid);
        }
    }

    [Benchmark]
    public void WriteGuid_ByIn()
    {
        _position = 0;
        for (int i = 0; i < 1000; i++)
        {
            WriteGuidByIn(in _guid);
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void WriteGuidByValue(Guid value)
    {
        value.TryWriteBytes(_buffer.AsSpan(_position, 16));
        _position += 16;
        if (_position > 900)
            _position = 0;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void WriteGuidByIn(in Guid value)
    {
        value.TryWriteBytes(_buffer.AsSpan(_position, 16));
        _position += 16;
        if (_position > 900)
            _position = 0;
    }
}